diff options
Diffstat (limited to '')
-rw-r--r-- | bin/.gitignore | 36 | ||||
-rw-r--r-- | bin/1sh/.gitignore | 13 | ||||
-rw-r--r-- | bin/1sh/1sh-kill.1 | 157 | ||||
-rw-r--r-- | bin/1sh/1sh-printf.1 | 385 | ||||
-rw-r--r-- | bin/1sh/1sh-test.1 | 395 | ||||
-rw-r--r-- | bin/1sh/1sh.1 | 2917 | ||||
-rw-r--r-- | bin/1sh/Makefile | 84 | ||||
-rw-r--r-- | bin/1sh/TOUR | 301 | ||||
-rw-r--r-- | bin/1sh/alias.c | 256 | ||||
-rw-r--r-- | bin/1sh/alias.h | 45 | ||||
-rw-r--r-- | bin/1sh/arith.h | 37 | ||||
-rw-r--r-- | bin/1sh/arith_yacc.c | 381 | ||||
-rw-r--r-- | bin/1sh/arith_yacc.h | 94 | ||||
-rw-r--r-- | bin/1sh/arith_yylex.c | 276 | ||||
-rw-r--r-- | bin/1sh/bltin.h | 81 | ||||
-rw-r--r-- | bin/1sh/builtins.def | 96 | ||||
-rw-r--r-- | bin/1sh/cd.c | 430 | ||||
-rw-r--r-- | bin/1sh/cd.h | 32 | ||||
-rw-r--r-- | bin/1sh/echo.c | 109 | ||||
-rw-r--r-- | bin/1sh/error.c | 199 | ||||
-rw-r--r-- | bin/1sh/error.h | 95 | ||||
-rw-r--r-- | bin/1sh/eval.c | 1381 | ||||
-rw-r--r-- | bin/1sh/eval.h | 70 | ||||
-rw-r--r-- | bin/1sh/exec.c | 784 | ||||
-rw-r--r-- | bin/1sh/exec.h | 76 | ||||
-rw-r--r-- | bin/1sh/expand.c | 1553 | ||||
-rw-r--r-- | bin/1sh/expand.h | 62 | ||||
-rw-r--r-- | bin/1sh/histedit.c | 558 | ||||
-rw-r--r-- | bin/1sh/input.c | 520 | ||||
-rw-r--r-- | bin/1sh/input.h | 65 | ||||
-rw-r--r-- | bin/1sh/jobs.c | 1569 | ||||
-rw-r--r-- | bin/1sh/jobs.h | 67 | ||||
-rw-r--r-- | bin/1sh/kill.c | 215 | ||||
-rw-r--r-- | bin/1sh/mail.c | 120 | ||||
-rw-r--r-- | bin/1sh/mail.h | 38 | ||||
-rw-r--r-- | bin/1sh/main.c | 356 | ||||
-rw-r--r-- | bin/1sh/main.h | 42 | ||||
-rw-r--r-- | bin/1sh/memalloc.c | 344 | ||||
-rw-r--r-- | bin/1sh/memalloc.h | 88 | ||||
-rw-r--r-- | bin/1sh/miscbltin.c | 534 | ||||
-rwxr-xr-x | bin/1sh/mkbuiltins | 137 | ||||
-rw-r--r-- | bin/1sh/mknodes.c | 461 | ||||
-rw-r--r-- | bin/1sh/mksyntax.c | 332 | ||||
-rw-r--r-- | bin/1sh/mktokens | 93 | ||||
-rw-r--r-- | bin/1sh/myhistedit.h | 45 | ||||
-rw-r--r-- | bin/1sh/mystring.c | 100 | ||||
-rw-r--r-- | bin/1sh/mystring.h | 43 | ||||
-rw-r--r-- | bin/1sh/nodes.c.pat | 193 | ||||
-rw-r--r-- | bin/1sh/nodetypes | 145 | ||||
-rw-r--r-- | bin/1sh/options.c | 594 | ||||
-rw-r--r-- | bin/1sh/options.h | 117 | ||||
-rw-r--r-- | bin/1sh/output.c | 376 | ||||
-rw-r--r-- | bin/1sh/output.h | 87 | ||||
-rw-r--r-- | bin/1sh/parser.c | 2182 | ||||
-rw-r--r-- | bin/1sh/parser.h | 88 | ||||
-rw-r--r-- | bin/1sh/printf.c | 686 | ||||
-rw-r--r-- | bin/1sh/redir.c | 365 | ||||
-rw-r--r-- | bin/1sh/redir.h | 47 | ||||
-rw-r--r-- | bin/1sh/shell.h | 77 | ||||
-rw-r--r-- | bin/1sh/show.c | 410 | ||||
-rw-r--r-- | bin/1sh/show.h | 42 | ||||
-rw-r--r-- | bin/1sh/test.c | 630 | ||||
-rw-r--r-- | bin/1sh/trap.c | 553 | ||||
-rw-r--r-- | bin/1sh/trap.h | 50 | ||||
-rw-r--r-- | bin/1sh/var.c | 983 | ||||
-rw-r--r-- | bin/1sh/var.h | 140 | ||||
-rw-r--r-- | bin/LICENSE | 661 | ||||
-rw-r--r-- | bin/Makefile | 153 | ||||
-rw-r--r-- | bin/README.7 | 83 | ||||
-rw-r--r-- | bin/beef.c | 141 | ||||
-rw-r--r-- | bin/bibsort.pl | 66 | ||||
-rw-r--r-- | bin/bit.y | 194 | ||||
-rw-r--r-- | bin/bri.c | 85 | ||||
-rw-r--r-- | bin/c.sh | 87 | ||||
-rw-r--r-- | bin/dash/.gitignore | 42 | ||||
-rw-r--r-- | bin/dash/COPYING (renamed from COPYING) | 0 | ||||
-rw-r--r-- | bin/dash/ChangeLog (renamed from ChangeLog) | 0 | ||||
-rw-r--r-- | bin/dash/ChangeLog.O (renamed from ChangeLog.O) | 0 | ||||
-rw-r--r-- | bin/dash/Makefile.am (renamed from Makefile.am) | 0 | ||||
-rwxr-xr-x | bin/dash/autogen.sh (renamed from autogen.sh) | 0 | ||||
-rw-r--r-- | bin/dash/configure.ac (renamed from configure.ac) | 0 | ||||
-rw-r--r-- | bin/dash/src/.gitignore (renamed from src/.gitignore) | 0 | ||||
-rw-r--r-- | bin/dash/src/Makefile.am (renamed from src/Makefile.am) | 0 | ||||
-rw-r--r-- | bin/dash/src/TOUR (renamed from src/TOUR) | 0 | ||||
-rw-r--r-- | bin/dash/src/alias.c (renamed from src/alias.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/alias.h (renamed from src/alias.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/arith_yacc.c (renamed from src/arith_yacc.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/arith_yacc.h (renamed from src/arith_yacc.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/arith_yylex.c (renamed from src/arith_yylex.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/bltin/bltin.h (renamed from src/bltin/bltin.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/bltin/echo.1 (renamed from src/bltin/echo.1) | 0 | ||||
-rw-r--r-- | bin/dash/src/bltin/printf.1 (renamed from src/bltin/printf.1) | 0 | ||||
-rw-r--r-- | bin/dash/src/bltin/printf.c (renamed from src/bltin/printf.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/bltin/test.1 (renamed from src/bltin/test.1) | 0 | ||||
-rw-r--r-- | bin/dash/src/bltin/test.c (renamed from src/bltin/test.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/bltin/times.c (renamed from src/bltin/times.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/builtins.def.in (renamed from src/builtins.def.in) | 0 | ||||
-rw-r--r-- | bin/dash/src/cd.c (renamed from src/cd.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/cd.h (renamed from src/cd.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/dash.1 (renamed from src/dash.1) | 0 | ||||
-rw-r--r-- | bin/dash/src/error.c (renamed from src/error.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/error.h (renamed from src/error.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/eval.c (renamed from src/eval.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/eval.h (renamed from src/eval.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/exec.c (renamed from src/exec.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/exec.h (renamed from src/exec.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/expand.c (renamed from src/expand.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/expand.h (renamed from src/expand.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/cmv (renamed from src/funcs/cmv) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/dirs (renamed from src/funcs/dirs) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/kill (renamed from src/funcs/kill) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/login (renamed from src/funcs/login) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/newgrp (renamed from src/funcs/newgrp) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/popd (renamed from src/funcs/popd) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/pushd (renamed from src/funcs/pushd) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/suspend (renamed from src/funcs/suspend) | 0 | ||||
-rw-r--r-- | bin/dash/src/histedit.c (renamed from src/histedit.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/init.h (renamed from src/init.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/input.c (renamed from src/input.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/input.h (renamed from src/input.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/jobs.c (renamed from src/jobs.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/jobs.h (renamed from src/jobs.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/machdep.h (renamed from src/machdep.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/mail.c (renamed from src/mail.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/mail.h (renamed from src/mail.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/main.c (renamed from src/main.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/main.h (renamed from src/main.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/memalloc.c (renamed from src/memalloc.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/memalloc.h (renamed from src/memalloc.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/miscbltin.c (renamed from src/miscbltin.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/miscbltin.h (renamed from src/miscbltin.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/mkbuiltins (renamed from src/mkbuiltins) | 0 | ||||
-rw-r--r-- | bin/dash/src/mkinit.c (renamed from src/mkinit.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/mknodes.c (renamed from src/mknodes.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/mksignames.c (renamed from src/mksignames.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/mksyntax.c (renamed from src/mksyntax.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/mktokens (renamed from src/mktokens) | 0 | ||||
-rw-r--r-- | bin/dash/src/myhistedit.h (renamed from src/myhistedit.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/mystring.c (renamed from src/mystring.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/mystring.h (renamed from src/mystring.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/nodes.c.pat (renamed from src/nodes.c.pat) | 0 | ||||
-rw-r--r-- | bin/dash/src/nodetypes (renamed from src/nodetypes) | 0 | ||||
-rw-r--r-- | bin/dash/src/options.c (renamed from src/options.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/options.h (renamed from src/options.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/output.c (renamed from src/output.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/output.h (renamed from src/output.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/parser.c (renamed from src/parser.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/parser.h (renamed from src/parser.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/redir.c (renamed from src/redir.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/redir.h (renamed from src/redir.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/shell.h (renamed from src/shell.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/show.c (renamed from src/show.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/show.h (renamed from src/show.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/system.c (renamed from src/system.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/system.h (renamed from src/system.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/trap.c (renamed from src/trap.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/trap.h (renamed from src/trap.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/var.c (renamed from src/var.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/var.h (renamed from src/var.h) | 0 | ||||
-rw-r--r-- | bin/dtch.c | 271 | ||||
-rw-r--r-- | bin/ever.c | 116 | ||||
-rw-r--r-- | bin/fbatt.c | 123 | ||||
-rw-r--r-- | bin/fbclock.c | 132 | ||||
-rw-r--r-- | bin/glitch.c | 538 | ||||
-rw-r--r-- | bin/hi.c | 766 | ||||
-rw-r--r-- | bin/hnel.c | 120 | ||||
-rw-r--r-- | bin/html.sh | 41 | ||||
-rw-r--r-- | bin/man1/beef.1 | 91 | ||||
-rw-r--r-- | bin/man1/bibsort.1 | 37 | ||||
-rw-r--r-- | bin/man1/bit.1 | 45 | ||||
-rw-r--r-- | bin/man1/bri.1 | 44 | ||||
-rw-r--r-- | bin/man1/c.1 | 40 | ||||
-rw-r--r-- | bin/man1/dtch.1 | 67 | ||||
-rw-r--r-- | bin/man1/ever.1 | 49 | ||||
-rw-r--r-- | bin/man1/fbatt.1 | 34 | ||||
-rw-r--r-- | bin/man1/fbclock.1 | 36 | ||||
-rw-r--r-- | bin/man1/glitch.1 | 77 | ||||
-rw-r--r-- | bin/man1/hi.1 | 173 | ||||
-rw-r--r-- | bin/man1/hnel.1 | 36 | ||||
-rw-r--r-- | bin/man1/modem.1 | 31 | ||||
-rw-r--r-- | bin/man1/nudge.1 | 44 | ||||
-rw-r--r-- | bin/man1/order.1 | 38 | ||||
-rw-r--r-- | bin/man1/pbd.1 | 59 | ||||
-rw-r--r-- | bin/man1/pngo.1 | 56 | ||||
-rw-r--r-- | bin/man1/psf2png.1 | 53 | ||||
-rw-r--r-- | bin/man1/psfed.1 | 166 | ||||
-rw-r--r-- | bin/man1/ptee.1 | 40 | ||||
-rw-r--r-- | bin/man1/relay.1 | 48 | ||||
-rw-r--r-- | bin/man1/scheme.1 | 56 | ||||
-rw-r--r-- | bin/man1/shotty.1 | 92 | ||||
-rw-r--r-- | bin/man1/title.1 | 51 | ||||
-rw-r--r-- | bin/man1/ttpre.1 | 32 | ||||
-rw-r--r-- | bin/man1/up.1 | 76 | ||||
-rw-r--r-- | bin/man1/when.1 | 76 | ||||
-rw-r--r-- | bin/man1/xx.1 | 68 | ||||
-rw-r--r-- | bin/man3/png.3 | 90 | ||||
-rw-r--r-- | bin/modem.c | 102 | ||||
-rw-r--r-- | bin/nudge.c | 78 | ||||
-rw-r--r-- | bin/order.y | 193 | ||||
-rw-r--r-- | bin/pbd.c | 152 | ||||
-rw-r--r-- | bin/png.h | 108 | ||||
-rw-r--r-- | bin/pngo.c | 812 | ||||
-rw-r--r-- | bin/psf2png.c | 107 | ||||
-rw-r--r-- | bin/psfed.c | 577 | ||||
-rw-r--r-- | bin/ptee.c | 116 | ||||
-rw-r--r-- | bin/relay.c | 218 | ||||
-rw-r--r-- | bin/scheme.c | 256 | ||||
-rw-r--r-- | bin/shotty.c | 648 | ||||
-rw-r--r-- | bin/title.c | 211 | ||||
-rw-r--r-- | bin/ttpre.c | 66 | ||||
-rw-r--r-- | bin/up.sh | 76 | ||||
-rw-r--r-- | bin/when.y | 250 | ||||
-rw-r--r-- | bin/xx.c | 142 |
213 files changed, 33373 insertions, 0 deletions
diff --git a/bin/.gitignore b/bin/.gitignore new file mode 100644 index 00000000..df322fd4 --- /dev/null +++ b/bin/.gitignore @@ -0,0 +1,36 @@ +*.html +*.o +beef +bibsort +bit +bri +c +config.mk +dtch +ever +fbatt +fbclock +glitch +hi +hnel +modem +nudge +open +order +pbcopy +pbd +pbpaste +pngo +psf2png +psfed +ptee +relay +scheme +scheme.h +shotty +tags +title +ttpre +up +when +xx diff --git a/bin/1sh/.gitignore b/bin/1sh/.gitignore new file mode 100644 index 00000000..54db1cf1 --- /dev/null +++ b/bin/1sh/.gitignore @@ -0,0 +1,13 @@ +*.o +.depend +1sh +builtins.c +builtins.h +config.mk +mknodes +mksyntax +nodes.c +nodes.h +syntax.c +syntax.h +token.h diff --git a/bin/1sh/1sh-kill.1 b/bin/1sh/1sh-kill.1 new file mode 100644 index 00000000..110fab7d --- /dev/null +++ b/bin/1sh/1sh-kill.1 @@ -0,0 +1,157 @@ +.\"- +.\" Copyright (c) 1980, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)kill.1 8.2 (Berkeley) 4/28/95 +.\" $FreeBSD: releng/12.1/bin/kill/kill.1 314436 2017-02-28 23:42:47Z imp $ +.\" +.Dd October 3, 2016 +.Dt 1SH-KILL 1 +.Os +.Sh NAME +.Nm kill +.Nd terminate or signal a process +.Sh SYNOPSIS +.Nm +.Op Fl s Ar signal_name +.Ar pid ... +.Nm +.Fl l +.Op Ar exit_status +.Nm +.Fl Ar signal_name +.Ar pid ... +.Nm +.Fl Ar signal_number +.Ar pid ... +.Sh DESCRIPTION +The +.Nm +utility sends a signal to the processes specified by the +.Ar pid +operands. +.Pp +Only the super-user may send signals to other users' processes. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl s Ar signal_name +A symbolic signal name specifying the signal to be sent instead of the +default +.Dv TERM . +.It Fl l Op Ar exit_status +If no operand is given, list the signal names; otherwise, write +the signal name corresponding to +.Ar exit_status . +.It Fl Ar signal_name +A symbolic signal name specifying the signal to be sent instead of the +default +.Dv TERM . +.It Fl Ar signal_number +A non-negative decimal integer, specifying the signal to be sent instead +of the default +.Dv TERM . +.El +.Pp +The following PIDs have special meanings: +.Bl -tag -width indent +.It -1 +If superuser, broadcast the signal to all processes; otherwise broadcast +to all processes belonging to the user. +.El +.Pp +Some of the more commonly used signals: +.Pp +.Bl -tag -width indent -compact +.It 1 +HUP (hang up) +.It 2 +INT (interrupt) +.It 3 +QUIT (quit) +.It 6 +ABRT (abort) +.It 9 +KILL (non-catchable, non-ignorable kill) +.It 14 +ALRM (alarm clock) +.It 15 +TERM (software termination signal) +.El +.Pp +Some shells may provide a builtin +.Nm +command which is similar or identical to this utility. +Consult the +.Xr builtin 1 +manual page. +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Terminate +the processes with PIDs 142 and 157: +.Pp +.Dl "kill 142 157" +.Pp +Send the hangup signal +.Pq Dv SIGHUP +to the process with PID 507: +.Pp +.Dl "kill -s HUP 507" +.Pp +Terminate the process group with PGID 117: +.Pp +.Dl "kill -- -117" +.Sh SEE ALSO +.Xr builtin 1 , +.Xr csh 1 , +.Xr killall 1 , +.Xr ps 1 , +.Xr sh 1 , +.Xr kill 2 , +.Xr sigaction 2 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. +.Sh HISTORY +A +.Nm +command appeared in +.At v3 +in section 8 of the manual. +.Sh BUGS +A replacement for the command +.Dq Li kill 0 +for +.Xr csh 1 +users should be provided. diff --git a/bin/1sh/1sh-printf.1 b/bin/1sh/1sh-printf.1 new file mode 100644 index 00000000..33c4b9f5 --- /dev/null +++ b/bin/1sh/1sh-printf.1 @@ -0,0 +1,385 @@ +.\" Copyright (c) 1989, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)printf.1 8.1 (Berkeley) 6/6/93 +.\" $FreeBSD: releng/12.1/usr.bin/printf/printf.1 350613 2019-08-05 20:19:38Z jilles $ +.\" +.Dd July 29, 2019 +.Dt 1SH-PRINTF 1 +.Os +.Sh NAME +.Nm printf +.Nd formatted output +.Sh SYNOPSIS +.Nm +.Ar format Op Ar arguments ... +.Sh DESCRIPTION +The +.Nm +utility formats and prints its arguments, after the first, under control +of the +.Ar format . +The +.Ar format +is a character string which contains three types of objects: plain characters, +which are simply copied to standard output, character escape sequences which +are converted and copied to the standard output, and format specifications, +each of which causes printing of the next successive +.Ar argument . +.Pp +The +.Ar arguments +after the first are treated as strings if the corresponding format is +either +.Cm c , b +or +.Cm s ; +otherwise it is evaluated as a C constant, with the following extensions: +.Pp +.Bl -bullet -offset indent -compact +.It +A leading plus or minus sign is allowed. +.It +If the leading character is a single or double quote, the value is the +character code of the next character. +.El +.Pp +The format string is reused as often as necessary to satisfy the +.Ar arguments . +Any extra format specifications are evaluated with zero or the null +string. +.Pp +Character escape sequences are in backslash notation as defined in the +.St -ansiC , +with extensions. +The characters and their meanings +are as follows: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It Cm \ea +Write a <bell> character. +.It Cm \eb +Write a <backspace> character. +.It Cm \ef +Write a <form-feed> character. +.It Cm \en +Write a <new-line> character. +.It Cm \er +Write a <carriage return> character. +.It Cm \et +Write a <tab> character. +.It Cm \ev +Write a <vertical tab> character. +.It Cm \e\' +Write a <single quote> character. +.It Cm \e\e +Write a backslash character. +.It Cm \e Ns Ar num +Write a byte whose +value is the 1-, 2-, or 3-digit +octal number +.Ar num . +Multibyte characters can be constructed using multiple +.Cm \e Ns Ar num +sequences. +.El +.Pp +Each format specification is introduced by the percent character +(``%''). +The remainder of the format specification includes, +in the following order: +.Bl -tag -width Ds +.It "Zero or more of the following flags:" +.Bl -tag -width Ds +.It Cm # +A `#' character +specifying that the value should be printed in an ``alternate form''. +For +.Cm b , c , d , s +and +.Cm u +formats, this option has no effect. +For the +.Cm o +formats the precision of the number is increased to force the first +character of the output string to a zero. +For the +.Cm x +.Pq Cm X +format, a non-zero result has the string +.Li 0x +.Pq Li 0X +prepended to it. +For +.Cm a , A , e , E , f , F , g +and +.Cm G +formats, the result will always contain a decimal point, even if no +digits follow the point (normally, a decimal point only appears in the +results of those formats if a digit follows the decimal point). +For +.Cm g +and +.Cm G +formats, trailing zeros are not removed from the result as they +would otherwise be; +.It Cm \&\- +A minus sign `\-' which specifies +.Em left adjustment +of the output in the indicated field; +.It Cm \&+ +A `+' character specifying that there should always be +a sign placed before the number when using signed formats. +.It Sq \&\ \& +A space specifying that a blank should be left before a positive number +for a signed format. +A `+' overrides a space if both are used; +.It Cm \&0 +A zero `0' character indicating that zero-padding should be used +rather than blank-padding. +A `\-' overrides a `0' if both are used; +.El +.It "Field Width:" +An optional digit string specifying a +.Em field width ; +if the output string has fewer bytes than the field width it will +be blank-padded on the left (or right, if the left-adjustment indicator +has been given) to make up the field width (note that a leading zero +is a flag, but an embedded zero is part of a field width); +.It Precision: +An optional period, +.Sq Cm \&.\& , +followed by an optional digit string giving a +.Em precision +which specifies the number of digits to appear after the decimal point, +for +.Cm e +and +.Cm f +formats, or the maximum number of bytes to be printed +from a string; if the digit string is missing, the precision is treated +as zero; +.It Format: +A character which indicates the type of format to use (one of +.Cm diouxXfFeEgGaAcsb ) . +The uppercase formats differ from their lowercase counterparts only in +that the output of the former is entirely in uppercase. +The floating-point format specifiers +.Pq Cm fFeEgGaA +may be prefixed by an +.Cm L +to request that additional precision be used, if available. +.El +.Pp +A field width or precision may be +.Sq Cm \&* +instead of a digit string. +In this case an +.Ar argument +supplies the field width or precision. +.Pp +The format characters and their meanings are: +.Bl -tag -width Fl +.It Cm diouXx +The +.Ar argument +is printed as a signed decimal (d or i), unsigned octal, unsigned decimal, +or unsigned hexadecimal (X or x), respectively. +.It Cm fF +The +.Ar argument +is printed in the style `[\-]ddd.ddd' where the number of d's +after the decimal point is equal to the precision specification for +the argument. +If the precision is missing, 6 digits are given; if the precision +is explicitly 0, no digits and no decimal point are printed. +The values \*[If] and \*[Na] are printed as +.Ql inf +and +.Ql nan , +respectively. +.It Cm eE +The +.Ar argument +is printed in the style +.Cm e +.Sm off +.Sq Op - Ar d.ddd No \(+- Ar dd +.Sm on +where there +is one digit before the decimal point and the number after is equal to +the precision specification for the argument; when the precision is +missing, 6 digits are produced. +The values \*[If] and \*[Na] are printed as +.Ql inf +and +.Ql nan , +respectively. +.It Cm gG +The +.Ar argument +is printed in style +.Cm f +.Pq Cm F +or in style +.Cm e +.Pq Cm E +whichever gives full precision in minimum space. +.It Cm aA +The +.Ar argument +is printed in style +.Sm off +.Sq Op - Ar h.hhh No \(+- Li p Ar d +.Sm on +where there is one digit before the hexadecimal point and the number +after is equal to the precision specification for the argument; +when the precision is missing, enough digits are produced to convey +the argument's exact double-precision floating-point representation. +The values \*[If] and \*[Na] are printed as +.Ql inf +and +.Ql nan , +respectively. +.It Cm c +The first byte of +.Ar argument +is printed. +.It Cm s +Bytes from the string +.Ar argument +are printed until the end is reached or until the number of bytes +indicated by the precision specification is reached; however if the +precision is 0 or missing, the string is printed entirely. +.It Cm b +As for +.Cm s , +but interpret character escapes in backslash notation in the string +.Ar argument . +The permitted escape sequences are slightly different in that +octal escapes are +.Cm \e0 Ns Ar num +instead of +.Cm \e Ns Ar num +and that an additional escape sequence +.Cm \ec +stops further output from this +.Nm +invocation. +.It Cm n$ +Allows reordering of the output according to +.Ar argument . +.It Cm \&% +Print a `%'; no argument is used. +.El +.Pp +The decimal point +character is defined in the program's locale (category +.Dv LC_NUMERIC ) . +.Pp +In no case does a non-existent or small field width cause truncation of +a field; padding takes place only if the specified field width exceeds +the actual width. +.Pp +Some shells may provide a builtin +.Nm +command which is similar or identical to this utility. +Consult the +.Xr builtin 1 +manual page. +.Sh EXIT STATUS +.Ex -std +.Sh COMPATIBILITY +The traditional +.Bx +behavior of converting arguments of numeric formats not beginning +with a digit to the +.Tn ASCII +code of the first character is not supported. +.Sh SEE ALSO +.Xr builtin 1 , +.Xr echo 1 , +.Xr sh 1 , +.Xr printf 3 +.Sh STANDARDS +The +.Nm +command is expected to be compatible with the +.St -p1003.2 +specification. +.Sh HISTORY +The +.Nm +command appeared in +.Bx 4.3 Reno . +It is modeled +after the standard library function, +.Xr printf 3 . +.Sh CAVEATS +.Tn ANSI +hexadecimal character constants were deliberately not provided. +.Pp +Trying to print a dash ("-") as the first character causes +.Nm +to interpret the dash as a program argument. +.Nm -- +must be used before +.Ar format . +.Pp +If the locale contains multibyte characters +(such as UTF-8), +the +.Cm c +format and +.Cm b +and +.Cm s +formats with a precision +may not operate as expected. +.Sh BUGS +Since the floating point numbers are translated from +.Tn ASCII +to floating-point and +then back again, floating-point precision may be lost. +(By default, the number is translated to an IEEE-754 double-precision +value before being printed. +The +.Cm L +modifier may produce additional precision, depending on the hardware platform.) +.Pp +The escape sequence \e000 is the string terminator. +When present in the argument for the +.Cm b +format, the argument will be truncated at the \e000 character. +.Pp +Multibyte characters are not recognized in format strings (this is only +a problem if +.Ql % +can appear inside a multibyte character). diff --git a/bin/1sh/1sh-test.1 b/bin/1sh/1sh-test.1 new file mode 100644 index 00000000..8ad8d0f4 --- /dev/null +++ b/bin/1sh/1sh-test.1 @@ -0,0 +1,395 @@ +.\"- +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)test.1 8.1 (Berkeley) 5/31/93 +.\" $FreeBSD: releng/12.1/bin/test/test.1 314436 2017-02-28 23:42:47Z imp $ +.\" +.Dd October 5, 2016 +.Dt 1SH-TEST 1 +.Os +.Sh NAME +.Nm test , +.Nm \&[ +.Nd condition evaluation utility +.Sh SYNOPSIS +.Nm +.Ar expression +.Nm \&[ +.Ar expression Cm \&] +.Sh DESCRIPTION +The +.Nm +utility evaluates the expression and, if it evaluates +to true, returns a zero (true) exit status; otherwise +it returns 1 (false). +If there is no expression, +.Nm +also +returns 1 (false). +.Pp +All operators and flags are separate arguments to the +.Nm +utility. +.Pp +The following primaries are used to construct expression: +.Bl -tag -width Ar +.It Fl b Ar file +True if +.Ar file +exists and is a block special +file. +.It Fl c Ar file +True if +.Ar file +exists and is a character +special file. +.It Fl d Ar file +True if +.Ar file +exists and is a directory. +.It Fl e Ar file +True if +.Ar file +exists (regardless of type). +.It Fl f Ar file +True if +.Ar file +exists and is a regular file. +.It Fl g Ar file +True if +.Ar file +exists and its set group ID flag +is set. +.It Fl h Ar file +True if +.Ar file +exists and is a symbolic link. +This operator is retained for compatibility with previous versions of +this program. +Do not rely on its existence; use +.Fl L +instead. +.It Fl k Ar file +True if +.Ar file +exists and its sticky bit is set. +.It Fl n Ar string +True if the length of +.Ar string +is nonzero. +.It Fl p Ar file +True if +.Ar file +is a named pipe +.Pq Tn FIFO . +.It Fl r Ar file +True if +.Ar file +exists and is readable. +.It Fl s Ar file +True if +.Ar file +exists and has a size greater +than zero. +.It Fl t Ar file_descriptor +True if the file whose file descriptor number +is +.Ar file_descriptor +is open and is associated with a terminal. +.It Fl u Ar file +True if +.Ar file +exists and its set user ID flag +is set. +.It Fl w Ar file +True if +.Ar file +exists and is writable. +True +indicates only that the write flag is on. +The file is not writable on a read-only file +system even if this test indicates true. +.It Fl x Ar file +True if +.Ar file +exists and is executable. +True +indicates only that the execute flag is on. +If +.Ar file +is a directory, true indicates that +.Ar file +can be searched. +.It Fl z Ar string +True if the length of +.Ar string +is zero. +.It Fl L Ar file +True if +.Ar file +exists and is a symbolic link. +.It Fl O Ar file +True if +.Ar file +exists and its owner matches the effective user id of this process. +.It Fl G Ar file +True if +.Ar file +exists and its group matches the effective group id of this process. +.It Fl S Ar file +True if +.Ar file +exists and is a socket. +.It Ar file1 Fl nt Ar file2 +True if +.Ar file1 +exists and is newer than +.Ar file2 . +.It Ar file1 Fl ot Ar file2 +True if +.Ar file1 +exists and is older than +.Ar file2 . +.It Ar file1 Fl ef Ar file2 +True if +.Ar file1 +and +.Ar file2 +exist and refer to the same file. +.It Ar string +True if +.Ar string +is not the null +string. +.It Ar s1 Cm = Ar s2 +True if the strings +.Ar s1 +and +.Ar s2 +are identical. +.It Ar s1 Cm != Ar s2 +True if the strings +.Ar s1 +and +.Ar s2 +are not identical. +.It Ar s1 Cm < Ar s2 +True if string +.Ar s1 +comes before +.Ar s2 +based on the binary value of their characters. +.It Ar s1 Cm > Ar s2 +True if string +.Ar s1 +comes after +.Ar s2 +based on the binary value of their characters. +.It Ar n1 Fl eq Ar n2 +True if the integers +.Ar n1 +and +.Ar n2 +are algebraically +equal. +.It Ar n1 Fl ne Ar n2 +True if the integers +.Ar n1 +and +.Ar n2 +are not +algebraically equal. +.It Ar n1 Fl gt Ar n2 +True if the integer +.Ar n1 +is algebraically +greater than the integer +.Ar n2 . +.It Ar n1 Fl ge Ar n2 +True if the integer +.Ar n1 +is algebraically +greater than or equal to the integer +.Ar n2 . +.It Ar n1 Fl lt Ar n2 +True if the integer +.Ar n1 +is algebraically less +than the integer +.Ar n2 . +.It Ar n1 Fl le Ar n2 +True if the integer +.Ar n1 +is algebraically less +than or equal to the integer +.Ar n2 . +.El +.Pp +If +.Ar file +is a symbolic link, +.Nm +will fully dereference it and then evaluate the expression +against the file referenced, except for the +.Fl h +and +.Fl L +primaries. +.Pp +These primaries can be combined with the following operators: +.Bl -tag -width Ar +.It Cm \&! Ar expression +True if +.Ar expression +is false. +.It Ar expression1 Fl a Ar expression2 +True if both +.Ar expression1 +and +.Ar expression2 +are true. +.It Ar expression1 Fl o Ar expression2 +True if either +.Ar expression1 +or +.Ar expression2 +are true. +.It Cm \&( Ar expression Cm \&) +True if expression is true. +.El +.Pp +The +.Fl a +operator has higher precedence than the +.Fl o +operator. +.Pp +Some shells may provide a builtin +.Nm +command which is similar or identical to this utility. +Consult the +.Xr builtin 1 +manual page. +.Sh GRAMMAR AMBIGUITY +The +.Nm +grammar is inherently ambiguous. +In order to assure a degree of consistency, +the cases described in the +.St -p1003.2 , +section D11.2/4.62.4, standard +are evaluated consistently according to the rules specified in the +standards document. +All other cases are subject to the ambiguity in the +command semantics. +.Pp +In particular, only expressions containing +.Fl a , +.Fl o , +.Cm \&( +or +.Cm \&) +can be ambiguous. +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Bl -tag -width indent +.It 0 +expression evaluated to true. +.It 1 +expression evaluated to false or expression was +missing. +.It >1 +An error occurred. +.El +.Sh EXAMPLES +Implement +.Li test FILE1 -nt FILE2 +using only +.Tn POSIX +functionality: +.Pp +.Dl test -n \&"$(find -L -- FILE1 -prune -newer FILE2 2>/dev/null)\&" +.Pp +This can be modified using non-standard +.Xr find 1 +primaries like +.Cm -newerca +to compare other timestamps. +.Sh COMPATIBILITY +For compatibility with some other implementations, +the +.Cm = +primary can be substituted with +.Cm == +with the same meaning. +.Sh SEE ALSO +.Xr builtin 1 , +.Xr expr 1 , +.Xr find 1 , +.Xr sh 1 , +.Xr stat 1 , +.Xr symlink 7 +.Sh STANDARDS +The +.Nm +utility implements a superset of the +.St -p1003.2 +specification. +The primaries +.Cm < , +.Cm == , +.Cm > , +.Fl ef , +.Fl nt , +.Fl ot , +.Fl G , +and +.Fl O +are extensions. +.Sh HISTORY +A +.Nm +utility appeared in +.At v7 . +.Sh BUGS +Both sides are always evaluated in +.Fl a +and +.Fl o . +For instance, the writable status of +.Pa file +will be tested by the following command even though the former expression +indicated false, which results in a gratuitous access to the file system: +.Dl "[ -z abc -a -w file ]" +To avoid this, write +.Dl "[ -z abc ] && [ -w file ]" diff --git a/bin/1sh/1sh.1 b/bin/1sh/1sh.1 new file mode 100644 index 00000000..aa906bca --- /dev/null +++ b/bin/1sh/1sh.1 @@ -0,0 +1,2917 @@ +.\"- +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Kenneth Almquist. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)sh.1 8.6 (Berkeley) 5/4/95 +.\" $FreeBSD: releng/12.1/bin/sh/sh.1 345487 2019-03-24 22:10:26Z jilles $ +.\" +.Dd March 9, 2020 +.Dt 1SH 1 +.Os +.Sh NAME +.Nm 1sh +.Nd command interpreter (shell) +.Sh SYNOPSIS +.Nm +.Op Fl /+abCEefhIimnPpTuVvx +.Op Fl /+o Ar longname +.Oo +.Ar script +.Op Ar arg ... +.Oc +.Nm +.Op Fl /+abCEefhIimnPpTuVvx +.Op Fl /+o Ar longname +.Fl c Ar string +.Oo +.Ar name +.Op Ar arg ... +.Oc +.Nm +.Op Fl /+abCEefhIimnPpTuVvx +.Op Fl /+o Ar longname +.Fl s +.Op Ar arg ... +.Sh DESCRIPTION +The +.Nm +utility is a command interpreter. +The current version of +.Nm +is close to the +.St -p1003.1 +specification for the shell. +It only supports features +designated by POSIX, +plus a few Berkeley extensions. +This man page is not intended to be a tutorial nor a complete +specification of the shell. +.Ss Overview +The shell is a command that reads lines from +either a file or the terminal, interprets them, and +generally executes other commands. +It is the program that is started when a user logs into the system, +although a user can select a different shell with the +.Xr chsh 1 +command. +The shell +implements a language that has flow control constructs, +a macro facility that provides a variety of features in +addition to data storage, along with built-in history and line +editing capabilities. +It incorporates many features to +aid interactive use and has the advantage that the interpretative +language is common to both interactive and non-interactive +use (shell scripts). +That is, commands can be typed directly +to the running shell or can be put into a file, +which can be executed directly by the shell. +.Ss Invocation +.\" +.\" XXX This next sentence is incredibly confusing. +.\" +If no arguments are present and if the standard input of the shell +is connected to a terminal +(or if the +.Fl i +option is set), +the shell is considered an interactive shell. +An interactive shell +generally prompts before each command and handles programming +and command errors differently (as described below). +When first starting, the shell inspects argument 0, and +if it begins with a dash +.Pq Ql - , +the shell is also considered a login shell. +This is normally done automatically by the system +when the user first logs in. +A login shell first reads commands +from the files +.Pa /etc/profile +and then +.Pa .profile +in a user's home directory, +if they exist. +If the environment variable +.Ev ENV +is set on entry to a shell, or is set in the +.Pa .profile +of a login shell, the shell then subjects its value to parameter expansion +and arithmetic expansion and reads commands from the named file. +Therefore, a user should place commands that are to be executed only +at login time in the +.Pa .profile +file, and commands that are executed for every shell inside the +.Ev ENV +file. +The user can set the +.Ev ENV +variable to some file by placing the following line in the file +.Pa .profile +in the home directory, +substituting for +.Pa .shrc +the filename desired: +.Pp +.Dl "ENV=$HOME/.shrc; export ENV" +.Pp +The first non-option argument specified on the command line +will be treated as the +name of a file from which to read commands (a shell script), and +the remaining arguments are set as the positional parameters +of the shell +.Li ( $1 , $2 , +etc.). +Otherwise, the shell reads commands +from its standard input. +.Pp +Unlike older versions of +.Nm +the +.Ev ENV +script is only sourced on invocation of interactive shells. +This +closes a well-known, and sometimes easily exploitable security +hole related to poorly thought out +.Ev ENV +scripts. +.Ss Argument List Processing +All of the single letter options to +.Nm +have a corresponding long name, +with the exception of +.Fl c +and +.Fl /+o . +These long names are provided next to the single letter options +in the descriptions below. +The long name for an option may be specified as an argument to the +.Fl /+o +option of +.Nm . +Once the shell is running, +the long name for an option may be specified as an argument to the +.Fl /+o +option of the +.Ic set +built-in command +(described later in the section called +.Sx Built-in Commands ) . +Introducing an option with a dash +.Pq Ql - +enables the option, +while using a plus +.Pq Ql + +disables the option. +A +.Dq Li -- +or plain +.Ql - +will stop option processing and will force the remaining +words on the command line to be treated as arguments. +The +.Fl /+o +and +.Fl c +options do not have long names. +They take arguments and are described after the single letter options. +.Bl -tag -width indent +.It Fl a Li allexport +Flag variables for export when assignments are made to them. +.It Fl b Li notify +Enable asynchronous notification of background job +completion. +(UNIMPLEMENTED) +.It Fl C Li noclobber +Do not overwrite existing files with +.Ql > . +.It Fl E Li emacs +Enable the built-in +.Xr emacs 1 +command line editor (disables the +.Fl V +option if it has been set; +set automatically when interactive on terminals). +.It Fl e Li errexit +Exit immediately if any untested command fails in non-interactive mode. +The exit status of a command is considered to be +explicitly tested if the command is part of the list used to control +an +.Ic if , elif , while , +or +.Ic until ; +if the command is the left +hand operand of an +.Dq Li && +or +.Dq Li || +operator; or if the command is a pipeline preceded by the +.Ic !\& +keyword. +If a shell function is executed and its exit status is explicitly +tested, all commands of the function are considered to be tested as +well. +.Pp +It is recommended to check for failures explicitly +instead of relying on +.Fl e +because it tends to behave in unexpected ways, +particularly in larger scripts. +.It Fl f Li noglob +Disable pathname expansion. +.It Fl h Li trackall +A do-nothing option for POSIX compliance. +.It Fl I Li ignoreeof +Ignore +.Dv EOF Ap s +from input when in interactive mode. +.It Fl i Li interactive +Force the shell to behave interactively. +.It Fl m Li monitor +Turn on job control (set automatically when interactive). +A new process group is created for each pipeline (called a job). +It is possible to suspend jobs or to have them run in the foreground or +in the background. +In a non-interactive shell, +this option can be set even if no terminal is available +and is useful to place processes in separate process groups. +.It Fl n Li noexec +If not interactive, read commands but do not +execute them. +This is useful for checking the +syntax of shell scripts. +.It Fl P Li physical +Change the default for the +.Ic cd +and +.Ic pwd +commands from +.Fl L +(logical directory layout) +to +.Fl P +(physical directory layout). +.It Fl p Li privileged +Turn on privileged mode. +This mode is enabled on startup +if either the effective user or group ID is not equal to the +real user or group ID. +Turning this mode off sets the +effective user and group IDs to the real user and group IDs. +When this mode is enabled for interactive shells, the file +.Pa /etc/suid_profile +is sourced instead of +.Pa ~/.profile +after +.Pa /etc/profile +is sourced, and the contents of the +.Ev ENV +variable are ignored. +.It Fl s Li stdin +Read commands from standard input (set automatically +if no file arguments are present). +This option has +no effect when set after the shell has already started +running (i.e., when set with the +.Ic set +command). +.It Fl T Li trapsasync +When waiting for a child, execute traps immediately. +If this option is not set, +traps are executed after the child exits, +as specified in +.St -p1003.2 . +This nonstandard option is useful for putting guarding shells around +children that block signals. +The surrounding shell may kill the child +or it may just return control to the tty and leave the child alone, +like this: +.Bd -literal -offset indent +1sh -T -c "trap 'exit 1' 2 ; some-blocking-program" +.Ed +.It Fl u Li nounset +Write a message to standard error when attempting +to expand a variable, a positional parameter or +the special parameter +.Va \&! +that is not set, and if the +shell is not interactive, exit immediately. +.It Fl V Li vi +Enable the built-in +.Xr vi 1 +command line editor (disables +.Fl E +if it has been set). +.It Fl v Li verbose +The shell writes its input to standard error +as it is read. +Useful for debugging. +.It Fl x Li xtrace +Write each command +(preceded by the value of the +.Va PS4 +variable subjected to parameter expansion and arithmetic expansion) +to standard error before it is executed. +Useful for debugging. +.It Li nolog +Another do-nothing option for POSIX compliance. +It only has a long name. +.It Li pipefail +Change the exit status of a pipeline to the last non-zero exit status of +any command in the pipeline, if any. +Since an exit due to +.Dv SIGPIPE +counts as a non-zero exit status, +this option may cause non-zero exit status for successful pipelines +if a command such as +.Xr head 1 +in the pipeline terminates with status 0 without reading its +input completely. +This option only has a long name. +.El +.Pp +The +.Fl c +option causes the commands to be read from the +.Ar string +operand instead of from the standard input. +Keep in mind that this option only accepts a single string as its +argument, hence multi-word strings must be quoted. +.Pp +The +.Fl /+o +option takes as its only argument the long name of an option +to be enabled or disabled. +For example, the following two invocations of +.Nm +both enable the built-in +.Xr emacs 1 +command line editor: +.Bd -literal -offset indent +set -E +set -o emacs +.Ed +.Pp +If used without an argument, the +.Fl o +option displays the current option settings in a human-readable format. +If +.Cm +o +is used without an argument, the current option settings are output +in a format suitable for re-input into the shell. +.Ss Lexical Structure +The shell reads input in terms of lines from a file and breaks +it up into words at whitespace (blanks and tabs), and at +certain sequences of +characters called +.Dq operators , +which are special to the shell. +There are two types of operators: control operators and +redirection operators (their meaning is discussed later). +The following is a list of valid operators: +.Bl -tag -width indent +.It Control operators: +.Bl -column "XXX" "XXX" "XXX" "XXX" "XXX" -offset center -compact +.It Li & Ta Li && Ta Li \&( Ta Li \&) Ta Li \en +.It Li ;; Ta Li ;& Ta Li \&; Ta Li \&| Ta Li || +.El +.It Redirection operators: +.Bl -column "XXX" "XXX" "XXX" "XXX" "XXX" -offset center -compact +.It Li < Ta Li > Ta Li << Ta Li >> Ta Li <> +.It Li <& Ta Li >& Ta Li <<- Ta Li >| Ta \& +.El +.El +.Pp +The character +.Ql # +introduces a comment if used at the beginning of a word. +The word starting with +.Ql # +and the rest of the line are ignored. +.Pp +ASCII +.Dv NUL +characters (character code 0) are not allowed in shell input. +.Ss Quoting +Quoting is used to remove the special meaning of certain characters +or words to the shell, such as operators, whitespace, keywords, +or alias names. +.Pp +There are four types of quoting: matched single quotes, +dollar-single quotes, +matched double quotes, and backslash. +.Bl -tag -width indent +.It Single Quotes +Enclosing characters in single quotes preserves the literal +meaning of all the characters (except single quotes, making +it impossible to put single-quotes in a single-quoted string). +.It Dollar-Single Quotes +Enclosing characters between +.Li $' +and +.Li ' +preserves the literal meaning of all characters +except backslashes and single quotes. +A backslash introduces a C-style escape sequence: +.Bl -tag -width xUnnnnnnnn +.It \ea +Alert (ring the terminal bell) +.It \eb +Backspace +.It \ec Ns Ar c +The control character denoted by +.Li ^ Ns Ar c +in +.Xr stty 1 . +If +.Ar c +is a backslash, it must be doubled. +.It \ee +The ESC character (ASCII 0x1b) +.It \ef +Formfeed +.It \en +Newline +.It \er +Carriage return +.It \et +Horizontal tab +.It \ev +Vertical tab +.It \e\e +Literal backslash +.It \e\&' +Literal single-quote +.It \e\&" +Literal double-quote +.It \e Ns Ar nnn +The byte whose octal value is +.Ar nnn +(one to three digits) +.It \ex Ns Ar nn +The byte whose hexadecimal value is +.Ar nn +(one or more digits only the last two of which are used) +.It \eu Ns Ar nnnn +The Unicode code point +.Ar nnnn +(four hexadecimal digits) +.It \eU Ns Ar nnnnnnnn +The Unicode code point +.Ar nnnnnnnn +(eight hexadecimal digits) +.El +.Pp +The sequences for Unicode code points are currently only useful with +UTF-8 locales. +They reject code point 0 and UTF-16 surrogates. +.Pp +If an escape sequence would produce a byte with value 0, +that byte and the rest of the string until the matching single-quote +are ignored. +.Pp +Any other string starting with a backslash is an error. +.It Double Quotes +Enclosing characters within double quotes preserves the literal +meaning of all characters except dollar sign +.Pq Ql $ , +backquote +.Pq Ql ` , +and backslash +.Pq Ql \e . +The backslash inside double quotes is historically weird. +It remains literal unless it precedes the following characters, +which it serves to quote: +.Pp +.Bl -column "XXX" "XXX" "XXX" "XXX" "XXX" -offset center -compact +.It Li $ Ta Li ` Ta Li \&" Ta Li \e Ta Li \en +.El +.It Backslash +A backslash preserves the literal meaning of the following +character, with the exception of the newline character +.Pq Ql \en . +A backslash preceding a newline is treated as a line continuation. +.El +.Ss Keywords +Keywords or reserved words are words that have special meaning to the +shell and are recognized at the beginning of a line and +after a control operator. +The following are keywords: +.Bl -column "doneXX" "elifXX" "elseXX" "untilXX" "whileX" -offset center +.It Li \&! Ta { Ta } Ta Ic case Ta Ic do +.It Ic done Ta Ic elif Ta Ic else Ta Ic esac Ta Ic fi +.It Ic for Ta Ic if Ta Ic then Ta Ic until Ta Ic while +.El +.Ss Aliases +An alias is a name and corresponding value set using the +.Ic alias +built-in command. +Wherever the command word of a simple command may occur, +and after checking for keywords if a keyword may occur, the shell +checks the word to see if it matches an alias. +If it does, it replaces it in the input stream with its value. +For example, if there is an alias called +.Dq Li lf +with the value +.Dq Li "ls -F" , +then the input +.Pp +.Dl "lf foobar" +.Pp +would become +.Pp +.Dl "ls -F foobar" +.Pp +Aliases are also recognized after an alias +whose value ends with a space or tab. +For example, if there is also an alias called +.Dq Li nohup +with the value +.Dq Li "nohup " , +then the input +.Pp +.Dl "nohup lf foobar" +.Pp +would become +.Pp +.Dl "nohup ls -F foobar" +.Pp +Aliases provide a convenient way for naive users to +create shorthands for commands without having to learn how +to create functions with arguments. +Using aliases in scripts is discouraged +because the command that defines them must be executed +before the code that uses them is parsed. +This is fragile and not portable. +.Pp +An alias name may be escaped in a command line, so that it is not +replaced by its alias value, by using quoting characters within or +adjacent to the alias name. +This is most often done by prefixing +an alias name with a backslash to execute a function, built-in, or +normal program with the same name. +See the +.Sx Quoting +subsection. +.Ss Commands +The shell interprets the words it reads according to a +language, the specification of which is outside the scope +of this man page (refer to the BNF in the +.St -p1003.2 +document). +Essentially though, a line is read and if +the first word of the line (or after a control operator) +is not a keyword, then the shell has recognized a +simple command. +Otherwise, a complex command or some +other special construct may have been recognized. +.Ss Simple Commands +If a simple command has been recognized, the shell performs +the following actions: +.Bl -enum +.It +Leading words of the form +.Dq Li name=value +are stripped off and assigned to the environment of +the simple command +(they do not affect expansions). +Redirection operators and +their arguments (as described below) are stripped +off and saved for processing. +.It +The remaining words are expanded as described in +the section called +.Sx Word Expansions , +and the first remaining word is considered the command +name and the command is located. +The remaining +words are considered the arguments of the command. +If no command name resulted, then the +.Dq Li name=value +variable assignments recognized in 1) affect the +current shell. +.It +Redirections are performed as described in +the next section. +.El +.Ss Redirections +Redirections are used to change where a command reads its input +or sends its output. +In general, redirections open, close, or +duplicate an existing reference to a file. +The overall format +used for redirection is: +.Pp +.D1 Oo Ar n Oc Ar redir-op file +.Pp +The +.Ar redir-op +is one of the redirection operators mentioned +previously. +The following gives some examples of how these +operators can be used. +Note that stdin and stdout are commonly used abbreviations +for standard input and standard output respectively. +.Bl -tag -width "1234567890XX" -offset indent +.It Oo Ar n Oc Ns Li > Ar file +redirect stdout (or file descriptor +.Ar n ) +to +.Ar file +.It Oo Ar n Oc Ns Li >| Ar file +same as above, but override the +.Fl C +option +.It Oo Ar n Oc Ns Li >> Ar file +append stdout (or file descriptor +.Ar n ) +to +.Ar file +.It Oo Ar n Oc Ns Li < Ar file +redirect stdin (or file descriptor +.Ar n ) +from +.Ar file +.It Oo Ar n Oc Ns Li <> Ar file +redirect stdin (or file descriptor +.Ar n ) +to and from +.Ar file +.It Oo Ar n1 Oc Ns Li <& Ns Ar n2 +duplicate stdin (or file descriptor +.Ar n1 ) +from file descriptor +.Ar n2 +.It Oo Ar n Oc Ns Li <&- +close stdin (or file descriptor +.Ar n ) +.It Oo Ar n1 Oc Ns Li >& Ns Ar n2 +duplicate stdout (or file descriptor +.Ar n1 ) +to file descriptor +.Ar n2 +.It Oo Ar n Oc Ns Li >&- +close stdout (or file descriptor +.Ar n ) +.El +.Pp +The following redirection is often called a +.Dq here-document . +.Bd -unfilled -offset indent +.Oo Ar n Oc Ns Li << Ar delimiter +.Ar here-doc-text +.Ar ... +.Ar delimiter +.Ed +.Pp +All the text on successive lines up to the delimiter is +saved away and made available to the command on standard +input, or file descriptor +.Ar n +if it is specified. +If the +.Ar delimiter +as specified on the initial line is quoted, then the +.Ar here-doc-text +is treated literally, otherwise the text is subjected to +parameter expansion, command substitution, and arithmetic +expansion (as described in the section on +.Sx Word Expansions ) . +If the operator is +.Dq Li <<- +instead of +.Dq Li << , +then leading tabs +in the +.Ar here-doc-text +are stripped. +.Ss Search and Execution +There are three types of commands: shell functions, +built-in commands, and normal programs. +The command is searched for (by name) in that order. +The three types of commands are all executed in a different way. +.Pp +When a shell function is executed, all of the shell positional +parameters (except +.Li $0 , +which remains unchanged) are +set to the arguments of the shell function. +The variables which are explicitly placed in the environment of +the command (by placing assignments to them before the +function name) are made local to the function and are set +to the values given. +Then the command given in the function definition is executed. +The positional parameters are restored to their original values +when the command completes. +This all occurs within the current shell. +.Pp +Shell built-in commands are executed internally to the shell, without +spawning a new process. +There are two kinds of built-in commands: regular and special. +Assignments before special builtins persist after they finish +executing and assignment errors, redirection errors and certain +operand errors cause a script to be aborted. +Special builtins cannot be overridden with a function. +Both regular and special builtins can affect the shell in ways +normal programs cannot. +.Pp +Otherwise, if the command name does not match a function +or built-in command, the command is searched for as a normal +program in the file system (as described in the next section). +When a normal program is executed, the shell runs the program, +passing the arguments and the environment to the program. +If the program is not a normal executable file +(i.e., if it does not begin with the +.Dq "magic number" +whose ASCII representation is +.Dq Li #! , +resulting in an +.Er ENOEXEC +return value from +.Xr execve 2 ) +but appears to be a text file, +the shell will run a new instance of +.Nm +to interpret it. +.Pp +Note that previous versions of this document +and the source code itself misleadingly and sporadically +refer to a shell script without a magic number +as a +.Dq "shell procedure" . +.Ss Path Search +When locating a command, the shell first looks to see if +it has a shell function by that name. +Then it looks for a +built-in command by that name. +If a built-in command is not found, +one of two things happen: +.Bl -enum +.It +Command names containing a slash are simply executed without +performing any searches. +.It +The shell searches each entry in the +.Va PATH +variable +in turn for the command. +The value of the +.Va PATH +variable should be a series of +entries separated by colons. +Each entry consists of a +directory name. +The current directory +may be indicated implicitly by an empty directory name, +or explicitly by a single period. +.El +.Ss Command Exit Status +Each command has an exit status that can influence the behavior +of other shell commands. +The paradigm is that a command exits +with zero for normal or success, and non-zero for failure, +error, or a false indication. +The man page for each command +should indicate the various exit codes and what they mean. +Additionally, the built-in commands return exit codes, as does +an executed shell function. +.Pp +If a command is terminated by a signal, its exit status is greater than 128. +The signal name can be found by passing the exit status to +.Li kill -l . +.Pp +If there is no command word, +the exit status is the exit status of the last command substitution executed, +or zero if the command does not contain any command substitutions. +.Ss Complex Commands +Complex commands are combinations of simple commands +with control operators or keywords, together creating a larger complex +command. +More generally, a command is one of the following: +.Bl -item -offset indent +.It +simple command +.It +pipeline +.It +list or compound-list +.It +compound command +.It +function definition +.El +.Pp +Unless otherwise stated, the exit status of a command is +that of the last simple command executed by the command, +or zero if no simple command was executed. +.Ss Pipelines +A pipeline is a sequence of one or more commands separated +by the control operator +.Ql \&| . +The standard output of all but +the last command is connected to the standard input +of the next command. +The standard output of the last +command is inherited from the shell, as usual. +.Pp +The format for a pipeline is: +.Pp +.D1 Oo Li \&! Oc Ar command1 Op Li \&| Ar command2 ... +.Pp +The standard output of +.Ar command1 +is connected to the standard input of +.Ar command2 . +The standard input, standard output, or +both of a command is considered to be assigned by the +pipeline before any redirection specified by redirection +operators that are part of the command. +.Pp +Note that unlike some other shells, +.Nm +executes each process in a pipeline with more than one command +in a subshell environment and as a child of the +.Nm +process. +.Pp +If the pipeline is not in the background (discussed later), +the shell waits for all commands to complete. +.Pp +If the keyword +.Ic !\& +does not precede the pipeline, the +exit status is the exit status of the last command specified +in the pipeline if the +.Cm pipefail +option is not set or all commands returned zero, +or the last non-zero exit status of any command in the pipeline otherwise. +Otherwise, the exit status is the logical +NOT of that exit status. +That is, if +that status is zero, the exit status is 1; if +that status is greater than zero, the exit status +is zero. +.Pp +Because pipeline assignment of standard input or standard +output or both takes place before redirection, it can be +modified by redirection. +For example: +.Pp +.Dl "command1 2>&1 | command2" +.Pp +sends both the standard output and standard error of +.Ar command1 +to the standard input of +.Ar command2 . +.Pp +A +.Ql \&; +or newline terminator causes the preceding +AND-OR-list +(described below in the section called +.Sx Short-Circuit List Operators ) +to be executed sequentially; +an +.Ql & +causes asynchronous execution of the preceding AND-OR-list. +.Ss Background Commands (&) +If a command is terminated by the control operator ampersand +.Pq Ql & , +the shell executes the command in a subshell environment (see +.Sx Grouping Commands Together +below) and asynchronously; +the shell does not wait for the command to finish +before executing the next command. +.Pp +The format for running a command in background is: +.Pp +.D1 Ar command1 Li & Op Ar command2 Li & Ar ... +.Pp +If the shell is not interactive, the standard input of an +asynchronous command is set to +.Pa /dev/null . +.Pp +The exit status is zero. +.Ss Lists (Generally Speaking) +A list is a sequence of zero or more commands separated by +newlines, semicolons, or ampersands, +and optionally terminated by one of these three characters. +The commands in a +list are executed in the order they are written. +If command is followed by an ampersand, the shell starts the +command and immediately proceeds onto the next command; +otherwise it waits for the command to terminate before +proceeding to the next one. +.Ss Short-Circuit List Operators +.Dq Li && +and +.Dq Li || +are AND-OR list operators. +.Dq Li && +executes the first command, and then executes the second command +if the exit status of the first command is zero. +.Dq Li || +is similar, but executes the second command if the exit +status of the first command is nonzero. +.Dq Li && +and +.Dq Li || +both have the same priority. +.Ss Flow-Control Constructs (if, while, for, case) +The syntax of the +.Ic if +command is: +.Bd -unfilled -offset indent -compact +.Ic if Ar list +.Ic then Ar list +.Oo Ic elif Ar list +.Ic then Ar list Oc Ar ... +.Op Ic else Ar list +.Ic fi +.Ed +.Pp +The exit status is that of selected +.Ic then +or +.Ic else +list, +or zero if no list was selected. +.Pp +The syntax of the +.Ic while +command is: +.Bd -unfilled -offset indent -compact +.Ic while Ar list +.Ic do Ar list +.Ic done +.Ed +.Pp +The two lists are executed repeatedly while the exit status of the +first list is zero. +The +.Ic until +command is similar, but has the word +.Ic until +in place of +.Ic while , +which causes it to +repeat until the exit status of the first list is zero. +.Pp +The exit status is that of the last execution of the second list, +or zero if it was never executed. +.Pp +The syntax of the +.Ic for +command is: +.Bd -unfilled -offset indent -compact +.Ic for Ar variable Op Ic in Ar word ... +.Ic do Ar list +.Ic done +.Ed +.Pp +If +.Ic in +and the following words are omitted, +.Ic in Li \&"$@\&" +is used instead. +The words are expanded, and then the list is executed +repeatedly with the variable set to each word in turn. +The +.Ic do +and +.Ic done +commands may be replaced with +.Ql { +and +.Ql } . +.Pp +The syntax of the +.Ic break +and +.Ic continue +commands is: +.D1 Ic break Op Ar num +.D1 Ic continue Op Ar num +.Pp +The +.Ic break +command terminates the +.Ar num +innermost +.Ic for +or +.Ic while +loops. +The +.Ic continue +command continues with the next iteration of the innermost loop. +These are implemented as special built-in commands. +.Pp +The syntax of the +.Ic case +command is: +.Bd -unfilled -offset indent -compact +.Ic case Ar word Ic in +.Ar pattern ) Ar list Li ;; +.Ar ... +.Ic esac +.Ed +.Pp +The pattern can actually be one or more patterns +(see +.Sx Shell Patterns +described later), +separated by +.Ql \&| +characters. +Tilde expansion, parameter expansion, command substitution, +arithmetic expansion and quote removal are applied to the word. +Then, each pattern is expanded in turn using tilde expansion, +parameter expansion, command substitution and arithmetic expansion and +the expanded form of the word is checked against it. +If a match is found, the corresponding list is executed. +If the selected list is terminated by the control operator +.Ql ;& +instead of +.Ql ;; , +execution continues with the next list, +continuing until a list terminated with +.Ql ;; +or the end of the +.Ic case +command. +.Ss Grouping Commands Together +Commands may be grouped by writing either +.Pp +.Sm off +.Bd -literal -offset -ident +.Po Ar list Pc +.Ed +.Sm on +.Pp +or +.Bd -literal -offset -ident +.No { Ar list ; } +.Ed +.Pp +The first form executes the commands in a subshell environment. +A subshell environment has its own copy of: +.Bl -enum +.It +The current working directory as set by +.Ic cd . +.It +The file creation mask as set by +.Ic umask . +.It +Resource limits as set by +.Ic ulimit . +.It +References to open files. +.It +Traps as set by +.Ic trap . +.It +Known jobs. +.It +Positional parameters and variables. +.It +Shell options. +.It +Shell functions. +.It +Shell aliases. +.El +.Pp +These are copied from the parent shell environment, +except that trapped (but not ignored) signals are reset to the default action +and known jobs are cleared. +Any changes do not affect the parent shell environment. +.Pp +A subshell environment may be implemented as a child process or differently. +If job control is enabled in an interactive shell, +commands grouped in parentheses can be suspended and continued as a unit. +.Pp +For compatibility with other shells, +two open parentheses in sequence should be separated by whitespace. +.Pp +The second form never forks another shell, +so it is slightly more efficient. +Grouping commands together this way allows the user to +redirect their output as though they were one program: +.Bd -literal -offset indent +{ echo -n "hello"; echo " world"; } > greeting +.Ed +.Ss Functions +The syntax of a function definition is +.Pp +.D1 Ar name Li \&( \&) Ar command +.Pp +A function definition is an executable statement; when +executed it installs a function named +.Ar name +and returns an +exit status of zero. +The +.Ar command +is normally a list +enclosed between +.Ql { +and +.Ql } . +.Pp +Variables may be declared to be local to a function by +using the +.Ic local +command. +This should appear as the first statement of a function, +and the syntax is: +.Pp +.D1 Ic local Oo Ar variable ... Oc Op Fl +.Pp +The +.Ic local +command is implemented as a built-in command. +The exit status is zero +unless the command is not in a function or a variable name is invalid. +.Pp +When a variable is made local, it inherits the initial +value and exported and readonly flags from the variable +with the same name in the surrounding scope, if there is +one. +Otherwise, the variable is initially unset. +The shell +uses dynamic scoping, so that if the variable +.Va x +is made local to function +.Em f , +which then calls function +.Em g , +references to the variable +.Va x +made inside +.Em g +will refer to the variable +.Va x +declared inside +.Em f , +not to the global variable named +.Va x . +.Pp +The only special parameter that can be made local is +.Ql - . +Making +.Ql - +local causes any shell options +(including those that only have long names) +that are +changed via the +.Ic set +command inside the function to be +restored to their original values when the function +returns. +.Pp +The syntax of the +.Ic return +command is +.Pp +.D1 Ic return Op Ar exitstatus +.Pp +It terminates the current executional scope, returning from the closest +nested function or sourced script; +if no function or sourced script is being executed, +it exits the shell instance. +The +.Ic return +command is implemented as a special built-in command. +.Ss Variables and Parameters +The shell maintains a set of parameters. +A parameter +denoted by a name +(consisting solely +of alphabetics, numerics, and underscores, +and starting with an alphabetic or an underscore) +is called a variable. +When starting up, +the shell turns all environment variables with valid names into shell +variables. +New variables can be set using the form +.Pp +.D1 Ar name Ns = Ns Ar value +.Pp +A parameter can also be denoted by a number +or a special character as explained below. +.Pp +Assignments are expanded differently from other words: +tilde expansion is also performed after the equals sign and after any colon +and usernames are also terminated by colons, +and field splitting and pathname expansion are not performed. +.Pp +This special expansion applies not only to assignments that form a simple +command by themselves or precede a command word, +but also to words passed to the +.Ic export , +.Ic local +or +.Ic readonly +built-in commands that have this form. +For this, the builtin's name must be literal +(not the result of an expansion) +and may optionally be preceded by one or more literal instances of +.Ic command +without options. +.Ss Positional Parameters +A positional parameter is a parameter denoted by a number greater than zero. +The shell sets these initially to the values of its command line +arguments that follow the name of the shell script. +The +.Ic set +built-in command can also be used to set or reset them. +.Ss Special Parameters +Special parameters are parameters denoted by a single special character +or the digit zero. +They are shown in the following list, exactly as they would appear in input +typed by the user or in the source of a shell script. +.Bl -hang +.It Li $* +Expands to the positional parameters, starting from one. +When +the expansion occurs within a double-quoted string +it expands to a single field with the value of each parameter +separated by the first character of the +.Va IFS +variable, +or by a space if +.Va IFS +is unset. +.It Li $@ +Expands to the positional parameters, starting from one. +When +the expansion occurs within double-quotes, each positional +parameter expands as a separate argument. +If there are no positional parameters, the +expansion of +.Li @ +generates zero arguments, even when +.Li @ +is double-quoted. +What this basically means, for example, is +if +.Li $1 +is +.Dq Li abc +and +.Li $2 +is +.Dq Li "def ghi" , +then +.Li \&"$@\&" +expands to +the two arguments: +.Bd -literal -offset indent +"abc" "def ghi" +.Ed +.It Li $# +Expands to the number of positional parameters. +.It Li $? +Expands to the exit status of the most recent pipeline. +.It Li $- +(hyphen) Expands to the current option flags (the single-letter +option names concatenated into a string) as specified on +invocation, by the +.Ic set +built-in command, or implicitly +by the shell. +.It Li $$ +Expands to the process ID of the invoked shell. +A subshell +retains the same value of +.Va $ +as its parent. +.It Li $! +Expands to the process ID of the most recent background +command executed from the current shell. +For a +pipeline, the process ID is that of the last command in the +pipeline. +If this parameter is referenced, the shell will remember +the process ID and its exit status until the +.Ic wait +built-in command reports completion of the process. +.It Li $0 +(zero) Expands to the name of the shell script if passed on the command line, +the +.Ar name +operand if given (with +.Fl c ) +or otherwise argument 0 passed to the shell. +.El +.Ss Special Variables +The following variables are set by the shell or +have special meaning to it: +.Bl -tag -width ".Va HISTSIZE" +.It Va CDPATH +The search path used with the +.Ic cd +built-in. +.It Va EDITOR +The fallback editor used with the +.Ic fc +built-in. +If not set, the default editor is +.Xr ed 1 . +.It Va FCEDIT +The default editor used with the +.Ic fc +built-in. +.It Va HISTFILE +The path to the file in which command history is saved. +History is loaded when +.Va HISTFILE +is set and saved on exit. +.It Va HISTSIZE +The number of previous commands that are accessible. +.It Va HOME +The user's home directory, +used in tilde expansion and as a default directory for the +.Ic cd +built-in. +.It Va IFS +Input Field Separators. +This is initialized at startup to +.Aq space , +.Aq tab , +and +.Aq newline +in that order. +This value also applies if +.Va IFS +is unset, but not if it is set to the empty string. +See the +.Sx White Space Splitting +section for more details. +.It Va LINENO +The current line number in the script or function. +.It Va MAIL +The name of a mail file, that will be checked for the arrival of new +mail. +Overridden by +.Va MAILPATH . +.It Va MAILPATH +A colon +.Pq Ql \&: +separated list of file names, for the shell to check for incoming +mail. +This variable overrides the +.Va MAIL +setting. +There is a maximum of 10 mailboxes that can be monitored at once. +.It Va OPTIND +The index of the next argument to be processed by +.Ic getopts . +This is initialized to 1 at startup. +.It Va PATH +The default search path for executables. +See the +.Sx Path Search +section for details. +.It Va PPID +The parent process ID of the invoked shell. +This is set at startup +unless this variable is in the environment. +A later change of parent process ID is not reflected. +A subshell retains the same value of +.Va PPID . +.It Va PS0 +The pre-prompt string, +which is printed before each new prompt. +.Va PS0 +may include any of the formatting sequences from +.Va PS1 . +.It Va PS1 +The primary prompt string, which defaults to +.Dq Li "$ " , +unless you are the superuser, in which case it defaults to +.Dq Li "# " . +.Va PS1 +may include any of the following formatting sequences, +which are replaced by the given information: +.Bl -tag -width indent +.It Li \eH +This system's fully-qualified hostname (FQDN). +.It Li \eh +This system's hostname. +.It Li \eW +The final component of the current working directory. +.It Li \ew +The entire path of the current working directory. +.It Li \e? +Exit status if it is non-zero, empty string otherwise. +.It Li \e$ +Superuser status. +.Dq Li "$ " +for normal users and +.Dq Li "# " +for superusers. +.It Li \e\e +A literal backslash. +.El +.It Va PS2 +The secondary prompt string, which defaults to +.Dq Li "> " . +.Va PS2 +may include any of the formatting sequences from +.Va PS1 . +.It Va PS4 +The prefix for the trace output (if +.Fl x +is active). +The default is +.Dq Li "+ " . +.It Va RPS1 +The primary right prompt string. +.Va RPS1 +may include any of the formatting sequences from +.Va PS1 . +.It Va RPS2 +The secondary right prompt string. +.Va RPS2 +may include any of the formatting sequences from +.Va PS1 . +.El +.Ss Word Expansions +This clause describes the various expansions that are +performed on words. +Not all expansions are performed on +every word, as explained later. +.Pp +Tilde expansions, parameter expansions, command substitutions, +arithmetic expansions, and quote removals that occur within +a single word expand to a single field. +It is only field +splitting or pathname expansion that can create multiple +fields from a single word. +The single exception to this rule is +the expansion of the special parameter +.Va @ +within double-quotes, +as was described above. +.Pp +The order of word expansion is: +.Bl -enum +.It +Tilde Expansion, Parameter Expansion, Command Substitution, +Arithmetic Expansion (these all occur at the same time). +.It +Field Splitting is performed on fields generated by step (1) +unless the +.Va IFS +variable is null. +.It +Pathname Expansion (unless the +.Fl f +option is in effect). +.It +Quote Removal. +.El +.Pp +The +.Ql $ +character is used to introduce parameter expansion, command +substitution, or arithmetic expansion. +.Ss Tilde Expansion (substituting a user's home directory) +A word beginning with an unquoted tilde character +.Pq Ql ~ +is +subjected to tilde expansion. +All the characters up to a slash +.Pq Ql / +or the end of the word are treated as a username +and are replaced with the user's home directory. +If the +username is missing (as in +.Pa ~/foobar ) , +the tilde is replaced with the value of the +.Va HOME +variable (the current user's home directory). +.Ss Parameter Expansion +The format for parameter expansion is as follows: +.Pp +.D1 Li ${ Ns Ar expression Ns Li } +.Pp +where +.Ar expression +consists of all characters until the matching +.Ql } . +Any +.Ql } +escaped by a backslash or within a single-quoted or double-quoted +string, and characters in +embedded arithmetic expansions, command substitutions, and variable +expansions, are not examined in determining the matching +.Ql } . +If the variants with +.Ql + , +.Ql - , +.Ql = +or +.Ql ?\& +occur within a double-quoted string, +as an extension there may be unquoted parts +(via double-quotes inside the expansion); +.Ql } +within such parts are also not examined in determining the matching +.Ql } . +.Pp +The simplest form for parameter expansion is: +.Pp +.D1 Li ${ Ns Ar parameter Ns Li } +.Pp +The value, if any, of +.Ar parameter +is substituted. +.Pp +The parameter name or symbol can be enclosed in braces, which are +optional except for positional parameters with more than one digit or +when parameter is followed by a character that could be interpreted as +part of the name. +If a parameter expansion occurs inside double-quotes: +.Bl -enum +.It +Field splitting is not performed on the results of the +expansion, with the exception of the special parameter +.Va @ . +.It +Pathname expansion is not performed on the results of the +expansion. +.El +.Pp +In addition, a parameter expansion can be modified by using one of the +following formats. +.Bl -tag -width indent +.It Li ${ Ns Ar parameter Ns Li :- Ns Ar word Ns Li } +Use Default Values. +If +.Ar parameter +is unset or null, the expansion of +.Ar word +is substituted; otherwise, the value of +.Ar parameter +is substituted. +.It Li ${ Ns Ar parameter Ns Li := Ns Ar word Ns Li } +Assign Default Values. +If +.Ar parameter +is unset or null, the expansion of +.Ar word +is assigned to +.Ar parameter . +In all cases, the +final value of +.Ar parameter +is substituted. +Quoting inside +.Ar word +does not prevent field splitting or pathname expansion. +Only variables, not positional +parameters or special parameters, can be +assigned in this way. +.It Li ${ Ns Ar parameter Ns Li :? Ns Oo Ar word Oc Ns Li } +Indicate Error if Null or Unset. +If +.Ar parameter +is unset or null, the expansion of +.Ar word +(or a message indicating it is unset if +.Ar word +is omitted) is written to standard +error and the shell exits with a nonzero +exit status. +Otherwise, the value of +.Ar parameter +is substituted. +An +interactive shell need not exit. +.It Li ${ Ns Ar parameter Ns Li :+ Ns Ar word Ns Li } +Use Alternate Value. +If +.Ar parameter +is unset or null, null is substituted; +otherwise, the expansion of +.Ar word +is substituted. +.El +.Pp +In the parameter expansions shown previously, use of the colon in the +format results in a test for a parameter that is unset or null; omission +of the colon results in a test for a parameter that is only unset. +.Pp +The +.Ar word +inherits the type of quoting +(unquoted, double-quoted or here-document) +from the surroundings, +with the exception that a backslash that quotes a closing brace is removed +during quote removal. +.Bl -tag -width indent +.It Li ${# Ns Ar parameter Ns Li } +String Length. +The length in characters of +the value of +.Ar parameter . +.El +.Pp +The following four varieties of parameter expansion provide for substring +processing. +In each case, pattern matching notation +(see +.Sx Shell Patterns ) , +rather than regular expression notation, +is used to evaluate the patterns. +If parameter is one of the special parameters +.Va * +or +.Va @ , +the result of the expansion is unspecified. +Enclosing the full parameter expansion string in double-quotes does not +cause the following four varieties of pattern characters to be quoted, +whereas quoting characters within the braces has this effect. +.Bl -tag -width indent +.It Li ${ Ns Ar parameter Ns Li % Ns Ar word Ns Li } +Remove Smallest Suffix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar parameter , +with the smallest portion of the +suffix matched by the pattern deleted. +.It Li ${ Ns Ar parameter Ns Li %% Ns Ar word Ns Li } +Remove Largest Suffix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar parameter , +with the largest portion of the +suffix matched by the pattern deleted. +.It Li ${ Ns Ar parameter Ns Li # Ns Ar word Ns Li } +Remove Smallest Prefix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar parameter , +with the smallest portion of the +prefix matched by the pattern deleted. +.It Li ${ Ns Ar parameter Ns Li ## Ns Ar word Ns Li } +Remove Largest Prefix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar parameter , +with the largest portion of the +prefix matched by the pattern deleted. +.El +.Ss Command Substitution +Command substitution allows the output of a command to be substituted in +place of the command name itself. +Command substitution occurs when +the command is enclosed as follows: +.Pp +.D1 Li $( Ns Ar command Ns Li )\& +.Pp +or the backquoted version: +.Pp +.D1 Li ` Ns Ar command Ns Li ` +.Pp +The shell expands the command substitution by executing command +and replacing the command substitution +with the standard output of the command, +removing sequences of one or more newlines at the end of the substitution. +Embedded newlines before the end of the output are not removed; +however, during field splitting, they may be translated into spaces +depending on the value of +.Va IFS +and the quoting that is in effect. +The command is executed in a subshell environment, +except that the built-in commands +.Ic jobid , +.Ic jobs , +and +.Ic trap +return information about the parent shell environment +and +.Ic times +returns information about the same process +if they are the only command in a command substitution. +.Pp +If a command substitution of the +.Li $( +form begins with a subshell, +the +.Li $( +and +.Li (\& +must be separated by whitespace +to avoid ambiguity with arithmetic expansion. +.Ss Arithmetic Expansion +Arithmetic expansion provides a mechanism for evaluating an arithmetic +expression and substituting its value. +The format for arithmetic expansion is as follows: +.Pp +.D1 Li $(( Ns Ar expression Ns Li )) +.Pp +The +.Ar expression +is treated as if it were in double-quotes, except +that a double-quote inside the expression is not treated specially. +The +shell expands all tokens in the +.Ar expression +for parameter expansion, +command substitution, +arithmetic expansion +and quote removal. +.Pp +The allowed expressions are a subset of C expressions, +summarized below. +.Bl -tag -width "Variables" -offset indent +.It Values +All values are of type +.Ft intmax_t . +.It Constants +Decimal, octal (starting with +.Li 0 ) +and hexadecimal (starting with +.Li 0x ) +integer constants. +.It Variables +Shell variables can be read and written +and contain integer constants. +.It Unary operators +.Li "! ~ + -" +.It Binary operators +.Li "* / % + - << >> < <= > >= == != & ^ | && ||"\& +.It Assignment operators +.Li "= += -= *= /= %= <<= >>= &= ^= |=" +.It Conditional operator +.Li "? :"\& +.El +.Pp +The result of the expression is substituted in decimal. +.Ss White Space Splitting (Field Splitting) +In certain contexts, +after parameter expansion, command substitution, and +arithmetic expansion the shell scans the results of +expansions and substitutions that did not occur in double-quotes for +field splitting and multiple fields can result. +.Pp +Characters in +.Va IFS +that are whitespace +.Po +.Aq space , +.Aq tab , +and +.Aq newline +.Pc +are treated differently from other characters in +.Va IFS . +.Pp +Whitespace in +.Va IFS +at the beginning or end of a word is discarded. +.Pp +Subsequently, a field is delimited by either +.Bl -enum +.It +a non-whitespace character in +.Va IFS +with any whitespace in +.Va IFS +surrounding it, or +.It +one or more whitespace characters in +.Va IFS . +.El +.Pp +If a word ends with a non-whitespace character in +.Va IFS , +there is no empty field after this character. +.Pp +If no field is delimited, the word is discarded. +In particular, if a word consists solely of an unquoted substitution +and the result of the substitution is null, +it is removed by field splitting even if +.Va IFS +is null. +.Ss Pathname Expansion (File Name Generation) +Unless the +.Fl f +option is set, +file name generation is performed +after word splitting is complete. +Each word is +viewed as a series of patterns, separated by slashes. +The +process of expansion replaces the word with the names of +all existing files whose names can be formed by replacing +each pattern with a string that matches the specified pattern. +There are two restrictions on this: first, a pattern cannot match +a string containing a slash, and second, +a pattern cannot match a string starting with a period +unless the first character of the pattern is a period. +The next section describes the patterns used for +Pathname Expansion, +the four varieties of parameter expansion for substring processing and the +.Ic case +command. +.Ss Shell Patterns +A pattern consists of normal characters, which match themselves, +and meta-characters. +The meta-characters are +.Ql * , +.Ql \&? , +and +.Ql \&[ . +These characters lose their special meanings if they are quoted. +When command or variable substitution is performed and the dollar sign +or back quotes are not double-quoted, the value of the +variable or the output of the command is scanned for these +characters and they are turned into meta-characters. +.Pp +An asterisk +.Pq Ql * +matches any string of characters. +A question mark +.Pq Ql \&? +matches any single character. +A left bracket +.Pq Ql \&[ +introduces a character class. +The end of the character class is indicated by a +.Ql \&] ; +if the +.Ql \&] +is missing then the +.Ql \&[ +matches a +.Ql \&[ +rather than introducing a character class. +A character class matches any of the characters between the square brackets. +A locale-dependent range of characters may be specified using a minus sign. +A named class of characters (see +.Xr wctype 3 ) +may be specified by surrounding the name with +.Ql \&[:\& +and +.Ql :\&] . +For example, +.Ql \&[\&[:alpha:\&]\&] +is a shell pattern that matches a single letter. +The character class may be complemented by making an exclamation point +.Pq Ql !\& +the first character of the character class. +A caret +.Pq Ql ^ +has the same effect but is non-standard. +.Pp +To include a +.Ql \&] +in a character class, make it the first character listed +(after the +.Ql \&! +or +.Ql ^ , +if any). +To include a +.Ql - , +make it the first or last character listed. +.Ss Built-in Commands +This section lists the built-in commands. +.Bl -tag -width indent +.It Ic \&: +A null command that returns a 0 (true) exit value. +.It Ic \&. Ar file +The commands in the specified file are read and executed by the shell. +The +.Ic return +command may be used to return to the +.Ic \&. +command's caller. +If +.Ar file +contains any +.Ql / +characters, it is used as is. +Otherwise, the shell searches the +.Va PATH +for the file. +If it is not found in the +.Va PATH , +it is sought in the current working directory. +.It Ic \&[ +A built-in equivalent of +.Xr test 1 . +.It Ic alias Oo Ar name Ns Oo = Ns Ar string Oc ... Oc +If +.Ar name Ns = Ns Ar string +is specified, the shell defines the alias +.Ar name +with value +.Ar string . +If just +.Ar name +is specified, the value of the alias +.Ar name +is printed. +With no arguments, the +.Ic alias +built-in command prints the names and values of all defined aliases +(see +.Ic unalias ) . +Alias values are written with appropriate quoting so that they are +suitable for re-input to the shell. +Also see the +.Sx Aliases +subsection. +.It Ic bg Op Ar job ... +Continue the specified jobs +(or the current job if no jobs are given) +in the background. +.It Ic bind Oo Fl aeklrsv Oc Oo Ar key Oo Ar command Oc Oc +List or alter key bindings for the line editor. +This command is documented in +.Xr editrc 5 . +.It Ic break Op Ar num +See the +.Sx Flow-Control Constructs +subsection. +.It Ic builtin Ar cmd Op Ar arg ... +Execute the specified built-in command, +.Ar cmd . +This is useful when the user wishes to override a shell function +with the same name as a built-in command. +.It Ic cd Oo Fl L | P Oc Oo Fl e Oc Op Ar directory +.It Ic cd Fl +Switch to the specified +.Ar directory , +to the directory specified in the +.Va HOME +environment variable if no +.Ar directory +is specified or +to the directory specified in the +.Va OLDPWD +environment variable if +.Ar directory +is +.Fl . +If +.Ar directory +does not begin with +.Pa / , \&. , +or +.Pa .. , +then the directories listed in the +.Va CDPATH +variable will be +searched for the specified +.Ar directory . +If +.Va CDPATH +is unset, the current directory is searched. +The format of +.Va CDPATH +is the same as that of +.Va PATH . +In an interactive shell, +the +.Ic cd +command will print out the name of the directory +that it actually switched to +if the +.Va CDPATH +mechanism was used or if +.Ar directory +was +.Fl . +.Pp +If the +.Fl P +option is specified, +.Pa .. +is handled physically and symbolic links are resolved before +.Pa .. +components are processed. +If the +.Fl L +option is specified, +.Pa .. +is handled logically. +This is the default. +.Pp +The +.Fl e +option causes +.Ic cd +to return exit status 1 if the full pathname of the new directory +cannot be determined reliably or at all. +Normally this is not considered an error, +although a warning is printed. +.Pp +If changing the directory fails, the exit status is greater than 1. +If the directory is changed, the exit status is 0, or also 1 if +.Fl e +was given. +.It Ic chdir +A synonym for the +.Ic cd +built-in command. +.It Ic command Oo Fl p Oc Op Ar utility Op Ar argument ... +.It Ic command Oo Fl p Oc Fl v Ar utility +.It Ic command Oo Fl p Oc Fl V Ar utility +The first form of invocation executes the specified +.Ar utility , +ignoring shell functions in the search. +If +.Ar utility +is a special builtin, +it is executed as if it were a regular builtin. +.Pp +If the +.Fl p +option is specified, the command search is performed using a +default value of +.Va PATH +that is guaranteed to find all of the standard utilities. +.Pp +If the +.Fl v +option is specified, +.Ar utility +is not executed but a description of its interpretation by the shell is +printed. +For ordinary commands the output is the path name; for shell built-in +commands, shell functions and keywords only the name is written. +Aliases are printed as +.Dq Ic alias Ar name Ns = Ns Ar value . +.Pp +The +.Fl V +option is identical to +.Fl v +except for the output. +It prints +.Dq Ar utility Ic is Ar description +where +.Ar description +is either +the path name to +.Ar utility , +a special shell builtin, +a shell builtin, +a shell function, +a shell keyword +or +an alias for +.Ar value . +.It Ic continue Op Ar num +See the +.Sx Flow-Control Constructs +subsection. +.It Ic echo Oo Fl e | n Oc Op Ar string ... +Print a space-separated list of the arguments to the standard output +and append a newline character. +.Bl -tag -width indent +.It Fl n +Suppress the output of the trailing newline. +.It Fl e +Process C-style backslash escape sequences. +The +.Ic echo +command understands the following character escapes: +.Bl -tag -width indent +.It \ea +Alert (ring the terminal bell) +.It \eb +Backspace +.It \ec +Suppress the trailing newline (this has the side-effect of truncating the +line if it is not the last character) +.It \ee +The ESC character (ASCII 0x1b) +.It \ef +Formfeed +.It \en +Newline +.It \er +Carriage return +.It \et +Horizontal tab +.It \ev +Vertical tab +.It \e\e +Literal backslash +.It \e0nnn +(Zero) The character whose octal value is +.Ar nnn +.El +.Pp +If +.Ar string +is not enclosed in quotes then the backslash itself must be escaped +with a backslash to protect it from the shell. +For example +.Bd -literal -offset indent +$ echo -e "a\evb" +a + b +$ echo -e a\e\evb +a + b +$ echo -e "a\e\eb" +a\eb +$ echo -e a\e\e\e\eb +a\eb +.Ed +.El +.Pp +Only one of the +.Fl e +and +.Fl n +options may be specified. +.It Ic eval Ar string ... +Concatenate all the arguments with spaces. +Then re-parse and execute the command. +.It Ic exec Op Ar command Op arg ... +Unless +.Ar command +is omitted, +the shell process is replaced with the specified program +(which must be a real program, not a shell built-in command or function). +Any redirections on the +.Ic exec +command are marked as permanent, +so that they are not undone when the +.Ic exec +command finishes. +.It Ic exit Op Ar exitstatus +Terminate the shell process. +If +.Ar exitstatus +is given +it is used as the exit status of the shell. +Otherwise, if the shell is executing an +.Cm EXIT +trap, the exit status of the last command before the trap is used; +if the shell is executing a trap for a signal, +the shell exits by resending the signal to itself. +Otherwise, the exit status of the preceding command is used. +The exit status should be an integer between 0 and 255. +.It Ic export Ar name ... +.It Ic export Op Fl p +The specified names are exported so that they will +appear in the environment of subsequent commands. +The only way to un-export a variable is to +.Ic unset +it. +The shell allows the value of a variable to be set +at the same time as it is exported by writing +.Pp +.D1 Ic export Ar name Ns = Ns Ar value +.Pp +With no arguments the +.Ic export +command lists the names +of all exported variables. +If the +.Fl p +option is specified, the exported variables are printed as +.Dq Ic export Ar name Ns = Ns Ar value +lines, suitable for re-input to the shell. +.It Ic false +A null command that returns a non-zero (false) exit value. +.It Ic fc Oo Fl e Ar editor Oc Op Ar first Op Ar last +.It Ic fc Fl l Oo Fl nr Oc Op Ar first Op Ar last +.It Ic fc Fl s Oo Ar old Ns = Ns Ar new Oc Op Ar first +The +.Ic fc +built-in command lists, or edits and re-executes, +commands previously entered to an interactive shell. +.Bl -tag -width indent +.It Fl e Ar editor +Use the editor named by +.Ar editor +to edit the commands. +The +.Ar editor +string is a command name, +subject to search via the +.Va PATH +variable. +The value in the +.Va FCEDIT +variable is used as a default when +.Fl e +is not specified. +If +.Va FCEDIT +is null or unset, the value of the +.Va EDITOR +variable is used. +If +.Va EDITOR +is null or unset, +.Xr ed 1 +is used as the editor. +.It Fl l No (ell) +List the commands rather than invoking +an editor on them. +The commands are written in the +sequence indicated by the +.Ar first +and +.Ar last +operands, as affected by +.Fl r , +with each command preceded by the command number. +.It Fl n +Suppress command numbers when listing with +.Fl l . +.It Fl r +Reverse the order of the commands listed +(with +.Fl l ) +or edited +(with neither +.Fl l +nor +.Fl s ) . +.It Fl s +Re-execute the command without invoking an editor. +.It Ar first +.It Ar last +Select the commands to list or edit. +The number of previous commands that can be accessed +are determined by the value of the +.Va HISTSIZE +variable. +The value of +.Ar first +or +.Ar last +or both are one of the following: +.Bl -tag -width indent +.It Oo Cm + Oc Ns Ar num +A positive number representing a command number; +command numbers can be displayed with the +.Fl l +option. +.It Fl Ar num +A negative decimal number representing the +command that was executed +.Ar num +of +commands previously. +For example, \-1 is the immediately previous command. +.It Ar string +A string indicating the most recently entered command +that begins with that string. +If the +.Ar old Ns = Ns Ar new +operand is not also specified with +.Fl s , +the string form of the first operand cannot contain an embedded equal sign. +.El +.El +.Pp +The following variables affect the execution of +.Ic fc : +.Bl -tag -width ".Va HISTSIZE" +.It Va FCEDIT +Name of the editor to use for history editing. +.It Va HISTSIZE +The number of previous commands that are accessible. +.El +.It Ic fg Op Ar job +Move the specified +.Ar job +or the current job to the foreground. +.It Ic getopts Ar optstring var +The POSIX +.Ic getopts +command. +The +.Ic getopts +command deprecates the older +.Xr getopt 1 +command. +The first argument should be a series of letters, each possibly +followed by a colon which indicates that the option takes an argument. +The specified variable is set to the parsed option. +The index of +the next argument is placed into the shell variable +.Va OPTIND . +If an option takes an argument, it is placed into the shell variable +.Va OPTARG . +If an invalid option is encountered, +.Ar var +is set to +.Ql \&? . +It returns a false value (1) when it encounters the end of the options. +A new set of arguments may be parsed by assigning +.Li OPTIND=1 . +.It Ic hash Oo Fl rv Oc Op Ar command ... +The shell maintains a hash table which remembers the locations of commands. +With no arguments whatsoever, the +.Ic hash +command prints out the contents of this table. +.Pp +With arguments, the +.Ic hash +command removes each specified +.Ar command +from the hash table (unless they are functions) and then locates it. +With the +.Fl v +option, +.Ic hash +prints the locations of the commands as it finds them. +The +.Fl r +option causes the +.Ic hash +command to delete all the entries in the hash table except for functions. +.It Ic jobid Op Ar job +Print the process IDs of the processes in the specified +.Ar job . +If the +.Ar job +argument is omitted, use the current job. +.It Ic jobs Oo Fl lps Oc Op Ar job ... +Print information about the specified jobs, or all jobs if no +.Ar job +argument is given. +The information printed includes job ID, status and command name. +.Pp +If the +.Fl l +option is specified, the PID of each job is also printed. +If the +.Fl p +option is specified, only the process IDs for the process group leaders +are printed, one per line. +If the +.Fl s +option is specified, only the PIDs of the job commands are printed, one per +line. +.It Ic kill +A built-in equivalent of +.Xr kill 1 +that additionally supports sending signals to jobs. +.It Ic local Oo Ar variable ... Oc Op Fl +See the +.Sx Functions +subsection. +.It Ic printf +A built-in equivalent of +.Xr printf 1 . +.It Ic pwd Op Fl L | P +Print the path of the current directory. +The built-in command may +differ from the program of the same name because the +built-in command remembers what the current directory +is rather than recomputing it each time. +This makes +it faster. +However, if the current directory is +renamed, +the built-in version of +.Xr pwd 1 +will continue to print the old name for the directory. +.Pp +If the +.Fl P +option is specified, symbolic links are resolved. +If the +.Fl L +option is specified, the shell's notion of the current directory +is printed (symbolic links are not resolved). +This is the default. +.It Ic read Oo Fl p Ar prompt Oc Oo +.Fl t Ar timeout Oc Oo Fl er Oc Ar variable ... +The +.Ar prompt +is printed if the +.Fl p +option is specified +and the standard input is a terminal. +Then a line is +read from the standard input. +The trailing newline +is deleted from the line and the line is split as +described in the section on +.Sx White Space Splitting (Field Splitting)\& +above, and +the pieces are assigned to the variables in order. +If there are more pieces than variables, the remaining +pieces (along with the characters in +.Va IFS +that separated them) +are assigned to the last variable. +If there are more variables than pieces, the remaining +variables are assigned the null string. +.Pp +Backslashes are treated specially, unless the +.Fl r +option is +specified. +If a backslash is followed by +a newline, the backslash and the newline will be +deleted. +If a backslash is followed by any other +character, the backslash will be deleted and the following +character will be treated as though it were not in +.Va IFS , +even if it is. +.Pp +If the +.Fl t +option is specified and the +.Ar timeout +elapses before a complete line of input is supplied, +the +.Ic read +command will return an exit status as if terminated by +.Dv SIGALRM +without assigning any values. +The +.Ar timeout +value may optionally be followed by one of +.Ql s , +.Ql m +or +.Ql h +to explicitly specify seconds, minutes or hours. +If none is supplied, +.Ql s +is assumed. +.Pp +The +.Fl e +option exists only for backward compatibility with older scripts. +.Pp +The exit status is 0 on success, 1 on end of file, +between 2 and 128 if an error occurs +and greater than 128 if a trapped signal interrupts +.Ic read . +.It Ic readonly Oo Fl p Oc Op Ar name ... +Each specified +.Ar name +is marked as read only, +so that it cannot be subsequently modified or unset. +The shell allows the value of a variable to be set +at the same time as it is marked read only +by using the following form: +.Pp +.D1 Ic readonly Ar name Ns = Ns Ar value +.Pp +With no arguments the +.Ic readonly +command lists the names of all read only variables. +If the +.Fl p +option is specified, the read-only variables are printed as +.Dq Ic readonly Ar name Ns = Ns Ar value +lines, suitable for re-input to the shell. +.It Ic return Op Ar exitstatus +See the +.Sx Functions +subsection. +.It Ic set Oo Fl /+abCEefIimnpTuVvx Oc Oo Fl /+o Ar longname Oc Oo +.Fl c Ar string Oc Op Fl - Ar arg ... +The +.Ic set +command performs three different functions: +.Bl -item +.It +With no arguments, it lists the values of all shell variables. +.It +If options are given, +either in short form or using the long +.Dq Fl /+o Ar longname +form, +it sets or clears the specified options as described in the section called +.Sx Argument List Processing . +.It +If the +.Dq Fl - +option is specified, +.Ic set +will replace the shell's positional parameters with the subsequent +arguments. +If no arguments follow the +.Dq Fl - +option, +all the positional parameters will be cleared, +which is equivalent to executing the command +.Dq Li "shift $#" . +The +.Dq Fl - +flag may be omitted when specifying arguments to be used +as positional replacement parameters. +This is not recommended, +because the first argument may begin with a dash +.Pq Ql - +or a plus +.Pq Ql + , +which the +.Ic set +command will interpret as a request to enable or disable options. +.El +.It Ic setvar Ar variable value +Assigns the specified +.Ar value +to the specified +.Ar variable . +The +.Ic setvar +command is intended to be used in functions that +assign values to variables whose names are passed as parameters. +In general it is better to write +.Dq Ar variable Ns = Ns Ar value +rather than using +.Ic setvar . +.It Ic shift Op Ar n +Shift the positional parameters +.Ar n +times, or once if +.Ar n +is not specified. +A shift sets the value of +.Li $1 +to the value of +.Li $2 , +the value of +.Li $2 +to the value of +.Li $3 , +and so on, +decreasing the value of +.Li $# +by one. +For portability, shifting if there are zero positional parameters +should be avoided, since the shell may abort. +.It Ic test +A built-in equivalent of +.Xr test 1 . +.It Ic times +Print the amount of time spent executing the shell process and its children. +The first output line shows the user and system times for the shell process +itself, the second one contains the user and system times for the +children. +.It Ic trap Oo Ar action Oc Ar signal ... +.It Ic trap Fl l +Cause the shell to parse and execute +.Ar action +when any specified +.Ar signal +is received. +The signals are specified by name or number. +In addition, the pseudo-signal +.Cm EXIT +may be used to specify an +.Ar action +that is performed when the shell terminates. +The +.Ar action +may be an empty string or a dash +.Pq Ql - ; +the former causes the specified signal to be ignored +and the latter causes the default action to be taken. +Omitting the +.Ar action +and using only signal numbers is another way to request the default action. +In a subshell or utility environment, +the shell resets trapped (but not ignored) signals to the default action. +The +.Ic trap +command has no effect on signals that were ignored on entry to the shell. +.Pp +Option +.Fl l +causes the +.Ic trap +command to display a list of valid signal names. +.It Ic true +A null command that returns a 0 (true) exit value. +.It Ic type Op Ar name ... +Interpret each +.Ar name +as a command and print the resolution of the command search. +Possible resolutions are: +shell keyword, alias, special shell builtin, shell builtin, command, +tracked alias +and not found. +For aliases the alias expansion is printed; +for commands and tracked aliases +the complete pathname of the command is printed. +.It Ic ulimit Oo Fl HSabcdfklmnopstuvw Oc Op Ar limit +Set or display resource limits (see +.Xr getrlimit 2 ) . +If +.Ar limit +is specified, the named resource will be set; +otherwise the current resource value will be displayed. +.Pp +If +.Fl H +is specified, the hard limits will be set or displayed. +While everybody is allowed to reduce a hard limit, +only the superuser can increase it. +The +.Fl S +option +specifies the soft limits instead. +When displaying limits, +only one of +.Fl S +or +.Fl H +can be given. +The default is to display the soft limits, +and to set both the hard and the soft limits. +.Pp +Option +.Fl a +causes the +.Ic ulimit +command to display all resources. +The parameter +.Ar limit +is not acceptable in this mode. +.Pp +The remaining options specify which resource value is to be +displayed or modified. +They are mutually exclusive. +.Bl -tag -width indent +.It Fl b Ar sbsize +The maximum size of socket buffer usage, in bytes. +.It Fl c Ar coredumpsize +The maximal size of core dump files, in 512-byte blocks. +Setting +.Ar coredumpsize +to 0 prevents core dump files from being created. +.It Fl d Ar datasize +The maximal size of the data segment of a process, in kilobytes. +.It Fl f Ar filesize +The maximal size of a file, in 512-byte blocks. +.It Fl k Ar kqueues +The maximal number of kqueues +(see +.Xr kqueue 2 ) +for this user ID. +.It Fl l Ar lockedmem +The maximal size of memory that can be locked by a process, in +kilobytes. +.It Fl m Ar memoryuse +The maximal resident set size of a process, in kilobytes. +.It Fl n Ar nofiles +The maximal number of descriptors that could be opened by a process. +.It Fl o Ar umtxp +The maximal number of process-shared locks +(see +.Xr pthread 3 ) +for this user ID. +.It Fl p Ar pseudoterminals +The maximal number of pseudo-terminals for this user ID. +.It Fl s Ar stacksize +The maximal size of the stack segment, in kilobytes. +.It Fl t Ar time +The maximal amount of CPU time to be used by each process, in seconds. +.It Fl u Ar userproc +The maximal number of simultaneous processes for this user ID. +.It Fl v Ar virtualmem +The maximal virtual size of a process, in kilobytes. +.It Fl w Ar swapuse +The maximum amount of swap space reserved or used for this user ID, +in kilobytes. +.El +.It Ic umask Oo Fl S Oc Op Ar mask +Set the file creation mask (see +.Xr umask 2 ) +to the octal or symbolic (see +.Xr chmod 1 ) +value specified by +.Ar mask . +If the argument is omitted, the current mask value is printed. +If the +.Fl S +option is specified, the output is symbolic, otherwise the output is octal. +.It Ic unalias Oo Fl a Oc Op Ar name ... +The specified alias names are removed. +If +.Fl a +is specified, all aliases are removed. +.It Ic unset Oo Fl fv Oc Ar name ... +The specified variables or functions are unset and unexported. +If the +.Fl v +option is specified or no options are given, the +.Ar name +arguments are treated as variable names. +If the +.Fl f +option is specified, the +.Ar name +arguments are treated as function names. +.It Ic wait Op Ar job ... +Wait for each specified +.Ar job +to complete and return the exit status of the last process in the +last specified +.Ar job . +If any +.Ar job +specified is unknown to the shell, it is treated as if it +were a known job that exited with exit status 127. +If no operands are given, wait for all jobs to complete +and return an exit status of zero. +.El +.Ss Command Line Editing +When +.Nm +is being used interactively from a terminal, the current command +and the command history +(see +.Ic fc +in +.Sx Built-in Commands ) +can be edited using +.Nm vi Ns -mode +command line editing. +This mode uses commands similar +to a subset of those described in the +.Xr vi 1 +man page. +The command +.Dq Li "set -o vi" +(or +.Dq Li "set -V" ) +enables +.Nm vi Ns -mode +editing and places +.Nm +into +.Nm vi +insert mode. +With +.Nm vi Ns -mode +enabled, +.Nm +can be switched between insert mode and command mode by typing +.Aq ESC . +Hitting +.Aq return +while in command mode will pass the line to the shell. +.Pp +Similarly, the +.Dq Li "set -o emacs" +(or +.Dq Li "set -E" ) +command can be used to enable a subset of +.Nm emacs Ns -style +command line editing features. +.Sh ENVIRONMENT +The following environment variables affect the execution of +.Nm : +.Bl -tag -width ".Ev LANGXXXXXX" +.It Ev ENV +Initialization file for interactive shells. +.It Ev LANG , Ev LC_* +Locale settings. +These are inherited by children of the shell, +and is used in a limited manner by the shell itself. +.It Ev OLDPWD +The previous current directory. +This is used and updated by +.Ic cd . +.It Ev PWD +An absolute pathname for the current directory, +possibly containing symbolic links. +This is used and updated by the shell. +.It Ev TERM +The default terminal setting for the shell. +This is inherited by children of the shell, and is used in the history +editing modes. +.El +.Pp +Additionally, environment variables are turned into shell variables +at startup, +which may affect the shell as described under +.Sx Special Variables . +.Sh FILES +.Bl -tag -width "/etc/suid_profileXX" -compact +.It Pa ~/.profile +User's login profile. +.It Pa /etc/profile +System login profile. +.It Pa /etc/shells +Shell database. +.It Pa /etc/suid_profile +Privileged shell profile. +.El +.Sh EXIT STATUS +Errors that are detected by the shell, such as a syntax error, will +cause the shell to exit with a non-zero exit status. +If the shell is not an interactive shell, the execution of the shell +file will be aborted. +Otherwise the shell will return the exit status of the last command +executed, or if the +.Ic exit +builtin is used with a numeric argument, it +will return the argument. +.Sh SEE ALSO +.Xr 1sh-kill 1 , +.Xr 1sh-printf 1 , +.Xr 1sh-test 1 , +.Xr builtin 1 , +.Xr chsh 1 , +.Xr echo 1 , +.Xr ed 1 , +.Xr emacs 1 , +.Xr pwd 1 , +.Xr vi 1 , +.Xr execve 2 , +.Xr getrlimit 2 , +.Xr umask 2 , +.Xr wctype 3 , +.Xr editrc 5 , +.Xr shells 5 +.Sh HISTORY +A +.Nm sh +command, the Thompson shell, appeared in +.At v1 . +It was superseded in +.At v7 +by the Bourne shell, which inherited the name +.Nm sh . +.Pp +This version of +.Nm sh +was rewritten in 1989 under the +.Bx +license after the Bourne shell from +.At V.4 . +The +.Nm +command is based on sources from +.Fx 12.1 . +.Sh AUTHORS +This version of +.Nm +was originally written by +.An Kenneth Almquist . +.Sh BUGS +The +.Nm +utility does not recognize multibyte characters other than UTF-8. +Splitting using +.Va IFS +does not recognize multibyte characters. diff --git a/bin/1sh/Makefile b/bin/1sh/Makefile new file mode 100644 index 00000000..26ce125f --- /dev/null +++ b/bin/1sh/Makefile @@ -0,0 +1,84 @@ +PREFIX = /usr/local +MANDIR = ${PREFIX}/share/man + +CFLAGS += -std=c99 -Wall -Wextra -DSHELL +LDLIBS = -ledit + +-include config.mk + +SRCS += alias.c +SRCS += arith_yacc.c +SRCS += arith_yylex.c +SRCS += cd.c +SRCS += echo.c +SRCS += error.c +SRCS += eval.c +SRCS += exec.c +SRCS += expand.c +SRCS += histedit.c +SRCS += input.c +SRCS += jobs.c +SRCS += kill.c +SRCS += mail.c +SRCS += main.c +SRCS += memalloc.c +SRCS += miscbltin.c +SRCS += mystring.c +SRCS += options.c +SRCS += output.c +SRCS += parser.c +SRCS += printf.c +SRCS += redir.c +SRCS += show.c +SRCS += test.c +SRCS += trap.c +SRCS += var.c + +GENSRCS = builtins.c nodes.c syntax.c +GENHDRS = builtins.h nodes.h syntax.h token.h + +SRCS += ${GENSRCS} +OBJS = ${SRCS:.c=.o} + +MANS = 1sh.1 1sh-kill.1 1sh-printf.1 1sh-test.1 + +all: tags 1sh + +1sh: ${OBJS} + ${CC} ${LDFLAGS} ${OBJS} ${LDLIBS} -o $@ + +${OBJS}: ${GENHDRS} + +builtins.c builtins.h: mkbuiltins builtins.def + sh mkbuiltins . + +nodes.c nodes.h: mknodes nodetypes nodes.c.pat + ./mknodes nodetypes nodes.c.pat + +syntax.c syntax.h: mksyntax + ./mksyntax + +token.h: mktokens + sh mktokens + +tags: *.h *.c + ctags -w *.h *.c + +depend: ${SRCS} ${GENHDRS} + ${CC} ${CFLAGS} -MM ${SRCS} > .depend + +-include .depend + +clean: + rm -f 1sh ${OBJS} mknodes mksyntax ${GENSRCS} ${GENHDRS} tags .depend + +install: 1sh ${MANS} + install -d ${PREFIX}/bin ${MANDIR}/man1 + install 1sh ${PREFIX}/bin + for man in ${MANS}; do gzip -c $$man > ${MANDIR}/man1/$$man.gz; done + grep -q '^${PREFIX}/bin/1sh$$' /etc/shells \ + || echo '${PREFIX}/bin/1sh' >> /etc/shells + +uninstall: + rm -f ${PREFIX}/bin/1sh ${MANS:%=${MANDIR}/man1/%.gz} + sed -i sed '\;^${PREFIX}/bin/1sh$$;d' /etc/shells diff --git a/bin/1sh/TOUR b/bin/1sh/TOUR new file mode 100644 index 00000000..3f196c9b --- /dev/null +++ b/bin/1sh/TOUR @@ -0,0 +1,301 @@ +# @(#)TOUR 8.1 (Berkeley) 5/31/93 +# $FreeBSD: releng/12.1/bin/sh/TOUR 317882 2017-05-06 13:28:42Z jilles $ + +NOTE -- This is the original TOUR paper distributed with ash and +does not represent the current state of the shell. It is provided anyway +since it provides helpful information for how the shell is structured, +but be warned that things have changed -- the current shell is +still under development. + +================================================================ + + A Tour through Ash + + Copyright 1989 by Kenneth Almquist. + + +DIRECTORIES: The subdirectory bltin contains commands which can +be compiled stand-alone. The rest of the source is in the main +ash directory. + +SOURCE CODE GENERATORS: Files whose names begin with "mk" are +programs that generate source code. A complete list of these +programs is: + + program input files generates + ------- ----------- --------- + mkbuiltins builtins.def builtins.h builtins.c + mknodes nodetypes nodes.h nodes.c + mksyntax - syntax.h syntax.c + mktokens - token.h + +There are undoubtedly too many of these. + +EXCEPTIONS: Code for dealing with exceptions appears in +exceptions.c. The C language doesn't include exception handling, +so I implement it using setjmp and longjmp. The global variable +exception contains the type of exception. EXERROR is raised by +calling error. EXINT is an interrupt. + +INTERRUPTS: In an interactive shell, an interrupt will cause an +EXINT exception to return to the main command loop. (Exception: +EXINT is not raised if the user traps interrupts using the trap +command.) The INTOFF and INTON macros (defined in exception.h) +provide uninterruptible critical sections. Between the execution +of INTOFF and the execution of INTON, interrupt signals will be +held for later delivery. INTOFF and INTON can be nested. + +MEMALLOC.C: Memalloc.c defines versions of malloc and realloc +which call error when there is no memory left. It also defines a +stack oriented memory allocation scheme. Allocating off a stack +is probably more efficient than allocation using malloc, but the +big advantage is that when an exception occurs all we have to do +to free up the memory in use at the time of the exception is to +restore the stack pointer. The stack is implemented using a +linked list of blocks. + +STPUTC: If the stack were contiguous, it would be easy to store +strings on the stack without knowing in advance how long the +string was going to be: + p = stackptr; + *p++ = c; /* repeated as many times as needed */ + stackptr = p; +The following three macros (defined in memalloc.h) perform these +operations, but grow the stack if you run off the end: + STARTSTACKSTR(p); + STPUTC(c, p); /* repeated as many times as needed */ + grabstackstr(p); + +We now start a top-down look at the code: + +MAIN.C: The main routine performs some initialization, executes +the user's profile if necessary, and calls cmdloop. Cmdloop +repeatedly parses and executes commands. + +OPTIONS.C: This file contains the option processing code. It is +called from main to parse the shell arguments when the shell is +invoked, and it also contains the set builtin. The -i and -m op- +tions (the latter turns on job control) require changes in signal +handling. The routines setjobctl (in jobs.c) and setinteractive +(in trap.c) are called to handle changes to these options. + +PARSING: The parser code is all in parser.c. A recursive des- +cent parser is used. Syntax tables (generated by mksyntax) are +used to classify characters during lexical analysis. There are +four tables: one for normal use, one for use when inside single +quotes and dollar single quotes, one for use when inside double +quotes and one for use in arithmetic. The tables are machine +dependent because they are indexed by character variables and +the range of a char varies from machine to machine. + +PARSE OUTPUT: The output of the parser consists of a tree of +nodes. The various types of nodes are defined in the file node- +types. + +Nodes of type NARG are used to represent both words and the con- +tents of here documents. An early version of ash kept the con- +tents of here documents in temporary files, but keeping here do- +cuments in memory typically results in significantly better per- +formance. It would have been nice to make it an option to use +temporary files for here documents, for the benefit of small +machines, but the code to keep track of when to delete the tem- +porary files was complex and I never fixed all the bugs in it. +(AT&T has been maintaining the Bourne shell for more than ten +years, and to the best of my knowledge they still haven't gotten +it to handle temporary files correctly in obscure cases.) + +The text field of a NARG structure points to the text of the +word. The text consists of ordinary characters and a number of +special codes defined in parser.h. The special codes are: + + CTLVAR Parameter expansion + CTLENDVAR End of parameter expansion + CTLBACKQ Command substitution + CTLBACKQ|CTLQUOTE Command substitution inside double quotes + CTLARI Arithmetic expansion + CTLENDARI End of arithmetic expansion + CTLESC Escape next character + +A variable substitution contains the following elements: + + CTLVAR type name '=' [ alternative-text CTLENDVAR ] + +The type field is a single character specifying the type of sub- +stitution. The possible types are: + + VSNORMAL $var + VSMINUS ${var-text} + VSMINUS|VSNUL ${var:-text} + VSPLUS ${var+text} + VSPLUS|VSNUL ${var:+text} + VSQUESTION ${var?text} + VSQUESTION|VSNUL ${var:?text} + VSASSIGN ${var=text} + VSASSIGN|VSNUL ${var:=text} + VSTRIMLEFT ${var#text} + VSTRIMLEFTMAX ${var##text} + VSTRIMRIGHT ${var%text} + VSTRIMRIGHTMAX ${var%%text} + VSLENGTH ${#var} + VSERROR delayed error + +In addition, the type field will have the VSQUOTE flag set if the +variable is enclosed in double quotes and the VSLINENO flag if +LINENO is being expanded (the parameter name is the decimal line +number). The parameter's name comes next, terminated by an equals +sign. If the type is not VSNORMAL (including when it is VSLENGTH), +then the text field in the substitution follows, terminated by a +CTLENDVAR byte. + +The type VSERROR is used to allow parsing bad substitutions like +${var[7]} and generate an error when they are expanded. + +Commands in back quotes are parsed and stored in a linked list. +The locations of these commands in the string are indicated by +CTLBACKQ and CTLBACKQ+CTLQUOTE characters, depending upon whether +the back quotes were enclosed in double quotes. + +Arithmetic expansion starts with CTLARI and ends with CTLENDARI. + +The character CTLESC escapes the next character, so that in case +any of the CTL characters mentioned above appear in the input, +they can be passed through transparently. CTLESC is also used to +escape '*', '?', '[', and '!' characters which were quoted by the +user and thus should not be used for file name generation. + +CTLESC characters have proved to be particularly tricky to get +right. In the case of here documents which are not subject to +variable and command substitution, the parser doesn't insert any +CTLESC characters to begin with (so the contents of the text +field can be written without any processing). Other here docu- +ments, and words which are not subject to file name generation, +have the CTLESC characters removed during the variable and command +substitution phase. Words which are subject to file name +generation have the CTLESC characters removed as part of the file +name phase. + +EXECUTION: Command execution is handled by the following files: + eval.c The top level routines. + redir.c Code to handle redirection of input and output. + jobs.c Code to handle forking, waiting, and job control. + exec.c Code to do path searches and the actual exec sys call. + expand.c Code to evaluate arguments. + var.c Maintains the variable symbol table. Called from expand.c. + +EVAL.C: Evaltree recursively executes a parse tree. The exit +status is returned in the global variable exitstatus. The alter- +native entry evalbackcmd is called to evaluate commands in back +quotes. It saves the result in memory if the command is a buil- +tin; otherwise it forks off a child to execute the command and +connects the standard output of the child to a pipe. + +JOBS.C: To create a process, you call makejob to return a job +structure, and then call forkshell (passing the job structure as +an argument) to create the process. Waitforjob waits for a job +to complete. These routines take care of process groups if job +control is defined. + +REDIR.C: Ash allows file descriptors to be redirected and then +restored without forking off a child process. This is accom- +plished by duplicating the original file descriptors. The redir- +tab structure records where the file descriptors have been dupli- +cated to. + +EXEC.C: The routine find_command locates a command, and enters +the command in the hash table if it is not already there. The +third argument specifies whether it is to print an error message +if the command is not found. (When a pipeline is set up, +find_command is called for all the commands in the pipeline be- +fore any forking is done, so to get the commands into the hash +table of the parent process. But to make command hashing as +transparent as possible, we silently ignore errors at that point +and only print error messages if the command cannot be found +later.) + +The routine shellexec is the interface to the exec system call. + +EXPAND.C: As the routine argstr generates words by parameter +expansion, command substitution and arithmetic expansion, it +performs word splitting on the result. As each word is output, +the routine expandmeta performs file name generation (if enabled). + +VAR.C: Variables are stored in a hash table. Probably we should +switch to extensible hashing. The variable name is stored in the +same string as the value (using the format "name=value") so that +no string copying is needed to create the environment of a com- +mand. Variables which the shell references internally are preal- +located so that the shell can reference the values of these vari- +ables without doing a lookup. + +When a program is run, the code in eval.c sticks any environment +variables which precede the command (as in "PATH=xxx command") in +the variable table as the simplest way to strip duplicates, and +then calls "environment" to get the value of the environment. + +BUILTIN COMMANDS: The procedures for handling these are scat- +tered throughout the code, depending on which location appears +most appropriate. They can be recognized because their names al- +ways end in "cmd". The mapping from names to procedures is +specified in the file builtins.def, which is processed by the +mkbuiltins command. + +A builtin command is invoked with argc and argv set up like a +normal program. A builtin command is allowed to overwrite its +arguments. Builtin routines can call nextopt to do option pars- +ing. This is kind of like getopt, but you don't pass argc and +argv to it. Builtin routines can also call error. This routine +normally terminates the shell (or returns to the main command +loop if the shell is interactive), but when called from a non- +special builtin command it causes the builtin command to +terminate with an exit status of 2. + +The directory bltins contains commands which can be compiled in- +dependently but can also be built into the shell for efficiency +reasons. The header file bltin.h takes care of most of the +differences between the ash and the stand-alone environment. +The user should call the main routine "main", and #define main to +be the name of the routine to use when the program is linked into +ash. This #define should appear before bltin.h is included; +bltin.h will #undef main if the program is to be compiled +stand-alone. A similar approach is used for a few utilities from +bin and usr.bin. + +CD.C: This file defines the cd and pwd builtins. + +SIGNALS: Trap.c implements the trap command. The routine set- +signal figures out what action should be taken when a signal is +received and invokes the signal system call to set the signal ac- +tion appropriately. When a signal that a user has set a trap for +is caught, the routine "onsig" sets a flag. The routine dotrap +is called at appropriate points to actually handle the signal. +When an interrupt is caught and no trap has been set for that +signal, the routine "onint" in error.c is called. + +OUTPUT: Ash uses its own output routines. There are three out- +put structures allocated. "Output" represents the standard out- +put, "errout" the standard error, and "memout" contains output +which is to be stored in memory. This last is used when a buil- +tin command appears in backquotes, to allow its output to be col- +lected without doing any I/O through the UNIX operating system. +The variables out1 and out2 normally point to output and errout, +respectively, but they are set to point to memout when appropri- +ate inside backquotes. + +INPUT: The basic input routine is pgetc, which reads from the +current input file. There is a stack of input files; the current +input file is the top file on this stack. The code allows the +input to come from a string rather than a file. (This is for the +-c option and the "." and eval builtin commands.) The global +variable plinno is saved and restored when files are pushed and +popped from the stack. The parser routines store the number of +the current line in this variable. + +DEBUGGING: If DEBUG is defined in shell.h, then the shell will +write debugging information to the file $HOME/trace. Most of +this is done using the TRACE macro, which takes a set of printf +arguments inside two sets of parenthesis. Example: +"TRACE(("n=%d0, n))". The double parenthesis are necessary be- +cause the preprocessor can't handle functions with a variable +number of arguments. Defining DEBUG also causes the shell to +generate a core dump if it is sent a quit signal. The tracing +code is in show.c. diff --git a/bin/1sh/alias.c b/bin/1sh/alias.c new file mode 100644 index 00000000..e343b853 --- /dev/null +++ b/bin/1sh/alias.c @@ -0,0 +1,256 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)alias.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/alias.c 317039 2017-04-16 22:10:02Z jilles $ */ + +#include <stdlib.h> +#include "shell.h" +#include "output.h" +#include "error.h" +#include "memalloc.h" +#include "mystring.h" +#include "alias.h" +#include "options.h" +#include "builtins.h" + +#define ATABSIZE 39 + +static struct alias *atab[ATABSIZE]; +static int aliases; + +static void setalias(const char *, const char *); +static int unalias(const char *); +static struct alias **hashalias(const char *); + +static +void +setalias(const char *name, const char *val) +{ + struct alias *ap, **app; + + unalias(name); + app = hashalias(name); + INTOFF; + ap = ckmalloc(sizeof (struct alias)); + ap->name = savestr(name); + ap->val = savestr(val); + ap->flag = 0; + ap->next = *app; + *app = ap; + aliases++; + INTON; +} + +static void +freealias(struct alias *ap) +{ + ckfree(ap->name); + ckfree(ap->val); + ckfree(ap); +} + +static int +unalias(const char *name) +{ + struct alias *ap, **app; + + app = hashalias(name); + + for (ap = *app; ap; app = &(ap->next), ap = ap->next) { + if (equal(name, ap->name)) { + /* + * if the alias is currently in use (i.e. its + * buffer is being used by the input routine) we + * just null out the name instead of freeing it. + * We could clear it out later, but this situation + * is so rare that it hardly seems worth it. + */ + if (ap->flag & ALIASINUSE) + *ap->name = '\0'; + else { + INTOFF; + *app = ap->next; + freealias(ap); + INTON; + } + aliases--; + return (0); + } + } + + return (1); +} + +static void +rmaliases(void) +{ + struct alias *ap, **app; + int i; + + INTOFF; + for (i = 0; i < ATABSIZE; i++) { + app = &atab[i]; + while (*app) { + ap = *app; + if (ap->flag & ALIASINUSE) { + *ap->name = '\0'; + app = &(*app)->next; + } else { + *app = ap->next; + freealias(ap); + } + } + } + aliases = 0; + INTON; +} + +struct alias * +lookupalias(const char *name, int check) +{ + struct alias *ap; + + if (aliases == 0) + return (NULL); + for (ap = *hashalias(name); ap; ap = ap->next) { + if (equal(name, ap->name)) { + if (check && (ap->flag & ALIASINUSE)) + return (NULL); + return (ap); + } + } + + return (NULL); +} + +static int +comparealiases(const void *p1, const void *p2) +{ + const struct alias *const *a1 = p1; + const struct alias *const *a2 = p2; + + return strcmp((*a1)->name, (*a2)->name); +} + +static void +printalias(const struct alias *a) +{ + out1fmt("%s=", a->name); + out1qstr(a->val); + out1c('\n'); +} + +static void +printaliases(void) +{ + int i, j; + struct alias **sorted, *ap; + + INTOFF; + sorted = ckmalloc(aliases * sizeof(*sorted)); + j = 0; + for (i = 0; i < ATABSIZE; i++) + for (ap = atab[i]; ap; ap = ap->next) + if (*ap->name != '\0') + sorted[j++] = ap; + qsort(sorted, aliases, sizeof(*sorted), comparealiases); + for (i = 0; i < aliases; i++) { + printalias(sorted[i]); + if (int_pending()) + break; + } + ckfree(sorted); + INTON; +} + +int +aliascmd(int argc __unused, char **argv __unused) +{ + char *n, *v; + int ret = 0; + struct alias *ap; + + nextopt(""); + + if (*argptr == NULL) { + printaliases(); + return (0); + } + while ((n = *argptr++) != NULL) { + if ((v = strchr(n+1, '=')) == NULL) /* n+1: funny ksh stuff */ + if ((ap = lookupalias(n, 0)) == NULL) { + warning("%s: not found", n); + ret = 1; + } else + printalias(ap); + else { + *v++ = '\0'; + setalias(n, v); + } + } + + return (ret); +} + +int +unaliascmd(int argc __unused, char **argv __unused) +{ + int i; + + while ((i = nextopt("a")) != '\0') { + if (i == 'a') { + rmaliases(); + return (0); + } + } + for (i = 0; *argptr; argptr++) + i |= unalias(*argptr); + + return (i); +} + +static struct alias ** +hashalias(const char *p) +{ + unsigned int hashval; + + hashval = (unsigned char)*p << 4; + while (*p) + hashval+= *p++; + return &atab[hashval % ATABSIZE]; +} diff --git a/bin/1sh/alias.h b/bin/1sh/alias.h new file mode 100644 index 00000000..625d1a50 --- /dev/null +++ b/bin/1sh/alias.h @@ -0,0 +1,45 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)alias.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/alias.h 314436 2017-02-28 23:42:47Z imp $ + */ + +#define ALIASINUSE 1 + +struct alias { + struct alias *next; + char *name; + char *val; + int flag; +}; + +struct alias *lookupalias(const char *, int); diff --git a/bin/1sh/arith.h b/bin/1sh/arith.h new file mode 100644 index 00000000..ecaad30f --- /dev/null +++ b/bin/1sh/arith.h @@ -0,0 +1,37 @@ +/*- + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)arith.h 1.1 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/arith.h 315511 2017-03-18 20:41:07Z jilles $ + */ + +#include "shell.h" + +#define DIGITS(var) (3 + (2 + CHAR_BIT * sizeof((var))) / 3) + +arith_t arith(const char *); diff --git a/bin/1sh/arith_yacc.c b/bin/1sh/arith_yacc.c new file mode 100644 index 00000000..57285831 --- /dev/null +++ b/bin/1sh/arith_yacc.c @@ -0,0 +1,381 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2007 + * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/arith_yacc.c 345117 2019-03-13 21:53:10Z jilles $ */ + +#include <limits.h> +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include "arith.h" +#include "arith_yacc.h" +#include "expand.h" +#include "shell.h" +#include "error.h" +#include "memalloc.h" +#include "output.h" +#include "options.h" +#include "var.h" + +#if ARITH_BOR + 11 != ARITH_BORASS || ARITH_ASS + 11 != ARITH_EQ +#error Arithmetic tokens are out of order. +#endif + +static const char *arith_startbuf; + +const char *arith_buf; +union yystype yylval; + +static int last_token; + +#define ARITH_PRECEDENCE(op, prec) [op - ARITH_BINOP_MIN] = prec + +static const char prec[ARITH_BINOP_MAX - ARITH_BINOP_MIN] = { + ARITH_PRECEDENCE(ARITH_MUL, 0), + ARITH_PRECEDENCE(ARITH_DIV, 0), + ARITH_PRECEDENCE(ARITH_REM, 0), + ARITH_PRECEDENCE(ARITH_ADD, 1), + ARITH_PRECEDENCE(ARITH_SUB, 1), + ARITH_PRECEDENCE(ARITH_LSHIFT, 2), + ARITH_PRECEDENCE(ARITH_RSHIFT, 2), + ARITH_PRECEDENCE(ARITH_LT, 3), + ARITH_PRECEDENCE(ARITH_LE, 3), + ARITH_PRECEDENCE(ARITH_GT, 3), + ARITH_PRECEDENCE(ARITH_GE, 3), + ARITH_PRECEDENCE(ARITH_EQ, 4), + ARITH_PRECEDENCE(ARITH_NE, 4), + ARITH_PRECEDENCE(ARITH_BAND, 5), + ARITH_PRECEDENCE(ARITH_BXOR, 6), + ARITH_PRECEDENCE(ARITH_BOR, 7), +}; + +#define ARITH_MAX_PREC 8 + +int letcmd(int, char **); + +static __attribute__((noreturn)) void yyerror(const char *s) +{ + error("arithmetic expression: %s: \"%s\"", s, arith_startbuf); + /* NOTREACHED */ +} + +static arith_t arith_lookupvarint(char *varname) +{ + const char *str; + char *p; + arith_t result; + + str = lookupvar(varname); + if (uflag && str == NULL) + yyerror("variable not set"); + if (str == NULL || *str == '\0') + str = "0"; + errno = 0; + result = strtoarith_t(str, &p); + if (errno != 0 || *p != '\0') + yyerror("variable conversion error"); + return result; +} + +static inline int arith_prec(int op) +{ + return prec[op - ARITH_BINOP_MIN]; +} + +static inline int higher_prec(int op1, int op2) +{ + return arith_prec(op1) < arith_prec(op2); +} + +static arith_t do_binop(int op, arith_t a, arith_t b) +{ + + switch (op) { + default: + case ARITH_REM: + case ARITH_DIV: + if (!b) + yyerror("division by zero"); + if (a == ARITH_MIN && b == -1) + yyerror("divide error"); + return op == ARITH_REM ? a % b : a / b; + case ARITH_MUL: + return (uintmax_t)a * (uintmax_t)b; + case ARITH_ADD: + return (uintmax_t)a + (uintmax_t)b; + case ARITH_SUB: + return (uintmax_t)a - (uintmax_t)b; + case ARITH_LSHIFT: + return (uintmax_t)a << (b & (sizeof(uintmax_t) * CHAR_BIT - 1)); + case ARITH_RSHIFT: + return a >> (b & (sizeof(uintmax_t) * CHAR_BIT - 1)); + case ARITH_LT: + return a < b; + case ARITH_LE: + return a <= b; + case ARITH_GT: + return a > b; + case ARITH_GE: + return a >= b; + case ARITH_EQ: + return a == b; + case ARITH_NE: + return a != b; + case ARITH_BAND: + return a & b; + case ARITH_BXOR: + return a ^ b; + case ARITH_BOR: + return a | b; + } +} + +static arith_t assignment(int var, int noeval); + +static arith_t primary(int token, union yystype *val, int op, int noeval) +{ + arith_t result; + +again: + switch (token) { + case ARITH_LPAREN: + result = assignment(op, noeval); + if (last_token != ARITH_RPAREN) + yyerror("expecting ')'"); + last_token = yylex(); + return result; + case ARITH_NUM: + last_token = op; + return val->val; + case ARITH_VAR: + last_token = op; + return noeval ? val->val : arith_lookupvarint(val->name); + case ARITH_ADD: + token = op; + *val = yylval; + op = yylex(); + goto again; + case ARITH_SUB: + *val = yylval; + return -primary(op, val, yylex(), noeval); + case ARITH_NOT: + *val = yylval; + return !primary(op, val, yylex(), noeval); + case ARITH_BNOT: + *val = yylval; + return ~primary(op, val, yylex(), noeval); + default: + yyerror("expecting primary"); + } +} + +static arith_t binop2(arith_t a, int op, int precedence, int noeval) +{ + for (;;) { + union yystype val; + arith_t b; + int op2; + int token; + + token = yylex(); + val = yylval; + + b = primary(token, &val, yylex(), noeval); + + op2 = last_token; + if (op2 >= ARITH_BINOP_MIN && op2 < ARITH_BINOP_MAX && + higher_prec(op2, op)) { + b = binop2(b, op2, arith_prec(op), noeval); + op2 = last_token; + } + + a = noeval ? b : do_binop(op, a, b); + + if (op2 < ARITH_BINOP_MIN || op2 >= ARITH_BINOP_MAX || + arith_prec(op2) >= precedence) + return a; + + op = op2; + } +} + +static arith_t binop(int token, union yystype *val, int op, int noeval) +{ + arith_t a = primary(token, val, op, noeval); + + op = last_token; + if (op < ARITH_BINOP_MIN || op >= ARITH_BINOP_MAX) + return a; + + return binop2(a, op, ARITH_MAX_PREC, noeval); +} + +static arith_t and(int token, union yystype *val, int op, int noeval) +{ + arith_t a = binop(token, val, op, noeval); + arith_t b; + + op = last_token; + if (op != ARITH_AND) + return a; + + token = yylex(); + *val = yylval; + + b = and(token, val, yylex(), noeval | !a); + + return a && b; +} + +static arith_t or(int token, union yystype *val, int op, int noeval) +{ + arith_t a = and(token, val, op, noeval); + arith_t b; + + op = last_token; + if (op != ARITH_OR) + return a; + + token = yylex(); + *val = yylval; + + b = or(token, val, yylex(), noeval | !!a); + + return a || b; +} + +static arith_t cond(int token, union yystype *val, int op, int noeval) +{ + arith_t a = or(token, val, op, noeval); + arith_t b; + arith_t c; + + if (last_token != ARITH_QMARK) + return a; + + b = assignment(yylex(), noeval | !a); + + if (last_token != ARITH_COLON) + yyerror("expecting ':'"); + + token = yylex(); + *val = yylval; + + c = cond(token, val, yylex(), noeval | !!a); + + return a ? b : c; +} + +static arith_t assignment(int var, int noeval) +{ + union yystype val = yylval; + int op = yylex(); + arith_t result; + char sresult[DIGITS(result) + 1]; + + if (var != ARITH_VAR) + return cond(var, &val, op, noeval); + + if (op != ARITH_ASS && (op < ARITH_ASS_MIN || op >= ARITH_ASS_MAX)) + return cond(var, &val, op, noeval); + + result = assignment(yylex(), noeval); + if (noeval) + return result; + + if (op != ARITH_ASS) + result = do_binop(op - 11, arith_lookupvarint(val.name), result); + snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR, result); + setvar(val.name, sresult, 0); + return result; +} + +arith_t arith(const char *s) +{ + struct stackmark smark; + arith_t result; + + setstackmark(&smark); + + arith_buf = arith_startbuf = s; + + result = assignment(yylex(), 0); + + if (last_token) + yyerror("expecting EOF"); + + popstackmark(&smark); + + return result; +} + +/* + * The exp(1) builtin. + */ +int +letcmd(int argc, char **argv) +{ + const char *p; + char *concat; + char **ap; + arith_t i; + + if (argc > 1) { + p = argv[1]; + if (argc > 2) { + /* + * Concatenate arguments. + */ + STARTSTACKSTR(concat); + ap = argv + 2; + for (;;) { + while (*p) + STPUTC(*p++, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + } else + p = ""; + + i = arith(p); + + out1fmt(ARITH_FORMAT_STR "\n", i); + return !i; +} diff --git a/bin/1sh/arith_yacc.h b/bin/1sh/arith_yacc.h new file mode 100644 index 00000000..433481bb --- /dev/null +++ b/bin/1sh/arith_yacc.h @@ -0,0 +1,94 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2007 + * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: releng/12.1/bin/sh/arith_yacc.h 345117 2019-03-13 21:53:10Z jilles $ + */ + +#define ARITH_ASS 1 + +#define ARITH_OR 2 +#define ARITH_AND 3 +#define ARITH_BAD 4 +#define ARITH_NUM 5 +#define ARITH_VAR 6 +#define ARITH_NOT 7 + +#define ARITH_BINOP_MIN 8 +#define ARITH_LE 8 +#define ARITH_GE 9 +#define ARITH_LT 10 +#define ARITH_GT 11 +#define ARITH_EQ 12 +#define ARITH_REM 13 +#define ARITH_BAND 14 +#define ARITH_LSHIFT 15 +#define ARITH_RSHIFT 16 +#define ARITH_MUL 17 +#define ARITH_ADD 18 +#define ARITH_BOR 19 +#define ARITH_SUB 20 +#define ARITH_BXOR 21 +#define ARITH_DIV 22 +#define ARITH_NE 23 +#define ARITH_BINOP_MAX 24 + +#define ARITH_ASS_MIN 24 +#define ARITH_REMASS 24 +#define ARITH_BANDASS 25 +#define ARITH_LSHIFTASS 26 +#define ARITH_RSHIFTASS 27 +#define ARITH_MULASS 28 +#define ARITH_ADDASS 29 +#define ARITH_BORASS 30 +#define ARITH_SUBASS 31 +#define ARITH_BXORASS 32 +#define ARITH_DIVASS 33 +#define ARITH_ASS_MAX 34 + +#define ARITH_LPAREN 34 +#define ARITH_RPAREN 35 +#define ARITH_BNOT 36 +#define ARITH_QMARK 37 +#define ARITH_COLON 38 + +extern const char *arith_buf; + +union yystype { + arith_t val; + char *name; +}; + +extern union yystype yylval; + +arith_t strtoarith_t(const char *restrict nptr, char **restrict endptr); +int yylex(void); diff --git a/bin/1sh/arith_yylex.c b/bin/1sh/arith_yylex.c new file mode 100644 index 00000000..da3a4f70 --- /dev/null +++ b/bin/1sh/arith_yylex.c @@ -0,0 +1,276 @@ +/*- + * Copyright (c) 2002 + * Herbert Xu. + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/arith_yylex.c 345117 2019-03-13 21:53:10Z jilles $ */ + +#include <ctype.h> +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> +#include "shell.h" +#include "arith_yacc.h" +#include "expand.h" +#include "error.h" +#include "memalloc.h" +#include "parser.h" +#include "syntax.h" + +#if ARITH_BOR + 11 != ARITH_BORASS || ARITH_ASS + 11 != ARITH_EQ +#error Arithmetic tokens are out of order. +#endif + +arith_t +strtoarith_t(const char *restrict nptr, char **restrict endptr) +{ + arith_t val; + + while (isspace((unsigned char)*nptr)) + nptr++; + switch (*nptr) { + case '-': + return strtoimax(nptr, endptr, 0); + case '0': + return (arith_t)strtoumax(nptr, endptr, 0); + default: + val = (arith_t)strtoumax(nptr, endptr, 0); + if (val >= 0) + return val; + else if (val == ARITH_MIN) { + errno = ERANGE; + return ARITH_MIN; + } else { + errno = ERANGE; + return ARITH_MAX; + } + } +} + +int +yylex(void) +{ + int value; + const char *buf = arith_buf; + char *end; + const char *p; + + for (;;) { + value = *buf; + switch (value) { + case ' ': + case '\t': + case '\n': + buf++; + continue; + default: + return ARITH_BAD; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + yylval.val = strtoarith_t(buf, &end); + arith_buf = end; + return ARITH_NUM; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + p = buf; + while (buf++, is_in_name(*buf)) + ; + yylval.name = stalloc(buf - p + 1); + memcpy(yylval.name, p, buf - p); + yylval.name[buf - p] = '\0'; + value = ARITH_VAR; + goto out; + case '=': + value += ARITH_ASS - '='; +checkeq: + buf++; +checkeqcur: + if (*buf != '=') + goto out; + value += 11; + break; + case '>': + switch (*++buf) { + case '=': + value += ARITH_GE - '>'; + break; + case '>': + value += ARITH_RSHIFT - '>'; + goto checkeq; + default: + value += ARITH_GT - '>'; + goto out; + } + break; + case '<': + switch (*++buf) { + case '=': + value += ARITH_LE - '<'; + break; + case '<': + value += ARITH_LSHIFT - '<'; + goto checkeq; + default: + value += ARITH_LT - '<'; + goto out; + } + break; + case '|': + if (*++buf != '|') { + value += ARITH_BOR - '|'; + goto checkeqcur; + } + value += ARITH_OR - '|'; + break; + case '&': + if (*++buf != '&') { + value += ARITH_BAND - '&'; + goto checkeqcur; + } + value += ARITH_AND - '&'; + break; + case '!': + if (*++buf != '=') { + value += ARITH_NOT - '!'; + goto out; + } + value += ARITH_NE - '!'; + break; + case 0: + goto out; + case '(': + value += ARITH_LPAREN - '('; + break; + case ')': + value += ARITH_RPAREN - ')'; + break; + case '*': + value += ARITH_MUL - '*'; + goto checkeq; + case '/': + value += ARITH_DIV - '/'; + goto checkeq; + case '%': + value += ARITH_REM - '%'; + goto checkeq; + case '+': + if (buf[1] == '+') + return ARITH_BAD; + value += ARITH_ADD - '+'; + goto checkeq; + case '-': + if (buf[1] == '-') + return ARITH_BAD; + value += ARITH_SUB - '-'; + goto checkeq; + case '~': + value += ARITH_BNOT - '~'; + break; + case '^': + value += ARITH_BXOR - '^'; + goto checkeq; + case '?': + value += ARITH_QMARK - '?'; + break; + case ':': + value += ARITH_COLON - ':'; + break; + } + break; + } + + buf++; +out: + arith_buf = buf; + return value; +} diff --git a/bin/1sh/bltin.h b/bin/1sh/bltin.h new file mode 100644 index 00000000..3d2cb4d3 --- /dev/null +++ b/bin/1sh/bltin.h @@ -0,0 +1,81 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)bltin.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/bltin/bltin.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* + * This file is included by programs which are optionally built into the + * shell. If SHELL is defined, we try to map the standard UNIX library + * routines to ash routines using defines. + */ + +#include "shell.h" +#include "mystring.h" +#ifdef SHELL +#include "error.h" +#include "output.h" +#include "builtins.h" +#define FILE struct output +#undef stdout +#define stdout out1 +#undef stderr +#define stderr out2 +#define printf out1fmt +#undef putc +#define putc(c, file) outc(c, file) +#undef putchar +#define putchar(c) out1c(c) +#define fprintf outfmt +#define fputs outstr +#define fwrite(ptr, size, nmemb, file) outbin(ptr, (size) * (nmemb), file) +#define fflush flushout +#define INITARGS(argv) +#define warnx warning +#define warn(fmt, ...) warning(fmt ": %s", __VA_ARGS__, strerror(errno)) +#define errx(exitstatus, ...) error(__VA_ARGS__) + +#else +#undef NULL +#include <stdio.h> +#undef main +#define INITARGS(argv) if ((commandname = argv[0]) == NULL) {fputs("Argc is zero\n", stderr); exit(2);} else +#endif + +#include <unistd.h> + +pointer stalloc(int); +int killjob(const char *, int); + +extern char *commandname; diff --git a/bin/1sh/builtins.def b/bin/1sh/builtins.def new file mode 100644 index 00000000..5986a5a6 --- /dev/null +++ b/bin/1sh/builtins.def @@ -0,0 +1,96 @@ +#!/bin/sh - + +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)builtins.def 8.4 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/builtins.def 319576 2017-06-04 21:02:48Z bdrewery $ + +# +# This file lists all the builtin commands. The first column is the name +# of a C routine. +# The -j flag specifies that this command is to be excluded from systems +# without job control. +# The -h flag specifies that this command is to be excluded from systems +# based on the NO_HISTORY compile-time symbol. +# The -n flag specifies that this command can safely be run in the same +# process when it is the only command in a command substitution. Some +# commands have special logic defined in safe_builtin(). +# The -s flag specifies that this is a POSIX 'special built-in' command. +# The rest of the line specifies the command name or names used to run the +# command. The entry for bltincmd, which is run when the user does not specify +# a command, must come first. +# +# NOTE: bltincmd must come first! + +bltincmd -n builtin +aliascmd alias +bgcmd -j bg +bindcmd bind +breakcmd -s break -s continue +cdcmd cd chdir +commandcmd -n command +dotcmd -s . +echocmd -n echo +evalcmd -s eval +execcmd -s exec +exitcmd -s exit +letcmd let +exportcmd -s export -s readonly +#exprcmd expr +falsecmd -n false +fgcmd -j fg +freebsd_wordexpcmd freebsd_wordexp +getoptscmd getopts +hashcmd hash +histcmd -h fc +jobidcmd -n jobid +jobscmd -n jobs +killcmd -n kill +localcmd local +printfcmd -n printf +pwdcmd -n pwd +readcmd read +returncmd -s return +setcmd -s set +setvarcmd setvar +shiftcmd -s shift +testcmd -n test [ +timescmd -n -s times +trapcmd -s trap +truecmd -n -s : true +typecmd -n type +ulimitcmd ulimit +umaskcmd umask +unaliascmd unalias +unsetcmd -s unset +waitcmd wait +wordexpcmd wordexp diff --git a/bin/1sh/cd.c b/bin/1sh/cd.c new file mode 100644 index 00000000..61bb60cf --- /dev/null +++ b/bin/1sh/cd.c @@ -0,0 +1,430 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)cd.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/cd.c 336320 2018-07-15 21:55:17Z jilles $ */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> + +/* + * The cd and pwd commands. + */ + +#include "shell.h" +#include "var.h" +#include "nodes.h" /* for jobs.h */ +#include "jobs.h" +#include "options.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "exec.h" +#include "redir.h" +#include "mystring.h" +#include "show.h" +#include "cd.h" +#include "builtins.h" + +static int cdlogical(char *); +static int cdphysical(char *); +static int docd(char *, int, int); +static char *getcomponent(char **); +static char *findcwd(char *); +static void updatepwd(char *); +static char *getpwd(void); +static char *getpwd2(void); + +static char *curdir = NULL; /* current working directory */ + +int +cdcmd(int argc __unused, char **argv __unused) +{ + const char *dest; + const char *path; + char *p; + struct stat statb; + int ch, phys, print = 0, getcwderr = 0; + int rc; + int errno1 = ENOENT; + + phys = Pflag; + while ((ch = nextopt("eLP")) != '\0') { + switch (ch) { + case 'e': + getcwderr = 1; + break; + case 'L': + phys = 0; + break; + case 'P': + phys = 1; + break; + } + } + + if (*argptr != NULL && argptr[1] != NULL) + error("too many arguments"); + + if ((dest = *argptr) == NULL && (dest = bltinlookup("HOME", 1)) == NULL) + error("HOME not set"); + if (*dest == '\0') + dest = "."; + if (dest[0] == '-' && dest[1] == '\0') { + dest = bltinlookup("OLDPWD", 1); + if (dest == NULL) + error("OLDPWD not set"); + print = 1; + } + if (dest[0] == '/' || + (dest[0] == '.' && (dest[1] == '/' || dest[1] == '\0')) || + (dest[0] == '.' && dest[1] == '.' && (dest[2] == '/' || dest[2] == '\0')) || + (path = bltinlookup("CDPATH", 1)) == NULL) + path = ""; + while ((p = padvance(&path, NULL, dest)) != NULL) { + if (stat(p, &statb) < 0) { + if (errno != ENOENT) + errno1 = errno; + } else if (!S_ISDIR(statb.st_mode)) + errno1 = ENOTDIR; + else { + if (!print) { + /* + * XXX - rethink + */ + if (p[0] == '.' && p[1] == '/' && p[2] != '\0') + print = strcmp(p + 2, dest); + else + print = strcmp(p, dest); + } + rc = docd(p, print, phys); + if (rc >= 0) + return getcwderr ? rc : 0; + if (errno != ENOENT) + errno1 = errno; + } + } + error("%s: %s", dest, strerror(errno1)); + /*NOTREACHED*/ + return 0; +} + + +/* + * Actually change the directory. In an interactive shell, print the + * directory name if "print" is nonzero. + */ +static int +docd(char *dest, int print, int phys) +{ + int rc; + + TRACE(("docd(\"%s\", %d, %d) called\n", dest, print, phys)); + + /* If logical cd fails, fall back to physical. */ + if ((phys || (rc = cdlogical(dest)) < 0) && (rc = cdphysical(dest)) < 0) + return (-1); + + if (print && iflag && curdir) { + out1fmt("%s\n", curdir); + /* + * Ignore write errors to preserve the invariant that the + * current directory is changed iff the exit status is 0 + * (or 1 if -e was given and the full pathname could not be + * determined). + */ + flushout(out1); + outclearerror(out1); + } + + return (rc); +} + +static int +cdlogical(char *dest) +{ + char *p; + char *q; + char *component; + char *path; + struct stat statb; + int first; + int badstat; + + /* + * Check each component of the path. If we find a symlink or + * something we can't stat, clear curdir to force a getcwd() + * next time we get the value of the current directory. + */ + badstat = 0; + path = stsavestr(dest); + STARTSTACKSTR(p); + if (*dest == '/') { + STPUTC('/', p); + path++; + } + first = 1; + while ((q = getcomponent(&path)) != NULL) { + if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0')) + continue; + if (! first) + STPUTC('/', p); + first = 0; + component = q; + STPUTS(q, p); + if (equal(component, "..")) + continue; + STACKSTRNUL(p); + if (lstat(stackblock(), &statb) < 0) { + badstat = 1; + break; + } + } + + INTOFF; + if ((p = findcwd(badstat ? NULL : dest)) == NULL || chdir(p) < 0) { + INTON; + return (-1); + } + updatepwd(p); + INTON; + return (0); +} + +static int +cdphysical(char *dest) +{ + char *p; + int rc = 0; + + INTOFF; + if (chdir(dest) < 0) { + INTON; + return (-1); + } + p = findcwd(NULL); + if (p == NULL) { + warning("warning: failed to get name of current directory"); + rc = 1; + } + updatepwd(p); + INTON; + return (rc); +} + +/* + * Get the next component of the path name pointed to by *path. + * This routine overwrites *path and the string pointed to by it. + */ +static char * +getcomponent(char **path) +{ + char *p; + char *start; + + if ((p = *path) == NULL) + return NULL; + start = *path; + while (*p != '/' && *p != '\0') + p++; + if (*p == '\0') { + *path = NULL; + } else { + *p++ = '\0'; + *path = p; + } + return start; +} + + +static char * +findcwd(char *dir) +{ + char *new; + char *p; + char *path; + + /* + * If our argument is NULL, we don't know the current directory + * any more because we traversed a symbolic link or something + * we couldn't stat(). + */ + if (dir == NULL || curdir == NULL) + return getpwd2(); + path = stsavestr(dir); + STARTSTACKSTR(new); + if (*dir != '/') { + STPUTS(curdir, new); + if (STTOPC(new) == '/') + STUNPUTC(new); + } + while ((p = getcomponent(&path)) != NULL) { + if (equal(p, "..")) { + while (new > stackblock() && (STUNPUTC(new), *new) != '/'); + } else if (*p != '\0' && ! equal(p, ".")) { + STPUTC('/', new); + STPUTS(p, new); + } + } + if (new == stackblock()) + STPUTC('/', new); + STACKSTRNUL(new); + return stackblock(); +} + +/* + * Update curdir (the name of the current directory) in response to a + * cd command. We also call hashcd to let the routines in exec.c know + * that the current directory has changed. + */ +static void +updatepwd(char *dir) +{ + char *prevdir; + + hashcd(); /* update command hash table */ + + setvar("PWD", dir, VEXPORT); + setvar("OLDPWD", curdir, VEXPORT); + prevdir = curdir; + curdir = dir ? savestr(dir) : NULL; + ckfree(prevdir); +} + +int +pwdcmd(int argc __unused, char **argv __unused) +{ + char *p; + int ch, phys; + + phys = Pflag; + while ((ch = nextopt("LP")) != '\0') { + switch (ch) { + case 'L': + phys = 0; + break; + case 'P': + phys = 1; + break; + } + } + + if (*argptr != NULL) + error("too many arguments"); + + if (!phys && getpwd()) { + out1str(curdir); + out1c('\n'); + } else { + if ((p = getpwd2()) == NULL) + error(".: %s", strerror(errno)); + out1str(p); + out1c('\n'); + } + + return 0; +} + +/* + * Get the current directory and cache the result in curdir. + */ +static char * +getpwd(void) +{ + char *p; + + if (curdir) + return curdir; + + p = getpwd2(); + if (p != NULL) + curdir = savestr(p); + + return curdir; +} + +#define MAXPWD 256 + +/* + * Return the current directory. + */ +static char * +getpwd2(void) +{ + char *pwd; + int i; + + for (i = MAXPWD;; i *= 2) { + pwd = stalloc(i); + if (getcwd(pwd, i) != NULL) + return pwd; + stunalloc(pwd); + if (errno != ERANGE) + break; + } + + return NULL; +} + +/* + * Initialize PWD in a new shell. + * If the shell is interactive, we need to warn if this fails. + */ +void +pwd_init(int warn) +{ + char *pwd; + struct stat stdot, stpwd; + + pwd = lookupvar("PWD"); + if (pwd && *pwd == '/' && stat(".", &stdot) != -1 && + stat(pwd, &stpwd) != -1 && + stdot.st_dev == stpwd.st_dev && + stdot.st_ino == stpwd.st_ino) { + if (curdir) + ckfree(curdir); + curdir = savestr(pwd); + } + if (getpwd() == NULL && warn) + out2fmt_flush("sh: cannot determine working directory\n"); + setvar("PWD", curdir, VEXPORT); +} diff --git a/bin/1sh/cd.h b/bin/1sh/cd.h new file mode 100644 index 00000000..dfa5c3e5 --- /dev/null +++ b/bin/1sh/cd.h @@ -0,0 +1,32 @@ +/*- + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: releng/12.1/bin/sh/cd.h 314436 2017-02-28 23:42:47Z imp $ + */ + +void pwd_init(int); diff --git a/bin/1sh/echo.c b/bin/1sh/echo.c new file mode 100644 index 00000000..5fd50f59 --- /dev/null +++ b/bin/1sh/echo.c @@ -0,0 +1,109 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)echo.c 8.2 (Berkeley) 5/4/95 + */ + +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/bltin/echo.c 326025 2017-11-20 19:49:47Z pfg $ */ + +/* + * Echo command. + */ + +#define main echocmd + +#include "bltin.h" + +/* #define eflag 1 */ + +int +main(int argc, char *argv[]) +{ + char **ap; + char *p; + char c; + int count; + int nflag = 0; +#ifndef eflag + int eflag = 0; +#endif + + ap = argv; + if (argc) + ap++; + if ((p = *ap) != NULL) { + if (equal(p, "-n")) { + nflag++; + ap++; + } else if (equal(p, "-e")) { +#ifndef eflag + eflag++; +#endif + ap++; + } + } + while ((p = *ap++) != NULL) { + while ((c = *p++) != '\0') { + if (c == '\\' && eflag) { + switch (*p++) { + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 'c': return 0; /* exit */ + case 'e': c = '\033'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case '\\': break; /* c = '\\' */ + case '0': + c = 0; + count = 3; + while (--count >= 0 && (unsigned)(*p - '0') < 8) + c = (c << 3) + (*p++ - '0'); + break; + default: + p--; + break; + } + } + putchar(c); + } + if (*ap) + putchar(' '); + } + if (! nflag) + putchar('\n'); + return 0; +} diff --git a/bin/1sh/error.c b/bin/1sh/error.c new file mode 100644 index 00000000..57c10fae --- /dev/null +++ b/bin/1sh/error.c @@ -0,0 +1,199 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)error.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/error.c 314436 2017-02-28 23:42:47Z imp $ */ + +/* + * Errors and exceptions. + */ + +#include "shell.h" +#include "eval.h" +#include "main.h" +#include "options.h" +#include "output.h" +#include "error.h" +#include "nodes.h" /* show.h needs nodes.h */ +#include "show.h" +#include "trap.h" +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> + + +/* + * Code to handle exceptions in C. + */ + +struct jmploc *handler; +volatile sig_atomic_t exception; +volatile sig_atomic_t suppressint; +volatile sig_atomic_t intpending; + + +static void exverror(int, const char *, va_list) __attribute__((format(printf, 2, 0))) __attribute__((noreturn)); + +/* + * Called to raise an exception. Since C doesn't include exceptions, we + * just do a longjmp to the exception handler. The type of exception is + * stored in the global variable "exception". + * + * Interrupts are disabled; they should be reenabled when the exception is + * caught. + */ + +void +exraise(int e) +{ + INTOFF; + if (handler == NULL) + abort(); + exception = e; + longjmp(handler->loc, 1); +} + + +/* + * Called from trap.c when a SIGINT is received and not suppressed, or when + * an interrupt is pending and interrupts are re-enabled using INTON. + * (If the user specifies that SIGINT is to be trapped or ignored using the + * trap builtin, then this routine is not called.) Suppressint is nonzero + * when interrupts are held using the INTOFF macro. If SIGINTs are not + * suppressed and the shell is not a root shell, then we want to be + * terminated if we get here, as if we were terminated directly by a SIGINT. + * Arrange for this here. + */ + +void +onint(void) +{ + sigset_t sigs; + + intpending = 0; + sigemptyset(&sigs); + sigprocmask(SIG_SETMASK, &sigs, NULL); + + /* + * This doesn't seem to be needed, since main() emits a newline. + */ +#if 0 + if (tcgetpgrp(0) == getpid()) + write(STDERR_FILENO, "\n", 1); +#endif + if (rootshell && iflag) + exraise(EXINT); + else { + signal(SIGINT, SIG_DFL); + kill(getpid(), SIGINT); + _exit(128 + SIGINT); + } +} + + +static void +vwarning(const char *msg, va_list ap) +{ + if (commandname) + outfmt(out2, "%s: ", commandname); + else if (arg0) + outfmt(out2, "%s: ", arg0); + doformat(out2, msg, ap); + out2fmt_flush("\n"); +} + + +void +warning(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + vwarning(msg, ap); + va_end(ap); +} + + +/* + * Exverror is called to raise the error exception. If the first argument + * is not NULL then error prints an error message using printf style + * formatting. It then raises the error exception. + */ +static void +exverror(int cond, const char *msg, va_list ap) +{ + /* + * An interrupt trumps an error. Certain places catch error + * exceptions or transform them to a plain nonzero exit code + * in child processes, and if an error exception can be handled, + * an interrupt can be handled as well. + * + * exraise() will disable interrupts for the exception handler. + */ + FORCEINTON; + +#ifdef DEBUG + if (msg) + TRACE(("exverror(%d, \"%s\") pid=%d\n", cond, msg, getpid())); + else + TRACE(("exverror(%d, NULL) pid=%d\n", cond, getpid())); +#endif + if (msg) + vwarning(msg, ap); + flushall(); + exraise(cond); +} + + +void +error(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + exverror(EXERROR, msg, ap); + va_end(ap); +} + + +void +exerror(int cond, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + exverror(cond, msg, ap); + va_end(ap); +} diff --git a/bin/1sh/error.h b/bin/1sh/error.h new file mode 100644 index 00000000..0401598f --- /dev/null +++ b/bin/1sh/error.h @@ -0,0 +1,95 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)error.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/error.h 319591 2017-06-04 21:58:02Z jilles $ + */ + +/* + * We enclose jmp_buf in a structure so that we can declare pointers to + * jump locations. The global variable handler contains the location to + * jump to when an exception occurs, and the global variable exception + * contains a code identifying the exception. To implement nested + * exception handlers, the user should save the value of handler on entry + * to an inner scope, set handler to point to a jmploc structure for the + * inner scope, and restore handler on exit from the scope. + */ + +#include <setjmp.h> +#include <signal.h> + +struct jmploc { + jmp_buf loc; +}; + +extern struct jmploc *handler; +extern volatile sig_atomic_t exception; + +/* exceptions */ +#define EXINT 0 /* SIGINT received */ +#define EXERROR 1 /* a generic error */ +#define EXEXEC 2 /* command execution failed */ +#define EXEXIT 3 /* call exitshell(exitstatus) */ + + +/* + * These macros allow the user to suspend the handling of interrupt signals + * over a period of time. This is similar to SIGHOLD to or sigblock, but + * much more efficient and portable. (But hacking the kernel is so much + * more fun than worrying about efficiency and portability. :-)) + */ + +extern volatile sig_atomic_t suppressint; +extern volatile sig_atomic_t intpending; + +#define INTOFF suppressint++ +#define INTON { if (--suppressint == 0 && intpending) onint(); } +#define is_int_on() suppressint +#define SETINTON(s) do { suppressint = (s); if (suppressint == 0 && intpending) onint(); } while (0) +#define FORCEINTON {suppressint = 0; if (intpending) onint();} +#define SET_PENDING_INT intpending = 1 +#define CLEAR_PENDING_INT intpending = 0 +#define int_pending() intpending + +void exraise(int) __attribute__((noreturn)); +void onint(void) __attribute__((noreturn)); +void warning(const char *, ...) __attribute__((format(printf, 1, 2))); +void error(const char *, ...) __attribute__((format(printf, 1, 2))) __attribute__((noreturn)); +void exerror(int, const char *, ...) __attribute__((format(printf, 2, 3))) __attribute__((noreturn)); + + +/* + * BSD setjmp saves the signal mask, which violates ANSI C and takes time, + * so we use _setjmp instead. + */ + +#define setjmp(jmploc) _setjmp(jmploc) +#define longjmp(jmploc, val) _longjmp(jmploc, val) diff --git a/bin/1sh/eval.c b/bin/1sh/eval.c new file mode 100644 index 00000000..f4baf39e --- /dev/null +++ b/bin/1sh/eval.c @@ -0,0 +1,1381 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)eval.c 8.9 (Berkeley) 6/8/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/eval.c 327212 2017-12-26 16:23:18Z jilles $ */ + +#include <paths.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/resource.h> +#include <errno.h> + +/* + * Evaluate a command. + */ + +#include "shell.h" +#include "nodes.h" +#include "syntax.h" +#include "expand.h" +#include "parser.h" +#include "jobs.h" +#include "eval.h" +#include "builtins.h" +#include "options.h" +#include "exec.h" +#include "redir.h" +#include "input.h" +#include "output.h" +#include "trap.h" +#include "var.h" +#include "memalloc.h" +#include "error.h" +#include "show.h" +#include "mystring.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + + +int evalskip; /* set if we are skipping commands */ +int skipcount; /* number of levels to skip */ +static int loopnest; /* current loop nesting level */ +int funcnest; /* depth of function calls */ +static int builtin_flags; /* evalcommand flags for builtins */ + + +char *commandname; +struct arglist *cmdenviron; +int exitstatus; /* exit status of last command */ +int oexitstatus; /* saved exit status */ + + +static void evalloop(union node *, int); +static void evalfor(union node *, int); +static union node *evalcase(union node *); +static void evalsubshell(union node *, int); +static void evalredir(union node *, int); +static void exphere(union node *, struct arglist *); +static void expredir(union node *); +static void evalpipe(union node *); +static int is_valid_fast_cmdsubst(union node *n); +static void evalcommand(union node *, int, struct backcmd *); +static void prehash(union node *); + + +/* + * Called to reset things after an exception. + */ + +void +reseteval(void) +{ + evalskip = 0; + loopnest = 0; +} + + +/* + * The eval command. + */ + +int +evalcmd(int argc, char **argv) +{ + char *p; + char *concat; + char **ap; + + if (argc > 1) { + p = argv[1]; + if (argc > 2) { + STARTSTACKSTR(concat); + ap = argv + 2; + for (;;) { + STPUTS(p, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + evalstring(p, builtin_flags); + } else + exitstatus = 0; + return exitstatus; +} + + +/* + * Execute a command or commands contained in a string. + */ + +void +evalstring(const char *s, int flags) +{ + union node *n; + struct stackmark smark; + int flags_exit; + int any; + + flags_exit = flags & EV_EXIT; + flags &= ~EV_EXIT; + any = 0; + setstackmark(&smark); + setinputstring(s, 1); + while ((n = parsecmd(0)) != NEOF) { + if (n != NULL && !nflag) { + if (flags_exit && preadateof()) + evaltree(n, flags | EV_EXIT); + else + evaltree(n, flags); + any = 1; + if (evalskip) + break; + } + popstackmark(&smark); + setstackmark(&smark); + } + popfile(); + popstackmark(&smark); + if (!any) + exitstatus = 0; + if (flags_exit) + exraise(EXEXIT); +} + + +/* + * Evaluate a parse tree. The value is left in the global variable + * exitstatus. + */ + +void +evaltree(union node *n, int flags) +{ + int do_etest; + union node *next; + struct stackmark smark; + + setstackmark(&smark); + do_etest = 0; + if (n == NULL) { + TRACE(("evaltree(NULL) called\n")); + exitstatus = 0; + goto out; + } + do { + next = NULL; +#ifndef NO_HISTORY + displayhist = 1; /* show history substitutions done with fc */ +#endif + TRACE(("evaltree(%p: %d) called\n", (void *)n, n->type)); + switch (n->type) { + case NSEMI: + evaltree(n->nbinary.ch1, flags & ~EV_EXIT); + if (evalskip) + goto out; + next = n->nbinary.ch2; + break; + case NAND: + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip || exitstatus != 0) { + goto out; + } + next = n->nbinary.ch2; + break; + case NOR: + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip || exitstatus == 0) + goto out; + next = n->nbinary.ch2; + break; + case NREDIR: + evalredir(n, flags); + break; + case NSUBSHELL: + evalsubshell(n, flags); + do_etest = !(flags & EV_TESTED); + break; + case NBACKGND: + evalsubshell(n, flags); + break; + case NIF: { + evaltree(n->nif.test, EV_TESTED); + if (evalskip) + goto out; + if (exitstatus == 0) + next = n->nif.ifpart; + else if (n->nif.elsepart) + next = n->nif.elsepart; + else + exitstatus = 0; + break; + } + case NWHILE: + case NUNTIL: + evalloop(n, flags & ~EV_EXIT); + break; + case NFOR: + evalfor(n, flags & ~EV_EXIT); + break; + case NCASE: + next = evalcase(n); + break; + case NCLIST: + next = n->nclist.body; + break; + case NCLISTFALLTHRU: + if (n->nclist.body) { + evaltree(n->nclist.body, flags & ~EV_EXIT); + if (evalskip) + goto out; + } + next = n->nclist.next; + break; + case NDEFUN: + defun(n->narg.text, n->narg.next); + exitstatus = 0; + break; + case NNOT: + evaltree(n->nnot.com, EV_TESTED); + if (evalskip) + goto out; + exitstatus = !exitstatus; + break; + + case NPIPE: + evalpipe(n); + do_etest = !(flags & EV_TESTED); + break; + case NCMD: + evalcommand(n, flags, (struct backcmd *)NULL); + do_etest = !(flags & EV_TESTED); + break; + default: + out1fmt("Node type = %d\n", n->type); + flushout(&output); + break; + } + n = next; + popstackmark(&smark); + setstackmark(&smark); + } while (n != NULL); +out: + popstackmark(&smark); + if (pendingsig) + dotrap(); + if (eflag && exitstatus != 0 && do_etest) + exitshell(exitstatus); + if (flags & EV_EXIT) + exraise(EXEXIT); +} + + +static void +evalloop(union node *n, int flags) +{ + int status; + + loopnest++; + status = 0; + for (;;) { + if (!evalskip) + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + if (evalskip == SKIPRETURN) + status = exitstatus; + break; + } + if (n->type == NWHILE) { + if (exitstatus != 0) + break; + } else { + if (exitstatus == 0) + break; + } + evaltree(n->nbinary.ch2, flags); + status = exitstatus; + } + loopnest--; + exitstatus = status; +} + + + +static void +evalfor(union node *n, int flags) +{ + struct arglist arglist; + union node *argp; + int i; + int status; + + emptyarglist(&arglist); + for (argp = n->nfor.args ; argp ; argp = argp->narg.next) { + oexitstatus = exitstatus; + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + } + + loopnest++; + status = 0; + for (i = 0; i < arglist.count; i++) { + setvar(n->nfor.var, arglist.args[i], 0); + evaltree(n->nfor.body, flags); + status = exitstatus; + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + break; + } + } + loopnest--; + exitstatus = status; +} + + +/* + * Evaluate a case statement, returning the selected tree. + * + * The exit status needs care to get right. + */ + +static union node * +evalcase(union node *n) +{ + union node *cp; + union node *patp; + struct arglist arglist; + + emptyarglist(&arglist); + oexitstatus = exitstatus; + expandarg(n->ncase.expr, &arglist, EXP_TILDE); + for (cp = n->ncase.cases ; cp ; cp = cp->nclist.next) { + for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) { + if (casematch(patp, arglist.args[0])) { + while (cp->nclist.next && + cp->type == NCLISTFALLTHRU && + cp->nclist.body == NULL) + cp = cp->nclist.next; + if (cp->nclist.next && + cp->type == NCLISTFALLTHRU) + return (cp); + if (cp->nclist.body == NULL) + exitstatus = 0; + return (cp->nclist.body); + } + } + } + exitstatus = 0; + return (NULL); +} + + + +/* + * Kick off a subshell to evaluate a tree. + */ + +static void +evalsubshell(union node *n, int flags) +{ + struct job *jp; + int backgnd = (n->type == NBACKGND); + + oexitstatus = exitstatus; + expredir(n->nredir.redirect); + if ((!backgnd && flags & EV_EXIT && !have_traps()) || + forkshell(jp = makejob(n, 1), n, backgnd) == 0) { + if (backgnd) + flags &=~ EV_TESTED; + redirect(n->nredir.redirect, 0); + evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */ + } else if (! backgnd) { + INTOFF; + exitstatus = waitforjob(jp, (int *)NULL); + INTON; + } else + exitstatus = 0; +} + + +/* + * Evaluate a redirected compound command. + */ + +static void +evalredir(union node *n, int flags) +{ + struct jmploc jmploc; + struct jmploc *savehandler; + volatile int in_redirect = 1; + + oexitstatus = exitstatus; + expredir(n->nredir.redirect); + savehandler = handler; + if (setjmp(jmploc.loc)) { + int e; + + handler = savehandler; + e = exception; + popredir(); + if (e == EXERROR || e == EXEXEC) { + if (in_redirect) { + exitstatus = 2; + FORCEINTON; + return; + } + } + longjmp(handler->loc, 1); + } else { + INTOFF; + handler = &jmploc; + redirect(n->nredir.redirect, REDIR_PUSH); + in_redirect = 0; + INTON; + evaltree(n->nredir.n, flags); + } + INTOFF; + handler = savehandler; + popredir(); + INTON; +} + + +static void +exphere(union node *redir, struct arglist *fn) +{ + struct jmploc jmploc; + struct jmploc *savehandler; + struct localvar *savelocalvars; + int need_longjmp = 0; + unsigned char saveoptreset; + + redir->nhere.expdoc = ""; + savelocalvars = localvars; + localvars = NULL; + saveoptreset = shellparam.reset; + forcelocal++; + savehandler = handler; + if (setjmp(jmploc.loc)) + need_longjmp = exception != EXERROR && exception != EXEXEC; + else { + handler = &jmploc; + expandarg(redir->nhere.doc, fn, 0); + redir->nhere.expdoc = fn->args[0]; + INTOFF; + } + handler = savehandler; + forcelocal--; + poplocalvars(); + localvars = savelocalvars; + shellparam.reset = saveoptreset; + if (need_longjmp) + longjmp(handler->loc, 1); + INTON; +} + + +/* + * Compute the names of the files in a redirection list. + */ + +static void +expredir(union node *n) +{ + union node *redir; + + for (redir = n ; redir ; redir = redir->nfile.next) { + struct arglist fn; + emptyarglist(&fn); + switch (redir->type) { + case NFROM: + case NTO: + case NFROMTO: + case NAPPEND: + case NCLOBBER: + expandarg(redir->nfile.fname, &fn, EXP_TILDE); + redir->nfile.expfname = fn.args[0]; + break; + case NFROMFD: + case NTOFD: + if (redir->ndup.vname) { + expandarg(redir->ndup.vname, &fn, EXP_TILDE); + fixredir(redir, fn.args[0], 1); + } + break; + case NXHERE: + exphere(redir, &fn); + break; + } + } +} + + + +/* + * Evaluate a pipeline. All the processes in the pipeline are children + * of the process creating the pipeline. (This differs from some versions + * of the shell, which make the last process in a pipeline the parent + * of all the rest.) + */ + +static void +evalpipe(union node *n) +{ + struct job *jp; + struct nodelist *lp; + int pipelen; + int prevfd; + int pip[2]; + + TRACE(("evalpipe(%p) called\n", (void *)n)); + pipelen = 0; + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) + pipelen++; + INTOFF; + jp = makejob(n, pipelen); + prevfd = -1; + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + prehash(lp->n); + pip[1] = -1; + if (lp->next) { + if (pipe(pip) < 0) { + if (prevfd >= 0) + close(prevfd); + error("Pipe call failed: %s", strerror(errno)); + } + } + if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) { + INTON; + if (prevfd > 0) { + dup2(prevfd, 0); + close(prevfd); + } + if (pip[1] >= 0) { + if (!(prevfd >= 0 && pip[0] == 0)) + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + } + evaltree(lp->n, EV_EXIT); + } + if (prevfd >= 0) + close(prevfd); + prevfd = pip[0]; + if (pip[1] != -1) + close(pip[1]); + } + INTON; + if (n->npipe.backgnd == 0) { + INTOFF; + exitstatus = waitforjob(jp, (int *)NULL); + TRACE(("evalpipe: job done exit status %d\n", exitstatus)); + INTON; + } else + exitstatus = 0; +} + + + +static int +is_valid_fast_cmdsubst(union node *n) +{ + + return (n->type == NCMD); +} + +/* + * Execute a command inside back quotes. If it's a builtin command, we + * want to save its output in a block obtained from malloc. Otherwise + * we fork off a subprocess and get the output of the command via a pipe. + * Should be called with interrupts off. + */ + +void +evalbackcmd(union node *n, struct backcmd *result) +{ + int pip[2]; + struct job *jp; + struct stackmark smark; + struct jmploc jmploc; + struct jmploc *savehandler; + struct localvar *savelocalvars; + unsigned char saveoptreset; + + result->fd = -1; + result->buf = NULL; + result->nleft = 0; + result->jp = NULL; + if (n == NULL) { + exitstatus = 0; + return; + } + setstackmark(&smark); + exitstatus = oexitstatus; + if (is_valid_fast_cmdsubst(n)) { + savelocalvars = localvars; + localvars = NULL; + saveoptreset = shellparam.reset; + forcelocal++; + savehandler = handler; + if (setjmp(jmploc.loc)) { + if (exception == EXERROR || exception == EXEXEC) + exitstatus = 2; + else if (exception != 0) { + handler = savehandler; + forcelocal--; + poplocalvars(); + localvars = savelocalvars; + shellparam.reset = saveoptreset; + longjmp(handler->loc, 1); + } + } else { + handler = &jmploc; + evalcommand(n, EV_BACKCMD, result); + } + handler = savehandler; + forcelocal--; + poplocalvars(); + localvars = savelocalvars; + shellparam.reset = saveoptreset; + } else { + if (pipe(pip) < 0) + error("Pipe call failed: %s", strerror(errno)); + jp = makejob(n, 1); + if (forkshell(jp, n, FORK_NOJOB) == 0) { + FORCEINTON; + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + evaltree(n, EV_EXIT); + } + close(pip[1]); + result->fd = pip[0]; + result->jp = jp; + } + popstackmark(&smark); + TRACE(("evalbackcmd done: fd=%d buf=%p nleft=%d jp=%p\n", + result->fd, result->buf, result->nleft, result->jp)); +} + +static int +mustexpandto(const char *argtext, const char *mask) +{ + for (;;) { + if (*argtext == CTLQUOTEMARK || *argtext == CTLQUOTEEND) { + argtext++; + continue; + } + if (*argtext == CTLESC) + argtext++; + else if (BASESYNTAX[(int)*argtext] == CCTL) + return (0); + if (*argtext != *mask) + return (0); + if (*argtext == '\0') + return (1); + argtext++; + mask++; + } +} + +static int +isdeclarationcmd(struct narg *arg) +{ + int have_command = 0; + + if (arg == NULL) + return (0); + while (mustexpandto(arg->text, "command")) { + have_command = 1; + arg = &arg->next->narg; + if (arg == NULL) + return (0); + /* + * To also allow "command -p" and "command --" as part of + * a declaration command, add code here. + * We do not do this, as ksh does not do it either and it + * is not required by POSIX. + */ + } + return (mustexpandto(arg->text, "export") || + mustexpandto(arg->text, "readonly") || + (mustexpandto(arg->text, "local") && + (have_command || !isfunc("local")))); +} + +static void +xtracecommand(struct arglist *varlist, int argc, char **argv) +{ + char sep = 0; + const char *text, *p, *ps4; + int i; + + ps4 = expandstr(ps4val()); + out2str(ps4 != NULL ? ps4 : ps4val()); + for (i = 0; i < varlist->count; i++) { + text = varlist->args[i]; + if (sep != 0) + out2c(' '); + p = strchr(text, '='); + if (p != NULL) { + p++; + outbin(text, p - text, out2); + out2qstr(p); + } else + out2qstr(text); + sep = ' '; + } + for (i = 0; i < argc; i++) { + text = argv[i]; + if (sep != 0) + out2c(' '); + out2qstr(text); + sep = ' '; + } + out2c('\n'); + flushout(&errout); +} + +/* + * Check if a builtin can safely be executed in the same process, + * even though it should be in a subshell (command substitution). + * Note that jobid, jobs, times and trap can show information not + * available in a child process; this is deliberate. + * The arguments should already have been expanded. + */ +static int +safe_builtin(int idx, int argc, char **argv) +{ + /* Generated from builtins.def. */ + if (safe_builtin_always(idx)) + return (1); + if (idx == EXPORTCMD || idx == TRAPCMD || idx == ULIMITCMD || + idx == UMASKCMD) + return (argc <= 1 || (argc == 2 && argv[1][0] == '-')); + if (idx == SETCMD) + return (argc <= 1 || (argc == 2 && (argv[1][0] == '-' || + argv[1][0] == '+') && argv[1][1] == 'o' && + argv[1][2] == '\0')); + return (0); +} + +/* + * Execute a simple command. + * Note: This may or may not return if (flags & EV_EXIT). + */ + +static void +evalcommand(union node *cmd, int flags, struct backcmd *backcmd) +{ + union node *argp; + struct arglist arglist; + struct arglist varlist; + char **argv; + int argc; + char **envp; + int varflag; + int mode; + int pip[2]; + struct cmdentry cmdentry; + struct job *jp; + struct jmploc jmploc; + struct jmploc *savehandler; + char *savecmdname; + struct shparam saveparam; + struct localvar *savelocalvars; + struct parsefile *savetopfile; + volatile int e; + char *lastarg; + int signaled; + int do_clearcmdentry; + const char *path = pathval(); + int i; + + /* First expand the arguments. */ + TRACE(("evalcommand(%p, %d) called\n", (void *)cmd, flags)); + emptyarglist(&arglist); + emptyarglist(&varlist); + varflag = 1; + jp = NULL; + do_clearcmdentry = 0; + oexitstatus = exitstatus; + exitstatus = 0; + /* Add one slot at the beginning for tryexec(). */ + appendarglist(&arglist, nullstr); + for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) { + if (varflag && isassignment(argp->narg.text)) { + expandarg(argp, varflag == 1 ? &varlist : &arglist, + EXP_VARTILDE); + continue; + } else if (varflag == 1) + varflag = isdeclarationcmd(&argp->narg) ? 2 : 0; + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + } + appendarglist(&arglist, nullstr); + expredir(cmd->ncmd.redirect); + argc = arglist.count - 2; + argv = &arglist.args[1]; + + argv[argc] = NULL; + lastarg = NULL; + if (iflag && funcnest == 0 && argc > 0) + lastarg = argv[argc - 1]; + + /* Print the command if xflag is set. */ + if (xflag) + xtracecommand(&varlist, argc, argv); + + /* Now locate the command. */ + if (argc == 0) { + /* Variable assignment(s) without command */ + cmdentry.cmdtype = CMDBUILTIN; + cmdentry.u.index = BLTINCMD; + cmdentry.special = 0; + } else { + static const char PATH[] = "PATH="; + int cmd_flags = 0, bltinonly = 0; + + /* + * Modify the command lookup path, if a PATH= assignment + * is present + */ + for (i = 0; i < varlist.count; i++) + if (strncmp(varlist.args[i], PATH, sizeof(PATH) - 1) == 0) { + path = varlist.args[i] + sizeof(PATH) - 1; + /* + * On `PATH=... command`, we need to make + * sure that the command isn't using the + * non-updated hash table of the outer PATH + * setting and we need to make sure that + * the hash table isn't filled with items + * from the temporary setting. + * + * It would be better to forbit using and + * updating the table while this command + * runs, by the command finding mechanism + * is heavily integrated with hash handling, + * so we just delete the hash before and after + * the command runs. Partly deleting like + * changepatch() does doesn't seem worth the + * bookinging effort, since most such runs add + * directories in front of the new PATH. + */ + clearcmdentry(); + do_clearcmdentry = 1; + } + + for (;;) { + if (bltinonly) { + cmdentry.u.index = find_builtin(*argv, &cmdentry.special); + if (cmdentry.u.index < 0) { + cmdentry.u.index = BLTINCMD; + argv--; + argc++; + break; + } + } else + find_command(argv[0], &cmdentry, cmd_flags, path); + /* implement the bltin and command builtins here */ + if (cmdentry.cmdtype != CMDBUILTIN) + break; + if (cmdentry.u.index == BLTINCMD) { + if (argc == 1) + break; + argv++; + argc--; + bltinonly = 1; + } else if (cmdentry.u.index == COMMANDCMD) { + if (argc == 1) + break; + if (!strcmp(argv[1], "-p")) { + if (argc == 2) + break; + if (argv[2][0] == '-') { + if (strcmp(argv[2], "--")) + break; + if (argc == 3) + break; + argv += 3; + argc -= 3; + } else { + argv += 2; + argc -= 2; + } + path = _PATH_STDPATH; + clearcmdentry(); + do_clearcmdentry = 1; + } else if (!strcmp(argv[1], "--")) { + if (argc == 2) + break; + argv += 2; + argc -= 2; + } else if (argv[1][0] == '-') + break; + else { + argv++; + argc--; + } + cmd_flags |= DO_NOFUNC; + bltinonly = 0; + } else + break; + } + /* + * Special builtins lose their special properties when + * called via 'command'. + */ + if (cmd_flags & DO_NOFUNC) + cmdentry.special = 0; + } + + /* Fork off a child process if necessary. */ + if (((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN) + && ((flags & EV_EXIT) == 0 || have_traps())) + || ((flags & EV_BACKCMD) != 0 + && (cmdentry.cmdtype != CMDBUILTIN || + !safe_builtin(cmdentry.u.index, argc, argv)))) { + jp = makejob(cmd, 1); + mode = FORK_FG; + if (flags & EV_BACKCMD) { + mode = FORK_NOJOB; + if (pipe(pip) < 0) + error("Pipe call failed: %s", strerror(errno)); + } + if (cmdentry.cmdtype == CMDNORMAL && + cmd->ncmd.redirect == NULL && + varlist.count == 0 && + (mode == FORK_FG || mode == FORK_NOJOB) && + !disvforkset() && !iflag && !mflag) { + vforkexecshell(jp, argv, environment(), path, + cmdentry.u.index, flags & EV_BACKCMD ? pip : NULL); + goto parent; + } + if (forkshell(jp, cmd, mode) != 0) + goto parent; /* at end of routine */ + if (flags & EV_BACKCMD) { + FORCEINTON; + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + flags &= ~EV_BACKCMD; + } + flags |= EV_EXIT; + } + + /* This is the child process if a fork occurred. */ + /* Execute the command. */ + if (cmdentry.cmdtype == CMDFUNCTION) { +#ifdef DEBUG + trputs("Shell function: "); trargs(argv); +#endif + saveparam = shellparam; + shellparam.malloc = 0; + shellparam.reset = 1; + shellparam.nparam = argc - 1; + shellparam.p = argv + 1; + shellparam.optp = NULL; + shellparam.optnext = NULL; + INTOFF; + savelocalvars = localvars; + localvars = NULL; + reffunc(cmdentry.u.func); + savehandler = handler; + if (setjmp(jmploc.loc)) { + popredir(); + unreffunc(cmdentry.u.func); + poplocalvars(); + localvars = savelocalvars; + freeparam(&shellparam); + shellparam = saveparam; + funcnest--; + handler = savehandler; + longjmp(handler->loc, 1); + } + handler = &jmploc; + funcnest++; + redirect(cmd->ncmd.redirect, REDIR_PUSH); + INTON; + for (i = 0; i < varlist.count; i++) + mklocal(varlist.args[i]); + exitstatus = oexitstatus; + evaltree(getfuncnode(cmdentry.u.func), + flags & (EV_TESTED | EV_EXIT)); + INTOFF; + unreffunc(cmdentry.u.func); + poplocalvars(); + localvars = savelocalvars; + freeparam(&shellparam); + shellparam = saveparam; + handler = savehandler; + funcnest--; + popredir(); + INTON; + if (evalskip == SKIPRETURN) { + evalskip = 0; + skipcount = 0; + } + if (jp) + exitshell(exitstatus); + } else if (cmdentry.cmdtype == CMDBUILTIN) { +#ifdef DEBUG + trputs("builtin command: "); trargs(argv); +#endif + mode = (cmdentry.u.index == EXECCMD)? 0 : REDIR_PUSH; + if (flags == EV_BACKCMD) { + memout.nextc = memout.buf; + mode |= REDIR_BACKQ; + } + savecmdname = commandname; + savetopfile = getcurrentfile(); + cmdenviron = &varlist; + e = -1; + savehandler = handler; + if (setjmp(jmploc.loc)) { + e = exception; + if (e == EXINT) + exitstatus = SIGINT+128; + else if (e != EXEXIT) + exitstatus = 2; + goto cmddone; + } + handler = &jmploc; + redirect(cmd->ncmd.redirect, mode); + outclearerror(out1); + /* + * If there is no command word, redirection errors should + * not be fatal but assignment errors should. + */ + if (argc == 0) + cmdentry.special = 1; + listsetvar(cmdenviron, cmdentry.special ? 0 : VNOSET); + if (argc > 0) + bltinsetlocale(); + commandname = argv[0]; + argptr = argv + 1; + nextopt_optptr = NULL; /* initialize nextopt */ + builtin_flags = flags; + exitstatus = (*builtinfunc[cmdentry.u.index])(argc, argv); + flushall(); + if (outiserror(out1)) { + warning("write error on stdout"); + if (exitstatus == 0 || exitstatus == 1) + exitstatus = 2; + } +cmddone: + if (argc > 0) + bltinunsetlocale(); + cmdenviron = NULL; + out1 = &output; + out2 = &errout; + freestdout(); + handler = savehandler; + commandname = savecmdname; + if (jp) + exitshell(exitstatus); + if (flags == EV_BACKCMD) { + backcmd->buf = memout.buf; + backcmd->nleft = memout.buf != NULL ? + memout.nextc - memout.buf : 0; + memout.buf = NULL; + memout.nextc = NULL; + memout.bufend = NULL; + memout.bufsize = 64; + } + if (cmdentry.u.index != EXECCMD) + popredir(); + if (e != -1) { + if ((e != EXERROR && e != EXEXEC) + || cmdentry.special) + exraise(e); + popfilesupto(savetopfile); + if (flags != EV_BACKCMD) + FORCEINTON; + } + } else { +#ifdef DEBUG + trputs("normal command: "); trargs(argv); +#endif + redirect(cmd->ncmd.redirect, 0); + for (i = 0; i < varlist.count; i++) + setvareq(varlist.args[i], VEXPORT|VSTACK); + envp = environment(); + shellexec(argv, envp, path, cmdentry.u.index); + /*NOTREACHED*/ + } + goto out; + +parent: /* parent process gets here (if we forked) */ + if (mode == FORK_FG) { /* argument to fork */ + INTOFF; + exitstatus = waitforjob(jp, &signaled); + INTON; + if (iflag && loopnest > 0 && signaled) { + evalskip = SKIPBREAK; + skipcount = loopnest; + } + } else if (mode == FORK_NOJOB) { + backcmd->fd = pip[0]; + close(pip[1]); + backcmd->jp = jp; + } + +out: + if (lastarg) + setvar("_", lastarg, 0); + if (do_clearcmdentry) + clearcmdentry(); +} + + + +/* + * Search for a command. This is called before we fork so that the + * location of the command will be available in the parent as well as + * the child. The check for "goodname" is an overly conservative + * check that the name will not be subject to expansion. + */ + +static void +prehash(union node *n) +{ + struct cmdentry entry; + + if (n && n->type == NCMD && n->ncmd.args) + if (goodname(n->ncmd.args->narg.text)) + find_command(n->ncmd.args->narg.text, &entry, 0, + pathval()); +} + + + +/* + * Builtin commands. Builtin commands whose functions are closely + * tied to evaluation are implemented here. + */ + +/* + * No command given, a bltin command with no arguments, or a bltin command + * with an invalid name. + */ + +int +bltincmd(int argc, char **argv) +{ + if (argc > 1) { + out2fmt_flush("%s: not found\n", argv[1]); + return 127; + } + /* + * Preserve exitstatus of a previous possible command substitution + * as POSIX mandates + */ + return exitstatus; +} + + +/* + * Handle break and continue commands. Break, continue, and return are + * all handled by setting the evalskip flag. The evaluation routines + * above all check this flag, and if it is set they start skipping + * commands rather than executing them. The variable skipcount is + * the number of loops to break/continue, or the number of function + * levels to return. (The latter is always 1.) It should probably + * be an error to break out of more loops than exist, but it isn't + * in the standard shell so we don't make it one here. + */ + +int +breakcmd(int argc, char **argv) +{ + long n; + char *end; + + if (argc > 1) { + /* Allow arbitrarily large numbers. */ + n = strtol(argv[1], &end, 10); + if (!is_digit(argv[1][0]) || *end != '\0') + error("Illegal number: %s", argv[1]); + } else + n = 1; + if (n > loopnest) + n = loopnest; + if (n > 0) { + evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; + skipcount = n; + } + return 0; +} + +/* + * The `command' command. + */ +int +commandcmd(int argc __unused, char **argv __unused) +{ + const char *path; + int ch; + int cmd = -1; + + path = bltinlookup("PATH", 1); + + while ((ch = nextopt("pvV")) != '\0') { + switch (ch) { + case 'p': + path = _PATH_STDPATH; + break; + case 'v': + cmd = TYPECMD_SMALLV; + break; + case 'V': + cmd = TYPECMD_BIGV; + break; + } + } + + if (cmd != -1) { + if (*argptr == NULL || argptr[1] != NULL) + error("wrong number of arguments"); + return typecmd_impl(2, argptr - 1, cmd, path); + } + if (*argptr != NULL) + error("commandcmd bad call"); + + /* + * Do nothing successfully if no command was specified; + * ksh also does this. + */ + return 0; +} + + +/* + * The return command. + */ + +int +returncmd(int argc, char **argv) +{ + int ret = argc > 1 ? number(argv[1]) : oexitstatus; + + evalskip = SKIPRETURN; + skipcount = 1; + return ret; +} + + +int +falsecmd(int argc __unused, char **argv __unused) +{ + return 1; +} + + +int +truecmd(int argc __unused, char **argv __unused) +{ + return 0; +} + + +int +execcmd(int argc, char **argv) +{ + int i; + + /* + * Because we have historically not supported any options, + * only treat "--" specially. + */ + if (argc > 1 && strcmp(argv[1], "--") == 0) + argc--, argv++; + if (argc > 1) { + iflag = 0; /* exit on error */ + mflag = 0; + optschanged(); + for (i = 0; i < cmdenviron->count; i++) + setvareq(cmdenviron->args[i], VEXPORT|VSTACK); + shellexec(argv + 1, environment(), pathval(), 0); + + } + return 0; +} + + +int +timescmd(int argc __unused, char **argv __unused) +{ + struct rusage ru; + long shumins, shsmins, chumins, chsmins; + double shusecs, shssecs, chusecs, chssecs; + + if (getrusage(RUSAGE_SELF, &ru) < 0) + return 1; + shumins = ru.ru_utime.tv_sec / 60; + shusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.; + shsmins = ru.ru_stime.tv_sec / 60; + shssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.; + if (getrusage(RUSAGE_CHILDREN, &ru) < 0) + return 1; + chumins = ru.ru_utime.tv_sec / 60; + chusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.; + chsmins = ru.ru_stime.tv_sec / 60; + chssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.; + out1fmt("%ldm%.3fs %ldm%.3fs\n%ldm%.3fs %ldm%.3fs\n", shumins, + shusecs, shsmins, shssecs, chumins, chusecs, chsmins, chssecs); + return 0; +} diff --git a/bin/1sh/eval.h b/bin/1sh/eval.h new file mode 100644 index 00000000..d413ccd5 --- /dev/null +++ b/bin/1sh/eval.h @@ -0,0 +1,70 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)eval.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/eval.h 314436 2017-02-28 23:42:47Z imp $ + */ + +extern char *commandname; /* currently executing command */ +extern int exitstatus; /* exit status of last command */ +extern int oexitstatus; /* saved exit status */ +extern struct arglist *cmdenviron; /* environment for builtin command */ + + +struct backcmd { /* result of evalbackcmd */ + int fd; /* file descriptor to read from */ + char *buf; /* buffer */ + int nleft; /* number of chars in buffer */ + struct job *jp; /* job structure for command */ +}; + +void reseteval(void); + +/* flags in argument to evaltree/evalstring */ +#define EV_EXIT 01 /* exit after evaluating tree */ +#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ +#define EV_BACKCMD 04 /* command executing within back quotes */ + +void evalstring(const char *, int); +union node; /* BLETCH for ansi C */ +void evaltree(union node *, int); +void evalbackcmd(union node *, struct backcmd *); + +/* in_function returns nonzero if we are currently evaluating a function */ +#define in_function() funcnest +extern int funcnest; +extern int evalskip; +extern int skipcount; + +/* reasons for skipping commands (see comment on breakcmd routine) */ +#define SKIPBREAK 1 +#define SKIPCONT 2 +#define SKIPRETURN 3 diff --git a/bin/1sh/exec.c b/bin/1sh/exec.c new file mode 100644 index 00000000..81efa565 --- /dev/null +++ b/bin/1sh/exec.c @@ -0,0 +1,784 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)exec.c 8.4 (Berkeley) 6/8/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/exec.c 336320 2018-07-15 21:55:17Z jilles $ */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <paths.h> +#include <stdlib.h> + +/* + * When commands are first encountered, they are entered in a hash table. + * This ensures that a full path search will not have to be done for them + * on each invocation. + * + * We should investigate converting to a linear search, even though that + * would make the command name "hash" a misnomer. + */ + +#include "shell.h" +#include "main.h" +#include "nodes.h" +#include "parser.h" +#include "redir.h" +#include "eval.h" +#include "exec.h" +#include "builtins.h" +#include "var.h" +#include "options.h" +#include "input.h" +#include "output.h" +#include "syntax.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "show.h" +#include "jobs.h" +#include "alias.h" + + +#define CMDTABLESIZE 31 /* should be prime */ + + + +struct tblentry { + struct tblentry *next; /* next entry in hash chain */ + union param param; /* definition of builtin function */ + int special; /* flag for special builtin commands */ + signed char cmdtype; /* index identifying command */ + char cmdname[]; /* name of command */ +}; + + +static struct tblentry *cmdtable[CMDTABLESIZE]; +static int cmdtable_cd = 0; /* cmdtable contains cd-dependent entries */ +int exerrno = 0; /* Last exec error */ + + +static void tryexec(char *, char **, char **); +static void printentry(struct tblentry *, int); +static struct tblentry *cmdlookup(const char *, int); +static void delete_cmd_entry(void); +static void addcmdentry(const char *, struct cmdentry *); + + + +/* + * Exec a program. Never returns. If you change this routine, you may + * have to change the find_command routine as well. + * + * The argv array may be changed and element argv[-1] should be writable. + */ + +void +shellexec(char **argv, char **envp, const char *path, int idx) +{ + char *cmdname; + const char *opt; + int e; + + if (strchr(argv[0], '/') != NULL) { + tryexec(argv[0], argv, envp); + e = errno; + } else { + e = ENOENT; + while ((cmdname = padvance(&path, &opt, argv[0])) != NULL) { + if (--idx < 0 && opt == NULL) { + tryexec(cmdname, argv, envp); + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + if (e == ENOEXEC) + break; + } + stunalloc(cmdname); + } + } + + /* Map to POSIX errors */ + if (e == ENOENT || e == ENOTDIR) { + exerrno = 127; + exerror(EXEXEC, "%s: not found", argv[0]); + } else { + exerrno = 126; + exerror(EXEXEC, "%s: %s", argv[0], strerror(e)); + } +} + + +static void +tryexec(char *cmd, char **argv, char **envp) +{ + int e, in; + ssize_t n; + char buf[256]; + + execve(cmd, argv, envp); + e = errno; + if (e == ENOEXEC) { + INTOFF; + in = open(cmd, O_RDONLY | O_NONBLOCK); + if (in != -1) { + n = pread(in, buf, sizeof buf, 0); + close(in); + if (n > 0 && memchr(buf, '\0', n) != NULL) { + errno = ENOEXEC; + return; + } + } + *argv = cmd; + *--argv = (char *)(_PATH_BSHELL); + execve(_PATH_BSHELL, argv, envp); + } + errno = e; +} + +/* + * Do a path search. The variable path (passed by reference) should be + * set to the start of the path before the first call; padvance will update + * this value as it proceeds. Successive calls to padvance will return + * the possible path expansions in sequence. If popt is not NULL, options + * are processed: if an option (indicated by a percent sign) appears in + * the path entry then *popt will be set to point to it; else *popt will be + * set to NULL. If popt is NULL, percent signs are not special. + */ + +char * +padvance(const char **path, const char **popt, const char *name) +{ + const char *p, *start; + char *q; + size_t len, namelen; + + if (*path == NULL) + return NULL; + start = *path; + if (popt != NULL) + for (p = start; *p && *p != ':' && *p != '%'; p++) + ; /* nothing */ + else + for (p = start; *p && *p != ':'; p++) + ; /* nothing */ + namelen = strlen(name); + len = p - start + namelen + 2; /* "2" is for '/' and '\0' */ + STARTSTACKSTR(q); + CHECKSTRSPACE(len, q); + if (p != start) { + memcpy(q, start, p - start); + q += p - start; + *q++ = '/'; + } + memcpy(q, name, namelen + 1); + if (popt != NULL) { + if (*p == '%') { + *popt = ++p; + while (*p && *p != ':') p++; + } else + *popt = NULL; + } + if (*p == ':') + *path = p + 1; + else + *path = NULL; + return stalloc(len); +} + + + +/*** Command hashing code ***/ + + +int +hashcmd(int argc __unused, char **argv __unused) +{ + struct tblentry **pp; + struct tblentry *cmdp; + int c; + int verbose; + struct cmdentry entry; + char *name; + int errors; + + errors = 0; + verbose = 0; + while ((c = nextopt("rv")) != '\0') { + if (c == 'r') { + clearcmdentry(); + } else if (c == 'v') { + verbose++; + } + } + if (*argptr == NULL) { + for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL) + printentry(cmdp, verbose); + } + } + return 0; + } + while ((name = *argptr) != NULL) { + if ((cmdp = cmdlookup(name, 0)) != NULL + && cmdp->cmdtype == CMDNORMAL) + delete_cmd_entry(); + find_command(name, &entry, DO_ERR, pathval()); + if (entry.cmdtype == CMDUNKNOWN) + errors = 1; + else if (verbose) { + cmdp = cmdlookup(name, 0); + if (cmdp != NULL) + printentry(cmdp, verbose); + else { + outfmt(out2, "%s: not found\n", name); + errors = 1; + } + flushall(); + } + argptr++; + } + return errors; +} + + +static void +printentry(struct tblentry *cmdp, int verbose) +{ + int idx; + const char *path, *opt; + char *name; + + if (cmdp->cmdtype == CMDNORMAL) { + idx = cmdp->param.index; + path = pathval(); + do { + name = padvance(&path, &opt, cmdp->cmdname); + stunalloc(name); + } while (--idx >= 0); + out1str(name); + } else if (cmdp->cmdtype == CMDBUILTIN) { + out1fmt("builtin %s", cmdp->cmdname); + } else if (cmdp->cmdtype == CMDFUNCTION) { + out1fmt("function %s", cmdp->cmdname); + if (verbose) { + INTOFF; + name = commandtext(getfuncnode(cmdp->param.func)); + out1c(' '); + out1str(name); + ckfree(name); + INTON; + } +#ifdef DEBUG + } else { + error("internal error: cmdtype %d", cmdp->cmdtype); +#endif + } + out1c('\n'); +} + + + +/* + * Resolve a command name. If you change this routine, you may have to + * change the shellexec routine as well. + */ + +void +find_command(const char *name, struct cmdentry *entry, int act, + const char *path) +{ + struct tblentry *cmdp, loc_cmd; + int idx; + const char *opt; + char *fullname; + struct stat statb; + int e; + int i; + int spec; + int cd; + + /* If name contains a slash, don't use the hash table */ + if (strchr(name, '/') != NULL) { + entry->cmdtype = CMDNORMAL; + entry->u.index = 0; + entry->special = 0; + return; + } + + cd = 0; + + /* If name is in the table, we're done */ + if ((cmdp = cmdlookup(name, 0)) != NULL) { + if (cmdp->cmdtype == CMDFUNCTION && act & DO_NOFUNC) + cmdp = NULL; + else + goto success; + } + + /* Check for builtin next */ + if ((i = find_builtin(name, &spec)) >= 0) { + INTOFF; + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) + cmdp = &loc_cmd; + cmdp->cmdtype = CMDBUILTIN; + cmdp->param.index = i; + cmdp->special = spec; + INTON; + goto success; + } + + /* We have to search path. */ + + e = ENOENT; + idx = -1; + for (;(fullname = padvance(&path, &opt, name)) != NULL; + stunalloc(fullname)) { + idx++; + if (opt) { + if (strncmp(opt, "func", 4) == 0) { + /* handled below */ + } else { + continue; /* ignore unimplemented options */ + } + } + if (fullname[0] != '/') + cd = 1; + if (stat(fullname, &statb) < 0) { + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + continue; + } + e = EACCES; /* if we fail, this will be the error */ + if (!S_ISREG(statb.st_mode)) + continue; + if (opt) { /* this is a %func directory */ + readcmdfile(fullname); + if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION) + error("%s not defined in %s", name, fullname); + stunalloc(fullname); + goto success; + } +#ifdef notdef + if (statb.st_uid == geteuid()) { + if ((statb.st_mode & 0100) == 0) + goto loop; + } else if (statb.st_gid == getegid()) { + if ((statb.st_mode & 010) == 0) + goto loop; + } else { + if ((statb.st_mode & 01) == 0) + goto loop; + } +#endif + TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname)); + INTOFF; + stunalloc(fullname); + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) + cmdp = &loc_cmd; + cmdp->cmdtype = CMDNORMAL; + cmdp->param.index = idx; + cmdp->special = 0; + INTON; + goto success; + } + + if (act & DO_ERR) { + if (e == ENOENT || e == ENOTDIR) + outfmt(out2, "%s: not found\n", name); + else + outfmt(out2, "%s: %s\n", name, strerror(e)); + } + entry->cmdtype = CMDUNKNOWN; + entry->u.index = 0; + entry->special = 0; + return; + +success: + if (cd) + cmdtable_cd = 1; + entry->cmdtype = cmdp->cmdtype; + entry->u = cmdp->param; + entry->special = cmdp->special; +} + + + +/* + * Search the table of builtin commands. + */ + +int +find_builtin(const char *name, int *special) +{ + const unsigned char *bp; + size_t len; + + len = strlen(name); + for (bp = builtincmd ; *bp ; bp += 2 + bp[0]) { + if (bp[0] == len && memcmp(bp + 2, name, len) == 0) { + *special = (bp[1] & BUILTIN_SPECIAL) != 0; + return bp[1] & ~BUILTIN_SPECIAL; + } + } + return -1; +} + + + +/* + * Called when a cd is done. If any entry in cmdtable depends on the current + * directory, simply clear cmdtable completely. + */ + +void +hashcd(void) +{ + if (cmdtable_cd) + clearcmdentry(); +} + + + +/* + * Called before PATH is changed. The argument is the new value of PATH; + * pathval() still returns the old value at this point. Called with + * interrupts off. + */ + +void +changepath(const char *newval __unused) +{ + clearcmdentry(); +} + + +/* + * Clear out cached utility locations. + */ + +void +clearcmdentry(void) +{ + struct tblentry **tblp; + struct tblentry **pp; + struct tblentry *cmdp; + + INTOFF; + for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) { + pp = tblp; + while ((cmdp = *pp) != NULL) { + if (cmdp->cmdtype == CMDNORMAL) { + *pp = cmdp->next; + ckfree(cmdp); + } else { + pp = &cmdp->next; + } + } + } + cmdtable_cd = 0; + INTON; +} + + +/* + * Locate a command in the command hash table. If "add" is nonzero, + * add the command to the table if it is not already present. The + * variable "lastcmdentry" is set to point to the address of the link + * pointing to the entry, so that delete_cmd_entry can delete the + * entry. + */ + +static struct tblentry **lastcmdentry; + + +static struct tblentry * +cmdlookup(const char *name, int add) +{ + unsigned int hashval; + const char *p; + struct tblentry *cmdp; + struct tblentry **pp; + size_t len; + + p = name; + hashval = (unsigned char)*p << 4; + while (*p) + hashval += *p++; + pp = &cmdtable[hashval % CMDTABLESIZE]; + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (equal(cmdp->cmdname, name)) + break; + pp = &cmdp->next; + } + if (add && cmdp == NULL) { + INTOFF; + len = strlen(name); + cmdp = *pp = ckmalloc(sizeof (struct tblentry) + len + 1); + cmdp->next = NULL; + cmdp->cmdtype = CMDUNKNOWN; + memcpy(cmdp->cmdname, name, len + 1); + INTON; + } + lastcmdentry = pp; + return cmdp; +} + +/* + * Delete the command entry returned on the last lookup. + */ + +static void +delete_cmd_entry(void) +{ + struct tblentry *cmdp; + + INTOFF; + cmdp = *lastcmdentry; + *lastcmdentry = cmdp->next; + ckfree(cmdp); + INTON; +} + + + +/* + * Add a new command entry, replacing any existing command entry for + * the same name. + */ + +static void +addcmdentry(const char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp; + + INTOFF; + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) { + unreffunc(cmdp->param.func); + } + cmdp->cmdtype = entry->cmdtype; + cmdp->param = entry->u; + cmdp->special = entry->special; + INTON; +} + + +/* + * Define a shell function. + */ + +void +defun(const char *name, union node *func) +{ + struct cmdentry entry; + + INTOFF; + entry.cmdtype = CMDFUNCTION; + entry.u.func = copyfunc(func); + entry.special = 0; + addcmdentry(name, &entry); + INTON; +} + + +/* + * Delete a function if it exists. + * Called with interrupts off. + */ + +int +unsetfunc(const char *name) +{ + struct tblentry *cmdp; + + if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) { + unreffunc(cmdp->param.func); + delete_cmd_entry(); + return (0); + } + return (0); +} + + +/* + * Check if a function by a certain name exists. + */ +int +isfunc(const char *name) +{ + struct tblentry *cmdp; + cmdp = cmdlookup(name, 0); + return (cmdp != NULL && cmdp->cmdtype == CMDFUNCTION); +} + + +/* + * Shared code for the following builtin commands: + * type, command -v, command -V + */ + +int +typecmd_impl(int argc, char **argv, int cmd, const char *path) +{ + struct cmdentry entry; + struct tblentry *cmdp; + const char *const *pp; + struct alias *ap; + int i; + int error1 = 0; + + if (path != pathval()) + clearcmdentry(); + + for (i = 1; i < argc; i++) { + /* First look at the keywords */ + for (pp = parsekwd; *pp; pp++) + if (**pp == *argv[i] && equal(*pp, argv[i])) + break; + + if (*pp) { + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else + out1fmt("%s is a shell keyword\n", argv[i]); + continue; + } + + /* Then look at the aliases */ + if ((ap = lookupalias(argv[i], 1)) != NULL) { + if (cmd == TYPECMD_SMALLV) { + out1fmt("alias %s=", argv[i]); + out1qstr(ap->val); + outcslow('\n', out1); + } else + out1fmt("%s is an alias for %s\n", argv[i], + ap->val); + continue; + } + + /* Then check if it is a tracked alias */ + if ((cmdp = cmdlookup(argv[i], 0)) != NULL) { + entry.cmdtype = cmdp->cmdtype; + entry.u = cmdp->param; + entry.special = cmdp->special; + } + else { + /* Finally use brute force */ + find_command(argv[i], &entry, 0, path); + } + + switch (entry.cmdtype) { + case CMDNORMAL: { + if (strchr(argv[i], '/') == NULL) { + const char *path2 = path; + const char *opt2; + char *name; + int j = entry.u.index; + do { + name = padvance(&path2, &opt2, argv[i]); + stunalloc(name); + } while (--j >= 0); + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", name); + else + out1fmt("%s is%s %s\n", argv[i], + (cmdp && cmd == TYPECMD_TYPE) ? + " a tracked alias for" : "", + name); + } else { + if (faccessat(AT_FDCWD, argv[i], X_OK, AT_EACCESS) == 0) { + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else + out1fmt("%s is %s\n", argv[i], + argv[i]); + } else { + if (cmd != TYPECMD_SMALLV) + outfmt(out2, "%s: %s\n", + argv[i], strerror(errno)); + error1 |= 127; + } + } + break; + } + case CMDFUNCTION: + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else + out1fmt("%s is a shell function\n", argv[i]); + break; + + case CMDBUILTIN: + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else if (entry.special) + out1fmt("%s is a special shell builtin\n", + argv[i]); + else + out1fmt("%s is a shell builtin\n", argv[i]); + break; + + default: + if (cmd != TYPECMD_SMALLV) + outfmt(out2, "%s: not found\n", argv[i]); + error1 |= 127; + break; + } + } + + if (path != pathval()) + clearcmdentry(); + + return error1; +} + +/* + * Locate and print what a word is... + */ + +int +typecmd(int argc, char **argv) +{ + if (argc > 2 && strcmp(argv[1], "--") == 0) + argc--, argv++; + return typecmd_impl(argc, argv, TYPECMD_TYPE, bltinlookup("PATH", 1)); +} diff --git a/bin/1sh/exec.h b/bin/1sh/exec.h new file mode 100644 index 00000000..f93c14c2 --- /dev/null +++ b/bin/1sh/exec.h @@ -0,0 +1,76 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)exec.h 8.3 (Berkeley) 6/8/95 + * $FreeBSD: releng/12.1/bin/sh/exec.h 336320 2018-07-15 21:55:17Z jilles $ + */ + +/* values of cmdtype */ +#define CMDUNKNOWN -1 /* no entry in table for command */ +#define CMDNORMAL 0 /* command is an executable program */ +#define CMDBUILTIN 1 /* command is a shell builtin */ +#define CMDFUNCTION 2 /* command is a shell function */ + +/* values for typecmd_impl's third parameter */ +enum { + TYPECMD_SMALLV, /* command -v */ + TYPECMD_BIGV, /* command -V */ + TYPECMD_TYPE /* type */ +}; + +union node; +struct cmdentry { + int cmdtype; + union param { + int index; + struct funcdef *func; + } u; + int special; +}; + + +/* action to find_command() */ +#define DO_ERR 0x01 /* prints errors */ +#define DO_NOFUNC 0x02 /* don't return shell functions, for command */ + +extern int exerrno; /* last exec error */ + +void shellexec(char **, char **, const char *, int) __attribute__((noreturn)); +char *padvance(const char **, const char **, const char *); +void find_command(const char *, struct cmdentry *, int, const char *); +int find_builtin(const char *, int *); +void hashcd(void); +void changepath(const char *); +void defun(const char *, union node *); +int unsetfunc(const char *); +int isfunc(const char *); +int typecmd_impl(int, char **, int, const char *); +void clearcmdentry(void); diff --git a/bin/1sh/expand.c b/bin/1sh/expand.c new file mode 100644 index 00000000..3bdd0733 --- /dev/null +++ b/bin/1sh/expand.c @@ -0,0 +1,1553 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1997-2005 + * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved. + * Copyright (c) 2010-2015 + * Jilles Tjoelker <jilles@stack.nl>. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/expand.c 341767 2018-12-09 19:14:21Z jilles $ */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <dirent.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wchar.h> +#include <wctype.h> + +/* + * Routines to expand arguments to commands. We have to deal with + * backquotes, shell variables, and file metacharacters. + */ + +#include "shell.h" +#include "main.h" +#include "nodes.h" +#include "eval.h" +#include "expand.h" +#include "syntax.h" +#include "parser.h" +#include "jobs.h" +#include "options.h" +#include "var.h" +#include "input.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "arith.h" +#include "show.h" +#include "builtins.h" + +enum wordstate { WORD_IDLE, WORD_WS_DELIMITED, WORD_QUOTEMARK }; + +struct worddest { + struct arglist *list; + enum wordstate state; +}; + +static char *expdest; /* output of current string */ + +static const char *argstr(const char *, struct nodelist **restrict, int, + struct worddest *); +static const char *exptilde(const char *, int); +static const char *expari(const char *, struct nodelist **restrict, int, + struct worddest *); +static void expbackq(union node *, int, int, struct worddest *); +static const char *subevalvar_trim(const char *, struct nodelist **restrict, + int, int, int); +static const char *subevalvar_misc(const char *, struct nodelist **restrict, + const char *, int, int, int); +static const char *evalvar(const char *, struct nodelist **restrict, int, + struct worddest *); +static int varisset(const char *, int); +static void strtodest(const char *, int, int, int, struct worddest *); +static void reprocess(int, int, int, int, struct worddest *); +static void varvalue(const char *, int, int, int, struct worddest *); +static void expandmeta(char *, struct arglist *); +static void expmeta(char *, char *, struct arglist *); +static int expsortcmp(const void *, const void *); +static int patmatch(const char *, const char *); +static void cvtnum(int, char *); +static int collate_range_cmp(wchar_t, wchar_t); + +void +emptyarglist(struct arglist *list) +{ + + list->args = list->smallarg; + list->count = 0; + list->capacity = sizeof(list->smallarg) / sizeof(list->smallarg[0]); +} + +void +appendarglist(struct arglist *list, char *str) +{ + char **newargs; + int newcapacity; + + if (list->count >= list->capacity) { + newcapacity = list->capacity * 2; + if (newcapacity < 16) + newcapacity = 16; + if (newcapacity > INT_MAX / (int)sizeof(newargs[0])) + error("Too many entries in arglist"); + newargs = stalloc(newcapacity * sizeof(newargs[0])); + memcpy(newargs, list->args, list->count * sizeof(newargs[0])); + list->args = newargs; + list->capacity = newcapacity; + } + list->args[list->count++] = str; +} + +static int +collate_range_cmp(wchar_t c1, wchar_t c2) +{ + wchar_t s1[2], s2[2]; + + s1[0] = c1; + s1[1] = L'\0'; + s2[0] = c2; + s2[1] = L'\0'; + return (wcscoll(s1, s2)); +} + +static char * +stputs_quotes(const char *data, const char *syntax, char *p) +{ + while (*data) { + CHECKSTRSPACE(2, p); + if (syntax[(int)*data] == CCTL) + USTPUTC(CTLESC, p); + USTPUTC(*data++, p); + } + return (p); +} +#define STPUTS_QUOTES(data, syntax, p) p = stputs_quotes((data), syntax, p) + +static char * +nextword(char c, int flag, char *p, struct worddest *dst) +{ + int is_ws; + + is_ws = c == '\t' || c == '\n' || c == ' '; + if (p != stackblock() || (is_ws ? dst->state == WORD_QUOTEMARK : + dst->state != WORD_WS_DELIMITED) || c == '\0') { + STPUTC('\0', p); + if (flag & EXP_GLOB) + expandmeta(grabstackstr(p), dst->list); + else + appendarglist(dst->list, grabstackstr(p)); + dst->state = is_ws ? WORD_WS_DELIMITED : WORD_IDLE; + } else if (!is_ws && dst->state == WORD_WS_DELIMITED) + dst->state = WORD_IDLE; + /* Reserve space while the stack string is empty. */ + appendarglist(dst->list, NULL); + dst->list->count--; + STARTSTACKSTR(p); + return p; +} +#define NEXTWORD(c, flag, p, dstlist) p = nextword(c, flag, p, dstlist) + +static char * +stputs_split(const char *data, const char *syntax, int flag, char *p, + struct worddest *dst) +{ + const char *ifs; + char c; + + ifs = ifsset() ? ifsval() : " \t\n"; + while (*data) { + CHECKSTRSPACE(2, p); + c = *data++; + if (strchr(ifs, c) != NULL) { + NEXTWORD(c, flag, p, dst); + continue; + } + if (flag & EXP_GLOB && syntax[(int)c] == CCTL) + USTPUTC(CTLESC, p); + USTPUTC(c, p); + } + return (p); +} +#define STPUTS_SPLIT(data, syntax, flag, p, dst) p = stputs_split((data), syntax, flag, p, dst) + +/* + * Perform expansions on an argument, placing the resulting list of arguments + * in arglist. Parameter expansion, command substitution and arithmetic + * expansion are always performed; additional expansions can be requested + * via flag (EXP_*). + * The result is left in the stack string. + * When arglist is NULL, perform here document expansion. + * + * When doing something that may cause this to be re-entered, make sure + * the stack string is empty via grabstackstr() and do not assume expdest + * remains valid. + */ +void +expandarg(union node *arg, struct arglist *arglist, int flag) +{ + struct worddest exparg; + struct nodelist *argbackq; + + if (fflag) + flag &= ~EXP_GLOB; + argbackq = arg->narg.backquote; + exparg.list = arglist; + exparg.state = WORD_IDLE; + STARTSTACKSTR(expdest); + argstr(arg->narg.text, &argbackq, flag, &exparg); + if (arglist == NULL) { + STACKSTRNUL(expdest); + return; /* here document expanded */ + } + if ((flag & EXP_SPLIT) == 0 || expdest != stackblock() || + exparg.state == WORD_QUOTEMARK) { + STPUTC('\0', expdest); + if (flag & EXP_SPLIT) { + if (flag & EXP_GLOB) + expandmeta(grabstackstr(expdest), exparg.list); + else + appendarglist(exparg.list, grabstackstr(expdest)); + } + } + if ((flag & EXP_SPLIT) == 0) + appendarglist(arglist, grabstackstr(expdest)); +} + + + +/* + * Perform parameter expansion, command substitution and arithmetic + * expansion, and tilde expansion if requested via EXP_TILDE/EXP_VARTILDE. + * Processing ends at a CTLENDVAR or CTLENDARI character as well as '\0'. + * This is used to expand word in ${var+word} etc. + * If EXP_GLOB or EXP_CASE are set, keep and/or generate CTLESC + * characters to allow for further processing. + * + * If EXP_SPLIT is set, dst receives any complete words produced. + */ +static const char * +argstr(const char *p, struct nodelist **restrict argbackq, int flag, + struct worddest *dst) +{ + char c; + int quotes = flag & (EXP_GLOB | EXP_CASE); /* do CTLESC */ + int firsteq = 1; + int split_lit; + int lit_quoted; + + split_lit = flag & EXP_SPLIT_LIT; + lit_quoted = flag & EXP_LIT_QUOTED; + flag &= ~(EXP_SPLIT_LIT | EXP_LIT_QUOTED); + if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE))) + p = exptilde(p, flag); + for (;;) { + CHECKSTRSPACE(2, expdest); + switch (c = *p++) { + case '\0': + return (p - 1); + case CTLENDVAR: + case CTLENDARI: + return (p); + case CTLQUOTEMARK: + lit_quoted = 1; + /* "$@" syntax adherence hack */ + if (p[0] == CTLVAR && (p[1] & VSQUOTE) != 0 && + p[2] == '@' && p[3] == '=') + break; + if ((flag & EXP_SPLIT) != 0 && expdest == stackblock()) + dst->state = WORD_QUOTEMARK; + break; + case CTLQUOTEEND: + lit_quoted = 0; + break; + case CTLESC: + c = *p++; + if (split_lit && !lit_quoted && + strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { + NEXTWORD(c, flag, expdest, dst); + break; + } + if (quotes) + USTPUTC(CTLESC, expdest); + USTPUTC(c, expdest); + break; + case CTLVAR: + p = evalvar(p, argbackq, flag, dst); + break; + case CTLBACKQ: + case CTLBACKQ|CTLQUOTE: + expbackq((*argbackq)->n, c & CTLQUOTE, flag, dst); + *argbackq = (*argbackq)->next; + break; + case CTLARI: + p = expari(p, argbackq, flag, dst); + break; + case ':': + case '=': + /* + * sort of a hack - expand tildes in variable + * assignments (after the first '=' and after ':'s). + */ + if (split_lit && !lit_quoted && + strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { + NEXTWORD(c, flag, expdest, dst); + break; + } + USTPUTC(c, expdest); + if (flag & EXP_VARTILDE && *p == '~' && + (c != '=' || firsteq)) { + if (c == '=') + firsteq = 0; + p = exptilde(p, flag); + } + break; + default: + if (split_lit && !lit_quoted && + strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { + NEXTWORD(c, flag, expdest, dst); + break; + } + USTPUTC(c, expdest); + } + } +} + +/* + * Perform tilde expansion, placing the result in the stack string and + * returning the next position in the input string to process. + */ +static const char * +exptilde(const char *p, int flag) +{ + char c; + const char *startp = p; + const char *user; + struct passwd *pw; + char *home; + int len; + + for (;;) { + c = *p; + switch(c) { + case CTLESC: /* This means CTL* are always considered quoted. */ + case CTLVAR: + case CTLBACKQ: + case CTLBACKQ | CTLQUOTE: + case CTLARI: + case CTLENDARI: + case CTLQUOTEMARK: + return (startp); + case ':': + if ((flag & EXP_VARTILDE) == 0) + break; + /* FALLTHROUGH */ + case '\0': + case '/': + case CTLENDVAR: + len = p - startp - 1; + STPUTBIN(startp + 1, len, expdest); + STACKSTRNUL(expdest); + user = expdest - len; + if (*user == '\0') { + home = lookupvar("HOME"); + } else { + pw = getpwnam(user); + home = pw != NULL ? pw->pw_dir : NULL; + } + STADJUST(-len, expdest); + if (home == NULL || *home == '\0') + return (startp); + strtodest(home, flag, VSNORMAL, 1, NULL); + return (p); + } + p++; + } +} + + +/* + * Expand arithmetic expression. + */ +static const char * +expari(const char *p, struct nodelist **restrict argbackq, int flag, + struct worddest *dst) +{ + char *q, *start; + arith_t result; + int begoff; + int quoted; + int adj; + + quoted = *p++ == '"'; + begoff = expdest - stackblock(); + p = argstr(p, argbackq, 0, NULL); + STPUTC('\0', expdest); + start = stackblock() + begoff; + + q = grabstackstr(expdest); + result = arith(start); + ungrabstackstr(q, expdest); + + start = stackblock() + begoff; + adj = start - expdest; + STADJUST(adj, expdest); + + CHECKSTRSPACE((int)(DIGITS(result) + 1), expdest); + fmtstr(expdest, DIGITS(result), ARITH_FORMAT_STR, result); + adj = strlen(expdest); + STADJUST(adj, expdest); + /* + * If this is quoted, a '-' must not indicate a range in [...]. + * If this is not quoted, splitting may occur. + */ + if (quoted ? + result < 0 && begoff > 1 && flag & (EXP_GLOB | EXP_CASE) : + flag & EXP_SPLIT) + reprocess(expdest - adj - stackblock(), flag, VSNORMAL, quoted, + dst); + return p; +} + + +/* + * Perform command substitution. + */ +static void +expbackq(union node *cmd, int quoted, int flag, struct worddest *dst) +{ + struct backcmd in; + int i; + char buf[128]; + char *p; + char *dest = expdest; + char lastc; + char const *syntax = quoted? DQSYNTAX : BASESYNTAX; + int quotes = flag & (EXP_GLOB | EXP_CASE); + size_t nnl; + const char *ifs; + int startloc; + + INTOFF; + p = grabstackstr(dest); + evalbackcmd(cmd, &in); + ungrabstackstr(p, dest); + + p = in.buf; + startloc = dest - stackblock(); + nnl = 0; + if (!quoted && flag & EXP_SPLIT) + ifs = ifsset() ? ifsval() : " \t\n"; + else + ifs = ""; + /* Remove trailing newlines */ + for (;;) { + if (--in.nleft < 0) { + if (in.fd < 0) + break; + while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR) + ; + TRACE(("expbackq: read returns %d\n", i)); + if (i <= 0) + break; + p = buf; + in.nleft = i - 1; + } + lastc = *p++; + if (lastc == '\0') + continue; + if (nnl > 0 && lastc != '\n') { + NEXTWORD('\n', flag, dest, dst); + nnl = 0; + } + if (strchr(ifs, lastc) != NULL) { + if (lastc == '\n') + nnl++; + else + NEXTWORD(lastc, flag, dest, dst); + } else { + CHECKSTRSPACE(2, dest); + if (quotes && syntax[(int)lastc] == CCTL) + USTPUTC(CTLESC, dest); + USTPUTC(lastc, dest); + } + } + while (dest > stackblock() + startloc && STTOPC(dest) == '\n') + STUNPUTC(dest); + + if (in.fd >= 0) + close(in.fd); + if (in.buf) + ckfree(in.buf); + if (in.jp) { + p = grabstackstr(dest); + exitstatus = waitforjob(in.jp, (int *)NULL); + ungrabstackstr(p, dest); + } + TRACE(("expbackq: done\n")); + expdest = dest; + INTON; +} + + + +static void +recordleft(const char *str, const char *loc, char *startp) +{ + int amount; + + amount = ((str - 1) - (loc - startp)) - expdest; + STADJUST(amount, expdest); + while (loc != str - 1) + *startp++ = *loc++; +} + +static const char * +subevalvar_trim(const char *p, struct nodelist **restrict argbackq, int strloc, + int subtype, int startloc) +{ + char *startp; + char *loc = NULL; + char *str; + int c = 0; + int amount; + + p = argstr(p, argbackq, EXP_CASE | EXP_TILDE, NULL); + STACKSTRNUL(expdest); + startp = stackblock() + startloc; + str = stackblock() + strloc; + + switch (subtype) { + case VSTRIMLEFT: + for (loc = startp; loc < str; loc++) { + c = *loc; + *loc = '\0'; + if (patmatch(str, startp)) { + *loc = c; + recordleft(str, loc, startp); + return p; + } + *loc = c; + } + break; + + case VSTRIMLEFTMAX: + for (loc = str - 1; loc >= startp;) { + c = *loc; + *loc = '\0'; + if (patmatch(str, startp)) { + *loc = c; + recordleft(str, loc, startp); + return p; + } + *loc = c; + loc--; + } + break; + + case VSTRIMRIGHT: + for (loc = str - 1; loc >= startp;) { + if (patmatch(str, loc)) { + amount = loc - expdest; + STADJUST(amount, expdest); + return p; + } + loc--; + } + break; + + case VSTRIMRIGHTMAX: + for (loc = startp; loc < str - 1; loc++) { + if (patmatch(str, loc)) { + amount = loc - expdest; + STADJUST(amount, expdest); + return p; + } + } + break; + + + default: + abort(); + } + amount = (expdest - stackblock() - strloc) + 1; + STADJUST(-amount, expdest); + return p; +} + + +static const char * +subevalvar_misc(const char *p, struct nodelist **restrict argbackq, + const char *var, int subtype, int startloc, int varflags) +{ + const char *end; + char *startp; + int amount; + + end = argstr(p, argbackq, EXP_TILDE, NULL); + STACKSTRNUL(expdest); + startp = stackblock() + startloc; + + switch (subtype) { + case VSASSIGN: + setvar(var, startp, 0); + amount = startp - expdest; + STADJUST(amount, expdest); + return end; + + case VSQUESTION: + if (*p != CTLENDVAR) { + outfmt(out2, "%s\n", startp); + error((char *)NULL); + } + error("%.*s: parameter %snot set", (int)(p - var - 1), + var, (varflags & VSNUL) ? "null or " : ""); + + default: + abort(); + } +} + + +/* + * Expand a variable, and return a pointer to the next character in the + * input string. + */ + +static const char * +evalvar(const char *p, struct nodelist **restrict argbackq, int flag, + struct worddest *dst) +{ + int subtype; + int varflags; + const char *var; + const char *val; + int patloc; + int c; + int set; + int special; + int startloc; + int varlen; + int varlenb; + char buf[21]; + + varflags = (unsigned char)*p++; + subtype = varflags & VSTYPE; + var = p; + special = 0; + if (! is_name(*p)) + special = 1; + p = strchr(p, '=') + 1; + if (varflags & VSLINENO) { + set = 1; + special = 1; + val = NULL; + } else if (special) { + set = varisset(var, varflags & VSNUL); + val = NULL; + } else { + val = bltinlookup(var, 1); + if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) { + val = NULL; + set = 0; + } else + set = 1; + } + varlen = 0; + startloc = expdest - stackblock(); + if (!set && uflag && *var != '@' && *var != '*') { + switch (subtype) { + case VSNORMAL: + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + case VSLENGTH: + error("%.*s: parameter not set", (int)(p - var - 1), + var); + } + } + if (set && subtype != VSPLUS) { + /* insert the value of the variable */ + if (special) { + if (varflags & VSLINENO) { + if (p - var > (ptrdiff_t)sizeof(buf)) + abort(); + memcpy(buf, var, p - var - 1); + buf[p - var - 1] = '\0'; + strtodest(buf, flag, subtype, + varflags & VSQUOTE, dst); + } else + varvalue(var, varflags & VSQUOTE, subtype, flag, + dst); + if (subtype == VSLENGTH) { + varlenb = expdest - stackblock() - startloc; + varlen = varlenb; + if (localeisutf8) { + val = stackblock() + startloc; + for (;val != expdest; val++) + if ((*val & 0xC0) == 0x80) + varlen--; + } + STADJUST(-varlenb, expdest); + } + } else { + if (subtype == VSLENGTH) { + for (;*val; val++) + if (!localeisutf8 || + (*val & 0xC0) != 0x80) + varlen++; + } + else + strtodest(val, flag, subtype, + varflags & VSQUOTE, dst); + } + } + + if (subtype == VSPLUS) + set = ! set; + + switch (subtype) { + case VSLENGTH: + cvtnum(varlen, buf); + strtodest(buf, flag, VSNORMAL, varflags & VSQUOTE, dst); + break; + + case VSNORMAL: + return p; + + case VSPLUS: + case VSMINUS: + if (!set) { + return argstr(p, argbackq, + flag | (flag & EXP_SPLIT ? EXP_SPLIT_LIT : 0) | + (varflags & VSQUOTE ? EXP_LIT_QUOTED : 0), dst); + } + break; + + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + if (!set) + break; + /* + * Terminate the string and start recording the pattern + * right after it + */ + STPUTC('\0', expdest); + patloc = expdest - stackblock(); + p = subevalvar_trim(p, argbackq, patloc, subtype, startloc); + reprocess(startloc, flag, VSNORMAL, varflags & VSQUOTE, dst); + if (flag & EXP_SPLIT && *var == '@' && varflags & VSQUOTE) + dst->state = WORD_QUOTEMARK; + return p; + + case VSASSIGN: + case VSQUESTION: + if (!set) { + p = subevalvar_misc(p, argbackq, var, subtype, + startloc, varflags); + /* assert(subtype == VSASSIGN); */ + val = lookupvar(var); + strtodest(val, flag, subtype, varflags & VSQUOTE, dst); + return p; + } + break; + + case VSERROR: + c = p - var - 1; + error("${%.*s%s}: Bad substitution", c, var, + (c > 0 && *p != CTLENDVAR) ? "..." : ""); + + default: + abort(); + } + + { /* skip to end of alternative */ + int nesting = 1; + for (;;) { + if ((c = *p++) == CTLESC) + p++; + else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) + *argbackq = (*argbackq)->next; + else if (c == CTLVAR) { + if ((*p++ & VSTYPE) != VSNORMAL) + nesting++; + } else if (c == CTLENDVAR) { + if (--nesting == 0) + break; + } + } + } + return p; +} + + + +/* + * Test whether a special or positional parameter is set. + */ + +static int +varisset(const char *name, int nulok) +{ + + if (*name == '!') + return backgndpidset(); + else if (*name == '@' || *name == '*') { + if (*shellparam.p == NULL) + return 0; + + if (nulok) { + char **av; + + for (av = shellparam.p; *av; av++) + if (**av != '\0') + return 1; + return 0; + } + } else if (is_digit(*name)) { + char *ap; + long num; + + errno = 0; + num = strtol(name, NULL, 10); + if (errno != 0 || num > shellparam.nparam) + return 0; + + if (num == 0) + ap = arg0; + else + ap = shellparam.p[num - 1]; + + if (nulok && (ap == NULL || *ap == '\0')) + return 0; + } + return 1; +} + +static void +strtodest(const char *p, int flag, int subtype, int quoted, + struct worddest *dst) +{ + if (subtype == VSLENGTH || subtype == VSTRIMLEFT || + subtype == VSTRIMLEFTMAX || subtype == VSTRIMRIGHT || + subtype == VSTRIMRIGHTMAX) + STPUTS(p, expdest); + else if (flag & EXP_SPLIT && !quoted && dst != NULL) + STPUTS_SPLIT(p, BASESYNTAX, flag, expdest, dst); + else if (flag & (EXP_GLOB | EXP_CASE)) + STPUTS_QUOTES(p, quoted ? DQSYNTAX : BASESYNTAX, expdest); + else + STPUTS(p, expdest); +} + +static void +reprocess(int startloc, int flag, int subtype, int quoted, + struct worddest *dst) +{ + static char *buf = NULL; + static size_t buflen = 0; + char *startp; + size_t len, zpos, zlen; + + startp = stackblock() + startloc; + len = expdest - startp; + if (len >= SIZE_MAX / 2 || len > PTRDIFF_MAX) + abort(); + INTOFF; + if (len >= buflen) { + ckfree(buf); + buf = NULL; + } + if (buflen < 128) + buflen = 128; + while (len >= buflen) + buflen <<= 1; + if (buf == NULL) + buf = ckmalloc(buflen); + INTON; + memcpy(buf, startp, len); + buf[len] = '\0'; + STADJUST(-(ptrdiff_t)len, expdest); + for (zpos = 0;;) { + zlen = strlen(buf + zpos); + strtodest(buf + zpos, flag, subtype, quoted, dst); + zpos += zlen + 1; + if (zpos == len + 1) + break; + if (flag & EXP_SPLIT && (quoted || (zlen > 0 && zpos < len))) + NEXTWORD('\0', flag, expdest, dst); + } +} + +/* + * Add the value of a special or positional parameter to the stack string. + */ + +static void +varvalue(const char *name, int quoted, int subtype, int flag, + struct worddest *dst) +{ + int num; + char *p; + int i; + int splitlater; + char sep[2]; + char **ap; + char buf[(NSHORTOPTS > 10 ? NSHORTOPTS : 10) + 1]; + + if (subtype == VSLENGTH) + flag &= ~EXP_FULL; + splitlater = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX || + subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX; + + switch (*name) { + case '$': + num = rootpid; + break; + case '?': + num = oexitstatus; + break; + case '#': + num = shellparam.nparam; + break; + case '!': + num = backgndpidval(); + break; + case '-': + p = buf; + for (i = 0 ; i < NSHORTOPTS ; i++) { + if (optval[i]) + *p++ = optletter[i]; + } + *p = '\0'; + strtodest(buf, flag, subtype, quoted, dst); + return; + case '@': + if (flag & EXP_SPLIT && quoted) { + for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { + strtodest(p, flag, subtype, quoted, dst); + if (*ap) { + if (splitlater) + STPUTC('\0', expdest); + else + NEXTWORD('\0', flag, expdest, + dst); + } + } + if (shellparam.nparam > 0) + dst->state = WORD_QUOTEMARK; + return; + } + /* FALLTHROUGH */ + case '*': + if (ifsset()) + sep[0] = ifsval()[0]; + else + sep[0] = ' '; + sep[1] = '\0'; + for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { + strtodest(p, flag, subtype, quoted, dst); + if (!*ap) + break; + if (sep[0]) + strtodest(sep, flag, subtype, quoted, dst); + else if (flag & EXP_SPLIT && !quoted && **ap != '\0') { + if (splitlater) + STPUTC('\0', expdest); + else + NEXTWORD('\0', flag, expdest, dst); + } + } + return; + default: + if (is_digit(*name)) { + num = atoi(name); + if (num == 0) + p = arg0; + else if (num > 0 && num <= shellparam.nparam) + p = shellparam.p[num - 1]; + else + return; + strtodest(p, flag, subtype, quoted, dst); + } + return; + } + cvtnum(num, buf); + strtodest(buf, flag, subtype, quoted, dst); +} + + + +static char expdir[PATH_MAX]; +#define expdir_end (expdir + sizeof(expdir)) + +/* + * Perform pathname generation and remove control characters. + * At this point, the only control characters should be CTLESC. + * The results are stored in the list dstlist. + */ +static void +expandmeta(char *pattern, struct arglist *dstlist) +{ + char *p; + int firstmatch; + char c; + + firstmatch = dstlist->count; + p = pattern; + for (; (c = *p) != '\0'; p++) { + /* fast check for meta chars */ + if (c == '*' || c == '?' || c == '[') { + INTOFF; + expmeta(expdir, pattern, dstlist); + INTON; + break; + } + } + if (dstlist->count == firstmatch) { + /* + * no matches + */ + rmescapes(pattern); + appendarglist(dstlist, pattern); + } else { + qsort(&dstlist->args[firstmatch], + dstlist->count - firstmatch, + sizeof(dstlist->args[0]), expsortcmp); + } +} + + +/* + * Do metacharacter (i.e. *, ?, [...]) expansion. + */ + +static void +expmeta(char *enddir, char *name, struct arglist *arglist) +{ + const char *p; + const char *q; + const char *start; + char *endname; + int metaflag; + struct stat statb; + DIR *dirp; + struct dirent *dp; + int atend; + int matchdot; + int esc; + int namlen; + + metaflag = 0; + start = name; + for (p = name; esc = 0, *p; p += esc + 1) { + if (*p == '*' || *p == '?') + metaflag = 1; + else if (*p == '[') { + q = p + 1; + if (*q == '!' || *q == '^') + q++; + for (;;) { + if (*q == CTLESC) + q++; + if (*q == '/' || *q == '\0') + break; + if (*++q == ']') { + metaflag = 1; + break; + } + } + } else if (*p == '\0') + break; + else { + if (*p == CTLESC) + esc++; + if (p[esc] == '/') { + if (metaflag) + break; + start = p + esc + 1; + } + } + } + if (metaflag == 0) { /* we've reached the end of the file name */ + if (enddir != expdir) + metaflag++; + for (p = name ; ; p++) { + if (*p == CTLESC) + p++; + *enddir++ = *p; + if (*p == '\0') + break; + if (enddir == expdir_end) + return; + } + if (metaflag == 0 || lstat(expdir, &statb) >= 0) + appendarglist(arglist, stsavestr(expdir)); + return; + } + endname = name + (p - name); + if (start != name) { + p = name; + while (p < start) { + if (*p == CTLESC) + p++; + *enddir++ = *p++; + if (enddir == expdir_end) + return; + } + } + if (enddir == expdir) { + p = "."; + } else if (enddir == expdir + 1 && *expdir == '/') { + p = "/"; + } else { + p = expdir; + enddir[-1] = '\0'; + } + if ((dirp = opendir(p)) == NULL) + return; + if (enddir != expdir) + enddir[-1] = '/'; + if (*endname == 0) { + atend = 1; + } else { + atend = 0; + *endname = '\0'; + endname += esc + 1; + } + matchdot = 0; + p = start; + if (*p == CTLESC) + p++; + if (*p == '.') + matchdot++; + while (! int_pending() && (dp = readdir(dirp)) != NULL) { + if (dp->d_name[0] == '.' && ! matchdot) + continue; + if (patmatch(start, dp->d_name)) { + namlen = dp->d_namlen; + if (enddir + namlen + 1 > expdir_end) + continue; + memcpy(enddir, dp->d_name, namlen + 1); + if (atend) + appendarglist(arglist, stsavestr(expdir)); + else { + if (dp->d_type != DT_UNKNOWN && + dp->d_type != DT_DIR && + dp->d_type != DT_LNK) + continue; + if (enddir + namlen + 2 > expdir_end) + continue; + enddir[namlen] = '/'; + enddir[namlen + 1] = '\0'; + expmeta(enddir + namlen + 1, endname, arglist); + } + } + } + closedir(dirp); + if (! atend) + endname[-esc - 1] = esc ? CTLESC : '/'; +} + + +static int +expsortcmp(const void *p1, const void *p2) +{ + const char *s1 = *(const char * const *)p1; + const char *s2 = *(const char * const *)p2; + + return (strcoll(s1, s2)); +} + + + +static wchar_t +get_wc(const char **p) +{ + wchar_t c; + int chrlen; + + chrlen = mbtowc(&c, *p, 4); + if (chrlen == 0) + return 0; + else if (chrlen == -1) + c = 0; + else + *p += chrlen; + return c; +} + + +/* + * See if a character matches a character class, starting at the first colon + * of "[:class:]". + * If a valid character class is recognized, a pointer to the next character + * after the final closing bracket is stored into *end, otherwise a null + * pointer is stored into *end. + */ +static int +match_charclass(const char *p, wchar_t chr, const char **end) +{ + char name[20]; + const char *nameend; + wctype_t cclass; + + *end = NULL; + p++; + nameend = strstr(p, ":]"); + if (nameend == NULL || (size_t)(nameend - p) >= sizeof(name) || + nameend == p) + return 0; + memcpy(name, p, nameend - p); + name[nameend - p] = '\0'; + *end = nameend + 2; + cclass = wctype(name); + /* An unknown class matches nothing but is valid nevertheless. */ + if (cclass == 0) + return 0; + return iswctype(chr, cclass); +} + + +/* + * Returns true if the pattern matches the string. + */ + +static int +patmatch(const char *pattern, const char *string) +{ + const char *p, *q, *end; + const char *bt_p, *bt_q; + char c; + wchar_t wc, wc2; + + p = pattern; + q = string; + bt_p = NULL; + bt_q = NULL; + for (;;) { + switch (c = *p++) { + case '\0': + if (*q != '\0') + goto backtrack; + return 1; + case CTLESC: + if (*q++ != *p++) + goto backtrack; + break; + case '?': + if (*q == '\0') + return 0; + if (localeisutf8) { + wc = get_wc(&q); + /* + * A '?' does not match invalid UTF-8 but a + * '*' does, so backtrack. + */ + if (wc == 0) + goto backtrack; + } else + q++; + break; + case '*': + c = *p; + while (c == '*') + c = *++p; + /* + * If the pattern ends here, we know the string + * matches without needing to look at the rest of it. + */ + if (c == '\0') + return 1; + /* + * First try the shortest match for the '*' that + * could work. We can forget any earlier '*' since + * there is no way having it match more characters + * can help us, given that we are already here. + */ + bt_p = p; + bt_q = q; + break; + case '[': { + const char *savep, *saveq; + int invert, found; + wchar_t chr; + + savep = p, saveq = q; + invert = 0; + if (*p == '!' || *p == '^') { + invert++; + p++; + } + found = 0; + if (*q == '\0') + return 0; + if (localeisutf8) { + chr = get_wc(&q); + if (chr == 0) + goto backtrack; + } else + chr = (unsigned char)*q++; + c = *p++; + do { + if (c == '\0') { + p = savep, q = saveq; + c = '['; + goto dft; + } + if (c == '[' && *p == ':') { + found |= match_charclass(p, chr, &end); + if (end != NULL) { + p = end; + continue; + } + } + if (c == CTLESC) + c = *p++; + if (localeisutf8 && c & 0x80) { + p--; + wc = get_wc(&p); + if (wc == 0) /* bad utf-8 */ + return 0; + } else + wc = (unsigned char)c; + if (*p == '-' && p[1] != ']') { + p++; + if (*p == CTLESC) + p++; + if (localeisutf8) { + wc2 = get_wc(&p); + if (wc2 == 0) /* bad utf-8 */ + return 0; + } else + wc2 = (unsigned char)*p++; + if ( collate_range_cmp(chr, wc) >= 0 + && collate_range_cmp(chr, wc2) <= 0 + ) + found = 1; + } else { + if (chr == wc) + found = 1; + } + } while ((c = *p++) != ']'); + if (found == invert) + goto backtrack; + break; + } +dft: default: + if (*q == '\0') + return 0; + if (*q++ == c) + break; +backtrack: + /* + * If we have a mismatch (other than hitting the end + * of the string), go back to the last '*' seen and + * have it match one additional character. + */ + if (bt_p == NULL) + return 0; + if (*bt_q == '\0') + return 0; + bt_q++; + p = bt_p; + q = bt_q; + break; + } + } +} + + + +/* + * Remove any CTLESC and CTLQUOTEMARK characters from a string. + */ + +void +rmescapes(char *str) +{ + char *p, *q; + + p = str; + while (*p != CTLESC && *p != CTLQUOTEMARK && *p != CTLQUOTEEND) { + if (*p++ == '\0') + return; + } + q = p; + while (*p) { + if (*p == CTLQUOTEMARK || *p == CTLQUOTEEND) { + p++; + continue; + } + if (*p == CTLESC) + p++; + *q++ = *p++; + } + *q = '\0'; +} + + + +/* + * See if a pattern matches in a case statement. + */ + +int +casematch(union node *pattern, const char *val) +{ + struct stackmark smark; + struct nodelist *argbackq; + int result; + char *p; + + setstackmark(&smark); + argbackq = pattern->narg.backquote; + STARTSTACKSTR(expdest); + argstr(pattern->narg.text, &argbackq, EXP_TILDE | EXP_CASE, NULL); + STPUTC('\0', expdest); + p = grabstackstr(expdest); + result = patmatch(p, val); + popstackmark(&smark); + return result; +} + +/* + * Our own itoa(). + */ + +static void +cvtnum(int num, char *buf) +{ + char temp[32]; + int neg = num < 0; + char *p = temp + 31; + + temp[31] = '\0'; + + do { + *--p = num % 10 + '0'; + } while ((num /= 10) != 0); + + if (neg) + *--p = '-'; + + memcpy(buf, p, temp + 32 - p); +} + +/* + * Do most of the work for wordexp(3). + */ + +int +wordexpcmd(int argc, char **argv) +{ + size_t len; + int i; + + out1fmt("%08x", argc - 1); + for (i = 1, len = 0; i < argc; i++) + len += strlen(argv[i]); + out1fmt("%08x", (int)len); + for (i = 1; i < argc; i++) + outbin(argv[i], strlen(argv[i]) + 1, out1); + return (0); +} + +/* + * Do most of the work for wordexp(3), new version. + */ + +int +freebsd_wordexpcmd(int argc __unused, char **argv __unused) +{ + struct arglist arglist; + union node *args, *n; + size_t len; + int ch; + int protected = 0; + int fd = -1; + int i; + + while ((ch = nextopt("f:p")) != '\0') { + switch (ch) { + case 'f': + fd = number(shoptarg); + break; + case 'p': + protected = 1; + break; + } + } + if (*argptr != NULL) + error("wrong number of arguments"); + if (fd < 0) + error("missing fd"); + INTOFF; + setinputfd(fd, 1); + INTON; + args = parsewordexp(); + popfile(); /* will also close fd */ + if (protected) + for (n = args; n != NULL; n = n->narg.next) { + if (n->narg.backquote != NULL) { + outcslow('C', out1); + error("command substitution disabled"); + } + } + outcslow(' ', out1); + emptyarglist(&arglist); + for (n = args; n != NULL; n = n->narg.next) + expandarg(n, &arglist, EXP_FULL | EXP_TILDE); + for (i = 0, len = 0; i < arglist.count; i++) + len += strlen(arglist.args[i]); + out1fmt("%016x %016zx", arglist.count, len); + for (i = 0; i < arglist.count; i++) + outbin(arglist.args[i], strlen(arglist.args[i]) + 1, out1); + return (0); +} diff --git a/bin/1sh/expand.h b/bin/1sh/expand.h new file mode 100644 index 00000000..2bd9bbd9 --- /dev/null +++ b/bin/1sh/expand.h @@ -0,0 +1,62 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)expand.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/expand.h 314436 2017-02-28 23:42:47Z imp $ + */ + +struct arglist { + char **args; + int count; + int capacity; + char *smallarg[1]; +}; + +/* + * expandarg() flags + */ +#define EXP_SPLIT 0x1 /* perform word splitting */ +#define EXP_TILDE 0x2 /* do normal tilde expansion */ +#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ +#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ +#define EXP_SPLIT_LIT 0x20 /* IFS split literal text ${v+-a b c} */ +#define EXP_LIT_QUOTED 0x40 /* for EXP_SPLIT_LIT, start off quoted */ +#define EXP_GLOB 0x80 /* perform file globbing */ + +#define EXP_FULL (EXP_SPLIT | EXP_GLOB) + + +void emptyarglist(struct arglist *); +void appendarglist(struct arglist *, char *); +union node; +void expandarg(union node *, struct arglist *, int); +void rmescapes(char *); +int casematch(union node *, const char *); diff --git a/bin/1sh/histedit.c b/bin/1sh/histedit.c new file mode 100644 index 00000000..e0bda06e --- /dev/null +++ b/bin/1sh/histedit.c @@ -0,0 +1,558 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/histedit.c 345613 2019-03-27 21:53:44Z jilles $ */ + +#include <sys/param.h> +#include <limits.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +/* + * Editline and history functions (and glue). + */ +#include "shell.h" +#include "parser.h" +#include "var.h" +#include "options.h" +#include "main.h" +#include "output.h" +#include "mystring.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#include "error.h" +#include "eval.h" +#include "memalloc.h" +#include "builtins.h" + +#define MAXHISTLOOPS 4 /* max recursions through fc */ +#define DEFEDITOR "ed" /* default editor *should* be $EDITOR */ + +History *hist; /* history cookie */ +EditLine *el; /* editline cookie */ +int displayhist; +static FILE *el_in, *el_out, *el_err; + +static void history_load(const char *hf); +static void history_save(const char *hf); + +static char *fc_replace(const char *, char *, char *); +static int not_fcnumber(const char *); +static int str_to_event(const char *, int); + +/* + * Set history and editing status. Called whenever the status may + * have changed (figures out what to do). + */ +void +histedit(void) +{ + +#define editing (Eflag || Vflag) + + if (iflag) { + if (!hist) { + /* + * turn history on + */ + INTOFF; + hist = history_init(); + INTON; + + if (hist != NULL) { + sethistsize(histsizeval()); + sethistfile(histfileval()); + } else + out2fmt_flush("sh: can't initialize history\n"); + } + if (editing && !el && isatty(0)) { /* && isatty(2) ??? */ + /* + * turn editing on + */ + char *term; + + INTOFF; + if (el_in == NULL) + el_in = fdopen(0, "r"); + if (el_err == NULL) + el_err = fdopen(1, "w"); + if (el_out == NULL) + el_out = fdopen(2, "w"); + if (el_in == NULL || el_err == NULL || el_out == NULL) + goto bad; + term = lookupvar("TERM"); + if (term) + setenv("TERM", term, 1); + else + unsetenv("TERM"); + el = el_init(arg0, el_in, el_out, el_err); + if (el != NULL) { + if (hist) + el_set(el, EL_HIST, history, hist); + el_set(el, EL_PROMPT, getprompt); + el_set(el, EL_RPROMPT, getrprompt); + el_set(el, EL_ADDFN, "sh-complete", + "Filename completion", + _el_fn_complete); + } else { +bad: + out2fmt_flush("sh: can't initialize editing\n"); + } + INTON; + } else if (!editing && el) { + INTOFF; + el_end(el); + el = NULL; + INTON; + } + if (el) { + el_source(el, NULL); + if (Vflag) + el_set(el, EL_EDITOR, "vi"); + else if (Eflag) + el_set(el, EL_EDITOR, "emacs"); + el_set(el, EL_BIND, "^I", "sh-complete", NULL); + } + } else { + INTOFF; + if (el) { /* no editing if not interactive */ + el_end(el); + el = NULL; + } + if (hist) { + if (*histfileval() != '\0') + history_save(histfileval()); + history_end(hist); + hist = NULL; + } + INTON; + } +} + + +void +sethistfile(const char *hf) +{ + if (hist != NULL && hf != NULL && *hf != '\0') + history_load(hf); +} + + +static void +history_load(const char *hf) +{ + HistEvent he; + + if (history(hist, &he, H_LOAD, hf) == -1) + warning("%s: %s", he.str, hf); +} + + +static void +history_save(const char *hf) +{ + HistEvent he; + + if (history(hist, &he, H_SAVE, hf) == -1) + warning("%s: %s", he.str, hf); +} + + +void +sethistsize(const char *hs) +{ + int histsize; + HistEvent he; + + if (hist != NULL) { + if (hs == NULL || !is_number(hs)) + histsize = 100; + else + histsize = atoi(hs); + history(hist, &he, H_SETSIZE, histsize); + history(hist, &he, H_SETUNIQUE, 1); + } +} + +void +setterm(const char *term) +{ + if (rootshell && el != NULL && term != NULL) + el_set(el, EL_TERMINAL, term); +} + +int +histcmd(int argc, char **argv __unused) +{ + int ch; + const char *editor = NULL; + HistEvent he; + int lflg = 0, nflg = 0, rflg = 0, sflg = 0; + int i, retval; + const char *firststr, *laststr; + int first, last, direction; + char *pat = NULL, *repl = NULL; + static int active = 0; + struct jmploc jmploc; + struct jmploc *savehandler; + char editfilestr[PATH_MAX]; + char *volatile editfile; + FILE *efp = NULL; + int oldhistnum; + + if (hist == NULL) + error("history not active"); + + if (argc == 1) + error("missing history argument"); + + while (not_fcnumber(*argptr) && (ch = nextopt("e:lnrs")) != '\0') + switch ((char)ch) { + case 'e': + editor = shoptarg; + break; + case 'l': + lflg = 1; + break; + case 'n': + nflg = 1; + break; + case 'r': + rflg = 1; + break; + case 's': + sflg = 1; + break; + } + + savehandler = handler; + /* + * If executing... + */ + if (lflg == 0 || editor || sflg) { + lflg = 0; /* ignore */ + editfile = NULL; + /* + * Catch interrupts to reset active counter and + * cleanup temp files. + */ + if (setjmp(jmploc.loc)) { + active = 0; + if (editfile) + unlink(editfile); + handler = savehandler; + longjmp(handler->loc, 1); + } + handler = &jmploc; + if (++active > MAXHISTLOOPS) { + active = 0; + displayhist = 0; + error("called recursively too many times"); + } + /* + * Set editor. + */ + if (sflg == 0) { + if (editor == NULL && + (editor = bltinlookup("FCEDIT", 1)) == NULL && + (editor = bltinlookup("EDITOR", 1)) == NULL) + editor = DEFEDITOR; + if (editor[0] == '-' && editor[1] == '\0') { + sflg = 1; /* no edit */ + editor = NULL; + } + } + } + + /* + * If executing, parse [old=new] now + */ + if (lflg == 0 && *argptr != NULL && + ((repl = strchr(*argptr, '=')) != NULL)) { + pat = *argptr; + *repl++ = '\0'; + argptr++; + } + /* + * determine [first] and [last] + */ + if (*argptr == NULL) { + firststr = lflg ? "-16" : "-1"; + laststr = "-1"; + } else if (argptr[1] == NULL) { + firststr = argptr[0]; + laststr = lflg ? "-1" : argptr[0]; + } else if (argptr[2] == NULL) { + firststr = argptr[0]; + laststr = argptr[1]; + } else + error("too many arguments"); + /* + * Turn into event numbers. + */ + first = str_to_event(firststr, 0); + last = str_to_event(laststr, 1); + + if (rflg) { + i = last; + last = first; + first = i; + } + /* + * XXX - this should not depend on the event numbers + * always increasing. Add sequence numbers or offset + * to the history element in next (diskbased) release. + */ + direction = first < last ? H_PREV : H_NEXT; + + /* + * If editing, grab a temp file. + */ + if (editor) { + int fd; + INTOFF; /* easier */ + sprintf(editfilestr, "%s/_shXXXXXX", _PATH_TMP); + if ((fd = mkstemp(editfilestr)) < 0) + error("can't create temporary file %s", editfile); + editfile = editfilestr; + if ((efp = fdopen(fd, "w")) == NULL) { + close(fd); + error("Out of space"); + } + } + + /* + * Loop through selected history events. If listing or executing, + * do it now. Otherwise, put into temp file and call the editor + * after. + * + * The history interface needs rethinking, as the following + * convolutions will demonstrate. + */ + history(hist, &he, H_FIRST); + retval = history(hist, &he, H_NEXT_EVENT, first); + for (;retval != -1; retval = history(hist, &he, direction)) { + if (lflg) { + if (!nflg) + out1fmt("%5d ", he.num); + out1str(he.str); + } else { + const char *s = pat ? + fc_replace(he.str, pat, repl) : he.str; + + if (sflg) { + if (displayhist) { + out2str(s); + flushout(out2); + } + evalstring(s, 0); + if (displayhist && hist) { + /* + * XXX what about recursive and + * relative histnums. + */ + oldhistnum = he.num; + history(hist, &he, H_ENTER, s); + /* + * XXX H_ENTER moves the internal + * cursor, set it back to the current + * entry. + */ + history(hist, &he, + H_NEXT_EVENT, oldhistnum); + } + } else + fputs(s, efp); + } + /* + * At end? (if we were to lose last, we'd sure be + * messed up). + */ + if (he.num == last) + break; + } + if (editor) { + char *editcmd; + + fclose(efp); + INTON; + editcmd = stalloc(strlen(editor) + strlen(editfile) + 2); + sprintf(editcmd, "%s %s", editor, editfile); + evalstring(editcmd, 0); /* XXX - should use no JC command */ + readcmdfile(editfile); /* XXX - should read back - quick tst */ + unlink(editfile); + } + + if (lflg == 0 && active > 0) + --active; + if (displayhist) + displayhist = 0; + handler = savehandler; + return 0; +} + +static char * +fc_replace(const char *s, char *p, char *r) +{ + char *dest; + int plen = strlen(p); + + STARTSTACKSTR(dest); + while (*s) { + if (*s == *p && strncmp(s, p, plen) == 0) { + STPUTS(r, dest); + s += plen; + *p = '\0'; /* so no more matches */ + } else + STPUTC(*s++, dest); + } + STPUTC('\0', dest); + dest = grabstackstr(dest); + + return (dest); +} + +static int +not_fcnumber(const char *s) +{ + if (s == NULL) + return (0); + if (*s == '-') + s++; + return (!is_number(s)); +} + +static int +str_to_event(const char *str, int last) +{ + HistEvent he; + const char *s = str; + int relative = 0; + int i, retval; + + retval = history(hist, &he, H_FIRST); + switch (*s) { + case '-': + relative = 1; + /*FALLTHROUGH*/ + case '+': + s++; + } + if (is_number(s)) { + i = atoi(s); + if (relative) { + while (retval != -1 && i--) { + retval = history(hist, &he, H_NEXT); + } + if (retval == -1) + retval = history(hist, &he, H_LAST); + } else { + retval = history(hist, &he, H_NEXT_EVENT, i); + if (retval == -1) { + /* + * the notion of first and last is + * backwards to that of the history package + */ + retval = history(hist, &he, last ? H_FIRST : H_LAST); + } + } + if (retval == -1) + error("history number %s not found (internal error)", + str); + } else { + /* + * pattern + */ + retval = history(hist, &he, H_PREV_STR, str); + if (retval == -1) + error("history pattern not found: %s", str); + } + return (he.num); +} + +int +bindcmd(int argc, char **argv) +{ + int ret; + FILE *old; + FILE *out; + + if (el == NULL) + error("line editing is disabled"); + + INTOFF; + + out = out1fp(); + if (out == NULL) + error("Out of space"); + + el_get(el, EL_GETFP, 1, &old); + el_set(el, EL_SETFP, 1, out); + + ret = el_parse(el, argc, (const char **)(argv)); + + el_set(el, EL_SETFP, 1, old); + + fclose(out); + + INTON; + + return ret; +} + +#else +#include "error.h" + +int +histcmd(int argc, char **argv) +{ + + error("not compiled with history support"); + /*NOTREACHED*/ + return (0); +} + +int +bindcmd(int argc, char **argv) +{ + + error("not compiled with line editing support"); + return (0); +} +#endif diff --git a/bin/1sh/input.c b/bin/1sh/input.c new file mode 100644 index 00000000..37f689a5 --- /dev/null +++ b/bin/1sh/input.c @@ -0,0 +1,520 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)input.c 8.3 (Berkeley) 6/9/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/input.c 314436 2017-02-28 23:42:47Z imp $ */ + +#include <stdio.h> /* defines BUFSIZ */ +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +/* + * This file implements the input routines used by the parser. + */ + +#include "shell.h" +#include "redir.h" +#include "syntax.h" +#include "input.h" +#include "output.h" +#include "options.h" +#include "memalloc.h" +#include "error.h" +#include "alias.h" +#include "parser.h" +#include "myhistedit.h" +#include "trap.h" + +#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */ + +struct strpush { + struct strpush *prev; /* preceding string on stack */ + const char *prevstring; + int prevnleft; + int prevlleft; + struct alias *ap; /* if push was associated with an alias */ +}; + +/* + * The parsefile structure pointed to by the global variable parsefile + * contains information about the current file being read. + */ + +struct parsefile { + struct parsefile *prev; /* preceding file on stack */ + int linno; /* current line */ + int fd; /* file descriptor (or -1 if string) */ + int nleft; /* number of chars left in this line */ + int lleft; /* number of lines left in this buffer */ + const char *nextc; /* next char in buffer */ + char *buf; /* input buffer */ + struct strpush *strpush; /* for pushing strings at this level */ + struct strpush basestrpush; /* so pushing one is fast */ +}; + + +int plinno = 1; /* input line number */ +int parsenleft; /* copy of parsefile->nleft */ +static int parselleft; /* copy of parsefile->lleft */ +const char *parsenextc; /* copy of parsefile->nextc */ +static char basebuf[BUFSIZ + 1];/* buffer for top level input file */ +static struct parsefile basepf = { /* top level input file */ + .nextc = basebuf, + .buf = basebuf +}; +static struct parsefile *parsefile = &basepf; /* current input file */ +int whichprompt; /* 1 == PS1, 2 == PS2 */ + +EditLine *el; /* cookie for editline package */ + +static void pushfile(void); +static int preadfd(void); +static void popstring(void); + +void +resetinput(void) +{ + popallfiles(); + parselleft = parsenleft = 0; /* clear input buffer */ +} + + + +/* + * Read a character from the script, returning PEOF on end of file. + * Nul characters in the input are silently discarded. + */ + +int +pgetc(void) +{ + return pgetc_macro(); +} + + +static int +preadfd(void) +{ + int nr; + parsenextc = parsefile->buf; + +retry: +#ifndef NO_HISTORY + if (parsefile->fd == 0 && el) { + static const char *rl_cp; + static int el_len; + + if (rl_cp == NULL) { + el_resize(el); + rl_cp = el_gets(el, &el_len); + } + if (rl_cp == NULL) + nr = el_len == 0 ? 0 : -1; + else { + nr = el_len; + if (nr > BUFSIZ) + nr = BUFSIZ; + memcpy(parsefile->buf, rl_cp, nr); + if (nr != el_len) { + el_len -= nr; + rl_cp += nr; + } else + rl_cp = NULL; + } + } else +#endif + nr = read(parsefile->fd, parsefile->buf, BUFSIZ); + + if (nr <= 0) { + if (nr < 0) { + if (errno == EINTR) + goto retry; + if (parsefile->fd == 0 && errno == EWOULDBLOCK) { + int flags = fcntl(0, F_GETFL, 0); + if (flags >= 0 && flags & O_NONBLOCK) { + flags &=~ O_NONBLOCK; + if (fcntl(0, F_SETFL, flags) >= 0) { + out2fmt_flush("sh: turning off NDELAY mode\n"); + goto retry; + } + } + } + } + nr = -1; + } + return nr; +} + +/* + * Refill the input buffer and return the next input character: + * + * 1) If a string was pushed back on the input, pop it; + * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading + * from a string so we can't refill the buffer, return EOF. + * 3) If there is more in this buffer, use it else call read to fill it. + * 4) Process input up to the next newline, deleting nul characters. + */ + +int +preadbuffer(void) +{ + char *p, *q, *r, *end; + char savec; + + while (parsefile->strpush) { + /* + * Add a space to the end of an alias to ensure that the + * alias remains in use while parsing its last word. + * This avoids alias recursions. + */ + if (parsenleft == -1 && parsefile->strpush->ap != NULL) + return ' '; + popstring(); + if (--parsenleft >= 0) + return (*parsenextc++); + } + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return PEOF; + +again: + if (parselleft <= 0) { + if ((parselleft = preadfd()) == -1) { + parselleft = parsenleft = EOF_NLEFT; + return PEOF; + } + } + + p = parsefile->buf + (parsenextc - parsefile->buf); + end = p + parselleft; + *end = '\0'; + q = strchr(p, '\n'); + if (!q) q = strchr(p, '\0'); + if (q != end && *q == '\0') { + /* delete nul characters */ + for (r = q; q != end; q++) { + if (*q != '\0') + *r++ = *q; + } + parselleft -= end - r; + if (parselleft == 0) + goto again; + end = p + parselleft; + *end = '\0'; + q = strchr(p, '\n'); + if (!q) q = strchr(p, '\0'); + } + if (q == end) { + parsenleft = parselleft; + parselleft = 0; + } else /* *q == '\n' */ { + q++; + parsenleft = q - parsenextc; + parselleft -= parsenleft; + } + parsenleft--; + + savec = *q; + *q = '\0'; + +#ifndef NO_HISTORY + if (parsefile->fd == 0 && hist && + parsenextc[strspn(parsenextc, " \t\n")] != '\0') { + HistEvent he; + INTOFF; + history(hist, &he, whichprompt == 1 ? H_ENTER : H_ADD, + parsenextc); + INTON; + } +#endif + + if (vflag) { + out2str(parsenextc); + flushout(out2); + } + + *q = savec; + + return *parsenextc++; +} + +/* + * Returns if we are certain we are at EOF. Does not cause any more input + * to be read from the outside world. + */ + +int +preadateof(void) +{ + if (parsenleft > 0) + return 0; + if (parsefile->strpush) + return 0; + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return 1; + return 0; +} + +/* + * Undo the last call to pgetc. Only one character may be pushed back. + * PEOF may be pushed back. + */ + +void +pungetc(void) +{ + parsenleft++; + parsenextc--; +} + +/* + * Push a string back onto the input at this current parsefile level. + * We handle aliases this way. + */ +void +pushstring(const char *s, int len, struct alias *ap) +{ + struct strpush *sp; + + INTOFF; +/*out2fmt_flush("*** calling pushstring: %s, %d\n", s, len);*/ + if (parsefile->strpush) { + sp = ckmalloc(sizeof (struct strpush)); + sp->prev = parsefile->strpush; + parsefile->strpush = sp; + } else + sp = parsefile->strpush = &(parsefile->basestrpush); + sp->prevstring = parsenextc; + sp->prevnleft = parsenleft; + sp->prevlleft = parselleft; + sp->ap = ap; + if (ap) + ap->flag |= ALIASINUSE; + parsenextc = s; + parsenleft = len; + INTON; +} + +static void +popstring(void) +{ + struct strpush *sp = parsefile->strpush; + + INTOFF; + if (sp->ap) { + if (parsenextc != sp->ap->val && + (parsenextc[-1] == ' ' || parsenextc[-1] == '\t')) + forcealias(); + sp->ap->flag &= ~ALIASINUSE; + } + parsenextc = sp->prevstring; + parsenleft = sp->prevnleft; + parselleft = sp->prevlleft; +/*out2fmt_flush("*** calling popstring: restoring to '%s'\n", parsenextc);*/ + parsefile->strpush = sp->prev; + if (sp != &(parsefile->basestrpush)) + ckfree(sp); + INTON; +} + +/* + * Set the input to take input from a file. If push is set, push the + * old input onto the stack first. + */ + +void +setinputfile(const char *fname, int push) +{ + int fd; + int fd2; + + INTOFF; + if ((fd = open(fname, O_RDONLY | O_CLOEXEC)) < 0) + error("cannot open %s: %s", fname, strerror(errno)); + if (fd < 10) { + fd2 = fcntl(fd, F_DUPFD_CLOEXEC, 10); + close(fd); + if (fd2 < 0) + error("Out of file descriptors"); + fd = fd2; + } + setinputfd(fd, push); + INTON; +} + + +/* + * Like setinputfile, but takes an open file descriptor (which should have + * its FD_CLOEXEC flag already set). Call this with interrupts off. + */ + +void +setinputfd(int fd, int push) +{ + if (push) { + pushfile(); + parsefile->buf = ckmalloc(BUFSIZ + 1); + } + if (parsefile->fd > 0) + close(parsefile->fd); + parsefile->fd = fd; + if (parsefile->buf == NULL) + parsefile->buf = ckmalloc(BUFSIZ + 1); + parselleft = parsenleft = 0; + plinno = 1; +} + + +/* + * Like setinputfile, but takes input from a string. + */ + +void +setinputstring(const char *string, int push) +{ + INTOFF; + if (push) + pushfile(); + parsenextc = string; + parselleft = parsenleft = strlen(string); + parsefile->buf = NULL; + plinno = 1; + INTON; +} + + + +/* + * To handle the "." command, a stack of input files is used. Pushfile + * adds a new entry to the stack and popfile restores the previous level. + */ + +static void +pushfile(void) +{ + struct parsefile *pf; + + parsefile->nleft = parsenleft; + parsefile->lleft = parselleft; + parsefile->nextc = parsenextc; + parsefile->linno = plinno; + pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile)); + pf->prev = parsefile; + pf->fd = -1; + pf->strpush = NULL; + pf->basestrpush.prev = NULL; + parsefile = pf; +} + + +void +popfile(void) +{ + struct parsefile *pf = parsefile; + + INTOFF; + if (pf->fd >= 0) + close(pf->fd); + if (pf->buf) + ckfree(pf->buf); + while (pf->strpush) + popstring(); + parsefile = pf->prev; + ckfree(pf); + parsenleft = parsefile->nleft; + parselleft = parsefile->lleft; + parsenextc = parsefile->nextc; + plinno = parsefile->linno; + INTON; +} + + +/* + * Return current file (to go back to it later using popfilesupto()). + */ + +struct parsefile * +getcurrentfile(void) +{ + return parsefile; +} + + +/* + * Pop files until the given file is on top again. Useful for regular + * builtins that read shell commands from files or strings. + * If the given file is not an active file, an error is raised. + */ + +void +popfilesupto(struct parsefile *file) +{ + while (parsefile != file && parsefile != &basepf) + popfile(); + if (parsefile != file) + error("popfilesupto() misused"); +} + +/* + * Return to top level. + */ + +void +popallfiles(void) +{ + while (parsefile != &basepf) + popfile(); +} + + + +/* + * Close the file(s) that the shell is reading commands from. Called + * after a fork is done. + */ + +void +closescript(void) +{ + popallfiles(); + if (parsefile->fd > 0) { + close(parsefile->fd); + parsefile->fd = 0; + } +} diff --git a/bin/1sh/input.h b/bin/1sh/input.h new file mode 100644 index 00000000..1d0047cc --- /dev/null +++ b/bin/1sh/input.h @@ -0,0 +1,65 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)input.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/input.h 314436 2017-02-28 23:42:47Z imp $ + */ + +/* PEOF (the end of file marker) is defined in syntax.h */ + +/* + * The input line number. Input.c just defines this variable, and saves + * and restores it when files are pushed and popped. The user of this + * package must set its value. + */ +extern int plinno; +extern int parsenleft; /* number of characters left in input buffer */ +extern const char *parsenextc; /* next character in input buffer */ + +struct alias; +struct parsefile; + +void resetinput(void); +int pgetc(void); +int preadbuffer(void); +int preadateof(void); +void pungetc(void); +void pushstring(const char *, int, struct alias *); +void setinputfile(const char *, int); +void setinputfd(int, int); +void setinputstring(const char *, int); +void popfile(void); +struct parsefile *getcurrentfile(void); +void popfilesupto(struct parsefile *); +void popallfiles(void); +void closescript(void); + +#define pgetc_macro() (--parsenleft >= 0? *parsenextc++ : preadbuffer()) diff --git a/bin/1sh/jobs.c b/bin/1sh/jobs.c new file mode 100644 index 00000000..492251e0 --- /dev/null +++ b/bin/1sh/jobs.c @@ -0,0 +1,1569 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/jobs.c 345487 2019-03-24 22:10:26Z jilles $ */ + +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <signal.h> +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> + +#include "shell.h" +#if JOBS +#include <termios.h> +#undef CEOF /* syntax.h redefines this */ +#endif +#include "redir.h" +#include "exec.h" +#include "show.h" +#include "main.h" +#include "parser.h" +#include "nodes.h" +#include "jobs.h" +#include "options.h" +#include "trap.h" +#include "syntax.h" +#include "input.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "var.h" +#include "builtins.h" + + +/* + * A job structure contains information about a job. A job is either a + * single process or a set of processes contained in a pipeline. In the + * latter case, pidlist will be non-NULL, and will point to a -1 terminated + * array of pids. + */ + +struct procstat { + pid_t pid; /* process id */ + int status; /* status flags (defined above) */ + char *cmd; /* text of command being run */ +}; + + +/* states */ +#define JOBSTOPPED 1 /* all procs are stopped */ +#define JOBDONE 2 /* all procs are completed */ + + +struct job { + struct procstat ps0; /* status of process */ + struct procstat *ps; /* status or processes when more than one */ + short nprocs; /* number of processes */ + pid_t pgrp; /* process group of this job */ + char state; /* true if job is finished */ + char used; /* true if this entry is in used */ + char changed; /* true if status has changed */ + char foreground; /* true if running in the foreground */ + char remembered; /* true if $! referenced */ + char pipefail; /* pass any non-zero status */ +#if JOBS + char jobctl; /* job running under job control */ + struct job *next; /* job used after this one */ +#endif +}; + + +static struct job *jobtab; /* array of jobs */ +static int njobs; /* size of array */ +static pid_t backgndpid = -1; /* pid of last background process */ +static struct job *bgjob = NULL; /* last background process */ +#if JOBS +static struct job *jobmru; /* most recently used job list */ +static pid_t initialpgrp; /* pgrp of shell on invocation */ +#endif +static int ttyfd = -1; + +/* mode flags for dowait */ +#define DOWAIT_BLOCK 0x1 /* wait until a child exits */ +#define DOWAIT_SIG 0x2 /* if DOWAIT_BLOCK, abort on signal */ +#define DOWAIT_SIG_TRAP 0x4 /* if DOWAIT_SIG, abort on trapped signal only */ + +#if JOBS +static void restartjob(struct job *); +#endif +static void freejob(struct job *); +static int waitcmdloop(struct job *); +static struct job *getjob_nonotfound(const char *); +static struct job *getjob(const char *); +pid_t killjob(const char *, int); +static pid_t dowait(int, struct job *); +static void checkzombies(void); +static void cmdtxt(union node *); +static void cmdputs(const char *); +#if JOBS +static void setcurjob(struct job *); +static void deljob(struct job *); +static struct job *getcurjob(struct job *); +#endif +static int getjobstatus(const struct job *); +static void printjobcmd(struct job *); +static void showjob(struct job *, int); + + +/* + * Turn job control on and off. + */ + +static int jobctl; + +#if JOBS +static void +jobctl_notty(void) +{ + if (ttyfd >= 0) { + close(ttyfd); + ttyfd = -1; + } + if (!iflag) { + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + jobctl = 1; + return; + } + out2fmt_flush("sh: can't access tty; job control turned off\n"); + mflag = 0; +} + +void +setjobctl(int on) +{ + int i; + + if (on == jobctl || rootshell == 0) + return; + if (on) { + if (ttyfd != -1) + close(ttyfd); + if ((ttyfd = open(_PATH_TTY, O_RDWR | O_CLOEXEC)) < 0) { + i = 0; + while (i <= 2 && !isatty(i)) + i++; + if (i > 2 || + (ttyfd = fcntl(i, F_DUPFD_CLOEXEC, 10)) < 0) { + jobctl_notty(); + return; + } + } + if (ttyfd < 10) { + /* + * Keep our TTY file descriptor out of the way of + * the user's redirections. + */ + if ((i = fcntl(ttyfd, F_DUPFD_CLOEXEC, 10)) < 0) { + jobctl_notty(); + return; + } + close(ttyfd); + ttyfd = i; + } + do { /* while we are in the background */ + initialpgrp = tcgetpgrp(ttyfd); + if (initialpgrp < 0) { + jobctl_notty(); + return; + } + if (initialpgrp != getpgrp()) { + if (!iflag) { + initialpgrp = -1; + jobctl_notty(); + return; + } + kill(0, SIGTTIN); + continue; + } + } while (0); + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + setpgid(0, rootpid); + tcsetpgrp(ttyfd, rootpid); + } else { /* turning job control off */ + setpgid(0, initialpgrp); + if (ttyfd >= 0) { + tcsetpgrp(ttyfd, initialpgrp); + close(ttyfd); + ttyfd = -1; + } + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + } + jobctl = on; +} +#endif + + +#if JOBS +int +fgcmd(int argc __unused, char **argv __unused) +{ + struct job *jp; + pid_t pgrp; + int status; + + nextopt(""); + jp = getjob(*argptr); + if (jp->jobctl == 0) + error("job not created under job control"); + printjobcmd(jp); + flushout(&output); + pgrp = jp->ps[0].pid; + if (ttyfd >= 0) + tcsetpgrp(ttyfd, pgrp); + restartjob(jp); + jp->foreground = 1; + INTOFF; + status = waitforjob(jp, (int *)NULL); + INTON; + return status; +} + + +int +bgcmd(int argc __unused, char **argv __unused) +{ + struct job *jp; + + nextopt(""); + do { + jp = getjob(*argptr); + if (jp->jobctl == 0) + error("job not created under job control"); + if (jp->state == JOBDONE) + continue; + restartjob(jp); + jp->foreground = 0; + out1fmt("[%td] ", jp - jobtab + 1); + printjobcmd(jp); + } while (*argptr != NULL && *++argptr != NULL); + return 0; +} + + +static void +restartjob(struct job *jp) +{ + struct procstat *ps; + int i; + + if (jp->state == JOBDONE) + return; + setcurjob(jp); + INTOFF; + kill(-jp->ps[0].pid, SIGCONT); + for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { + if (WIFSTOPPED(ps->status)) { + ps->status = -1; + jp->state = 0; + } + } + INTON; +} +#endif + + +int +jobscmd(int argc __unused, char *argv[] __unused) +{ + char *id; + int ch, mode; + + mode = SHOWJOBS_DEFAULT; + while ((ch = nextopt("lps")) != '\0') { + switch (ch) { + case 'l': + mode = SHOWJOBS_VERBOSE; + break; + case 'p': + mode = SHOWJOBS_PGIDS; + break; + case 's': + mode = SHOWJOBS_PIDS; + break; + } + } + + if (*argptr == NULL) + showjobs(0, mode); + else + while ((id = *argptr++) != NULL) + showjob(getjob(id), mode); + + return (0); +} + +static int getjobstatus(const struct job *jp) +{ + int i, status; + + if (!jp->pipefail) + return (jp->ps[jp->nprocs - 1].status); + for (i = jp->nprocs - 1; i >= 0; i--) { + status = jp->ps[i].status; + if (status != 0) + return (status); + } + return (0); +} + +static void +printjobcmd(struct job *jp) +{ + struct procstat *ps; + int i; + + for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { + out1str(ps->cmd); + if (i > 0) + out1str(" | "); + } + out1c('\n'); +} + +static void +showjob(struct job *jp, int mode) +{ + char s[64]; + char statebuf[16]; + const char *statestr, *coredump; + struct procstat *ps; + struct job *j; + int col, curr, i, jobno, prev, procno, status; + char c; + + procno = (mode == SHOWJOBS_PGIDS) ? 1 : jp->nprocs; + jobno = jp - jobtab + 1; + curr = prev = 0; +#if JOBS + if ((j = getcurjob(NULL)) != NULL) { + curr = j - jobtab + 1; + if ((j = getcurjob(j)) != NULL) + prev = j - jobtab + 1; + } +#endif + coredump = ""; + status = getjobstatus(jp); + if (jp->state == 0) { + statestr = "Running"; +#if JOBS + } else if (jp->state == JOBSTOPPED) { + ps = jp->ps + jp->nprocs - 1; + while (!WIFSTOPPED(ps->status) && ps > jp->ps) + ps--; + if (WIFSTOPPED(ps->status)) + i = WSTOPSIG(ps->status); + else + i = -1; + statestr = strsignal(i); + if (statestr == NULL) + statestr = "Suspended"; +#endif + } else if (WIFEXITED(status)) { + if (WEXITSTATUS(status) == 0) + statestr = "Done"; + else { + fmtstr(statebuf, sizeof(statebuf), "Done(%d)", + WEXITSTATUS(status)); + statestr = statebuf; + } + } else { + i = WTERMSIG(status); + statestr = strsignal(i); + if (statestr == NULL) + statestr = "Unknown signal"; + if (WCOREDUMP(status)) + coredump = " (core dumped)"; + } + + for (ps = jp->ps ; procno > 0 ; ps++, procno--) { /* for each process */ + if (mode == SHOWJOBS_PIDS || mode == SHOWJOBS_PGIDS) { + out1fmt("%d\n", (int)ps->pid); + continue; + } + if (mode != SHOWJOBS_VERBOSE && ps != jp->ps) + continue; + if (jobno == curr && ps == jp->ps) + c = '+'; + else if (jobno == prev && ps == jp->ps) + c = '-'; + else + c = ' '; + if (ps == jp->ps) + fmtstr(s, 64, "[%d] %c ", jobno, c); + else + fmtstr(s, 64, " %c ", c); + out1str(s); + col = strlen(s); + if (mode == SHOWJOBS_VERBOSE) { + fmtstr(s, 64, "%d ", (int)ps->pid); + out1str(s); + col += strlen(s); + } + if (ps == jp->ps) { + out1str(statestr); + out1str(coredump); + col += strlen(statestr) + strlen(coredump); + } + do { + out1c(' '); + col++; + } while (col < 30); + if (mode == SHOWJOBS_VERBOSE) { + out1str(ps->cmd); + out1c('\n'); + } else + printjobcmd(jp); + } +} + +/* + * Print a list of jobs. If "change" is nonzero, only print jobs whose + * statuses have changed since the last call to showjobs. + * + * If the shell is interrupted in the process of creating a job, the + * result may be a job structure containing zero processes. Such structures + * will be freed here. + */ + +void +showjobs(int change, int mode) +{ + int jobno; + struct job *jp; + + TRACE(("showjobs(%d) called\n", change)); + checkzombies(); + for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) { + if (! jp->used) + continue; + if (jp->nprocs == 0) { + freejob(jp); + continue; + } + if (change && ! jp->changed) + continue; + showjob(jp, mode); + if (mode == SHOWJOBS_DEFAULT || mode == SHOWJOBS_VERBOSE) { + jp->changed = 0; + /* Hack: discard jobs for which $! has not been + * referenced in interactive mode when they terminate. + */ + if (jp->state == JOBDONE && !jp->remembered && + (iflag || jp != bgjob)) { + freejob(jp); + } + } + } +} + + +/* + * Mark a job structure as unused. + */ + +static void +freejob(struct job *jp) +{ + struct procstat *ps; + int i; + + INTOFF; + if (bgjob == jp) + bgjob = NULL; + for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) { + if (ps->cmd != nullstr) + ckfree(ps->cmd); + } + if (jp->ps != &jp->ps0) + ckfree(jp->ps); + jp->used = 0; +#if JOBS + deljob(jp); +#endif + INTON; +} + + + +int +waitcmd(int argc __unused, char **argv __unused) +{ + struct job *job; + int retval; + + nextopt(""); + if (*argptr == NULL) + return (waitcmdloop(NULL)); + + do { + job = getjob_nonotfound(*argptr); + if (job == NULL) + retval = 127; + else + retval = waitcmdloop(job); + argptr++; + } while (*argptr != NULL); + + return (retval); +} + +static int +waitcmdloop(struct job *job) +{ + int status, retval, sig; + struct job *jp; + + /* + * Loop until a process is terminated or stopped, or a SIGINT is + * received. + */ + + do { + if (job != NULL) { + if (job->state == JOBDONE) { + status = getjobstatus(job); + if (WIFEXITED(status)) + retval = WEXITSTATUS(status); + else + retval = WTERMSIG(status) + 128; + if (! iflag || ! job->changed) + freejob(job); + else { + job->remembered = 0; + if (job == bgjob) + bgjob = NULL; + } + return retval; + } + } else { + for (jp = jobtab ; jp < jobtab + njobs; jp++) + if (jp->used && jp->state == JOBDONE) { + if (! iflag || ! jp->changed) + freejob(jp); + else { + jp->remembered = 0; + if (jp == bgjob) + bgjob = NULL; + } + } + for (jp = jobtab ; ; jp++) { + if (jp >= jobtab + njobs) { /* no running procs */ + return 0; + } + if (jp->used && jp->state == 0) + break; + } + } + } while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, (struct job *)NULL) != -1); + + sig = pendingsig_waitcmd; + pendingsig_waitcmd = 0; + return sig + 128; +} + + + +int +jobidcmd(int argc __unused, char **argv __unused) +{ + struct job *jp; + int i; + + nextopt(""); + jp = getjob(*argptr); + for (i = 0 ; i < jp->nprocs ; ) { + out1fmt("%d", (int)jp->ps[i].pid); + out1c(++i < jp->nprocs? ' ' : '\n'); + } + return 0; +} + + + +/* + * Convert a job name to a job structure. + */ + +static struct job * +getjob_nonotfound(const char *name) +{ + int jobno; + struct job *found, *jp; + size_t namelen; + pid_t pid; + int i; + + if (name == NULL) { +#if JOBS + name = "%+"; +#else + error("No current job"); +#endif + } + if (name[0] == '%') { + if (is_digit(name[1])) { + jobno = number(name + 1); + if (jobno > 0 && jobno <= njobs + && jobtab[jobno - 1].used != 0) + return &jobtab[jobno - 1]; +#if JOBS + } else if ((name[1] == '%' || name[1] == '+') && + name[2] == '\0') { + if ((jp = getcurjob(NULL)) == NULL) + error("No current job"); + return (jp); + } else if (name[1] == '-' && name[2] == '\0') { + if ((jp = getcurjob(NULL)) == NULL || + (jp = getcurjob(jp)) == NULL) + error("No previous job"); + return (jp); +#endif + } else if (name[1] == '?') { + found = NULL; + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && strstr(jp->ps[0].cmd, name + 2) != NULL) { + if (found) + error("%s: ambiguous", name); + found = jp; + } + } + if (found != NULL) + return (found); + } else { + namelen = strlen(name); + found = NULL; + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && strncmp(jp->ps[0].cmd, name + 1, + namelen - 1) == 0) { + if (found) + error("%s: ambiguous", name); + found = jp; + } + } + if (found) + return found; + } + } else if (is_number(name)) { + pid = (pid_t)number(name); + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && jp->ps[jp->nprocs - 1].pid == pid) + return jp; + } + } + return NULL; +} + + +static struct job * +getjob(const char *name) +{ + struct job *jp; + + jp = getjob_nonotfound(name); + if (jp == NULL) + error("No such job: %s", name); + return (jp); +} + + +int +killjob(const char *name, int sig) +{ + struct job *jp; + int i, ret; + + jp = getjob(name); + if (jp->state == JOBDONE) + return 0; + if (jp->jobctl) + return kill(-jp->ps[0].pid, sig); + ret = -1; + errno = ESRCH; + for (i = 0; i < jp->nprocs; i++) + if (jp->ps[i].status == -1 || WIFSTOPPED(jp->ps[i].status)) { + if (kill(jp->ps[i].pid, sig) == 0) + ret = 0; + } else + ret = 0; + return ret; +} + +/* + * Return a new job structure, + */ + +struct job * +makejob(union node *node __unused, int nprocs) +{ + int i; + struct job *jp; + + for (i = njobs, jp = jobtab ; ; jp++) { + if (--i < 0) { + INTOFF; + if (njobs == 0) { + jobtab = ckmalloc(4 * sizeof jobtab[0]); +#if JOBS + jobmru = NULL; +#endif + } else { + jp = ckmalloc((njobs + 4) * sizeof jobtab[0]); + memcpy(jp, jobtab, njobs * sizeof jp[0]); +#if JOBS + /* Relocate `next' pointers and list head */ + if (jobmru != NULL) + jobmru = &jp[jobmru - jobtab]; + for (i = 0; i < njobs; i++) + if (jp[i].next != NULL) + jp[i].next = &jp[jp[i].next - + jobtab]; +#endif + if (bgjob != NULL) + bgjob = &jp[bgjob - jobtab]; + /* Relocate `ps' pointers */ + for (i = 0; i < njobs; i++) + if (jp[i].ps == &jobtab[i].ps0) + jp[i].ps = &jp[i].ps0; + ckfree(jobtab); + jobtab = jp; + } + jp = jobtab + njobs; + for (i = 4 ; --i >= 0 ; jobtab[njobs++].used = 0) + ; + INTON; + break; + } + if (jp->used == 0) + break; + } + INTOFF; + jp->state = 0; + jp->used = 1; + jp->changed = 0; + jp->nprocs = 0; + jp->foreground = 0; + jp->remembered = 0; + jp->pipefail = pipefailflag; +#if JOBS + jp->jobctl = jobctl; + jp->next = NULL; +#endif + if (nprocs > 1) { + jp->ps = ckmalloc(nprocs * sizeof (struct procstat)); + } else { + jp->ps = &jp->ps0; + } + INTON; + TRACE(("makejob(%p, %d) returns %%%td\n", (void *)node, nprocs, + jp - jobtab + 1)); + return jp; +} + +#if JOBS +static void +setcurjob(struct job *cj) +{ + struct job *jp, *prev; + + for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { + if (jp == cj) { + if (prev != NULL) + prev->next = jp->next; + else + jobmru = jp->next; + jp->next = jobmru; + jobmru = cj; + return; + } + } + cj->next = jobmru; + jobmru = cj; +} + +static void +deljob(struct job *j) +{ + struct job *jp, *prev; + + for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { + if (jp == j) { + if (prev != NULL) + prev->next = jp->next; + else + jobmru = jp->next; + return; + } + } +} + +/* + * Return the most recently used job that isn't `nj', and preferably one + * that is stopped. + */ +static struct job * +getcurjob(struct job *nj) +{ + struct job *jp; + + /* Try to find a stopped one.. */ + for (jp = jobmru; jp != NULL; jp = jp->next) + if (jp->used && jp != nj && jp->state == JOBSTOPPED) + return (jp); + /* Otherwise the most recently used job that isn't `nj' */ + for (jp = jobmru; jp != NULL; jp = jp->next) + if (jp->used && jp != nj) + return (jp); + + return (NULL); +} + +#endif + +/* + * Fork of a subshell. If we are doing job control, give the subshell its + * own process group. Jp is a job structure that the job is to be added to. + * N is the command that will be evaluated by the child. Both jp and n may + * be NULL. The mode parameter can be one of the following: + * FORK_FG - Fork off a foreground process. + * FORK_BG - Fork off a background process. + * FORK_NOJOB - Like FORK_FG, but don't give the process its own + * process group even if job control is on. + * + * When job control is turned off, background processes have their standard + * input redirected to /dev/null (except for the second and later processes + * in a pipeline). + */ + +pid_t +forkshell(struct job *jp, union node *n, int mode) +{ + pid_t pid; + pid_t pgrp; + + TRACE(("forkshell(%%%td, %p, %d) called\n", jp - jobtab, (void *)n, + mode)); + INTOFF; + if (mode == FORK_BG && (jp == NULL || jp->nprocs == 0)) + checkzombies(); + flushall(); + pid = fork(); + if (pid == -1) { + TRACE(("Fork failed, errno=%d\n", errno)); + INTON; + error("Cannot fork: %s", strerror(errno)); + } + if (pid == 0) { + struct job *p; + int wasroot; + int i; + + TRACE(("Child shell %d\n", (int)getpid())); + wasroot = rootshell; + rootshell = 0; + handler = &main_handler; + closescript(); + INTON; + forcelocal = 0; + clear_traps(); +#if JOBS + jobctl = 0; /* do job control only in root shell */ + if (wasroot && mode != FORK_NOJOB && mflag) { + if (jp == NULL || jp->nprocs == 0) + pgrp = getpid(); + else + pgrp = jp->ps[0].pid; + if (setpgid(0, pgrp) == 0 && mode == FORK_FG && + ttyfd >= 0) { + /*** this causes superfluous TIOCSPGRPS ***/ + if (tcsetpgrp(ttyfd, pgrp) < 0) + error("tcsetpgrp failed, errno=%d", errno); + } + setsignal(SIGTSTP); + setsignal(SIGTTOU); + } else if (mode == FORK_BG) { + ignoresig(SIGINT); + ignoresig(SIGQUIT); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(_PATH_DEVNULL, O_RDONLY) != 0) + error("cannot open %s: %s", + _PATH_DEVNULL, strerror(errno)); + } + } +#else + if (mode == FORK_BG) { + ignoresig(SIGINT); + ignoresig(SIGQUIT); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(_PATH_DEVNULL, O_RDONLY) != 0) + error("cannot open %s: %s", + _PATH_DEVNULL, strerror(errno)); + } + } +#endif + INTOFF; + for (i = njobs, p = jobtab ; --i >= 0 ; p++) + if (p->used) + freejob(p); + INTON; + if (wasroot && iflag) { + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); + } + return pid; + } + if (rootshell && mode != FORK_NOJOB && mflag) { + if (jp == NULL || jp->nprocs == 0) + pgrp = pid; + else + pgrp = jp->ps[0].pid; + setpgid(pid, pgrp); + } + if (mode == FORK_BG) { + if (bgjob != NULL && bgjob->state == JOBDONE && + !bgjob->remembered && !iflag) + freejob(bgjob); + backgndpid = pid; /* set $! */ + bgjob = jp; + } + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd = nullstr; + if (iflag && rootshell && n) + ps->cmd = commandtext(n); + jp->foreground = mode == FORK_FG; +#if JOBS + setcurjob(jp); +#endif + } + INTON; + TRACE(("In parent shell: child = %d\n", (int)pid)); + return pid; +} + + +pid_t +vforkexecshell(struct job *jp, char **argv, char **envp, const char *path, int idx, int pip[2]) +{ + pid_t pid; + struct jmploc jmploc; + struct jmploc *savehandler; + + TRACE(("vforkexecshell(%%%td, %s, %p) called\n", jp - jobtab, argv[0], + (void *)pip)); + INTOFF; + flushall(); + savehandler = handler; + pid = vfork(); + if (pid == -1) { + TRACE(("Vfork failed, errno=%d\n", errno)); + INTON; + error("Cannot fork: %s", strerror(errno)); + } + if (pid == 0) { + TRACE(("Child shell %d\n", (int)getpid())); + if (setjmp(jmploc.loc)) + _exit(exception == EXEXEC ? exerrno : 2); + if (pip != NULL) { + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + } + handler = &jmploc; + shellexec(argv, envp, path, idx); + } + handler = savehandler; + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd = nullstr; + jp->foreground = 1; +#if JOBS + setcurjob(jp); +#endif + } + INTON; + TRACE(("In parent shell: child = %d\n", (int)pid)); + return pid; +} + + +/* + * Wait for job to finish. + * + * Under job control we have the problem that while a child process is + * running interrupts generated by the user are sent to the child but not + * to the shell. This means that an infinite loop started by an inter- + * active user may be hard to kill. With job control turned off, an + * interactive user may place an interactive program inside a loop. If + * the interactive program catches interrupts, the user doesn't want + * these interrupts to also abort the loop. The approach we take here + * is to have the shell ignore interrupt signals while waiting for a + * foreground process to terminate, and then send itself an interrupt + * signal if the child process was terminated by an interrupt signal. + * Unfortunately, some programs want to do a bit of cleanup and then + * exit on interrupt; unless these processes terminate themselves by + * sending a signal to themselves (instead of calling exit) they will + * confuse this approach. + */ + +int +waitforjob(struct job *jp, int *signaled) +{ +#if JOBS + int propagate_int = jp->jobctl && jp->foreground; +#endif + int status; + int st; + + INTOFF; + TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1)); + while (jp->state == 0) + if (dowait(DOWAIT_BLOCK | (Tflag ? DOWAIT_SIG | + DOWAIT_SIG_TRAP : 0), jp) == -1) + dotrap(); +#if JOBS + if (jp->jobctl) { + if (ttyfd >= 0 && tcsetpgrp(ttyfd, rootpid) < 0) + error("tcsetpgrp failed, errno=%d\n", errno); + } + if (jp->state == JOBSTOPPED) + setcurjob(jp); +#endif + status = getjobstatus(jp); + if (signaled != NULL) + *signaled = WIFSIGNALED(status); + /* convert to 8 bits */ + if (WIFEXITED(status)) + st = WEXITSTATUS(status); +#if JOBS + else if (WIFSTOPPED(status)) + st = WSTOPSIG(status) + 128; +#endif + else + st = WTERMSIG(status) + 128; + if (! JOBS || jp->state == JOBDONE) + freejob(jp); + if (int_pending()) { + if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGINT) + CLEAR_PENDING_INT; + } +#if JOBS + else if (rootshell && propagate_int && + WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) + kill(getpid(), SIGINT); +#endif + INTON; + return st; +} + + +static void +dummy_handler(int sig __unused) +{ +} + +/* + * Wait for a process to terminate. + */ + +static pid_t +dowait(int mode, struct job *job) +{ + struct sigaction sa, osa; + sigset_t mask, omask; + pid_t pid; + int status; + struct procstat *sp; + struct job *jp; + struct job *thisjob; + const char *sigstr; + int done; + int stopped; + int sig; + int coredump; + int wflags; + int restore_sigchld; + + TRACE(("dowait(%d, %p) called\n", mode, job)); + restore_sigchld = 0; + if ((mode & DOWAIT_SIG) != 0) { + sigfillset(&mask); + sigprocmask(SIG_BLOCK, &mask, &omask); + INTOFF; + if (!issigchldtrapped()) { + restore_sigchld = 1; + sa.sa_handler = dummy_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGCHLD, &sa, &osa); + } + } + do { +#if JOBS + if (iflag) + wflags = WUNTRACED | WCONTINUED; + else +#endif + wflags = 0; + if ((mode & (DOWAIT_BLOCK | DOWAIT_SIG)) != DOWAIT_BLOCK) + wflags |= WNOHANG; + pid = wait3(&status, wflags, (struct rusage *)NULL); + TRACE(("wait returns %d, status=%d\n", (int)pid, status)); + if (pid == 0 && (mode & DOWAIT_SIG) != 0) { + pid = -1; + if (((mode & DOWAIT_SIG_TRAP) != 0 ? + pendingsig : pendingsig_waitcmd) != 0) { + errno = EINTR; + break; + } + sigsuspend(&omask); + if (int_pending()) + break; + } + } while (pid == -1 && errno == EINTR); + if (pid == -1 && errno == ECHILD && job != NULL) + job->state = JOBDONE; + if ((mode & DOWAIT_SIG) != 0) { + if (restore_sigchld) + sigaction(SIGCHLD, &osa, NULL); + sigprocmask(SIG_SETMASK, &omask, NULL); + INTON; + } + if (pid <= 0) + return pid; + INTOFF; + thisjob = NULL; + for (jp = jobtab ; jp < jobtab + njobs ; jp++) { + if (jp->used && jp->nprocs > 0) { + done = 1; + stopped = 1; + for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) { + if (sp->pid == -1) + continue; + if (sp->pid == pid && (sp->status == -1 || + WIFSTOPPED(sp->status))) { + TRACE(("Changing status of proc %d from 0x%x to 0x%x\n", + (int)pid, sp->status, + status)); + if (WIFCONTINUED(status)) { + sp->status = -1; + jp->state = 0; + } else + sp->status = status; + thisjob = jp; + } + if (sp->status == -1) + stopped = 0; + else if (WIFSTOPPED(sp->status)) + done = 0; + } + if (stopped) { /* stopped or done */ + int state = done? JOBDONE : JOBSTOPPED; + if (jp->state != state) { + TRACE(("Job %td: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state)); + jp->state = state; + if (jp != job) { + if (done && !jp->remembered && + !iflag && jp != bgjob) + freejob(jp); +#if JOBS + else if (done) + deljob(jp); +#endif + } + } + } + } + } + INTON; + if (!thisjob || thisjob->state == 0) + ; + else if ((!rootshell || !iflag || thisjob == job) && + thisjob->foreground && thisjob->state != JOBSTOPPED) { + sig = 0; + coredump = 0; + for (sp = thisjob->ps; sp < thisjob->ps + thisjob->nprocs; sp++) + if (WIFSIGNALED(sp->status)) { + sig = WTERMSIG(sp->status); + coredump = WCOREDUMP(sp->status); + } + if (sig > 0 && sig != SIGINT && sig != SIGPIPE) { + sigstr = strsignal(sig); + if (sigstr != NULL) + out2str(sigstr); + else + out2str("Unknown signal"); + if (coredump) + out2str(" (core dumped)"); + out2c('\n'); + flushout(out2); + } + } else { + TRACE(("Not printing status, rootshell=%d, job=%p\n", rootshell, job)); + thisjob->changed = 1; + } + return pid; +} + + + +/* + * return 1 if there are stopped jobs, otherwise 0 + */ +int job_warning = 0; +int +stoppedjobs(void) +{ + int jobno; + struct job *jp; + + if (job_warning) + return (0); + for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) { + if (jp->used == 0) + continue; + if (jp->state == JOBSTOPPED) { + out2fmt_flush("You have stopped jobs.\n"); + job_warning = 2; + return (1); + } + } + + return (0); +} + + +static void +checkzombies(void) +{ + while (njobs > 0 && dowait(0, NULL) > 0) + ; +} + + +int +backgndpidset(void) +{ + return backgndpid != -1; +} + + +pid_t +backgndpidval(void) +{ + if (bgjob != NULL && !forcelocal) + bgjob->remembered = 1; + return backgndpid; +} + +/* + * Return a string identifying a command (to be printed by the + * jobs command. + */ + +static char *cmdnextc; +static int cmdnleft; +#define MAXCMDTEXT 200 + +char * +commandtext(union node *n) +{ + char *name; + + cmdnextc = name = ckmalloc(MAXCMDTEXT); + cmdnleft = MAXCMDTEXT - 4; + cmdtxt(n); + *cmdnextc = '\0'; + return name; +} + + +static void +cmdtxtdogroup(union node *n) +{ + cmdputs("; do "); + cmdtxt(n); + cmdputs("; done"); +} + + +static void +cmdtxtredir(union node *n, const char *op, int deffd) +{ + char s[2]; + + if (n->nfile.fd != deffd) { + s[0] = n->nfile.fd + '0'; + s[1] = '\0'; + cmdputs(s); + } + cmdputs(op); + if (n->type == NTOFD || n->type == NFROMFD) { + if (n->ndup.dupfd >= 0) + s[0] = n->ndup.dupfd + '0'; + else + s[0] = '-'; + s[1] = '\0'; + cmdputs(s); + } else { + cmdtxt(n->nfile.fname); + } +} + + +static void +cmdtxt(union node *n) +{ + union node *np; + struct nodelist *lp; + + if (n == NULL) + return; + switch (n->type) { + case NSEMI: + cmdtxt(n->nbinary.ch1); + cmdputs("; "); + cmdtxt(n->nbinary.ch2); + break; + case NAND: + cmdtxt(n->nbinary.ch1); + cmdputs(" && "); + cmdtxt(n->nbinary.ch2); + break; + case NOR: + cmdtxt(n->nbinary.ch1); + cmdputs(" || "); + cmdtxt(n->nbinary.ch2); + break; + case NPIPE: + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + cmdtxt(lp->n); + if (lp->next) + cmdputs(" | "); + } + break; + case NSUBSHELL: + cmdputs("("); + cmdtxt(n->nredir.n); + cmdputs(")"); + break; + case NREDIR: + case NBACKGND: + cmdtxt(n->nredir.n); + break; + case NIF: + cmdputs("if "); + cmdtxt(n->nif.test); + cmdputs("; then "); + cmdtxt(n->nif.ifpart); + cmdputs("..."); + break; + case NWHILE: + cmdputs("while "); + cmdtxt(n->nbinary.ch1); + cmdtxtdogroup(n->nbinary.ch2); + break; + case NUNTIL: + cmdputs("until "); + cmdtxt(n->nbinary.ch1); + cmdtxtdogroup(n->nbinary.ch2); + break; + case NFOR: + cmdputs("for "); + cmdputs(n->nfor.var); + cmdputs(" in ..."); + break; + case NCASE: + cmdputs("case "); + cmdputs(n->ncase.expr->narg.text); + cmdputs(" in ..."); + break; + case NDEFUN: + cmdputs(n->narg.text); + cmdputs("() ..."); + break; + case NNOT: + cmdputs("! "); + cmdtxt(n->nnot.com); + break; + case NCMD: + for (np = n->ncmd.args ; np ; np = np->narg.next) { + cmdtxt(np); + if (np->narg.next) + cmdputs(" "); + } + for (np = n->ncmd.redirect ; np ; np = np->nfile.next) { + cmdputs(" "); + cmdtxt(np); + } + break; + case NARG: + cmdputs(n->narg.text); + break; + case NTO: + cmdtxtredir(n, ">", 1); + break; + case NAPPEND: + cmdtxtredir(n, ">>", 1); + break; + case NTOFD: + cmdtxtredir(n, ">&", 1); + break; + case NCLOBBER: + cmdtxtredir(n, ">|", 1); + break; + case NFROM: + cmdtxtredir(n, "<", 0); + break; + case NFROMTO: + cmdtxtredir(n, "<>", 0); + break; + case NFROMFD: + cmdtxtredir(n, "<&", 0); + break; + case NHERE: + case NXHERE: + cmdputs("<<..."); + break; + default: + cmdputs("???"); + break; + } +} + + + +static void +cmdputs(const char *s) +{ + const char *p; + char *q; + char c; + int subtype = 0; + + if (cmdnleft <= 0) + return; + p = s; + q = cmdnextc; + while ((c = *p++) != '\0') { + if (c == CTLESC) + *q++ = *p++; + else if (c == CTLVAR) { + *q++ = '$'; + if (--cmdnleft > 0) + *q++ = '{'; + subtype = *p++; + if ((subtype & VSTYPE) == VSLENGTH && --cmdnleft > 0) + *q++ = '#'; + } else if (c == '=' && subtype != 0) { + *q = "}-+?=##%%\0X"[(subtype & VSTYPE) - VSNORMAL]; + if (*q) + q++; + else + cmdnleft++; + if (((subtype & VSTYPE) == VSTRIMLEFTMAX || + (subtype & VSTYPE) == VSTRIMRIGHTMAX) && + --cmdnleft > 0) + *q = q[-1], q++; + subtype = 0; + } else if (c == CTLENDVAR) { + *q++ = '}'; + } else if (c == CTLBACKQ || c == CTLBACKQ+CTLQUOTE) { + cmdnleft -= 5; + if (cmdnleft > 0) { + *q++ = '$'; + *q++ = '('; + *q++ = '.'; + *q++ = '.'; + *q++ = '.'; + *q++ = ')'; + } + } else if (c == CTLARI) { + cmdnleft -= 2; + if (cmdnleft > 0) { + *q++ = '$'; + *q++ = '('; + *q++ = '('; + } + p++; + } else if (c == CTLENDARI) { + if (--cmdnleft > 0) { + *q++ = ')'; + *q++ = ')'; + } + } else if (c == CTLQUOTEMARK || c == CTLQUOTEEND) + cmdnleft++; /* ignore */ + else + *q++ = c; + if (--cmdnleft <= 0) { + *q++ = '.'; + *q++ = '.'; + *q++ = '.'; + break; + } + } + cmdnextc = q; +} diff --git a/bin/1sh/jobs.h b/bin/1sh/jobs.h new file mode 100644 index 00000000..98c2eb5c --- /dev/null +++ b/bin/1sh/jobs.h @@ -0,0 +1,67 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)jobs.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/jobs.h 327475 2018-01-01 22:31:52Z jilles $ + */ + +/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ +#define FORK_FG 0 +#define FORK_BG 1 +#define FORK_NOJOB 2 + +#include <signal.h> /* for sig_atomic_t */ + +struct job; + +enum { + SHOWJOBS_DEFAULT, /* job number, status, command */ + SHOWJOBS_VERBOSE, /* job number, PID, status, command */ + SHOWJOBS_PIDS, /* PID only */ + SHOWJOBS_PGIDS /* PID of the group leader only */ +}; + +extern int job_warning; /* user was warned about stopped jobs */ + +void setjobctl(int); +void showjobs(int, int); +struct job *makejob(union node *, int); +pid_t forkshell(struct job *, union node *, int); +pid_t vforkexecshell(struct job *, char **, char **, const char *, int, int []); +int waitforjob(struct job *, int *); +int stoppedjobs(void); +int backgndpidset(void); +pid_t backgndpidval(void); +char *commandtext(union node *); + +#if ! JOBS +#define setjobctl(on) /* do nothing */ +#endif diff --git a/bin/1sh/kill.c b/bin/1sh/kill.c new file mode 100644 index 00000000..6003fa9d --- /dev/null +++ b/bin/1sh/kill.c @@ -0,0 +1,215 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/* + * Important: This file is used both as a standalone program /bin/kill and + * as a builtin for /bin/sh (#define SHELL). + */ + +#if 0 +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1988, 1993, 1994\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static char sccsid[] = "@(#)kill.c 8.4 (Berkeley) 4/28/95"; +#endif /* not lint */ +#endif +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/kill/kill.c 326025 2017-11-20 19:49:47Z pfg $ */ + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef SHELL +#define main killcmd +#include "bltin.h" +#endif + +static void nosig(const char *); +static void printsignals(FILE *); +static int signame_to_signum(const char *); +static void usage(void); + +int +main(int argc, char *argv[]) +{ + long pidl; + pid_t pid; + int errors, numsig, ret; + char *ep; + + if (argc < 2) + usage(); + + numsig = SIGTERM; + + argc--, argv++; + if (!strcmp(*argv, "-l")) { + argc--, argv++; + if (argc > 1) + usage(); + if (argc == 1) { + if (!isdigit(**argv)) + usage(); + numsig = strtol(*argv, &ep, 10); + if (!**argv || *ep) + errx(2, "illegal signal number: %s", *argv); + if (numsig >= 128) + numsig -= 128; + if (numsig <= 0 || numsig >= NSIG) + nosig(*argv); + printf("%s\n", sys_signame[numsig]); + return (0); + } + printsignals(stdout); + return (0); + } + + if (!strcmp(*argv, "-s")) { + argc--, argv++; + if (argc < 1) { + warnx("option requires an argument -- s"); + usage(); + } + if (strcmp(*argv, "0")) { + if ((numsig = signame_to_signum(*argv)) < 0) + nosig(*argv); + } else + numsig = 0; + argc--, argv++; + } else if (**argv == '-' && *(*argv + 1) != '-') { + ++*argv; + if (isalpha(**argv)) { + if ((numsig = signame_to_signum(*argv)) < 0) + nosig(*argv); + } else if (isdigit(**argv)) { + numsig = strtol(*argv, &ep, 10); + if (!**argv || *ep) + errx(2, "illegal signal number: %s", *argv); + if (numsig < 0) + nosig(*argv); + } else + nosig(*argv); + argc--, argv++; + } + + if (argc > 0 && strncmp(*argv, "--", 2) == 0) + argc--, argv++; + + if (argc == 0) + usage(); + + for (errors = 0; argc; argc--, argv++) { +#ifdef SHELL + if (**argv == '%') + ret = killjob(*argv, numsig); + else +#endif + { + pidl = strtol(*argv, &ep, 10); + /* Check for overflow of pid_t. */ + pid = (pid_t)pidl; + if (!**argv || *ep || pid != pidl) + errx(2, "illegal process id: %s", *argv); + ret = kill(pid, numsig); + } + if (ret == -1) { + warn("%s", *argv); + errors = 1; + } + } + + return (errors); +} + +static int +signame_to_signum(const char *sig) +{ + int n; + + if (strncasecmp(sig, "SIG", 3) == 0) + sig += 3; + for (n = 1; n < NSIG; n++) { + if (!strcasecmp(sys_signame[n], sig)) + return (n); + } + return (-1); +} + +static void +nosig(const char *name) +{ + + warnx("unknown signal %s; valid signals:", name); + printsignals(stderr); +#ifdef SHELL + error(NULL); +#else + exit(2); +#endif +} + +static void +printsignals(FILE *fp) +{ + int n; + + for (n = 1; n < NSIG; n++) { + (void)fprintf(fp, "%s", sys_signame[n]); + if (n == (NSIG / 2) || n == (NSIG - 1)) + (void)fprintf(fp, "\n"); + else + (void)fprintf(fp, " "); + } +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "%s\n%s\n%s\n%s\n", + "usage: kill [-s signal_name] pid ...", + " kill -l [exit_status]", + " kill -signal_name pid ...", + " kill -signal_number pid ..."); +#ifdef SHELL + error(NULL); +#else + exit(2); +#endif +} diff --git a/bin/1sh/mail.c b/bin/1sh/mail.c new file mode 100644 index 00000000..adf54a06 --- /dev/null +++ b/bin/1sh/mail.c @@ -0,0 +1,120 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mail.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/mail.c 336303 2018-07-15 09:14:30Z jilles $ */ + +/* + * Routines to check for mail. (Perhaps make part of main.c?) + */ + +#include "shell.h" +#include "mail.h" +#include "var.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> + + +#define MAXMBOXES 10 + + +static int nmboxes; /* number of mailboxes */ +static time_t mailtime[MAXMBOXES]; /* times of mailboxes */ + + + +/* + * Print appropriate message(s) if mail has arrived. If the argument is + * nozero, then the value of MAIL has changed, so we just update the + * values. + */ + +void +chkmail(int silent) +{ + int i; + char *mpath; + char *p; + char *msg; + struct stackmark smark; + struct stat statb; + + if (silent) + nmboxes = 10; + if (nmboxes == 0) + return; + setstackmark(&smark); + mpath = stsavestr(mpathset()? mpathval() : mailval()); + for (i = 0 ; i < nmboxes ; i++) { + p = mpath; + if (*p == '\0') + break; + mpath = strchr(p, ':'); + if (!mpath) mpath = strchr(p, '\0'); + if (*mpath != '\0') { + *mpath++ = '\0'; + if (p == mpath - 1) + continue; + } + msg = strchr(p, '%'); + if (msg != NULL) + *msg++ = '\0'; +#ifdef notdef /* this is what the System V shell claims to do (it lies) */ + if (stat(p, &statb) < 0) + statb.st_mtime = 0; + if (statb.st_mtime > mailtime[i] && ! silent) { + out2str(msg? msg : "you have mail"); + out2c('\n'); + } + mailtime[i] = statb.st_mtime; +#else /* this is what it should do */ + if (stat(p, &statb) < 0) + statb.st_size = 0; + if (statb.st_size > mailtime[i] && ! silent) { + out2str(msg? msg : "you have mail"); + out2c('\n'); + } + mailtime[i] = statb.st_size; +#endif + } + nmboxes = i; + popstackmark(&smark); +} diff --git a/bin/1sh/mail.h b/bin/1sh/mail.h new file mode 100644 index 00000000..bc124ba3 --- /dev/null +++ b/bin/1sh/mail.h @@ -0,0 +1,38 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)mail.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/mail.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +void chkmail(int); diff --git a/bin/1sh/main.c b/bin/1sh/main.c new file mode 100644 index 00000000..3f0bc1da --- /dev/null +++ b/bin/1sh/main.c @@ -0,0 +1,356 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if 0 +static char const copyright[] = +"@(#) Copyright (c) 1991, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 5/28/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/main.c 336320 2018-07-15 21:55:17Z jilles $ */ + +#include <stdio.h> +#include <signal.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <locale.h> +#include <errno.h> + +#include "shell.h" +#include "main.h" +#include "mail.h" +#include "options.h" +#include "output.h" +#include "parser.h" +#include "nodes.h" +#include "expand.h" +#include "eval.h" +#include "jobs.h" +#include "input.h" +#include "trap.h" +#include "var.h" +#include "show.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "exec.h" +#include "cd.h" +#include "redir.h" +#include "builtins.h" + +int rootpid; +int rootshell; +struct jmploc main_handler; +int localeisutf8, initial_localeisutf8; + +static void reset(void); +static void cmdloop(int); +static void read_profile(const char *); +static char *find_dot_file(char *); + +/* + * Main routine. We initialize things, parse the arguments, execute + * profiles if we're a login shell, and then call cmdloop to execute + * commands. The setjmp call sets up the location to jump to when an + * exception occurs. When an exception occurs the variable "state" + * is used to figure out how far we had gotten. + */ + +int +main(int argc, char *argv[]) +{ + struct stackmark smark, smark2; + volatile int state; + char *shinit; + + (void) setlocale(LC_ALL, ""); + initcharset(); + state = 0; + if (setjmp(main_handler.loc)) { + switch (exception) { + case EXEXEC: + exitstatus = exerrno; + break; + + case EXERROR: + exitstatus = 2; + break; + + default: + break; + } + + if (state == 0 || iflag == 0 || ! rootshell || + exception == EXEXIT) + exitshell(exitstatus); + reset(); + if (exception == EXINT) + out2fmt_flush("\n"); + popstackmark(&smark); + FORCEINTON; /* enable interrupts */ + if (state == 1) + goto state1; + else if (state == 2) + goto state2; + else if (state == 3) + goto state3; + else + goto state4; + } + handler = &main_handler; +#ifdef DEBUG + opentrace(); + trputs("Shell args: "); trargs(argv); +#endif + rootpid = getpid(); + rootshell = 1; + INTOFF; + initvar(); + setstackmark(&smark); + setstackmark(&smark2); + procargs(argc, argv); + pwd_init(iflag); + INTON; + if (iflag) + chkmail(1); + if (argv[0] && argv[0][0] == '-') { + state = 1; + read_profile("/etc/profile"); +state1: + state = 2; + if (privileged == 0) + read_profile("${HOME-}/.profile"); + else + read_profile("/etc/suid_profile"); + } +state2: + state = 3; + if (!privileged && iflag) { + if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') { + state = 3; + read_profile(shinit); + } + } +state3: + state = 4; + popstackmark(&smark2); + if (minusc) { + evalstring(minusc, sflag ? 0 : EV_EXIT); + } +state4: + if (sflag || minusc == NULL) { + cmdloop(1); + } + iflag = 0; + optschanged(); + exitshell(exitstatus); + /*NOTREACHED*/ + return 0; +} + +static void +reset(void) +{ + reseteval(); + resetinput(); +} + +/* + * Read and execute commands. "Top" is nonzero for the top level command + * loop; it turns on prompting if the shell is interactive. + */ + +static void +cmdloop(int top) +{ + union node *n; + struct stackmark smark; + int inter; + int numeof = 0; + + TRACE(("cmdloop(%d) called\n", top)); + setstackmark(&smark); + for (;;) { + if (pendingsig) + dotrap(); + inter = 0; + if (iflag && top) { + inter++; + showjobs(1, SHOWJOBS_DEFAULT); + chkmail(0); + flushout(&output); + } + n = parsecmd(inter); + /* showtree(n); DEBUG */ + if (n == NEOF) { + if (!top || numeof >= 50) + break; + if (!stoppedjobs()) { + if (!Iflag) + break; + out2fmt_flush("\nUse \"exit\" to leave shell.\n"); + } + numeof++; + } else if (n != NULL && nflag == 0) { + job_warning = (job_warning == 2) ? 1 : 0; + numeof = 0; + evaltree(n, 0); + } + popstackmark(&smark); + setstackmark(&smark); + if (evalskip != 0) { + if (evalskip == SKIPRETURN) + evalskip = 0; + break; + } + } + popstackmark(&smark); +} + + + +/* + * Read /etc/profile or .profile. Return on error. + */ + +static void +read_profile(const char *name) +{ + int fd; + const char *expandedname; + + expandedname = expandstr(name); + if (expandedname == NULL) + return; + INTOFF; + if ((fd = open(expandedname, O_RDONLY | O_CLOEXEC)) >= 0) + setinputfd(fd, 1); + INTON; + if (fd < 0) + return; + cmdloop(0); + popfile(); +} + + + +/* + * Read a file containing shell functions. + */ + +void +readcmdfile(const char *name) +{ + setinputfile(name, 1); + cmdloop(0); + popfile(); +} + + + +/* + * Take commands from a file. To be compatible we should do a path + * search for the file, which is necessary to find sub-commands. + */ + + +static char * +find_dot_file(char *basename) +{ + char *fullname; + const char *opt; + const char *path = pathval(); + struct stat statb; + + /* don't try this for absolute or relative paths */ + if( strchr(basename, '/')) + return basename; + + while ((fullname = padvance(&path, &opt, basename)) != NULL) { + if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { + /* + * Don't bother freeing here, since it will + * be freed by the caller. + */ + return fullname; + } + stunalloc(fullname); + } + return basename; +} + +int +dotcmd(int argc, char **argv) +{ + char *filename, *fullname; + + if (argc < 2) + error("missing filename"); + + exitstatus = 0; + + /* + * Because we have historically not supported any options, + * only treat "--" specially. + */ + filename = argc > 2 && strcmp(argv[1], "--") == 0 ? argv[2] : argv[1]; + + fullname = find_dot_file(filename); + setinputfile(fullname, 1); + commandname = fullname; + cmdloop(0); + popfile(); + return exitstatus; +} + + +int +exitcmd(int argc, char **argv) +{ + if (stoppedjobs()) + return 0; + iflag = 0; + optschanged(); + if (argc > 1) + exitshell(number(argv[1])); + else + exitshell_savedstatus(); +} diff --git a/bin/1sh/main.h b/bin/1sh/main.h new file mode 100644 index 00000000..329a5c8b --- /dev/null +++ b/bin/1sh/main.h @@ -0,0 +1,42 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)main.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/main.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +extern int rootpid; /* pid of main shell */ +extern int rootshell; /* true if we aren't a child of the main shell */ +extern struct jmploc main_handler; /* top level exception handler */ + +void readcmdfile(const char *); diff --git a/bin/1sh/memalloc.c b/bin/1sh/memalloc.c new file mode 100644 index 00000000..c53521ad --- /dev/null +++ b/bin/1sh/memalloc.c @@ -0,0 +1,344 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)memalloc.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/memalloc.c 326025 2017-11-20 19:49:47Z pfg $ */ + +#include <sys/param.h> +#include "shell.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "expand.h" +#include <stdlib.h> +#include <unistd.h> + +/* + * Like malloc, but returns an error when out of space. + */ + +pointer +ckmalloc(size_t nbytes) +{ + pointer p; + + INTOFF; + p = malloc(nbytes); + INTON; + if (p == NULL) + error("Out of space"); + return p; +} + + +/* + * Same for realloc. + */ + +pointer +ckrealloc(pointer p, int nbytes) +{ + INTOFF; + p = realloc(p, nbytes); + INTON; + if (p == NULL) + error("Out of space"); + return p; +} + +void +ckfree(pointer p) +{ + INTOFF; + free(p); + INTON; +} + + +/* + * Make a copy of a string in safe storage. + */ + +char * +savestr(const char *s) +{ + char *p; + size_t len; + + len = strlen(s); + p = ckmalloc(len + 1); + memcpy(p, s, len + 1); + return p; +} + + +/* + * Parse trees for commands are allocated in lifo order, so we use a stack + * to make this more efficient, and also to avoid all sorts of exception + * handling code to handle interrupts in the middle of a parse. + * + * The size 496 was chosen because with 16-byte alignment the total size + * for the allocated block is 512. + */ + +#define MINSIZE 496 /* minimum size of a block. */ + + +struct stack_block { + struct stack_block *prev; + /* Data follows */ +}; +#define SPACE(sp) ((char*)(sp) + ALIGN(sizeof(struct stack_block))) + +static struct stack_block *stackp; +char *stacknxt; +int stacknleft; +char *sstrend; + + +static void +stnewblock(int nbytes) +{ + struct stack_block *sp; + int allocsize; + + if (nbytes < MINSIZE) + nbytes = MINSIZE; + + allocsize = ALIGN(sizeof(struct stack_block)) + ALIGN(nbytes); + + INTOFF; + sp = ckmalloc(allocsize); + sp->prev = stackp; + stacknxt = SPACE(sp); + stacknleft = allocsize - (stacknxt - (char*)sp); + sstrend = stacknxt + stacknleft; + stackp = sp; + INTON; +} + + +pointer +stalloc(int nbytes) +{ + char *p; + + nbytes = ALIGN(nbytes); + if (nbytes > stacknleft) + stnewblock(nbytes); + p = stacknxt; + stacknxt += nbytes; + stacknleft -= nbytes; + return p; +} + + +void +stunalloc(pointer p) +{ + if (p == NULL) { /*DEBUG */ + write(STDERR_FILENO, "stunalloc\n", 10); + abort(); + } + stacknleft += stacknxt - (char *)p; + stacknxt = p; +} + + +char * +stsavestr(const char *s) +{ + char *p; + size_t len; + + len = strlen(s); + p = stalloc(len + 1); + memcpy(p, s, len + 1); + return p; +} + + +void +setstackmark(struct stackmark *mark) +{ + mark->stackp = stackp; + mark->stacknxt = stacknxt; + mark->stacknleft = stacknleft; + /* Ensure this block stays in place. */ + if (stackp != NULL && stacknxt == SPACE(stackp)) + stalloc(1); +} + + +void +popstackmark(struct stackmark *mark) +{ + struct stack_block *sp; + + INTOFF; + while (stackp != mark->stackp) { + sp = stackp; + stackp = sp->prev; + ckfree(sp); + } + stacknxt = mark->stacknxt; + stacknleft = mark->stacknleft; + sstrend = stacknxt + stacknleft; + INTON; +} + + +/* + * When the parser reads in a string, it wants to stick the string on the + * stack and only adjust the stack pointer when it knows how big the + * string is. Stackblock (defined in stack.h) returns a pointer to a block + * of space on top of the stack and stackblocklen returns the length of + * this block. Growstackblock will grow this space by at least one byte, + * possibly moving it (like realloc). Grabstackblock actually allocates the + * part of the block that has been used. + */ + +static void +growstackblock(int min) +{ + char *p; + int newlen; + char *oldspace; + int oldlen; + struct stack_block *sp; + struct stack_block *oldstackp; + + if (min < stacknleft) + min = stacknleft; + if ((unsigned int)min >= + INT_MAX / 2 - ALIGN(sizeof(struct stack_block))) + error("Out of space"); + min += stacknleft; + min += ALIGN(sizeof(struct stack_block)); + newlen = 512; + while (newlen < min) + newlen <<= 1; + oldspace = stacknxt; + oldlen = stacknleft; + + if (stackp != NULL && stacknxt == SPACE(stackp)) { + INTOFF; + oldstackp = stackp; + stackp = oldstackp->prev; + sp = ckrealloc((pointer)oldstackp, newlen); + sp->prev = stackp; + stackp = sp; + stacknxt = SPACE(sp); + stacknleft = newlen - (stacknxt - (char*)sp); + sstrend = stacknxt + stacknleft; + INTON; + } else { + newlen -= ALIGN(sizeof(struct stack_block)); + p = stalloc(newlen); + if (oldlen != 0) + memcpy(p, oldspace, oldlen); + stunalloc(p); + } +} + + + +/* + * The following routines are somewhat easier to use that the above. + * The user declares a variable of type STACKSTR, which may be declared + * to be a register. The macro STARTSTACKSTR initializes things. Then + * the user uses the macro STPUTC to add characters to the string. In + * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is + * grown as necessary. When the user is done, she can just leave the + * string there and refer to it using stackblock(). Or she can allocate + * the space for it using grabstackstr(). If it is necessary to allow + * someone else to use the stack temporarily and then continue to grow + * the string, the user should use grabstack to allocate the space, and + * then call ungrabstr(p) to return to the previous mode of operation. + * + * USTPUTC is like STPUTC except that it doesn't check for overflow. + * CHECKSTACKSPACE can be called before USTPUTC to ensure that there + * is space for at least one character. + */ + +static char * +growstrstackblock(int n, int min) +{ + growstackblock(min); + return stackblock() + n; +} + +char * +growstackstr(void) +{ + int len; + + len = stackblocksize(); + return (growstrstackblock(len, 0)); +} + + +/* + * Called from CHECKSTRSPACE. + */ + +char * +makestrspace(int min, char *p) +{ + int len; + + len = p - stackblock(); + return (growstrstackblock(len, min)); +} + + +char * +stputbin(const char *data, size_t len, char *p) +{ + CHECKSTRSPACE(len, p); + memcpy(p, data, len); + return (p + len); +} + +char * +stputs(const char *data, char *p) +{ + return (stputbin(data, strlen(data), p)); +} diff --git a/bin/1sh/memalloc.h b/bin/1sh/memalloc.h new file mode 100644 index 00000000..f3acc03d --- /dev/null +++ b/bin/1sh/memalloc.h @@ -0,0 +1,88 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)memalloc.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/memalloc.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +#include <string.h> + +struct stackmark { + struct stack_block *stackp; + char *stacknxt; + int stacknleft; +}; + + +extern char *stacknxt; +extern int stacknleft; +extern char *sstrend; + +pointer ckmalloc(size_t); +pointer ckrealloc(pointer, int); +void ckfree(pointer); +char *savestr(const char *); +pointer stalloc(int); +void stunalloc(pointer); +char *stsavestr(const char *); +void setstackmark(struct stackmark *); +void popstackmark(struct stackmark *); +char *growstackstr(void); +char *makestrspace(int, char *); +char *stputbin(const char *data, size_t len, char *p); +char *stputs(const char *data, char *p); + + + +#define stackblock() stacknxt +#define stackblocksize() stacknleft +#define grabstackblock(n) stalloc(n) +#define STARTSTACKSTR(p) p = stackblock() +#define STPUTC(c, p) do { if (p == sstrend) p = growstackstr(); *p++ = (c); } while(0) +#define CHECKSTRSPACE(n, p) { if ((size_t)(sstrend - p) < n) p = makestrspace(n, p); } +#define USTPUTC(c, p) (*p++ = (c)) +/* + * STACKSTRNUL's use is where we want to be able to turn a stack + * (non-sentinel, character counting string) into a C string, + * and later pretend the NUL is not there. + * Note: Because of STACKSTRNUL's semantics, STACKSTRNUL cannot be used + * on a stack that will grabstackstr()ed. + */ +#define STACKSTRNUL(p) (p == sstrend ? (p = growstackstr(), *p = '\0') : (*p = '\0')) +#define STUNPUTC(p) (--p) +#define STTOPC(p) p[-1] +#define STADJUST(amount, p) (p += (amount)) +#define grabstackstr(p) stalloc((char *)p - stackblock()) +#define ungrabstackstr(s, p) stunalloc((s)) +#define STPUTBIN(s, len, p) p = stputbin((s), (len), p) +#define STPUTS(s, p) p = stputs((s), p) diff --git a/bin/1sh/miscbltin.c b/bin/1sh/miscbltin.c new file mode 100644 index 00000000..66329850 --- /dev/null +++ b/bin/1sh/miscbltin.c @@ -0,0 +1,534 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)miscbltin.c 8.4 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/miscbltin.c 326025 2017-11-20 19:49:47Z pfg $ */ + +/* + * Miscellaneous builtins. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "shell.h" +#include "options.h" +#include "var.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "syntax.h" +#include "trap.h" + +#undef eflag + +int readcmd(int, char **); +int umaskcmd(int, char **); +int ulimitcmd(int, char **); + +/* + * The read builtin. The -r option causes backslashes to be treated like + * ordinary characters. + * + * This uses unbuffered input, which may be avoidable in some cases. + * + * Note that if IFS=' :' then read x y should work so that: + * 'a b' x='a', y='b' + * ' a b ' x='a', y='b' + * ':b' x='', y='b' + * ':' x='', y='' + * '::' x='', y='' + * ': :' x='', y='' + * ':::' x='', y='::' + * ':b c:' x='', y='b c:' + */ + +int +readcmd(int argc __unused, char **argv __unused) +{ + char **ap; + int backslash; + char c; + int rflag; + char *prompt; + const char *ifs; + char *p; + int startword; + int status; + int i; + int is_ifs; + int saveall = 0; + ptrdiff_t lastnonifs, lastnonifsws; + struct timeval tv; + char *tvptr; + fd_set ifds; + ssize_t nread; + int sig; + + rflag = 0; + prompt = NULL; + tv.tv_sec = -1; + tv.tv_usec = 0; + while ((i = nextopt("erp:t:")) != '\0') { + switch(i) { + case 'p': + prompt = shoptarg; + break; + case 'e': + break; + case 'r': + rflag = 1; + break; + case 't': + tv.tv_sec = strtol(shoptarg, &tvptr, 0); + if (tvptr == shoptarg) + error("timeout value"); + switch(*tvptr) { + case 0: + case 's': + break; + case 'h': + tv.tv_sec *= 60; + /* FALLTHROUGH */ + case 'm': + tv.tv_sec *= 60; + break; + default: + error("timeout unit"); + } + break; + } + } + if (prompt && isatty(0)) { + out2str(prompt); + flushall(); + } + if (*(ap = argptr) == NULL) + error("arg count"); + if ((ifs = bltinlookup("IFS", 1)) == NULL) + ifs = " \t\n"; + + if (tv.tv_sec >= 0) { + /* + * Wait for something to become available. + */ + FD_ZERO(&ifds); + FD_SET(0, &ifds); + status = select(1, &ifds, NULL, NULL, &tv); + /* + * If there's nothing ready, return an error. + */ + if (status <= 0) { + sig = pendingsig; + return (128 + (sig != 0 ? sig : SIGALRM)); + } + } + + status = 0; + startword = 2; + backslash = 0; + STARTSTACKSTR(p); + lastnonifs = lastnonifsws = -1; + for (;;) { + nread = read(STDIN_FILENO, &c, 1); + if (nread == -1) { + if (errno == EINTR) { + sig = pendingsig; + if (sig == 0) + continue; + status = 128 + sig; + break; + } + warning("read error: %s", strerror(errno)); + status = 2; + break; + } else if (nread != 1) { + status = 1; + break; + } + if (c == '\0') + continue; + CHECKSTRSPACE(1, p); + if (backslash) { + backslash = 0; + if (c != '\n') { + startword = 0; + lastnonifs = lastnonifsws = p - stackblock(); + USTPUTC(c, p); + } + continue; + } + if (!rflag && c == '\\') { + backslash++; + continue; + } + if (c == '\n') + break; + if (strchr(ifs, c)) + is_ifs = strchr(" \t\n", c) ? 1 : 2; + else + is_ifs = 0; + + if (startword != 0) { + if (is_ifs == 1) { + /* Ignore leading IFS whitespace */ + if (saveall) + USTPUTC(c, p); + continue; + } + if (is_ifs == 2 && startword == 1) { + /* Only one non-whitespace IFS per word */ + startword = 2; + if (saveall) { + lastnonifsws = p - stackblock(); + USTPUTC(c, p); + } + continue; + } + } + + if (is_ifs == 0) { + /* append this character to the current variable */ + startword = 0; + if (saveall) + /* Not just a spare terminator */ + saveall++; + lastnonifs = lastnonifsws = p - stackblock(); + USTPUTC(c, p); + continue; + } + + /* end of variable... */ + startword = is_ifs; + + if (ap[1] == NULL) { + /* Last variable needs all IFS chars */ + saveall++; + if (is_ifs == 2) + lastnonifsws = p - stackblock(); + USTPUTC(c, p); + continue; + } + + STACKSTRNUL(p); + setvar(*ap, stackblock(), 0); + ap++; + STARTSTACKSTR(p); + lastnonifs = lastnonifsws = -1; + } + STACKSTRNUL(p); + + /* + * Remove trailing IFS chars: always remove whitespace, don't remove + * non-whitespace unless it was naked + */ + if (saveall <= 1) + lastnonifsws = lastnonifs; + stackblock()[lastnonifsws + 1] = '\0'; + setvar(*ap, stackblock(), 0); + + /* Set any remaining args to "" */ + while (*++ap != NULL) + setvar(*ap, "", 0); + return status; +} + + + +int +umaskcmd(int argc __unused, char **argv __unused) +{ + char *ap; + int mask; + int i; + int symbolic_mode = 0; + + while ((i = nextopt("S")) != '\0') { + symbolic_mode = 1; + } + + INTOFF; + mask = umask(0); + umask(mask); + INTON; + + if ((ap = *argptr) == NULL) { + if (symbolic_mode) { + char u[4], g[4], o[4]; + + i = 0; + if ((mask & S_IRUSR) == 0) + u[i++] = 'r'; + if ((mask & S_IWUSR) == 0) + u[i++] = 'w'; + if ((mask & S_IXUSR) == 0) + u[i++] = 'x'; + u[i] = '\0'; + + i = 0; + if ((mask & S_IRGRP) == 0) + g[i++] = 'r'; + if ((mask & S_IWGRP) == 0) + g[i++] = 'w'; + if ((mask & S_IXGRP) == 0) + g[i++] = 'x'; + g[i] = '\0'; + + i = 0; + if ((mask & S_IROTH) == 0) + o[i++] = 'r'; + if ((mask & S_IWOTH) == 0) + o[i++] = 'w'; + if ((mask & S_IXOTH) == 0) + o[i++] = 'x'; + o[i] = '\0'; + + out1fmt("u=%s,g=%s,o=%s\n", u, g, o); + } else { + out1fmt("%.4o\n", mask); + } + } else { + if (is_digit(*ap)) { + mask = 0; + do { + if (*ap >= '8' || *ap < '0') + error("Illegal number: %s", *argptr); + mask = (mask << 3) + (*ap - '0'); + } while (*++ap != '\0'); + umask(mask); + } else { + void *set; + INTOFF; + if ((set = setmode (ap)) == NULL) + error("Illegal number: %s", ap); + + mask = getmode (set, ~mask & 0777); + umask(~mask & 0777); + free(set); + INTON; + } + } + return 0; +} + +/* + * ulimit builtin + * + * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and + * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with + * ash by J.T. Conklin. + * + * Public domain. + */ + +struct limits { + const char *name; + const char *units; + int cmd; + short factor; /* multiply by to get rlim_{cur,max} values */ + char option; +}; + +static const struct limits limits[] = { +#ifdef RLIMIT_CPU + { "cpu time", "seconds", RLIMIT_CPU, 1, 't' }, +#endif +#ifdef RLIMIT_FSIZE + { "file size", "512-blocks", RLIMIT_FSIZE, 512, 'f' }, +#endif +#ifdef RLIMIT_DATA + { "data seg size", "kbytes", RLIMIT_DATA, 1024, 'd' }, +#endif +#ifdef RLIMIT_STACK + { "stack size", "kbytes", RLIMIT_STACK, 1024, 's' }, +#endif +#ifdef RLIMIT_CORE + { "core file size", "512-blocks", RLIMIT_CORE, 512, 'c' }, +#endif +#ifdef RLIMIT_RSS + { "max memory size", "kbytes", RLIMIT_RSS, 1024, 'm' }, +#endif +#ifdef RLIMIT_MEMLOCK + { "locked memory", "kbytes", RLIMIT_MEMLOCK, 1024, 'l' }, +#endif +#ifdef RLIMIT_NPROC + { "max user processes", (char *)0, RLIMIT_NPROC, 1, 'u' }, +#endif +#ifdef RLIMIT_NOFILE + { "open files", (char *)0, RLIMIT_NOFILE, 1, 'n' }, +#endif +#ifdef RLIMIT_VMEM + { "virtual mem size", "kbytes", RLIMIT_VMEM, 1024, 'v' }, +#endif +#ifdef RLIMIT_SWAP + { "swap limit", "kbytes", RLIMIT_SWAP, 1024, 'w' }, +#endif +#ifdef RLIMIT_SBSIZE + { "socket buffer size", "bytes", RLIMIT_SBSIZE, 1, 'b' }, +#endif +#ifdef RLIMIT_NPTS + { "pseudo-terminals", (char *)0, RLIMIT_NPTS, 1, 'p' }, +#endif +#ifdef RLIMIT_KQUEUES + { "kqueues", (char *)0, RLIMIT_KQUEUES, 1, 'k' }, +#endif +#ifdef RLIMIT_UMTXP + { "umtx shared locks", (char *)0, RLIMIT_UMTXP, 1, 'o' }, +#endif + { (char *) 0, (char *)0, 0, 0, '\0' } +}; + +enum limithow { SOFT = 0x1, HARD = 0x2 }; + +static void +printlimit(enum limithow how, const struct rlimit *limit, + const struct limits *l) +{ + rlim_t val = 0; + + if (how & SOFT) + val = limit->rlim_cur; + else if (how & HARD) + val = limit->rlim_max; + if (val == RLIM_INFINITY) + out1str("unlimited\n"); + else + { + val /= l->factor; + out1fmt("%jd\n", (intmax_t)val); + } +} + +int +ulimitcmd(int argc __unused, char **argv __unused) +{ + rlim_t val = 0; + enum limithow how = SOFT | HARD; + const struct limits *l; + int set, all = 0; + int optc, what; + struct rlimit limit; + + what = 'f'; + while ((optc = nextopt("HSatfdsmcnuvlbpwko")) != '\0') + switch (optc) { + case 'H': + how = HARD; + break; + case 'S': + how = SOFT; + break; + case 'a': + all = 1; + break; + default: + what = optc; + } + + for (l = limits; l->name && l->option != what; l++) + ; + if (!l->name) + error("internal error (%c)", what); + + set = *argptr ? 1 : 0; + if (set) { + char *p = *argptr; + + if (all || argptr[1]) + error("too many arguments"); + if (strcmp(p, "unlimited") == 0) + val = RLIM_INFINITY; + else { + char *end; + uintmax_t uval; + + if (*p < '0' || *p > '9') + error("bad number"); + errno = 0; + uval = strtoumax(p, &end, 10); + if (errno != 0 || *end != '\0') + error("bad number"); + if (uval > UINTMAX_MAX / l->factor) + error("bad number"); + uval *= l->factor; + val = (rlim_t)uval; + if (val < 0 || (uintmax_t)val != uval || + val == RLIM_INFINITY) + error("bad number"); + } + } + if (all) { + for (l = limits; l->name; l++) { + char optbuf[40]; + if (getrlimit(l->cmd, &limit) < 0) + error("can't get limit: %s", strerror(errno)); + + if (l->units) + snprintf(optbuf, sizeof(optbuf), + "(%s, -%c) ", l->units, l->option); + else + snprintf(optbuf, sizeof(optbuf), + "(-%c) ", l->option); + out1fmt("%-18s %18s ", l->name, optbuf); + printlimit(how, &limit, l); + } + return 0; + } + + if (getrlimit(l->cmd, &limit) < 0) + error("can't get limit: %s", strerror(errno)); + if (set) { + if (how & SOFT) + limit.rlim_cur = val; + if (how & HARD) + limit.rlim_max = val; + if (setrlimit(l->cmd, &limit) < 0) + error("bad limit: %s", strerror(errno)); + } else + printlimit(how, &limit, l); + return 0; +} diff --git a/bin/1sh/mkbuiltins b/bin/1sh/mkbuiltins new file mode 100755 index 00000000..cbcf0ec1 --- /dev/null +++ b/bin/1sh/mkbuiltins @@ -0,0 +1,137 @@ +#!/bin/sh - + +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)mkbuiltins 8.2 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/mkbuiltins 328934 2018-02-06 15:41:35Z arichardson $ + +temp=`mktemp` +havehist=1 +if [ "X$1" = "X-h" ]; then + havehist=0 + shift +fi +srcdir=$1 +havejobs=0 +if grep '^#define[ ]*JOBS[ ]*1' $srcdir/shell.h > /dev/null +then havejobs=1 +fi +exec > builtins.c +cat <<\! +/* + * This file was generated by the mkbuiltins program. + */ + +#include <stdlib.h> +#include "shell.h" +#include "builtins.h" + +! +awk '/^[^#]/ {if(('$havejobs' || $2 != "-j") && ('$havehist' || $2 != "-h")) \ + print $0}' $srcdir/builtins.def | sed 's/-[hj]//' > $temp +echo 'int (*const builtinfunc[])(int, char **) = {' +awk '/^[^#]/ { printf "\t%s,\n", $1}' $temp +echo '}; + +const unsigned char builtincmd[] = {' +awk '{ for (i = 2 ; i <= NF ; i++) { + if ($i == "-s") { + spc = 1; + } else if ($i == "-n") { + # Handled later for builtins.h + continue + } else { + printf "\t\"\\%03o\\%03o%s\"\n", length($i), (spc ? 128 : 0) + NR-1, $i + spc = 0; + } + }}' $temp +echo '};' + +exec > builtins.h +cat <<\! +/* + * This file was generated by the mkbuiltins program. + */ + +#include <sys/cdefs.h> +! +tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ < $temp | + awk '{ printf "#define %s %d\n", $1, NR-1}' +echo ' +#define BUILTIN_SPECIAL 0x80 + +extern int (*const builtinfunc[])(int, char **); +extern const unsigned char builtincmd[]; +' +awk '{ printf "int %s(int, char **);\n", $1}' $temp + +# Build safe_builtin_always() +cat <<EOF + +static inline int +safe_builtin_always(int idx) +{ +EOF +awk ' +BEGIN { printed = 0 } +{ + for (i = 2 ; i <= NF ; i++) { + if ($i == "-s") { + continue + } else if ($i == "-n") { + nofork = 1; + } else { + if (nofork == 0) { + continue + } + if (printed == 1) { + printf " || \n\t " + } else { + printf "\tif (" + } + printf "idx == " toupper($1) + printed = 1 + nofork = 0; + # Only need to check each once + break + } + } +}' $temp + +cat << EOF +) + return (1); + return(0); +} +EOF + +rm -f $temp diff --git a/bin/1sh/mknodes.c b/bin/1sh/mknodes.c new file mode 100644 index 00000000..9ec4dfbd --- /dev/null +++ b/bin/1sh/mknodes.c @@ -0,0 +1,461 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if 0 +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1991, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static char sccsid[] = "@(#)mknodes.c 8.2 (Berkeley) 5/4/95"; +#endif /* not lint */ +#endif +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/mknodes.c 326025 2017-11-20 19:49:47Z pfg $ */ + +/* + * This program reads the nodetypes file and nodes.c.pat file. It generates + * the files nodes.h and nodes.c. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdarg.h> + +#define MAXTYPES 50 /* max number of node types */ +#define MAXFIELDS 20 /* max fields in a structure */ +#define BUFLEN 100 /* size of character buffers */ + +/* field types */ +#define T_NODE 1 /* union node *field */ +#define T_NODELIST 2 /* struct nodelist *field */ +#define T_STRING 3 +#define T_INT 4 /* int field */ +#define T_OTHER 5 /* other */ +#define T_TEMP 6 /* don't copy this field */ + + +struct field { /* a structure field */ + char *name; /* name of field */ + int type; /* type of field */ + char *decl; /* declaration of field */ +}; + + +struct str { /* struct representing a node structure */ + char *tag; /* structure tag */ + int nfields; /* number of fields in the structure */ + struct field field[MAXFIELDS]; /* the fields of the structure */ + int done; /* set if fully parsed */ +}; + + +static int ntypes; /* number of node types */ +static char *nodename[MAXTYPES]; /* names of the nodes */ +static struct str *nodestr[MAXTYPES]; /* type of structure used by the node */ +static int nstr; /* number of structures */ +static struct str str[MAXTYPES]; /* the structures */ +static struct str *curstr; /* current structure */ +static char line[1024]; +static int linno; +static char *linep; + +static void parsenode(void); +static void parsefield(void); +static void output(char *); +static void outsizes(FILE *); +static void outfunc(FILE *, int); +static void indent(int, FILE *); +static int nextfield(char *); +static void skipbl(void); +static int readline(FILE *); +static void error(const char *, ...) __attribute__((format(printf, 1, 2))) __attribute__((noreturn)); +static char *savestr(const char *); + + +int +main(int argc, char *argv[]) +{ + FILE *infp; + + if (argc != 3) + error("usage: mknodes file"); + if ((infp = fopen(argv[1], "r")) == NULL) + error("Can't open %s: %s", argv[1], strerror(errno)); + while (readline(infp)) { + if (line[0] == ' ' || line[0] == '\t') + parsefield(); + else if (line[0] != '\0') + parsenode(); + } + fclose(infp); + output(argv[2]); + exit(0); +} + + + +static void +parsenode(void) +{ + char name[BUFLEN]; + char tag[BUFLEN]; + struct str *sp; + + if (curstr && curstr->nfields > 0) + curstr->done = 1; + nextfield(name); + if (! nextfield(tag)) + error("Tag expected"); + if (*linep != '\0') + error("Garbage at end of line"); + nodename[ntypes] = savestr(name); + for (sp = str ; sp < str + nstr ; sp++) { + if (strcmp(sp->tag, tag) == 0) + break; + } + if (sp >= str + nstr) { + sp->tag = savestr(tag); + sp->nfields = 0; + curstr = sp; + nstr++; + } + nodestr[ntypes] = sp; + ntypes++; +} + + +static void +parsefield(void) +{ + char name[BUFLEN]; + char type[BUFLEN]; + char decl[2 * BUFLEN]; + struct field *fp; + + if (curstr == NULL || curstr->done) + error("No current structure to add field to"); + if (! nextfield(name)) + error("No field name"); + if (! nextfield(type)) + error("No field type"); + fp = &curstr->field[curstr->nfields]; + fp->name = savestr(name); + if (strcmp(type, "nodeptr") == 0) { + fp->type = T_NODE; + sprintf(decl, "union node *%s", name); + } else if (strcmp(type, "nodelist") == 0) { + fp->type = T_NODELIST; + sprintf(decl, "struct nodelist *%s", name); + } else if (strcmp(type, "string") == 0) { + fp->type = T_STRING; + sprintf(decl, "char *%s", name); + } else if (strcmp(type, "int") == 0) { + fp->type = T_INT; + sprintf(decl, "int %s", name); + } else if (strcmp(type, "other") == 0) { + fp->type = T_OTHER; + } else if (strcmp(type, "temp") == 0) { + fp->type = T_TEMP; + } else { + error("Unknown type %s", type); + } + if (fp->type == T_OTHER || fp->type == T_TEMP) { + skipbl(); + fp->decl = savestr(linep); + } else { + if (*linep) + error("Garbage at end of line"); + fp->decl = savestr(decl); + } + curstr->nfields++; +} + + +static const char writer[] = "\ +/*\n\ + * This file was generated by the mknodes program.\n\ + */\n\ +\n"; + +static void +output(char *file) +{ + FILE *hfile; + FILE *cfile; + FILE *patfile; + int i; + struct str *sp; + struct field *fp; + char *p; + + if ((patfile = fopen(file, "r")) == NULL) + error("Can't open %s: %s", file, strerror(errno)); + if ((hfile = fopen("nodes.h", "w")) == NULL) + error("Can't create nodes.h: %s", strerror(errno)); + if ((cfile = fopen("nodes.c", "w")) == NULL) + error("Can't create nodes.c"); + fputs(writer, hfile); + for (i = 0 ; i < ntypes ; i++) + fprintf(hfile, "#define %s %d\n", nodename[i], i); + fputs("\n\n\n", hfile); + for (sp = str ; sp < &str[nstr] ; sp++) { + fprintf(hfile, "struct %s {\n", sp->tag); + for (i = sp->nfields, fp = sp->field ; --i >= 0 ; fp++) { + fprintf(hfile, " %s;\n", fp->decl); + } + fputs("};\n\n\n", hfile); + } + fputs("union node {\n", hfile); + fprintf(hfile, " int type;\n"); + for (sp = str ; sp < &str[nstr] ; sp++) { + fprintf(hfile, " struct %s %s;\n", sp->tag, sp->tag); + } + fputs("};\n\n\n", hfile); + fputs("struct nodelist {\n", hfile); + fputs("\tstruct nodelist *next;\n", hfile); + fputs("\tunion node *n;\n", hfile); + fputs("};\n\n\n", hfile); + fputs("struct funcdef;\n", hfile); + fputs("struct funcdef *copyfunc(union node *);\n", hfile); + fputs("union node *getfuncnode(struct funcdef *);\n", hfile); + fputs("void reffunc(struct funcdef *);\n", hfile); + fputs("void unreffunc(struct funcdef *);\n", hfile); + if (ferror(hfile)) + error("Can't write to nodes.h"); + if (fclose(hfile)) + error("Can't close nodes.h"); + + fputs(writer, cfile); + while (fgets(line, sizeof line, patfile) != NULL) { + for (p = line ; *p == ' ' || *p == '\t' ; p++); + if (strcmp(p, "%SIZES\n") == 0) + outsizes(cfile); + else if (strcmp(p, "%CALCSIZE\n") == 0) + outfunc(cfile, 1); + else if (strcmp(p, "%COPY\n") == 0) + outfunc(cfile, 0); + else + fputs(line, cfile); + } + fclose(patfile); + if (ferror(cfile)) + error("Can't write to nodes.c"); + if (fclose(cfile)) + error("Can't close nodes.c"); +} + + + +static void +outsizes(FILE *cfile) +{ + int i; + + fprintf(cfile, "static const short nodesize[%d] = {\n", ntypes); + for (i = 0 ; i < ntypes ; i++) { + fprintf(cfile, " ALIGN(sizeof (struct %s)),\n", nodestr[i]->tag); + } + fprintf(cfile, "};\n"); +} + + +static void +outfunc(FILE *cfile, int calcsize) +{ + struct str *sp; + struct field *fp; + int i; + + fputs(" if (n == NULL)\n", cfile); + if (calcsize) + fputs(" return;\n", cfile); + else + fputs(" return NULL;\n", cfile); + if (calcsize) + fputs(" result->blocksize += nodesize[n->type];\n", cfile); + else { + fputs(" new = state->block;\n", cfile); + fputs(" state->block = (char *)state->block + nodesize[n->type];\n", cfile); + } + fputs(" switch (n->type) {\n", cfile); + for (sp = str ; sp < &str[nstr] ; sp++) { + for (i = 0 ; i < ntypes ; i++) { + if (nodestr[i] == sp) + fprintf(cfile, " case %s:\n", nodename[i]); + } + for (i = sp->nfields ; --i >= 1 ; ) { + fp = &sp->field[i]; + switch (fp->type) { + case T_NODE: + if (calcsize) { + indent(12, cfile); + fprintf(cfile, "calcsize(n->%s.%s, result);\n", + sp->tag, fp->name); + } else { + indent(12, cfile); + fprintf(cfile, "new->%s.%s = copynode(n->%s.%s, state);\n", + sp->tag, fp->name, sp->tag, fp->name); + } + break; + case T_NODELIST: + if (calcsize) { + indent(12, cfile); + fprintf(cfile, "sizenodelist(n->%s.%s, result);\n", + sp->tag, fp->name); + } else { + indent(12, cfile); + fprintf(cfile, "new->%s.%s = copynodelist(n->%s.%s, state);\n", + sp->tag, fp->name, sp->tag, fp->name); + } + break; + case T_STRING: + if (calcsize) { + indent(12, cfile); + fprintf(cfile, "result->stringsize += strlen(n->%s.%s) + 1;\n", + sp->tag, fp->name); + } else { + indent(12, cfile); + fprintf(cfile, "new->%s.%s = nodesavestr(n->%s.%s, state);\n", + sp->tag, fp->name, sp->tag, fp->name); + } + break; + case T_INT: + case T_OTHER: + if (! calcsize) { + indent(12, cfile); + fprintf(cfile, "new->%s.%s = n->%s.%s;\n", + sp->tag, fp->name, sp->tag, fp->name); + } + break; + } + } + indent(12, cfile); + fputs("break;\n", cfile); + } + fputs(" };\n", cfile); + if (! calcsize) + fputs(" new->type = n->type;\n", cfile); +} + + +static void +indent(int amount, FILE *fp) +{ + while (amount >= 8) { + putc('\t', fp); + amount -= 8; + } + while (--amount >= 0) { + putc(' ', fp); + } +} + + +static int +nextfield(char *buf) +{ + char *p, *q; + + p = linep; + while (*p == ' ' || *p == '\t') + p++; + q = buf; + while (*p != ' ' && *p != '\t' && *p != '\0') + *q++ = *p++; + *q = '\0'; + linep = p; + return (q > buf); +} + + +static void +skipbl(void) +{ + while (*linep == ' ' || *linep == '\t') + linep++; +} + + +static int +readline(FILE *infp) +{ + char *p; + + if (fgets(line, 1024, infp) == NULL) + return 0; + for (p = line ; *p != '#' && *p != '\n' && *p != '\0' ; p++); + while (p > line && (p[-1] == ' ' || p[-1] == '\t')) + p--; + *p = '\0'; + linep = line; + linno++; + if (p - line > BUFLEN) + error("Line too long"); + return 1; +} + + + +static void +error(const char *msg, ...) +{ + va_list va; + va_start(va, msg); + + (void) fprintf(stderr, "line %d: ", linno); + (void) vfprintf(stderr, msg, va); + (void) fputc('\n', stderr); + + va_end(va); + + exit(2); +} + + + +static char * +savestr(const char *s) +{ + char *p; + + if ((p = malloc(strlen(s) + 1)) == NULL) + error("Out of space"); + (void) strcpy(p, s); + return p; +} diff --git a/bin/1sh/mksyntax.c b/bin/1sh/mksyntax.c new file mode 100644 index 00000000..b61378a9 --- /dev/null +++ b/bin/1sh/mksyntax.c @@ -0,0 +1,332 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if 0 +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1991, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static char sccsid[] = "@(#)mksyntax.c 8.2 (Berkeley) 5/4/95"; +#endif /* not lint */ +#endif +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/mksyntax.c 334008 2018-05-21 21:52:48Z jilles $ */ + +/* + * This program creates syntax.h and syntax.c. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "parser.h" + + +struct synclass { + const char *name; + const char *comment; +}; + +/* Syntax classes */ +static const struct synclass synclass[] = { + { "CWORD", "character is nothing special" }, + { "CNL", "newline character" }, + { "CQNL", "newline character in quotes" }, + { "CBACK", "a backslash character" }, + { "CSBACK", "a backslash character in single quotes" }, + { "CSQUOTE", "single quote" }, + { "CDQUOTE", "double quote" }, + { "CENDQUOTE", "a terminating quote" }, + { "CBQUOTE", "backwards single quote" }, + { "CVAR", "a dollar sign" }, + { "CENDVAR", "a '}' character" }, + { "CLP", "a left paren in arithmetic" }, + { "CRP", "a right paren in arithmetic" }, + { "CEOF", "end of file" }, + { "CCTL", "like CWORD, except it must be escaped" }, + { "CSPCL", "these terminate a word" }, + { "CIGN", "character should be ignored" }, + { NULL, NULL } +}; + + +/* + * Syntax classes for is_ functions. Warning: if you add new classes + * you may have to change the definition of the is_in_name macro. + */ +static const struct synclass is_entry[] = { + { "ISDIGIT", "a digit" }, + { "ISUPPER", "an upper case letter" }, + { "ISLOWER", "a lower case letter" }, + { "ISUNDER", "an underscore" }, + { "ISSPECL", "the name of a special parameter" }, + { NULL, NULL } +}; + +static const char writer[] = "\ +/*\n\ + * This file was generated by the mksyntax program.\n\ + */\n\ +\n"; + + +static FILE *cfile; +static FILE *hfile; + +static void add_default(void); +static void finish(void); +static void init(const char *); +static void add(const char *, const char *); +static void output_type_macros(void); + +int +main(int argc __unused, char **argv __unused) +{ + int i; + char buf[80]; + int pos; + + /* Create output files */ + if ((cfile = fopen("syntax.c", "w")) == NULL) { + perror("syntax.c"); + exit(2); + } + if ((hfile = fopen("syntax.h", "w")) == NULL) { + perror("syntax.h"); + exit(2); + } + fputs(writer, hfile); + fputs(writer, cfile); + + fputs("#include <sys/cdefs.h>\n", hfile); + fputs("#include <limits.h>\n\n", hfile); + + /* Generate the #define statements in the header file */ + fputs("/* Syntax classes */\n", hfile); + for (i = 0 ; synclass[i].name ; i++) { + sprintf(buf, "#define %s %d", synclass[i].name, i); + fputs(buf, hfile); + for (pos = strlen(buf) ; pos < 32 ; pos = (pos + 8) & ~07) + putc('\t', hfile); + fprintf(hfile, "/* %s */\n", synclass[i].comment); + } + putc('\n', hfile); + fputs("/* Syntax classes for is_ functions */\n", hfile); + for (i = 0 ; is_entry[i].name ; i++) { + sprintf(buf, "#define %s %#o", is_entry[i].name, 1 << i); + fputs(buf, hfile); + for (pos = strlen(buf) ; pos < 32 ; pos = (pos + 8) & ~07) + putc('\t', hfile); + fprintf(hfile, "/* %s */\n", is_entry[i].comment); + } + putc('\n', hfile); + fputs("#define SYNBASE (1 - CHAR_MIN)\n", hfile); + fputs("#define PEOF -SYNBASE\n\n", hfile); + putc('\n', hfile); + fputs("#define BASESYNTAX (basesyntax + SYNBASE)\n", hfile); + fputs("#define DQSYNTAX (dqsyntax + SYNBASE)\n", hfile); + fputs("#define SQSYNTAX (sqsyntax + SYNBASE)\n", hfile); + fputs("#define ARISYNTAX (arisyntax + SYNBASE)\n", hfile); + putc('\n', hfile); + output_type_macros(); /* is_digit, etc. */ + putc('\n', hfile); + + /* Generate the syntax tables. */ + fputs("#include \"parser.h\"\n", cfile); + fputs("#include \"shell.h\"\n", cfile); + fputs("#include \"syntax.h\"\n\n", cfile); + + fputs("/* syntax table used when not in quotes */\n", cfile); + init("basesyntax"); + add_default(); + add("\n", "CNL"); + add("\\", "CBACK"); + add("'", "CSQUOTE"); + add("\"", "CDQUOTE"); + add("`", "CBQUOTE"); + add("$", "CVAR"); + add("}", "CENDVAR"); + add("<>();&| \t", "CSPCL"); + finish(); + + fputs("\n/* syntax table used when in double quotes */\n", cfile); + init("dqsyntax"); + add_default(); + add("\n", "CQNL"); + add("\\", "CBACK"); + add("\"", "CENDQUOTE"); + add("`", "CBQUOTE"); + add("$", "CVAR"); + add("}", "CENDVAR"); + /* ':/' for tilde expansion, '-^]' for [a\-x] pattern ranges */ + add("!*?[]=~:/-^", "CCTL"); + finish(); + + fputs("\n/* syntax table used when in single quotes */\n", cfile); + init("sqsyntax"); + add_default(); + add("\n", "CQNL"); + add("\\", "CSBACK"); + add("'", "CENDQUOTE"); + /* ':/' for tilde expansion, '-^]' for [a\-x] pattern ranges */ + add("!*?[]=~:/-^", "CCTL"); + finish(); + + fputs("\n/* syntax table used when in arithmetic */\n", cfile); + init("arisyntax"); + add_default(); + add("\n", "CQNL"); + add("\\", "CBACK"); + add("`", "CBQUOTE"); + add("\"", "CIGN"); + add("$", "CVAR"); + add("}", "CENDVAR"); + add("(", "CLP"); + add(")", "CRP"); + finish(); + + fputs("\n/* character classification table */\n", cfile); + init("is_type"); + add("0123456789", "ISDIGIT"); + add("abcdefghijklmnopqrstuvwxyz", "ISLOWER"); + add("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "ISUPPER"); + add("_", "ISUNDER"); + add("#?$!-*@", "ISSPECL"); + finish(); + + exit(0); +} + + +/* + * Output the header and declaration of a syntax table. + */ + +static void +init(const char *name) +{ + fprintf(hfile, "extern const char %s[];\n", name); + fprintf(cfile, "const char %s[SYNBASE + CHAR_MAX + 1] = {\n", name); +} + + +static void +add_one(const char *key, const char *type) +{ + fprintf(cfile, "\t[SYNBASE + %s] = %s,\n", key, type); +} + + +/* + * Add default values to the syntax table. + */ + +static void +add_default(void) +{ + add_one("PEOF", "CEOF"); + add_one("CTLESC", "CCTL"); + add_one("CTLVAR", "CCTL"); + add_one("CTLENDVAR", "CCTL"); + add_one("CTLBACKQ", "CCTL"); + add_one("CTLBACKQ + CTLQUOTE", "CCTL"); + add_one("CTLARI", "CCTL"); + add_one("CTLENDARI", "CCTL"); + add_one("CTLQUOTEMARK", "CCTL"); + add_one("CTLQUOTEEND", "CCTL"); +} + + +/* + * Output the footer of a syntax table. + */ + +static void +finish(void) +{ + fputs("};\n", cfile); +} + + +/* + * Add entries to the syntax table. + */ + +static void +add(const char *p, const char *type) +{ + for (; *p; ++p) { + char c = *p; + switch (c) { + case '\t': c = 't'; break; + case '\n': c = 'n'; break; + case '\'': c = '\''; break; + case '\\': c = '\\'; break; + + default: + fprintf(cfile, "\t[SYNBASE + '%c'] = %s,\n", c, type); + continue; + } + fprintf(cfile, "\t[SYNBASE + '\\%c'] = %s,\n", c, type); + } +} + + +/* + * Output character classification macros (e.g. is_digit). If digits are + * contiguous, we can test for them quickly. + */ + +static const char *macro[] = { + "#define is_digit(c)\t((unsigned int)((c) - '0') <= 9)", + "#define is_eof(c)\t((c) == PEOF)", + "#define is_alpha(c)\t((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER))", + "#define is_name(c)\t((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER|ISUNDER))", + "#define is_in_name(c)\t((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER|ISUNDER|ISDIGIT))", + "#define is_special(c)\t((is_type+SYNBASE)[(int)c] & (ISSPECL|ISDIGIT))", + "#define digit_val(c)\t((c) - '0')", + NULL +}; + +static void +output_type_macros(void) +{ + const char **pp; + + for (pp = macro ; *pp ; pp++) + fprintf(hfile, "%s\n", *pp); +} diff --git a/bin/1sh/mktokens b/bin/1sh/mktokens new file mode 100644 index 00000000..f0d80cc5 --- /dev/null +++ b/bin/1sh/mktokens @@ -0,0 +1,93 @@ +#!/bin/sh - + +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)mktokens 8.1 (Berkeley) 5/31/93 +# $FreeBSD: releng/12.1/bin/sh/mktokens 328934 2018-02-06 15:41:35Z arichardson $ + +# The following is a list of tokens. The second column is nonzero if the +# token marks the end of a list. The third column is the name to print in +# error messages. + +temp=`mktemp` +cat > $temp <<\! +TEOF 1 end of file +TNL 0 newline +TSEMI 0 ";" +TBACKGND 0 "&" +TAND 0 "&&" +TOR 0 "||" +TPIPE 0 "|" +TLP 0 "(" +TRP 1 ")" +TENDCASE 1 ";;" +TFALLTHRU 1 ";&" +TREDIR 0 redirection +TWORD 0 word +TIF 0 "if" +TTHEN 1 "then" +TELSE 1 "else" +TELIF 1 "elif" +TFI 1 "fi" +TWHILE 0 "while" +TUNTIL 0 "until" +TFOR 0 "for" +TDO 1 "do" +TDONE 1 "done" +TBEGIN 0 "{" +TEND 1 "}" +TCASE 0 "case" +TESAC 1 "esac" +TNOT 0 "!" +! +nl=`wc -l $temp` +exec > token.h +awk '{print "#define " $1 " " NR-1}' $temp +echo ' +/* Array indicating which tokens mark the end of a list */ +static const char tokendlist[] = {' +awk '{print "\t" $2 ","}' $temp +echo '}; + +static const char *const tokname[] = {' +sed -e 's/"/\\"/g' \ + -e 's/[^ ]*[ ][ ]*[^ ]*[ ][ ]*\(.*\)/ "\1",/' \ + $temp +echo '}; +' +sed 's/"//g' $temp | awk ' +/TIF/{print "#define KWDOFFSET " NR-1; print ""; print "const char *const parsekwd[] = {"} +/TIF/,/neverfound/{print " \"" $3 "\","}' +echo ' 0 +};' + +rm $temp diff --git a/bin/1sh/myhistedit.h b/bin/1sh/myhistedit.h new file mode 100644 index 00000000..e9a2be2c --- /dev/null +++ b/bin/1sh/myhistedit.h @@ -0,0 +1,45 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)myhistedit.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/myhistedit.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +#include <histedit.h> + +extern History *hist; +extern EditLine *el; +extern int displayhist; + +void histedit(void); +void sethistfile(const char *); +void sethistsize(const char *); +void setterm(const char *); + diff --git a/bin/1sh/mystring.c b/bin/1sh/mystring.c new file mode 100644 index 00000000..ddeea4c1 --- /dev/null +++ b/bin/1sh/mystring.c @@ -0,0 +1,100 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mystring.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/mystring.c 326025 2017-11-20 19:49:47Z pfg $ */ + +/* + * String functions. + * + * equal(s1, s2) Return true if strings are equal. + * number(s) Convert a string of digits to an integer. + * is_number(s) Return true if s is a string of digits. + */ + +#include <stdlib.h> +#include "shell.h" +#include "syntax.h" +#include "error.h" +#include "mystring.h" + + +char nullstr[1]; /* zero length string */ + +/* + * equal - #defined in mystring.h + */ + + +/* + * Convert a string of digits to an integer, printing an error message on + * failure. + */ + +int +number(const char *s) +{ + if (! is_number(s)) + error("Illegal number: %s", s); + return atoi(s); +} + + + +/* + * Check for a valid number. This should be elsewhere. + */ + +int +is_number(const char *p) +{ + const char *q; + + if (*p == '\0') + return 0; + while (*p == '0') + p++; + for (q = p; *q != '\0'; q++) + if (! is_digit(*q)) + return 0; + if (q - p > 10 || + (q - p == 10 && memcmp(p, "2147483647", 10) > 0)) + return 0; + return 1; +} diff --git a/bin/1sh/mystring.h b/bin/1sh/mystring.h new file mode 100644 index 00000000..9f4960e5 --- /dev/null +++ b/bin/1sh/mystring.h @@ -0,0 +1,43 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)mystring.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/mystring.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +#include <string.h> + +int number(const char *); +int is_number(const char *); + +#define equal(s1, s2) (strcmp(s1, s2) == 0) diff --git a/bin/1sh/nodes.c.pat b/bin/1sh/nodes.c.pat new file mode 100644 index 00000000..b687b570 --- /dev/null +++ b/bin/1sh/nodes.c.pat @@ -0,0 +1,193 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)nodes.c.pat 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/nodes.c.pat 314436 2017-02-28 23:42:47Z imp $ + */ + +#include <sys/param.h> +#include <stdlib.h> +#include <stddef.h> +/* + * Routine for dealing with parsed shell commands. + */ + +#include "shell.h" +#include "nodes.h" +#include "memalloc.h" +#include "mystring.h" + + +struct nodesize { + int blocksize; /* size of structures in function */ + int stringsize; /* size of strings in node */ +}; + +struct nodecopystate { + pointer block; /* block to allocate function from */ + char *string; /* block to allocate strings from */ +}; + +%SIZES + + +static void calcsize(union node *, struct nodesize *); +static void sizenodelist(struct nodelist *, struct nodesize *); +static union node *copynode(union node *, struct nodecopystate *); +static struct nodelist *copynodelist(struct nodelist *, struct nodecopystate *); +static char *nodesavestr(const char *, struct nodecopystate *); + + +struct funcdef { + unsigned int refcount; + union node n; +}; + +/* + * Make a copy of a parse tree. + */ + +struct funcdef * +copyfunc(union node *n) +{ + struct nodesize sz; + struct nodecopystate st; + struct funcdef *fn; + + if (n == NULL) + return NULL; + sz.blocksize = offsetof(struct funcdef, n); + sz.stringsize = 0; + calcsize(n, &sz); + fn = ckmalloc(sz.blocksize + sz.stringsize); + fn->refcount = 1; + st.block = (char *)fn + offsetof(struct funcdef, n); + st.string = (char *)fn + sz.blocksize; + copynode(n, &st); + return fn; +} + + +union node * +getfuncnode(struct funcdef *fn) +{ + return fn == NULL ? NULL : &fn->n; +} + + +static void +calcsize(union node *n, struct nodesize *result) +{ + %CALCSIZE +} + + + +static void +sizenodelist(struct nodelist *lp, struct nodesize *result) +{ + while (lp) { + result->blocksize += ALIGN(sizeof(struct nodelist)); + calcsize(lp->n, result); + lp = lp->next; + } +} + + + +static union node * +copynode(union node *n, struct nodecopystate *state) +{ + union node *new; + + %COPY + return new; +} + + +static struct nodelist * +copynodelist(struct nodelist *lp, struct nodecopystate *state) +{ + struct nodelist *start; + struct nodelist **lpp; + + lpp = &start; + while (lp) { + *lpp = state->block; + state->block = (char *)state->block + + ALIGN(sizeof(struct nodelist)); + (*lpp)->n = copynode(lp->n, state); + lp = lp->next; + lpp = &(*lpp)->next; + } + *lpp = NULL; + return start; +} + + + +static char * +nodesavestr(const char *s, struct nodecopystate *state) +{ + const char *p = s; + char *q = state->string; + char *rtn = state->string; + + while ((*q++ = *p++) != '\0') + continue; + state->string = q; + return rtn; +} + + +void +reffunc(struct funcdef *fn) +{ + if (fn) + fn->refcount++; +} + + +/* + * Decrement the reference count of a function definition, freeing it + * if it falls to 0. + */ + +void +unreffunc(struct funcdef *fn) +{ + if (fn) { + fn->refcount--; + if (fn->refcount > 0) + return; + ckfree(fn); + } +} diff --git a/bin/1sh/nodetypes b/bin/1sh/nodetypes new file mode 100644 index 00000000..51facbe8 --- /dev/null +++ b/bin/1sh/nodetypes @@ -0,0 +1,145 @@ +#- +# Copyright (c) 1991, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Kenneth Almquist. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)nodetypes 8.2 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/nodetypes 314436 2017-02-28 23:42:47Z imp $ + +# This file describes the nodes used in parse trees. Unindented lines +# contain a node type followed by a structure tag. Subsequent indented +# lines specify the fields of the structure. Several node types can share +# the same structure, in which case the fields of the structure should be +# specified only once. +# +# A field of a structure is described by the name of the field followed +# by a type. The currently implemented types are: +# nodeptr - a pointer to a node +# nodelist - a pointer to a list of nodes +# string - a pointer to a nul terminated string +# int - an integer +# other - any type that can be copied by assignment +# temp - a field that doesn't have to be copied when the node is copied +# The last two types should be followed by the text of a C declaration for +# the field. + +NSEMI nbinary # two commands separated by a semicolon + type int + ch1 nodeptr # the first child + ch2 nodeptr # the second child + +NCMD ncmd # a simple command + type int + args nodeptr # the arguments + redirect nodeptr # list of file redirections + +NPIPE npipe # a pipeline + type int + backgnd int # set to run pipeline in background + cmdlist nodelist # the commands in the pipeline + +NREDIR nredir # redirection (of a compex command) + type int + n nodeptr # the command + redirect nodeptr # list of file redirections + +NBACKGND nredir # run command in background +NSUBSHELL nredir # run command in a subshell + +NAND nbinary # the && operator +NOR nbinary # the || operator + +NIF nif # the if statement. Elif clauses are handled + type int # using multiple if nodes. + test nodeptr # if test + ifpart nodeptr # then ifpart + elsepart nodeptr # else elsepart + +NWHILE nbinary # the while statement. First child is the test +NUNTIL nbinary # the until statement + +NFOR nfor # the for statement + type int + args nodeptr # for var in args + body nodeptr # do body; done + var string # the for variable + +NCASE ncase # a case statement + type int + expr nodeptr # the word to switch on + cases nodeptr # the list of cases (NCLIST nodes) + +NCLIST nclist # a case ending with ;; + type int + next nodeptr # the next case in list + pattern nodeptr # list of patterns for this case + body nodeptr # code to execute for this case + +NCLISTFALLTHRU nclist # a case ending with ;& + +NDEFUN narg # define a function. The "next" field contains + # the body of the function. + +NARG narg # represents a word + type int + next nodeptr # next word in list + text string # the text of the word + backquote nodelist # list of commands in back quotes + +NTO nfile # fd> fname +NFROM nfile # fd< fname +NFROMTO nfile # fd<> fname +NAPPEND nfile # fd>> fname +NCLOBBER nfile # fd>| fname + type int + fd int # file descriptor being redirected + next nodeptr # next redirection in list + fname nodeptr # file name, in a NARG node + expfname temp char *expfname # actual file name + +NTOFD ndup # fd<&dupfd +NFROMFD ndup # fd>&dupfd + type int + fd int # file descriptor being redirected + next nodeptr # next redirection in list + dupfd int # file descriptor to duplicate + vname nodeptr # file name if fd>&$var + + +NHERE nhere # fd<<\! +NXHERE nhere # fd<<! + type int + fd int # file descriptor being redirected + next nodeptr # next redirection in list + doc nodeptr # input to command (NARG node) + expdoc temp const char *expdoc # actual document (for NXHERE) + +NNOT nnot # ! command (actually pipeline) + type int + com nodeptr diff --git a/bin/1sh/options.c b/bin/1sh/options.c new file mode 100644 index 00000000..a1bb57ca --- /dev/null +++ b/bin/1sh/options.c @@ -0,0 +1,594 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)options.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/options.c 326025 2017-11-20 19:49:47Z pfg $ */ + +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> + +#include "shell.h" +#define DEFINE_OPTIONS +#include "options.h" +#undef DEFINE_OPTIONS +#include "nodes.h" /* for other header files */ +#include "eval.h" +#include "jobs.h" +#include "input.h" +#include "output.h" +#include "trap.h" +#include "var.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "builtins.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + +char *arg0; /* value of $0 */ +struct shparam shellparam; /* current positional parameters */ +char **argptr; /* argument list for builtin commands */ +char *shoptarg; /* set by nextopt (like getopt) */ +char *nextopt_optptr; /* used by nextopt */ + +char *minusc; /* argument to -c option */ + + +static void options(int); +static void minus_o(char *, int); +static void setoption(int, int); +static void setoptionbyindex(int, int); +static void setparam(int, char **); +static int getopts(char *, char *, char **, char ***, char **); + + +/* + * Process the shell command line arguments. + */ + +void +procargs(int argc, char **argv) +{ + int i; + char *scriptname; + + argptr = argv; + if (argc > 0) + argptr++; + for (i = 0; i < NOPTS; i++) + optval[i] = 2; + privileged = (getuid() != geteuid() || getgid() != getegid()); + options(1); + if (*argptr == NULL && minusc == NULL) + sflag = 1; + if (iflag != 0 && sflag == 1 && isatty(0) && isatty(1)) { + iflag = 1; + if (Eflag == 2) + Eflag = 1; + } + if (mflag == 2) + mflag = iflag; + for (i = 0; i < NOPTS; i++) + if (optval[i] == 2) + optval[i] = 0; + arg0 = argv[0]; + if (sflag == 0 && minusc == NULL) { + scriptname = *argptr++; + setinputfile(scriptname, 0); + commandname = arg0 = scriptname; + } + /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */ + if (argptr && minusc && *argptr) + arg0 = *argptr++; + + shellparam.p = argptr; + shellparam.reset = 1; + /* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */ + while (*argptr) { + shellparam.nparam++; + argptr++; + } + optschanged(); +} + + +void +optschanged(void) +{ + setinteractive(); +#ifndef NO_HISTORY + histedit(); +#endif + setjobctl(mflag); +} + +/* + * Process shell options. The global variable argptr contains a pointer + * to the argument list; we advance it past the options. + * If cmdline is true, process the shell's argv; otherwise, process arguments + * to the set special builtin. + */ + +static void +options(int cmdline) +{ + char *kp, *p; + int val; + int c; + + if (cmdline) + minusc = NULL; + while ((p = *argptr) != NULL) { + argptr++; + if ((c = *p++) == '-') { + val = 1; + /* A "-" or "--" terminates options */ + if (p[0] == '\0') + goto end_options1; + if (p[0] == '-' && p[1] == '\0') + goto end_options2; + /** + * For the benefit of `#!' lines in shell scripts, + * treat a string of '-- *#.*' the same as '--'. + * This is needed so that a script starting with: + * #!/bin/sh -- # -*- perl -*- + * will continue to work after a change is made to + * kern/imgact_shell.c to NOT token-ize the options + * specified on a '#!' line. A bit of a kludge, + * but that trick is recommended in documentation + * for some scripting languages, and we might as + * well continue to support it. + */ + if (p[0] == '-') { + kp = p + 1; + while (*kp == ' ' || *kp == '\t') + kp++; + if (*kp == '#' || *kp == '\0') + goto end_options2; + } + } else if (c == '+') { + val = 0; + } else { + argptr--; + break; + } + while ((c = *p++) != '\0') { + if (c == 'c' && cmdline) { + char *q; + + q = *argptr++; + if (q == NULL || minusc != NULL) + error("Bad -c option"); + minusc = q; + } else if (c == 'o') { + minus_o(*argptr, val); + if (*argptr) + argptr++; + } else + setoption(c, val); + } + } + return; + + /* When processing `set', a single "-" means turn off -x and -v */ +end_options1: + if (!cmdline) { + xflag = vflag = 0; + return; + } + + /* + * When processing `set', a "--" means the remaining arguments + * replace the positional parameters in the active shell. If + * there are no remaining options, then all the positional + * parameters are cleared (equivalent to doing ``shift $#''). + */ +end_options2: + if (!cmdline) { + if (*argptr == NULL) + setparam(0, argptr); + return; + } + + /* + * At this point we are processing options given to 'sh' on a command + * line. If an end-of-options marker ("-" or "--") is followed by an + * arg of "#", then skip over all remaining arguments. Some scripting + * languages (e.g.: perl) document that /bin/sh will implement this + * behavior, and they recommend that users take advantage of it to + * solve certain issues that can come up when writing a perl script. + * Yes, this feature is in /bin/sh to help users write perl scripts. + */ + p = *argptr; + if (p != NULL && p[0] == '#' && p[1] == '\0') { + while (*argptr != NULL) + argptr++; + /* We need to keep the final argument */ + argptr--; + } +} + +static void +minus_o(char *name, int val) +{ + int i; + const unsigned char *on; + size_t len; + + if (name == NULL) { + if (val) { + /* "Pretty" output. */ + out1str("Current option settings\n"); + for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1) + out1fmt("%-16.*s%s\n", *on, on + 1, + optval[i] ? "on" : "off"); + } else { + /* Output suitable for re-input to shell. */ + for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1) + out1fmt("%s %co %.*s%s", + i % 6 == 0 ? "set" : "", + optval[i] ? '-' : '+', + *on, on + 1, + i % 6 == 5 || i == NOPTS - 1 ? "\n" : ""); + } + } else { + len = strlen(name); + for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1) + if (*on == len && memcmp(on + 1, name, len) == 0) { + setoptionbyindex(i, val); + return; + } + error("Illegal option -o %s", name); + } +} + + +static void +setoptionbyindex(int idx, int val) +{ + if (&optval[idx] == &privileged && !val && privileged) { + if (setgid(getgid()) == -1) + error("setgid"); + if (setuid(getuid()) == -1) + error("setuid"); + } + optval[idx] = val; + if (val) { + /* #%$ hack for ksh semantics */ + if (&optval[idx] == &Vflag) + Eflag = 0; + else if (&optval[idx] == &Eflag) + Vflag = 0; + } +} + +static void +setoption(int flag, int val) +{ + int i; + + for (i = 0; i < NSHORTOPTS; i++) + if (optletter[i] == flag) { + setoptionbyindex(i, val); + return; + } + error("Illegal option -%c", flag); +} + + +/* + * Set the shell parameters. + */ + +static void +setparam(int argc, char **argv) +{ + char **newparam; + char **ap; + + ap = newparam = ckmalloc((argc + 1) * sizeof *ap); + while (*argv) { + *ap++ = savestr(*argv++); + } + *ap = NULL; + freeparam(&shellparam); + shellparam.malloc = 1; + shellparam.nparam = argc; + shellparam.p = newparam; + shellparam.optp = NULL; + shellparam.reset = 1; + shellparam.optnext = NULL; +} + + +/* + * Free the list of positional parameters. + */ + +void +freeparam(struct shparam *param) +{ + char **ap; + + if (param->malloc) { + for (ap = param->p ; *ap ; ap++) + ckfree(*ap); + ckfree(param->p); + } + if (param->optp) { + for (ap = param->optp ; *ap ; ap++) + ckfree(*ap); + ckfree(param->optp); + } +} + + + +/* + * The shift builtin command. + */ + +int +shiftcmd(int argc, char **argv) +{ + int i, n; + + n = 1; + if (argc > 1) + n = number(argv[1]); + if (n > shellparam.nparam) + return 1; + INTOFF; + shellparam.nparam -= n; + if (shellparam.malloc) + for (i = 0; i < n; i++) + ckfree(shellparam.p[i]); + memmove(shellparam.p, shellparam.p + n, + (shellparam.nparam + 1) * sizeof(shellparam.p[0])); + shellparam.reset = 1; + INTON; + return 0; +} + + + +/* + * The set builtin command. + */ + +int +setcmd(int argc, char **argv) +{ + if (argc == 1) + return showvarscmd(argc, argv); + INTOFF; + options(0); + optschanged(); + if (*argptr != NULL) { + setparam(argc - (argptr - argv), argptr); + } + INTON; + return 0; +} + + +void +getoptsreset(const char *value) +{ + while (*value == '0') + value++; + if (strcmp(value, "1") == 0) + shellparam.reset = 1; +} + +/* + * The getopts builtin. Shellparam.optnext points to the next argument + * to be processed. Shellparam.optptr points to the next character to + * be processed in the current argument. If shellparam.optnext is NULL, + * then it's the first time getopts has been called. + */ + +int +getoptscmd(int argc, char **argv) +{ + char **optbase = NULL, **ap; + int i; + + if (argc < 3) + error("usage: getopts optstring var [arg]"); + + if (shellparam.reset == 1) { + INTOFF; + if (shellparam.optp) { + for (ap = shellparam.optp ; *ap ; ap++) + ckfree(*ap); + ckfree(shellparam.optp); + shellparam.optp = NULL; + } + if (argc > 3) { + shellparam.optp = ckmalloc((argc - 2) * sizeof *ap); + memset(shellparam.optp, '\0', (argc - 2) * sizeof *ap); + for (i = 0; i < argc - 3; i++) + shellparam.optp[i] = savestr(argv[i + 3]); + } + INTON; + optbase = argc == 3 ? shellparam.p : shellparam.optp; + shellparam.optnext = optbase; + shellparam.optptr = NULL; + shellparam.reset = 0; + } else + optbase = shellparam.optp ? shellparam.optp : shellparam.p; + + return getopts(argv[1], argv[2], optbase, &shellparam.optnext, + &shellparam.optptr); +} + +static int +getopts(char *optstr, char *optvar, char **optfirst, char ***optnext, + char **optptr) +{ + char *p, *q; + char c = '?'; + int done = 0; + int ind = 0; + int err = 0; + char s[10]; + const char *newoptarg = NULL; + + if ((p = *optptr) == NULL || *p == '\0') { + /* Current word is done, advance */ + if (*optnext == NULL) + return 1; + p = **optnext; + if (p == NULL || *p != '-' || *++p == '\0') { +atend: + ind = *optnext - optfirst + 1; + *optnext = NULL; + p = NULL; + done = 1; + goto out; + } + (*optnext)++; + if (p[0] == '-' && p[1] == '\0') /* check for "--" */ + goto atend; + } + + c = *p++; + for (q = optstr; *q != c; ) { + if (*q == '\0') { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + newoptarg = s; + } + else + out2fmt_flush("Illegal option -%c\n", c); + c = '?'; + goto out; + } + if (*++q == ':') + q++; + } + + if (*++q == ':') { + if (*p == '\0' && (p = **optnext) == NULL) { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + newoptarg = s; + c = ':'; + } + else { + out2fmt_flush("No arg for -%c option\n", c); + c = '?'; + } + goto out; + } + + if (p == **optnext) + (*optnext)++; + newoptarg = p; + p = NULL; + } + +out: + if (*optnext != NULL) + ind = *optnext - optfirst + 1; + *optptr = p; + if (newoptarg != NULL) + err |= setvarsafe("OPTARG", newoptarg, 0); + else { + INTOFF; + err |= unsetvar("OPTARG"); + INTON; + } + fmtstr(s, sizeof(s), "%d", ind); + err |= setvarsafe("OPTIND", s, VNOFUNC); + s[0] = c; + s[1] = '\0'; + err |= setvarsafe(optvar, s, 0); + if (err) { + *optnext = NULL; + *optptr = NULL; + flushall(); + exraise(EXERROR); + } + return done; +} + +/* + * Standard option processing (a la getopt) for builtin routines. The + * only argument that is passed to nextopt is the option string; the + * other arguments are unnecessary. It returns the option, or '\0' on + * end of input. + */ + +int +nextopt(const char *optstring) +{ + char *p; + const char *q; + char c; + + if ((p = nextopt_optptr) == NULL || *p == '\0') { + p = *argptr; + if (p == NULL || *p != '-' || *++p == '\0') + return '\0'; + argptr++; + if (p[0] == '-' && p[1] == '\0') /* check for "--" */ + return '\0'; + } + c = *p++; + for (q = optstring ; *q != c ; ) { + if (*q == '\0') + error("Illegal option -%c", c); + if (*++q == ':') + q++; + } + if (*++q == ':') { + if (*p == '\0' && (p = *argptr++) == NULL) + error("No arg for -%c option", c); + shoptarg = p; + p = NULL; + } + nextopt_optptr = p; + return c; +} diff --git a/bin/1sh/options.h b/bin/1sh/options.h new file mode 100644 index 00000000..007ea312 --- /dev/null +++ b/bin/1sh/options.h @@ -0,0 +1,117 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)options.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/options.h 345487 2019-03-24 22:10:26Z jilles $ + */ + +struct shparam { + int nparam; /* # of positional parameters (without $0) */ + unsigned char malloc; /* if parameter list dynamically allocated */ + unsigned char reset; /* if getopts has been reset */ + char **p; /* parameter list */ + char **optp; /* parameter list for getopts */ + char **optnext; /* next parameter to be processed by getopts */ + char *optptr; /* used by getopts */ +}; + + + +#define eflag optval[0] +#define fflag optval[1] +#define Iflag optval[2] +#define iflag optval[3] +#define mflag optval[4] +#define nflag optval[5] +#define sflag optval[6] +#define xflag optval[7] +#define vflag optval[8] +#define Vflag optval[9] +#define Eflag optval[10] +#define Cflag optval[11] +#define aflag optval[12] +#define bflag optval[13] +#define uflag optval[14] +#define privileged optval[15] +#define Tflag optval[16] +#define Pflag optval[17] +#define hflag optval[18] +#define nologflag optval[19] +#define pipefailflag optval[20] + +#define NSHORTOPTS 19 +#define NOPTS 21 + +extern char optval[NOPTS]; +extern const char optletter[NSHORTOPTS]; +#ifdef DEFINE_OPTIONS +char optval[NOPTS]; +const char optletter[NSHORTOPTS] = "efIimnsxvVECabupTPh"; +static const unsigned char optname[] = + "\007errexit" + "\006noglob" + "\011ignoreeof" + "\013interactive" + "\007monitor" + "\006noexec" + "\005stdin" + "\006xtrace" + "\007verbose" + "\002vi" + "\005emacs" + "\011noclobber" + "\011allexport" + "\006notify" + "\007nounset" + "\012privileged" + "\012trapsasync" + "\010physical" + "\010trackall" + "\005nolog" + "\010pipefail" +; +#endif + + +extern char *minusc; /* argument to -c option */ +extern char *arg0; /* $0 */ +extern struct shparam shellparam; /* $@ */ +extern char **argptr; /* argument list for builtin commands */ +extern char *shoptarg; /* set by nextopt */ +extern char *nextopt_optptr; /* used by nextopt */ + +void procargs(int, char **); +void optschanged(void); +void freeparam(struct shparam *); +int nextopt(const char *); +void getoptsreset(const char *); diff --git a/bin/1sh/output.c b/bin/1sh/output.c new file mode 100644 index 00000000..af93ec6d --- /dev/null +++ b/bin/1sh/output.c @@ -0,0 +1,376 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)output.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/output.c 345613 2019-03-27 21:53:44Z jilles $ */ + +/* + * Shell output routines. We use our own output routines because: + * When a builtin command is interrupted we have to discard + * any pending output. + * When a builtin command appears in back quotes, we want to + * save the output of the command in a region obtained + * via malloc, rather than doing a fork and reading the + * output of the command via a pipe. + */ + +#include <stdio.h> /* defines BUFSIZ */ +#include <string.h> +#include <stdarg.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <wchar.h> +#include <wctype.h> + +#include "shell.h" +#include "syntax.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "var.h" + + +#define OUTBUFSIZ BUFSIZ +#define MEM_OUT -2 /* output to dynamically allocated memory */ +#define OUTPUT_ERR 01 /* error occurred on output */ + +static int doformat_wr(void *, const char *, int); + +struct output output = {NULL, NULL, NULL, OUTBUFSIZ, 1, 0}; +struct output errout = {NULL, NULL, NULL, 256, 2, 0}; +struct output memout = {NULL, NULL, NULL, 64, MEM_OUT, 0}; +struct output *out1 = &output; +struct output *out2 = &errout; + +void +outcslow(int c, struct output *file) +{ + outc(c, file); +} + +void +out1str(const char *p) +{ + outstr(p, out1); +} + +void +out1qstr(const char *p) +{ + outqstr(p, out1); +} + +void +out2str(const char *p) +{ + outstr(p, out2); +} + +void +out2qstr(const char *p) +{ + outqstr(p, out2); +} + +void +outstr(const char *p, struct output *file) +{ + outbin(p, strlen(p), file); +} + +static void +byteseq(int ch, struct output *file) +{ + char seq[4]; + + seq[0] = '\\'; + seq[1] = (ch >> 6 & 0x3) + '0'; + seq[2] = (ch >> 3 & 0x7) + '0'; + seq[3] = (ch & 0x7) + '0'; + outbin(seq, 4, file); +} + +static void +outdqstr(const char *p, struct output *file) +{ + const char *end; + mbstate_t mbs; + size_t clen; + wchar_t wc; + + memset(&mbs, '\0', sizeof(mbs)); + end = p + strlen(p); + outstr("$'", file); + while ((clen = mbrtowc(&wc, p, end - p + 1, &mbs)) != 0) { + if (clen == (size_t)-2) { + while (p < end) + byteseq(*p++, file); + break; + } + if (clen == (size_t)-1) { + memset(&mbs, '\0', sizeof(mbs)); + byteseq(*p++, file); + continue; + } + if (wc == L'\n') + outcslow('\n', file), p++; + else if (wc == L'\r') + outstr("\\r", file), p++; + else if (wc == L'\t') + outstr("\\t", file), p++; + else if (!iswprint(wc)) { + for (; clen > 0; clen--) + byteseq(*p++, file); + } else { + if (wc == L'\'' || wc == L'\\') + outcslow('\\', file); + outbin(p, clen, file); + p += clen; + } + } + outcslow('\'', file); +} + +/* Like outstr(), but quote for re-input into the shell. */ +void +outqstr(const char *p, struct output *file) +{ + int i; + + if (p[0] == '\0') { + outstr("''", file); + return; + } + for (i = 0; p[i] != '\0'; i++) { + if ((p[i] > '\0' && p[i] < ' ' && p[i] != '\n') || + (p[i] & 0x80) != 0 || p[i] == '\'') { + outdqstr(p, file); + return; + } + } + + if (p[strcspn(p, "|&;<>()$`\\\" \n*?[~#=")] == '\0' || + strcmp(p, "[") == 0) { + outstr(p, file); + return; + } + + outcslow('\'', file); + outstr(p, file); + outcslow('\'', file); +} + +void +outbin(const void *data, size_t len, struct output *file) +{ + const char *p; + + p = data; + while (len-- > 0) + outc(*p++, file); +} + +void +emptyoutbuf(struct output *dest) +{ + int offset, newsize; + + if (dest->buf == NULL) { + INTOFF; + dest->buf = ckmalloc(dest->bufsize); + dest->nextc = dest->buf; + dest->bufend = dest->buf + dest->bufsize; + INTON; + } else if (dest->fd == MEM_OUT) { + offset = dest->nextc - dest->buf; + newsize = dest->bufsize << 1; + INTOFF; + dest->buf = ckrealloc(dest->buf, newsize); + dest->bufsize = newsize; + dest->bufend = dest->buf + newsize; + dest->nextc = dest->buf + offset; + INTON; + } else { + flushout(dest); + } +} + + +void +flushall(void) +{ + flushout(&output); + flushout(&errout); +} + + +void +flushout(struct output *dest) +{ + + if (dest->buf == NULL || dest->nextc == dest->buf || dest->fd < 0) + return; + if (xwrite(dest->fd, dest->buf, dest->nextc - dest->buf) < 0) + dest->flags |= OUTPUT_ERR; + dest->nextc = dest->buf; +} + + +void +freestdout(void) +{ + output.nextc = output.buf; +} + + +int +outiserror(struct output *file) +{ + return (file->flags & OUTPUT_ERR); +} + + +void +outclearerror(struct output *file) +{ + file->flags &= ~OUTPUT_ERR; +} + + +void +outfmt(struct output *file, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(file, fmt, ap); + va_end(ap); +} + + +void +out1fmt(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(out1, fmt, ap); + va_end(ap); +} + +void +out2fmt_flush(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(out2, fmt, ap); + va_end(ap); + flushout(out2); +} + +void +fmtstr(char *outbuf, int length, const char *fmt, ...) +{ + va_list ap; + + INTOFF; + va_start(ap, fmt); + vsnprintf(outbuf, length, fmt, ap); + va_end(ap); + INTON; +} + +static int +doformat_wr(void *cookie, const char *buf, int len) +{ + struct output *o; + + o = (struct output *)cookie; + outbin(buf, len, o); + + return (len); +} + +void +doformat(struct output *dest, const char *f, va_list ap) +{ + FILE *fp; + + if ((fp = fwopen(dest, doformat_wr)) != NULL) { + vfprintf(fp, f, ap); + fclose(fp); + } +} + +FILE * +out1fp(void) +{ + return fwopen(out1, doformat_wr); +} + +/* + * Version of write which resumes after a signal is caught. + */ + +int +xwrite(int fd, const char *buf, int nbytes) +{ + int ntry; + int i; + int n; + + n = nbytes; + ntry = 0; + for (;;) { + i = write(fd, buf, n); + if (i > 0) { + if ((n -= i) <= 0) + return nbytes; + buf += i; + ntry = 0; + } else if (i == 0) { + if (++ntry > 10) + return nbytes - n; + } else if (errno != EINTR) { + return -1; + } + } +} diff --git a/bin/1sh/output.h b/bin/1sh/output.h new file mode 100644 index 00000000..8d5ded6f --- /dev/null +++ b/bin/1sh/output.h @@ -0,0 +1,87 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)output.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/output.h 345613 2019-03-27 21:53:44Z jilles $ + */ + +#ifndef OUTPUT_INCL + +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> + +struct output { + char *nextc; + char *bufend; + char *buf; + int bufsize; + short fd; + short flags; +}; + +extern struct output output; /* to fd 1 */ +extern struct output errout; /* to fd 2 */ +extern struct output memout; +extern struct output *out1; /* &memout if backquote, otherwise &output */ +extern struct output *out2; /* &memout if backquote with 2>&1, otherwise + &errout */ + +void outcslow(int, struct output *); +void out1str(const char *); +void out1qstr(const char *); +void out2str(const char *); +void out2qstr(const char *); +void outstr(const char *, struct output *); +void outqstr(const char *, struct output *); +void outbin(const void *, size_t, struct output *); +void emptyoutbuf(struct output *); +void flushall(void); +void flushout(struct output *); +void freestdout(void); +int outiserror(struct output *); +void outclearerror(struct output *); +void outfmt(struct output *, const char *, ...) __attribute__((format(printf, 2, 3))); +void out1fmt(const char *, ...) __attribute__((format(printf, 1, 2))); +void out2fmt_flush(const char *, ...) __attribute__((format(printf, 1, 2))); +void fmtstr(char *, int, const char *, ...) __attribute__((format(printf, 3, 4))); +void doformat(struct output *, const char *, va_list) __attribute__((format(printf, 2, 0))); +FILE *out1fp(void); +int xwrite(int, const char *, int); + +#define outc(c, file) ((file)->nextc == (file)->bufend ? (emptyoutbuf(file), *(file)->nextc++ = (c)) : (*(file)->nextc++ = (c))) +#define out1c(c) outc(c, out1); +#define out2c(c) outcslow(c, out2); + +#define OUTPUT_INCL +#endif diff --git a/bin/1sh/parser.c b/bin/1sh/parser.c new file mode 100644 index 00000000..b2f761c8 --- /dev/null +++ b/bin/1sh/parser.c @@ -0,0 +1,2182 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)parser.c 8.7 (Berkeley) 5/16/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/parser.c 334008 2018-05-21 21:52:48Z jilles $ */ + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> + +#include "shell.h" +#include "parser.h" +#include "nodes.h" +#include "expand.h" /* defines rmescapes() */ +#include "syntax.h" +#include "options.h" +#include "input.h" +#include "output.h" +#include "var.h" +#include "error.h" +#include "memalloc.h" +#include "mystring.h" +#include "alias.h" +#include "show.h" +#include "eval.h" +#include "exec.h" /* to check for special builtins */ +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + +/* + * Shell command parser. + */ + +#define PROMPTLEN 128 + +/* values of checkkwd variable */ +#define CHKALIAS 0x1 +#define CHKKWD 0x2 +#define CHKNL 0x4 + +/* values returned by readtoken */ +#include "token.h" + + + +struct heredoc { + struct heredoc *next; /* next here document in list */ + union node *here; /* redirection node */ + char *eofmark; /* string indicating end of input */ + int striptabs; /* if set, strip leading tabs */ +}; + +struct parser_temp { + struct parser_temp *next; + void *data; +}; + + +static struct heredoc *heredoclist; /* list of here documents to read */ +static int doprompt; /* if set, prompt the user */ +static int needprompt; /* true if interactive and at start of line */ +static int lasttoken; /* last token read */ +static int tokpushback; /* last token pushed back */ +static char *wordtext; /* text of last word returned by readtoken */ +static int checkkwd; +static struct nodelist *backquotelist; +static union node *redirnode; +static struct heredoc *heredoc; +static int quoteflag; /* set if (part of) last token was quoted */ +static int startlinno; /* line # where last token started */ +static int funclinno; /* line # where the current function started */ +static struct parser_temp *parser_temp; + +#define NOEOFMARK ((const char *)&heredoclist) + + +static union node *list(int); +static union node *andor(void); +static union node *pipeline(void); +static union node *command(void); +static union node *simplecmd(union node **, union node *); +static union node *makename(void); +static union node *makebinary(int type, union node *n1, union node *n2); +static void parsefname(void); +static void parseheredoc(void); +static int peektoken(void); +static int readtoken(void); +static int xxreadtoken(void); +static int readtoken1(int, const char *, const char *, int); +static int noexpand(char *); +static void consumetoken(int); +static void synexpect(int) __attribute__((noreturn)); +static void synerror(const char *) __attribute__((noreturn)); +static void setprompt(int); +static char *expandprompt(const char *); +static int pgetc_linecont(void); + + +static void * +parser_temp_alloc(size_t len) +{ + struct parser_temp *t; + + INTOFF; + t = ckmalloc(sizeof(*t)); + t->data = NULL; + t->next = parser_temp; + parser_temp = t; + t->data = ckmalloc(len); + INTON; + return t->data; +} + + +static void * +parser_temp_realloc(void *ptr, size_t len) +{ + struct parser_temp *t; + + INTOFF; + t = parser_temp; + if (ptr != t->data) + error("bug: parser_temp_realloc misused"); + t->data = ckrealloc(t->data, len); + INTON; + return t->data; +} + + +static void +parser_temp_free_upto(void *ptr) +{ + struct parser_temp *t; + int done = 0; + + INTOFF; + while (parser_temp != NULL && !done) { + t = parser_temp; + parser_temp = t->next; + done = t->data == ptr; + ckfree(t->data); + ckfree(t); + } + INTON; + if (!done) + error("bug: parser_temp_free_upto misused"); +} + + +static void +parser_temp_free_all(void) +{ + struct parser_temp *t; + + INTOFF; + while (parser_temp != NULL) { + t = parser_temp; + parser_temp = t->next; + ckfree(t->data); + ckfree(t); + } + INTON; +} + + +/* + * Read and parse a command. Returns NEOF on end of file. (NULL is a + * valid parse tree indicating a blank line.) + */ + +union node * +parsecmd(int interact) +{ + int t; + + /* This assumes the parser is not re-entered, + * which could happen if we add command substitution on PS1/PS2. + */ + parser_temp_free_all(); + heredoclist = NULL; + + tokpushback = 0; + checkkwd = 0; + doprompt = interact; + if (doprompt) + setprompt(1); + else + setprompt(0); + needprompt = 0; + t = readtoken(); + if (t == TEOF) + return NEOF; + if (t == TNL) + return NULL; + tokpushback++; + return list(1); +} + + +/* + * Read and parse words for wordexp. + * Returns a list of NARG nodes; NULL if there are no words. + */ +union node * +parsewordexp(void) +{ + union node *n, *first = NULL, **pnext; + int t; + + /* This assumes the parser is not re-entered, + * which could happen if we add command substitution on PS1/PS2. + */ + parser_temp_free_all(); + heredoclist = NULL; + + tokpushback = 0; + checkkwd = 0; + doprompt = 0; + setprompt(0); + needprompt = 0; + pnext = &first; + while ((t = readtoken()) != TEOF) { + if (t != TWORD) + synexpect(TWORD); + n = makename(); + *pnext = n; + pnext = &n->narg.next; + } + return first; +} + + +static union node * +list(int nlflag) +{ + union node *ntop, *n1, *n2, *n3; + int tok; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (!nlflag && tokendlist[peektoken()]) + return NULL; + ntop = n1 = NULL; + for (;;) { + n2 = andor(); + tok = readtoken(); + if (tok == TBACKGND) { + if (n2 != NULL && n2->type == NPIPE) { + n2->npipe.backgnd = 1; + } else if (n2 != NULL && n2->type == NREDIR) { + n2->type = NBACKGND; + } else { + n3 = (union node *)stalloc(sizeof (struct nredir)); + n3->type = NBACKGND; + n3->nredir.n = n2; + n3->nredir.redirect = NULL; + n2 = n3; + } + } + if (ntop == NULL) + ntop = n2; + else if (n1 == NULL) { + n1 = makebinary(NSEMI, ntop, n2); + ntop = n1; + } + else { + n3 = makebinary(NSEMI, n1->nbinary.ch2, n2); + n1->nbinary.ch2 = n3; + n1 = n3; + } + switch (tok) { + case TBACKGND: + case TSEMI: + tok = readtoken(); + /* FALLTHROUGH */ + case TNL: + if (tok == TNL) { + parseheredoc(); + if (nlflag) + return ntop; + } else if (tok == TEOF && nlflag) { + parseheredoc(); + return ntop; + } else { + tokpushback++; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (!nlflag && tokendlist[peektoken()]) + return ntop; + break; + case TEOF: + if (heredoclist) + parseheredoc(); + else + pungetc(); /* push back EOF on input */ + return ntop; + default: + if (nlflag) + synexpect(-1); + tokpushback++; + return ntop; + } + } +} + + + +static union node * +andor(void) +{ + union node *n; + int t; + + n = pipeline(); + for (;;) { + if ((t = readtoken()) == TAND) { + t = NAND; + } else if (t == TOR) { + t = NOR; + } else { + tokpushback++; + return n; + } + n = makebinary(t, n, pipeline()); + } +} + + + +static union node * +pipeline(void) +{ + union node *n1, *n2, *pipenode; + struct nodelist *lp, *prev; + int negate, t; + + negate = 0; + checkkwd = CHKNL | CHKKWD | CHKALIAS; + TRACE(("pipeline: entered\n")); + while (readtoken() == TNOT) + negate = !negate; + tokpushback++; + n1 = command(); + if (readtoken() == TPIPE) { + pipenode = (union node *)stalloc(sizeof (struct npipe)); + pipenode->type = NPIPE; + pipenode->npipe.backgnd = 0; + lp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + pipenode->npipe.cmdlist = lp; + lp->n = n1; + do { + prev = lp; + lp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + checkkwd = CHKNL | CHKKWD | CHKALIAS; + t = readtoken(); + tokpushback++; + if (t == TNOT) + lp->n = pipeline(); + else + lp->n = command(); + prev->next = lp; + } while (readtoken() == TPIPE); + lp->next = NULL; + n1 = pipenode; + } + tokpushback++; + if (negate) { + n2 = (union node *)stalloc(sizeof (struct nnot)); + n2->type = NNOT; + n2->nnot.com = n1; + return n2; + } else + return n1; +} + + + +static union node * +command(void) +{ + union node *n1, *n2; + union node *ap, **app; + union node *cp, **cpp; + union node *redir, **rpp; + int t; + int is_subshell; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + is_subshell = 0; + redir = NULL; + n1 = NULL; + rpp = &redir; + + /* Check for redirection which may precede command */ + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); + } + tokpushback++; + + switch (readtoken()) { + case TIF: + n1 = (union node *)stalloc(sizeof (struct nif)); + n1->type = NIF; + if ((n1->nif.test = list(0)) == NULL) + synexpect(-1); + consumetoken(TTHEN); + n1->nif.ifpart = list(0); + n2 = n1; + while (readtoken() == TELIF) { + n2->nif.elsepart = (union node *)stalloc(sizeof (struct nif)); + n2 = n2->nif.elsepart; + n2->type = NIF; + if ((n2->nif.test = list(0)) == NULL) + synexpect(-1); + consumetoken(TTHEN); + n2->nif.ifpart = list(0); + } + if (lasttoken == TELSE) + n2->nif.elsepart = list(0); + else { + n2->nif.elsepart = NULL; + tokpushback++; + } + consumetoken(TFI); + checkkwd = CHKKWD | CHKALIAS; + break; + case TWHILE: + case TUNTIL: + t = lasttoken; + if ((n1 = list(0)) == NULL) + synexpect(-1); + consumetoken(TDO); + n1 = makebinary((t == TWHILE)? NWHILE : NUNTIL, n1, list(0)); + consumetoken(TDONE); + checkkwd = CHKKWD | CHKALIAS; + break; + case TFOR: + if (readtoken() != TWORD || quoteflag || ! goodname(wordtext)) + synerror("Bad for loop variable"); + n1 = (union node *)stalloc(sizeof (struct nfor)); + n1->type = NFOR; + n1->nfor.var = wordtext; + while (readtoken() == TNL) + ; + if (lasttoken == TWORD && ! quoteflag && equal(wordtext, "in")) { + app = ≈ + while (readtoken() == TWORD) { + n2 = makename(); + *app = n2; + app = &n2->narg.next; + } + *app = NULL; + n1->nfor.args = ap; + if (lasttoken != TNL && lasttoken != TSEMI) + synexpect(-1); + } else { + static char argvars[5] = { + CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' + }; + n2 = (union node *)stalloc(sizeof (struct narg)); + n2->type = NARG; + n2->narg.text = argvars; + n2->narg.backquote = NULL; + n2->narg.next = NULL; + n1->nfor.args = n2; + /* + * Newline or semicolon here is optional (but note + * that the original Bourne shell only allowed NL). + */ + if (lasttoken != TNL && lasttoken != TSEMI) + tokpushback++; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if ((t = readtoken()) == TDO) + t = TDONE; + else if (t == TBEGIN) + t = TEND; + else + synexpect(-1); + n1->nfor.body = list(0); + consumetoken(t); + checkkwd = CHKKWD | CHKALIAS; + break; + case TCASE: + n1 = (union node *)stalloc(sizeof (struct ncase)); + n1->type = NCASE; + consumetoken(TWORD); + n1->ncase.expr = makename(); + while (readtoken() == TNL); + if (lasttoken != TWORD || ! equal(wordtext, "in")) + synerror("expecting \"in\""); + cpp = &n1->ncase.cases; + checkkwd = CHKNL | CHKKWD, readtoken(); + while (lasttoken != TESAC) { + *cpp = cp = (union node *)stalloc(sizeof (struct nclist)); + cp->type = NCLIST; + app = &cp->nclist.pattern; + if (lasttoken == TLP) + readtoken(); + for (;;) { + *app = ap = makename(); + checkkwd = CHKNL | CHKKWD; + if (readtoken() != TPIPE) + break; + app = &ap->narg.next; + readtoken(); + } + ap->narg.next = NULL; + if (lasttoken != TRP) + synexpect(TRP); + cp->nclist.body = list(0); + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if ((t = readtoken()) != TESAC) { + if (t == TENDCASE) + ; + else if (t == TFALLTHRU) + cp->type = NCLISTFALLTHRU; + else + synexpect(TENDCASE); + checkkwd = CHKNL | CHKKWD, readtoken(); + } + cpp = &cp->nclist.next; + } + *cpp = NULL; + checkkwd = CHKKWD | CHKALIAS; + break; + case TLP: + n1 = (union node *)stalloc(sizeof (struct nredir)); + n1->type = NSUBSHELL; + n1->nredir.n = list(0); + n1->nredir.redirect = NULL; + consumetoken(TRP); + checkkwd = CHKKWD | CHKALIAS; + is_subshell = 1; + break; + case TBEGIN: + n1 = list(0); + consumetoken(TEND); + checkkwd = CHKKWD | CHKALIAS; + break; + /* A simple command must have at least one redirection or word. */ + case TBACKGND: + case TSEMI: + case TAND: + case TOR: + case TPIPE: + case TENDCASE: + case TFALLTHRU: + case TEOF: + case TNL: + case TRP: + if (!redir) + synexpect(-1); + case TWORD: + tokpushback++; + n1 = simplecmd(rpp, redir); + return n1; + default: + synexpect(-1); + } + + /* Now check for redirection which may follow command */ + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); + } + tokpushback++; + *rpp = NULL; + if (redir) { + if (!is_subshell) { + n2 = (union node *)stalloc(sizeof (struct nredir)); + n2->type = NREDIR; + n2->nredir.n = n1; + n1 = n2; + } + n1->nredir.redirect = redir; + } + + return n1; +} + + +static union node * +simplecmd(union node **rpp, union node *redir) +{ + union node *args, **app; + union node **orig_rpp = rpp; + union node *n = NULL; + int special; + int savecheckkwd; + + /* If we don't have any redirections already, then we must reset */ + /* rpp to be the address of the local redir variable. */ + if (redir == NULL) + rpp = &redir; + + args = NULL; + app = &args; + /* + * We save the incoming value, because we need this for shell + * functions. There can not be a redirect or an argument between + * the function name and the open parenthesis. + */ + orig_rpp = rpp; + + savecheckkwd = CHKALIAS; + + for (;;) { + checkkwd = savecheckkwd; + if (readtoken() == TWORD) { + n = makename(); + *app = n; + app = &n->narg.next; + if (savecheckkwd != 0 && !isassignment(wordtext)) + savecheckkwd = 0; + } else if (lasttoken == TREDIR) { + *rpp = n = redirnode; + rpp = &n->nfile.next; + parsefname(); /* read name of redirection file */ + } else if (lasttoken == TLP && app == &args->narg.next + && rpp == orig_rpp) { + /* We have a function */ + consumetoken(TRP); + funclinno = plinno; + /* + * - Require plain text. + * - Functions with '/' cannot be called. + * - Reject name=(). + * - Reject ksh extended glob patterns. + */ + if (!noexpand(n->narg.text) || quoteflag || + strchr(n->narg.text, '/') || + strchr("!%*+-=?@}~", + n->narg.text[strlen(n->narg.text) - 1])) + synerror("Bad function name"); + rmescapes(n->narg.text); + if (find_builtin(n->narg.text, &special) >= 0 && + special) + synerror("Cannot override a special builtin with a function"); + n->type = NDEFUN; + n->narg.next = command(); + funclinno = 0; + return n; + } else { + tokpushback++; + break; + } + } + *app = NULL; + *rpp = NULL; + n = (union node *)stalloc(sizeof (struct ncmd)); + n->type = NCMD; + n->ncmd.args = args; + n->ncmd.redirect = redir; + return n; +} + +static union node * +makename(void) +{ + union node *n; + + n = (union node *)stalloc(sizeof (struct narg)); + n->type = NARG; + n->narg.next = NULL; + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + return n; +} + +static union node * +makebinary(int type, union node *n1, union node *n2) +{ + union node *n; + + n = (union node *)stalloc(sizeof (struct nbinary)); + n->type = type; + n->nbinary.ch1 = n1; + n->nbinary.ch2 = n2; + return (n); +} + +void +forcealias(void) +{ + checkkwd |= CHKALIAS; +} + +void +fixredir(union node *n, const char *text, int err) +{ + TRACE(("Fix redir %s %d\n", text, err)); + if (!err) + n->ndup.vname = NULL; + + if (is_digit(text[0]) && text[1] == '\0') + n->ndup.dupfd = digit_val(text[0]); + else if (text[0] == '-' && text[1] == '\0') + n->ndup.dupfd = -1; + else { + + if (err) + synerror("Bad fd number"); + else + n->ndup.vname = makename(); + } +} + + +static void +parsefname(void) +{ + union node *n = redirnode; + + consumetoken(TWORD); + if (n->type == NHERE) { + struct heredoc *here = heredoc; + struct heredoc *p; + + if (quoteflag == 0) + n->type = NXHERE; + TRACE(("Here document %d\n", n->type)); + if (here->striptabs) { + while (*wordtext == '\t') + wordtext++; + } + if (! noexpand(wordtext)) + synerror("Illegal eof marker for << redirection"); + rmescapes(wordtext); + here->eofmark = wordtext; + here->next = NULL; + if (heredoclist == NULL) + heredoclist = here; + else { + for (p = heredoclist ; p->next ; p = p->next); + p->next = here; + } + } else if (n->type == NTOFD || n->type == NFROMFD) { + fixredir(n, wordtext, 0); + } else { + n->nfile.fname = makename(); + } +} + + +/* + * Input any here documents. + */ + +static void +parseheredoc(void) +{ + struct heredoc *here; + union node *n; + + while (heredoclist) { + here = heredoclist; + heredoclist = here->next; + if (needprompt) { + setprompt(2); + needprompt = 0; + } + readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX, + here->eofmark, here->striptabs); + n = makename(); + here->here->nhere.doc = n; + } +} + +static int +peektoken(void) +{ + int t; + + t = readtoken(); + tokpushback++; + return (t); +} + +static int +readtoken(void) +{ + int t; + struct alias *ap; +#ifdef DEBUG + int alreadyseen = tokpushback; +#endif + + top: + t = xxreadtoken(); + + /* + * eat newlines + */ + if (checkkwd & CHKNL) { + while (t == TNL) { + parseheredoc(); + t = xxreadtoken(); + } + } + + /* + * check for keywords and aliases + */ + if (t == TWORD && !quoteflag) + { + const char * const *pp; + + if (checkkwd & CHKKWD) + for (pp = parsekwd; *pp; pp++) { + if (**pp == *wordtext && equal(*pp, wordtext)) + { + lasttoken = t = pp - parsekwd + KWDOFFSET; + TRACE(("keyword %s recognized\n", tokname[t])); + goto out; + } + } + if (checkkwd & CHKALIAS && + (ap = lookupalias(wordtext, 1)) != NULL) { + pushstring(ap->val, strlen(ap->val), ap); + goto top; + } + } +out: + if (t != TNOT) + checkkwd = 0; + +#ifdef DEBUG + if (!alreadyseen) + TRACE(("token %s %s\n", tokname[t], t == TWORD ? wordtext : "")); + else + TRACE(("reread token %s %s\n", tokname[t], t == TWORD ? wordtext : "")); +#endif + return (t); +} + + +/* + * Read the next input token. + * If the token is a word, we set backquotelist to the list of cmds in + * backquotes. We set quoteflag to true if any part of the word was + * quoted. + * If the token is TREDIR, then we set redirnode to a structure containing + * the redirection. + * In all cases, the variable startlinno is set to the number of the line + * on which the token starts. + * + * [Change comment: here documents and internal procedures] + * [Readtoken shouldn't have any arguments. Perhaps we should make the + * word parsing code into a separate routine. In this case, readtoken + * doesn't need to have any internal procedures, but parseword does. + * We could also make parseoperator in essence the main routine, and + * have parseword (readtoken1?) handle both words and redirection.] + */ + +#define RETURN(token) return lasttoken = token + +static int +xxreadtoken(void) +{ + int c; + + if (tokpushback) { + tokpushback = 0; + return lasttoken; + } + if (needprompt) { + setprompt(2); + needprompt = 0; + } + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_macro(); + switch (c) { + case ' ': case '\t': + continue; + case '#': + while ((c = pgetc()) != '\n' && c != PEOF); + pungetc(); + continue; + case '\\': + if (pgetc() == '\n') { + startlinno = ++plinno; + if (doprompt) + setprompt(2); + else + setprompt(0); + continue; + } + pungetc(); + /* FALLTHROUGH */ + default: + return readtoken1(c, BASESYNTAX, (char *)NULL, 0); + case '\n': + plinno++; + needprompt = doprompt; + RETURN(TNL); + case PEOF: + RETURN(TEOF); + case '&': + if (pgetc_linecont() == '&') + RETURN(TAND); + pungetc(); + RETURN(TBACKGND); + case '|': + if (pgetc_linecont() == '|') + RETURN(TOR); + pungetc(); + RETURN(TPIPE); + case ';': + c = pgetc_linecont(); + if (c == ';') + RETURN(TENDCASE); + else if (c == '&') + RETURN(TFALLTHRU); + pungetc(); + RETURN(TSEMI); + case '(': + RETURN(TLP); + case ')': + RETURN(TRP); + } + } +#undef RETURN +} + + +#define MAXNEST_static 8 +struct tokenstate +{ + const char *syntax; /* *SYNTAX */ + int parenlevel; /* levels of parentheses in arithmetic */ + enum tokenstate_category + { + TSTATE_TOP, + TSTATE_VAR_OLD, /* ${var+-=?}, inherits dquotes */ + TSTATE_VAR_NEW, /* other ${var...}, own dquote state */ + TSTATE_ARITH + } category; +}; + + +/* + * Check to see whether we are at the end of the here document. When this + * is called, c is set to the first character of the next input line. If + * we are at the end of the here document, this routine sets the c to PEOF. + * The new value of c is returned. + */ + +static int +checkend(int c, const char *eofmark, int striptabs) +{ + if (striptabs) { + while (c == '\t') + c = pgetc(); + } + if (c == *eofmark) { + int c2; + const char *q; + + for (q = eofmark + 1; c2 = pgetc(), *q != '\0' && c2 == *q; q++) + ; + if ((c2 == PEOF || c2 == '\n') && *q == '\0') { + c = PEOF; + if (c2 == '\n') { + plinno++; + needprompt = doprompt; + } + } else { + pungetc(); + pushstring(eofmark + 1, q - (eofmark + 1), NULL); + } + } else if (c == '\n' && *eofmark == '\0') { + c = PEOF; + plinno++; + needprompt = doprompt; + } + return (c); +} + + +/* + * Parse a redirection operator. The variable "out" points to a string + * specifying the fd to be redirected. The variable "c" contains the + * first character of the redirection operator. + */ + +static void +parseredir(char *out, int c) +{ + char fd = *out; + union node *np; + + np = (union node *)stalloc(sizeof (struct nfile)); + if (c == '>') { + np->nfile.fd = 1; + c = pgetc_linecont(); + if (c == '>') + np->type = NAPPEND; + else if (c == '&') + np->type = NTOFD; + else if (c == '|') + np->type = NCLOBBER; + else { + np->type = NTO; + pungetc(); + } + } else { /* c == '<' */ + np->nfile.fd = 0; + c = pgetc_linecont(); + if (c == '<') { + if (sizeof (struct nfile) != sizeof (struct nhere)) { + np = (union node *)stalloc(sizeof (struct nhere)); + np->nfile.fd = 0; + } + np->type = NHERE; + heredoc = (struct heredoc *)stalloc(sizeof (struct heredoc)); + heredoc->here = np; + if ((c = pgetc_linecont()) == '-') { + heredoc->striptabs = 1; + } else { + heredoc->striptabs = 0; + pungetc(); + } + } else if (c == '&') + np->type = NFROMFD; + else if (c == '>') + np->type = NFROMTO; + else { + np->type = NFROM; + pungetc(); + } + } + if (fd != '\0') + np->nfile.fd = digit_val(fd); + redirnode = np; +} + +/* + * Called to parse command substitutions. + */ + +static char * +parsebackq(char *out, struct nodelist **pbqlist, + int oldstyle, int dblquote, int quoted) +{ + struct nodelist **nlpp; + union node *n; + char *volatile str; + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + size_t savelen; + int saveprompt; + const int bq_startlinno = plinno; + char *volatile ostr = NULL; + struct parsefile *const savetopfile = getcurrentfile(); + struct heredoc *const saveheredoclist = heredoclist; + struct heredoc *here; + + str = NULL; + if (setjmp(jmploc.loc)) { + popfilesupto(savetopfile); + if (str) + ckfree(str); + if (ostr) + ckfree(ostr); + heredoclist = saveheredoclist; + handler = savehandler; + if (exception == EXERROR) { + startlinno = bq_startlinno; + synerror("Error in command substitution"); + } + longjmp(handler->loc, 1); + } + INTOFF; + savelen = out - stackblock(); + if (savelen > 0) { + str = ckmalloc(savelen); + memcpy(str, stackblock(), savelen); + } + handler = &jmploc; + heredoclist = NULL; + INTON; + if (oldstyle) { + /* We must read until the closing backquote, giving special + treatment to some slashes, and then push the string and + reread it as input, interpreting it normally. */ + char *oout; + int c; + int olen; + + + STARTSTACKSTR(oout); + for (;;) { + if (needprompt) { + setprompt(2); + needprompt = 0; + } + CHECKSTRSPACE(2, oout); + c = pgetc_linecont(); + if (c == '`') + break; + switch (c) { + case '\\': + c = pgetc(); + if (c != '\\' && c != '`' && c != '$' + && (!dblquote || c != '"')) + USTPUTC('\\', oout); + break; + + case '\n': + plinno++; + needprompt = doprompt; + break; + + case PEOF: + startlinno = plinno; + synerror("EOF in backquote substitution"); + break; + + default: + break; + } + USTPUTC(c, oout); + } + USTPUTC('\0', oout); + olen = oout - stackblock(); + INTOFF; + ostr = ckmalloc(olen); + memcpy(ostr, stackblock(), olen); + setinputstring(ostr, 1); + INTON; + } + nlpp = pbqlist; + while (*nlpp) + nlpp = &(*nlpp)->next; + *nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + (*nlpp)->next = NULL; + + if (oldstyle) { + saveprompt = doprompt; + doprompt = 0; + } + + n = list(0); + + if (oldstyle) { + if (peektoken() != TEOF) + synexpect(-1); + doprompt = saveprompt; + } else + consumetoken(TRP); + + (*nlpp)->n = n; + if (oldstyle) { + /* + * Start reading from old file again, ignoring any pushed back + * tokens left from the backquote parsing + */ + popfile(); + tokpushback = 0; + } + STARTSTACKSTR(out); + CHECKSTRSPACE(savelen + 1, out); + INTOFF; + if (str) { + memcpy(out, str, savelen); + STADJUST(savelen, out); + ckfree(str); + str = NULL; + } + if (ostr) { + ckfree(ostr); + ostr = NULL; + } + here = saveheredoclist; + if (here != NULL) { + while (here->next != NULL) + here = here->next; + here->next = heredoclist; + heredoclist = saveheredoclist; + } + handler = savehandler; + INTON; + if (quoted) + USTPUTC(CTLBACKQ | CTLQUOTE, out); + else + USTPUTC(CTLBACKQ, out); + return out; +} + + +/* + * Called to parse a backslash escape sequence inside $'...'. + * The backslash has already been read. + */ +static char * +readcstyleesc(char *out) +{ + int c, vc, i, n; + unsigned int v; + + c = pgetc(); + switch (c) { + case '\0': + synerror("Unterminated quoted string"); + case '\n': + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + return out; + case '\\': + case '\'': + case '"': + v = c; + break; + case 'a': v = '\a'; break; + case 'b': v = '\b'; break; + case 'e': v = '\033'; break; + case 'f': v = '\f'; break; + case 'n': v = '\n'; break; + case 'r': v = '\r'; break; + case 't': v = '\t'; break; + case 'v': v = '\v'; break; + case 'x': + v = 0; + for (;;) { + c = pgetc(); + if (c >= '0' && c <= '9') + v = (v << 4) + c - '0'; + else if (c >= 'A' && c <= 'F') + v = (v << 4) + c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + v = (v << 4) + c - 'a' + 10; + else + break; + } + pungetc(); + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + v = c - '0'; + c = pgetc(); + if (c >= '0' && c <= '7') { + v <<= 3; + v += c - '0'; + c = pgetc(); + if (c >= '0' && c <= '7') { + v <<= 3; + v += c - '0'; + } else + pungetc(); + } else + pungetc(); + break; + case 'c': + c = pgetc(); + if (c < 0x3f || c > 0x7a || c == 0x60) + synerror("Bad escape sequence"); + if (c == '\\' && pgetc() != '\\') + synerror("Bad escape sequence"); + if (c == '?') + v = 127; + else + v = c & 0x1f; + break; + case 'u': + case 'U': + n = c == 'U' ? 8 : 4; + v = 0; + for (i = 0; i < n; i++) { + c = pgetc(); + if (c >= '0' && c <= '9') + v = (v << 4) + c - '0'; + else if (c >= 'A' && c <= 'F') + v = (v << 4) + c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + v = (v << 4) + c - 'a' + 10; + else + synerror("Bad escape sequence"); + } + if (v == 0 || (v >= 0xd800 && v <= 0xdfff)) + synerror("Bad escape sequence"); + /* We really need iconv here. */ + if (initial_localeisutf8 && v > 127) { + CHECKSTRSPACE(4, out); + /* + * We cannot use wctomb() as the locale may have + * changed. + */ + if (v <= 0x7ff) { + USTPUTC(0xc0 | v >> 6, out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } else if (v <= 0xffff) { + USTPUTC(0xe0 | v >> 12, out); + USTPUTC(0x80 | ((v >> 6) & 0x3f), out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } else if (v <= 0x10ffff) { + USTPUTC(0xf0 | v >> 18, out); + USTPUTC(0x80 | ((v >> 12) & 0x3f), out); + USTPUTC(0x80 | ((v >> 6) & 0x3f), out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } + } + if (v > 127) + v = '?'; + break; + default: + synerror("Bad escape sequence"); + } + vc = (char)v; + /* + * We can't handle NUL bytes. + * POSIX says we should skip till the closing quote. + */ + if (vc == '\0') { + while ((c = pgetc()) != '\'') { + if (c == '\\') + c = pgetc(); + if (c == PEOF) + synerror("Unterminated quoted string"); + if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } + } + pungetc(); + return out; + } + if (SQSYNTAX[vc] == CCTL) + USTPUTC(CTLESC, out); + USTPUTC(vc, out); + return out; +} + + +/* + * If eofmark is NULL, read a word or a redirection symbol. If eofmark + * is not NULL, read a here document. In the latter case, eofmark is the + * word which marks the end of the document and striptabs is true if + * leading tabs should be stripped from the document. The argument firstc + * is the first character of the input token or document. + * + * Because C does not have internal subroutines, I have simulated them + * using goto's to implement the subroutine linkage. The following macros + * will run code that appears at the end of readtoken1. + */ + +#define PARSESUB() {goto parsesub; parsesub_return:;} +#define PARSEARITH() {goto parsearith; parsearith_return:;} + +static int +readtoken1(int firstc, char const *initialsyntax, const char *eofmark, + int striptabs) +{ + int c = firstc; + char *out; + int len; + struct nodelist *bqlist; + int quotef; + int newvarnest; + int level; + int synentry; + struct tokenstate state_static[MAXNEST_static]; + int maxnest = MAXNEST_static; + struct tokenstate *state = state_static; + int sqiscstyle = 0; + + startlinno = plinno; + quotef = 0; + bqlist = NULL; + newvarnest = 0; + level = 0; + state[level].syntax = initialsyntax; + state[level].parenlevel = 0; + state[level].category = TSTATE_TOP; + + STARTSTACKSTR(out); + loop: { /* for each line, until end of word */ + if (eofmark && eofmark != NOEOFMARK) + /* set c to PEOF if at end of here document */ + c = checkend(c, eofmark, striptabs); + for (;;) { /* until end of line or end of word */ + CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ + + synentry = state[level].syntax[c]; + + switch(synentry) { + case CNL: /* '\n' */ + if (level == 0) + goto endword; /* exit outer loop */ + /* FALLTHROUGH */ + case CQNL: + USTPUTC(c, out); + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + c = pgetc(); + goto loop; /* continue outer loop */ + case CSBACK: + if (sqiscstyle) { + out = readcstyleesc(out); + break; + } + /* FALLTHROUGH */ + case CWORD: + USTPUTC(c, out); + break; + case CCTL: + if (eofmark == NULL || initialsyntax != SQSYNTAX) + USTPUTC(CTLESC, out); + USTPUTC(c, out); + break; + case CBACK: /* backslash */ + c = pgetc(); + if (c == PEOF) { + USTPUTC('\\', out); + pungetc(); + } else if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } else { + if (state[level].syntax == DQSYNTAX && + c != '\\' && c != '`' && c != '$' && + (c != '"' || (eofmark != NULL && + newvarnest == 0)) && + (c != '}' || state[level].category != TSTATE_VAR_OLD)) + USTPUTC('\\', out); + if ((eofmark == NULL || + newvarnest > 0) && + state[level].syntax == BASESYNTAX) + USTPUTC(CTLQUOTEMARK, out); + if (SQSYNTAX[c] == CCTL) + USTPUTC(CTLESC, out); + USTPUTC(c, out); + if ((eofmark == NULL || + newvarnest > 0) && + state[level].syntax == BASESYNTAX && + state[level].category == TSTATE_VAR_OLD) + USTPUTC(CTLQUOTEEND, out); + quotef++; + } + break; + case CSQUOTE: + USTPUTC(CTLQUOTEMARK, out); + state[level].syntax = SQSYNTAX; + sqiscstyle = 0; + break; + case CDQUOTE: + USTPUTC(CTLQUOTEMARK, out); + state[level].syntax = DQSYNTAX; + break; + case CENDQUOTE: + if (eofmark != NULL && newvarnest == 0) + USTPUTC(c, out); + else { + if (state[level].category == TSTATE_VAR_OLD) + USTPUTC(CTLQUOTEEND, out); + state[level].syntax = BASESYNTAX; + quotef++; + } + break; + case CVAR: /* '$' */ + PARSESUB(); /* parse substitution */ + break; + case CENDVAR: /* '}' */ + if (level > 0 && + ((state[level].category == TSTATE_VAR_OLD && + state[level].syntax == + state[level - 1].syntax) || + (state[level].category == TSTATE_VAR_NEW && + state[level].syntax == BASESYNTAX))) { + if (state[level].category == TSTATE_VAR_NEW) + newvarnest--; + level--; + USTPUTC(CTLENDVAR, out); + } else { + USTPUTC(c, out); + } + break; + case CLP: /* '(' in arithmetic */ + state[level].parenlevel++; + USTPUTC(c, out); + break; + case CRP: /* ')' in arithmetic */ + if (state[level].parenlevel > 0) { + USTPUTC(c, out); + --state[level].parenlevel; + } else { + if (pgetc_linecont() == ')') { + if (level > 0 && + state[level].category == TSTATE_ARITH) { + level--; + USTPUTC(CTLENDARI, out); + } else + USTPUTC(')', out); + } else { + /* + * unbalanced parens + * (don't 2nd guess - no error) + */ + pungetc(); + USTPUTC(')', out); + } + } + break; + case CBQUOTE: /* '`' */ + out = parsebackq(out, &bqlist, 1, + state[level].syntax == DQSYNTAX && + (eofmark == NULL || newvarnest > 0), + state[level].syntax == DQSYNTAX || state[level].syntax == ARISYNTAX); + break; + case CEOF: + goto endword; /* exit outer loop */ + case CIGN: + break; + default: + if (level == 0) + goto endword; /* exit outer loop */ + USTPUTC(c, out); + } + c = pgetc_macro(); + } + } +endword: + if (state[level].syntax == ARISYNTAX) + synerror("Missing '))'"); + if (state[level].syntax != BASESYNTAX && eofmark == NULL) + synerror("Unterminated quoted string"); + if (state[level].category == TSTATE_VAR_OLD || + state[level].category == TSTATE_VAR_NEW) { + startlinno = plinno; + synerror("Missing '}'"); + } + if (state != state_static) + parser_temp_free_upto(state); + USTPUTC('\0', out); + len = out - stackblock(); + out = stackblock(); + if (eofmark == NULL) { + if ((c == '>' || c == '<') + && quotef == 0 + && len <= 2 + && (*out == '\0' || is_digit(*out))) { + parseredir(out, c); + return lasttoken = TREDIR; + } else { + pungetc(); + } + } + quoteflag = quotef; + backquotelist = bqlist; + grabstackblock(len); + wordtext = out; + return lasttoken = TWORD; +/* end of readtoken routine */ + + +/* + * Parse a substitution. At this point, we have read the dollar sign + * and nothing else. + */ + +parsesub: { + int subtype; + int typeloc; + int flags; + char *p; + static const char types[] = "}-+?="; + int linno; + int length; + int c1; + + c = pgetc_linecont(); + if (c == '(') { /* $(command) or $((arith)) */ + if (pgetc_linecont() == '(') { + PARSEARITH(); + } else { + pungetc(); + out = parsebackq(out, &bqlist, 0, + state[level].syntax == DQSYNTAX && + (eofmark == NULL || newvarnest > 0), + state[level].syntax == DQSYNTAX || + state[level].syntax == ARISYNTAX); + } + } else if (c == '{' || is_name(c) || is_special(c)) { + USTPUTC(CTLVAR, out); + typeloc = out - stackblock(); + USTPUTC(VSNORMAL, out); + subtype = VSNORMAL; + flags = 0; + if (c == '{') { + c = pgetc_linecont(); + subtype = 0; + } +varname: + if (!is_eof(c) && is_name(c)) { + length = 0; + do { + STPUTC(c, out); + c = pgetc_linecont(); + length++; + } while (!is_eof(c) && is_in_name(c)); + if (length == 6 && + strncmp(out - length, "LINENO", length) == 0) { + /* Replace the variable name with the + * current line number. */ + STADJUST(-6, out); + CHECKSTRSPACE(11, out); + linno = plinno; + if (funclinno != 0) + linno -= funclinno - 1; + length = snprintf(out, 11, "%d", linno); + if (length > 10) + length = 10; + out += length; + flags |= VSLINENO; + } + } else if (is_digit(c)) { + if (subtype != VSNORMAL) { + do { + STPUTC(c, out); + c = pgetc_linecont(); + } while (is_digit(c)); + } else { + USTPUTC(c, out); + c = pgetc_linecont(); + } + } else if (is_special(c)) { + c1 = c; + c = pgetc_linecont(); + if (subtype == 0 && c1 == '#') { + subtype = VSLENGTH; + if (strchr(types, c) == NULL && c != ':' && + c != '#' && c != '%') + goto varname; + c1 = c; + c = pgetc_linecont(); + if (c1 != '}' && c == '}') { + pungetc(); + c = c1; + goto varname; + } + pungetc(); + c = c1; + c1 = '#'; + subtype = 0; + } + USTPUTC(c1, out); + } else { + subtype = VSERROR; + if (c == '}') + pungetc(); + else if (c == '\n' || c == PEOF) + synerror("Unexpected end of line in substitution"); + else if (BASESYNTAX[c] != CCTL) + USTPUTC(c, out); + } + if (subtype == 0) { + switch (c) { + case ':': + flags |= VSNUL; + c = pgetc_linecont(); + /*FALLTHROUGH*/ + default: + p = strchr(types, c); + if (p == NULL) { + if (c == '\n' || c == PEOF) + synerror("Unexpected end of line in substitution"); + if (flags == VSNUL) + STPUTC(':', out); + if (BASESYNTAX[c] != CCTL) + STPUTC(c, out); + subtype = VSERROR; + } else + subtype = p - types + VSNORMAL; + break; + case '%': + case '#': + { + int cc = c; + subtype = c == '#' ? VSTRIMLEFT : + VSTRIMRIGHT; + c = pgetc_linecont(); + if (c == cc) + subtype++; + else + pungetc(); + break; + } + } + } else if (subtype != VSERROR) { + if (subtype == VSLENGTH && c != '}') + subtype = VSERROR; + pungetc(); + } + STPUTC('=', out); + if (state[level].syntax == DQSYNTAX || + state[level].syntax == ARISYNTAX) + flags |= VSQUOTE; + *(stackblock() + typeloc) = subtype | flags; + if (subtype != VSNORMAL) { + if (level + 1 >= maxnest) { + maxnest *= 2; + if (state == state_static) { + state = parser_temp_alloc( + maxnest * sizeof(*state)); + memcpy(state, state_static, + MAXNEST_static * sizeof(*state)); + } else + state = parser_temp_realloc(state, + maxnest * sizeof(*state)); + } + level++; + state[level].parenlevel = 0; + if (subtype == VSMINUS || subtype == VSPLUS || + subtype == VSQUESTION || subtype == VSASSIGN) { + /* + * For operators that were in the Bourne shell, + * inherit the double-quote state. + */ + state[level].syntax = state[level - 1].syntax; + state[level].category = TSTATE_VAR_OLD; + } else { + /* + * The other operators take a pattern, + * so go to BASESYNTAX. + * Also, ' and " are now special, even + * in here documents. + */ + state[level].syntax = BASESYNTAX; + state[level].category = TSTATE_VAR_NEW; + newvarnest++; + } + } + } else if (c == '\'' && state[level].syntax == BASESYNTAX) { + /* $'cstylequotes' */ + USTPUTC(CTLQUOTEMARK, out); + state[level].syntax = SQSYNTAX; + sqiscstyle = 1; + } else { + USTPUTC('$', out); + pungetc(); + } + goto parsesub_return; +} + + +/* + * Parse an arithmetic expansion (indicate start of one and set state) + */ +parsearith: { + + if (level + 1 >= maxnest) { + maxnest *= 2; + if (state == state_static) { + state = parser_temp_alloc( + maxnest * sizeof(*state)); + memcpy(state, state_static, + MAXNEST_static * sizeof(*state)); + } else + state = parser_temp_realloc(state, + maxnest * sizeof(*state)); + } + level++; + state[level].syntax = ARISYNTAX; + state[level].parenlevel = 0; + state[level].category = TSTATE_ARITH; + USTPUTC(CTLARI, out); + if (state[level - 1].syntax == DQSYNTAX) + USTPUTC('"',out); + else + USTPUTC(' ',out); + goto parsearith_return; +} + +} /* end of readtoken */ + + +/* + * Returns true if the text contains nothing to expand (no dollar signs + * or backquotes). + */ + +static int +noexpand(char *text) +{ + char *p; + char c; + + p = text; + while ((c = *p++) != '\0') { + if ( c == CTLQUOTEMARK) + continue; + if (c == CTLESC) + p++; + else if (BASESYNTAX[(int)c] == CCTL) + return 0; + } + return 1; +} + + +/* + * Return true if the argument is a legal variable name (a letter or + * underscore followed by zero or more letters, underscores, and digits). + */ + +int +goodname(const char *name) +{ + const char *p; + + p = name; + if (! is_name(*p)) + return 0; + while (*++p) { + if (! is_in_name(*p)) + return 0; + } + return 1; +} + + +int +isassignment(const char *p) +{ + if (!is_name(*p)) + return 0; + p++; + for (;;) { + if (*p == '=') + return 1; + else if (!is_in_name(*p)) + return 0; + p++; + } +} + + +static void +consumetoken(int token) +{ + if (readtoken() != token) + synexpect(token); +} + + +/* + * Called when an unexpected token is read during the parse. The argument + * is the token that is expected, or -1 if more than one type of token can + * occur at this point. + */ + +static void +synexpect(int token) +{ + char msg[64]; + + if (token >= 0) { + fmtstr(msg, 64, "%s unexpected (expecting %s)", + tokname[lasttoken], tokname[token]); + } else { + fmtstr(msg, 64, "%s unexpected", tokname[lasttoken]); + } + synerror(msg); +} + + +static void +synerror(const char *msg) +{ + if (commandname) + outfmt(out2, "%s: %d: ", commandname, startlinno); + else if (arg0) + outfmt(out2, "%s: ", arg0); + outfmt(out2, "Syntax error: %s\n", msg); + error((char *)NULL); +} + +static void +setprompt(int which) +{ + whichprompt = which; + if (which == 0) + return; + + if (which == 1 && *ps0val()) { + out2str(expandprompt(ps0val())); + flushout(out2); + } + +#ifndef NO_HISTORY + if (!el) +#endif + { + out2str(getprompt(NULL)); + flushout(out2); + } +} + +static int +pgetc_linecont(void) +{ + int c; + + while ((c = pgetc_macro()) == '\\') { + c = pgetc(); + if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } else { + pungetc(); + /* Allow the backslash to be pushed back. */ + pushstring("\\", 1, NULL); + return (pgetc()); + } + } + return (c); +} + +static char * +expandprompt(const char *fmt) { + static char ps[PROMPTLEN]; + const char *pwd, *home; + int i, trim; + + /* + * Format prompt string. + */ + for (i = 0; (i < PROMPTLEN - 1) && (*fmt != '\0'); i++, fmt++) + if (*fmt == '\\') + switch (*++fmt) { + + /* + * Hostname. + * + * \h specifies just the local hostname, + * \H specifies fully-qualified hostname. + */ + case 'h': + case 'H': + ps[i] = '\0'; + gethostname(&ps[i], PROMPTLEN - i - 1); + ps[PROMPTLEN - 1] = '\0'; + /* Skip to end of hostname. */ + trim = (*fmt == 'h') ? '.' : '\0'; + while ((ps[i] != '\0') && (ps[i] != trim)) + i++; + --i; + break; + + /* + * Working directory. + * + * \W specifies just the final component, + * \w specifies the entire path. + */ + case 'W': + case 'w': + pwd = lookupvar("PWD"); + home = lookupvar("HOME"); + if (pwd == NULL || *pwd == '\0') + pwd = "?"; + if (home == NULL || *home == '\0') + home = "?"; + if (*fmt == 'W' && + *pwd == '/' && pwd[1] != '\0' && + strcmp(pwd, home) != 0) { + strlcpy(&ps[i], strrchr(pwd, '/') + 1, + PROMPTLEN - i); + } else { + if (strncmp(pwd, home, strlen(home)) == 0) { + ps[i++] = '~'; + pwd += strlen(home); + } + strlcpy(&ps[i], pwd, PROMPTLEN - i); + } + /* Skip to end of path. */ + while (ps[i] != '\0') + i++; + --i; + break; + + /* + * Exit status if non-zero. + */ + case '?': + if (exitstatus != 0) { + snprintf(&ps[i], PROMPTLEN - i, "%d", exitstatus); + while (ps[i] != '\0') + i++; + } + i--; + break; + + /* + * Superuser status. + * + * '$' for normal users, '#' for root. + */ + case '$': + ps[i] = (geteuid() != 0) ? '$' : '#'; + break; + + /* + * A literal \. + */ + case '\\': + ps[i] = '\\'; + break; + + /* + * Emit unrecognized formats verbatim. + */ + default: + ps[i] = '\\'; + if (i < PROMPTLEN - 2) + ps[++i] = *fmt; + break; + } + else + ps[i] = *fmt; + ps[i] = '\0'; + return (ps); +} + +/* + * called by editline -- any expansions to the prompt + * should be added here. + */ +char * +getprompt(void *unused __unused) +{ + const char *fmt; + static char internal_error[] = "??"; + + /* + * Select prompt format. + */ + switch (whichprompt) { + case 0: + fmt = ""; + break; + case 1: + fmt = ps1val(); + break; + case 2: + fmt = ps2val(); + break; + default: + return internal_error; + } + + return expandprompt(fmt); +} + +char * +getrprompt(void *unused __unused) +{ + const char *fmt; + static char internal_error[] = "??"; + + /* + * Select prompt format. + */ + switch (whichprompt) { + case 0: + fmt = ""; + break; + case 1: + fmt = rps1val(); + break; + case 2: + fmt = rps2val(); + break; + default: + return internal_error; + } + + return expandprompt(fmt); +} + + +const char * +expandstr(const char *ps) +{ + union node n; + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + const int saveprompt = doprompt; + struct parsefile *const savetopfile = getcurrentfile(); + struct parser_temp *const saveparser_temp = parser_temp; + const char *result = NULL; + + if (!setjmp(jmploc.loc)) { + handler = &jmploc; + parser_temp = NULL; + setinputstring(ps, 1); + doprompt = 0; + readtoken1(pgetc(), DQSYNTAX, NOEOFMARK, 0); + if (backquotelist != NULL) + error("Command substitution not allowed here"); + + n.narg.type = NARG; + n.narg.next = NULL; + n.narg.text = wordtext; + n.narg.backquote = backquotelist; + + expandarg(&n, NULL, 0); + result = stackblock(); + INTOFF; + } + handler = savehandler; + doprompt = saveprompt; + popfilesupto(savetopfile); + if (parser_temp != saveparser_temp) { + parser_temp_free_all(); + parser_temp = saveparser_temp; + } + if (result != NULL) { + INTON; + } else if (exception == EXINT) + raise(SIGINT); + return result; +} diff --git a/bin/1sh/parser.h b/bin/1sh/parser.h new file mode 100644 index 00000000..171bc994 --- /dev/null +++ b/bin/1sh/parser.h @@ -0,0 +1,88 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)parser.h 8.3 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/parser.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* control characters in argument strings */ +#define CTLESC '\300' +#define CTLVAR '\301' +#define CTLENDVAR '\371' +#define CTLBACKQ '\372' +#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ +/* CTLBACKQ | CTLQUOTE == '\373' */ +#define CTLARI '\374' +#define CTLENDARI '\375' +#define CTLQUOTEMARK '\376' +#define CTLQUOTEEND '\377' /* only for ${v+-...} */ + +/* variable substitution byte (follows CTLVAR) */ +#define VSTYPE 0x0f /* type of variable substitution */ +#define VSNUL 0x10 /* colon--treat the empty string as unset */ +#define VSLINENO 0x20 /* expansion of $LINENO, the line number \ + follows immediately */ +#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */ + +/* values of VSTYPE field */ +#define VSNORMAL 0x1 /* normal variable: $var or ${var} */ +#define VSMINUS 0x2 /* ${var-text} */ +#define VSPLUS 0x3 /* ${var+text} */ +#define VSQUESTION 0x4 /* ${var?message} */ +#define VSASSIGN 0x5 /* ${var=text} */ +#define VSTRIMLEFT 0x6 /* ${var#pattern} */ +#define VSTRIMLEFTMAX 0x7 /* ${var##pattern} */ +#define VSTRIMRIGHT 0x8 /* ${var%pattern} */ +#define VSTRIMRIGHTMAX 0x9 /* ${var%%pattern} */ +#define VSLENGTH 0xa /* ${#var} */ +#define VSERROR 0xb /* Syntax error, issue when expanded */ + + +/* + * NEOF is returned by parsecmd when it encounters an end of file. It + * must be distinct from NULL. + */ +#define NEOF ((union node *)-1) +extern int whichprompt; /* 1 == PS1, 2 == PS2 */ +extern const char *const parsekwd[]; + + +union node *parsecmd(int); +union node *parsewordexp(void); +void forcealias(void); +void fixredir(union node *, const char *, int); +int goodname(const char *); +int isassignment(const char *); +char *getprompt(void *); +char *getrprompt(void *); +const char *expandstr(const char *); diff --git a/bin/1sh/printf.c b/bin/1sh/printf.c new file mode 100644 index 00000000..2545fb62 --- /dev/null +++ b/bin/1sh/printf.c @@ -0,0 +1,686 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> + * Copyright 2014 Garrett D'Amore <garrett@damore.org> + * Copyright 2010 Nexenta Systems, Inc. All rights reserved. + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/* + * Important: This file is used both as a standalone program /usr/bin/printf + * and as a builtin for /bin/sh (#define SHELL). + */ + +#ifndef SHELL +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1989, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ +#endif + +#if 0 +static char const sccsid[] = "@(#)printf.c 8.1 (Berkeley) 7/20/93"; +static const char rcsid[] = + "$FreeBSD: releng/12.1/usr.bin/printf/printf.c 337618 2018-08-11 11:13:34Z jilles $"; +#endif + +#include <sys/types.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wchar.h> + +#ifdef SHELL +#define main printfcmd +#include "bltin.h" +#include "options.h" +#endif + +#define PF(f, func) do { \ + if (havewidth) \ + if (haveprec) \ + (void)printf(f, fieldwidth, precision, func); \ + else \ + (void)printf(f, fieldwidth, func); \ + else if (haveprec) \ + (void)printf(f, precision, func); \ + else \ + (void)printf(f, func); \ +} while (0) + +static int asciicode(void); +static char *printf_doformat(char *, int *); +static int escape(char *, int, size_t *); +static int getchr(void); +static int getfloating(long double *, int); +static int getint(int *); +static int getnum(intmax_t *, uintmax_t *, int); +static const char + *getstr(void); +static char *mknum(char *, char); +static void usage(void); + +static const char digits[] = "0123456789"; + +static char end_fmt[1]; + +static int myargc; +static char **myargv; +static char **gargv; +static char **maxargv; + +int +main(int argc, char *argv[]) +{ + size_t len; + int end, rval; + char *format, *fmt, *start; +#ifndef SHELL + int ch; + + (void) setlocale(LC_ALL, ""); +#endif + +#ifdef SHELL + nextopt(""); + argc -= argptr - argv; + argv = argptr; +#else + while ((ch = getopt(argc, argv, "")) != -1) + switch (ch) { + case '?': + default: + usage(); + return (1); + } + argc -= optind; + argv += optind; +#endif + + if (argc < 1) { + usage(); + return (1); + } + +#ifdef SHELL + INTOFF; +#endif + /* + * Basic algorithm is to scan the format string for conversion + * specifications -- once one is found, find out if the field + * width or precision is a '*'; if it is, gather up value. Note, + * format strings are reused as necessary to use up the provided + * arguments, arguments of zero/null string are provided to use + * up the format string. + */ + fmt = format = *argv; + escape(fmt, 1, &len); /* backslash interpretation */ + rval = end = 0; + gargv = ++argv; + + for (;;) { + maxargv = gargv; + + myargv = gargv; + for (myargc = 0; gargv[myargc]; myargc++) + /* nop */; + start = fmt; + while (fmt < format + len) { + if (fmt[0] == '%') { + fwrite(start, 1, fmt - start, stdout); + if (fmt[1] == '%') { + /* %% prints a % */ + putchar('%'); + fmt += 2; + } else { + fmt = printf_doformat(fmt, &rval); + if (fmt == NULL || fmt == end_fmt) { +#ifdef SHELL + INTON; +#endif + return (fmt == NULL ? 1 : rval); + } + end = 0; + } + start = fmt; + } else + fmt++; + if (gargv > maxargv) + maxargv = gargv; + } + gargv = maxargv; + + if (end == 1) { + warnx("missing format character"); +#ifdef SHELL + INTON; +#endif + return (1); + } + fwrite(start, 1, fmt - start, stdout); + if (!*gargv) { +#ifdef SHELL + INTON; +#endif + return (rval); + } + /* Restart at the beginning of the format string. */ + fmt = format; + end = 1; + } + /* NOTREACHED */ +} + + +static char * +printf_doformat(char *fmt, int *rval) +{ + static const char skip1[] = "#'-+ 0"; + int fieldwidth, haveprec, havewidth, mod_ldbl, precision; + char convch, nextch; + char start[strlen(fmt) + 1]; + char **fargv; + char *dptr; + int l; + + dptr = start; + *dptr++ = '%'; + *dptr = 0; + + fmt++; + + /* look for "n$" field index specifier */ + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + if (gargv > maxargv) + maxargv = gargv; + fmt += l + 1; + + /* save format argument */ + fargv = gargv; + } else { + fargv = NULL; + } + + /* skip to field width */ + while (*fmt && strchr(skip1, *fmt) != NULL) { + *dptr++ = *fmt++; + *dptr = 0; + } + + if (*fmt == '*') { + + fmt++; + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (fargv == NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + fmt += l + 1; + } else if (fargv != NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + + if (getint(&fieldwidth)) + return (NULL); + if (gargv > maxargv) + maxargv = gargv; + havewidth = 1; + + *dptr++ = '*'; + *dptr = 0; + } else { + havewidth = 0; + + /* skip to possible '.', get following precision */ + while (isdigit(*fmt)) { + *dptr++ = *fmt++; + *dptr = 0; + } + } + + if (*fmt == '.') { + /* precision present? */ + fmt++; + *dptr++ = '.'; + + if (*fmt == '*') { + + fmt++; + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (fargv == NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + fmt += l + 1; + } else if (fargv != NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + + if (getint(&precision)) + return (NULL); + if (gargv > maxargv) + maxargv = gargv; + haveprec = 1; + *dptr++ = '*'; + *dptr = 0; + } else { + haveprec = 0; + + /* skip to conversion char */ + while (isdigit(*fmt)) { + *dptr++ = *fmt++; + *dptr = 0; + } + } + } else + haveprec = 0; + if (!*fmt) { + warnx("missing format character"); + return (NULL); + } + *dptr++ = *fmt; + *dptr = 0; + + /* + * Look for a length modifier. POSIX doesn't have these, so + * we only support them for floating-point conversions, which + * are extensions. This is useful because the L modifier can + * be used to gain extra range and precision, while omitting + * it is more likely to produce consistent results on different + * architectures. This is not so important for integers + * because overflow is the only bad thing that can happen to + * them, but consider the command printf %a 1.1 + */ + if (*fmt == 'L') { + mod_ldbl = 1; + fmt++; + if (!strchr("aAeEfFgG", *fmt)) { + warnx("bad modifier L for %%%c", *fmt); + return (NULL); + } + } else { + mod_ldbl = 0; + } + + /* save the current arg offset, and set to the format arg */ + if (fargv != NULL) { + gargv = fargv; + } + + convch = *fmt; + nextch = *++fmt; + + *fmt = '\0'; + switch (convch) { + case 'b': { + size_t len; + char *p; + int getout; + + /* Convert "b" to "s" for output. */ + start[strlen(start) - 1] = 's'; + if ((p = strdup(getstr())) == NULL) { + warnx("%s", strerror(ENOMEM)); + return (NULL); + } + getout = escape(p, 0, &len); + PF(start, p); + /* Restore format for next loop. */ + + free(p); + if (getout) + return (end_fmt); + break; + } + case 'c': { + char p; + + p = getchr(); + if (p != '\0') + PF(start, p); + break; + } + case 's': { + const char *p; + + p = getstr(); + PF(start, p); + break; + } + case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': { + char *f; + intmax_t val; + uintmax_t uval; + int signedconv; + + signedconv = (convch == 'd' || convch == 'i'); + if ((f = mknum(start, convch)) == NULL) + return (NULL); + if (getnum(&val, &uval, signedconv)) + *rval = 1; + if (signedconv) + PF(f, val); + else + PF(f, uval); + break; + } + case 'e': case 'E': + case 'f': case 'F': + case 'g': case 'G': + case 'a': case 'A': { + long double p; + + if (getfloating(&p, mod_ldbl)) + *rval = 1; + if (mod_ldbl) + PF(start, p); + else + PF(start, (double)p); + break; + } + default: + warnx("illegal format character %c", convch); + return (NULL); + } + *fmt = nextch; + /* return the gargv to the next element */ + return (fmt); +} + +static char * +mknum(char *str, char ch) +{ + static char *copy; + static size_t copy_size; + char *newcopy; + size_t len, newlen; + + len = strlen(str) + 2; + if (len > copy_size) { + newlen = ((len + 1023) >> 10) << 10; + if ((newcopy = realloc(copy, newlen)) == NULL) { + warnx("%s", strerror(ENOMEM)); + return (NULL); + } + copy = newcopy; + copy_size = newlen; + } + + memmove(copy, str, len - 3); + copy[len - 3] = 'j'; + copy[len - 2] = ch; + copy[len - 1] = '\0'; + return (copy); +} + +static int +escape(char *fmt, int percent, size_t *len) +{ + char *save, *store, c; + int value; + + for (save = store = fmt; ((c = *fmt) != 0); ++fmt, ++store) { + if (c != '\\') { + *store = c; + continue; + } + switch (*++fmt) { + case '\0': /* EOS, user error */ + *store = '\\'; + *++store = '\0'; + *len = store - save; + return (0); + case '\\': /* backslash */ + case '\'': /* single quote */ + *store = *fmt; + break; + case 'a': /* bell/alert */ + *store = '\a'; + break; + case 'b': /* backspace */ + *store = '\b'; + break; + case 'c': + if (!percent) { + *store = '\0'; + *len = store - save; + return (1); + } + *store = 'c'; + break; + case 'f': /* form-feed */ + *store = '\f'; + break; + case 'n': /* newline */ + *store = '\n'; + break; + case 'r': /* carriage-return */ + *store = '\r'; + break; + case 't': /* horizontal tab */ + *store = '\t'; + break; + case 'v': /* vertical tab */ + *store = '\v'; + break; + /* octal constant */ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c = (!percent && *fmt == '0') ? 4 : 3; + for (value = 0; + c-- && *fmt >= '0' && *fmt <= '7'; ++fmt) { + value <<= 3; + value += *fmt - '0'; + } + --fmt; + if (percent && value == '%') { + *store++ = '%'; + *store = '%'; + } else + *store = (char)value; + break; + default: + *store = *fmt; + break; + } + } + *store = '\0'; + *len = store - save; + return (0); +} + +static int +getchr(void) +{ + if (!*gargv) + return ('\0'); + return ((int)**gargv++); +} + +static const char * +getstr(void) +{ + if (!*gargv) + return (""); + return (*gargv++); +} + +static int +getint(int *ip) +{ + intmax_t val; + uintmax_t uval; + int rval; + + if (getnum(&val, &uval, 1)) + return (1); + rval = 0; + if (val < INT_MIN || val > INT_MAX) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + *ip = (int)val; + return (rval); +} + +static int +getnum(intmax_t *ip, uintmax_t *uip, int signedconv) +{ + char *ep; + int rval; + + if (!*gargv) { + *ip = *uip = 0; + return (0); + } + if (**gargv == '"' || **gargv == '\'') { + if (signedconv) + *ip = asciicode(); + else + *uip = asciicode(); + return (0); + } + rval = 0; + errno = 0; + if (signedconv) + *ip = strtoimax(*gargv, &ep, 0); + else + *uip = strtoumax(*gargv, &ep, 0); + if (ep == *gargv) { + warnx("%s: expected numeric value", *gargv); + rval = 1; + } + else if (*ep != '\0') { + warnx("%s: not completely converted", *gargv); + rval = 1; + } + if (errno == ERANGE) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + ++gargv; + return (rval); +} + +static int +getfloating(long double *dp, int mod_ldbl) +{ + char *ep; + int rval; + + if (!*gargv) { + *dp = 0.0; + return (0); + } + if (**gargv == '"' || **gargv == '\'') { + *dp = asciicode(); + return (0); + } + rval = 0; + errno = 0; + if (mod_ldbl) + *dp = strtold(*gargv, &ep); + else + *dp = strtod(*gargv, &ep); + if (ep == *gargv) { + warnx("%s: expected numeric value", *gargv); + rval = 1; + } else if (*ep != '\0') { + warnx("%s: not completely converted", *gargv); + rval = 1; + } + if (errno == ERANGE) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + ++gargv; + return (rval); +} + +static int +asciicode(void) +{ + int ch; + wchar_t wch; + mbstate_t mbs; + + ch = (unsigned char)**gargv; + if (ch == '\'' || ch == '"') { + memset(&mbs, 0, sizeof(mbs)); + switch (mbrtowc(&wch, *gargv + 1, MB_LEN_MAX, &mbs)) { + case (size_t)-2: + case (size_t)-1: + wch = (unsigned char)gargv[0][1]; + break; + case 0: + wch = 0; + break; + } + ch = wch; + } + ++gargv; + return (ch); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: printf format [arguments ...]\n"); +} diff --git a/bin/1sh/redir.c b/bin/1sh/redir.c new file mode 100644 index 00000000..f8fd9324 --- /dev/null +++ b/bin/1sh/redir.c @@ -0,0 +1,365 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)redir.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/redir.c 326025 2017-11-20 19:49:47Z pfg $ */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <signal.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> + +/* + * Code for dealing with input/output redirection. + */ + +#include "shell.h" +#include "nodes.h" +#include "jobs.h" +#include "expand.h" +#include "redir.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "options.h" + + +#define EMPTY -2 /* marks an unused slot in redirtab */ +#define CLOSED -1 /* fd was not open before redir */ + + +struct redirtab { + struct redirtab *next; + int renamed[10]; + int fd0_redirected; + unsigned int empty_redirs; +}; + + +static struct redirtab *redirlist; + +/* + * We keep track of whether or not fd0 has been redirected. This is for + * background commands, where we want to redirect fd0 to /dev/null only + * if it hasn't already been redirected. +*/ +static int fd0_redirected = 0; + +/* Number of redirtabs that have not been allocated. */ +static unsigned int empty_redirs = 0; + +static void openredirect(union node *, char[10 ]); +static int openhere(union node *); + + +/* + * Process a list of redirection commands. If the REDIR_PUSH flag is set, + * old file descriptors are stashed away so that the redirection can be + * undone by calling popredir. If the REDIR_BACKQ flag is set, then the + * standard output, and the standard error if it becomes a duplicate of + * stdout, is saved in memory. +* + * We suppress interrupts so that we won't leave open file + * descriptors around. Because the signal handler remains + * installed and we do not use system call restart, interrupts + * will still abort blocking opens such as fifos (they will fail + * with EINTR). There is, however, a race condition if an interrupt + * arrives after INTOFF and before open blocks. + */ + +void +redirect(union node *redir, int flags) +{ + union node *n; + struct redirtab *sv = NULL; + int i; + int fd; + char memory[10]; /* file descriptors to write to memory */ + + INTOFF; + for (i = 10 ; --i >= 0 ; ) + memory[i] = 0; + memory[1] = flags & REDIR_BACKQ; + if (flags & REDIR_PUSH) { + empty_redirs++; + if (redir != NULL) { + sv = ckmalloc(sizeof (struct redirtab)); + for (i = 0 ; i < 10 ; i++) + sv->renamed[i] = EMPTY; + sv->fd0_redirected = fd0_redirected; + sv->empty_redirs = empty_redirs - 1; + sv->next = redirlist; + redirlist = sv; + empty_redirs = 0; + } + } + for (n = redir ; n ; n = n->nfile.next) { + fd = n->nfile.fd; + if (fd == 0) + fd0_redirected = 1; + if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) && + n->ndup.dupfd == fd) + continue; /* redirect from/to same file descriptor */ + + if ((flags & REDIR_PUSH) && sv->renamed[fd] == EMPTY) { + INTOFF; + if ((i = fcntl(fd, F_DUPFD_CLOEXEC, 10)) == -1) { + switch (errno) { + case EBADF: + i = CLOSED; + break; + default: + INTON; + error("%d: %s", fd, strerror(errno)); + break; + } + } + sv->renamed[fd] = i; + INTON; + } + openredirect(n, memory); + INTON; + INTOFF; + } + if (memory[1]) + out1 = &memout; + if (memory[2]) + out2 = &memout; + INTON; +} + + +static void +openredirect(union node *redir, char memory[10]) +{ + struct stat sb; + int fd = redir->nfile.fd; + const char *fname; + int f; + int e; + + memory[fd] = 0; + switch (redir->nfile.type) { + case NFROM: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDONLY)) < 0) + error("cannot open %s: %s", fname, strerror(errno)); + break; + case NFROMTO: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDWR|O_CREAT, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + break; + case NTO: + if (Cflag) { + fname = redir->nfile.expfname; + if (stat(fname, &sb) == -1) { + if ((f = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + } else if (!S_ISREG(sb.st_mode)) { + if ((f = open(fname, O_WRONLY, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + if (fstat(f, &sb) != -1 && S_ISREG(sb.st_mode)) { + close(f); + error("cannot create %s: %s", fname, + strerror(EEXIST)); + } + } else + error("cannot create %s: %s", fname, + strerror(EEXIST)); + break; + } + /* FALLTHROUGH */ + case NCLOBBER: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + break; + case NAPPEND: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + break; + case NTOFD: + case NFROMFD: + if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ + if (memory[redir->ndup.dupfd]) + memory[fd] = 1; + else { + if (dup2(redir->ndup.dupfd, fd) < 0) + error("%d: %s", redir->ndup.dupfd, + strerror(errno)); + } + } else { + close(fd); + } + return; + case NHERE: + case NXHERE: + f = openhere(redir); + break; + default: + abort(); + } + if (f != fd) { + if (dup2(f, fd) == -1) { + e = errno; + close(f); + error("%d: %s", fd, strerror(e)); + } + close(f); + } +} + + +/* + * Handle here documents. Normally we fork off a process to write the + * data to a pipe. If the document is short, we can stuff the data in + * the pipe without forking. + */ + +static int +openhere(union node *redir) +{ + const char *p; + int pip[2]; + size_t len = 0; + int flags; + ssize_t written = 0; + + if (pipe(pip) < 0) + error("Pipe call failed: %s", strerror(errno)); + + if (redir->type == NXHERE) + p = redir->nhere.expdoc; + else + p = redir->nhere.doc->narg.text; + len = strlen(p); + if (len == 0) + goto out; + flags = fcntl(pip[1], F_GETFL, 0); + if (flags != -1 && fcntl(pip[1], F_SETFL, flags | O_NONBLOCK) != -1) { + written = write(pip[1], p, len); + if (written < 0) + written = 0; + if ((size_t)written == len) + goto out; + fcntl(pip[1], F_SETFL, flags); + } + + if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) { + close(pip[0]); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGHUP, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + signal(SIGPIPE, SIG_DFL); + xwrite(pip[1], p + written, len - written); + _exit(0); + } +out: + close(pip[1]); + return pip[0]; +} + + + +/* + * Undo the effects of the last redirection. + */ + +void +popredir(void) +{ + struct redirtab *rp = redirlist; + int i; + + INTOFF; + if (empty_redirs > 0) { + empty_redirs--; + INTON; + return; + } + for (i = 0 ; i < 10 ; i++) { + if (rp->renamed[i] != EMPTY) { + if (rp->renamed[i] >= 0) { + dup2(rp->renamed[i], i); + close(rp->renamed[i]); + } else { + close(i); + } + } + } + fd0_redirected = rp->fd0_redirected; + empty_redirs = rp->empty_redirs; + redirlist = rp->next; + ckfree(rp); + INTON; +} + +/* Return true if fd 0 has already been redirected at least once. */ +int +fd0_redirected_p(void) +{ + return fd0_redirected != 0; +} + +/* + * Discard all saved file descriptors. + */ + +void +clearredir(void) +{ + struct redirtab *rp; + int i; + + for (rp = redirlist ; rp ; rp = rp->next) { + for (i = 0 ; i < 10 ; i++) { + if (rp->renamed[i] >= 0) { + close(rp->renamed[i]); + } + rp->renamed[i] = EMPTY; + } + } +} diff --git a/bin/1sh/redir.h b/bin/1sh/redir.h new file mode 100644 index 00000000..6946f6a7 --- /dev/null +++ b/bin/1sh/redir.h @@ -0,0 +1,47 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)redir.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/redir.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* flags passed to redirect */ +#define REDIR_PUSH 01 /* save previous values of file descriptors */ +#define REDIR_BACKQ 02 /* save the command output in memory */ + +union node; +void redirect(union node *, int); +void popredir(void); +int fd0_redirected_p(void); +void clearredir(void); + diff --git a/bin/1sh/shell.h b/bin/1sh/shell.h new file mode 100644 index 00000000..98ac98df --- /dev/null +++ b/bin/1sh/shell.h @@ -0,0 +1,77 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)shell.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/shell.h 345117 2019-03-13 21:53:10Z jilles $ + */ + +#ifndef SHELL_H_ +#define SHELL_H_ + +#include <inttypes.h> + +/* + * The follow should be set to reflect the type of system you have: + * JOBS -> 1 if you have Berkeley job control, 0 otherwise. + * define DEBUG=1 to compile in debugging (set global "debug" to turn on) + * define DEBUG=2 to compile in and turn on debugging. + * + * When debugging is on, debugging info will be written to ./trace and + * a quit signal will generate a core dump. + */ + + +#define JOBS 1 +/* #define DEBUG 1 */ + +/* + * Type of used arithmetics. SUSv3 requires us to have at least signed long. + */ +typedef intmax_t arith_t; +#define ARITH_FORMAT_STR "%" PRIdMAX +#define ARITH_MIN INTMAX_MIN +#define ARITH_MAX INTMAX_MAX + +typedef void *pointer; + +#include <sys/cdefs.h> + +extern char nullstr[1]; /* null string */ + +#ifdef DEBUG +#define TRACE(param) sh_trace param +#else +#define TRACE(param) +#endif + +#endif /* !SHELL_H_ */ diff --git a/bin/1sh/show.c b/bin/1sh/show.c new file mode 100644 index 00000000..a87a506d --- /dev/null +++ b/bin/1sh/show.c @@ -0,0 +1,410 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)show.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/show.c 326025 2017-11-20 19:49:47Z pfg $ */ + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> + +#include "shell.h" +#include "parser.h" +#include "nodes.h" +#include "mystring.h" +#include "show.h" + + +#ifdef DEBUG +static void shtree(union node *, int, char *, FILE*); +static void shcmd(union node *, FILE *); +static void sharg(union node *, FILE *); +static void indent(int, char *, FILE *); +static void trstring(char *); + + +void +showtree(union node *n) +{ + trputs("showtree called\n"); + shtree(n, 1, NULL, stdout); +} + + +static void +shtree(union node *n, int ind, char *pfx, FILE *fp) +{ + struct nodelist *lp; + char *s; + + if (n == NULL) + return; + + indent(ind, pfx, fp); + switch(n->type) { + case NSEMI: + s = "; "; + goto binop; + case NAND: + s = " && "; + goto binop; + case NOR: + s = " || "; +binop: + shtree(n->nbinary.ch1, ind, NULL, fp); + /* if (ind < 0) */ + fputs(s, fp); + shtree(n->nbinary.ch2, ind, NULL, fp); + break; + case NCMD: + shcmd(n, fp); + if (ind >= 0) + putc('\n', fp); + break; + case NPIPE: + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + shcmd(lp->n, fp); + if (lp->next) + fputs(" | ", fp); + } + if (n->npipe.backgnd) + fputs(" &", fp); + if (ind >= 0) + putc('\n', fp); + break; + default: + fprintf(fp, "<node type %d>", n->type); + if (ind >= 0) + putc('\n', fp); + break; + } +} + + + +static void +shcmd(union node *cmd, FILE *fp) +{ + union node *np; + int first; + char *s; + int dftfd; + + first = 1; + for (np = cmd->ncmd.args ; np ; np = np->narg.next) { + if (! first) + putchar(' '); + sharg(np, fp); + first = 0; + } + for (np = cmd->ncmd.redirect ; np ; np = np->nfile.next) { + if (! first) + putchar(' '); + switch (np->nfile.type) { + case NTO: s = ">"; dftfd = 1; break; + case NAPPEND: s = ">>"; dftfd = 1; break; + case NTOFD: s = ">&"; dftfd = 1; break; + case NCLOBBER: s = ">|"; dftfd = 1; break; + case NFROM: s = "<"; dftfd = 0; break; + case NFROMTO: s = "<>"; dftfd = 0; break; + case NFROMFD: s = "<&"; dftfd = 0; break; + case NHERE: s = "<<"; dftfd = 0; break; + case NXHERE: s = "<<"; dftfd = 0; break; + default: s = "*error*"; dftfd = 0; break; + } + if (np->nfile.fd != dftfd) + fprintf(fp, "%d", np->nfile.fd); + fputs(s, fp); + if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) { + if (np->ndup.dupfd >= 0) + fprintf(fp, "%d", np->ndup.dupfd); + else + fprintf(fp, "-"); + } else if (np->nfile.type == NHERE) { + fprintf(fp, "HERE"); + } else if (np->nfile.type == NXHERE) { + fprintf(fp, "XHERE"); + } else { + sharg(np->nfile.fname, fp); + } + first = 0; + } +} + + + +static void +sharg(union node *arg, FILE *fp) +{ + char *p; + struct nodelist *bqlist; + int subtype; + + if (arg->type != NARG) { + printf("<node type %d>\n", arg->type); + fflush(stdout); + abort(); + } + bqlist = arg->narg.backquote; + for (p = arg->narg.text ; *p ; p++) { + switch (*p) { + case CTLESC: + putc(*++p, fp); + break; + case CTLVAR: + putc('$', fp); + putc('{', fp); + subtype = *++p; + if (subtype == VSLENGTH) + putc('#', fp); + + while (*p != '=') + putc(*p++, fp); + + if (subtype & VSNUL) + putc(':', fp); + + switch (subtype & VSTYPE) { + case VSNORMAL: + putc('}', fp); + break; + case VSMINUS: + putc('-', fp); + break; + case VSPLUS: + putc('+', fp); + break; + case VSQUESTION: + putc('?', fp); + break; + case VSASSIGN: + putc('=', fp); + break; + case VSTRIMLEFT: + putc('#', fp); + break; + case VSTRIMLEFTMAX: + putc('#', fp); + putc('#', fp); + break; + case VSTRIMRIGHT: + putc('%', fp); + break; + case VSTRIMRIGHTMAX: + putc('%', fp); + putc('%', fp); + break; + case VSLENGTH: + break; + default: + printf("<subtype %d>", subtype); + } + break; + case CTLENDVAR: + putc('}', fp); + break; + case CTLBACKQ: + case CTLBACKQ|CTLQUOTE: + putc('$', fp); + putc('(', fp); + shtree(bqlist->n, -1, NULL, fp); + putc(')', fp); + break; + default: + putc(*p, fp); + break; + } + } +} + + +static void +indent(int amount, char *pfx, FILE *fp) +{ + int i; + + for (i = 0 ; i < amount ; i++) { + if (pfx && i == amount - 1) + fputs(pfx, fp); + putc('\t', fp); + } +} + + +/* + * Debugging stuff. + */ + + +FILE *tracefile; + +#if DEBUG >= 2 +int debug = 1; +#else +int debug = 0; +#endif + + +void +trputc(int c) +{ + if (tracefile == NULL) + return; + putc(c, tracefile); + if (c == '\n') + fflush(tracefile); +} + + +void +sh_trace(const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + if (tracefile != NULL) { + (void) vfprintf(tracefile, fmt, va); + if (strchr(fmt, '\n')) + (void) fflush(tracefile); + } + va_end(va); +} + + +void +trputs(const char *s) +{ + if (tracefile == NULL) + return; + fputs(s, tracefile); + if (strchr(s, '\n')) + fflush(tracefile); +} + + +static void +trstring(char *s) +{ + char *p; + char c; + + if (tracefile == NULL) + return; + putc('"', tracefile); + for (p = s ; *p ; p++) { + switch (*p) { + case '\n': c = 'n'; goto backslash; + case '\t': c = 't'; goto backslash; + case '\r': c = 'r'; goto backslash; + case '"': c = '"'; goto backslash; + case '\\': c = '\\'; goto backslash; + case CTLESC: c = 'e'; goto backslash; + case CTLVAR: c = 'v'; goto backslash; + case CTLVAR+CTLQUOTE: c = 'V'; goto backslash; + case CTLBACKQ: c = 'q'; goto backslash; + case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash; +backslash: putc('\\', tracefile); + putc(c, tracefile); + break; + default: + if (*p >= ' ' && *p <= '~') + putc(*p, tracefile); + else { + putc('\\', tracefile); + putc(*p >> 6 & 03, tracefile); + putc(*p >> 3 & 07, tracefile); + putc(*p & 07, tracefile); + } + break; + } + } + putc('"', tracefile); +} + + +void +trargs(char **ap) +{ + if (tracefile == NULL) + return; + while (*ap) { + trstring(*ap++); + if (*ap) + putc(' ', tracefile); + else + putc('\n', tracefile); + } + fflush(tracefile); +} + + +void +opentrace(void) +{ + char s[100]; + int flags; + + if (!debug) + return; +#ifdef not_this_way + { + char *p; + if ((p = getenv("HOME")) == NULL) { + if (geteuid() == 0) + p = "/"; + else + p = "/tmp"; + } + strcpy(s, p); + strcat(s, "/trace"); + } +#else + strcpy(s, "./trace"); +#endif /* not_this_way */ + if ((tracefile = fopen(s, "a")) == NULL) { + fprintf(stderr, "Can't open %s: %s\n", s, strerror(errno)); + return; + } + if ((flags = fcntl(fileno(tracefile), F_GETFL, 0)) >= 0) + fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND); + fputs("\nTracing started.\n", tracefile); + fflush(tracefile); +} +#endif /* DEBUG */ diff --git a/bin/1sh/show.h b/bin/1sh/show.h new file mode 100644 index 00000000..68840549 --- /dev/null +++ b/bin/1sh/show.h @@ -0,0 +1,42 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)show.h 1.1 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/show.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +void showtree(union node *); +#ifdef DEBUG +void sh_trace(const char *, ...) __attribute__((format(printf, 1, 2))); +void trargs(char **); +void trputc(int); +void trputs(const char *); +void opentrace(void); +#endif diff --git a/bin/1sh/test.c b/bin/1sh/test.c new file mode 100644 index 00000000..54c16eef --- /dev/null +++ b/bin/1sh/test.c @@ -0,0 +1,630 @@ +/* $NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $ */ + +/*- + * test(1); version 7-like -- author Erik Baalbergen + * modified by Eric Gisin to be used as built-in. + * modified by Arnold Robbins to add SVR3 compatibility + * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). + * modified by J.T. Conklin for NetBSD. + * + * This program is in the Public Domain. + */ +/* + * Important: This file is used both as a standalone program /bin/test and + * as a builtin for /bin/sh (#define SHELL). + */ + +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/test/test.c 298232 2016-04-19 00:38:07Z araujo $ */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef SHELL +#define main testcmd +#include "bltin.h" +#else +#include <locale.h> + +static void error(const char *, ...) __attribute__((noreturn)) __attribute__((format(printf, 1, 2))); + +static void +error(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + verrx(2, msg, ap); + /*NOTREACHED*/ + va_end(ap); +} +#endif + +/* test(1) accepts the following grammar: + oexpr ::= aexpr | aexpr "-o" oexpr ; + aexpr ::= nexpr | nexpr "-a" aexpr ; + nexpr ::= primary | "!" primary + primary ::= unary-operator operand + | operand binary-operator operand + | operand + | "(" oexpr ")" + ; + unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| + "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; + + binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| + "-nt"|"-ot"|"-ef"; + operand ::= <any legal UNIX file name> +*/ + +enum token_types { + UNOP = 0x100, + BINOP = 0x200, + BUNOP = 0x300, + BBINOP = 0x400, + PAREN = 0x500 +}; + +enum token { + EOI, + OPERAND, + FILRD = UNOP + 1, + FILWR, + FILEX, + FILEXIST, + FILREG, + FILDIR, + FILCDEV, + FILBDEV, + FILFIFO, + FILSOCK, + FILSYM, + FILGZ, + FILTT, + FILSUID, + FILSGID, + FILSTCK, + STREZ, + STRNZ, + FILUID, + FILGID, + FILNT = BINOP + 1, + FILOT, + FILEQ, + STREQ, + STRNE, + STRLT, + STRGT, + INTEQ, + INTNE, + INTGE, + INTGT, + INTLE, + INTLT, + UNOT = BUNOP + 1, + BAND = BBINOP + 1, + BOR, + LPAREN = PAREN + 1, + RPAREN +}; + +#define TOKEN_TYPE(token) ((token) & 0xff00) + +static const struct t_op { + char op_text[2]; + short op_num; +} ops1[] = { + {"=", STREQ}, + {"<", STRLT}, + {">", STRGT}, + {"!", UNOT}, + {"(", LPAREN}, + {")", RPAREN}, +}, opsm1[] = { + {"r", FILRD}, + {"w", FILWR}, + {"x", FILEX}, + {"e", FILEXIST}, + {"f", FILREG}, + {"d", FILDIR}, + {"c", FILCDEV}, + {"b", FILBDEV}, + {"p", FILFIFO}, + {"u", FILSUID}, + {"g", FILSGID}, + {"k", FILSTCK}, + {"s", FILGZ}, + {"t", FILTT}, + {"z", STREZ}, + {"n", STRNZ}, + {"h", FILSYM}, /* for backwards compat */ + {"O", FILUID}, + {"G", FILGID}, + {"L", FILSYM}, + {"S", FILSOCK}, + {"a", BAND}, + {"o", BOR}, +}, ops2[] = { + {"==", STREQ}, + {"!=", STRNE}, +}, opsm2[] = { + {"eq", INTEQ}, + {"ne", INTNE}, + {"ge", INTGE}, + {"gt", INTGT}, + {"le", INTLE}, + {"lt", INTLT}, + {"nt", FILNT}, + {"ot", FILOT}, + {"ef", FILEQ}, +}; + +static int nargc; +static char **t_wp; +static int parenlevel; + +static int aexpr(enum token); +static int binop(enum token); +static int equalf(const char *, const char *); +static int filstat(char *, enum token); +static int getn(const char *); +static intmax_t getq(const char *); +static int intcmp(const char *, const char *); +static int isunopoperand(void); +static int islparenoperand(void); +static int isrparenoperand(void); +static int newerf(const char *, const char *); +static int nexpr(enum token); +static int oexpr(enum token); +static int olderf(const char *, const char *); +static int primary(enum token); +static void syntax(const char *, const char *); +static enum token t_lex(char *); + +int +main(int argc, char **argv) +{ + int res; + char *p; + + if ((p = strrchr(argv[0], '/')) == NULL) + p = argv[0]; + else + p++; + if (strcmp(p, "[") == 0) { + if (strcmp(argv[--argc], "]") != 0) + error("missing ]"); + argv[argc] = NULL; + } + + /* no expression => false */ + if (--argc <= 0) + return 1; + +#ifndef SHELL + (void)setlocale(LC_CTYPE, ""); +#endif + nargc = argc; + t_wp = &argv[1]; + parenlevel = 0; + if (nargc == 4 && strcmp(*t_wp, "!") == 0) { + /* Things like ! "" -o x do not fit in the normal grammar. */ + --nargc; + ++t_wp; + res = oexpr(t_lex(*t_wp)); + } else + res = !oexpr(t_lex(*t_wp)); + + if (--nargc > 0) + syntax(*t_wp, "unexpected operator"); + + return res; +} + +static void +syntax(const char *op, const char *msg) +{ + + if (op && *op) + error("%s: %s", op, msg); + else + error("%s", msg); +} + +static int +oexpr(enum token n) +{ + int res; + + res = aexpr(n); + if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR) + return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) || + res; + t_wp--; + nargc++; + return res; +} + +static int +aexpr(enum token n) +{ + int res; + + res = nexpr(n); + if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND) + return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) && + res; + t_wp--; + nargc++; + return res; +} + +static int +nexpr(enum token n) +{ + if (n == UNOT) + return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)); + return primary(n); +} + +static int +primary(enum token n) +{ + enum token nn; + int res; + + if (n == EOI) + return 0; /* missing expression */ + if (n == LPAREN) { + parenlevel++; + if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) == + RPAREN) { + parenlevel--; + return 0; /* missing expression */ + } + res = oexpr(nn); + if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN) + syntax(NULL, "closing paren expected"); + parenlevel--; + return res; + } + if (TOKEN_TYPE(n) == UNOP) { + /* unary expression */ + if (--nargc == 0) + syntax(NULL, "argument expected"); /* impossible */ + switch (n) { + case STREZ: + return strlen(*++t_wp) == 0; + case STRNZ: + return strlen(*++t_wp) != 0; + case FILTT: + return isatty(getn(*++t_wp)); + default: + return filstat(*++t_wp, n); + } + } + + nn = t_lex(nargc > 0 ? t_wp[1] : NULL); + if (TOKEN_TYPE(nn) == BINOP) + return binop(nn); + + return strlen(*t_wp) > 0; +} + +static int +binop(enum token n) +{ + const char *opnd1, *op, *opnd2; + + opnd1 = *t_wp; + op = nargc > 0 ? (--nargc, *++t_wp) : NULL; + + if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL) + syntax(op, "argument expected"); + + switch (n) { + case STREQ: + return strcmp(opnd1, opnd2) == 0; + case STRNE: + return strcmp(opnd1, opnd2) != 0; + case STRLT: + return strcmp(opnd1, opnd2) < 0; + case STRGT: + return strcmp(opnd1, opnd2) > 0; + case INTEQ: + return intcmp(opnd1, opnd2) == 0; + case INTNE: + return intcmp(opnd1, opnd2) != 0; + case INTGE: + return intcmp(opnd1, opnd2) >= 0; + case INTGT: + return intcmp(opnd1, opnd2) > 0; + case INTLE: + return intcmp(opnd1, opnd2) <= 0; + case INTLT: + return intcmp(opnd1, opnd2) < 0; + case FILNT: + return newerf (opnd1, opnd2); + case FILOT: + return olderf (opnd1, opnd2); + case FILEQ: + return equalf (opnd1, opnd2); + default: + abort(); + /* NOTREACHED */ + } +} + +static int +filstat(char *nm, enum token mode) +{ + struct stat s; + + if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) + return 0; + + switch (mode) { + case FILRD: + return (faccessat(AT_FDCWD, nm, R_OK, AT_EACCESS) == 0); + case FILWR: + return (faccessat(AT_FDCWD, nm, W_OK, AT_EACCESS) == 0); + case FILEX: + /* XXX work around eaccess(2) false positives for superuser */ + if (faccessat(AT_FDCWD, nm, X_OK, AT_EACCESS) != 0) + return 0; + if (S_ISDIR(s.st_mode) || geteuid() != 0) + return 1; + return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; + case FILEXIST: + return (faccessat(AT_FDCWD, nm, F_OK, AT_EACCESS) == 0); + case FILREG: + return S_ISREG(s.st_mode); + case FILDIR: + return S_ISDIR(s.st_mode); + case FILCDEV: + return S_ISCHR(s.st_mode); + case FILBDEV: + return S_ISBLK(s.st_mode); + case FILFIFO: + return S_ISFIFO(s.st_mode); + case FILSOCK: + return S_ISSOCK(s.st_mode); + case FILSYM: + return S_ISLNK(s.st_mode); + case FILSUID: + return (s.st_mode & S_ISUID) != 0; + case FILSGID: + return (s.st_mode & S_ISGID) != 0; + case FILSTCK: + return (s.st_mode & S_ISVTX) != 0; + case FILGZ: + return s.st_size > (off_t)0; + case FILUID: + return s.st_uid == geteuid(); + case FILGID: + return s.st_gid == getegid(); + default: + return 1; + } +} + +static int +find_op_1char(const struct t_op *op, const struct t_op *end, const char *s) +{ + char c; + + c = s[0]; + while (op != end) { + if (c == *op->op_text) + return op->op_num; + op++; + } + return OPERAND; +} + +static int +find_op_2char(const struct t_op *op, const struct t_op *end, const char *s) +{ + while (op != end) { + if (s[0] == op->op_text[0] && s[1] == op->op_text[1]) + return op->op_num; + op++; + } + return OPERAND; +} + +static int +find_op(const char *s) +{ + if (s[0] == '\0') + return OPERAND; + else if (s[1] == '\0') + return find_op_1char(ops1, (&ops1)[1], s); + else if (s[2] == '\0') + return s[0] == '-' ? find_op_1char(opsm1, (&opsm1)[1], s + 1) : + find_op_2char(ops2, (&ops2)[1], s); + else if (s[3] == '\0') + return s[0] == '-' ? find_op_2char(opsm2, (&opsm2)[1], s + 1) : + OPERAND; + else + return OPERAND; +} + +static enum token +t_lex(char *s) +{ + int num; + + if (s == NULL) { + return EOI; + } + num = find_op(s); + if (((TOKEN_TYPE(num) == UNOP || TOKEN_TYPE(num) == BUNOP) + && isunopoperand()) || + (num == LPAREN && islparenoperand()) || + (num == RPAREN && isrparenoperand())) + return OPERAND; + return num; +} + +static int +isunopoperand(void) +{ + char *s; + char *t; + int num; + + if (nargc == 1) + return 1; + s = *(t_wp + 1); + if (nargc == 2) + return parenlevel == 1 && strcmp(s, ")") == 0; + t = *(t_wp + 2); + num = find_op(s); + return TOKEN_TYPE(num) == BINOP && + (parenlevel == 0 || t[0] != ')' || t[1] != '\0'); +} + +static int +islparenoperand(void) +{ + char *s; + int num; + + if (nargc == 1) + return 1; + s = *(t_wp + 1); + if (nargc == 2) + return parenlevel == 1 && strcmp(s, ")") == 0; + if (nargc != 3) + return 0; + num = find_op(s); + return TOKEN_TYPE(num) == BINOP; +} + +static int +isrparenoperand(void) +{ + char *s; + + if (nargc == 1) + return 0; + s = *(t_wp + 1); + if (nargc == 2) + return parenlevel == 1 && strcmp(s, ")") == 0; + return 0; +} + +/* atoi with error detection */ +static int +getn(const char *s) +{ + char *p; + long r; + + errno = 0; + r = strtol(s, &p, 10); + + if (s == p) + error("%s: bad number", s); + + if (errno != 0) + error((errno == EINVAL) ? "%s: bad number" : + "%s: out of range", s); + + while (isspace((unsigned char)*p)) + p++; + + if (*p) + error("%s: bad number", s); + + return (int) r; +} + +/* atoi with error detection and 64 bit range */ +static intmax_t +getq(const char *s) +{ + char *p; + intmax_t r; + + errno = 0; + r = strtoimax(s, &p, 10); + + if (s == p) + error("%s: bad number", s); + + if (errno != 0) + error((errno == EINVAL) ? "%s: bad number" : + "%s: out of range", s); + + while (isspace((unsigned char)*p)) + p++; + + if (*p) + error("%s: bad number", s); + + return r; +} + +static int +intcmp (const char *s1, const char *s2) +{ + intmax_t q1, q2; + + + q1 = getq(s1); + q2 = getq(s2); + + if (q1 > q2) + return 1; + + if (q1 < q2) + return -1; + + return 0; +} + +static int +newerf (const char *f1, const char *f2) +{ + struct stat b1, b2; + + if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0) + return 0; + + if (b1.st_mtimespec.tv_sec > b2.st_mtimespec.tv_sec) + return 1; + if (b1.st_mtimespec.tv_sec < b2.st_mtimespec.tv_sec) + return 0; + + return (b1.st_mtimespec.tv_nsec > b2.st_mtimespec.tv_nsec); +} + +static int +olderf (const char *f1, const char *f2) +{ + return (newerf(f2, f1)); +} + +static int +equalf (const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat (f1, &b1) == 0 && + stat (f2, &b2) == 0 && + b1.st_dev == b2.st_dev && + b1.st_ino == b2.st_ino); +} diff --git a/bin/1sh/trap.c b/bin/1sh/trap.c new file mode 100644 index 00000000..fed13fcf --- /dev/null +++ b/bin/1sh/trap.c @@ -0,0 +1,553 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)trap.c 8.5 (Berkeley) 6/5/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/trap.c 326025 2017-11-20 19:49:47Z pfg $ */ + +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> + +#include "shell.h" +#include "main.h" +#include "nodes.h" /* for other headers */ +#include "eval.h" +#include "jobs.h" +#include "show.h" +#include "options.h" +#include "syntax.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "trap.h" +#include "mystring.h" +#include "builtins.h" +#include "myhistedit.h" + + +/* + * Sigmode records the current value of the signal handlers for the various + * modes. A value of zero means that the current handler is not known. + * S_HARD_IGN indicates that the signal was ignored on entry to the shell, + */ + +#define S_DFL 1 /* default signal handling (SIG_DFL) */ +#define S_CATCH 2 /* signal is caught */ +#define S_IGN 3 /* signal is ignored (SIG_IGN) */ +#define S_HARD_IGN 4 /* signal is ignored permanently */ +#define S_RESET 5 /* temporary - to reset a hard ignored sig */ + + +static char sigmode[NSIG]; /* current value of signal */ +volatile sig_atomic_t pendingsig; /* indicates some signal received */ +volatile sig_atomic_t pendingsig_waitcmd; /* indicates wait builtin should be interrupted */ +static int in_dotrap; /* do we execute in a trap handler? */ +static char *volatile trap[NSIG]; /* trap handler commands */ +static volatile sig_atomic_t gotsig[NSIG]; + /* indicates specified signal received */ +static int ignore_sigchld; /* Used while handling SIGCHLD traps. */ +static int last_trapsig; + +static int exiting; /* exitshell() has been called */ +static int exiting_exitstatus; /* value passed to exitshell() */ + +static int getsigaction(int, sig_t *); + + +/* + * Map a string to a signal number. + * + * Note: the signal number may exceed NSIG. + */ +static int +sigstring_to_signum(char *sig) +{ + + if (is_number(sig)) { + int signo; + + signo = atoi(sig); + return ((signo >= 0 && signo < NSIG) ? signo : (-1)); + } else if (strcasecmp(sig, "EXIT") == 0) { + return (0); + } else { + int n; + + if (strncasecmp(sig, "SIG", 3) == 0) + sig += 3; + for (n = 1; n < NSIG; n++) + if (sys_signame[n] && + strcasecmp(sys_signame[n], sig) == 0) + return (n); + } + return (-1); +} + + +/* + * Print a list of valid signal names. + */ +static void +printsignals(void) +{ + int n, outlen; + + outlen = 0; + for (n = 1; n < NSIG; n++) { + if (sys_signame[n]) { + out1fmt("%s", sys_signame[n]); + outlen += strlen(sys_signame[n]); + } else { + out1fmt("%d", n); + outlen += 3; /* good enough */ + } + ++outlen; + if (outlen > 71 || n == NSIG - 1) { + out1str("\n"); + outlen = 0; + } else { + out1c(' '); + } + } +} + + +/* + * The trap builtin. + */ +int +trapcmd(int argc __unused, char **argv) +{ + char *action; + int signo; + int errors = 0; + int i; + + while ((i = nextopt("l")) != '\0') { + switch (i) { + case 'l': + printsignals(); + return (0); + } + } + argv = argptr; + + if (*argv == NULL) { + for (signo = 0 ; signo < NSIG ; signo++) { + if (signo < NSIG && trap[signo] != NULL) { + out1str("trap -- "); + out1qstr(trap[signo]); + if (signo == 0) { + out1str(" EXIT\n"); + } else if (sys_signame[signo]) { + out1fmt(" %s\n", sys_signame[signo]); + } else { + out1fmt(" %d\n", signo); + } + } + } + return 0; + } + action = NULL; + if (*argv && !is_number(*argv)) { + if (strcmp(*argv, "-") == 0) + argv++; + else { + action = *argv; + argv++; + } + } + for (; *argv; argv++) { + if ((signo = sigstring_to_signum(*argv)) == -1) { + warning("bad signal %s", *argv); + errors = 1; + continue; + } + INTOFF; + if (action) + action = savestr(action); + if (trap[signo]) + ckfree(trap[signo]); + trap[signo] = action; + if (signo != 0) + setsignal(signo); + INTON; + } + return errors; +} + + +/* + * Clear traps on a fork. + */ +void +clear_traps(void) +{ + char *volatile *tp; + + for (tp = trap ; tp <= &trap[NSIG - 1] ; tp++) { + if (*tp && **tp) { /* trap not NULL or SIG_IGN */ + INTOFF; + ckfree(*tp); + *tp = NULL; + if (tp != &trap[0]) + setsignal(tp - trap); + INTON; + } + } +} + + +/* + * Check if we have any traps enabled. + */ +int +have_traps(void) +{ + char *volatile *tp; + + for (tp = trap ; tp <= &trap[NSIG - 1] ; tp++) { + if (*tp && **tp) /* trap not NULL or SIG_IGN */ + return 1; + } + return 0; +} + +/* + * Set the signal handler for the specified signal. The routine figures + * out what it should be set to. + */ +void +setsignal(int signo) +{ + int action; + sig_t sigact = SIG_DFL; + struct sigaction sa; + char *t; + + if ((t = trap[signo]) == NULL) + action = S_DFL; + else if (*t != '\0') + action = S_CATCH; + else + action = S_IGN; + if (action == S_DFL) { + switch (signo) { + case SIGINT: + action = S_CATCH; + break; + case SIGQUIT: +#ifdef DEBUG + { + extern int debug; + + if (debug) + break; + } +#endif + action = S_CATCH; + break; + case SIGTERM: + if (rootshell && iflag) + action = S_IGN; + break; +#if JOBS + case SIGTSTP: + case SIGTTOU: + if (rootshell && mflag) + action = S_IGN; + break; +#endif + } + } + + t = &sigmode[signo]; + if (*t == 0) { + /* + * current setting unknown + */ + if (!getsigaction(signo, &sigact)) { + /* + * Pretend it worked; maybe we should give a warning + * here, but other shells don't. We don't alter + * sigmode, so that we retry every time. + */ + return; + } + if (sigact == SIG_IGN) { + if (mflag && (signo == SIGTSTP || + signo == SIGTTIN || signo == SIGTTOU)) { + *t = S_IGN; /* don't hard ignore these */ + } else + *t = S_HARD_IGN; + } else { + *t = S_RESET; /* force to be set */ + } + } + if (*t == S_HARD_IGN || *t == action) + return; + switch (action) { + case S_DFL: sigact = SIG_DFL; break; + case S_CATCH: sigact = onsig; break; + case S_IGN: sigact = SIG_IGN; break; + } + *t = action; + sa.sa_handler = sigact; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(signo, &sa, NULL); +} + + +/* + * Return the current setting for sig w/o changing it. + */ +static int +getsigaction(int signo, sig_t *sigact) +{ + struct sigaction sa; + + if (sigaction(signo, (struct sigaction *)0, &sa) == -1) + return 0; + *sigact = (sig_t) sa.sa_handler; + return 1; +} + + +/* + * Ignore a signal. + */ +void +ignoresig(int signo) +{ + + if (sigmode[signo] == 0) + setsignal(signo); + if (sigmode[signo] != S_IGN && sigmode[signo] != S_HARD_IGN) { + signal(signo, SIG_IGN); + sigmode[signo] = S_IGN; + } +} + + +int +issigchldtrapped(void) +{ + + return (trap[SIGCHLD] != NULL && *trap[SIGCHLD] != '\0'); +} + + +/* + * Signal handler. + */ +void +onsig(int signo) +{ + + if (signo == SIGINT && trap[SIGINT] == NULL) { + /* + * The !in_dotrap here is safe. The only way we can arrive + * here with in_dotrap set is that a trap handler set SIGINT to + * SIG_DFL and killed itself. + */ + if (suppressint && !in_dotrap) + SET_PENDING_INT; + else + onint(); + return; + } + + /* If we are currently in a wait builtin, prepare to break it */ + if (signo == SIGINT || signo == SIGQUIT) + pendingsig_waitcmd = signo; + + if (trap[signo] != NULL && trap[signo][0] != '\0' && + (signo != SIGCHLD || !ignore_sigchld)) { + gotsig[signo] = 1; + pendingsig = signo; + pendingsig_waitcmd = signo; + } +} + + +/* + * Called to execute a trap. Perhaps we should avoid entering new trap + * handlers while we are executing a trap handler. + */ +void +dotrap(void) +{ + struct stackmark smark; + int i; + int savestatus, prev_evalskip, prev_skipcount; + + in_dotrap++; + for (;;) { + pendingsig = 0; + pendingsig_waitcmd = 0; + for (i = 1; i < NSIG; i++) { + if (gotsig[i]) { + gotsig[i] = 0; + if (trap[i]) { + /* + * Ignore SIGCHLD to avoid infinite + * recursion if the trap action does + * a fork. + */ + if (i == SIGCHLD) + ignore_sigchld++; + + /* + * Backup current evalskip + * state and reset it before + * executing a trap, so that the + * trap is not disturbed by an + * ongoing break/continue/return + * statement. + */ + prev_evalskip = evalskip; + prev_skipcount = skipcount; + evalskip = 0; + + last_trapsig = i; + savestatus = exitstatus; + setstackmark(&smark); + evalstring(stsavestr(trap[i]), 0); + popstackmark(&smark); + + /* + * If such a command was not + * already in progress, allow a + * break/continue/return in the + * trap action to have an effect + * outside of it. + */ + if (evalskip == 0 || + prev_evalskip != 0) { + evalskip = prev_evalskip; + skipcount = prev_skipcount; + exitstatus = savestatus; + } + + if (i == SIGCHLD) + ignore_sigchld--; + } + break; + } + } + if (i >= NSIG) + break; + } + in_dotrap--; +} + + +/* + * Controls whether the shell is interactive or not based on iflag. + */ +void +setinteractive(void) +{ + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); +} + + +/* + * Called to exit the shell. + */ +void +exitshell(int status) +{ + TRACE(("exitshell(%d) pid=%d\n", status, getpid())); + exiting = 1; + exiting_exitstatus = status; + exitshell_savedstatus(); +} + +void +exitshell_savedstatus(void) +{ + struct jmploc loc1, loc2; + char *p; + int sig = 0; + sigset_t sigs; + + if (!exiting) { + if (in_dotrap && last_trapsig) { + sig = last_trapsig; + exiting_exitstatus = sig + 128; + } else + exiting_exitstatus = oexitstatus; + } + exitstatus = oexitstatus = exiting_exitstatus; + if (!setjmp(loc1.loc)) { + handler = &loc1; + if ((p = trap[0]) != NULL && *p != '\0') { + /* + * Reset evalskip, or the trap on EXIT could be + * interrupted if the last command was a "return". + */ + evalskip = 0; + trap[0] = NULL; + FORCEINTON; + evalstring(p, 0); + } + } + if (!setjmp(loc2.loc)) { + handler = &loc2; /* probably unnecessary */ + FORCEINTON; + flushall(); +#if JOBS + setjobctl(0); +#endif + } + if (sig != 0 && sig != SIGSTOP && sig != SIGTSTP && sig != SIGTTIN && + sig != SIGTTOU) { + signal(sig, SIG_DFL); + sigemptyset(&sigs); + sigaddset(&sigs, sig); + sigprocmask(SIG_UNBLOCK, &sigs, NULL); + kill(getpid(), sig); + /* If the default action is to ignore, fall back to _exit(). */ + } + _exit(exiting_exitstatus); +} diff --git a/bin/1sh/trap.h b/bin/1sh/trap.h new file mode 100644 index 00000000..75427d56 --- /dev/null +++ b/bin/1sh/trap.h @@ -0,0 +1,50 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)trap.h 8.3 (Berkeley) 6/5/95 + * $FreeBSD: releng/12.1/bin/sh/trap.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +extern volatile sig_atomic_t pendingsig; +extern volatile sig_atomic_t pendingsig_waitcmd; + +void clear_traps(void); +int have_traps(void); +void setsignal(int); +void ignoresig(int); +int issigchldtrapped(void); +void onsig(int); +void dotrap(void); +void setinteractive(void); +void exitshell(int) __attribute__((noreturn)); +void exitshell_savedstatus(void) __attribute__((noreturn)); diff --git a/bin/1sh/var.c b/bin/1sh/var.c new file mode 100644 index 00000000..44742169 --- /dev/null +++ b/bin/1sh/var.c @@ -0,0 +1,983 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/var.c 329221 2018-02-13 16:48:57Z bdrewery $ */ + +#include <unistd.h> +#include <stdlib.h> +#include <paths.h> + +/* + * Shell variables. + */ + +#include <locale.h> +#include <langinfo.h> + +#include "shell.h" +#include "output.h" +#include "expand.h" +#include "nodes.h" /* for other headers */ +#include "eval.h" /* defines cmdenviron */ +#include "exec.h" +#include "syntax.h" +#include "options.h" +#include "mail.h" +#include "var.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "parser.h" +#include "builtins.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + + +#ifndef VTABSIZE +#define VTABSIZE 39 +#endif + + +struct varinit { + struct var *var; + int flags; + const char *text; + void (*func)(const char *); +}; + + +#ifndef NO_HISTORY +struct var vhistfile; +struct var vhistsize; +struct var vterm; +#endif +struct var vifs; +struct var vmail; +struct var vmpath; +struct var vpath; +struct var vps0; +struct var vps1; +struct var vps2; +struct var vps4; +struct var vrps1; +struct var vrps2; +static struct var voptind; +struct var vdisvfork; + +struct localvar *localvars; +int forcelocal; + +static const struct varinit varinit[] = { +#ifndef NO_HISTORY + { &vhistfile, VUNSET, "HISTFILE=", + sethistfile }, + { &vhistsize, VUNSET, "HISTSIZE=", + sethistsize }, +#endif + { &vifs, 0, "IFS= \t\n", + NULL }, + { &vmail, VUNSET, "MAIL=", + NULL }, + { &vmpath, VUNSET, "MAILPATH=", + NULL }, + { &vpath, 0, "PATH=" _PATH_DEFPATH, + changepath }, + { &vps0, VUNSET, "PS0=", + NULL }, + /* + * vps1 depends on uid + */ + { &vps2, 0, "PS2=> ", + NULL }, + { &vps4, 0, "PS4=+ ", + NULL }, + { &vrps1, VUNSET, "RPS1=", + NULL }, + { &vrps2, VUNSET, "RPS2=", + NULL }, +#ifndef NO_HISTORY + { &vterm, VUNSET, "TERM=", + setterm }, +#endif + { &voptind, 0, "OPTIND=1", + getoptsreset }, + { &vdisvfork, VUNSET, "SH_DISABLE_VFORK=", + NULL }, + { NULL, 0, NULL, + NULL } +}; + +static struct var *vartab[VTABSIZE]; + +static const char *const locale_names[7] = { + "LC_COLLATE", "LC_CTYPE", "LC_MONETARY", + "LC_NUMERIC", "LC_TIME", "LC_MESSAGES", NULL +}; +static const int locale_categories[7] = { + LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME, LC_MESSAGES, 0 +}; + +static int varequal(const char *, const char *); +static struct var *find_var(const char *, struct var ***, int *); +static int localevar(const char *); +static void setvareq_const(const char *s, int flags); + +extern char **environ; + +/* + * This routine initializes the builtin variables and imports the environment. + * It is called when the shell is initialized. + */ + +void +initvar(void) +{ + char ppid[20]; + const struct varinit *ip; + struct var *vp; + struct var **vpp; + char **envp; + + for (ip = varinit ; (vp = ip->var) != NULL ; ip++) { + if (find_var(ip->text, &vpp, &vp->name_len) != NULL) + continue; + vp->next = *vpp; + *vpp = vp; + vp->text = (char *)(ip->text); + vp->flags = ip->flags | VSTRFIXED | VTEXTFIXED; + vp->func = ip->func; + } + /* + * PS1 depends on uid + */ + if (find_var("PS1", &vpp, &vps1.name_len) == NULL) { + vps1.next = *vpp; + *vpp = &vps1; + vps1.text = (char *)(geteuid() ? "PS1=$ " : "PS1=# "); + vps1.flags = VSTRFIXED|VTEXTFIXED; + } + fmtstr(ppid, sizeof(ppid), "%d", (int)getppid()); + setvarsafe("PPID", ppid, 0); + for (envp = environ ; *envp ; envp++) { + if (strchr(*envp, '=')) { + setvareq(*envp, VEXPORT|VTEXTFIXED); + } + } + setvareq_const("OPTIND=1", 0); + setvareq_const("IFS= \t\n", 0); +} + +/* + * Safe version of setvar, returns 1 on success 0 on failure. + */ + +int +setvarsafe(const char *name, const char *val, int flags) +{ + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + int err = 0; + int inton; + + inton = is_int_on(); + if (setjmp(jmploc.loc)) + err = 1; + else { + handler = &jmploc; + setvar(name, val, flags); + } + handler = savehandler; + SETINTON(inton); + return err; +} + +/* + * Set the value of a variable. The flags argument is stored with the + * flags of the variable. If val is NULL, the variable is unset. + */ + +void +setvar(const char *name, const char *val, int flags) +{ + const char *p; + size_t len; + size_t namelen; + size_t vallen; + char *nameeq; + int isbad; + + isbad = 0; + p = name; + if (! is_name(*p)) + isbad = 1; + p++; + for (;;) { + if (! is_in_name(*p)) { + if (*p == '\0' || *p == '=') + break; + isbad = 1; + } + p++; + } + namelen = p - name; + if (isbad) + error("%.*s: bad variable name", (int)namelen, name); + len = namelen + 2; /* 2 is space for '=' and '\0' */ + if (val == NULL) { + flags |= VUNSET; + vallen = 0; + } else { + vallen = strlen(val); + len += vallen; + } + INTOFF; + nameeq = ckmalloc(len); + memcpy(nameeq, name, namelen); + nameeq[namelen] = '='; + if (val) + memcpy(nameeq + namelen + 1, val, vallen + 1); + else + nameeq[namelen + 1] = '\0'; + setvareq(nameeq, flags); + INTON; +} + +static int +localevar(const char *s) +{ + const char *const *ss; + + if (*s != 'L') + return 0; + if (varequal(s + 1, "ANG")) + return 1; + if (strncmp(s + 1, "C_", 2) != 0) + return 0; + if (varequal(s + 3, "ALL")) + return 1; + for (ss = locale_names; *ss ; ss++) + if (varequal(s + 3, *ss + 3)) + return 1; + return 0; +} + + +/* + * Sets/unsets an environment variable from a pointer that may actually be a + * pointer into environ where the string should not be manipulated. + */ +static void +change_env(const char *s, int set) +{ + char *eqp; + char *ss; + + INTOFF; + ss = savestr(s); + if ((eqp = strchr(ss, '=')) != NULL) + *eqp = '\0'; + if (set && eqp != NULL) + (void) setenv(ss, eqp + 1, 1); + else + (void) unsetenv(ss); + ckfree(ss); + INTON; + + return; +} + + +/* + * Same as setvar except that the variable and value are passed in + * the first argument as name=value. Since the first argument will + * be actually stored in the table, it should not be a string that + * will go away. + */ + +void +setvareq(char *s, int flags) +{ + struct var *vp, **vpp; + int nlen; + + if (aflag) + flags |= VEXPORT; + if (forcelocal && !(flags & (VNOSET | VNOLOCAL))) + mklocal(s); + vp = find_var(s, &vpp, &nlen); + if (vp != NULL) { + if (vp->flags & VREADONLY) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + error("%.*s: is read only", vp->name_len, vp->text); + } + if (flags & VNOSET) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + return; + } + INTOFF; + + if (vp->func && (flags & VNOFUNC) == 0) + (*vp->func)(s + vp->name_len + 1); + + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(vp->text); + + vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET); + vp->flags |= flags; + vp->text = s; + + /* + * We could roll this to a function, to handle it as + * a regular variable function callback, but why bother? + * + * Note: this assumes iflag is not set to 1 initially. + * As part of initvar(), this is called before arguments + * are looked at. + */ + if ((vp == &vmpath || (vp == &vmail && ! mpathset())) && + iflag == 1) + chkmail(1); + if ((vp->flags & VEXPORT) && localevar(s)) { + change_env(s, 1); + (void) setlocale(LC_ALL, ""); + updatecharset(); + } + INTON; + return; + } + /* not found */ + if (flags & VNOSET) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + return; + } + INTOFF; + vp = ckmalloc(sizeof (*vp)); + vp->flags = flags; + vp->text = s; + vp->name_len = nlen; + vp->next = *vpp; + vp->func = NULL; + *vpp = vp; + if ((vp->flags & VEXPORT) && localevar(s)) { + change_env(s, 1); + (void) setlocale(LC_ALL, ""); + updatecharset(); + } + INTON; +} + + +static void +setvareq_const(const char *s, int flags) +{ + setvareq((char *)(s), flags | VTEXTFIXED); +} + + +/* + * Process a linked list of variable assignments. + */ + +void +listsetvar(struct arglist *list, int flags) +{ + int i; + + INTOFF; + for (i = 0; i < list->count; i++) + setvareq(savestr(list->args[i]), flags); + INTON; +} + + + +/* + * Find the value of a variable. Returns NULL if not set. + */ + +char * +lookupvar(const char *name) +{ + struct var *v; + + v = find_var(name, NULL, NULL); + if (v == NULL || v->flags & VUNSET) + return NULL; + return v->text + v->name_len + 1; +} + + + +/* + * Search the environment of a builtin command. If the second argument + * is nonzero, return the value of a variable even if it hasn't been + * exported. + */ + +char * +bltinlookup(const char *name, int doall) +{ + struct var *v; + char *result; + int i; + + result = NULL; + if (cmdenviron) for (i = 0; i < cmdenviron->count; i++) { + if (varequal(cmdenviron->args[i], name)) + result = strchr(cmdenviron->args[i], '=') + 1; + } + if (result != NULL) + return result; + + v = find_var(name, NULL, NULL); + if (v == NULL || v->flags & VUNSET || + (!doall && (v->flags & VEXPORT) == 0)) + return NULL; + return v->text + v->name_len + 1; +} + + +/* + * Set up locale for a builtin (LANG/LC_* assignments). + */ +void +bltinsetlocale(void) +{ + int act = 0; + char *loc, *locdef; + int i; + + if (cmdenviron) for (i = 0; i < cmdenviron->count; i++) { + if (localevar(cmdenviron->args[i])) { + act = 1; + break; + } + } + if (!act) + return; + loc = bltinlookup("LC_ALL", 0); + INTOFF; + if (loc != NULL) { + setlocale(LC_ALL, loc); + INTON; + updatecharset(); + return; + } + locdef = bltinlookup("LANG", 0); + for (i = 0; locale_names[i] != NULL; i++) { + loc = bltinlookup(locale_names[i], 0); + if (loc == NULL) + loc = locdef; + if (loc != NULL) + setlocale(locale_categories[i], loc); + } + INTON; + updatecharset(); +} + +/* + * Undo the effect of bltinlocaleset(). + */ +void +bltinunsetlocale(void) +{ + int i; + + INTOFF; + if (cmdenviron) for (i = 0; i < cmdenviron->count; i++) { + if (localevar(cmdenviron->args[i])) { + setlocale(LC_ALL, ""); + updatecharset(); + break; + } + } + INTON; +} + +/* + * Update the localeisutf8 flag. + */ +void +updatecharset(void) +{ + char *charset; + + charset = nl_langinfo(CODESET); + localeisutf8 = !strcmp(charset, "UTF-8"); +} + +void +initcharset(void) +{ + updatecharset(); + initial_localeisutf8 = localeisutf8; +} + +/* + * Generate a list of exported variables. This routine is used to construct + * the third argument to execve when executing a program. + */ + +char ** +environment(void) +{ + int nenv; + struct var **vpp; + struct var *vp; + char **env, **ep; + + nenv = 0; + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) + if (vp->flags & VEXPORT) + nenv++; + } + ep = env = stalloc((nenv + 1) * sizeof *env); + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) + if (vp->flags & VEXPORT) + *ep++ = vp->text; + } + *ep = NULL; + return env; +} + + +static int +var_compare(const void *a, const void *b) +{ + const char *const *sa, *const *sb; + + sa = a; + sb = b; + /* + * This compares two var=value strings which creates a different + * order from what you would probably expect. POSIX is somewhat + * ambiguous on what should be sorted exactly. + */ + return strcoll(*sa, *sb); +} + + +/* + * Command to list all variables which are set. This is invoked from the + * set command when it is called without any options or operands. + */ + +int +showvarscmd(int argc __unused, char **argv __unused) +{ + struct var **vpp; + struct var *vp; + const char *s; + const char **vars; + int i, n; + + /* + * POSIX requires us to sort the variables. + */ + n = 0; + for (vpp = vartab; vpp < vartab + VTABSIZE; vpp++) { + for (vp = *vpp; vp; vp = vp->next) { + if (!(vp->flags & VUNSET)) + n++; + } + } + + INTOFF; + vars = ckmalloc(n * sizeof(*vars)); + i = 0; + for (vpp = vartab; vpp < vartab + VTABSIZE; vpp++) { + for (vp = *vpp; vp; vp = vp->next) { + if (!(vp->flags & VUNSET)) + vars[i++] = vp->text; + } + } + + qsort(vars, n, sizeof(*vars), var_compare); + for (i = 0; i < n; i++) { + /* + * Skip improper variable names so the output remains usable as + * shell input. + */ + if (!isassignment(vars[i])) + continue; + s = strchr(vars[i], '='); + s++; + outbin(vars[i], s - vars[i], out1); + out1qstr(s); + out1c('\n'); + } + ckfree(vars); + INTON; + + return 0; +} + + + +/* + * The export and readonly commands. + */ + +int +exportcmd(int argc __unused, char **argv) +{ + struct var **vpp; + struct var *vp; + char **ap; + char *name; + char *p; + char *cmdname; + int ch, values; + int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT; + + cmdname = argv[0]; + values = 0; + while ((ch = nextopt("p")) != '\0') { + switch (ch) { + case 'p': + values = 1; + break; + } + } + + if (values && *argptr != NULL) + error("-p requires no arguments"); + if (*argptr != NULL) { + for (ap = argptr; (name = *ap) != NULL; ap++) { + if ((p = strchr(name, '=')) != NULL) { + p++; + } else { + vp = find_var(name, NULL, NULL); + if (vp != NULL) { + vp->flags |= flag; + if ((vp->flags & VEXPORT) && localevar(vp->text)) { + change_env(vp->text, 1); + (void) setlocale(LC_ALL, ""); + updatecharset(); + } + continue; + } + } + setvar(name, p, flag); + } + } else { + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) { + if (vp->flags & flag) { + if (values) { + /* + * Skip improper variable names + * so the output remains usable + * as shell input. + */ + if (!isassignment(vp->text)) + continue; + out1str(cmdname); + out1c(' '); + } + if (values && !(vp->flags & VUNSET)) { + outbin(vp->text, + vp->name_len + 1, out1); + out1qstr(vp->text + + vp->name_len + 1); + } else + outbin(vp->text, vp->name_len, + out1); + out1c('\n'); + } + } + } + } + return 0; +} + + +/* + * The "local" command. + */ + +int +localcmd(int argc __unused, char **argv __unused) +{ + char *name; + + nextopt(""); + if (! in_function()) + error("Not in a function"); + while ((name = *argptr++) != NULL) { + mklocal(name); + } + return 0; +} + + +/* + * Make a variable a local variable. When a variable is made local, it's + * value and flags are saved in a localvar structure. The saved values + * will be restored when the shell function returns. We handle the name + * "-" as a special case. + */ + +void +mklocal(char *name) +{ + struct localvar *lvp; + struct var **vpp; + struct var *vp; + + INTOFF; + lvp = ckmalloc(sizeof (struct localvar)); + if (name[0] == '-' && name[1] == '\0') { + lvp->text = ckmalloc(sizeof optval); + memcpy(lvp->text, optval, sizeof optval); + vp = NULL; + } else { + vp = find_var(name, &vpp, NULL); + if (vp == NULL) { + if (strchr(name, '=')) + setvareq(savestr(name), VSTRFIXED | VNOLOCAL); + else + setvar(name, NULL, VSTRFIXED | VNOLOCAL); + vp = *vpp; /* the new variable */ + lvp->text = NULL; + lvp->flags = VUNSET; + } else { + lvp->text = vp->text; + lvp->flags = vp->flags; + vp->flags |= VSTRFIXED|VTEXTFIXED; + if (name[vp->name_len] == '=') + setvareq(savestr(name), VNOLOCAL); + } + } + lvp->vp = vp; + lvp->next = localvars; + localvars = lvp; + INTON; +} + + +/* + * Called after a function returns. + */ + +void +poplocalvars(void) +{ + struct localvar *lvp; + struct var *vp; + int islocalevar; + + INTOFF; + while ((lvp = localvars) != NULL) { + localvars = lvp->next; + vp = lvp->vp; + if (vp == NULL) { /* $- saved */ + memcpy(optval, lvp->text, sizeof optval); + ckfree(lvp->text); + optschanged(); + } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + vp->flags &= ~VREADONLY; + (void)unsetvar(vp->text); + } else { + islocalevar = (vp->flags | lvp->flags) & VEXPORT && + localevar(lvp->text); + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + vp->flags = lvp->flags; + vp->text = lvp->text; + if (vp->func) + (*vp->func)(vp->text + vp->name_len + 1); + if (islocalevar) { + change_env(vp->text, vp->flags & VEXPORT && + (vp->flags & VUNSET) == 0); + setlocale(LC_ALL, ""); + updatecharset(); + } + } + ckfree(lvp); + } + INTON; +} + + +int +setvarcmd(int argc, char **argv) +{ + if (argc <= 2) + return unsetcmd(argc, argv); + else if (argc == 3) + setvar(argv[1], argv[2], 0); + else + error("too many arguments"); + return 0; +} + + +/* + * The unset builtin command. + */ + +int +unsetcmd(int argc __unused, char **argv __unused) +{ + char **ap; + int i; + int flg_func = 0; + int flg_var = 0; + int ret = 0; + + while ((i = nextopt("vf")) != '\0') { + if (i == 'f') + flg_func = 1; + else + flg_var = 1; + } + if (flg_func == 0 && flg_var == 0) + flg_var = 1; + + INTOFF; + for (ap = argptr; *ap ; ap++) { + if (flg_func) + ret |= unsetfunc(*ap); + if (flg_var) + ret |= unsetvar(*ap); + } + INTON; + return ret; +} + + +/* + * Unset the specified variable. + * Called with interrupts off. + */ + +int +unsetvar(const char *s) +{ + struct var **vpp; + struct var *vp; + + vp = find_var(s, &vpp, NULL); + if (vp == NULL) + return (0); + if (vp->flags & VREADONLY) + return (1); + if (vp->text[vp->name_len + 1] != '\0') + setvar(s, "", 0); + if ((vp->flags & VEXPORT) && localevar(vp->text)) { + change_env(s, 0); + setlocale(LC_ALL, ""); + updatecharset(); + } + vp->flags &= ~VEXPORT; + vp->flags |= VUNSET; + if ((vp->flags & VSTRFIXED) == 0) { + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + *vpp = vp->next; + ckfree(vp); + } + return (0); +} + + + +/* + * Returns true if the two strings specify the same variable. The first + * variable name is terminated by '='; the second may be terminated by + * either '=' or '\0'. + */ + +static int +varequal(const char *p, const char *q) +{ + while (*p == *q++) { + if (*p++ == '=') + return 1; + } + if (*p == '=' && *(q - 1) == '\0') + return 1; + return 0; +} + +/* + * Search for a variable. + * 'name' may be terminated by '=' or a NUL. + * vppp is set to the pointer to vp, or the list head if vp isn't found + * lenp is set to the number of characters in 'name' + */ + +static struct var * +find_var(const char *name, struct var ***vppp, int *lenp) +{ + unsigned int hashval; + int len; + struct var *vp, **vpp; + const char *p = name; + + hashval = 0; + while (*p && *p != '=') + hashval = 2 * hashval + (unsigned char)*p++; + len = p - name; + + if (lenp) + *lenp = len; + vpp = &vartab[hashval % VTABSIZE]; + if (vppp) + *vppp = vpp; + + for (vp = *vpp ; vp ; vpp = &vp->next, vp = *vpp) { + if (vp->name_len != len) + continue; + if (memcmp(vp->text, name, len) != 0) + continue; + if (vppp) + *vppp = vpp; + return vp; + } + return NULL; +} diff --git a/bin/1sh/var.h b/bin/1sh/var.h new file mode 100644 index 00000000..d06d8688 --- /dev/null +++ b/bin/1sh/var.h @@ -0,0 +1,140 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)var.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/var.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* + * Shell variables. + */ + +/* flags */ +#define VEXPORT 0x01 /* variable is exported */ +#define VREADONLY 0x02 /* variable cannot be modified */ +#define VSTRFIXED 0x04 /* variable struct is statically allocated */ +#define VTEXTFIXED 0x08 /* text is statically allocated */ +#define VSTACK 0x10 /* text is allocated on the stack */ +#define VUNSET 0x20 /* the variable is not set */ +#define VNOFUNC 0x40 /* don't call the callback function */ +#define VNOSET 0x80 /* do not set variable - just readonly test */ +#define VNOLOCAL 0x100 /* ignore forcelocal */ + + +struct var { + struct var *next; /* next entry in hash list */ + int flags; /* flags are defined above */ + int name_len; /* length of name */ + char *text; /* name=value */ + void (*func)(const char *); + /* function to be called when */ + /* the variable gets set/unset */ +}; + + +struct localvar { + struct localvar *next; /* next local variable in list */ + struct var *vp; /* the variable that was made local */ + int flags; /* saved flags */ + char *text; /* saved text */ +}; + + +extern struct localvar *localvars; +extern int forcelocal; + +extern struct var vifs; +extern struct var vmail; +extern struct var vmpath; +extern struct var vpath; +extern struct var vps0; +extern struct var vps1; +extern struct var vps2; +extern struct var vps4; +extern struct var vrps1; +extern struct var vrps2; +extern struct var vdisvfork; +#ifndef NO_HISTORY +extern struct var vhistfile; +extern struct var vhistsize; +extern struct var vterm; +#endif + +extern int localeisutf8; +/* The parser uses the locale that was in effect at startup. */ +extern int initial_localeisutf8; + +/* + * The following macros access the values of the above variables. + * They have to skip over the name. They return the null string + * for unset variables. + */ + +#define ifsval() (vifs.text + 4) +#define ifsset() ((vifs.flags & VUNSET) == 0) +#define mailval() (vmail.text + 5) +#define mpathval() (vmpath.text + 9) +#define pathval() (vpath.text + 5) +#define ps0val() (vps0.text + 4) +#define ps1val() (vps1.text + 4) +#define ps2val() (vps2.text + 4) +#define ps4val() (vps4.text + 4) +#define rps1val() (vrps1.text + 5) +#define rps2val() (vrps2.text + 5) +#define optindval() (voptind.text + 7) +#ifndef NO_HISTORY +#define histfileval() (vhistfile.text + 9) +#define histsizeval() (vhistsize.text + 9) +#define termval() (vterm.text + 5) +#endif + +#define mpathset() ((vmpath.flags & VUNSET) == 0) +#define disvforkset() ((vdisvfork.flags & VUNSET) == 0) + +void initvar(void); +void setvar(const char *, const char *, int); +void setvareq(char *, int); +struct arglist; +void listsetvar(struct arglist *, int); +char *lookupvar(const char *); +char *bltinlookup(const char *, int); +void bltinsetlocale(void); +void bltinunsetlocale(void); +void updatecharset(void); +void initcharset(void); +char **environment(void); +int showvarscmd(int, char **); +void mklocal(char *); +void poplocalvars(void); +int unsetvar(const char *); +int setvarsafe(const char *, const char *, int); diff --git a/bin/LICENSE b/bin/LICENSE new file mode 100644 index 00000000..dba13ed2 --- /dev/null +++ b/bin/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<http://www.gnu.org/licenses/>. diff --git a/bin/Makefile b/bin/Makefile new file mode 100644 index 00000000..5691b0fc --- /dev/null +++ b/bin/Makefile @@ -0,0 +1,153 @@ +PREFIX = ~/.local +MANDIR = ${PREFIX}/share/man + +LIBS_PREFIX = /usr/local +CFLAGS += -I${LIBS_PREFIX}/include +LDFLAGS += -L${LIBS_PREFIX}/lib + +CFLAGS += -Wall -Wextra -Wpedantic -Wno-gnu-case-range +LDLIBS.dtch = -lutil +LDLIBS.fbclock = -lz +LDLIBS.glitch = -lz +LDLIBS.hnel = -lutil +LDLIBS.modem = -lutil +LDLIBS.pngo = -lz +LDLIBS.ptee = -lutil +LDLIBS.relay = -ltls +LDLIBS.scheme = -lm +LDLIBS.title = -lcurl + +-include config.mk + +BINS += beef +BINS += bibsort +BINS += bit +BINS += c +BINS += dtch +BINS += glitch +BINS += hi +BINS += hnel +BINS += modem +BINS += nudge +BINS += order +BINS += pbd +BINS += pngo +BINS += psf2png +BINS += ptee +BINS += scheme +BINS += shotty +BINS += title +BINS += ttpre +BINS += up +BINS += when +BINS += xx + +LINKS += open +LINKS += pbcopy +LINKS += pbpaste + +BINS_BSD += ever +BINS_LINUX += bri +BINS_LINUX += fbatt +BINS_LINUX += fbclock +BINS_LINUX += psfed +BINS_TLS += relay + +BINS_ALL = ${BINS} ${BINS_BSD} ${BINS_LINUX} ${BINS_TLS} +MANS_ALL = ${BINS_ALL:%=man1/%.1} + +any: meta ${BINS} ${LINKS} + +bsd: meta ${BINS_BSD} + +linux: meta ${BINS_LINUX} + +tls: meta ${BINS_TLS} + +meta: .gitignore tags + +.SUFFIXES: .pl + +.c: + ${CC} ${CFLAGS} ${LDFLAGS} $< ${LDLIBS.$@} -o $@ + +.o: + ${CC} ${LDFLAGS} $< ${LDLIBS.$@} -o $@ + +.pl: + cp -f $< $@ + chmod a+x $@ + +hi: hi.c + ${CC} ${CFLAGS} ${LDFLAGS} hi.c ${LDLIBS.$@} -o $@ + ./hi -c + +open pbcopy pbpaste: pbd + ln -f pbd $@ + +fbatt.o fbclock.o: scheme.h + +psf2png.o scheme.o: png.h + +scheme.h: scheme + ./scheme -c > scheme.h + +tags: *.h *.c + ctags -w *.h *.c + +IGNORE = *.o *.html ${BINS_ALL} ${LINKS} scheme.h tags + +.gitignore: Makefile + echo config.mk '${IGNORE}' | tr ' ' '\n' | sort > .gitignore + +clean: + rm -f ${IGNORE} + +setuid: bri + chown root bri + chmod u+s bri + +link: + install -d ${PREFIX}/bin ${MANDIR}/man1 + ln -fs ${BINS_ALL:%=${PWD}/%} ${PREFIX}/bin + ln -fs ${MANS_ALL:%=${PWD}/%} ${MANDIR}/man1 + ln -fs ${LINKS:%=${PWD}/%} ${PREFIX}/bin + +unlink: + rm -f ${BINS_ALL:%=${PREFIX}/bin/%} + rm -f ${MANS_ALL:%=${MANDIR}/%} + rm -f ${LINKS:%=${PREFIX}/bin/%} + +HTMLS = index.html ${BINS_ALL:=.html} png.html +WEBROOT = /usr/local/www/causal.agency + +html: ${HTMLS} + @true + +${HTMLS}: html.sh scheme hi ttpre + +.SUFFIXES: .html + +.c.html: + sh html.sh $< man1/${<:.c=.1} > $@ + +.h.html: + sh html.sh $< man3/${<:.h=.3} > $@ + +.y.html: + sh html.sh $< man1/${<:.y=.1} > $@ + +.sh.html: + sh html.sh $< man1/${<:.sh=.1} > $@ + +.pl.html: + sh html.sh $< man1/${<:.pl=.1} > $@ + +index.html: Makefile README.7 + sh html.sh Makefile README.7 \ + | sed -E 's,([a-z0-9-]+)[(][1-9][)],<a href="\1.html">&</a>,' \ + > index.html + +install-html: ${HTMLS} + install -d ${WEBROOT}/bin + install -C -m 644 ${HTMLS} ${WEBROOT}/bin diff --git a/bin/README.7 b/bin/README.7 new file mode 100644 index 00000000..40dcdd0b --- /dev/null +++ b/bin/README.7 @@ -0,0 +1,83 @@ +.Dd December 15, 2020 +.Dt BIN 7 +.Os "Causal Agency" +. +.Sh NAME +.Nm bin +.Nd various utilities +. +.Sh DESCRIPTION +Various tools primarily targeting +Darwin, +.Fx +and +.Nx . +Some tools target Linux. +. +.Pp +.Bl -tag -width "fbclock(1)" -compact +.It Xr beef 1 +Befunge-93 interpreter +.It Xr bibsort 1 +reformat bibliography +.It Xr bit 1 +calculator +.It Xr bri 1 +backlight brightness control +.It Xr c 1 +run C statements +.It Xr dtch 1 +detached sessions +.It Xr ever 1 +watch files +.It Xr fbatt 1 +framebuffer battery indicator +.It Xr fbclock 1 +framebuffer clock +.It Xr glitch 1 +PNG glitcher +.It Xr hi 1 +syntax highlighter +.It Xr hnel 1 +PTY input remapper +.It Xr modem 1 +fixed baud rate wrapper +.It Xr nudge 1 +terminal vibrator +.It Xr order 1 +operator precedence +.It Xr pbd 1 +macOS pasteboard daemon +.It Xr pngo 1 +PNG optimizer +.It Xr psf2png 1 +PSF2 to PNG renderer +.It Xr psfed 1 +PSF2 font editor +.It Xr ptee 1 +tee for PTYs +.It Xr relay 1 +IRC relay bot +.It Xr scheme 1 +color scheme +.It Xr shotty 1 +terminal capture +.It Xr title 1 +page titles +.It Xr ttpre 1 +man output to HTML +.It Xr up 1 +upload file +.It Xr when 1 +date calculator +.It Xr xx 1 +hexdump +.El +. +.Pp +One piece of reused code. +.Pp +.Bl -tag -width "png(3)" -compact +.It Xr png 3 +basic PNG output +.El diff --git a/bin/beef.c b/bin/beef.c new file mode 100644 index 00000000..b2579b73 --- /dev/null +++ b/bin/beef.c @@ -0,0 +1,141 @@ +/* Copyright (C) 2019 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <time.h> + +enum { + Cols = 80, + Rows = 25, +}; +static char page[Rows][Cols]; + +static char get(int y, int x) { + if (y < 0 || y >= Rows) return 0; + if (x < 0 || x >= Cols) return 0; + return page[y][x]; +} +static void put(int y, int x, char v) { + if (y < 0 || y >= Rows) return; + if (x < 0 || x >= Cols) return; + page[y][x] = v; +} + +enum { StackLen = 1024 }; +static long stack[StackLen]; +static size_t top = StackLen; + +static void push(long val) { + if (!top) errx(EX_SOFTWARE, "stack overflow"); + stack[--top] = val; +} +static long pop(void) { + if (top == StackLen) return 0; + return stack[top++]; +} + +static struct { + int y, x; + int dy, dx; +} pc = { .dx = 1 }; + +static void inc(void) { + pc.y += pc.dy; + pc.x += pc.dx; + if (pc.y == -1) pc.y += Rows; + if (pc.x == -1) pc.x += Cols; + if (pc.y == Rows) pc.y -= Rows; + if (pc.x == Cols) pc.x -= Cols; +} + +static bool string; + +static bool step(void) { + char ch = page[pc.y][pc.x]; + + if (ch == '"') { + string ^= true; + } else if (string) { + push(ch); + inc(); + return true; + } + + if (ch == '?') ch = "><^v"[rand() % 4]; + + long x, y, v; + switch (ch) { + break; case '+': push(pop() + pop()); + break; case '-': y = pop(); x = pop(); push(x - y); + break; case '*': push(pop() * pop()); + break; case '/': y = pop(); x = pop(); push(x / y); + break; case '%': y = pop(); x = pop(); push(x % y); + break; case '!': push(!pop()); + break; case '`': y = pop(); x = pop(); push(x > y); + break; case '>': pc.dy = 0; pc.dx = +1; + break; case '<': pc.dy = 0; pc.dx = -1; + break; case '^': pc.dy = -1; pc.dx = 0; + break; case 'v': pc.dy = +1; pc.dx = 0; + break; case '_': pc.dy = 0; pc.dx = (pop() ? -1 : +1); + break; case '|': pc.dx = 0; pc.dy = (pop() ? -1 : +1); + break; case ':': x = pop(); push(x); push(x); + break; case '\\': y = pop(); x = pop(); push(y); push(x); + break; case '$': pop(); + break; case '.': printf("%ld ", pop()); fflush(stdout); + break; case ',': printf("%c", (char)pop()); fflush(stdout); + break; case '#': inc(); + break; case 'g': y = pop(); x = pop(); push(get(y, x)); + break; case 'p': y = pop(); x = pop(); v = pop(); put(y, x, v); + break; case '&': x = EOF; scanf("%ld", &x); push(x); + break; case '~': push(getchar()); + break; case '@': return false; + break; default: if (ch >= '0' && ch <= '9') push(ch - '0'); + } + + inc(); + return true; +} + +int main(int argc, char *argv[]) { + srand(time(NULL)); + memset(page, ' ', sizeof(page)); + + FILE *file = stdin; + if (argc > 1) { + file = fopen(argv[1], "r"); + if (!file) err(EX_NOINPUT, "%s", argv[1]); + } + + int y = 0; + char *line = NULL; + size_t cap = 0; + while (y < Rows && 0 < getline(&line, &cap, file)) { + for (int x = 0; x < Cols; ++x) { + if (line[x] == '\n' || !line[x]) break; + page[y][x] = line[x]; + } + y++; + } + free(line); + + while (step()); + return pop(); +} diff --git a/bin/bibsort.pl b/bin/bibsort.pl new file mode 100644 index 00000000..f87f066f --- /dev/null +++ b/bin/bibsort.pl @@ -0,0 +1,66 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +while (<>) { + print; + last if /^[.]Sh STANDARDS$/; +} + +my ($ref, @refs); +while (<>) { + next if /^[.](Bl|It|$)/; + last if /^[.]El$/; + if (/^[.]Rs$/) { + $ref = {}; + } elsif (/^[.]%(.) (.*)/) { + $ref->{$1} = [] unless $ref->{$1}; + push @{$ref->{$1}}, $2; + } elsif (/^[.]Re$/) { + push @refs, $ref; + } else { + print; + } +} + +sub byLast { + my ($af, $al) = split /\s(\S+)(,.*)?$/, $a; + my ($bf, $bl) = split /\s(\S+)(,.*)?$/, $b; + $al cmp $bl || $af cmp $bf; +} + +foreach $ref (@refs) { + @{$ref->{A}} = sort byLast @{$ref->{A}}; + @{$ref->{Q}} = sort @{$ref->{Q}} if $ref->{Q}; + next unless $ref->{N}; + if ($ref->{N}[0] =~ /RFC/) { + $ref->{R} = $ref->{N}; + delete $ref->{N}; + } +} + +sub byAuthor { + local ($a, $b) = ($a->{A}[0], $b->{A}[0]); + byLast(); +} + +{ + local ($,, $\) = (' ', "\n"); + print '.Bl', '-item'; + foreach $ref (sort byAuthor @refs) { + print '.It'; + print '.Rs'; + foreach my $key (qw(A T B I J R N V U P Q C D O)) { + next unless $ref->{$key}; + foreach (@{$ref->{$key}}) { + print ".%${key}", $_; + } + } + print '.Re'; + } + print '.El'; +} + +while (<>) { + print; +} diff --git a/bin/bit.y b/bin/bit.y new file mode 100644 index 00000000..0a9bc7ce --- /dev/null +++ b/bin/bit.y @@ -0,0 +1,194 @@ +/* Copyright (C) 2019 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +%{ + +#include <ctype.h> +#include <err.h> +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <sysexits.h> + +#define MASK(b) ((1ULL << (b)) - 1) + +static void yyerror(const char *str); +static int yylex(void); + +#define YYSTYPE uint64_t + +static uint64_t vars[128]; + +%} + +%right '=' +%left '|' +%left '^' +%left '&' +%left Shl Shr Sar +%left '+' '-' +%left '*' '/' '%' +%right '~' +%left 'K' 'M' 'G' 'T' + +%token Int Var + +%% + +stmt: + expr { vars['_'] = $1; } + ; + +expr: + Int + | Var { $$ = vars[$1]; } + | '(' expr ')' { $$ = $2; } + | expr 'K' { $$ = $1 << 10; } + | expr 'M' { $$ = $1 << 20; } + | expr 'G' { $$ = $1 << 30; } + | expr 'T' { $$ = $1 << 40; } + | '~' expr { $$ = ~$2; } + | '-' expr { $$ = -$2; } + | expr '*' expr { $$ = $1 * $3; } + | expr '/' expr { $$ = $1 / $3; } + | expr '%' expr { $$ = $1 % $3; } + | expr '+' expr { $$ = $1 + $3; } + | expr '-' expr { $$ = $1 - $3; } + | expr Shl expr { $$ = $1 << $3; } + | expr Shr expr { $$ = $1 >> $3; } + | expr Sar expr { $$ = (int64_t)$1 >> $3; } + | expr '&' expr { $$ = $1 & $3; } + | expr '^' expr { $$ = $1 ^ $3; } + | expr '|' expr { $$ = $1 | $3; } + | Var '=' expr { $$ = vars[$1] = $3; } + ; + +%% + +static void yyerror(const char *str) { + warnx("%s", str); +} + +#define T(a, b) ((int)(a) << 8 | (int)(b)) + +static const char *input; + +static int lexInt(uint64_t base) { + for (yylval = 0; input[0]; ++input) { + uint64_t digit; + if (input[0] == '_') { + continue; + } else if (input[0] >= '0' && input[0] <= '9') { + digit = input[0] - '0'; + } else if (input[0] >= 'A' && input[0] <= 'F') { + digit = 0xA + input[0] - 'A'; + } else if (input[0] >= 'a' && input[0] <= 'f') { + digit = 0xA + input[0] - 'a'; + } else { + return Int; + } + if (digit >= base) return Int; + yylval *= base; + yylval += digit; + } + return Int; +} + +static int yylex(void) { + while (isspace(input[0])) input++; + if (!input[0]) return EOF; + + if (input[0] == '\'' && input[1] && input[2] == '\'') { + yylval = input[1]; + input += 3; + return Int; + } + + if (input[0] == '0') { + if (input[1] == 'b') { + input += 2; + return lexInt(2); + } else if (input[1] == 'x') { + input += 2; + return lexInt(16); + } else { + input += 1; + return lexInt(8); + } + } else if (isdigit(input[0])) { + return lexInt(10); + } + + if (input[0] == '_' || islower(input[0])) { + yylval = *input++; + return Var; + } + + switch (T(input[0], input[1])) { + case T('<', '<'): input += 2; return Shl; + case T('>', '>'): input += 2; return Shr; + case T('-', '>'): input += 2; return Sar; + default: return *input++; + } +} + +int main(void) { + char *line = NULL; + size_t cap = 0; + while (0 < getline(&line, &cap, stdin)) { + if (line[0] == '\n') continue; + + input = line; + int error = yyparse(); + if (error) continue; + + uint64_t result = vars['_']; + + int bits = result > UINT32_MAX ? 64 + : result > UINT16_MAX ? 32 + : result > UINT8_MAX ? 16 + : 8; + + printf("0x%0*"PRIX64" %"PRId64"", bits >> 2, result, (int64_t)result); + + if (bits == 8) { + char bin[9] = {0}; + for (int i = 0; i < 8; ++i) { + bin[i] = '0' + (result >> (7 - i) & 1); + } + printf(" 0b%s", bin); + } + + if (result < 128 && isprint(result)) { + printf(" '%c'", (char)result); + } + + if (result) { + if (!(result & MASK(40))) { + printf(" %"PRIu64"T", result >> 40); + } else if (!(result & MASK(30))) { + printf(" %"PRIu64"G", result >> 30); + } else if (!(result & MASK(20))) { + printf(" %"PRIu64"M", result >> 20); + } else if (!(result & MASK(10))) { + printf(" %"PRIu64"K", result >> 10); + } + } + + printf("\n\n"); + } +} diff --git a/bin/bri.c b/bin/bri.c new file mode 100644 index 00000000..81df2995 --- /dev/null +++ b/bin/bri.c @@ -0,0 +1,85 @@ +/* Copyright (C) 2017 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +typedef unsigned uint; + +static const char *Class = "/sys/class/backlight"; + +int main(int argc, char *argv[]) { + int error; + + const char *input = (argc > 1) ? argv[1] : NULL; + + error = chdir(Class); + if (error) err(EX_OSFILE, "%s", Class); + + DIR *dir = opendir("."); + if (!dir) err(EX_OSFILE, "%s", Class); + + struct dirent *entry; + while (NULL != (errno = 0, entry = readdir(dir))) { + if (entry->d_name[0] == '.') continue; + + error = chdir(entry->d_name); + if (error) err(EX_OSFILE, "%s/%s", Class, entry->d_name); + break; + } + if (!entry) { + if (errno) err(EX_IOERR, "%s", Class); + errx(EX_CONFIG, "%s: empty", Class); + } + + FILE *actual = fopen("actual_brightness", "r"); + if (!actual) err(EX_OSFILE, "actual_brightness"); + + uint value; + int match = fscanf(actual, "%u", &value); + if (match == EOF) err(EX_IOERR, "actual_brightness"); + if (match < 1) errx(EX_DATAERR, "actual_brightness"); + + if (!input) { + printf("%u\n", value); + return EX_OK; + } + + if (input[0] == '+' || input[0] == '-') { + size_t count = strnlen(input, 16); + if (input[0] == '+') { + value += 16 * count; + } else { + value -= 16 * count; + } + } else { + value = strtoul(input, NULL, 0); + } + + FILE *brightness = fopen("brightness", "w"); + if (!brightness) err(EX_OSFILE, "brightness"); + + int size = fprintf(brightness, "%u", value); + if (size < 0) err(EX_IOERR, "brightness"); + + return EX_OK; +} diff --git a/bin/c.sh b/bin/c.sh new file mode 100644 index 00000000..f1143fd3 --- /dev/null +++ b/bin/c.sh @@ -0,0 +1,87 @@ +#!/bin/sh +set -eu + +temp=$(mktemp -d) +trap 'rm -r "${temp}"' EXIT + +exec 3>>"${temp}/run.c" + +cat >&3 <<EOF +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <locale.h> +#include <math.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <wchar.h> +#include <wctype.h> + +#include <fcntl.h> +#include <strings.h> +#include <unistd.h> +EOF + +expr= +while getopts 'e:i:' opt; do + case "${opt}" in + (e) expr=$OPTARG;; + (i) echo "#include <${OPTARG}>" >&3;; + (?) exit 1;; + esac +done +shift $((OPTIND - 1)) + +cat >&3 <<EOF +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + $*; +EOF + +if [ -n "${expr}" ]; then + cat >&3 <<EOF + printf( + _Generic( + ${expr}, + char: "%c\n", + char *: "%s\n", + const char *: "%s\n", + wchar_t *: "%ls\n", + const wchar_t *: "%ls\n", + signed char: "%hhd\n", + short: "%hd\n", + int: "%d\n", + long: "%ld\n", + long long: "%lld\n", + unsigned char: "%hhu\n", + unsigned short: "%hu\n", + unsigned int: "%u\n", + unsigned long: "%lu\n", + unsigned long long: "%llu\n", + double: "%g\n", + default: "%p\n" + ), + ${expr} + ); +EOF +fi + +if [ $# -eq 0 -a -z "${expr}" ]; then + cat >&3 +fi + +echo '}' >&3 + +cat >"${temp}/Makefile" <<EOF +CFLAGS += -Wall -Wextra -Wpedantic +EOF + +make -s -C "${temp}" run +"${temp}/run" diff --git a/bin/dash/.gitignore b/bin/dash/.gitignore new file mode 100644 index 00000000..e349901a --- /dev/null +++ b/bin/dash/.gitignore @@ -0,0 +1,42 @@ +# .gitignore for dash + +# generated by autogen.sh +Makefile.in +/aclocal.m4 +/autom4te.cache/ +/compile +/config.h.in +/configure +/depcomp +/install-sh +/missing + +# generated by configure +Makefile +.deps +.dirstamp +/config.cache +/config.h +/config.log +/config.status +/stamp-h1 + +# generated by make +/src/token_vars.h + +# Apple debug symbol bundles +*.dSYM/ + +# backups and patch artefacts +*~ +*.bak +*.orig +*.rej + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight* +.Trash* +*[Tt]humbs.db diff --git a/COPYING b/bin/dash/COPYING index 37f8189c..37f8189c 100644 --- a/COPYING +++ b/bin/dash/COPYING diff --git a/ChangeLog b/bin/dash/ChangeLog index 406e20c0..406e20c0 100644 --- a/ChangeLog +++ b/bin/dash/ChangeLog diff --git a/ChangeLog.O b/bin/dash/ChangeLog.O index dfdb2cec..dfdb2cec 100644 --- a/ChangeLog.O +++ b/bin/dash/ChangeLog.O diff --git a/Makefile.am b/bin/dash/Makefile.am index af437a64..af437a64 100644 --- a/Makefile.am +++ b/bin/dash/Makefile.am diff --git a/autogen.sh b/bin/dash/autogen.sh index 9879c53e..9879c53e 100755 --- a/autogen.sh +++ b/bin/dash/autogen.sh diff --git a/configure.ac b/bin/dash/configure.ac index 33f0b08c..33f0b08c 100644 --- a/configure.ac +++ b/bin/dash/configure.ac diff --git a/src/.gitignore b/bin/dash/src/.gitignore index 644eccb8..644eccb8 100644 --- a/src/.gitignore +++ b/bin/dash/src/.gitignore diff --git a/src/Makefile.am b/bin/dash/src/Makefile.am index 17324653..17324653 100644 --- a/src/Makefile.am +++ b/bin/dash/src/Makefile.am diff --git a/src/TOUR b/bin/dash/src/TOUR index e30836e1..e30836e1 100644 --- a/src/TOUR +++ b/bin/dash/src/TOUR diff --git a/src/alias.c b/bin/dash/src/alias.c index daeacbb8..daeacbb8 100644 --- a/src/alias.c +++ b/bin/dash/src/alias.c diff --git a/src/alias.h b/bin/dash/src/alias.h index fb841d64..fb841d64 100644 --- a/src/alias.h +++ b/bin/dash/src/alias.h diff --git a/src/arith_yacc.c b/bin/dash/src/arith_yacc.c index 1a087c32..1a087c32 100644 --- a/src/arith_yacc.c +++ b/bin/dash/src/arith_yacc.c diff --git a/src/arith_yacc.h b/bin/dash/src/arith_yacc.h index ff34d524..ff34d524 100644 --- a/src/arith_yacc.h +++ b/bin/dash/src/arith_yacc.h diff --git a/src/arith_yylex.c b/bin/dash/src/arith_yylex.c index ec5b5b25..ec5b5b25 100644 --- a/src/arith_yylex.c +++ b/bin/dash/src/arith_yylex.c diff --git a/src/bltin/bltin.h b/bin/dash/src/bltin/bltin.h index f5ac06f2..f5ac06f2 100644 --- a/src/bltin/bltin.h +++ b/bin/dash/src/bltin/bltin.h diff --git a/src/bltin/echo.1 b/bin/dash/src/bltin/echo.1 index fbc7fb43..fbc7fb43 100644 --- a/src/bltin/echo.1 +++ b/bin/dash/src/bltin/echo.1 diff --git a/src/bltin/printf.1 b/bin/dash/src/bltin/printf.1 index 38731732..38731732 100644 --- a/src/bltin/printf.1 +++ b/bin/dash/src/bltin/printf.1 diff --git a/src/bltin/printf.c b/bin/dash/src/bltin/printf.c index 7785735b..7785735b 100644 --- a/src/bltin/printf.c +++ b/bin/dash/src/bltin/printf.c diff --git a/src/bltin/test.1 b/bin/dash/src/bltin/test.1 index 42435fb3..42435fb3 100644 --- a/src/bltin/test.1 +++ b/bin/dash/src/bltin/test.1 diff --git a/src/bltin/test.c b/bin/dash/src/bltin/test.c index c7fc479d..c7fc479d 100644 --- a/src/bltin/test.c +++ b/bin/dash/src/bltin/test.c diff --git a/src/bltin/times.c b/bin/dash/src/bltin/times.c index 1166a68e..1166a68e 100644 --- a/src/bltin/times.c +++ b/bin/dash/src/bltin/times.c diff --git a/src/builtins.def.in b/bin/dash/src/builtins.def.in index 95e420cc..95e420cc 100644 --- a/src/builtins.def.in +++ b/bin/dash/src/builtins.def.in diff --git a/src/cd.c b/bin/dash/src/cd.c index 1ef1dc56..1ef1dc56 100644 --- a/src/cd.c +++ b/bin/dash/src/cd.c diff --git a/src/cd.h b/bin/dash/src/cd.h index 87631619..87631619 100644 --- a/src/cd.h +++ b/bin/dash/src/cd.h diff --git a/src/dash.1 b/bin/dash/src/dash.1 index 32f6ac0d..32f6ac0d 100644 --- a/src/dash.1 +++ b/bin/dash/src/dash.1 diff --git a/src/error.c b/bin/dash/src/error.c index 728ff885..728ff885 100644 --- a/src/error.c +++ b/bin/dash/src/error.c diff --git a/src/error.h b/bin/dash/src/error.h index 94e30a27..94e30a27 100644 --- a/src/error.h +++ b/bin/dash/src/error.h diff --git a/src/eval.c b/bin/dash/src/eval.c index 1b5d61dc..1b5d61dc 100644 --- a/src/eval.c +++ b/bin/dash/src/eval.c diff --git a/src/eval.h b/bin/dash/src/eval.h index 63e7d865..63e7d865 100644 --- a/src/eval.h +++ b/bin/dash/src/eval.h diff --git a/src/exec.c b/bin/dash/src/exec.c index 87354d49..87354d49 100644 --- a/src/exec.c +++ b/bin/dash/src/exec.c diff --git a/src/exec.h b/bin/dash/src/exec.h index 423b07e6..423b07e6 100644 --- a/src/exec.h +++ b/bin/dash/src/exec.h diff --git a/src/expand.c b/bin/dash/src/expand.c index 1730670e..1730670e 100644 --- a/src/expand.c +++ b/bin/dash/src/expand.c diff --git a/src/expand.h b/bin/dash/src/expand.h index c44b8481..c44b8481 100644 --- a/src/expand.h +++ b/bin/dash/src/expand.h diff --git a/src/funcs/cmv b/bin/dash/src/funcs/cmv index 91a67c53..91a67c53 100644 --- a/src/funcs/cmv +++ b/bin/dash/src/funcs/cmv diff --git a/src/funcs/dirs b/bin/dash/src/funcs/dirs index 5f6ce635..5f6ce635 100644 --- a/src/funcs/dirs +++ b/bin/dash/src/funcs/dirs diff --git a/src/funcs/kill b/bin/dash/src/funcs/kill index c5df95f5..c5df95f5 100644 --- a/src/funcs/kill +++ b/bin/dash/src/funcs/kill diff --git a/src/funcs/login b/bin/dash/src/funcs/login index 215e5352..215e5352 100644 --- a/src/funcs/login +++ b/bin/dash/src/funcs/login diff --git a/src/funcs/newgrp b/bin/dash/src/funcs/newgrp index ec0e7e5a..ec0e7e5a 100644 --- a/src/funcs/newgrp +++ b/bin/dash/src/funcs/newgrp diff --git a/src/funcs/popd b/bin/dash/src/funcs/popd index 7bccf50d..7bccf50d 100644 --- a/src/funcs/popd +++ b/bin/dash/src/funcs/popd diff --git a/src/funcs/pushd b/bin/dash/src/funcs/pushd index 19ac8e08..19ac8e08 100644 --- a/src/funcs/pushd +++ b/bin/dash/src/funcs/pushd diff --git a/src/funcs/suspend b/bin/dash/src/funcs/suspend index 44844678..44844678 100644 --- a/src/funcs/suspend +++ b/bin/dash/src/funcs/suspend diff --git a/src/histedit.c b/bin/dash/src/histedit.c index f5c90aba..f5c90aba 100644 --- a/src/histedit.c +++ b/bin/dash/src/histedit.c diff --git a/src/init.h b/bin/dash/src/init.h index d56fb28e..d56fb28e 100644 --- a/src/init.h +++ b/bin/dash/src/init.h diff --git a/src/input.c b/bin/dash/src/input.c index 17544e78..17544e78 100644 --- a/src/input.c +++ b/bin/dash/src/input.c diff --git a/src/input.h b/bin/dash/src/input.h index 8acc6e9f..8acc6e9f 100644 --- a/src/input.h +++ b/bin/dash/src/input.h diff --git a/src/jobs.c b/bin/dash/src/jobs.c index d4c13c0f..d4c13c0f 100644 --- a/src/jobs.c +++ b/bin/dash/src/jobs.c diff --git a/src/jobs.h b/bin/dash/src/jobs.h index 6ac6c56d..6ac6c56d 100644 --- a/src/jobs.h +++ b/bin/dash/src/jobs.h diff --git a/src/machdep.h b/bin/dash/src/machdep.h index f2ff0ad8..f2ff0ad8 100644 --- a/src/machdep.h +++ b/bin/dash/src/machdep.h diff --git a/src/mail.c b/bin/dash/src/mail.c index 8eacb2d0..8eacb2d0 100644 --- a/src/mail.c +++ b/bin/dash/src/mail.c diff --git a/src/mail.h b/bin/dash/src/mail.h index 3c6b21d2..3c6b21d2 100644 --- a/src/mail.h +++ b/bin/dash/src/mail.h diff --git a/src/main.c b/bin/dash/src/main.c index 7a285346..7a285346 100644 --- a/src/main.c +++ b/bin/dash/src/main.c diff --git a/src/main.h b/bin/dash/src/main.h index 19e49835..19e49835 100644 --- a/src/main.h +++ b/bin/dash/src/main.h diff --git a/src/memalloc.c b/bin/dash/src/memalloc.c index 60637da1..60637da1 100644 --- a/src/memalloc.c +++ b/bin/dash/src/memalloc.c diff --git a/src/memalloc.h b/bin/dash/src/memalloc.h index b9adf764..b9adf764 100644 --- a/src/memalloc.h +++ b/bin/dash/src/memalloc.h diff --git a/src/miscbltin.c b/bin/dash/src/miscbltin.c index 5ccbbcb8..5ccbbcb8 100644 --- a/src/miscbltin.c +++ b/bin/dash/src/miscbltin.c diff --git a/src/miscbltin.h b/bin/dash/src/miscbltin.h index dd9a8d1c..dd9a8d1c 100644 --- a/src/miscbltin.h +++ b/bin/dash/src/miscbltin.h diff --git a/src/mkbuiltins b/bin/dash/src/mkbuiltins index f1f25932..f1f25932 100644 --- a/src/mkbuiltins +++ b/bin/dash/src/mkbuiltins diff --git a/src/mkinit.c b/bin/dash/src/mkinit.c index 9025862e..9025862e 100644 --- a/src/mkinit.c +++ b/bin/dash/src/mkinit.c diff --git a/src/mknodes.c b/bin/dash/src/mknodes.c index 1903a605..1903a605 100644 --- a/src/mknodes.c +++ b/bin/dash/src/mknodes.c diff --git a/src/mksignames.c b/bin/dash/src/mksignames.c index a832eab9..a832eab9 100644 --- a/src/mksignames.c +++ b/bin/dash/src/mksignames.c diff --git a/src/mksyntax.c b/bin/dash/src/mksyntax.c index a23c18ca..a23c18ca 100644 --- a/src/mksyntax.c +++ b/bin/dash/src/mksyntax.c diff --git a/src/mktokens b/bin/dash/src/mktokens index 78055be8..78055be8 100644 --- a/src/mktokens +++ b/bin/dash/src/mktokens diff --git a/src/myhistedit.h b/bin/dash/src/myhistedit.h index 22e5c438..22e5c438 100644 --- a/src/myhistedit.h +++ b/bin/dash/src/myhistedit.h diff --git a/src/mystring.c b/bin/dash/src/mystring.c index de624b89..de624b89 100644 --- a/src/mystring.c +++ b/bin/dash/src/mystring.c diff --git a/src/mystring.h b/bin/dash/src/mystring.h index 083ea98c..083ea98c 100644 --- a/src/mystring.h +++ b/bin/dash/src/mystring.h diff --git a/src/nodes.c.pat b/bin/dash/src/nodes.c.pat index 9125bc73..9125bc73 100644 --- a/src/nodes.c.pat +++ b/bin/dash/src/nodes.c.pat diff --git a/src/nodetypes b/bin/dash/src/nodetypes index ceaf478c..ceaf478c 100644 --- a/src/nodetypes +++ b/bin/dash/src/nodetypes diff --git a/src/options.c b/bin/dash/src/options.c index a46c23b9..a46c23b9 100644 --- a/src/options.c +++ b/bin/dash/src/options.c diff --git a/src/options.h b/bin/dash/src/options.h index 975fe339..975fe339 100644 --- a/src/options.h +++ b/bin/dash/src/options.h diff --git a/src/output.c b/bin/dash/src/output.c index e9ee9b4d..e9ee9b4d 100644 --- a/src/output.c +++ b/bin/dash/src/output.c diff --git a/src/output.h b/bin/dash/src/output.h index c43d4937..c43d4937 100644 --- a/src/output.h +++ b/bin/dash/src/output.h diff --git a/src/parser.c b/bin/dash/src/parser.c index a47022e0..a47022e0 100644 --- a/src/parser.c +++ b/bin/dash/src/parser.c diff --git a/src/parser.h b/bin/dash/src/parser.h index 524ac1c7..524ac1c7 100644 --- a/src/parser.h +++ b/bin/dash/src/parser.h diff --git a/src/redir.c b/bin/dash/src/redir.c index 895140c3..895140c3 100644 --- a/src/redir.c +++ b/bin/dash/src/redir.c diff --git a/src/redir.h b/bin/dash/src/redir.h index 1cf27616..1cf27616 100644 --- a/src/redir.h +++ b/bin/dash/src/redir.h diff --git a/src/shell.h b/bin/dash/src/shell.h index 98edc8be..98edc8be 100644 --- a/src/shell.h +++ b/bin/dash/src/shell.h diff --git a/src/show.c b/bin/dash/src/show.c index 4a049e93..4a049e93 100644 --- a/src/show.c +++ b/bin/dash/src/show.c diff --git a/src/show.h b/bin/dash/src/show.h index d0ccac79..d0ccac79 100644 --- a/src/show.h +++ b/bin/dash/src/show.h diff --git a/src/system.c b/bin/dash/src/system.c index 844a6410..844a6410 100644 --- a/src/system.c +++ b/bin/dash/src/system.c diff --git a/src/system.h b/bin/dash/src/system.h index 007952c5..007952c5 100644 --- a/src/system.h +++ b/bin/dash/src/system.h diff --git a/src/trap.c b/bin/dash/src/trap.c index cd84814f..cd84814f 100644 --- a/src/trap.c +++ b/bin/dash/src/trap.c diff --git a/src/trap.h b/bin/dash/src/trap.h index beaf6605..beaf6605 100644 --- a/src/trap.h +++ b/bin/dash/src/trap.h diff --git a/src/var.c b/bin/dash/src/var.c index ef9c2bde..ef9c2bde 100644 --- a/src/var.c +++ b/bin/dash/src/var.c diff --git a/src/var.h b/bin/dash/src/var.h index aa7575a7..aa7575a7 100644 --- a/src/var.h +++ b/bin/dash/src/var.h diff --git a/bin/dtch.c b/bin/dtch.c new file mode 100644 index 00000000..2aea53ae --- /dev/null +++ b/bin/dtch.c @@ -0,0 +1,271 @@ +/* Copyright (C) 2017-2019 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <sysexits.h> +#include <termios.h> +#include <unistd.h> + +#if defined __FreeBSD__ +#include <libutil.h> +#elif defined __linux__ +#include <pty.h> +#else +#include <util.h> +#endif + +static char _; +static struct iovec iov = { .iov_base = &_, .iov_len = 1 }; + +static ssize_t sendfd(int sock, int fd) { + size_t len = CMSG_SPACE(sizeof(int)); + char buf[len]; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = buf, + .msg_controllen = len, + }; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *(int *)CMSG_DATA(cmsg) = fd; + + return sendmsg(sock, &msg, 0); +} + +static int recvfd(int sock) { + size_t len = CMSG_SPACE(sizeof(int)); + char buf[len]; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = buf, + .msg_controllen = len, + }; + if (0 > recvmsg(sock, &msg, 0)) return -1; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + if (!cmsg || cmsg->cmsg_type != SCM_RIGHTS) { + errno = ENOMSG; + return -1; + } + return *(int *)CMSG_DATA(cmsg); +} + +static struct sockaddr_un addr = { .sun_family = AF_UNIX }; + +static void handler(int sig) { + unlink(addr.sun_path); + _exit(-sig); +} + +static void detach(int server, bool sink, char *argv[]) { + int pty; + pid_t pid = forkpty(&pty, NULL, NULL, NULL); + if (pid < 0) err(EX_OSERR, "forkpty"); + + if (!pid) { + execvp(argv[0], argv); + err(EX_NOINPUT, "%s", argv[0]); + } + + signal(SIGINT, handler); + signal(SIGTERM, handler); + + int error = listen(server, 0); + if (error) err(EX_OSERR, "listen"); + + struct pollfd fds[] = { + { .events = POLLIN, .fd = server }, + { .events = POLLIN, .fd = pty }, + }; + while (0 < poll(fds, (sink ? 2 : 1), -1)) { + if (fds[0].revents) { + int client = accept(server, NULL, NULL); + if (client < 0) err(EX_IOERR, "accept"); + + ssize_t len = sendfd(client, pty); + if (len < 0) warn("sendfd"); + + len = recv(client, &_, sizeof(_), 0); + if (len < 0) warn("recv"); + + close(client); + } + + if (fds[1].revents) { + char buf[4096]; + ssize_t len = read(pty, buf, sizeof(buf)); + if (len < 0) err(EX_IOERR, "read"); + } + + int status; + pid_t dead = waitpid(pid, &status, WNOHANG); + if (dead < 0) err(EX_OSERR, "waitpid"); + if (dead) { + unlink(addr.sun_path); + exit(WIFEXITED(status) ? WEXITSTATUS(status) : -WTERMSIG(status)); + } + } + err(EX_IOERR, "poll"); +} + +static struct termios saveTerm; +static void restoreTerm(void) { + tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm); + fprintf(stderr, "\33c"); // RIS + warnx("detached"); +} + +static void nop(int sig) { + (void)sig; +} + +static void attach(int client) { + int error; + + int pty = recvfd(client); + if (pty < 0) err(EX_IOERR, "recvfd"); + warnx("attached"); + + struct winsize window; + error = ioctl(STDIN_FILENO, TIOCGWINSZ, &window); + if (error) err(EX_IOERR, "ioctl"); + + struct winsize redraw = { .ws_row = 1, .ws_col = 1 }; + error = ioctl(pty, TIOCSWINSZ, &redraw); + if (error) err(EX_IOERR, "ioctl"); + + error = ioctl(pty, TIOCSWINSZ, &window); + if (error) err(EX_IOERR, "ioctl"); + + error = tcgetattr(STDIN_FILENO, &saveTerm); + if (error) err(EX_IOERR, "tcgetattr"); + atexit(restoreTerm); + + struct termios raw = saveTerm; + cfmakeraw(&raw); + error = tcsetattr(STDIN_FILENO, TCSADRAIN, &raw); + if (error) err(EX_IOERR, "tcsetattr"); + + signal(SIGWINCH, nop); + + char buf[4096]; + struct pollfd fds[] = { + { .events = POLLIN, .fd = STDIN_FILENO }, + { .events = POLLIN, .fd = pty }, + }; + for (;;) { + int nfds = poll(fds, 2, -1); + if (nfds < 0) { + if (errno != EINTR) err(EX_IOERR, "poll"); + + error = ioctl(STDIN_FILENO, TIOCGWINSZ, &window); + if (error) err(EX_IOERR, "ioctl"); + + error = ioctl(pty, TIOCSWINSZ, &window); + if (error) err(EX_IOERR, "ioctl"); + + continue; + } + + if (fds[0].revents) { + ssize_t len = read(STDIN_FILENO, buf, sizeof(buf)); + if (len < 0) err(EX_IOERR, "read"); + if (!len) break; + + if (len == 1 && buf[0] == CTRL('Q')) break; + + len = write(pty, buf, len); + if (len < 0) err(EX_IOERR, "write"); + } + + if (fds[1].revents) { + ssize_t len = read(pty, buf, sizeof(buf)); + if (len < 0) err(EX_IOERR, "read"); + if (!len) break; + + len = write(STDOUT_FILENO, buf, len); + if (len < 0) err(EX_IOERR, "write"); + } + } +} + +int main(int argc, char *argv[]) { + int error; + + bool atch = false; + bool sink = false; + + int opt; + while (0 < (opt = getopt(argc, argv, "as"))) { + switch (opt) { + break; case 'a': atch = true; + break; case 's': sink = true; + break; default: return EX_USAGE; + } + } + if (optind == argc) errx(EX_USAGE, "no session name"); + const char *name = argv[optind++]; + + if (optind == argc) { + argv[--optind] = getenv("SHELL"); + if (!argv[optind]) errx(EX_CONFIG, "SHELL unset"); + } + + const char *home = getenv("HOME"); + if (!home) errx(EX_CONFIG, "HOME unset"); + + int fd = open(home, 0); + if (fd < 0) err(EX_CANTCREAT, "%s", home); + + error = mkdirat(fd, ".dtch", 0700); + if (error && errno != EEXIST) err(EX_CANTCREAT, "%s/.dtch", home); + + close(fd); + + int sock = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock < 0) err(EX_OSERR, "socket"); + fcntl(sock, F_SETFD, FD_CLOEXEC); + + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.dtch/%s", home, name); + + if (atch) { + error = connect(sock, (struct sockaddr *)&addr, SUN_LEN(&addr)); + if (error) err(EX_NOINPUT, "%s", addr.sun_path); + attach(sock); + } else { + error = bind(sock, (struct sockaddr *)&addr, SUN_LEN(&addr)); + if (error) err(EX_CANTCREAT, "%s", addr.sun_path); + detach(sock, sink, &argv[optind]); + } +} diff --git a/bin/ever.c b/bin/ever.c new file mode 100644 index 00000000..258b058b --- /dev/null +++ b/bin/ever.c @@ -0,0 +1,116 @@ +/* Copyright (C) 2017 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <sys/types.h> + +#include <err.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdlib.h> +#include <sys/event.h> +#include <sys/wait.h> +#include <sysexits.h> +#include <unistd.h> + +static int watch(int kq, char *path) { + int fd = open(path, O_CLOEXEC); + if (fd < 0) err(EX_NOINPUT, "%s", path); + + struct kevent event; + EV_SET( + &event, + fd, + EVFILT_VNODE, + EV_ADD | EV_CLEAR, + NOTE_WRITE | NOTE_DELETE, + 0, + path + ); + int nevents = kevent(kq, &event, 1, NULL, 0, NULL); + if (nevents < 0) err(EX_OSERR, "kevent"); + + return fd; +} + +static void exec(int fd, char *const argv[]) { + pid_t pid = fork(); + if (pid < 0) err(EX_OSERR, "fork"); + + if (!pid) { + dup2(fd, STDIN_FILENO); + execvp(*argv, argv); + err(EX_NOINPUT, "%s", *argv); + } + + int status; + pid = wait(&status); + if (pid < 0) err(EX_OSERR, "wait"); + + if (WIFEXITED(status)) { + warnx("exit %d\n", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + warnx("signal %d\n", WTERMSIG(status)); + } else { + warnx("status %d\n", status); + } +} + +int main(int argc, char *argv[]) { + bool input = false; + + for (int opt; 0 < (opt = getopt(argc, argv, "i"));) { + switch (opt) { + break; case 'i': input = true; + break; default: return EX_USAGE; + } + } + argc -= optind; + argv += optind; + if (argc < 2) return EX_USAGE; + + int kq = kqueue(); + if (kq < 0) err(EX_OSERR, "kqueue"); + + int i; + for (i = 0; i < argc - 1; ++i) { + if (argv[i][0] == '-') { + i++; + break; + } + watch(kq, argv[i]); + } + + if (!input) { + exec(STDIN_FILENO, &argv[i]); + } + + for (;;) { + struct kevent event; + int nevents = kevent(kq, NULL, 0, &event, 1, NULL); + if (nevents < 0) err(EX_OSERR, "kevent"); + + if (event.fflags & NOTE_DELETE) { + close(event.ident); + sleep(1); + event.ident = watch(kq, (char *)event.udata); + } else if (input) { + off_t off = lseek(event.ident, 0, SEEK_SET); + if (off < 0) err(EX_IOERR, "lseek"); + } + + exec((input ? event.ident : STDIN_FILENO), &argv[i]); + } +} diff --git a/bin/fbatt.c b/bin/fbatt.c new file mode 100644 index 00000000..9feffe47 --- /dev/null +++ b/bin/fbatt.c @@ -0,0 +1,123 @@ +/* Copyright (C) 2018 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/fb.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sysexits.h> +#include <unistd.h> + +#include "scheme.h" + +static const char *Class = "/sys/class/power_supply"; + +static const uint32_t Right = 5 * 8 + 1; // fbclock width. +static const uint32_t Width = 8; +static const uint32_t Height = 16; + +int main() { + int error; + + DIR *dir = opendir(Class); + if (!dir) err(EX_OSFILE, "%s", Class); + + FILE *chargeFull = NULL; + FILE *chargeNow = NULL; + + const struct dirent *entry; + while (NULL != (errno = 0, entry = readdir(dir))) { + if (entry->d_name[0] == '.') continue; + + error = chdir(Class); + if (error) err(EX_OSFILE, "%s", Class); + + error = chdir(entry->d_name); + if (error) err(EX_OSFILE, "%s/%s", Class, entry->d_name); + + chargeFull = fopen("charge_full", "r"); + chargeNow = fopen("charge_now", "r"); + if (chargeFull && chargeNow) break; + } + if (!chargeFull || !chargeNow) { + if (errno) err(EX_OSFILE, "%s", Class); + errx(EX_CONFIG, "%s: empty", Class); + } + closedir(dir); + + const char *path = getenv("FRAMEBUFFER"); + if (!path) path = "/dev/fb0"; + + int fb = open(path, O_RDWR); + if (fb < 0) err(EX_OSFILE, "%s", path); + + struct fb_var_screeninfo info; + error = ioctl(fb, FBIOGET_VSCREENINFO, &info); + if (error) err(EX_IOERR, "%s", path); + + size_t size = 4 * info.xres * info.yres; + uint32_t *buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fb, 0); + if (buf == MAP_FAILED) err(EX_IOERR, "%s", path); + + for (;;) { + int match; + + rewind(chargeFull); + fflush(chargeFull); + uint32_t full; + match = fscanf(chargeFull, "%u", &full); + if (match == EOF) err(EX_IOERR, "charge_full"); + if (match < 1) errx(EX_DATAERR, "charge_full"); + + rewind(chargeNow); + fflush(chargeNow); + uint32_t now; + match = fscanf(chargeNow, "%u", &now); + if (match == EOF) err(EX_IOERR, "charge_now"); + if (match < 1) errx(EX_DATAERR, "charge_now"); + + uint32_t percent = 100 * now / full; + uint32_t height = 16 * now / full; + + for (int i = 0; i < 60; ++i, sleep(1)) { + uint32_t left = info.xres - Right - Width; + + for (uint32_t y = 0; y <= Height; ++y) { + buf[y * info.xres + left - 1] = DarkWhite; + buf[y * info.xres + left + Width] = DarkWhite; + } + for (uint32_t x = left; x < left + Width; ++x) { + buf[Height * info.xres + x] = DarkWhite; + } + + for (uint32_t y = 0; y < Height; ++y) { + for (uint32_t x = left; x < left + Width; ++x) { + buf[y * info.xres + x] = + (Height - 1 - y > height) ? DarkBlack + : (percent <= 10) ? DarkRed + : (percent <= 30) ? DarkYellow + : LightBlack; + } + } + } + } +} diff --git a/bin/fbclock.c b/bin/fbclock.c new file mode 100644 index 00000000..ddc32db6 --- /dev/null +++ b/bin/fbclock.c @@ -0,0 +1,132 @@ +/* Copyright (C) 2018 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <err.h> +#include <fcntl.h> +#include <linux/fb.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sysexits.h> +#include <time.h> +#include <unistd.h> +#include <zlib.h> + +#include "scheme.h" + +static const uint32_t PSF2Magic = 0x864AB572; +struct PSF2Header { + uint32_t magic; + uint32_t version; + uint32_t headerSize; + uint32_t flags; + uint32_t glyphCount; + uint32_t glyphSize; + uint32_t glyphHeight; + uint32_t glyphWidth; +}; + +int main() { + size_t len; + + const char *fontPath = getenv("FONT"); + if (!fontPath) { + fontPath = "/usr/share/kbd/consolefonts/Lat2-Terminus16.psfu.gz"; + } + + gzFile font = gzopen(fontPath, "r"); + if (!font) err(EX_NOINPUT, "%s", fontPath); + + struct PSF2Header header; + len = gzfread(&header, sizeof(header), 1, font); + if (!len && gzeof(font)) errx(EX_DATAERR, "%s: missing header", fontPath); + if (!len) errx(EX_IOERR, "%s", gzerror(font, NULL)); + + if (header.magic != PSF2Magic) { + errx( + EX_DATAERR, "%s: invalid header magic %08X", + fontPath, header.magic + ); + } + if (header.headerSize != sizeof(struct PSF2Header)) { + errx( + EX_DATAERR, "%s: weird header size %d", + fontPath, header.headerSize + ); + } + + uint8_t glyphs[128][header.glyphSize]; + len = gzfread(glyphs, header.glyphSize, 128, font); + if (!len && gzeof(font)) errx(EX_DATAERR, "%s: missing glyphs", fontPath); + if (!len) errx(EX_IOERR, "%s", gzerror(font, NULL)); + + gzclose(font); + + const char *fbPath = getenv("FRAMEBUFFER"); + if (!fbPath) fbPath = "/dev/fb0"; + + int fb = open(fbPath, O_RDWR); + if (fb < 0) err(EX_OSFILE, "%s", fbPath); + + struct fb_var_screeninfo info; + int error = ioctl(fb, FBIOGET_VSCREENINFO, &info); + if (error) err(EX_IOERR, "%s", fbPath); + + size_t size = 4 * info.xres * info.yres; + uint32_t *buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fb, 0); + if (buf == MAP_FAILED) err(EX_IOERR, "%s", fbPath); + + for (;;) { + time_t t = time(NULL); + if (t < 0) err(EX_OSERR, "time"); + const struct tm *local = localtime(&t); + if (!local) err(EX_OSERR, "localtime"); + + char str[64]; + len = strftime(str, sizeof(str), "%H:%M", local); + assert(len); + + for (int i = 0; i < (60 - local->tm_sec); ++i, sleep(1)) { + uint32_t left = info.xres - header.glyphWidth * len; + uint32_t bottom = header.glyphHeight; + + for (uint32_t y = 0; y < bottom; ++y) { + buf[y * info.xres + left - 1] = DarkWhite; + } + for (uint32_t x = left - 1; x < info.xres; ++x) { + buf[bottom * info.xres + x] = DarkWhite; + } + + for (const char *s = str; *s; ++s) { + const uint8_t *glyph = glyphs[(unsigned)*s]; + uint32_t stride = header.glyphSize / header.glyphHeight; + for (uint32_t y = 0; y < header.glyphHeight; ++y) { + for (uint32_t x = 0; x < header.glyphWidth; ++x) { + uint8_t bits = glyph[y * stride + x / 8]; + uint8_t bit = bits >> (7 - x % 8) & 1; + buf[y * info.xres + left + x] = bit + ? DarkWhite + : DarkBlack; + } + } + left += header.glyphWidth; + } + } + } +} diff --git a/bin/glitch.c b/bin/glitch.c new file mode 100644 index 00000000..9747f35a --- /dev/null +++ b/bin/glitch.c @@ -0,0 +1,538 @@ +/* Copyright (C) 2018 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <arpa/inet.h> +#include <err.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> +#include <zlib.h> + +#define PACKED __attribute__((packed)) + +#define CRC_INIT (crc32(0, Z_NULL, 0)) + +static const char *path; +static FILE *file; +static uint32_t crc; + +static void readExpect(void *ptr, size_t size, const char *expect) { + fread(ptr, size, 1, file); + if (ferror(file)) err(EX_IOERR, "%s", path); + if (feof(file)) errx(EX_DATAERR, "%s: missing %s", path, expect); + crc = crc32(crc, ptr, size); +} + +static void writeExpect(const void *ptr, size_t size) { + fwrite(ptr, size, 1, file); + if (ferror(file)) err(EX_IOERR, "%s", path); + crc = crc32(crc, ptr, size); +} + +static const uint8_t Signature[8] = "\x89PNG\r\n\x1A\n"; + +static void readSignature(void) { + uint8_t signature[8]; + readExpect(signature, 8, "signature"); + if (0 != memcmp(signature, Signature, 8)) { + errx(EX_DATAERR, "%s: invalid signature", path); + } +} + +static void writeSignature(void) { + writeExpect(Signature, sizeof(Signature)); +} + +struct PACKED Chunk { + uint32_t size; + char type[4]; +}; + +static const char *typeStr(struct Chunk chunk) { + static char buf[5]; + memcpy(buf, chunk.type, 4); + return buf; +} + +static struct Chunk readChunk(void) { + struct Chunk chunk; + readExpect(&chunk, sizeof(chunk), "chunk"); + chunk.size = ntohl(chunk.size); + crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type)); + return chunk; +} + +static void writeChunk(struct Chunk chunk) { + chunk.size = htonl(chunk.size); + writeExpect(&chunk, sizeof(chunk)); + crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type)); +} + +static void readCrc(void) { + uint32_t expected = crc; + uint32_t found; + readExpect(&found, sizeof(found), "CRC32"); + found = ntohl(found); + if (found != expected) { + errx( + EX_DATAERR, "%s: expected CRC32 %08X, found %08X", + path, expected, found + ); + } +} + +static void writeCrc(void) { + uint32_t net = htonl(crc); + writeExpect(&net, sizeof(net)); +} + +static void skipChunk(struct Chunk chunk) { + uint8_t discard[chunk.size]; + readExpect(discard, sizeof(discard), "chunk data"); + readCrc(); +} + +static struct PACKED { + uint32_t width; + uint32_t height; + uint8_t depth; + enum PACKED { + Grayscale = 0, + Truecolor = 2, + Indexed = 3, + GrayscaleAlpha = 4, + TruecolorAlpha = 6, + } color; + uint8_t compression; + uint8_t filter; + uint8_t interlace; +} header; +_Static_assert(13 == sizeof(header), "header size"); + +static size_t pixelBits(void) { + switch (header.color) { + case Grayscale: return 1 * header.depth; + case Truecolor: return 3 * header.depth; + case Indexed: return 1 * header.depth; + case GrayscaleAlpha: return 2 * header.depth; + case TruecolorAlpha: return 4 * header.depth; + default: abort(); + } +} + +static size_t pixelSize(void) { + return (pixelBits() + 7) / 8; +} + +static size_t lineSize(void) { + return (header.width * pixelBits() + 7) / 8; +} + +static size_t dataSize(void) { + return (1 + lineSize()) * header.height; +} + +static void readHeader(void) { + struct Chunk ihdr = readChunk(); + if (0 != memcmp(ihdr.type, "IHDR", 4)) { + errx(EX_DATAERR, "%s: expected IHDR, found %s", path, typeStr(ihdr)); + } + if (ihdr.size != sizeof(header)) { + errx( + EX_DATAERR, "%s: expected IHDR size %zu, found %u", + path, sizeof(header), ihdr.size + ); + } + readExpect(&header, sizeof(header), "header"); + readCrc(); + header.width = ntohl(header.width); + header.height = ntohl(header.height); + if (!header.width) errx(EX_DATAERR, "%s: invalid width 0", path); + if (!header.height) errx(EX_DATAERR, "%s: invalid height 0", path); +} + +static void writeHeader(void) { + struct Chunk ihdr = { .size = sizeof(header), .type = "IHDR" }; + writeChunk(ihdr); + header.width = htonl(header.width); + header.height = htonl(header.height); + writeExpect(&header, sizeof(header)); + writeCrc(); + header.width = ntohl(header.width); + header.height = ntohl(header.height); +} + +static struct { + uint32_t len; + uint8_t entries[256][3]; +} palette; + +static void readPalette(void) { + struct Chunk chunk; + for (;;) { + chunk = readChunk(); + if (0 == memcmp(chunk.type, "PLTE", 4)) break; + skipChunk(chunk); + } + palette.len = chunk.size / 3; + readExpect(palette.entries, chunk.size, "palette data"); + readCrc(); +} + +static void writePalette(void) { + struct Chunk plte = { .size = 3 * palette.len, .type = "PLTE" }; + writeChunk(plte); + writeExpect(palette.entries, plte.size); + writeCrc(); +} + +static uint8_t *data; + +static void readData(void) { + data = malloc(dataSize()); + if (!data) err(EX_OSERR, "malloc(%zu)", dataSize()); + + struct z_stream_s stream = { .next_out = data, .avail_out = dataSize() }; + int error = inflateInit(&stream); + if (error != Z_OK) errx(EX_SOFTWARE, "%s: inflateInit: %s", path, stream.msg); + + for (;;) { + struct Chunk chunk = readChunk(); + if (0 == memcmp(chunk.type, "IDAT", 4)) { + uint8_t *idat = malloc(chunk.size); + if (!idat) err(EX_OSERR, "malloc"); + + readExpect(idat, chunk.size, "image data"); + readCrc(); + + stream.next_in = idat; + stream.avail_in = chunk.size; + int error = inflate(&stream, Z_SYNC_FLUSH); + free(idat); + + if (error == Z_STREAM_END) break; + if (error != Z_OK) errx(EX_DATAERR, "%s: inflate: %s", path, stream.msg); + + } else if (0 == memcmp(chunk.type, "IEND", 4)) { + errx(EX_DATAERR, "%s: missing IDAT chunk", path); + } else { + skipChunk(chunk); + } + } + + inflateEnd(&stream); + if ((size_t)stream.total_out != dataSize()) { + errx( + EX_DATAERR, "%s: expected data size %zu, found %zu", + path, dataSize(), (size_t)stream.total_out + ); + } +} + +static void writeData(void) { + uLong size = compressBound(dataSize()); + uint8_t *deflate = malloc(size); + if (!deflate) err(EX_OSERR, "malloc"); + + int error = compress2(deflate, &size, data, dataSize(), Z_BEST_SPEED); + if (error != Z_OK) errx(EX_SOFTWARE, "%s: compress2: %d", path, error); + + struct Chunk idat = { .size = size, .type = "IDAT" }; + writeChunk(idat); + writeExpect(deflate, size); + writeCrc(); + + free(deflate); +} + +static void writeEnd(void) { + struct Chunk iend = { .size = 0, .type = "IEND" }; + writeChunk(iend); + writeCrc(); +} + +enum PACKED Filter { + None, + Sub, + Up, + Average, + Paeth, + FilterCount, +}; + +static struct { + bool brokenPaeth; + bool filt; + bool recon; + uint8_t declareFilter; + uint8_t applyFilter; + enum Filter declareFilters[255]; + enum Filter applyFilters[255]; + bool invert; + bool mirror; + bool zeroX; + bool zeroY; +} options; + +struct Bytes { + uint8_t x; + uint8_t a; + uint8_t b; + uint8_t c; +}; + +static uint8_t paethPredictor(struct Bytes f) { + int32_t p = (int32_t)f.a + (int32_t)f.b - (int32_t)f.c; + int32_t pa = abs(p - (int32_t)f.a); + int32_t pb = abs(p - (int32_t)f.b); + int32_t pc = abs(p - (int32_t)f.c); + if (pa <= pb && pa <= pc) return f.a; + if (options.brokenPaeth) { + if (pb < pc) return f.b; + } else { + if (pb <= pc) return f.b; + } + return f.c; +} + +static uint8_t recon(enum Filter type, struct Bytes f) { + switch (type) { + case None: return f.x; + case Sub: return f.x + f.a; + case Up: return f.x + f.b; + case Average: return f.x + ((uint32_t)f.a + (uint32_t)f.b) / 2; + case Paeth: return f.x + paethPredictor(f); + default: abort(); + } +} + +static uint8_t filt(enum Filter type, struct Bytes f) { + switch (type) { + case None: return f.x; + case Sub: return f.x - f.a; + case Up: return f.x - f.b; + case Average: return f.x - ((uint32_t)f.a + (uint32_t)f.b) / 2; + case Paeth: return f.x - paethPredictor(f); + default: abort(); + } +} + +static struct Line { + enum Filter type; + uint8_t data[]; +} **lines; + +static void scanlines(void) { + lines = calloc(header.height, sizeof(*lines)); + if (!lines) err(EX_OSERR, "calloc(%u, %zu)", header.height, sizeof(*lines)); + + size_t stride = 1 + lineSize(); + for (uint32_t y = 0; y < header.height; ++y) { + lines[y] = (struct Line *)&data[y * stride]; + if (lines[y]->type >= FilterCount) { + errx(EX_DATAERR, "%s: invalid filter type %hhu", path, lines[y]->type); + } + } +} + +static struct Bytes origBytes(uint32_t y, size_t i) { + bool a = (i >= pixelSize()), b = (y > 0), c = (a && b); + return (struct Bytes) { + .x = lines[y]->data[i], + .a = a ? lines[y]->data[i - pixelSize()] : 0, + .b = b ? lines[y - 1]->data[i] : 0, + .c = c ? lines[y - 1]->data[i - pixelSize()] : 0, + }; +} + +static void reconData(void) { + for (uint32_t y = 0; y < header.height; ++y) { + for (size_t i = 0; i < lineSize(); ++i) { + if (options.filt) { + lines[y]->data[i] = filt(lines[y]->type, origBytes(y, i)); + } else { + lines[y]->data[i] = recon(lines[y]->type, origBytes(y, i)); + } + } + lines[y]->type = None; + } +} + +static void filterData(void) { + for (uint32_t y = header.height - 1; y < header.height; --y) { + uint8_t filter[FilterCount][lineSize()]; + uint32_t heuristic[FilterCount] = {0}; + enum Filter minType = None; + for (enum Filter type = None; type < FilterCount; ++type) { + for (size_t i = 0; i < lineSize(); ++i) { + if (options.recon) { + filter[type][i] = recon(type, origBytes(y, i)); + } else { + filter[type][i] = filt(type, origBytes(y, i)); + } + heuristic[type] += abs((int8_t)filter[type][i]); + } + if (heuristic[type] < heuristic[minType]) minType = type; + } + + if (options.declareFilter) { + lines[y]->type = options.declareFilters[y % options.declareFilter]; + } else { + lines[y]->type = minType; + } + + if (options.applyFilter) { + enum Filter type = options.applyFilters[y % options.applyFilter]; + memcpy(lines[y]->data, filter[type], lineSize()); + } else { + memcpy(lines[y]->data, filter[minType], lineSize()); + } + } +} + +static void invert(void) { + for (uint32_t y = 0; y < header.height; ++y) { + for (size_t i = 0; i < lineSize(); ++i) { + lines[y]->data[i] ^= 0xFF; + } + } +} + +static void mirror(void) { + for (uint32_t y = 0; y < header.height; ++y) { + for (size_t i = 0, j = lineSize() - 1; i < j; ++i, --j) { + uint8_t t = lines[y]->data[i]; + lines[y]->data[i] = lines[y]->data[j]; + lines[y]->data[j] = t; + } + } +} + +static void zeroX(void) { + for (uint32_t y = 0; y < header.height; ++y) { + memset(lines[y]->data, 0, pixelSize()); + } +} + +static void zeroY(void) { + memset(lines[0]->data, 0, lineSize()); +} + +static void glitch(const char *inPath, const char *outPath) { + if (inPath) { + path = inPath; + file = fopen(path, "r"); + if (!file) err(EX_NOINPUT, "%s", path); + } else { + path = "(stdin)"; + file = stdin; + } + + readSignature(); + readHeader(); + if (header.color == Indexed) readPalette(); + readData(); + fclose(file); + + scanlines(); + reconData(); + filterData(); + if (options.invert) invert(); + if (options.mirror) mirror(); + if (options.zeroX) zeroX(); + if (options.zeroY) zeroY(); + free(lines); + + if (outPath) { + path = outPath; + file = fopen(path, "w"); + if (!file) err(EX_CANTCREAT, "%s", path); + } else { + path = "(stdout)"; + file = stdout; + } + + writeSignature(); + writeHeader(); + if (header.color == Indexed) writePalette(); + writeData(); + writeEnd(); + free(data); + + int error = fclose(file); + if (error) err(EX_IOERR, "%s", path); +} + +static enum Filter parseFilter(const char *s) { + switch (s[0]) { + case 'N': case 'n': return None; + case 'S': case 's': return Sub; + case 'U': case 'u': return Up; + case 'A': case 'a': return Average; + case 'P': case 'p': return Paeth; + default: errx(EX_USAGE, "invalid filter type %s", s); + } +} + +static uint8_t parseFilters(enum Filter *filters, const char *s) { + uint8_t len = 0; + do { + filters[len++] = parseFilter(s); + s = strchr(s, ','); + } while (s++); + return len; +} + +int main(int argc, char *argv[]) { + bool stdio = false; + char *output = NULL; + + int opt; + while (0 < (opt = getopt(argc, argv, "a:cd:fimo:prxy"))) { + switch (opt) { + break; case 'a': + options.applyFilter = parseFilters(options.applyFilters, optarg); + break; case 'c': stdio = true; + break; case 'd': + options.declareFilter = parseFilters(options.declareFilters, optarg); + break; case 'f': options.filt = true; + break; case 'i': options.invert = true; + break; case 'm': options.mirror = true; + break; case 'o': output = optarg; + break; case 'p': options.brokenPaeth = true; + break; case 'r': options.recon = true; + break; case 'x': options.zeroX = true; + break; case 'y': options.zeroY = true; + break; default: return EX_USAGE; + } + } + + if (argc - optind == 1 && (output || stdio)) { + glitch(argv[optind], output); + } else if (optind < argc) { + for (int i = optind; i < argc; ++i) { + glitch(argv[i], argv[i]); + } + } else { + glitch(NULL, output); + } + + return EX_OK; +} diff --git a/bin/hi.c b/bin/hi.c new file mode 100644 index 00000000..b39c0af4 --- /dev/null +++ b/bin/hi.c @@ -0,0 +1,766 @@ +/* vim: set foldmethod=marker foldlevel=0: */ +/* Copyright (C) 2019 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <err.h> +#include <locale.h> +#include <regex.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sysexits.h> +#include <unistd.h> + +#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) + +typedef unsigned Set; +#define SET(x) ((Set)1 << (x)) + +#define ENUM_CLASS \ + X(Normal) \ + X(Keyword) \ + X(Macro) \ + X(Tag) \ + X(String) \ + X(Escape) \ + X(Format) \ + X(Interp) \ + X(Comment) \ + X(Todo) \ + X(DiffOld) \ + X(DiffNew) + +enum Class { +#define X(class) class, + ENUM_CLASS +#undef X + ClassLen, +}; + +static const char *ClassName[ClassLen] = { +#define X(class) [class] = #class, + ENUM_CLASS +#undef X +}; + +struct Syntax { + enum Class class; + Set parent; + bool newline; + size_t subexp; + const char *pattern; +}; + +#define WB "(^|[^_[:alnum:]]|\n)" +#define BL0 "[[:blank:]]*" +#define BL1 "[[:blank:]]+" +#define SP0 "[[:space:]]*" +#define SP1 "[[:space:]]+" +#define PATTERN_ID "[_[:alpha:]][_[:alnum:]]*" +#define PATTERN_SQ "'([^']|[\\]')*'" +#define PATTERN_DQ "\"([^\"]|[\\]\")*\"" +#define PATTERN_BC "/[*]" "([^*]|[*][^/])*" "[*]+/" +#define PATTERN_TODO "FIXME|TODO|XXX" + +// C syntax {{{ +static const struct Syntax CSyntax[] = { + { Keyword, .subexp = 2, .pattern = WB + "(" "auto|extern|register|static|(_T|t)hread_local|typedef" + "|" "_Atomic|const|restrict|volatile" + "|" "inline|(_N|n)oreturn" + "|" "(_A|a)lignas" + "|" "enum|struct|union" + "|" "do|else|for|if|switch|while" + "|" "break|case|continue|default|goto|return" + ")" WB }, + { Macro, + .pattern = "^" BL0 "#(.|[\\]\n)*" }, + { Tag, .parent = SET(Macro), .subexp = 1, + .pattern = "define" BL1 "(" PATTERN_ID ")" "[(]" }, + { Tag, .subexp = 2, + .pattern = "(enum|struct|union)" SP1 "(" PATTERN_ID ")" SP0 "[{]" }, + { Tag, .parent = ~SET(Keyword), .newline = true, .subexp = 1, + .pattern = "(" PATTERN_ID ")" SP0 "[(][^()]*[)]" SP0 "[{]" }, + { Tag, .newline = true, .subexp = 3, .pattern = + "(static|typedef)" SP1 + "(" "(" PATTERN_ID ")" SP0 + "(" "[*]" "|" "[[][^]]*[]]" "|" "[{][^}]*[}]" "|" SP0 ")*" ")+" }, + { String, .parent = SET(Macro), .subexp = 2, + .pattern = "(include|import)" BL0 "(<[^>]*>)" }, + { String, + .pattern = "[LUu]?" PATTERN_SQ }, + { String, .parent = ~SET(String), + .pattern = "([LU]|u8?)?" PATTERN_DQ }, + { Escape, .parent = SET(String), + .pattern = "[\\]([\"'?\\abfnrtv]|[0-7]{1,3}|x[0-9A-Fa-f]+)" }, + { Escape, .parent = SET(String), + .pattern = "[\\](U[0-9A-Fa-f]{8}|u[0-9A-Fa-f]{4})" }, + { Format, .parent = SET(String), .pattern = + "%%|%[ #+-0]*" // flags + "([*]|[0-9]+)?" // field width + "([.]([*]|[0-9]+))?" // precision + "([Lhjltz]|hh|ll)?" // length modifier + "[AEFGXacdefginopsux]" // format specifier + }, + { Comment, .parent = ~SET(String), + .pattern = "//(.|[\\]\n)*" }, + { Comment, .parent = ~SET(String), .newline = true, + .pattern = PATTERN_BC }, + { Todo, .parent = SET(Comment), + .pattern = PATTERN_TODO }, +}; +// }}} + +// diff syntax {{{ +static const struct Syntax DiffSyntax[] = { + { Keyword, .pattern = "^[^ ].*" }, + { Comment, .pattern = "^@@.*" }, + { DiffOld, .pattern = "^[-].*" }, + { DiffNew, .pattern = "^[+].*" }, +}; +// }}} + +// make syntax {{{ +#define MAKE_TARGET "[-./_[:alnum:]]+" +static const struct Syntax MakeSyntax[] = { + { Keyword, .subexp = 2, + .pattern = WB "([.](PHONY|PRECIOUS|SUFFIXES))" WB }, + { Macro, + .pattern = "^ *-?include" }, + { Tag, .parent = ~SET(Keyword), .subexp = 1, .pattern = + "(" MAKE_TARGET ")" "(" BL1 MAKE_TARGET ")*" BL0 ":([^=]|$)" }, + { String, .subexp = 1, + .pattern = "[._[:alnum:]]+" BL0 "[!+:?]?=" BL0 "(.*)" }, + { Normal, + .pattern = "^\t.*" }, + { String, + .pattern = PATTERN_SQ }, + { String, + .pattern = PATTERN_DQ }, + { Interp, + .pattern = "[$]." }, + // Support one level of nesting with the same delimiter. + { Interp, + .pattern = "[$][(](" "[^$)]" "|" "[$]." "|" "[$][(][^)]*[)]" ")*[)]" }, + { Interp, + .pattern = "[$][{](" "[^$}]" "|" "[$]." "|" "[$][{][^}]*[}]" ")*[}]" }, + { Escape, + .pattern = "[$][$]" }, + { Comment, .parent = ~SET(String), + .pattern = "#.*" }, + { Todo, .parent = SET(Comment), + .pattern = PATTERN_TODO }, +}; +// }}} + +// mdoc syntax {{{ +static const struct Syntax MdocSyntax[] = { + { Keyword, .subexp = 2, .pattern = WB + "(" "D[dt]|N[dm]|Os" + "|" "S[hsx]|[LP]p|Xr" + "|" "%[ABCDIJNOPQRTUV]|[BE][dl]|D[1l]|It|Ql|R[es]|Ta" + "|" "Ap|[BE]k|Ns|Pf|Sm" + "|" "Ar|Cm|Ev|Fl|O[cop]|Pa" + "|" "Dv|Er|F[acdnot]|In|Lb|V[at]" + "|" "A[dn]|Cd|Lk|M[st]" + "|" "[BE]f|Em|Li|No|Sy" + "|" "(Br|[ABDPQS])[coq]|E[co]" + "|" "At|(Bs|[BDEFNO])x|Rv|St" + ")" WB }, + { Tag, .subexp = 1, + .pattern = "^[.]S[hs]" BL1 "(.+)" }, + { String, + .pattern = PATTERN_DQ }, + { Normal, + .pattern = "^[^.].*" }, + { String, + .pattern = "[\\](" "." "|" "[(].{2}" "|" "[[][^]]*[]]" ")" }, + { Comment, + .pattern = "^[.][\\]\".*" }, + { Todo, .parent = SET(Comment), + .pattern = PATTERN_TODO }, +}; +// }}} + +// Rust syntax {{{ +static const struct Syntax RustSyntax[] = { + { Keyword, .subexp = 2, .pattern = WB + "(" "'?static|[Ss]elf|abstract|as|async|await|become|box|break|const" + "|" "continue|crate|do|dyn|else|enum|extern|false|final|fn|for|if" + "|" "impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref" + "|" "return|struct|super|trait|true|try|type(of)?|union|uns(afe|ized)" + "|" "use|virtual|where|while|yield" + ")" WB }, + { Tag, .subexp = 2, .pattern = + "(enum|fn|macro_rules!|mod|struct|type|union)" SP1 "(" PATTERN_ID ")" }, + { Macro, .newline = true, + .pattern = "#!?[[][^]]*[]]" }, + { Macro, + .pattern = PATTERN_ID "!" }, + { Interp, + .pattern = "[$]" PATTERN_ID }, + { String, + .pattern = "b?'([^']|[\\]')'" }, + { String, + .pattern = "b?" "\"([^\"]|[\\][\n\"])*\"" }, + { Escape, .parent = SET(String), + .pattern = "[\\]([\"'0\\nrt]|u[{][0-9A-Fa-f]{1,6}[}]|x[0-9A-Fa-f]{2})" }, + { Format, .parent = SET(String), + .pattern = "[{][{]|[{][^{}]*[}]|[}][}]" }, + { String, .parent = ~SET(String), .newline = true, + .pattern = "b?r\"[^\"]*\"" }, + { String, .parent = ~SET(String), .newline = true, + .pattern = "b?r#+\"" "([^\"]|\"[^#])*" "\"+#+" }, + { Comment, .parent = ~SET(String), + .pattern = "//.*" }, + { Comment, .parent = ~SET(String), .newline = true, + .pattern = PATTERN_BC }, + { Todo, .parent = SET(Comment), + .pattern = PATTERN_TODO }, +}; +// }}} + +// sh syntax {{{ +static const struct Syntax ShSyntax[] = { + { Keyword, .subexp = 2, .pattern = WB + "(" "!|case|do|done|elif|else|esac|fi|for|if|in|then|until|while" + "|" "alias|bg|cd|command|false|fc|fg|getopts|jobs|kill|newgrp|pwd|read" + "|" "true|type|ulimit|umask|unalias|wait" + "|" "[.:]|break|continue|eval|exec|exit|export|local|readonly|return" + "|" "set|shift|times|trap|unset" + ")" WB }, + { Tag, .subexp = 2, + .pattern = WB "(" PATTERN_ID ")" BL0 "[(]" BL0 "[)]" }, + { String, .newline = true, .subexp = 1, .pattern = + "<<-?" BL0 "EOF[^\n]*\n" + "(([^\n]|\n\t*[^E]|\n\t*E[^O]|\n\t*EO[^F]|\n\t*EOF[^\n])*)" + "\n\t*EOF\n" }, + { String, .parent = ~SET(String), .newline = true, + .pattern = PATTERN_DQ }, + { Escape, .parent = SET(String), + .pattern = "[\\][\"$\\`]" }, + { Interp, .parent = ~SET(Escape), + .pattern = "[$][(][^)]*[)]" "|" "`[^`]*`" }, + { Interp, .parent = ~SET(Escape), + .pattern = "[$][(][(]([^)]|[)][^)])*[)][)]" }, + { String, .parent = SET(Interp), + .pattern = PATTERN_DQ }, + { Interp, .parent = ~SET(Escape), + .pattern = "[$]([!#$*?@-]|[_[:alnum:]]+|[{][^}]*[}])" }, + { String, .parent = ~SET(Escape), + .pattern = "[\\]." }, + { String, .subexp = 1, .newline = true, .pattern = + "<<-?" BL0 "'EOF'[^\n]*\n" + "(([^\n]|\n\t*[^E]|\n\t*E[^O]|\n\t*EO[^F]|\n\t*EOF[^\n])*)" + "\n\t*EOF\n" }, + { String, .parent = ~SET(String), .newline = true, + .pattern = "'[^']*'" }, + { Comment, .parent = ~SET(String), .subexp = 2, + .pattern = "(^|[[:blank:]]+)(#.*)" }, + { Todo, .parent = SET(Comment), + .pattern = PATTERN_TODO }, +}; +// }}} + +static const struct Language { + const char *name; + const char *pattern; + const struct Syntax *syntax; + size_t len; +} Languages[] = { + { "c", "[.][chlmy]$", CSyntax, ARRAY_LEN(CSyntax) }, + { "diff", "[.](diff|patch)$", DiffSyntax, ARRAY_LEN(DiffSyntax) }, + { "make", "[.]mk$|^Makefile$", MakeSyntax, ARRAY_LEN(MakeSyntax) }, + { "mdoc", "[.][1-9]$", MdocSyntax, ARRAY_LEN(MdocSyntax) }, + { "rust", "[.]rs$", RustSyntax, ARRAY_LEN(RustSyntax) }, + { "sh", "[.](sh|in)$|^[.](profile|shrc)$", ShSyntax, ARRAY_LEN(ShSyntax) }, + { "text", "[.]txt$", NULL, 0 }, +}; + +static regex_t compile(const char *pattern, int flags) { + regex_t regex; + int error = regcomp(®ex, pattern, REG_EXTENDED | flags); + if (!error) return regex; + char buf[256]; + regerror(error, ®ex, buf, sizeof(buf)); + errx(EX_SOFTWARE, "regcomp: %s: %s", buf, pattern); +} + +enum { SubsLen = 8 }; +static void highlight(struct Language lang, enum Class *hi, const char *str) { + for (size_t i = 0; i < lang.len; ++i) { + struct Syntax syn = lang.syntax[i]; + regex_t regex = compile(syn.pattern, syn.newline ? 0 : REG_NEWLINE); + assert(syn.subexp < SubsLen); + assert(syn.subexp <= regex.re_nsub); + regmatch_t subs[SubsLen] = {{0}}; + for (size_t offset = 0; str[offset]; offset += subs[syn.subexp].rm_eo) { + int error = regexec( + ®ex, &str[offset], SubsLen, subs, offset ? REG_NOTBOL : 0 + ); + if (error == REG_NOMATCH) break; + if (error) errx(EX_SOFTWARE, "regexec: %d", error); + regmatch_t *sub = &subs[syn.subexp]; + if (syn.parent && !(syn.parent & SET(hi[offset + sub->rm_so]))) { + sub->rm_eo = sub->rm_so + 1; + continue; + } + for (regoff_t j = sub->rm_so; j < sub->rm_eo; ++j) { + hi[offset + j] = lang.syntax[i].class; + } + } + regfree(®ex); + } +} + +static void check(void) { + for (size_t i = 0; i < ARRAY_LEN(Languages); ++i) { + regex_t regex = compile(Languages[i].pattern, REG_NOSUB); + regfree(®ex); + for (size_t j = 0; j < Languages[i].len; ++j) { + struct Syntax syn = Languages[i].syntax[j]; + regex = compile(syn.pattern, 0); + if (syn.subexp >= SubsLen || syn.subexp > regex.re_nsub) { + errx( + EX_SOFTWARE, "subexpression %zu out of bounds: %s", + syn.subexp, syn.pattern + ); + } + regfree(®ex); + } + } +} + +#define ENUM_OPTION \ + X(Anchor, "anchor") \ + X(CSS, "css") \ + X(Document, "document") \ + X(Inline, "inline") \ + X(Monospace, "monospace") \ + X(Tab, "tab") \ + X(Title, "title") + +enum Option { +#define X(option, _) option, + ENUM_OPTION +#undef X + OptionLen, +}; + +static const char *OptionKey[OptionLen + 1] = { +#define X(option, key) [option] = key, + ENUM_OPTION +#undef X + NULL, +}; + +typedef void HeaderFn(const char *opts[]); +typedef void +OutputFn(const char *opts[], enum Class class, const char *str, size_t len); + +// ANSI format {{{ + +enum SGR { + SGRBoldOn = 1, + SGRUnderlineOn = 4, + SGRBoldOff = 22, + SGRUnderlineOff = 24, + SGRBlack = 30, + SGRRed, + SGRGreen, + SGRYellow, + SGRBlue, + SGRMagenta, + SGRCyan, + SGRWhite, + SGRDefault = 39, +}; + +static const enum SGR ANSIStyle[ClassLen][3] = { + [Normal] = { SGRDefault }, + [Keyword] = { SGRWhite }, + [Macro] = { SGRGreen }, + [Tag] = { SGRDefault, SGRUnderlineOn, SGRUnderlineOff }, + [String] = { SGRCyan }, + [Escape] = { SGRDefault }, + [Format] = { SGRCyan, SGRBoldOn, SGRBoldOff }, + [Interp] = { SGRYellow }, + [Comment] = { SGRBlue }, + [Todo] = { SGRBlue, SGRBoldOn, SGRBoldOff }, + [DiffOld] = { SGRRed }, + [DiffNew] = { SGRGreen }, +}; + +static void +ansiOutput(const char *opts[], enum Class class, const char *str, size_t len) { + (void)opts; + if (ANSIStyle[class][1]) { + printf( + "\x1B[%d;%dm%.*s\x1B[%dm", + ANSIStyle[class][0], ANSIStyle[class][1], + (int)len, str, + ANSIStyle[class][2] + ); + } else { + printf("\x1B[%dm%.*s", ANSIStyle[class][0], (int)len, str); + } +} + +// }}} + +// IRC format {{{ + +enum IRC { + IRCWhite, + IRCBlack, + IRCBlue, + IRCGreen, + IRCRed, + IRCBrown, + IRCMagenta, + IRCOrange, + IRCYellow, + IRCLightGreen, + IRCCyan, + IRCLightCyan, + IRCLightBlue, + IRCPink, + IRCGray, + IRCLightGray, + IRCBold = 0x02, + IRCColor = 0x03, + IRCMonospace = 0x11, + IRCUnderline = 0x1F, +}; + +static const enum IRC SGRIRC[] = { + [SGRBoldOn] = IRCBold, + [SGRBoldOff] = IRCBold, + [SGRUnderlineOn] = IRCUnderline, + [SGRUnderlineOff] = IRCUnderline, + [SGRBlack] = IRCBlack, + [SGRRed] = IRCRed, + [SGRGreen] = IRCGreen, + [SGRYellow] = IRCYellow, + [SGRBlue] = IRCBlue, + [SGRMagenta] = IRCMagenta, + [SGRCyan] = IRCCyan, + [SGRWhite] = IRCGray, + [SGRDefault] = 0, +}; + +static void ircHeader(const char *opts[]) { + if (opts[Monospace]) printf("%c", IRCMonospace); +} + +static void +ircOutput(const char *opts[], enum Class class, const char *str, size_t len) { + char cc[3] = ""; + if (ANSIStyle[class][0] != SGRDefault) { + snprintf(cc, sizeof(cc), "%d", SGRIRC[ANSIStyle[class][0]]); + } + // Prevent trailing formatting after newline ... + bool newline = (str[len - 1] == '\n'); + if (ANSIStyle[class][1]) { + printf( + "%c%s%c%.*s%c%s", + IRCColor, cc, SGRIRC[ANSIStyle[class][1]], + (int)(newline ? len - 1 : len), str, + SGRIRC[ANSIStyle[class][2]], + (newline ? "\n" : "") + ); + } else { + // Double-toggle bold to prevent str being interpreted as color. + printf("%c%s%c%c%.*s", IRCColor, cc, IRCBold, IRCBold, (int)len, str); + } + // ... except for monospace, at the beginning of each line. + if (newline && opts[Monospace]) printf("%c", IRCMonospace); +} + +// }}} + +// HTML format {{{ + +static void htmlEscape(const char *str, size_t len) { + while (len) { + size_t run = strcspn(str, "\"&<>"); + if (run > len) run = len; + switch (str[0]) { + break; case '"': run = 1; printf("""); + break; case '&': run = 1; printf("&"); + break; case '<': run = 1; printf("<"); + break; case '>': run = 1; printf(">"); + break; default: printf("%.*s", (int)run, str); + } + str += run; + len -= run; + } +} + +static const char *HTMLStyle[ClassLen] = { + [Keyword] = "color: dimgray;", + [Macro] = "color: green;", + [Tag] = "color: inherit; text-decoration: underline;", + [String] = "color: teal;", + [Format] = "color: teal; font-weight: bold;", + [Interp] = "color: olive;", + [Comment] = "color: navy;", + [Todo] = "color: navy; font-weight: bold;", + [DiffOld] = "color: red;", + [DiffNew] = "color: green;", +}; + +static void htmlTabSize(const char *tab) { + printf("-moz-tab-size: "); + htmlEscape(tab, strlen(tab)); + printf("; tab-size: "); + htmlEscape(tab, strlen(tab)); + printf(";"); +} + +static void htmlHeader(const char *opts[]) { + if (!opts[Document]) goto body; + printf("<!DOCTYPE html>\n<title>"); + if (opts[Title]) htmlEscape(opts[Title], strlen(opts[Title])); + printf("</title>\n"); + if (opts[CSS]) { + printf("<link rel=\"stylesheet\" href=\""); + htmlEscape(opts[CSS], strlen(opts[CSS])); + printf("\">\n"); + } else if (!opts[Inline]) { + printf("<style>\n"); + if (opts[Tab]) { + printf("pre.hi { "); + htmlTabSize(opts[Tab]); + printf(" }\n"); + } + for (enum Class class = 0; class < ClassLen; ++class) { + if (!HTMLStyle[class]) continue; + printf(".hi.%s { %s }\n", ClassName[class], HTMLStyle[class]); + } + printf( + ".hi.%s:target { color: goldenrod; outline: none; }\n", + ClassName[Tag] + ); + printf("</style>\n"); + } +body: + if (opts[Inline] && opts[Tab]) { + printf("<pre class=\"hi\" style=\""); + htmlTabSize(opts[Tab]); + printf("\">"); + } else { + printf("<pre class=\"hi\">"); + } +} + +static void htmlFooter(const char *opts[]) { + (void)opts; + printf("</pre>\n"); +} + +static void htmlAnchor(const char *opts[], const char *str, size_t len) { + if (opts[Inline]) { + printf("<a style=\"%s\" id=\"", HTMLStyle[Tag] ? HTMLStyle[Tag] : ""); + } else { + printf("<a class=\"hi %s\" id=\"", ClassName[Tag]); + } + htmlEscape(str, len); + printf("\" href=\"#"); + htmlEscape(str, len); + printf("\">"); + htmlEscape(str, len); + printf("</a>"); +} + +static void +htmlOutput(const char *opts[], enum Class class, const char *str, size_t len) { + if (opts[Anchor] && class == Tag) { + htmlAnchor(opts, str, len); + return; + } + if (opts[Inline]) { + printf("<span style=\"%s\">", HTMLStyle[class] ? HTMLStyle[class] : ""); + } else { + printf("<span class=\"hi %s\">", ClassName[class]); + } + htmlEscape(str, len); + printf("</span>"); +} + +// }}} + +// Debug format {{{ +static void +debugOutput(const char *opts[], enum Class class, const char *str, size_t len) { + (void)opts; + printf("%s\t\"", ClassName[class]); + while (len) { + size_t run = strcspn(str, "\t\n\"\\"); + if (run > len) run = len; + switch (str[0]) { + break; case '\t': run = 1; printf("\\t"); + break; case '\n': run = 1; printf("\\n"); + break; case '"': run = 1; printf("\\\""); + break; case '\\': run = 1; printf("\\\\"); + break; default: printf("%.*s", (int)run, str); + } + str += run; + len -= run; + } + printf("\"\n"); +} +// }}} + +static const struct Format { + const char *name; + OutputFn *output; + HeaderFn *header; + HeaderFn *footer; +} Formats[] = { + { "ansi", ansiOutput, NULL, NULL }, + { "irc", ircOutput, ircHeader, NULL }, + { "html", htmlOutput, htmlHeader, htmlFooter }, + { "debug", debugOutput, NULL, NULL }, +}; + +static bool findLanguage(struct Language *lang, const char *name) { + for (size_t i = 0; i < ARRAY_LEN(Languages); ++i) { + if (strcmp(name, Languages[i].name)) continue; + *lang = Languages[i]; + return true; + } + return false; +} + +static bool matchLanguage(struct Language *lang, const char *name) { + for (size_t i = 0; i < ARRAY_LEN(Languages); ++i) { + regex_t regex = compile(Languages[i].pattern, REG_NOSUB); + int error = regexec(®ex, name, 0, NULL, 0); + regfree(®ex); + if (error == REG_NOMATCH) continue; + if (error) errx(EX_SOFTWARE, "regexec: %d", error); + *lang = Languages[i]; + return true; + } + return false; +} + +static bool findFormat(struct Format *format, const char *name) { + for (size_t i = 0; i < ARRAY_LEN(Formats); ++i) { + if (strcmp(name, Formats[i].name)) continue; + *format = Formats[i]; + return true; + } + return false; +} + +int main(int argc, char *argv[]) { + setlocale(LC_CTYPE, ""); + + bool text = false; + const char *name = NULL; + struct Language lang = {0}; + struct Format format = Formats[0]; + const char *opts[OptionLen] = {0}; + + int opt; + while (0 < (opt = getopt(argc, argv, "cf:l:n:o:t"))) { + switch (opt) { + break; case 'c': check(); return EX_OK; + break; case 'f': { + if (!findFormat(&format, optarg)) { + errx(EX_USAGE, "no such format %s", optarg); + } + } + break; case 'l': { + if (!findLanguage(&lang, optarg)) { + errx(EX_USAGE, "no such language %s", optarg); + } + } + break; case 'n': name = optarg; + break; case 'o': { + char *val; + enum Option key; + while (optarg[0]) { + key = getsubopt(&optarg, (char *const *)OptionKey, &val); + if (key >= OptionLen) { + errx(EX_USAGE, "no such option %s", val); + } + opts[key] = (val ? val : ""); + } + } + break; case 't': text = true; + break; default: return EX_USAGE; + } + } + + const char *path = "(stdin)"; + FILE *file = stdin; + if (optind < argc) { + path = argv[optind]; + file = fopen(path, "r"); + if (!file) err(EX_NOINPUT, "%s", path); + } + + if (!name) { + name = strrchr(path, '/'); + name = (name ? &name[1] : path); + } + if (!lang.name && !matchLanguage(&lang, name) && !text) { + errx(EX_USAGE, "cannot infer language for %s", name); + } + if (!opts[Title]) opts[Title] = name; + + struct stat stat; + int error = fstat(fileno(file), &stat); + if (error) err(EX_IOERR, "fstat"); + + size_t cap = (stat.st_mode & S_IFREG ? stat.st_size + 1 : 4096); + char *str = malloc(cap); + if (!str) err(EX_OSERR, "malloc"); + + size_t len = 0, read; + while (0 < (read = fread(&str[len], 1, cap - len - 1, file))) { + len += read; + if (len + 1 < cap) continue; + cap *= 2; + str = realloc(str, cap); + if (!str) err(EX_OSERR, "realloc"); + } + if (ferror(file)) err(EX_IOERR, "fread"); + if (memchr(str, 0, len)) errx(EX_DATAERR, "input is binary"); + str[len] = '\0'; + + enum Class *hi = calloc(len, sizeof(*hi)); + if (!hi) err(EX_OSERR, "calloc"); + + highlight(lang, hi, str); + + size_t run = 0; + if (format.header) format.header(opts); + for (size_t i = 0; i < len; i += run) { + for (run = 1; i + run < len; ++run) { + if (hi[i + run] != hi[i]) break; + if (str[i + run - 1] == '\n') break; + } + format.output(opts, hi[i], &str[i], run); + } + if (format.footer) format.footer(opts); +} diff --git a/bin/hnel.c b/bin/hnel.c new file mode 100644 index 00000000..4a869c2d --- /dev/null +++ b/bin/hnel.c @@ -0,0 +1,120 @@ +/* Copyright (C) 2017 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <poll.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <sysexits.h> +#include <termios.h> +#include <unistd.h> + +#if defined __FreeBSD__ +#include <libutil.h> +#elif defined __linux__ +#include <pty.h> +#else +#include <util.h> +#endif + +typedef unsigned char byte; + +static struct termios saveTerm; +static void restoreTerm(void) { + tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm); +} + +int main(int argc, char *argv[]) { + int error; + + if (argc < 4) return EX_USAGE; + + byte table[256] = {0}; + if (strlen(argv[1]) != strlen(argv[2])) return EX_USAGE; + for (const char *from = argv[1], *to = argv[2]; *from; ++from, ++to) { + table[(byte)*from] = *to; + } + + error = tcgetattr(STDERR_FILENO, &saveTerm); + if (error) err(EX_IOERR, "tcgetattr"); + atexit(restoreTerm); + + struct termios raw = saveTerm; + cfmakeraw(&raw); + error = tcsetattr(STDERR_FILENO, TCSADRAIN, &raw); + if (error) err(EX_IOERR, "tcsetattr"); + + struct winsize window; + error = ioctl(STDERR_FILENO, TIOCGWINSZ, &window); + if (error) err(EX_IOERR, "TIOCGWINSZ"); + + int pty; + pid_t pid = forkpty(&pty, NULL, NULL, &window); + if (pid < 0) err(EX_OSERR, "forkpty"); + + if (!pid) { + execvp(argv[3], &argv[3]); + err(EX_NOINPUT, "%s", argv[3]); + } + + bool enable = true; + + byte buf[4096]; + struct pollfd fds[2] = { + { .fd = STDIN_FILENO, .events = POLLIN }, + { .fd = pty, .events = POLLIN }, + }; + while (0 < poll(fds, 2, -1)) { + if (fds[0].revents & POLLIN) { + ssize_t readSize = read(STDIN_FILENO, buf, sizeof(buf)); + if (readSize < 0) err(EX_IOERR, "read(%d)", STDIN_FILENO); + + if (readSize == 1) { + if (buf[0] == CTRL('S')) { + enable ^= true; + continue; + } + + byte c = buf[0]; + if (enable && table[c]) buf[0] = table[c]; + } + + ssize_t writeSize = write(pty, buf, readSize); + if (writeSize < 0) err(EX_IOERR, "write(%d)", pty); + if (writeSize < readSize) errx(EX_IOERR, "short write(%d)", pty); + } + + if (fds[1].revents & POLLIN) { + ssize_t readSize = read(pty, buf, sizeof(buf)); + if (readSize < 0) err(EX_IOERR, "read(%d)", pty); + + ssize_t writeSize = write(STDOUT_FILENO, buf, readSize); + if (writeSize < 0) err(EX_IOERR, "write(%d)", STDOUT_FILENO); + if (writeSize < readSize) { + errx(EX_IOERR, "short write(%d)", STDOUT_FILENO); + } + } + + int status; + pid_t dead = waitpid(pid, &status, WNOHANG); + if (dead < 0) err(EX_OSERR, "waitpid(%d)", pid); + if (dead) return WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE; + } + err(EX_IOERR, "poll"); +} diff --git a/bin/html.sh b/bin/html.sh new file mode 100644 index 00000000..e96a7210 --- /dev/null +++ b/bin/html.sh @@ -0,0 +1,41 @@ +#!/bin/sh +set -eu + +readonly GitURL='https://git.causal.agency/src/tree/bin' + +src=$1 +man=${2:-} + +cat <<EOF +<!DOCTYPE html> +<title>${src}</title> +<style> +$(./scheme -st) +html { + font-family: monospace; + color: var(--ansi17); + background-color: var(--ansi16); +} +a { color: var(--ansi4); } +a:visited { color: var(--ansi5); } +pre.hi { + -moz-tab-size: 4; + tab-size: 4; +} +.hi.Keyword { color: var(--ansi7); } +.hi.Macro { color: var(--ansi2); } +.hi.Tag { color: inherit; text-decoration: underline; } +.hi.Tag:target { color: var(--ansi11); outline: none; } +.hi.String { color: var(--ansi6); } +.hi.Format { color: var(--ansi14); } +.hi.Interp { color: var(--ansi3); } +.hi.Comment { color: var(--ansi4); } +.hi.Todo { color: var(--ansi12); } +.hi.DiffOld { color: var(--ansi1); } +.hi.DiffNew { color: var(--ansi2); } +</style> +<a href="${GitURL}/${src}">${src} in git</a> +EOF + +[ -f "$man" ] && man -P cat "${PWD}/${man}" | ./ttpre +./hi -t -f html -o anchor "$src" diff --git a/bin/man1/beef.1 b/bin/man1/beef.1 new file mode 100644 index 00000000..ea52cfa0 --- /dev/null +++ b/bin/man1/beef.1 @@ -0,0 +1,91 @@ +.Dd August 28, 2019 +.Dt BEEF 1 +.Os +. +.Sh NAME +.Nm beef +.Nd Befunge-93 interpreter +. +.Sh SYNOPSIS +.Nm +.Op Ar file +. +.Sh DESCRIPTION +.Nm +is a Befunge-93 interpreter. +If no +.Ar file +is provided, +the program is read from standard input. +. +.Ss Befunge-93 Command Summary +.Bl -tag -width "0-9" -compact +.It \(dq +toggle string mode +.It 0-9 +push value +.It + +add +.It - +subtract +.It * +multiply +.It / +divide +.It % +modulo +.It ! +not +.It ` +greater than +.It > +right +.It < +left +.It ^ +up +.It v +down +.It ? +random +.It _ +horizontal (left) if +.It | +vertical (up) if +.It : +duplicate +.It \e +swap +.It $ +drop +.It . +output integer +.It , +output ASCII +.It # +bridge +.It g +get (y, x) +.It p +put (y, x) = v +.It & +input integer +.It ~ +input ASCII +.It @ +exit +.El +. +.Sh EXIT STATUS +.Nm +exits with the top value left on the stack, +or 0 if the stack is left empty. +. +.Sh STANDARDS +.Rs +.%A Chris Pressey +.%Q Cat's Eye Technologies +.%T Befunge-93 +.%D September, 1993 +.%U https://github.com/catseye/Befunge-93/blob/master/doc/Befunge-93.markdown +.Re diff --git a/bin/man1/bibsort.1 b/bin/man1/bibsort.1 new file mode 100644 index 00000000..9d036920 --- /dev/null +++ b/bin/man1/bibsort.1 @@ -0,0 +1,37 @@ +.Dd December 15, 2020 +.Dt BIBSORT 1 +.Os +. +.Sh NAME +.Nm bibsort +.Nd reformat bibliography +. +.Sh SYNOPSIS +.Nm +.Op Ar file +. +.Sh DESCRIPTION +.Nm +reformats on standard output +the +.Em STANDARDS +section of the +.Xr mdoc 7 +manual page +.Ar file +or standard input. +Bibliographic references +are sorted by author last names, +and formatted in an item list +with macro lines appearing +in the order they are formatted by +.Xr mandoc 1 . +Additionally, +.Ic \&%N +macros referencing RFC numbers +are rewritten to +.Ic \&%R +macros. +. +.Sh EXAMPLES +.Dl :%!bibsort diff --git a/bin/man1/bit.1 b/bin/man1/bit.1 new file mode 100644 index 00000000..b61bc704 --- /dev/null +++ b/bin/man1/bit.1 @@ -0,0 +1,45 @@ +.Dd June 7, 2019 +.Dt BIT 1 +.Os +. +.Sh NAME +.Nm bit +.Nd a calculator +. +.Sh SYNOPSIS +.Nm +. +.Sh DESCRIPTION +.Nm +is an integer calculator. +Its syntax resembles that of C expressions, +with the following changes: +. +.Bl -bullet +.It +Underscores are allowed in integer literals. +.It +The +.Sy 0b +prefix is used for binary literals. +.It +The +.Sy -> +operator is used for arithmetic shift. +.It +The postfix operators +.Sy K , +.Sy M , +.Sy G , +.Sy T +are used as constant multipliers. +.It +Single-letter (lower case) variables +can be assigned. +The variable +.Sy _ +stores the previous result. +.El +. +.Sh SEE ALSO +.Xr operator 7 diff --git a/bin/man1/bri.1 b/bin/man1/bri.1 new file mode 100644 index 00000000..54a02322 --- /dev/null +++ b/bin/man1/bri.1 @@ -0,0 +1,44 @@ +.Dd September 7, 2018 +.Dt BRI 1 +.Os +. +.Sh NAME +.Nm bri +.Nd backlight brightness control +. +.Sh SYNOPSIS +.Nm +.Op Ar brightness +.Nm +.Cm + +.Nm +.Cm - +. +.Sh DESCRIPTION +.Nm +controls the backlight brightness on Linux. +. +.Pp +With no argument, +the current brightness is printed. +With a numerical +.Ar brightness +argument, +the brightness is set. +. +.Pp +The +.Cm + +and +.Cm - +arguments +may be repeated any number of times +and adjust the brightness +in increments of 16. +. +.Sh FILES +.Bl -tag +.It Pa /sys/class/backlight +Files under the first subdirectory found +are used to control the backlight brightness. +.El diff --git a/bin/man1/c.1 b/bin/man1/c.1 new file mode 100644 index 00000000..3ae10945 --- /dev/null +++ b/bin/man1/c.1 @@ -0,0 +1,40 @@ +.Dd May 31, 2020 +.Dt C 1 +.Os +. +.Sh NAME +.Nm c +.Nd run C +. +.Sh SYNOPSIS +.Nm +.Op Fl e Ar expr +.Op Fl i Ar include +.Op Ar stmts ... +. +.Sh DESCRIPTION +The +.Nm +utility compiles and runs +C statements wrapped in +.Fn main +with common includes. +If no +.Ar expr +or +.Ar stmts +are provided, +statements are read from standard input. +. +.Pp +The arguments are as follows: +.Bl -tag -width Ds +.It Fl e Ar expr +Print the result of the C expression +.Ar expr +after executing +.Ar stmts . +.It Fl i Ar include +Add the include file +.Ar include . +.El diff --git a/bin/man1/dtch.1 b/bin/man1/dtch.1 new file mode 100644 index 00000000..e27713e1 --- /dev/null +++ b/bin/man1/dtch.1 @@ -0,0 +1,67 @@ +.Dd August 12, 2019 +.Dt DTCH 1 +.Os +. +.Sh NAME +.Nm dtch +.Nd detached sessions +. +.Sh SYNOPSIS +.Nm +.Op Fl s +.Ar name +.Op Ar command ... +.Nm +.Fl a +.Ar name +. +.Sh DESCRIPTION +.Nm +spawns a +.Ar command +in a detachable session. +If no +.Ar command +is given, +the value of +.Ev SHELL +is used. +The +.Nm +process +should be run as a background job +or with +.Xr nohup 1 . +. +.Pp +To attach to an existing session, +pass the +.Fl a +flag. +To detach from the session, +type +.Ic ^Q . +. +.Pp +The arguments are as follows: +.Bl -tag -width Ds +.It Fl a +Attach to an existing session. +.It Fl s +Sink the output of +.Ar command +while detached. +.El +. +.Sh FILES +.Bl -tag -width Ds +.It Pa ~/.dtch +Directory of UNIX-domain sockets +for each session. +.El +. +.Sh EXAMPLES +.Bd -literal -offset indent +dtch foo vim & +dtch -a foo +.Ed diff --git a/bin/man1/ever.1 b/bin/man1/ever.1 new file mode 100644 index 00000000..7689c5fb --- /dev/null +++ b/bin/man1/ever.1 @@ -0,0 +1,49 @@ +.Dd June 1, 2020 +.Dt EVER 1 +.Os +. +.Sh NAME +.Nm ever +.Nd watch files +. +.Sh SYNOPSIS +.Nm +.Op Fl i +.Ar +.Ar command +.Nm +.Op Fl i +.Ar +.Fl - +.Ar command +.Op Ar argument ... +. +.Sh DESCRIPTION +.Nm +executes the +.Ar command +whenever +.Ar file +is modified. +. +.Pp +The arguments are as follows: +.Bl -tag -width Ds +.It Fl i +Attach the +.Ar file +which was modified +to the standard input of +.Ar command . +.El +. +.Sh EXAMPLES +.Dl ever ever.c make +.Dl ever when.y ever.c -- make when ever +.Dl ever -i ever.1 mandoc +. +.Sh CAVEATS +.Nm +does not support Linux +since it uses +.Xr kqueue 2 . diff --git a/bin/man1/fbatt.1 b/bin/man1/fbatt.1 new file mode 100644 index 00000000..2d30cba7 --- /dev/null +++ b/bin/man1/fbatt.1 @@ -0,0 +1,34 @@ +.Dd September 7, 2018 +.Dt FBATT 1 +.Os +. +.Sh NAME +.Nm fbatt +.Nd framebuffer battery indicator +. +.Sh SYNOPSIS +.Nm +. +.Sh DESCRIPTION +.Nm +displays a battery charge indicator +in the top-right corner +of the Linux framebuffer. +. +.Sh ENVIRONMENT +.Bl -tag +.It Ev FRAMEBUFFER +The framebuffer device path. +.El +. +.Sh FILES +.Bl -tag +.It Pa /dev/fb0 +The default framebuffer device path. +.It Pa /sys/class/power_supply +The first subdirectory containing +.Pa charge_full +and +.Pa charge_now +is used to read the battery charge. +.El diff --git a/bin/man1/fbclock.1 b/bin/man1/fbclock.1 new file mode 100644 index 00000000..3195eb42 --- /dev/null +++ b/bin/man1/fbclock.1 @@ -0,0 +1,36 @@ +.Dd September 7, 2018 +.Dt FBCLOCK 1 +.Os +. +.Sh NAME +.Nm fbclock +.Nd framebuffer clock +. +.Sh SYNOPSIS +.Nm +. +.Sh DESCRIPTION +.Nm +displays a clock +in the top-right corner +of the Linux framebuffer. +. +.Sh ENVIRONMENT +.Bl -tag +.It Ev FONT +Path to gzipped PSF file. +. +.It Ev FRAMEBUFFER +The framebuffer device path. +.El +. +.Sh FILES +.Bl -tag +.It Pa /dev/fb0 +The default framebuffer device path. +.It Pa /usr/share/kbd/consolefonts/Lat2-Terminus16.psfu.gz +The default font path. +.El +. +.Sh SEE ALSO +.Xr setfont 8 diff --git a/bin/man1/glitch.1 b/bin/man1/glitch.1 new file mode 100644 index 00000000..6562c4dc --- /dev/null +++ b/bin/man1/glitch.1 @@ -0,0 +1,77 @@ +.Dd September 7, 2018 +.Dt GLITCH 1 +.Os +. +.Sh NAME +.Nm glitch +.Nd PNG glitcher +. +.Sh SYNOPSIS +.Nm +.Op Fl cfimprxy +.Op Fl a Ar filters +.Op Fl d Ar filters +.Op Fl o Ar file +.Op Ar +. +.Sh DESCRIPTION +.Nm +misinterprets PNG files +according to the options given +to create natural glitch effects. +. +.Pp +The arguments are as follows: +.Bl -tag -width Ds +.It Fl a Ar filters +Apply a pattern of comma-separated filters. +Filters are +.Cm none , +.Cm sub , +.Cm up , +.Cm average , +.Cm paeth . +. +.It Fl c +Write to standard output. +. +.It Fl d Ar filters +Declare a pattern of comma-separated filters. +See +.Fl a +for list of filters. +. +.It Fl f +Apply filtering in place of reconstruction. +. +.It Fl i +Invert image data after filtering. +. +.It Fl m +Mirror scanlines after filtering. +. +.It Fl o Ar file +Write to +.Ar file . +. +.It Fl p +Use a broken Paeth predictor function. +. +.It Fl r +Apply reconstruction in place of filtering. +. +.It Fl x +Zero first pixel of each scanline after filtering. +. +.It Fl y +Zero first scanline after filtering. +.El +. +.Sh EXAMPLES +.Dl glitch -m -a sub -d sub +. +.Sh SEE ALSO +.Xr pngo 1 +. +.Sh BUGS +More wanted. diff --git a/bin/man1/hi.1 b/bin/man1/hi.1 new file mode 100644 index 00000000..517fbab9 --- /dev/null +++ b/bin/man1/hi.1 @@ -0,0 +1,173 @@ +.Dd December 15, 2019 +.Dt HI 1 +.Os +. +.Sh NAME +.Nm hi +.Nd syntax highlighter +. +.Sh SYNOPSIS +.Nm +.Op Fl t +.Op Fl f Ar format +.Op Fl l Ar lang +.Op Fl n Ar name +.Op Fl o Ar opts +.Op Ar file +.Nm +.Fl c +. +.Sh DESCRIPTION +.Nm +highlights the contents of a +.Ar file +or standard input +and formats it +on standard output. +. +.Pp +The arguments are as follows: +.Bl -tag -width "-f format" +.It Fl c +Compile all regular expressions and exit. +.It Fl f Ar format +Set the output format. +.It Fl l Ar lang +Set the input language. +.It Fl n Ar name +Override the name used +to infer the input language. +.It Fl o Ar opts +Set output format options. +.Ar opts +is a comma-separated list of options. +.It Fl t +Default to +.Cm text +if the input language cannot be inferred. +.El +. +.Ss Output Formats +The default output format is +.Cm ansi . +. +.Bl -tag -width Ds +.It Cm ansi +Output ANSI terminal escape codes. +. +.It Cm irc +Output IRC formatting codes. +.Pp +The options are as follows: +.Bl -tag -width "monospace" +.It Cm monospace +Use the monospace formatting code +introduced by IRCCloud. +.El +. +.It Cm html +Output HTML +.Sy <pre> +with +.Sy <span> +classes. +. +.Pp +The options are as follows: +.Bl -tag -width "title=..." +.It Cm anchor +Output tags +(top-level definition names) +as anchor links. +. +.It Cm css Ns = Ns Ar url +With +.Cm document , +output a +.Sy <link> +element for the external stylesheet +.Ar url . +If unset, +output default styles in a +.Sy <style> +element. +. +.It Cm document +Output an HTML document. +. +.It Cm inline +Output inline +.Sy style +attributes rather than classes. +. +.It Cm tab Ns = Ns Ar n +With +.Cm document +or +.Cm inline , +set the +.Sy tab-size +property to +.Ar n . +. +.It Cm title Ns = Ns Ar ... +With +.Cm document , +set the +.Sy <title> +element text. +The default title is the +.Ar file +name. +.El +.El +. +.Ss Input Languages +If no input language is set with +.Fl l , +it may be inferred from the name set by +.Fl m +or from the provided +.Ar file +name. +. +.Bl -tag -width Ds +.It Cm c +The C11 language. +. +.It Cm diff +The output of +.Xr diff 1 +with the +.Fl u +flag. +. +.It Cm make +The portable subset of +.Xr make 1 . +Variable substitution supports +one level of nesting with the same delimiter. +. +.It Cm mdoc +The +.Xr mdoc 7 +language. +. +.It Cm rust +The Rust 2018 language. +Nested raw strings and block comments +are not highlighted correctly. +. +.It Cm sh +The POSIX +.Xr sh 1 +language. +Here-documents are correctly highlighted +only with a delimiter of +.Ql EOF . +Arbitrarily nested strings and command substitutions +are not highlighted correctly. +. +.It Cm text +Plain text. +.El diff --git a/bin/man1/hnel.1 b/bin/man1/hnel.1 new file mode 100644 index 00000000..82305be8 --- /dev/null +++ b/bin/man1/hnel.1 @@ -0,0 +1,36 @@ +.Dd July 8, 2019 +.Dt HNEL 1 +.Os +. +.Sh NAME +.Nm hnel +.Nd PTY input remapper +. +.Sh SYNOPSIS +.Nm +.Ar string1 +.Ar string2 +.Ar command ... +. +.Sh DESCRIPTION +.Nm +runs the +.Ar command +in a new PTY, +remapping input characters in +.Ar string1 +to the corresponding characters in +.Ar string2 . +Type +.Ic ^S +to toggle remapping. +. +.Sh EXAMPLES +.Dl hnel '[]{}' '{}[]' vim +. +.Sh SEE ALSO +.Xr tr 1 +. +.Sh BUGS +Window size changes are not propagated +to the child PTY. diff --git a/bin/man1/modem.1 b/bin/man1/modem.1 new file mode 100644 index 00000000..a4bbc3f1 --- /dev/null +++ b/bin/man1/modem.1 @@ -0,0 +1,31 @@ +.Dd December 8, 2020 +.Dt MODEM 1 +.Os +. +.Sh NAME +.Nm modem +.Nd fixed baud rate wrapper +. +.Sh SYNOPSIS +.Nm +.Op Fl r Ar rate +.Ar command ... +. +.Sh DESCRIPTION +.Nm +runs the +.Ar command +in a new PTY +with a fixed baud rate. +. +.Pp +The arguments are as follows: +.Bl -tag -width Ds +.It Fl r Ar rate +Set the baud rate. +The default is 19200. +.El +. +.Sh BUGS +Window size changes are not propagated +to the child PTY. diff --git a/bin/man1/nudge.1 b/bin/man1/nudge.1 new file mode 100644 index 00000000..3ca4294a --- /dev/null +++ b/bin/man1/nudge.1 @@ -0,0 +1,44 @@ +.Dd September 4, 2020 +.Dt NUDGE 1 +.Os +. +.Sh NAME +.Nm nudge +.Nd terminal vibrator +. +.Sh SYNOPSIS +.Nm +.Op Fl f Ar file +.Op Fl n Ar count +.Op Fl s Ar shake +.Op Fl t Ar delay +. +.Sh DESCRIPTION +The +.Nm +utility +nudges the terminal. +An +.Xr xterm 1 +compatible terminal is required. +. +.Pp +The arguments are as follows: +.Bl -tag -width Ds +.It Fl f Ar file +Open the terminal named by +.Ar file . +The default is +.Pa /dev/tty . +.It Fl n Ar count +Set the number of times +to nudge the terminal. +The default is 2. +.It Fl s Ar shake +Set the shake intensity in pixels. +The default is 10. +.It Fl t Ar delay +Set the interval between shakes +in milliseconds. +The default is 20. +.El diff --git a/bin/man1/order.1 b/bin/man1/order.1 new file mode 100644 index 00000000..8e0f9661 --- /dev/null +++ b/bin/man1/order.1 @@ -0,0 +1,38 @@ +.Dd July 18, 2020 +.Dt ORDER 1 +.Os +. +.Sh NAME +.Nm order +.Nd operator precedence +. +.Sh SYNOPSIS +.Nm +.Op Ar expr ... +. +.Sh DESCRIPTION +.Nm +parses C expressions +and prints them with parentheses +according to the precedence rules in +.Xr operator 7 . +If no +.Ar expr +are given, +each line of standard input +is parsed and printed. +. +.Sh EXAMPLES +.Bd -literal +$ order 'a & b << 1' +(a & (b << 1)) +.Ed +. +.Sh SEE ALSO +.Xr operator 7 +. +.Sh CAVEATS +.Nm +does not support the +.Sy (type) +operator. diff --git a/bin/man1/pbd.1 b/bin/man1/pbd.1 new file mode 100644 index 00000000..bbc7b785 --- /dev/null +++ b/bin/man1/pbd.1 @@ -0,0 +1,59 @@ +.Dd September 7, 2018 +.Dt PBD 1 +.Os +. +.Sh NAME +.Nm pbd , +.Nm pbcopy , +.Nm pbpaste , +.Nm open +.Nd macOS pasteboard daemon +. +.Sh SYNOPSIS +.Nm +.Nm pbcopy +.Nm pbpaste +.Nm open +.Ar +. +.Sh DESCRIPTION +.Nm +is a daemon which pipes into macOS +.Xr pbcopy 1 , +from +.Xr pbpaste 1 +and invokes +.Xr open 1 +in response to messages +sent over TCP port 7062. +. +.Pp +The socket can be forwarded through +.Xr ssh 1 +and the stub implementations of +.Nm pbcopy , +.Nm pbpaste +and +.Nm open +can be used remotely +to access the local pasteboard +and open URLs. +. +.Pp +Forwarding can be configured with: +.Pp +.Dl RemoteForward 7062 127.0.0.1:7062 +. +.Sh EXAMPLES +.Bd -literal -offset indent +pbd & +ssh -R 7062:127.0.0.1:7062 tux.local +pbpaste +.Ed +. +.Sh SEE ALSO +.Xr open 1 , +.Xr pbcopy 1 , +.Xr pbpaste 1 , +.Xr ssh 1 , +.Xr ssh_config 5 diff --git a/bin/man1/pngo.1 b/bin/man1/pngo.1 new file mode 100644 index 00000000..cec13160 --- /dev/null +++ b/bin/man1/pngo.1 @@ -0,0 +1,56 @@ +.Dd September 17, 2018 +.Dt PNGO 1 +.Os +. +.Sh NAME +.Nm pngo +.Nd PNG optimizer +. +.Sh SYNOPSIS +.Nm +.Op Fl cv +.Op Fl o Ar file +.Op Ar +. +.Sh DESCRIPTION +.Nm +optimizes PNG files for size. +. +.Pp +The arguments are as follows: +.Bl -tag -width Ds +.It Fl c +Write to standard output. +.It Fl o Ar file +Write to +.Ar file . +.It Fl v +Output PNG header information. +.El +. +.Pp +.Nm +performs the following optimizations: +.Bl -bullet +.It +Discard ancillary chunks. +.It +Discard unnecessary alpha channel. +.It +Convert unnecessary truecolor to grayscale. +.It +Palletize color and alpha if possible. +.It +Reduce bit depth if possible. +.It +Apply a simple filter heuristic. +.It +Apply zlib's best compresion. +.El +. +.Sh SEE ALSO +.Xr glitch 1 +. +.Sh CAVEATS +.Nm +does not support interlaced PNGs. diff --git a/bin/man1/psf2png.1 b/bin/man1/psf2png.1 new file mode 100644 index 00000000..db74c6e2 --- /dev/null +++ b/bin/man1/psf2png.1 @@ -0,0 +1,53 @@ +.Dd September 28, 2018 +.Dt PSF2PNG 1 +.Os +. +.Sh NAME +.Nm psf2png +.Nd PSF2 to PNG renderer +. +.Sh SYNOPSIS +.Nm +.Op Fl b Ar bg +.Op Fl c Ar cols +.Op Fl f Ar fg +.Op Fl s Ar str +.Op Ar file +. +.Sh DESCRIPTION +.Nm +renders the PSF2 font +.Ar file +or standard input +to PNG +on standard output. +. +.Pp +The arguments are as follows: +.Bl -tag -width Ds +.It Fl b Ar bg +Use +.Ar bg +(hexadecimal RGB) +as background color. +The default background color is black. +.It Fl c Ar cols +Arrange glyphs in +.Ar cols +columns. +The default number of columns is 32. +.It Fl f Ar fg +Use +.Ar fg +(hexadecimal RGB) +as foreground color. +The default foreground color is white. +.It Fl s Ar str +Render glyphs for string +.Ar str +rather than all glyphs. +.El +. +.Sh SEE ALSO +.Xr pngo 1 , +.Xr psfed 1 diff --git a/bin/man1/psfed.1 b/bin/man1/psfed.1 new file mode 100644 index 00000000..3fbc4710 --- /dev/null +++ b/bin/man1/psfed.1 @@ -0,0 +1,166 @@ +.Dd January 14, 2019 +.Dt PSFED 1 +.Os +. +.Sh NAME +.Nm psfed +.Nd PSF2 font editor +. +.Sh SYNOPSIS +.Nm +.Op Fl H Ar height +.Op Fl g Ar glyphs +.Op Fl h Ar height +.Op Fl w Ar width +.Ar file +. +.Sh DESCRIPTION +.Nm +is a PSF2 font editor +for the Linux framebuffer. +. +.Pp +The arguments are as follows: +. +.Bl -tag -width Ds +.It Fl H Ar height +Modify the height of an existing font. +Only increasing the height is allowed. +. +.It Fl g Ar glyphs +Set the number of glyphs in a new font. +The default number of glyphs is 256. +. +.It Fl h Ar height +Set the height of a new font. +The default height is 16. +. +.It Fl w Ar width +Set the width of a new font. +The default width is 8. +.El +. +.Ss Normal Mode +In normal mode, +each glyph is displayed in a grid. +. +.Pp +.Bl -tag -width Ds -compact +.It Ic q +Quit. +.Nm +will ask for confirmation +if the font has been modified +since the last write. +. +.It Ic w +Write font to +.Ar file . +. +.It Ic - Ic + +Adjust display scale. +. +.It Ic h Ic l +Select previous/next glyph. +. +.It Ic k Ic j +Select glyph in previous/next row. +. +.It Ic f +Select glyph of next input character. +. +.It Ic ' +Return to previously selected glyph. +. +.It Ic y +Copy selected glyph. +. +.It Ic e +Edit selected glyph in +.Sx Edit Mode . +. +.It Ic i +Enter +.Sx Preview Mode . +.El +. +.Ss Edit Mode +In edit mode, +the selected glyph is displayed for editing +surrounded by a checked border. +The glyph is also displayed unscaled +in the bottom-right corner. +. +.Pp +.Bl -tag -width Ds -compact +.It Ic ESC +Return to +.Sx Normal Mode . +. +.It Ic - Ic + +Adjust display scale. +. +.It Ic g Ic G +Toggle guide on selected column/row. +. +.It Ic h Ic l +Select previous/next bit in row. +. +.It Ic k Ic j +Select previous/next bit in column. +. +.It Ic SPACE +Flip selected bit. +. +.It Ic r +Invert glyph. +. +.It Ic H Ic L +Move glyph left/right. +. +.It Ic K Ic J +Move glyph up/down. +. +.It Ic p +Paste the copied glyph. +. +.It Ic u +Revert glyph to initial state. +.El +. +.Ss Preview Mode +In preview mode, +arbitrary text may be entered +for preview. +Press +.Ic ESC +to return to +.Sx Normal Mode . +. +.Sh ENVIRONMENT +.Bl -tag -width FRAMEBUFFER +.It Ev FRAMEBUFFER +The framebuffer device path. +The default path is +.Pa /dev/fb0 . +.El +. +.Sh SEE ALSO +.Xr psfaddtable 1 , +.Xr psfgettable 1 , +.Xr psfstriptable 1 , +.Xr setfont 8 +. +.Sh CAVEATS +.Nm +does not support Unicode tables. +Use +.Xr psfaddtable 1 +to add Unicode tables +to fonts created by +.Nm . +. +.Sh BUGS +.Nm +makes no attempt to convert header fields +to and from little-endian format. diff --git a/bin/man1/ptee.1 b/bin/man1/ptee.1 new file mode 100644 index 00000000..04f9cdac --- /dev/null +++ b/bin/man1/ptee.1 @@ -0,0 +1,40 @@ +.Dd July 17, 2019 +.Dt PTEE 1 +.Os +. +.Sh NAME +.Nm ptee +.Nd tee for PTYs +. +.Sh SYNOPSIS +.Nm +.Ar command ... +.Cm > +.Ar file +. +.Sh DESCRIPTION +.Nm +runs +.Ar command +in a new PTY +which is mirrored to +the current PTY +and standard output. +Standard output must be redirected +to a file or pipe. +. +.Pp +Type +.Ic ^S +to toggle writing to standard output. +Type +.Ic ^Q +to write the media copy sequence for +.Xr shotty 1 . +. +.Sh SEE ALSO +.Xr tee 1 +. +.Sh BUGS +Window size changes are not propagated +to the child PTY. diff --git a/bin/man1/relay.1 b/bin/man1/relay.1 new file mode 100644 index 00000000..402c4726 --- /dev/null +++ b/bin/man1/relay.1 @@ -0,0 +1,48 @@ +.Dd April 28, 2019 +.Dt RELAY 1 +.Os +. +.Sh NAME +.Nm relay +.Nd IRC relay bot +. +.Sh SYNOPSIS +.Nm +.Ar host +.Ar port +.Ar nick +.Ar chan +. +.Sh DESCRIPTION +.Nm +is one half of an IRC relay pair. +It connects to +.Ar host Ns : Ns Ar port +over TLS +as +.Ar nick +and joins +.Ar chan . +. +.Pp +.Nm +outputs messages from +.Ar chan +to standard output +and sends messages to +.Ar chan +from standard input. +Two +.Nm +processes can be connected with +.Xr mkfifo 1 . +. +.Sh EXAMPLES +.Bd -literal -offset indent +mkfifo a b +relay a.example.com 6697 relay '#example' <>a >b +relay b.example.com 6697 relay '#example' <>b >a +.Ed +. +.Sh SEE ALSO +.Xr mkfifo 1 diff --git a/bin/man1/scheme.1 b/bin/man1/scheme.1 new file mode 100644 index 00000000..9aa4f054 --- /dev/null +++ b/bin/man1/scheme.1 @@ -0,0 +1,56 @@ +.Dd July 6, 2019 +.Dt SCHEME 1 +.Os +. +.Sh NAME +.Nm scheme +.Nd color scheme +. +.Sh SYNOPSIS +.Nm +.Op Fl acghilmstx +.Op Fl p Ar n +. +.Sh DESCRIPTION +.Nm +generates a color scheme +and outputs it in a number of formats. +. +.Pp +The arguments are as follows: +.Bl -tag -width Ds +.It Fl a +Generate the 16 ANSI colors. +This is the default. +.It Fl c +Output a C enum. +.It Fl g +Output a swatch PNG. +.It Fl h +Output floating point HSV. +.It Fl i +Swap black and white. +.It Fl l +Output Linux console OSC sequences. +.It Fl m +Output a +.Xr mintty 1 +theme. +Use with +.Fl t . +.It Fl p Ar n +Generate only the color +.Ar n . +.It Fl s +Output CSS +for classes named +.Sy fg Ns Ar n +and +.Sy bg Ns Ar n . +.It Fl t +Generate the 16 ANSI colors as well as +background, foreground, bold, selection and cursor colors. +.It Fl x +Output hexadecimal RGB. +This is the default. +.El diff --git a/bin/man1/shotty.1 b/bin/man1/shotty.1 new file mode 100644 index 00000000..d5eaa780 --- /dev/null +++ b/bin/man1/shotty.1 @@ -0,0 +1,92 @@ +.Dd November 25, 2019 +.Dt SHOTTY 1 +.Os +. +.Sh NAME +.Nm shotty +.Nd terminal capture +. +.Sh SYNOPSIS +.Nm +.Op Fl Bdns +.Op Fl b Ar bg +.Op Fl f Ar fg +.Op Fl h Ar rows +.Op Fl w Ar cols +.Op Ar file +. +.Sh DESCRIPTION +.Nm +interprets terminal output from +.Ar file +or standard input +and produces HTML +.Sy <pre> +on standard output. +. +.Pp +Terminal output +can be captured with +.Xr ptee 1 . +.Nm +targets compatibility with +.Ev TERM Ns = Ns Cm xterm +and +.Ev TERM Ns = Ns Cm xterm-256color +as used by +.Xr ncurses 3 . +A snapshot of the terminal +is output each time +a media copy sequence occurs, +or once at the end of the capture. +. +.Pp +HTML output uses the classes +.Sy bg Ns Va n +and +.Sy fg Ns Va n , +and inline styles for +bold, italic and underline. +CSS for colors can be generated with +.Xr scheme 1 . +. +.Pp +The arguments are as follows: +.Bl -tag -width "-w cols" +.It Fl B +Replace bold with bright colors. +. +.It Fl b Ar bg +Set the default background color. +The default value is 0 (black). +. +.It Fl d +Output the terminal state +following each control sequence. +. +.It Fl f Ar fg +Set the default foreground color. +The default value is 7 (white). +. +.It Fl h Ar rows +Set the terminal height. +The default value is 24. +. +.It Fl n +Do not show the cursor. +. +.It Fl s +Set the terminal size +from the current terminal size. +. +.It Fl w Ar cols +Set the terminal width. +The default value is 80. +.El +. +.Sh EXAMPLES +.Dl ptee htop | shotty -s > htop.html +. +.Sh SEE ALSO +.Xr ptee 1 , +.Xr scheme 1 diff --git a/bin/man1/title.1 b/bin/man1/title.1 new file mode 100644 index 00000000..43ecc5e2 --- /dev/null +++ b/bin/man1/title.1 @@ -0,0 +1,51 @@ +.Dd September 10, 2019 +.Dt TITLE 1 +.Os +. +.Sh NAME +.Nm title +.Nd page titles +. +.Sh SYNOPSIS +.Nm +.Op Fl v +.Op Fl x Ar pattern +.Op Ar url +. +.Sh DESCRIPTION +.Nm +fetches HTML page titles +over HTTP and HTTPS. +.Nm +scans standard input for URLs +and writes their titles to standard output. +If a +.Ar url +argument is given, +.Nm +exits after fetching its title. +. +.Pp +The arguments are as follows: +.Bl -tag -width Ds +.It Fl x Ar pattern +Exclude URLs matching +.Ar pattern , +which is a modern regular expression. +See +.Xr re_format 7 . +.It Fl v +Enable +.Xr libcurl 3 +verbose output. +.El +. +.Sh EXAMPLES +.Bd -literal -offset indent +mkfifo snarf titles +relay irc.example.org 6697 snarf '#example' <>titles >snarf +title <snarf >titles +.Ed +. +.Sh SEE ALSO +.Xr relay 1 diff --git a/bin/man1/ttpre.1 b/bin/man1/ttpre.1 new file mode 100644 index 00000000..bd178525 --- /dev/null +++ b/bin/man1/ttpre.1 @@ -0,0 +1,32 @@ +.Dd October 8, 2018 +.Dt TTPRE 1 +.Os +. +.Sh NAME +.Nm ttpre +.Nd man output to HTML +. +.Sh SYNOPSIS +.Nm +. +.Sh DESCRIPTION +.Nm +formats +.Xr man 1 +output on standard input +as an HTML +.Sy <pre> +fragment on standard output. +Ampersands and angle brackets +are replaced with corresponding HTML entities, +and backspace formatting sequences +are replaced with +.Sy <i> +and +.Sy <b> +tags. +. +.Sh SEE ALSO +.Xr less 1 , +.Xr man 1 , +.Xr mandoc 1 diff --git a/bin/man1/up.1 b/bin/man1/up.1 new file mode 100644 index 00000000..58b5359f --- /dev/null +++ b/bin/man1/up.1 @@ -0,0 +1,76 @@ +.Dd July 15, 2019 +.Dt UP 1 +.Os +. +.Sh NAME +.Nm up +.Nd upload file +. +.Sh SYNOPSIS +.Nm +.Op Fl h +.Op Ar file +. +.Nm +.Fl c | t +.Ar command ... +. +.Nm +.Fl s +. +.Sh DESCRIPTION +.Nm +uploads a file +to temp.causal.agency with +.Xr scp 1 . +If no +.Ar file +is provided, +standard input is read +and uploaded as text. +. +.Pp +The destination file name +is chosen using +.Xr date 1 +and +.Xr openssl 1 +.Cm rand . +The URL of the uploaded file is printed +and copied to the pasteboard with +.Xr pbcopy 1 +if available. +. +.Pp +The arguments are as follows: +.Bl -tag -width Ds +.It Fl c +Run a command +to produce a text file for upload. +.It Fl h +Use +.Xr hi 1 +to produce an HTML file for upload. +.It Fl s +Use +.Xr screencapture 1 +to produce a PNG file for upload. +The file is optimized by +.Xr pngo 1 +if available. +.It Fl t +Run a command with +.Xr ptee 1 +and +.Xr shotty 1 +to produce an HTML file for upload. +.El +. +.Pp +Any arguments after +.Ql \-\- +are passed to +.Xr hi 1 +and +.Xr screencapture 1 , +respectively. diff --git a/bin/man1/when.1 b/bin/man1/when.1 new file mode 100644 index 00000000..0b473573 --- /dev/null +++ b/bin/man1/when.1 @@ -0,0 +1,76 @@ +.Dd July 24, 2019 +.Dt WHEN 1 +.Os +. +.Sh NAME +.Nm when +.Nd date calculator +. +.Sh SYNOPSIS +.Nm +.Op Ar expr +. +.Sh DESCRIPTION +.Nm +is a date calculator. +If no +.Ar expr +is given, +expressions are read +from standard input. +. +.Pp +The grammar is as follows: +.Bl -tag -width Ds +.It Sy \&. +Today's date. +. +.It Ar month Ar date Op Ar year +A full date, +or a date in the current year. +.Ar month +must be at least three letters. +. +.It Ar day +A day of the week +in the current week. +.Ar day +must be at least three letters. +. +.It Sy < Ar date +The date one week before. +. +.It Sy > Ar date +The date one week after. +. +.It Ar date Sy + Ar interval +The date after some interval. +. +.It Ar date Sy - Ar interval +The date before some interval. +. +.It Ar date Sy - Ar date +The interval between two dates. +. +.It Ar num Sy d +A number of days. +. +.It Ar num Sy w +A number of weeks. +. +.It Ar num Sy m +A number of months. +. +.It Ar num Sy y +A number of years. +.El +. +.Sh EXAMPLES +.Bl -tag -width "Dec 25 - ." +.It Ic Dec 25 - \&. +How long until Christmas. +.It Ic >Fri +The date next Friday. +.It Ic \&. + 2w +Your last day at work. +.El diff --git a/bin/man1/xx.1 b/bin/man1/xx.1 new file mode 100644 index 00000000..d38789a7 --- /dev/null +++ b/bin/man1/xx.1 @@ -0,0 +1,68 @@ +.Dd September 7, 2018 +.Dt XX 1 +.Os +. +.Sh NAME +.Nm xx +.Nd hexdump +. +.Sh SYNOPSIS +.Nm +.Op Fl arsz +.Op Fl c Ar cols +.Op Fl g Ar group +.Op Fl p Ar count +.Op Ar file +. +.Sh DESCRIPTION +.Nm +dumps the contents of a +.Ar file +or standard input +in hexadecimal format. +. +.Pp +The arguments are as follows: +.Bl -tag -width Ds +.It Fl a +Toggle ASCII output. +. +.It Fl c Ar cols +Output +.Ar cols +bytes per line. +The default +.Ar cols +is 16. +. +.It Fl g Ar group +Output extra space after every +.Ar group +bytes. +The default +.Ar group +is 8. +. +.It Fl p Ar count +Output a blank line after every +.Ar count +bytes. +.Ar count +must be a multiple of +.Ar cols . +. +.It Fl r +Reverse hexdump. +Read hexadecimal input +and write byte output. +. +.It Fl s +Toggle offset output. +. +.It Fl z +Skip output of lines containing only zeros. +.El +. +.Sh SEE ALSO +.Xr hexdump 1 , +.Xr xxd 1 diff --git a/bin/man3/png.3 b/bin/man3/png.3 new file mode 100644 index 00000000..accffbd7 --- /dev/null +++ b/bin/man3/png.3 @@ -0,0 +1,90 @@ +.Dd July 25, 2019 +.Dt PNG 3 +.Os +. +.Sh NAME +.Nm png +.Nd basic PNG output +. +.Sh SYNOPSIS +.In png.h +. +.Ft void +.Fo pngHead +.Fa "FILE *file" +.Fa "uint32_t width" +.Fa "uint32_t height" +.Fa "uint8_t depth" +.Fa "uint8_t color" +.Fc +. +.Ft void +.Fn pngPalette "FILE *file" "const uint8_t *pal" "uint32_t len" +. +.Ft void +.Fn pngData "FILE *file" "const uint8_t *data" "uint32_t len" +. +.Ft void +.Fn pngTail "FILE *file" +. +.Sh DESCRIPTION +The +.Fn pngHead +function +writes the +.Sy IHDR +chunk to +.Fa file . +The +.Fa color +parameter can be one of +.Dv PNGGrayscale , +.Dv PNGTruecolor +optionally +.Em or Ns 'ed +with +.Dv PNGAlpha , +or +.Dv PNGIndexed . +. +.Pp +The +.Fn pngPalette +function +writes the +.Sy PLTE +chunk to +.Fa file . +. +.Pp +The +.Fn pngData +function +writes the +.Sy IDAT +chunk to +.Fa file +without compression. +The constants +.Dv PNGNone , +.Dv PNGSub , +.Dv PNGUp , +.Dv PNGAverage , +.Dv PNGPaeth +are defined +for use in PNG data. +. +.Pp +The +.Fn pngTail +function +writes the +.Sy IEND +chunk to +.Fa file . +. +.Sh ERRORS +Any errors from writing to +.Fa file +are handled by calling +.Xr err 3 . diff --git a/bin/modem.c b/bin/modem.c new file mode 100644 index 00000000..2133ae08 --- /dev/null +++ b/bin/modem.c @@ -0,0 +1,102 @@ +/* Copyright (C) 2018 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <poll.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <sysexits.h> +#include <termios.h> +#include <unistd.h> + +#if defined __FreeBSD__ +#include <libutil.h> +#elif defined __linux__ +#include <pty.h> +#else +#include <util.h> +#endif + +typedef unsigned uint; +typedef unsigned char byte; + +static struct termios saveTerm; +static void restoreTerm(void) { + tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm); +} + +int main(int argc, char *argv[]) { + int error; + + uint baudRate = 19200; + for (int opt; 0 < (opt = getopt(argc, argv, "r:"));) { + switch (opt) { + break; case 'r': baudRate = strtoul(optarg, NULL, 10); + break; default: return EX_USAGE; + } + } + if (argc - optind < 1) return EX_USAGE; + + error = tcgetattr(STDIN_FILENO, &saveTerm); + if (error) err(EX_IOERR, "tcgetattr"); + atexit(restoreTerm); + + struct termios raw = saveTerm; + cfmakeraw(&raw); + error = tcsetattr(STDIN_FILENO, TCSADRAIN, &raw); + if (error) err(EX_IOERR, "tcsetattr"); + + struct winsize window; + error = ioctl(STDIN_FILENO, TIOCGWINSZ, &window); + if (error) err(EX_IOERR, "TIOCGWINSZ"); + + int pty; + pid_t pid = forkpty(&pty, NULL, NULL, &window); + if (pid < 0) err(EX_OSERR, "forkpty"); + + if (!pid) { + execvp(argv[optind], &argv[optind]); + err(EX_NOINPUT, "%s", argv[optind]); + } + + byte c; + struct pollfd fds[2] = { + { .events = POLLIN, .fd = STDIN_FILENO }, + { .events = POLLIN, .fd = pty }, + }; + while (usleep(8 * 1000000 / baudRate), 0 < poll(fds, 2, -1)) { + if (fds[0].revents) { + ssize_t size = read(STDIN_FILENO, &c, 1); + if (size < 0) err(EX_IOERR, "read(%d)", STDIN_FILENO); + size = write(pty, &c, 1); + if (size < 0) err(EX_IOERR, "write(%d)", pty); + } + + if (fds[1].revents) { + ssize_t size = read(pty, &c, 1); + if (size < 0) err(EX_IOERR, "read(%d)", pty); + if (!size) break; + size = write(STDOUT_FILENO, &c, 1); + if (size < 0) err(EX_IOERR, "write(%d)", STDOUT_FILENO); + } + } + + int status; + pid_t dead = waitpid(pid, &status, 0); + if (dead < 0) err(EX_OSERR, "waitpid"); + return WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE; +} diff --git a/bin/nudge.c b/bin/nudge.c new file mode 100644 index 00000000..108d2459 --- /dev/null +++ b/bin/nudge.c @@ -0,0 +1,78 @@ +/* Copyright (C) 2020 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <sysexits.h> +#include <termios.h> +#include <unistd.h> + +static int shake = 10; +static int delay = 20000; +static int count = 2; + +static void move(int tty, int x, int y) { + dprintf(tty, "\33[3;%d;%dt", x, y); + usleep(delay); +} + +int main(int argc, char *argv[]) { + const char *path = "/dev/tty"; + for (int opt; 0 < (opt = getopt(argc, argv, "f:n:s:t:"));) { + switch (opt) { + break; case 'f': path = optarg; + break; case 'n': count = atoi(optarg); + break; case 's': shake = atoi(optarg); + break; case 't': delay = atoi(optarg) * 1000; + break; default: return EX_USAGE; + } + } + + int tty = open(path, O_RDWR); + if (tty < 0) err(EX_OSFILE, "%s", path); + + struct termios save; + int error = tcgetattr(tty, &save); + if (error) err(EX_IOERR, "tcgetattr"); + + struct termios raw = save; + cfmakeraw(&raw); + error = tcsetattr(tty, TCSAFLUSH, &raw); + if (error) err(EX_IOERR, "tcsetattr"); + + char buf[256]; + dprintf(tty, "\33[13t"); + ssize_t len = read(tty, buf, sizeof(buf) - 1); + buf[(len < 0 ? 0 : len)] = '\0'; + + int x, y; + int n = sscanf(buf, "\33[3;%d;%dt", &x, &y); + + error = tcsetattr(tty, TCSANOW, &save); + if (error) err(EX_IOERR, "tcsetattr"); + if (n < 2) return EX_CONFIG; + + dprintf(tty, "\33[5t"); + for (int i = 0; i < count; ++i) { + move(tty, x - shake, y); + move(tty, x, y + shake); + move(tty, x + shake, y); + move(tty, x, y - shake); + move(tty, x, y); + } +} diff --git a/bin/order.y b/bin/order.y new file mode 100644 index 00000000..b798e634 --- /dev/null +++ b/bin/order.y @@ -0,0 +1,193 @@ +/* Copyright (C) 2019 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +%{ + +#include <ctype.h> +#include <err.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> + +int vasprintf(char **ret, const char *format, va_list ap); + +static void yyerror(const char *str) { + errx(EX_DATAERR, "%s", str); +} + +#define YYSTYPE char * + +static char *fmt(const char *format, ...) { + char *str = NULL; + va_list ap; + va_start(ap, format); + vasprintf(&str, format, ap); + va_end(ap); + if (!str) err(EX_OSERR, "vasprintf"); + return str; +} + +static int yylex(void); + +%} + +%left ',' +%right '=' MulAss DivAss ModAss AddAss SubAss ShlAss ShrAss AndAss XorAss OrAss +%right '?' ':' +%left Or +%left And +%left '|' +%left '^' +%left '&' +%left Eq Ne +%left '<' Le '>' Ge +%left Shl Shr +%left '+' '-' +%left '*' '/' '%' +%right '!' '~' Inc Dec Sizeof +%left '(' ')' '[' ']' Arr '.' + +%token Var + +%% + +start: + expr { printf("%s\n", $1); } + ; + +expr: + Var + | '(' expr ')' { $$ = $2; } + | expr '[' expr ']' { $$ = fmt("(%s[%s])", $1, $3); } + | expr Arr Var { $$ = fmt("(%s->%s)", $1, $3); } + | expr '.' Var { $$ = fmt("(%s.%s)", $1, $3); } + | '!' expr { $$ = fmt("(!%s)", $2); } + | '~' expr { $$ = fmt("(~%s)", $2); } + | Inc expr { $$ = fmt("(++%s)", $2); } + | Dec expr { $$ = fmt("(--%s)", $2); } + | expr Inc { $$ = fmt("(%s++)", $1); } + | expr Dec { $$ = fmt("(%s--)", $1); } + | '-' expr %prec '!' { $$ = fmt("(-%s)", $2); } + | '*' expr %prec '!' { $$ = fmt("(*%s)", $2); } + | '&' expr %prec '!' { $$ = fmt("(&%s)", $2); } + | Sizeof expr { $$ = fmt("(sizeof %s)", $2); } + | expr '*' expr { $$ = fmt("(%s * %s)", $1, $3); } + | expr '/' expr { $$ = fmt("(%s / %s)", $1, $3); } + | expr '%' expr { $$ = fmt("(%s %% %s)", $1, $3); } + | expr '+' expr { $$ = fmt("(%s + %s)", $1, $3); } + | expr '-' expr { $$ = fmt("(%s - %s)", $1, $3); } + | expr Shl expr { $$ = fmt("(%s << %s)", $1, $3); } + | expr Shr expr { $$ = fmt("(%s >> %s)", $1, $3); } + | expr '<' expr { $$ = fmt("(%s < %s)", $1, $3); } + | expr Le expr { $$ = fmt("(%s <= %s)", $1, $3); } + | expr '>' expr { $$ = fmt("(%s > %s)", $1, $3); } + | expr Ge expr { $$ = fmt("(%s >= %s)", $1, $3); } + | expr Eq expr { $$ = fmt("(%s == %s)", $1, $3); } + | expr Ne expr { $$ = fmt("(%s != %s)", $1, $3); } + | expr '&' expr { $$ = fmt("(%s & %s)", $1, $3); } + | expr '^' expr { $$ = fmt("(%s ^ %s)", $1, $3); } + | expr '|' expr { $$ = fmt("(%s | %s)", $1, $3); } + | expr And expr { $$ = fmt("(%s && %s)", $1, $3); } + | expr Or expr { $$ = fmt("(%s || %s)", $1, $3); } + | expr '?' expr ':' expr { $$ = fmt("(%s ? %s : %s)", $1, $3, $5); } + | expr ass expr %prec '=' { $$ = fmt("(%s %s %s)", $1, $2, $3); } + | expr ',' expr { $$ = fmt("(%s, %s)", $1, $3); } + ; + +ass: + '=' { $$ = "="; } + | MulAss { $$ = "*="; } + | DivAss { $$ = "/="; } + | ModAss { $$ = "%="; } + | AddAss { $$ = "+="; } + | SubAss { $$ = "-="; } + | ShlAss { $$ = "<<="; } + | ShrAss { $$ = ">>="; } + | AndAss { $$ = "&="; } + | XorAss { $$ = "^="; } + | OrAss { $$ = "|="; } + ; + +%% + +#define T(a, b) ((int)(a) << 8 | (int)(b)) + +static const char *input; + +static int yylex(void) { + while (isspace(input[0])) input++; + if (!input[0]) return EOF; + + int len; + for (len = 0; isalnum(input[len]) || input[len] == '_'; ++len); + if (len) { + if (!strncmp(input, "sizeof", len)) { + input += len; + return Sizeof; + } + yylval = fmt("%.*s", len, input); + input += len; + return Var; + } + + int tok; + switch (T(input[0], input[1])) { + break; case T('-', '>'): tok = Arr; + break; case T('+', '+'): tok = Inc; + break; case T('-', '-'): tok = Dec; + break; case T('<', '<'): tok = Shl; + break; case T('>', '>'): tok = Shr; + break; case T('<', '='): tok = Le; + break; case T('>', '='): tok = Ge; + break; case T('=', '='): tok = Eq; + break; case T('!', '='): tok = Ne; + break; case T('&', '&'): tok = And; + break; case T('|', '|'): tok = Or; + break; case T('*', '='): tok = MulAss; + break; case T('/', '='): tok = DivAss; + break; case T('%', '='): tok = ModAss; + break; case T('+', '='): tok = AddAss; + break; case T('-', '='): tok = SubAss; + break; case T('&', '='): tok = AndAss; + break; case T('^', '='): tok = XorAss; + break; case T('|', '='): tok = OrAss; + break; default: return *input++; + } + input += 2; + + switch (T(tok, input[0])) { + case T(Shl, '='): input++; return ShlAss; + case T(Shr, '='): input++; return ShrAss; + } + + return tok; +} + +int main(int argc, char *argv[]) { + for (int i = 1; i < argc; ++i) { + input = argv[i]; + yyparse(); + } + if (argc > 1) return EX_OK; + size_t cap = 0; + char *buf = NULL; + while (0 < getline(&buf, &cap, stdin)) { + input = buf; + yyparse(); + } +} diff --git a/bin/pbd.c b/bin/pbd.c new file mode 100644 index 00000000..2ba333fd --- /dev/null +++ b/bin/pbd.c @@ -0,0 +1,152 @@ +/* Copyright (C) 2017 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <arpa/inet.h> +#include <err.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sysexits.h> +#include <unistd.h> + +typedef unsigned char byte; + +static void spawn(const char *cmd, const char *arg, int dest, int src) { + pid_t pid = fork(); + if (pid < 0) err(EX_OSERR, "fork"); + + if (pid) { + int status; + pid_t dead = waitpid(pid, &status, 0); + if (dead < 0) err(EX_OSERR, "waitpid(%d)", pid); + if (status) warnx("%s: status %d", cmd, status); + + } else { + int fd = dup2(src, dest); + if (fd < 0) err(EX_OSERR, "dup2"); + + execlp(cmd, cmd, arg, NULL); + err(EX_UNAVAILABLE, "%s", cmd); + } +} + +static int pbd(void) { + int error; + + int server = socket(PF_INET, SOCK_STREAM, 0); + if (server < 0) err(EX_OSERR, "socket"); + + error = fcntl(server, F_SETFD, FD_CLOEXEC); + if (error) err(EX_IOERR, "fcntl"); + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(7062), + .sin_addr = { .s_addr = htonl(0x7F000001) }, + }; + error = bind(server, (struct sockaddr *)&addr, sizeof(addr)); + if (error) err(EX_UNAVAILABLE, "bind"); + + error = listen(server, 0); + if (error) err(EX_UNAVAILABLE, "listen"); + + for (;;) { + int client = accept(server, NULL, NULL); + if (client < 0) err(EX_IOERR, "accept"); + + error = fcntl(client, F_SETFD, FD_CLOEXEC); + if (error) err(EX_IOERR, "fcntl"); + + char c = 0; + ssize_t size = read(client, &c, 1); + if (size < 0) warn("read"); + + switch (c) { + break; case 'p': spawn("pbpaste", NULL, STDOUT_FILENO, client); + break; case 'c': spawn("pbcopy", NULL, STDIN_FILENO, client); + break; case 'o': spawn("xargs", "open", STDIN_FILENO, client); + } + + close(client); + } +} + +static int pbdClient(char c) { + int client = socket(PF_INET, SOCK_STREAM, 0); + if (client < 0) err(EX_OSERR, "socket"); + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(7062), + .sin_addr = { .s_addr = htonl(0x7F000001) }, + }; + int error = connect(client, (struct sockaddr *)&addr, sizeof(addr)); + if (error) err(EX_UNAVAILABLE, "connect"); + + ssize_t size = write(client, &c, 1); + if (size < 0) err(EX_IOERR, "write"); + + return client; +} + +static void copy(int out, int in) { + byte buf[4096]; + ssize_t readSize; + while (0 < (readSize = read(in, buf, sizeof(buf)))) { + ssize_t writeSize = write(out, buf, readSize); + if (writeSize < 0) err(EX_IOERR, "write(%d)", out); + } + if (readSize < 0) err(EX_IOERR, "read(%d)", in); +} + +static int pbcopy(void) { + int client = pbdClient('c'); + copy(client, STDIN_FILENO); + return EX_OK; +} + +static int pbpaste(void) { + int client = pbdClient('p'); + copy(STDOUT_FILENO, client); + return EX_OK; +} + +static int open1(const char *url) { + if (!url) return EX_USAGE; + int client = pbdClient('o'); + ssize_t size = write(client, url, strlen(url)); + if (size < 0) err(EX_IOERR, "write"); + return EX_OK; +} + +int main(int argc, char *argv[]) { + (void)argc; + if (strchr(argv[0], '/')) { + argv[0] = strrchr(argv[0], '/') + 1; + } + switch (argv[0][0] && argv[0][1] ? argv[0][2] : 0) { + case 'd': return pbd(); + case 'c': return pbcopy(); + case 'p': return pbpaste(); + case 'e': return open1(argv[1]); + default: return EX_USAGE; + } +} diff --git a/bin/png.h b/bin/png.h new file mode 100644 index 00000000..15b6d13d --- /dev/null +++ b/bin/png.h @@ -0,0 +1,108 @@ +/* Copyright (C) 2018 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <sysexits.h> + +static inline uint32_t pngCRCTable(uint8_t n) { + static uint32_t table[256]; + if (table[1]) return table[n]; + for (int i = 0; i < 256; ++i) { + table[i] = i; + for (int j = 0; j < 8; ++j) { + table[i] = (table[i] >> 1) ^ (table[i] & 1 ? 0xEDB88320 : 0); + } + } + return table[n]; +} + +static uint32_t pngCRC; + +static inline void pngWrite(FILE *file, const uint8_t *ptr, uint32_t len) { + if (!fwrite(ptr, len, 1, file)) err(EX_IOERR, "pngWrite"); + for (uint32_t i = 0; i < len; ++i) { + pngCRC = pngCRCTable(pngCRC ^ ptr[i]) ^ (pngCRC >> 8); + } +} +static inline void pngInt32(FILE *file, uint32_t n) { + pngWrite(file, (uint8_t []) { n >> 24, n >> 16, n >> 8, n }, 4); +} +static inline void pngChunk(FILE *file, char type[static 4], uint32_t len) { + pngInt32(file, len); + pngCRC = ~0; + pngWrite(file, (uint8_t *)type, 4); +} + +enum { + PNGGrayscale, + PNGTruecolor = 2, + PNGIndexed, + PNGAlpha, +}; + +static inline void pngHead( + FILE *file, uint32_t width, uint32_t height, uint8_t depth, uint8_t color +) { + pngWrite(file, (uint8_t *)"\x89PNG\r\n\x1A\n", 8); + pngChunk(file, "IHDR", 13); + pngInt32(file, width); + pngInt32(file, height); + pngWrite(file, &depth, 1); + pngWrite(file, &color, 1); + pngWrite(file, (uint8_t []) { 0, 0, 0 }, 3); + pngInt32(file, ~pngCRC); +} + +static inline void pngPalette(FILE *file, const uint8_t *pal, uint32_t len) { + pngChunk(file, "PLTE", len); + pngWrite(file, pal, len); + pngInt32(file, ~pngCRC); +} + +enum { + PNGNone, + PNGSub, + PNGUp, + PNGAverage, + PNGPaeth, +}; + +static inline void pngData(FILE *file, const uint8_t *data, uint32_t len) { + uint32_t adler1 = 1, adler2 = 0; + for (uint32_t i = 0; i < len; ++i) { + adler1 = (adler1 + data[i]) % 65521; + adler2 = (adler1 + adler2) % 65521; + } + uint32_t zlen = 2 + 5 * ((len + 0xFFFE) / 0xFFFF) + len + 4; + pngChunk(file, "IDAT", zlen); + pngWrite(file, (uint8_t []) { 0x08, 0x1D }, 2); + for (; len > 0xFFFF; data += 0xFFFF, len -= 0xFFFF) { + pngWrite(file, (uint8_t []) { 0x00, 0xFF, 0xFF, 0x00, 0x00 }, 5); + pngWrite(file, data, 0xFFFF); + } + pngWrite(file, (uint8_t []) { 0x01, len, len >> 8, ~len, ~len >> 8 }, 5); + pngWrite(file, data, len); + pngInt32(file, adler2 << 16 | adler1); + pngInt32(file, ~pngCRC); +} + +static inline void pngTail(FILE *file) { + pngChunk(file, "IEND", 0); + pngInt32(file, ~pngCRC); +} diff --git a/bin/pngo.c b/bin/pngo.c new file mode 100644 index 00000000..322cb1ba --- /dev/null +++ b/bin/pngo.c @@ -0,0 +1,812 @@ +/* Copyright (C) 2018 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <arpa/inet.h> +#include <err.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> +#include <zlib.h> + +#define PACKED __attribute__((packed)) +#define PAIR(a, b) ((uint16_t)(a) << 8 | (uint16_t)(b)) + +#define CRC_INIT (crc32(0, Z_NULL, 0)) + +static bool verbose; +static const char *path; +static FILE *file; +static uint32_t crc; + +static void readExpect(void *ptr, size_t size, const char *expect) { + fread(ptr, size, 1, file); + if (ferror(file)) err(EX_IOERR, "%s", path); + if (feof(file)) errx(EX_DATAERR, "%s: missing %s", path, expect); + crc = crc32(crc, ptr, size); +} + +static void writeExpect(const void *ptr, size_t size) { + fwrite(ptr, size, 1, file); + if (ferror(file)) err(EX_IOERR, "%s", path); + crc = crc32(crc, ptr, size); +} + +static const uint8_t Signature[8] = "\x89PNG\r\n\x1A\n"; + +static void readSignature(void) { + uint8_t signature[8]; + readExpect(signature, 8, "signature"); + if (0 != memcmp(signature, Signature, 8)) { + errx(EX_DATAERR, "%s: invalid signature", path); + } +} + +static void writeSignature(void) { + writeExpect(Signature, sizeof(Signature)); +} + +struct PACKED Chunk { + uint32_t size; + char type[4]; +}; + +static const char *typeStr(struct Chunk chunk) { + static char buf[5]; + memcpy(buf, chunk.type, 4); + return buf; +} + +static struct Chunk readChunk(void) { + struct Chunk chunk; + readExpect(&chunk, sizeof(chunk), "chunk"); + chunk.size = ntohl(chunk.size); + crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type)); + return chunk; +} + +static void writeChunk(struct Chunk chunk) { + chunk.size = htonl(chunk.size); + writeExpect(&chunk, sizeof(chunk)); + crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type)); +} + +static void readCrc(void) { + uint32_t expected = crc; + uint32_t found; + readExpect(&found, sizeof(found), "CRC32"); + found = ntohl(found); + if (found != expected) { + errx( + EX_DATAERR, "%s: expected CRC32 %08X, found %08X", + path, expected, found + ); + } +} + +static void writeCrc(void) { + uint32_t net = htonl(crc); + writeExpect(&net, sizeof(net)); +} + +static void skipChunk(struct Chunk chunk) { + if (!(chunk.type[0] & 0x20)) { + errx(EX_CONFIG, "%s: unsupported critical chunk %s", path, typeStr(chunk)); + } + uint8_t discard[4096]; + while (chunk.size > sizeof(discard)) { + readExpect(discard, sizeof(discard), "chunk data"); + chunk.size -= sizeof(discard); + } + if (chunk.size) readExpect(discard, chunk.size, "chunk data"); + readCrc(); +} + +static struct PACKED { + uint32_t width; + uint32_t height; + uint8_t depth; + enum PACKED { + Grayscale = 0, + Truecolor = 2, + Indexed = 3, + GrayscaleAlpha = 4, + TruecolorAlpha = 6, + } color; + enum PACKED { Deflate } compression; + enum PACKED { Adaptive } filter; + enum PACKED { Progressive, Adam7 } interlace; +} header; +_Static_assert(13 == sizeof(header), "header size"); + +static size_t pixelBits(void) { + switch (header.color) { + case Grayscale: return 1 * header.depth; + case Truecolor: return 3 * header.depth; + case Indexed: return 1 * header.depth; + case GrayscaleAlpha: return 2 * header.depth; + case TruecolorAlpha: return 4 * header.depth; + default: abort(); + } +} + +static size_t pixelSize(void) { + return (pixelBits() + 7) / 8; +} + +static size_t lineSize(void) { + return (header.width * pixelBits() + 7) / 8; +} + +static size_t dataSize(void) { + return (1 + lineSize()) * header.height; +} + +static const char *ColorStr[] = { + [Grayscale] = "grayscale", + [Truecolor] = "truecolor", + [Indexed] = "indexed", + [GrayscaleAlpha] = "grayscale alpha", + [TruecolorAlpha] = "truecolor alpha", +}; +static void printHeader(void) { + fprintf( + stderr, + "%s: %ux%u %hhu-bit %s\n", + path, + header.width, header.height, + header.depth, ColorStr[header.color] + ); +} + +static void readHeader(struct Chunk chunk) { + if (chunk.size != sizeof(header)) { + errx( + EX_DATAERR, "%s: expected IHDR size %zu, found %u", + path, sizeof(header), chunk.size + ); + } + readExpect(&header, sizeof(header), "header"); + readCrc(); + + header.width = ntohl(header.width); + header.height = ntohl(header.height); + + if (!header.width) errx(EX_DATAERR, "%s: invalid width 0", path); + if (!header.height) errx(EX_DATAERR, "%s: invalid height 0", path); + switch (PAIR(header.color, header.depth)) { + case PAIR(Grayscale, 1): + case PAIR(Grayscale, 2): + case PAIR(Grayscale, 4): + case PAIR(Grayscale, 8): + case PAIR(Grayscale, 16): + case PAIR(Truecolor, 8): + case PAIR(Truecolor, 16): + case PAIR(Indexed, 1): + case PAIR(Indexed, 2): + case PAIR(Indexed, 4): + case PAIR(Indexed, 8): + case PAIR(GrayscaleAlpha, 8): + case PAIR(GrayscaleAlpha, 16): + case PAIR(TruecolorAlpha, 8): + case PAIR(TruecolorAlpha, 16): + break; + default: + errx( + EX_DATAERR, "%s: invalid color type %hhu and bit depth %hhu", + path, header.color, header.depth + ); + } + if (header.compression != Deflate) { + errx( + EX_DATAERR, "%s: invalid compression method %hhu", + path, header.compression + ); + } + if (header.filter != Adaptive) { + errx(EX_DATAERR, "%s: invalid filter method %hhu", path, header.filter); + } + if (header.interlace > Adam7) { + errx(EX_DATAERR, "%s: invalid interlace method %hhu", path, header.interlace); + } + + if (verbose) printHeader(); +} + +static void writeHeader(void) { + if (verbose) printHeader(); + + struct Chunk ihdr = { .size = sizeof(header), .type = "IHDR" }; + writeChunk(ihdr); + header.width = htonl(header.width); + header.height = htonl(header.height); + writeExpect(&header, sizeof(header)); + writeCrc(); + + header.width = ntohl(header.width); + header.height = ntohl(header.height); +} + +static struct { + uint32_t len; + uint8_t entries[256][3]; +} palette; + +static struct { + uint32_t len; + uint8_t alpha[256]; +} trans; + +static void paletteClear(void) { + palette.len = 0; + trans.len = 0; +} + +static uint32_t paletteIndex(bool alpha, const uint8_t *rgba) { + uint32_t i; + for (i = 0; i < palette.len; ++i) { + if (alpha && i < trans.len && trans.alpha[i] != rgba[3]) continue; + if (0 == memcmp(palette.entries[i], rgba, 3)) break; + } + return i; +} + +static bool paletteAdd(bool alpha, const uint8_t *rgba) { + uint32_t i = paletteIndex(alpha, rgba); + if (i < palette.len) return true; + if (i == 256) return false; + memcpy(palette.entries[i], rgba, 3); + palette.len++; + if (alpha) { + trans.alpha[i] = rgba[3]; + trans.len++; + } + return true; +} + +static void transCompact(void) { + uint32_t i; + for (i = 0; i < trans.len; ++i) { + if (trans.alpha[i] == 0xFF) break; + } + if (i == trans.len) return; + + for (uint32_t j = i + 1; j < trans.len; ++j) { + if (trans.alpha[j] == 0xFF) continue; + + uint8_t alpha = trans.alpha[i]; + trans.alpha[i] = trans.alpha[j]; + trans.alpha[j] = alpha; + + uint8_t rgb[3]; + memcpy(rgb, palette.entries[i], 3); + memcpy(palette.entries[i], palette.entries[j], 3); + memcpy(palette.entries[j], rgb, 3); + + i++; + } + trans.len = i; +} + +static void readPalette(struct Chunk chunk) { + if (chunk.size % 3) { + errx(EX_DATAERR, "%s: PLTE size %u not divisible by 3", path, chunk.size); + } + + palette.len = chunk.size / 3; + if (palette.len > 256) { + errx(EX_DATAERR, "%s: PLTE length %u > 256", path, palette.len); + } + + readExpect(palette.entries, chunk.size, "palette data"); + readCrc(); + + if (verbose) fprintf(stderr, "%s: palette length %u\n", path, palette.len); +} + +static void writePalette(void) { + if (verbose) fprintf(stderr, "%s: palette length %u\n", path, palette.len); + struct Chunk plte = { .size = 3 * palette.len, .type = "PLTE" }; + writeChunk(plte); + writeExpect(palette.entries, plte.size); + writeCrc(); +} + +static void readTrans(struct Chunk chunk) { + trans.len = chunk.size; + if (trans.len > 256) { + errx(EX_DATAERR, "%s: tRNS length %u > 256", path, trans.len); + } + readExpect(trans.alpha, chunk.size, "transparency alpha"); + readCrc(); + if (verbose) fprintf(stderr, "%s: transparency length %u\n", path, trans.len); +} + +static void writeTrans(void) { + if (verbose) fprintf(stderr, "%s: transparency length %u\n", path, trans.len); + struct Chunk trns = { .size = trans.len, .type = "tRNS" }; + writeChunk(trns); + writeExpect(trans.alpha, trns.size); + writeCrc(); +} + +static uint8_t *data; + +static void allocData(void) { + data = malloc(dataSize()); + if (!data) err(EX_OSERR, "malloc(%zu)", dataSize()); +} + +static void readData(struct Chunk chunk) { + if (verbose) fprintf(stderr, "%s: data size %zu\n", path, dataSize()); + + struct z_stream_s stream = { .next_out = data, .avail_out = dataSize() }; + int error = inflateInit(&stream); + if (error != Z_OK) errx(EX_SOFTWARE, "%s: inflateInit: %s", path, stream.msg); + + for (;;) { + if (0 != memcmp(chunk.type, "IDAT", 4)) { + errx(EX_DATAERR, "%s: missing IDAT chunk", path); + } + + uint8_t *idat = malloc(chunk.size); + if (!idat) err(EX_OSERR, "malloc"); + + readExpect(idat, chunk.size, "image data"); + readCrc(); + + stream.next_in = idat; + stream.avail_in = chunk.size; + int error = inflate(&stream, Z_SYNC_FLUSH); + free(idat); + + if (error == Z_STREAM_END) break; + if (error != Z_OK) { + errx(EX_DATAERR, "%s: inflate: %s", path, stream.msg); + } + + chunk = readChunk(); + } + + inflateEnd(&stream); + if ((size_t)stream.total_out != dataSize()) { + errx( + EX_DATAERR, "%s: expected data size %zu, found %zu", + path, dataSize(), (size_t)stream.total_out + ); + } + + if (verbose) { + fprintf( + stderr, "%s: deflate size %zu\n", path, (size_t)stream.total_in + ); + } +} + +static void writeData(void) { + if (verbose) fprintf(stderr, "%s: data size %zu\n", path, dataSize()); + + uLong size = compressBound(dataSize()); + uint8_t *deflate = malloc(size); + if (!deflate) err(EX_OSERR, "malloc"); + + int error = compress2(deflate, &size, data, dataSize(), Z_BEST_COMPRESSION); + if (error != Z_OK) errx(EX_SOFTWARE, "%s: compress2: %d", path, error); + + struct Chunk idat = { .size = size, .type = "IDAT" }; + writeChunk(idat); + writeExpect(deflate, size); + writeCrc(); + + free(deflate); + + if (verbose) fprintf(stderr, "%s: deflate size %lu\n", path, size); +} + +static void writeEnd(void) { + struct Chunk iend = { .size = 0, .type = "IEND" }; + writeChunk(iend); + writeCrc(); +} + +enum PACKED Filter { + None, + Sub, + Up, + Average, + Paeth, + FilterCount, +}; + +struct Bytes { + uint8_t x; + uint8_t a; + uint8_t b; + uint8_t c; +}; + +static uint8_t paethPredictor(struct Bytes f) { + int32_t p = (int32_t)f.a + (int32_t)f.b - (int32_t)f.c; + int32_t pa = abs(p - (int32_t)f.a); + int32_t pb = abs(p - (int32_t)f.b); + int32_t pc = abs(p - (int32_t)f.c); + if (pa <= pb && pa <= pc) return f.a; + if (pb <= pc) return f.b; + return f.c; +} + +static uint8_t recon(enum Filter type, struct Bytes f) { + switch (type) { + case None: return f.x; + case Sub: return f.x + f.a; + case Up: return f.x + f.b; + case Average: return f.x + ((uint32_t)f.a + (uint32_t)f.b) / 2; + case Paeth: return f.x + paethPredictor(f); + default: abort(); + } +} + +static uint8_t filt(enum Filter type, struct Bytes f) { + switch (type) { + case None: return f.x; + case Sub: return f.x - f.a; + case Up: return f.x - f.b; + case Average: return f.x - ((uint32_t)f.a + (uint32_t)f.b) / 2; + case Paeth: return f.x - paethPredictor(f); + default: abort(); + } +} + +static struct Line { + enum Filter type; + uint8_t data[]; +} **lines; + +static void allocLines(void) { + lines = calloc(header.height, sizeof(*lines)); + if (!lines) err(EX_OSERR, "calloc(%u, %zu)", header.height, sizeof(*lines)); +} + +static void scanlines(void) { + size_t stride = 1 + lineSize(); + for (uint32_t y = 0; y < header.height; ++y) { + lines[y] = (struct Line *)&data[y * stride]; + if (lines[y]->type >= FilterCount) { + errx(EX_DATAERR, "%s: invalid filter type %hhu", path, lines[y]->type); + } + } +} + +static struct Bytes origBytes(uint32_t y, size_t i) { + bool a = (i >= pixelSize()), b = (y > 0), c = (a && b); + return (struct Bytes) { + .x = lines[y]->data[i], + .a = a ? lines[y]->data[i - pixelSize()] : 0, + .b = b ? lines[y - 1]->data[i] : 0, + .c = c ? lines[y - 1]->data[i - pixelSize()] : 0, + }; +} + +static void reconData(void) { + for (uint32_t y = 0; y < header.height; ++y) { + for (size_t i = 0; i < lineSize(); ++i) { + lines[y]->data[i] = + recon(lines[y]->type, origBytes(y, i)); + } + lines[y]->type = None; + } +} + +static void filterData(void) { + if (header.color == Indexed || header.depth < 8) return; + for (uint32_t y = header.height - 1; y < header.height; --y) { + uint8_t filter[FilterCount][lineSize()]; + uint32_t heuristic[FilterCount] = {0}; + enum Filter minType = None; + for (enum Filter type = None; type < FilterCount; ++type) { + for (size_t i = 0; i < lineSize(); ++i) { + filter[type][i] = filt(type, origBytes(y, i)); + heuristic[type] += abs((int8_t)filter[type][i]); + } + if (heuristic[type] < heuristic[minType]) minType = type; + } + lines[y]->type = minType; + memcpy(lines[y]->data, filter[minType], lineSize()); + } +} + +static void discardAlpha(void) { + if (header.color != GrayscaleAlpha && header.color != TruecolorAlpha) return; + size_t sampleSize = header.depth / 8; + size_t colorSize = pixelSize() - sampleSize; + for (uint32_t y = 0; y < header.height; ++y) { + for (uint32_t x = 0; x < header.width; ++x) { + for (size_t i = 0; i < sampleSize; ++i) { + if (lines[y]->data[x * pixelSize() + colorSize + i] != 0xFF) return; + } + } + } + + uint8_t *ptr = data; + for (uint32_t y = 0; y < header.height; ++y) { + *ptr++ = lines[y]->type; + for (uint32_t x = 0; x < header.width; ++x) { + memmove(ptr, &lines[y]->data[x * pixelSize()], colorSize); + ptr += colorSize; + } + } + header.color = (header.color == GrayscaleAlpha) ? Grayscale : Truecolor; + scanlines(); +} + +static void discardColor(void) { + if (header.color != Truecolor && header.color != TruecolorAlpha) return; + size_t sampleSize = header.depth / 8; + for (uint32_t y = 0; y < header.height; ++y) { + for (uint32_t x = 0; x < header.width; ++x) { + uint8_t *r = &lines[y]->data[x * pixelSize()]; + uint8_t *g = r + sampleSize; + uint8_t *b = g + sampleSize; + if (0 != memcmp(r, g, sampleSize)) return; + if (0 != memcmp(g, b, sampleSize)) return; + } + } + + uint8_t *ptr = data; + for (uint32_t y = 0; y < header.height; ++y) { + *ptr++ = lines[y]->type; + for (uint32_t x = 0; x < header.width; ++x) { + uint8_t *pixel = &lines[y]->data[x * pixelSize()]; + memmove(ptr, pixel, sampleSize); + ptr += sampleSize; + if (header.color == TruecolorAlpha) { + memmove(ptr, pixel + 3 * sampleSize, sampleSize); + ptr += sampleSize; + } + } + } + header.color = (header.color == Truecolor) ? Grayscale : GrayscaleAlpha; + scanlines(); +} + +static void indexColor(void) { + if (header.color != Truecolor && header.color != TruecolorAlpha) return; + if (header.depth != 8) return; + bool alpha = (header.color == TruecolorAlpha); + for (uint32_t y = 0; y < header.height; ++y) { + for (uint32_t x = 0; x < header.width; ++x) { + if (!paletteAdd(alpha, &lines[y]->data[x * pixelSize()])) return; + } + } + transCompact(); + + uint8_t *ptr = data; + for (uint32_t y = 0; y < header.height; ++y) { + *ptr++ = lines[y]->type; + for (uint32_t x = 0; x < header.width; ++x) { + *ptr++ = paletteIndex(alpha, &lines[y]->data[x * pixelSize()]); + } + } + header.color = Indexed; + scanlines(); +} + +static void reduceDepth8(void) { + if (header.color != Grayscale && header.color != Indexed) return; + if (header.depth != 8) return; + if (header.color == Grayscale) { + for (uint32_t y = 0; y < header.height; ++y) { + for (size_t i = 0; i < lineSize(); ++i) { + uint8_t a = lines[y]->data[i]; + if ((a >> 4) != (a & 0x0F)) return; + } + } + } else if (palette.len > 16) { + return; + } + + uint8_t *ptr = data; + for (uint32_t y = 0; y < header.height; ++y) { + *ptr++ = lines[y]->type; + for (size_t i = 0; i < lineSize(); i += 2) { + uint8_t iByte = lines[y]->data[i]; + uint8_t jByte = (i + 1 < lineSize()) ? lines[y]->data[i + 1] : 0; + uint8_t a = iByte & 0x0F; + uint8_t b = jByte & 0x0F; + *ptr++ = a << 4 | b; + } + } + header.depth = 4; + scanlines(); +} + +static void reduceDepth4(void) { + if (header.depth != 4) return; + if (header.color == Grayscale) { + for (uint32_t y = 0; y < header.height; ++y) { + for (size_t i = 0; i < lineSize(); ++i) { + uint8_t a = lines[y]->data[i] >> 4; + uint8_t b = lines[y]->data[i] & 0x0F; + if ((a >> 2) != (a & 0x03)) return; + if ((b >> 2) != (b & 0x03)) return; + } + } + } else if (palette.len > 4) { + return; + } + + uint8_t *ptr = data; + for (uint32_t y = 0; y < header.height; ++y) { + *ptr++ = lines[y]->type; + for (size_t i = 0; i < lineSize(); i += 2) { + uint8_t iByte = lines[y]->data[i]; + uint8_t jByte = (i + 1 < lineSize()) ? lines[y]->data[i + 1] : 0; + uint8_t a = iByte >> 4 & 0x03, b = iByte & 0x03; + uint8_t c = jByte >> 4 & 0x03, d = jByte & 0x03; + *ptr++ = a << 6 | b << 4 | c << 2 | d; + } + } + header.depth = 2; + scanlines(); +} + +static void reduceDepth2(void) { + if (header.depth != 2) return; + if (header.color == Grayscale) { + for (uint32_t y = 0; y < header.height; ++y) { + for (size_t i = 0; i < lineSize(); ++i) { + uint8_t a = lines[y]->data[i] >> 6; + uint8_t b = lines[y]->data[i] >> 4 & 0x03; + uint8_t c = lines[y]->data[i] >> 2 & 0x03; + uint8_t d = lines[y]->data[i] & 0x03; + if ((a >> 1) != (a & 0x01)) return; + if ((b >> 1) != (b & 0x01)) return; + if ((c >> 1) != (c & 0x01)) return; + if ((d >> 1) != (d & 0x01)) return; + } + } + } else if (palette.len > 2) { + return; + } + + uint8_t *ptr = data; + for (uint32_t y = 0; y < header.height; ++y) { + *ptr++ = lines[y]->type; + for (size_t i = 0; i < lineSize(); i += 2) { + uint8_t iByte = lines[y]->data[i]; + uint8_t jByte = (i + 1 < lineSize()) ? lines[y]->data[i + 1] : 0; + uint8_t a = iByte >> 6 & 0x01, b = iByte >> 4 & 0x01; + uint8_t c = iByte >> 2 & 0x01, d = iByte & 0x01; + uint8_t e = jByte >> 6 & 0x01, f = jByte >> 4 & 0x01; + uint8_t g = jByte >> 2 & 0x01, h = jByte & 0x01; + *ptr++ = a << 7 | b << 6 | c << 5 | d << 4 | e << 3 | f << 2 | g << 1 | h; + } + } + header.depth = 1; + scanlines(); +} + +static void reduceDepth(void) { + reduceDepth8(); + reduceDepth4(); + reduceDepth2(); +} + +static void optimize(const char *inPath, const char *outPath) { + if (inPath) { + path = inPath; + file = fopen(path, "r"); + if (!file) err(EX_NOINPUT, "%s", path); + } else { + path = "(stdin)"; + file = stdin; + } + + readSignature(); + struct Chunk ihdr = readChunk(); + if (0 != memcmp(ihdr.type, "IHDR", 4)) { + errx(EX_DATAERR, "%s: expected IHDR, found %s", path, typeStr(ihdr)); + } + readHeader(ihdr); + if (header.interlace != Progressive) { + errx( + EX_CONFIG, "%s: unsupported interlace method %hhu", + path, header.interlace + ); + } + + paletteClear(); + allocData(); + for (;;) { + struct Chunk chunk = readChunk(); + if (0 == memcmp(chunk.type, "PLTE", 4)) { + readPalette(chunk); + } else if (0 == memcmp(chunk.type, "tRNS", 4)) { + readTrans(chunk); + } else if (0 == memcmp(chunk.type, "IDAT", 4)) { + readData(chunk); + } else if (0 != memcmp(chunk.type, "IEND", 4)) { + skipChunk(chunk); + } else { + break; + } + } + + fclose(file); + + allocLines(); + scanlines(); + reconData(); + + discardAlpha(); + discardColor(); + indexColor(); + reduceDepth(); + filterData(); + free(lines); + + if (outPath) { + path = outPath; + file = fopen(path, "w"); + if (!file) err(EX_CANTCREAT, "%s", path); + } else { + path = "(stdout)"; + file = stdout; + } + + writeSignature(); + writeHeader(); + if (header.color == Indexed) { + writePalette(); + if (trans.len) writeTrans(); + } + writeData(); + writeEnd(); + free(data); + + int error = fclose(file); + if (error) err(EX_IOERR, "%s", path); +} + +int main(int argc, char *argv[]) { + bool stdio = false; + char *output = NULL; + + int opt; + while (0 < (opt = getopt(argc, argv, "co:v"))) { + switch (opt) { + break; case 'c': stdio = true; + break; case 'o': output = optarg; + break; case 'v': verbose = true; + break; default: return EX_USAGE; + } + } + + if (argc - optind == 1 && (output || stdio)) { + optimize(argv[optind], output); + } else if (optind < argc) { + for (int i = optind; i < argc; ++i) { + optimize(argv[i], argv[i]); + } + } else { + optimize(NULL, output); + } + + return EX_OK; +} diff --git a/bin/psf2png.c b/bin/psf2png.c new file mode 100644 index 00000000..1aaa8635 --- /dev/null +++ b/bin/psf2png.c @@ -0,0 +1,107 @@ +/* Copyright (C) 2018 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +#include "png.h" + +int main(int argc, char *argv[]) { + uint32_t cols = 32; + const char *str = NULL; + uint32_t fg = 0xFFFFFF; + uint32_t bg = 0x000000; + + int opt; + while (0 < (opt = getopt(argc, argv, "b:c:f:s:"))) { + switch (opt) { + break; case 'b': bg = strtoul(optarg, NULL, 16); + break; case 'c': cols = strtoul(optarg, NULL, 0); + break; case 'f': fg = strtoul(optarg, NULL, 16); + break; case 's': str = optarg; + break; default: return EX_USAGE; + } + } + if (!cols && str) cols = strlen(str); + if (!cols) return EX_USAGE; + + const char *path = NULL; + if (optind < argc) path = argv[optind]; + + FILE *file = path ? fopen(path, "r") : stdin; + if (!file) err(EX_NOINPUT, "%s", path); + if (!path) path = "(stdin)"; + + struct { + uint32_t magic; + uint32_t version; + uint32_t size; + uint32_t flags; + struct { + uint32_t len; + uint32_t size; + uint32_t height; + uint32_t width; + } glyph; + } header; + size_t len = fread(&header, sizeof(header), 1, file); + if (ferror(file)) err(EX_IOERR, "%s", path); + if (len < 1) errx(EX_DATAERR, "%s: truncated header", path); + + uint32_t widthBytes = (header.glyph.width + 7) / 8; + uint8_t glyphs[header.glyph.len][header.glyph.height][widthBytes]; + len = fread(glyphs, header.glyph.size, header.glyph.len, file); + if (ferror(file)) err(EX_IOERR, "%s", path); + if (len < header.glyph.len) { + errx(EX_DATAERR, "%s: truncated glyphs", path); + } + fclose(file); + + uint32_t count = (str ? strlen(str) : header.glyph.len); + uint32_t width = header.glyph.width * cols; + uint32_t rows = (count + cols - 1) / cols; + uint32_t height = header.glyph.height * rows; + + pngHead(stdout, width, height, 8, PNGIndexed); + uint8_t pal[] = { + bg >> 16, bg >> 8, bg, + fg >> 16, fg >> 8, fg, + }; + pngPalette(stdout, pal, sizeof(pal)); + + uint8_t data[height][1 + width]; + memset(data, PNGNone, sizeof(data)); + + for (uint32_t i = 0; i < count; ++i) { + uint32_t row = header.glyph.height * (i / cols); + uint32_t col = 1 + header.glyph.width * (i % cols); + uint32_t g = (str ? str[i] : i); + for (uint32_t y = 0; y < header.glyph.height; ++y) { + for (uint32_t x = 0; x < header.glyph.width; ++x) { + uint8_t bit = glyphs[g][y][x / 8] >> (7 - x % 8) & 1; + data[row + y][col + x] = bit; + } + } + } + + pngData(stdout, (uint8_t *)data, sizeof(data)); + pngTail(stdout); +} diff --git a/bin/psfed.c b/bin/psfed.c new file mode 100644 index 00000000..4f46b500 --- /dev/null +++ b/bin/psfed.c @@ -0,0 +1,577 @@ +/* Copyright (C) 2018 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/fb.h> +#include <locale.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sysexits.h> +#include <termios.h> +#include <unistd.h> +#include <wchar.h> + +static const wchar_t CP437[256] = + L"\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼" + L"►◄↕‼¶§▬↨↑↓→←∟↔▲▼" + L" !\"#$%&'()*+,-./" + L"0123456789:;<=>?" + L"@ABCDEFGHIJKLMNO" + L"PQRSTUVWXYZ[\\]^_" + L"`abcdefghijklmno" + L"pqrstuvwxyz{|}~⌂" + L"ÇüéâäàåçêëèïîìÄÅ" + L"ÉæÆôöòûùÿÖÜ¢£¥₧ƒ" + L"áíóúñѪº¿⌐¬½¼¡«»" + L"░▒▓│┤╡╢╖╕╣║╗╝╜╛┐" + L"└┴┬├─┼╞╟╚╔╩╦╠═╬╧" + L"╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀" + L"αßΓπΣσµτΦΘΩδ∞φε∩" + L"≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\0"; + +static struct { + uint32_t width; + uint32_t height; + uint32_t *buffer; + uint32_t background; +} frame; + +static void frameClear(void) { + for (uint32_t i = 0; i < frame.width * frame.height; ++i) { + frame.buffer[i] = frame.background; + } +} + +static void frameOpen(void) { + const char *dev = getenv("FRAMEBUFFER"); + if (!dev) dev = "/dev/fb0"; + + int fd = open(dev, O_RDWR); + if (fd < 0) err(EX_OSFILE, "%s", dev); + + struct fb_var_screeninfo info; + int error = ioctl(fd, FBIOGET_VSCREENINFO, &info); + if (error) err(EX_IOERR, "%s", dev); + + frame.width = info.xres; + frame.height = 3 * info.yres / 4; + frame.buffer = mmap( + NULL, sizeof(*frame.buffer) * frame.width * frame.height, + PROT_READ | PROT_WRITE, MAP_SHARED, + fd, 0 + ); + if (frame.buffer == MAP_FAILED) err(EX_IOERR, "%s", dev); + close(fd); + + frame.background = frame.buffer[0]; + atexit(frameClear); +} + +static const uint32_t Magic = 0x864AB572; +static const uint32_t Version = 0; +static const uint32_t FlagUnicode = 1 << 0; +static uint32_t bytes(uint32_t bits) { + return (bits + 7) / 8; +} + +static char *path; +static struct { + uint32_t magic; + uint32_t version; + uint32_t size; + uint32_t flags; + struct { + uint32_t len; + uint32_t size; + uint32_t height; + uint32_t width; + } glyph; +} header; +static uint8_t *glyphs; + +static void fileRead(uint32_t newLen, uint32_t newWidth, uint32_t newHeight) { + FILE *file = fopen(path, "r"); + if (file) { + size_t len = fread(&header, sizeof(header), 1, file); + if (ferror(file)) err(EX_IOERR, "%s", path); + if (len < 1) errx(EX_DATAERR, "%s: truncated header", path); + + } else { + if (errno != ENOENT) err(EX_NOINPUT, "%s", path); + header.magic = Magic; + header.version = Version; + header.size = sizeof(header); + header.flags = 0; + header.glyph.len = newLen; + header.glyph.size = bytes(newWidth) * newHeight; + header.glyph.height = newHeight; + header.glyph.width = newWidth; + } + + if (header.magic != Magic) { + errx(EX_DATAERR, "%s: invalid magic %08X", path, header.magic); + } + if (header.version != Version) { + errx(EX_DATAERR, "%s: unsupported version %u", path, header.version); + } + if (header.flags & FlagUnicode) { + errx(EX_DATAERR, "%s: unsupported unicode table", path); + } + if (header.flags) { + errx(EX_DATAERR, "%s: unsupported flags %08X", path, header.flags); + } + + if (file && header.size > sizeof(header)) { + int error = fseek(file, header.size, SEEK_SET); + if (error) err(EX_IOERR, "%s", path); + + warnx("%s: truncating long header", path); + header.size = sizeof(header); + } + + glyphs = calloc(header.glyph.len, header.glyph.size); + if (!glyphs) err(EX_OSERR, "calloc"); + + if (file) { + size_t len = fread(glyphs, header.glyph.size, header.glyph.len, file); + if (ferror(file)) err(EX_IOERR, "%s", path); + if (len < header.glyph.len) { + errx(EX_DATAERR, "%s: truncated glyphs", path); + } + fclose(file); + } +} + +static void fileWrite(void) { + FILE *file = fopen(path, "w"); + if (!file) err(EX_CANTCREAT, "%s", path); + + fwrite(&header, sizeof(header), 1, file); + if (ferror(file)) err(EX_IOERR, "%s", path); + + fwrite(glyphs, header.glyph.size, header.glyph.len, file); + if (ferror(file)) err(EX_IOERR, "%s", path); + + int error = fclose(file); + if (error) err(EX_IOERR, "%s", path); +} + +static uint8_t *glyph(uint32_t index) { + return &glyphs[header.glyph.size * index]; +} +static uint8_t *bitByte(uint32_t index, uint32_t x, uint32_t y) { + return &glyph(index)[bytes(header.glyph.width) * y + x / 8]; +} +static uint8_t bitGet(uint32_t index, uint32_t x, uint32_t y) { + return *bitByte(index, x, y) >> (7 - x % 8) & 1; +} +static void bitFlip(uint32_t index, uint32_t x, uint32_t y) { + *bitByte(index, x, y) ^= 1 << (7 - x % 8); +} +static void bitSet(uint32_t index, uint32_t x, uint32_t y, uint8_t bit) { + *bitByte(index, x, y) &= ~(1 << (7 - x % 8)); + *bitByte(index, x, y) |= bit << (7 - x % 8); +} + +static void drawGlyph( + uint32_t destX, uint32_t destY, uint32_t scale, uint32_t index, + uint32_t selectX, uint32_t selectY, uint32_t guideX, uint32_t guideY +) { + destX <<= scale; + destY <<= scale; + + for (uint32_t y = 0; y < (header.glyph.height << scale); ++y) { + if (destY + y >= frame.height) break; + for (uint32_t x = 0; x < (header.glyph.width << scale); ++x) { + if (destX + x >= frame.width) break; + + uint32_t glyphX = x >> scale; + uint32_t glyphY = y >> scale; + uint32_t fill = -bitGet(index, glyphX, glyphY); + if (selectX & 1 << glyphX && selectY & 1 << glyphY) fill ^= 0x77; + if (guideX & 1 << glyphX || guideY & 1 << glyphY) fill ^= 0x3300; + + frame.buffer[frame.width * (destY + y) + destX + x] = fill; + } + } +} + +static void drawBorder(uint32_t destX, uint32_t destY, uint32_t scale) { + destX <<= scale; + destY <<= scale; + + for (uint32_t y = 0; y < destY; ++y) { + if (y >= frame.height) break; + uint32_t fill = -(y >> scale & 1) ^ 0x555555; + for (uint32_t x = 0; x < (uint32_t)(1 << scale); ++x) { + if (destX + x >= frame.width) break; + frame.buffer[frame.width * y + destX + x] = fill; + } + } + + for (uint32_t x = 0; x < destX; ++x) { + if (x >= frame.width) break; + uint32_t fill = -(x >> scale & 1) ^ 0x555555; + for (uint32_t y = 0; y < (uint32_t)(1 << scale); ++y) { + if (destY + y >= frame.height) break; + frame.buffer[frame.width * (destY + y) + x] = fill; + } + } +} + +enum { LF = '\n', Esc = '\33', Del = '\177' }; + +static enum { + Normal, + Edit, + Preview, + Discard, +} mode; + +static struct { + uint32_t scale; + uint32_t index; + bool modified; + bool to; + uint32_t from; +} normal; + +static struct { + uint32_t scale; + uint32_t index; + uint32_t x; + uint32_t y; + uint32_t guideX; + uint32_t guideY; + uint8_t *undo; + uint8_t *copy; +} edit = { + .scale = 4, +}; + +static const uint32_t NormalCols = 32; +static void drawNormal(void) { + for (uint32_t i = 0; i < header.glyph.len; ++i) { + drawGlyph( + header.glyph.width * (i % NormalCols), + header.glyph.height * (i / NormalCols), + normal.scale, i, + -(i == normal.index), -(i == normal.index), 0, 0 + ); + } +} + +static void normalDec(uint32_t n) { + if (normal.index >= n) normal.index -= n; +} +static void normalInc(uint32_t n) { + if (normal.index + n < header.glyph.len) normal.index += n; +} +static void normalPrint(const char *prefix) { + if (normal.index <= 256) { + printf( + "%s: %02X '%lc'\n", + prefix, normal.index, (wint_t)CP437[normal.index] + ); + } else { + printf("%s: %02X\n", prefix, normal.index); + } +} + +static void inputNormal(char ch) { + if (normal.to) { + if (ch < header.glyph.len) normal.index = ch; + normalPrint("index"); + normal.to = false; + return; + } + + switch (ch) { + break; case 'q': { + if (!normal.modified) exit(EX_OK); + mode = Discard; + } + break; case 'w': { + fileWrite(); + printf("write: %s\n", path); + normal.modified = false; + } + break; case '-': if (normal.scale) normal.scale--; frameClear(); + break; case '+': normal.scale++; + break; case 'h': normalDec(1); normalPrint("index"); + break; case 'l': normalInc(1); normalPrint("index"); + break; case 'k': normalDec(NormalCols); normalPrint("index"); + break; case 'j': normalInc(NormalCols); normalPrint("index"); + break; case 'f': normal.from = normal.index; normal.to = true; + break; case 047: normal.index = normal.from; normalPrint("index"); + break; case 'y': { + if (!edit.copy) edit.copy = malloc(header.glyph.size); + if (!edit.copy) err(EX_OSERR, "malloc"); + memcpy(edit.copy, glyph(normal.index), header.glyph.size); + normalPrint("copy"); + } + break; case 'e': { + normal.modified = true; + edit.index = normal.index; + if (!edit.undo) edit.undo = malloc(header.glyph.size); + if (!edit.undo) err(EX_OSERR, "malloc"); + memcpy(edit.undo, glyph(edit.index), header.glyph.size); + mode = Edit; + frameClear(); + } + break; case 'i': mode = Preview; frameClear(); + } +} + +static void drawEdit(void) { + drawGlyph( + 0, 0, edit.scale, edit.index, + 1 << edit.x, 1 << edit.y, edit.guideX, edit.guideY + ); + drawBorder(header.glyph.width, header.glyph.height, edit.scale); + drawGlyph( + header.glyph.width << edit.scale, + header.glyph.height << edit.scale, + 0, edit.index, + 0, 0, 0, 0 + ); +} + +static void inputEdit(char ch) { + switch (ch) { + break; case Esc: mode = Normal; frameClear(); + + break; case '-': if (edit.scale) edit.scale--; frameClear(); + break; case '+': edit.scale++; + break; case 'g': edit.guideY ^= 1 << edit.y; + break; case 'G': edit.guideX ^= 1 << edit.x; + + break; case 'h': if (edit.x) edit.x--; + break; case 'l': if (edit.x + 1 < header.glyph.width) edit.x++; + break; case 'k': if (edit.y) edit.y--; + break; case 'j': if (edit.y + 1 < header.glyph.height) edit.y++; + break; case ' ': bitFlip(edit.index, edit.x, edit.y); + + break; case 'r': { + for (uint32_t y = 0; y < header.glyph.height; ++y) { + for (uint32_t x = 0; x < header.glyph.width; ++x) { + bitFlip(edit.index, x, y); + } + } + } + + break; case 'H': { + for (uint32_t x = 0; x < header.glyph.width; ++x) { + for (uint32_t y = 0; y < header.glyph.height; ++y) { + if (x + 1 < header.glyph.width) { + bitSet(edit.index, x, y, bitGet(edit.index, x + 1, y)); + } else { + bitSet(edit.index, x, y, 0); + } + } + } + } + + break; case 'L': { + uint32_t width = header.glyph.width; + for (uint32_t x = width - 1; x < width; --x) { + for (uint32_t y = 0; y < header.glyph.height; ++y) { + if (x - 1 < width) { + bitSet(edit.index, x, y, bitGet(edit.index, x - 1, y)); + } else { + bitSet(edit.index, x, y, 0); + } + } + } + } + + break; case 'K': { + for (uint32_t y = 0; y < header.glyph.height; ++y) { + for (uint32_t x = 0; x < header.glyph.width; ++x) { + if (y + 1 < header.glyph.height) { + bitSet(edit.index, x, y, bitGet(edit.index, x, y + 1)); + } else { + bitSet(edit.index, x, y, 0); + } + } + } + } + + break; case 'J': { + uint32_t height = header.glyph.height; + for (uint32_t y = height - 1; y < height; --y) { + for (uint32_t x = 0; x < header.glyph.width; ++x) { + if (y - 1 < height) { + bitSet(edit.index, x, y, bitGet(edit.index, x, y - 1)); + } else { + bitSet(edit.index, x, y, 0); + } + } + } + } + + break; case 'p': { + if (!edit.copy) break; + memcpy(glyph(edit.index), edit.copy, header.glyph.size); + } + break; case 'u': { + if (!edit.undo) break; + memcpy(glyph(edit.index), edit.undo, header.glyph.size); + } + } +} + +enum { PreviewRows = 8, PreviewCols = 64 }; +static struct { + uint32_t glyphs[PreviewRows * PreviewCols]; + uint32_t index; +} preview; + +static void drawPreview(void) { + for (uint32_t i = 0; i < PreviewRows * PreviewCols; ++i) { + drawGlyph( + header.glyph.width * (i % PreviewCols), + header.glyph.height * (i / PreviewCols), + 0, preview.glyphs[i], + -(i == preview.index), -(i == preview.index), 0, 0 + ); + } +} + +static void inputPreview(char ch) { + switch (ch) { + break; case Esc: mode = Normal; frameClear(); + break; case Del: { + if (preview.index) preview.index--; + preview.glyphs[preview.index] = 0; + } + break; case LF: { + uint32_t tail = PreviewCols - (preview.index % PreviewCols); + memset( + &preview.glyphs[preview.index], + 0, sizeof(preview.glyphs[0]) * tail + ); + preview.index += tail; + } + break; default: preview.glyphs[preview.index++] = ch; + } + preview.index %= PreviewRows * PreviewCols; +} + +static void drawDiscard(void) { + printf("discard modifications? "); + fflush(stdout); +} + +static void inputDiscard(char ch) { + printf("%c\n", ch); + if (ch == 'Y' || ch == 'y') exit(EX_OK); + mode = Normal; +} + +static void draw(void) { + switch (mode) { + break; case Normal: drawNormal(); + break; case Edit: drawEdit(); + break; case Preview: drawPreview(); + break; case Discard: drawDiscard(); + } +} + +static void input(char ch) { + switch (mode) { + break; case Normal: inputNormal(ch); + break; case Edit: inputEdit(ch); + break; case Preview: inputPreview(ch); + break; case Discard: inputDiscard(ch); + } +} + +static struct termios saveTerm; +static void restoreTerm(void) { + tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm); +} + +int main(int argc, char *argv[]) { + setlocale(LC_CTYPE, ""); + + uint32_t newLen = 256; + uint32_t newWidth = 8; + uint32_t newHeight = 16; + uint32_t setHeight = 0; + + int opt; + while (0 < (opt = getopt(argc, argv, "H:g:h:w:"))) { + switch (opt) { + break; case 'H': setHeight = strtoul(optarg, NULL, 0); + break; case 'g': newLen = strtoul(optarg, NULL, 0); + break; case 'h': newHeight = strtoul(optarg, NULL, 0); + break; case 'w': newWidth = strtoul(optarg, NULL, 0); + break; default: return EX_USAGE; + } + } + if (!newLen || !newWidth || !newHeight) return EX_USAGE; + if (optind == argc) return EX_USAGE; + + path = strdup(argv[optind]); + fileRead(newLen, newWidth, newHeight); + + if (setHeight) { + if (setHeight < header.glyph.height) { + errx(EX_CONFIG, "cannot decrease height"); + } + + uint32_t setSize = bytes(header.glyph.width) * setHeight; + uint8_t *setGlyphs = calloc(header.glyph.len, setSize); + for (uint32_t i = 0; i < header.glyph.len; ++i) { + memcpy(&setGlyphs[setSize * i], glyph(i), header.glyph.size); + } + free(glyphs); + glyphs = setGlyphs; + + header.glyph.height = setHeight; + header.glyph.size = setSize; + normal.modified = true; + } + + frameOpen(); + frameClear(); + + int error = tcgetattr(STDIN_FILENO, &saveTerm); + if (error) err(EX_IOERR, "tcgetattr"); + atexit(restoreTerm); + + struct termios term = saveTerm; + term.c_lflag &= ~(ICANON | ECHO); + error = tcsetattr(STDIN_FILENO, TCSADRAIN, &term); + if (error) err(EX_IOERR, "tcsetattr"); + + for (;;) { + draw(); + char ch; + ssize_t size = read(STDIN_FILENO, &ch, 1); + if (size < 0) err(EX_IOERR, "read"); + if (!size) return EX_SOFTWARE; + input(ch); + } +} diff --git a/bin/ptee.c b/bin/ptee.c new file mode 100644 index 00000000..6a9a16b4 --- /dev/null +++ b/bin/ptee.c @@ -0,0 +1,116 @@ +/* Copyright (C) 2019 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <sysexits.h> +#include <termios.h> +#include <unistd.h> + +#if defined __FreeBSD__ +#include <libutil.h> +#elif defined __linux__ +#include <pty.h> +#else +#include <util.h> +#endif + +typedef unsigned char byte; + +static struct termios saveTerm; +static void restoreTerm(void) { + tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm); +} + +int main(int argc, char *argv[]) { + if (argc < 2) return EX_USAGE; + if (isatty(STDOUT_FILENO)) errx(EX_USAGE, "stdout is not redirected"); + + int error = tcgetattr(STDIN_FILENO, &saveTerm); + if (error) err(EX_IOERR, "tcgetattr"); + atexit(restoreTerm); + + struct termios raw = saveTerm; + cfmakeraw(&raw); + error = tcsetattr(STDIN_FILENO, TCSADRAIN, &raw); + if (error) err(EX_IOERR, "tcsetattr"); + + struct winsize window; + error = ioctl(STDIN_FILENO, TIOCGWINSZ, &window); + if (error) err(EX_IOERR, "ioctl"); + + int pty; + pid_t pid = forkpty(&pty, NULL, NULL, &window); + if (pid < 0) err(EX_OSERR, "forkpty"); + + if (!pid) { + execvp(argv[1], &argv[1]); + err(EX_NOINPUT, "%s", argv[1]); + } + + bool stop = false; + + byte buf[4096]; + struct pollfd fds[2] = { + { .events = POLLIN, .fd = STDIN_FILENO }, + { .events = POLLIN, .fd = pty }, + }; + while (0 < poll(fds, 2, -1)) { + if (fds[0].revents & POLLIN) { + ssize_t rlen = read(STDIN_FILENO, buf, sizeof(buf)); + if (rlen < 0) err(EX_IOERR, "read"); + + if (rlen == 1 && buf[0] == CTRL('S')) { + stop ^= true; + continue; + } + + if (rlen == 1 && buf[0] == CTRL('Q')) { + char dump[] = "\x1B[10i"; + ssize_t wlen = write(STDOUT_FILENO, dump, sizeof(dump) - 1); + if (wlen < 0) err(EX_IOERR, "write"); + continue; + } + + ssize_t wlen = write(pty, buf, rlen); + if (wlen < 0) err(EX_IOERR, "write"); + } + + if (fds[1].revents & POLLIN) { + ssize_t rlen = read(pty, buf, sizeof(buf)); + if (rlen < 0) err(EX_IOERR, "read"); + + ssize_t wlen = write(STDIN_FILENO, buf, rlen); + if (wlen < 0) err(EX_IOERR, "write"); + + if (!stop) { + wlen = write(STDOUT_FILENO, buf, rlen); + if (wlen < 0) err(EX_IOERR, "write"); + } + } + + int status; + pid_t dead = waitpid(pid, &status, WNOHANG); + if (dead < 0) err(EX_OSERR, "waitpid"); + if (dead) return WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE; + } + err(EX_IOERR, "poll"); +} diff --git a/bin/relay.c b/bin/relay.c new file mode 100644 index 00000000..aa7913c9 --- /dev/null +++ b/bin/relay.c @@ -0,0 +1,218 @@ +/* Copyright (C) 2019 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this Program, or any covered work, by linking or + * combining it with LibreSSL (or a modified version of that library), + * containing parts covered by the terms of the OpenSSL License and the + * original SSLeay license, the licensors of this Program grant you + * additional permission to convey the resulting work. Corresponding + * Source for a non-source form of such a combination shall include the + * source code for the parts of LibreSSL used as well as that of the + * covered work. + */ + +#include <err.h> +#include <netdb.h> +#include <netinet/in.h> +#include <poll.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sysexits.h> +#include <tls.h> +#include <unistd.h> + +#ifdef __FreeBSD__ +#include <sys/capsicum.h> +#endif + +static void clientWrite(struct tls *client, const char *ptr, size_t len) { + while (len) { + ssize_t ret = tls_write(client, ptr, len); + if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) continue; + if (ret < 0) errx(EX_IOERR, "tls_write: %s", tls_error(client)); + ptr += ret; + len -= ret; + } +} + +static void clientFormat(struct tls *client, const char *format, ...) { + char buf[1024]; + va_list ap; + va_start(ap, format); + int len = vsnprintf(buf, sizeof(buf), format, ap); + va_end(ap); + if ((size_t)len > sizeof(buf) - 1) errx(EX_DATAERR, "message too large"); + clientWrite(client, buf, len); +} + +static void clientHandle(struct tls *client, const char *chan, char *line) { + char *prefix = NULL; + if (line[0] == ':') { + prefix = strsep(&line, " ") + 1; + if (!line) errx(EX_PROTOCOL, "unexpected eol"); + } + + char *command = strsep(&line, " "); + if (!strcmp(command, "001") || !strcmp(command, "INVITE")) { + clientFormat(client, "JOIN :%s\r\n", chan); + } else if (!strcmp(command, "PING")) { + clientFormat(client, "PONG %s\r\n", line); + } + if (strcmp(command, "PRIVMSG") && strcmp(command, "NOTICE")) return; + + if (!prefix) errx(EX_PROTOCOL, "message without prefix"); + char *nick = strsep(&prefix, "!"); + + if (!line) errx(EX_PROTOCOL, "message without destination"); + char *dest = strsep(&line, " "); + if (strcmp(dest, chan)) return; + + if (!line || line[0] != ':') errx(EX_PROTOCOL, "message without message"); + line = &line[1]; + + if (!strncmp(line, "\1ACTION ", 8)) { + line = &line[8]; + size_t len = strcspn(line, "\1"); + printf("* %c\u200C%s %.*s\n", nick[0], &nick[1], (int)len, line); + } else if (command[0] == 'N') { + printf("-%c\u200C%s- %s\n", nick[0], &nick[1], line); + } else { + printf("<%c\u200C%s> %s\n", nick[0], &nick[1], line); + } +} + +#ifdef __FreeBSD__ +static void limit(int fd, const cap_rights_t *rights) { + int error = cap_rights_limit(fd, rights); + if (error) err(EX_OSERR, "cap_rights_limit"); +} +#endif + +int main(int argc, char *argv[]) { + int error; + + if (argc < 5) return EX_USAGE; + const char *host = argv[1]; + const char *port = argv[2]; + const char *nick = argv[3]; + const char *chan = argv[4]; + + setlinebuf(stdout); + signal(SIGPIPE, SIG_IGN); + + struct tls_config *config = tls_config_new(); + if (!config) errx(EX_SOFTWARE, "tls_config_new"); + + error = tls_config_set_ciphers(config, "compat"); + if (error) { + errx(EX_SOFTWARE, "tls_config_set_ciphers: %s", tls_config_error(config)); + } + + struct tls *client = tls_client(); + if (!client) errx(EX_SOFTWARE, "tls_client"); + + error = tls_configure(client, config); + if (error) errx(EX_SOFTWARE, "tls_configure: %s", tls_error(client)); + tls_config_free(config); + + struct addrinfo *head; + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + error = getaddrinfo(host, port, &hints, &head); + if (error) errx(EX_NOHOST, "getaddrinfo: %s", gai_strerror(error)); + + int sock = -1; + for (struct addrinfo *ai = head; ai; ai = ai->ai_next) { + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock < 0) err(EX_OSERR, "socket"); + + error = connect(sock, ai->ai_addr, ai->ai_addrlen); + if (!error) break; + + close(sock); + sock = -1; + } + if (sock < 0) err(EX_UNAVAILABLE, "connect"); + freeaddrinfo(head); + + error = tls_connect_socket(client, sock, host); + if (error) errx(EX_PROTOCOL, "tls_connect: %s", tls_error(client)); + +#ifdef __FreeBSD__ + error = cap_enter(); + if (error) err(EX_OSERR, "cap_enter"); + + cap_rights_t rights; + cap_rights_init(&rights, CAP_WRITE); + limit(STDOUT_FILENO, &rights); + limit(STDERR_FILENO, &rights); + + cap_rights_init(&rights, CAP_EVENT, CAP_READ); + limit(STDIN_FILENO, &rights); + + cap_rights_set(&rights, CAP_WRITE); + limit(sock, &rights); +#endif + + clientFormat(client, "NICK :%s\r\nUSER %s 0 * :%s\r\n", nick, nick, nick); + + char *input = NULL; + size_t cap = 0; + + char buf[4096]; + size_t len = 0; + + struct pollfd fds[2] = { + { .events = POLLIN, .fd = STDIN_FILENO }, + { .events = POLLIN, .fd = sock }, + }; + while (0 < poll(fds, 2, -1)) { + if (fds[0].revents) { + ssize_t len = getline(&input, &cap, stdin); + if (len < 0) err(EX_IOERR, "getline"); + input[len - 1] = '\0'; + clientFormat(client, "NOTICE %s :%s\r\n", chan, input); + } + if (!fds[1].revents) continue; + + ssize_t read = tls_read(client, &buf[len], sizeof(buf) - len); + if (read == TLS_WANT_POLLIN || read == TLS_WANT_POLLOUT) continue; + if (read < 0) errx(EX_IOERR, "tls_read: %s", tls_error(client)); + if (!read) return EX_UNAVAILABLE; + len += read; + + char *crlf; + char *line = buf; + for (;;) { + crlf = memmem(line, &buf[len] - line, "\r\n", 2); + if (!crlf) break; + crlf[0] = '\0'; + clientHandle(client, chan, line); + line = &crlf[2]; + } + len -= line - buf; + memmove(buf, line, len); + } + err(EX_IOERR, "poll"); +} diff --git a/bin/scheme.c b/bin/scheme.c new file mode 100644 index 00000000..ec5a2eec --- /dev/null +++ b/bin/scheme.c @@ -0,0 +1,256 @@ +/* Copyright (C) 2018, 2019 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +#include "png.h" + +typedef unsigned uint; +typedef unsigned char byte; + +struct HSV { + double h, s, v; +}; + +struct RGB { + byte r, g, b; +}; + +static struct RGB convert(struct HSV o) { + double c = o.v * o.s; + double h = o.h / 60.0; + double x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0)); + double m = o.v - c; + double r = m, g = m, b = m; + if (h <= 1.0) { r += c; g += x; } + else if (h <= 2.0) { r += x; g += c; } + else if (h <= 3.0) { g += c; b += x; } + else if (h <= 4.0) { g += x; b += c; } + else if (h <= 5.0) { r += x; b += c; } + else if (h <= 6.0) { r += c; b += x; } + return (struct RGB) { r * 255.0, g * 255.0, b * 255.0 }; +} + +static const struct HSV +R = { 0.0, 1.0, 1.0 }, +Y = { 60.0, 1.0, 1.0 }, +G = { 120.0, 1.0, 1.0 }, +C = { 180.0, 1.0, 1.0 }, +B = { 240.0, 1.0, 1.0 }, +M = { 300.0, 1.0, 1.0 }; + +static struct HSV x(struct HSV o, double hd, double sf, double vf) { + return (struct HSV) { + fmod(o.h + hd, 360.0), + fmin(o.s * sf, 1.0), + fmin(o.v * vf, 1.0), + }; +} + +enum { + Black, Red, Green, Yellow, Blue, Magenta, Cyan, White, + Dark = 0, + Light = 8, + Background = 16, + Foreground, + Bold, + Selection, + Cursor, + SchemeLen, +}; +static struct HSV scheme[SchemeLen]; +static struct HSV *dark = &scheme[Dark]; +static struct HSV *light = &scheme[Light]; + +static void generate(void) { + light[Black] = x(R, +45.0, 0.3, 0.3); + light[Red] = x(R, +10.0, 0.9, 0.8); + light[Green] = x(G, -55.0, 0.8, 0.6); + light[Yellow] = x(Y, -20.0, 0.8, 0.8); + light[Blue] = x(B, -55.0, 0.4, 0.5); + light[Magenta] = x(M, +45.0, 0.4, 0.6); + light[Cyan] = x(C, -60.0, 0.3, 0.6); + light[White] = x(R, +45.0, 0.3, 0.8); + + dark[Black] = x(light[Black], 0.0, 1.0, 0.3); + dark[White] = x(light[White], 0.0, 1.0, 0.7); + for (uint i = Red; i < White; ++i) { + dark[i] = x(light[i], 0.0, 1.0, 0.8); + } + + scheme[Background] = x(dark[Black], 0.0, 1.0, 0.9); + scheme[Foreground] = x(light[White], 0.0, 1.0, 0.9); + scheme[Bold] = x(light[White], 0.0, 1.0, 1.0); + scheme[Selection] = x(light[Red], +10.0, 1.0, 0.8); + scheme[Cursor] = x(dark[White], 0.0, 1.0, 0.8); +} + +static void swap(struct HSV *a, struct HSV *b) { + struct HSV c = *a; + *a = *b; + *b = c; +} + +static void invert(void) { + swap(&dark[Black], &light[White]); + swap(&dark[White], &light[Black]); +} + +typedef void OutputFn(const struct HSV *hsv, uint len); + +static void outputHSV(const struct HSV *hsv, uint len) { + for (uint i = 0; i < len; ++i) { + printf("%g,%g,%g\n", hsv[i].h, hsv[i].s, hsv[i].v); + } +} + +#define FORMAT_RGB "%02hhX%02hhX%02hhX" + +static void outputRGB(const struct HSV *hsv, uint len) { + for (uint i = 0; i < len; ++i) { + struct RGB rgb = convert(hsv[i]); + printf(FORMAT_RGB "\n", rgb.r, rgb.g, rgb.b); + } +} + +static void outputLinux(const struct HSV *hsv, uint len) { + for (uint i = 0; i < len; ++i) { + struct RGB rgb = convert(hsv[i]); + printf("\x1B]P%X" FORMAT_RGB, i, rgb.r, rgb.g, rgb.b); + } +} + +static const char *Enum[SchemeLen] = { + "DarkBlack", "DarkRed", "DarkGreen", "DarkYellow", + "DarkBlue", "DarkMagenta", "DarkCyan", "DarkWhite", + "LightBlack", "LightRed", "LightGreen", "LightYellow", + "LightBlue", "LightMagenta", "LightCyan", "LightWhite", + "Background", "Foreground", "Bold", "Selection", "Cursor", +}; + +static void outputEnum(const struct HSV *hsv, uint len) { + printf("enum {\n"); + for (uint i = 0; i < len; ++i) { + struct RGB rgb = convert(hsv[i]); + printf("\t%s = 0x" FORMAT_RGB ",\n", Enum[i], rgb.r, rgb.g, rgb.b); + } + printf("};\n"); +} + +static const char *Mintty[SchemeLen] = { + "Black", "Red", "Green", "Yellow", + "Blue", "Magenta", "Cyan", "White", + "BoldBlack", "BoldRed", "BoldGreen", "BoldYellow", + "BoldBlue", "BoldMagenta", "BoldCyan", "BoldWhite", + [Background] = "BackgroundColour", + [Foreground] = "ForegroundColour", + [Cursor] = "CursorColour", +}; + +static void outputMintty(const struct HSV *hsv, uint len) { + for (uint i = 0; i < len; ++i) { + if (!Mintty[i]) continue; + struct RGB rgb = convert(hsv[i]); + printf("%s=%hhu,%hhu,%hhu\n", Mintty[i], rgb.r, rgb.g, rgb.b); + } +} + +static void outputCSS(const struct HSV *hsv, uint len) { + printf(":root {\n"); + for (uint i = 0; i < len; ++i) { + struct RGB rgb = convert(hsv[i]); + printf("\t--ansi%u: #" FORMAT_RGB ";\n", i, rgb.r, rgb.g, rgb.b); + } + printf("}\n"); + for (uint i = 0; i < len; ++i) { + printf( + ".fg%u { color: var(--ansi%u); }\n" + ".bg%u { background-color: var(--ansi%u); }\n", + i, i, i, i + ); + } +} + +enum { + SwatchWidth = 64, + SwatchHeight = 64, + SwatchCols = 8, +}; + +static void outputPNG(const struct HSV *hsv, uint len) { + uint rows = (len + SwatchCols - 1) / SwatchCols; + uint width = SwatchWidth * SwatchCols; + uint height = SwatchHeight * rows; + pngHead(stdout, width, height, 8, PNGIndexed); + + struct RGB pal[len]; + for (uint i = 0; i < len; ++i) { + pal[i] = convert(hsv[i]); + } + pngPalette(stdout, (byte *)pal, sizeof(pal)); + + byte data[height][1 + width]; + memset(data, 0, sizeof(data)); + for (uint y = 0; y < height; ++y) { + data[y][0] = (y % SwatchHeight ? PNGUp : PNGSub); + } + for (uint i = 0; i < len; ++i) { + uint y = SwatchHeight * (i / SwatchCols); + uint x = SwatchWidth * (i % SwatchCols); + data[y][1 + x] = (x ? 1 : i); + } + pngData(stdout, (byte *)data, sizeof(data)); + pngTail(stdout); +} + +int main(int argc, char *argv[]) { + generate(); + + OutputFn *output = outputRGB; + const struct HSV *hsv = scheme; + uint len = 16; + + int opt; + while (0 < (opt = getopt(argc, argv, "acghilmp:stx"))) { + switch (opt) { + break; case 'a': len = 16; + break; case 'c': output = outputEnum; + break; case 'g': output = outputPNG; + break; case 'h': output = outputHSV; + break; case 'i': invert(); + break; case 'l': output = outputLinux; + break; case 'm': output = outputMintty; + break; case 'p': { + uint p = strtoul(optarg, NULL, 0); + if (p >= SchemeLen) return EX_USAGE; + hsv = &scheme[p]; + len = 1; + } + break; case 's': output = outputCSS; + break; case 't': len = SchemeLen; + break; case 'x': output = outputRGB; + break; default: return EX_USAGE; + } + } + + output(hsv, len); +} diff --git a/bin/shotty.c b/bin/shotty.c new file mode 100644 index 00000000..83b00313 --- /dev/null +++ b/bin/shotty.c @@ -0,0 +1,648 @@ +/* Copyright (C) 2019 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <err.h> +#include <locale.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sysexits.h> +#include <unistd.h> +#include <wchar.h> + +#define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit + +typedef unsigned uint; + +enum { + NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, + BS, HT, NL, VT, NP, CR, SO, SI, + DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, + CAN, EM, SUB, ESC, FS, GS, RS, US, + DEL = 0x7F, +}; + +enum Attr { + BIT(Bold), + BIT(Dim), + BIT(Italic), + BIT(Underline), + BIT(Blink), + BIT(Reverse), +}; + +struct Style { + enum Attr attr; + int bg, fg; +}; + +struct Cell { + struct Style style; + wchar_t ch; +}; + +static uint rows = 24, cols = 80; +static struct Cell *cells; + +static struct Cell *cell(uint y, uint x) { + assert(y <= rows); + assert(x <= cols); + assert(y * cols + x <= rows * cols); + return &cells[y * cols + x]; +} + +static uint y, x; +static struct Style style = { .bg = -1, .fg = -1 }; + +static struct { + uint y, x; +} save; + +enum { ParamCap = 16 }; +static struct { + uint s[ParamCap]; + uint n, i; +} param; + +static uint p(uint i, uint z) { + return (i < param.n ? param.s[i] : z); +} + +static uint min(uint a, uint b) { + return (a < b ? a : b); +} + +#define _ch ch __attribute__((unused)) +typedef void Action(wchar_t ch); + +static void nop(wchar_t _ch) { +} + +static void csi(wchar_t _ch) { + memset(¶m, 0, sizeof(param)); +} + +static void csiSep(wchar_t _ch) { + if (param.n == ParamCap) return; + if (!param.n) param.n++; + param.n++; + param.i++; +} + +static void csiDigit(wchar_t ch) { + param.s[param.i] *= 10; + param.s[param.i] += ch - L'0'; + if (!param.n) param.n++; +} + +static void bs(wchar_t _ch) { if (x) x--; } +static void ht(wchar_t _ch) { x = min(x - x % 8 + 8, cols - 1); } +static void cr(wchar_t _ch) { x = 0; } +static void cuu(wchar_t _ch) { y -= min(p(0, 1), y); } +static void cud(wchar_t _ch) { y = min(y + p(0, 1), rows - 1); } +static void cuf(wchar_t _ch) { x = min(x + p(0, 1), cols - 1); } +static void cub(wchar_t _ch) { x -= min(p(0, 1), x); } +static void cnl(wchar_t _ch) { x = 0; cud(0); } +static void cpl(wchar_t _ch) { x = 0; cuu(0); } +static void cha(wchar_t _ch) { x = min(p(0, 1) - 1, cols - 1); } +static void vpa(wchar_t _ch) { y = min(p(0, 1) - 1, rows - 1); } +static void cup(wchar_t _ch) { + y = min(p(0, 1) - 1, rows - 1); + x = min(p(1, 1) - 1, cols - 1); +} +static void decsc(wchar_t _ch) { + save.y = y; + save.x = x; +} +static void decrc(wchar_t _ch) { + y = save.y; + x = save.x; +} + +static void move(struct Cell *dst, struct Cell *src, size_t len) { + memmove(dst, src, sizeof(*dst) * len); +} + +static void erase(struct Cell *at, struct Cell *to) { + for (; at < to; ++at) { + at->style = style; + at->ch = L' '; + } +} + +static void ed(wchar_t _ch) { + erase( + (p(0, 0) == 0 ? cell(y, x) : cell(0, 0)), + (p(0, 0) == 1 ? cell(y, x) : cell(rows - 1, cols)) + ); +} +static void el(wchar_t _ch) { + erase( + (p(0, 0) == 0 ? cell(y, x) : cell(y, 0)), + (p(0, 0) == 1 ? cell(y, x) : cell(y, cols)) + ); +} +static void ech(wchar_t _ch) { + erase(cell(y, x), cell(y, min(x + p(0, 1), cols))); +} + +static void dch(wchar_t _ch) { + uint n = min(p(0, 1), cols - x); + move(cell(y, x), cell(y, x + n), cols - x - n); + erase(cell(y, cols - n), cell(y, cols)); +} +static void ich(wchar_t _ch) { + uint n = min(p(0, 1), cols - x); + move(cell(y, x + n), cell(y, x), cols - x - n); + erase(cell(y, x), cell(y, x + n)); +} + +static struct { + uint top, bot; +} scroll; + +static void scrollUp(uint top, uint n) { + n = min(n, scroll.bot - top); + move(cell(top, 0), cell(top + n, 0), cols * (scroll.bot - top - n)); + erase(cell(scroll.bot - n, 0), cell(scroll.bot, 0)); +} + +static void scrollDown(uint top, uint n) { + n = min(n, scroll.bot - top); + move(cell(top + n, 0), cell(top, 0), cols * (scroll.bot - top - n)); + erase(cell(top, 0), cell(top + n, 0)); +} + +static void decstbm(wchar_t _ch) { + scroll.bot = min(p(1, rows), rows); + scroll.top = min(p(0, 1) - 1, scroll.bot); +} + +static void su(wchar_t _ch) { scrollUp(scroll.top, p(0, 1)); } +static void sd(wchar_t _ch) { scrollDown(scroll.top, p(0, 1)); } +static void dl(wchar_t _ch) { scrollUp(min(y, scroll.bot), p(0, 1)); } +static void il(wchar_t _ch) { scrollDown(min(y, scroll.bot), p(0, 1)); } + +static void nl(wchar_t _ch) { + if (y + 1 == scroll.bot) { + scrollUp(scroll.top, 1); + } else { + y = min(y + 1, rows - 1); + } +} +static void ri(wchar_t _ch) { + if (y == scroll.top) { + scrollDown(scroll.top, 1); + } else { + if (y) y--; + } +} + +static enum Mode { + BIT(Insert), + BIT(Wrap), + BIT(Cursor), +} mode = Wrap | Cursor; + +static enum Mode paramMode(void) { + enum Mode mode = 0; + for (uint i = 0; i < param.n; ++i) { + switch (param.s[i]) { + break; case 4: mode |= Insert; + break; default: warnx("unhandled SM/RM %u", param.s[i]); + } + } + return mode; +} + +static enum Mode paramDECMode(void) { + enum Mode mode = 0; + for (uint i = 0; i < param.n; ++i) { + switch (param.s[i]) { + break; case 1: // DECCKM + break; case 7: mode |= Wrap; + break; case 12: // "Start Blinking Cursor" + break; case 25: mode |= Cursor; + break; default: { + if (param.s[i] < 1000) { + warnx("unhandled DECSET/DECRST %u", param.s[i]); + } + } + } + } + return mode; +} + +static void sm(wchar_t _ch) { mode |= paramMode(); } +static void rm(wchar_t _ch) { mode &= ~paramMode(); } +static void decset(wchar_t _ch) { mode |= paramDECMode(); } +static void decrst(wchar_t _ch) { mode &= ~paramDECMode(); } + +enum { + Reset, + SetBold, + SetDim, + SetItalic, + SetUnderline, + SetBlink, + SetReverse = 7, + + UnsetBoldDim = 22, + UnsetItalic, + UnsetUnderline, + UnsetBlink, + UnsetReverse = 27, + + SetFg0 = 30, + SetFg7 = 37, + SetFg, + ResetFg, + SetBg0 = 40, + SetBg7 = 47, + SetBg, + ResetBg, + + SetFg8 = 90, + SetFgF = 97, + SetBg8 = 100, + SetBgF = 107, + + Color256 = 5, +}; + +static void sgr(wchar_t _ch) { + uint n = param.i + 1; + for (uint i = 0; i < n; ++i) { + switch (param.s[i]) { + break; case Reset: style = (struct Style) { .bg = -1, .fg = -1 }; + + break; case SetBold: style.attr |= Bold; style.attr &= ~Dim; + break; case SetDim: style.attr |= Dim; style.attr &= ~Bold; + break; case SetItalic: style.attr |= Italic; + break; case SetUnderline: style.attr |= Underline; + break; case SetBlink: style.attr |= Blink; + break; case SetReverse: style.attr |= Reverse; + + break; case UnsetBoldDim: style.attr &= ~(Bold | Dim); + break; case UnsetItalic: style.attr &= ~Italic; + break; case UnsetUnderline: style.attr &= ~Underline; + break; case UnsetBlink: style.attr &= ~Blink; + break; case UnsetReverse: style.attr &= ~Reverse; + + break; case SetFg: { + if (++i < n && param.s[i] == Color256) { + if (++i < n) style.fg = param.s[i]; + } + } + break; case SetBg: { + if (++i < n && param.s[i] == Color256) { + if (++i < n) style.bg = param.s[i]; + } + } + + break; case ResetFg: style.fg = -1; + break; case ResetBg: style.bg = -1; + + break; default: { + uint p = param.s[i]; + if (p >= SetFg0 && p <= SetFg7) { + style.fg = p - SetFg0; + } else if (p >= SetBg0 && p <= SetBg7) { + style.bg = p - SetBg0; + } else if (p >= SetFg8 && p <= SetFgF) { + style.fg = 8 + p - SetFg8; + } else if (p >= SetBg8 && p <= SetBgF) { + style.bg = 8 + p - SetBg8; + } else { + warnx("unhandled SGR %u", p); + } + } + } + } +} + +static enum { + USASCII, + DECSpecial, +} charset; + +static void usascii(wchar_t _ch) { charset = USASCII; } +static void decSpecial(wchar_t _ch) { charset = DECSpecial; } + +static const wchar_t AltCharset[128] = { + ['`'] = L'◆', ['a'] = L'▒', ['f'] = L'°', ['g'] = L'±', ['i'] = L'␋', + ['j'] = L'┘', ['k'] = L'┐', ['l'] = L'┌', ['m'] = L'└', ['n'] = L'┼', + ['o'] = L'⎺', ['p'] = L'⎻', ['q'] = L'─', ['r'] = L'⎼', ['s'] = L'⎽', + ['t'] = L'├', ['u'] = L'┤', ['v'] = L'┴', ['w'] = L'┬', ['x'] = L'│', + ['y'] = L'≤', ['z'] = L'≥', ['{'] = L'π', ['|'] = L'≠', ['}'] = L'£', + ['~'] = L'·', +}; + +static void add(wchar_t ch) { + if (charset == DECSpecial && ch < 128 && AltCharset[ch]) { + ch = AltCharset[ch]; + } + + int width = wcwidth(ch); + if (width < 0) { + warnx("unhandled \\u%02X", ch); + return; + } + + if (mode & Insert) { + uint n = min(width, cols - x); + move(cell(y, x + n), cell(y, x), cols - x - n); + } + if (mode & Wrap && x + width > cols) { + cr(0); + nl(0); + } + + cell(y, x)->style = style; + cell(y, x)->ch = ch; + for (int i = 1; i < width && x + i < cols; ++i) { + cell(y, x + i)->style = style; + cell(y, x + i)->ch = L'\0'; + } + x = min(x + width, (mode & Wrap ? cols : cols - 1)); +} + +static void html(void); +static void mc(wchar_t _ch) { + if (p(0, 0) == 10) { + html(); + } else { + warnx("unhandled CSI %u MC", p(0, 0)); + } +} + +static enum { + Data, + Esc, + G0, + CSI, + CSILt, + CSIEq, + CSIGt, + CSIQm, + CSIInter, + OSC, + OSCEsc, +} state; + +static void escDefault(wchar_t ch) { + warnx("unhandled ESC %lc", ch); +} + +static void g0Default(wchar_t ch) { + warnx("unhandled G0 %lc", ch); + charset = USASCII; +} + +static void csiInter(wchar_t ch) { + switch (state) { + break; case CSI: warnx("unhandled CSI %lc ...", ch); + break; case CSILt: warnx("unhandled CSI < %lc ...", ch); + break; case CSIEq: warnx("unhandled CSI = %lc ...", ch); + break; case CSIGt: warnx("unhandled CSI > %lc ...", ch); + break; case CSIQm: warnx("unhandled CSI ? %lc ...", ch); + break; default: abort(); + } +} + +static void csiFinal(wchar_t ch) { + switch (state) { + break; case CSI: warnx("unhandled CSI %lc", ch); + break; case CSILt: warnx("unhandled CSI < %lc", ch); + break; case CSIEq: warnx("unhandled CSI = %lc", ch); + break; case CSIGt: warnx("unhandled CSI > %lc", ch); + break; case CSIQm: warnx("unhandled CSI ? %lc", ch); + break; case CSIInter: warnx("unhandled CSI ... %lc", ch); + break; default: abort(); + } +} + +#define S(s) break; case s: switch (ch) +#define A(c, a, s) break; case c: a(ch); state = s +#define D(a, s) break; default: a(ch); state = s +static void update(wchar_t ch) { + switch (state) { + default: abort(); + + S(Data) { + A(BEL, nop, Data); + A(BS, bs, Data); + A(HT, ht, Data); + A(NL, nl, Data); + A(CR, cr, Data); + A(ESC, nop, Esc); + D(add, Data); + } + + S(Esc) { + A('(', nop, G0); + A('7', decsc, Data); + A('8', decrc, Data); + A('=', nop, Data); + A('>', nop, Data); + A('M', ri, Data); + A('[', csi, CSI); + A(']', nop, OSC); + D(escDefault, Data); + } + S(G0) { + A('0', decSpecial, Data); + A('B', usascii, Data); + D(g0Default, Data); + } + + S(CSI) { + A(' ' ... '/', csiInter, CSIInter); + A('0' ... '9', csiDigit, CSI); + A(':', nop, CSI); + A(';', csiSep, CSI); + A('<', nop, CSILt); + A('=', nop, CSIEq); + A('>', nop, CSIGt); + A('?', nop, CSIQm); + A('@', ich, Data); + A('A', cuu, Data); + A('B', cud, Data); + A('C', cuf, Data); + A('D', cub, Data); + A('E', cnl, Data); + A('F', cpl, Data); + A('G', cha, Data); + A('H', cup, Data); + A('J', ed, Data); + A('K', el, Data); + A('L', il, Data); + A('M', dl, Data); + A('P', dch, Data); + A('S', su, Data); + A('T', sd, Data); + A('X', ech, Data); + A('d', vpa, Data); + A('h', sm, Data); + A('i', mc, Data); + A('l', rm, Data); + A('m', sgr, Data); + A('r', decstbm, Data); + A('t', nop, Data); + D(csiFinal, Data); + } + + S(CSILt ... CSIGt) { + A(' ' ... '/', csiInter, CSIInter); + A('0' ... '9', csiDigit, state); + A(':', nop, state); + A(';', csiSep, state); + D(csiFinal, Data); + } + + S(CSIQm) { + A(' ' ... '/', csiInter, CSIInter); + A('0' ... '9', csiDigit, CSIQm); + A(':', nop, CSIQm); + A(';', csiSep, CSIQm); + A('h', decset, Data); + A('l', decrst, Data); + D(csiFinal, Data); + } + + S(CSIInter) { + D(csiFinal, Data); + } + + S(OSC) { + A(BEL, nop, Data); + A(ESC, nop, OSCEsc); + D(nop, OSC); + } + S(OSCEsc) { + A('\\', nop, Data); + D(nop, OSC); + } + } +} + +static bool bright; +static int defaultBg = 0; +static int defaultFg = 7; + +static void span(const struct Style *prev, const struct Cell *cell) { + struct Style style = cell->style; + if (!prev || memcmp(prev, &style, sizeof(*prev))) { + if (prev) printf("</span>"); + if (style.bg < 0) style.bg = defaultBg; + if (style.fg < 0) style.fg = defaultFg; + if (bright && style.attr & Bold) { + if (style.fg < 8) style.fg += 8; + style.attr ^= Bold; + } + printf( + "<span style=\"%s%s%s\" class=\"bg%u fg%u\">", + (style.attr & Bold ? "font-weight:bold;" : ""), + (style.attr & Italic ? "font-style:italic;" : ""), + (style.attr & Underline ? "text-decoration:underline;" : ""), + (style.attr & Reverse ? style.fg : style.bg), + (style.attr & Reverse ? style.bg : style.fg) + ); + } + switch (cell->ch) { + break; case '&': printf("&"); + break; case '<': printf("<"); + break; case '>': printf(">"); + break; default: printf("%lc", (wint_t)cell->ch); + } +} + +static bool mediaCopy; +static void html(void) { + mediaCopy = true; + if (mode & Cursor) cell(y, x)->style.attr ^= Reverse; + printf( + "<pre style=\"width: %uch;\" class=\"bg%u fg%u\">", + cols, defaultBg, defaultFg + ); + for (uint y = 0; y < rows; ++y) { + for (uint x = 0; x < cols; ++x) { + if (!cell(y, x)->ch) continue; + span(x ? &cell(y, x - 1)->style : NULL, cell(y, x)); + } + printf("</span>\n"); + } + printf("</pre>\n"); + if (mode & Cursor) cell(y, x)->style.attr ^= Reverse; +} + +int main(int argc, char *argv[]) { + setlocale(LC_CTYPE, ""); + + bool debug = false; + bool size = false; + bool hide = false; + + int opt; + while (0 < (opt = getopt(argc, argv, "Bb:df:h:nsw:"))) { + switch (opt) { + break; case 'B': bright = true; + break; case 'b': defaultBg = strtol(optarg, NULL, 0); + break; case 'd': debug = true; + break; case 'f': defaultFg = strtol(optarg, NULL, 0); + break; case 'h': rows = strtoul(optarg, NULL, 0); + break; case 'n': hide = true; + break; case 's': size = true; + break; case 'w': cols = strtoul(optarg, NULL, 0); + break; default: return EX_USAGE; + } + } + + FILE *file = stdin; + if (optind < argc) { + file = fopen(argv[optind], "r"); + if (!file) err(EX_NOINPUT, "%s", argv[optind]); + } + + if (size) { + struct winsize window; + int error = ioctl(STDERR_FILENO, TIOCGWINSZ, &window); + if (error) err(EX_IOERR, "ioctl"); + rows = window.ws_row; + cols = window.ws_col; + } + scroll.bot = rows; + + cells = calloc(rows * cols, sizeof(*cells)); + if (!cells) err(EX_OSERR, "calloc"); + erase(cell(0, 0), cell(rows - 1, cols)); + + wint_t ch; + while (WEOF != (ch = getwc(file))) { + uint prev = state; + update(ch); + if (debug && state != prev && state == Data) html(); + } + if (ferror(file)) err(EX_IOERR, "getwc"); + + if (!mediaCopy) { + if (hide) mode &= ~Cursor; + html(); + } +} diff --git a/bin/title.c b/bin/title.c new file mode 100644 index 00000000..82f89d95 --- /dev/null +++ b/bin/title.c @@ -0,0 +1,211 @@ +/* Copyright (C) 2019 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <curl/curl.h> +#include <err.h> +#include <locale.h> +#include <regex.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> +#include <wchar.h> + +static regex_t regex(const char *pattern, int flags) { + regex_t regex; + int error = regcomp(®ex, pattern, REG_EXTENDED | flags); + if (!error) return regex; + + char buf[256]; + regerror(error, ®ex, buf, sizeof(buf)); + errx(EX_SOFTWARE, "regcomp: %s: %s", buf, pattern); +} + +static const struct Entity { + wchar_t ch; + const char *name; +} Entities[] = { + { L'"', """ }, + { L'&', "&" }, + { L'<', "<" }, + { L'>', ">" }, + { L'', " " }, +}; + +static wchar_t entity(const char *name) { + for (size_t i = 0; i < sizeof(Entities) / sizeof(Entities[0]); ++i) { + struct Entity entity = Entities[i]; + if (strncmp(name, entity.name, strlen(entity.name))) continue; + return entity.ch; + } + if (!strncmp(name, "&#x", 3)) return strtoul(&name[3], NULL, 16); + if (!strncmp(name, "&#", 2)) return strtoul(&name[2], NULL, 10); + return 0; +} + +static const char EntityPattern[] = { + "[[:space:]]+|&([[:alpha:]]+|#([[:digit:]]+|x[[:xdigit:]]+));" +}; +static regex_t EntityRegex; + +static void showTitle(const char *title) { + regmatch_t match = {0}; + for (; *title; title += match.rm_eo) { + if (regexec(&EntityRegex, title, 1, &match, 0)) break; + if (title[match.rm_so] != '&') { + printf("%.*s ", (int)match.rm_so, title); + continue; + } + wchar_t ch = entity(&title[match.rm_so]); + if (ch) { + printf("%.*s%lc", (int)match.rm_so, title, (wint_t)ch); + } else { + printf("%.*s", (int)match.rm_eo, title); + } + } + printf("%s\n", title); +} + +static CURL *curl; +static bool title; +static struct { + char buf[64 * 1024]; + size_t len; +} body; + +// HE COMES +static const char TitlePattern[] = "<title>([^<]*)</title>"; +static regex_t TitleRegex; + +static size_t handleBody(char *buf, size_t size, size_t nitems, void *user) { + (void)user; + size_t len = size * nitems; + size_t cap = sizeof(body.buf) - body.len - 1; + size_t new = (len < cap ? len : cap); + if (title || !new) return len; + + memcpy(&body.buf[body.len], buf, new); + body.len += new; + body.buf[body.len] = '\0'; + + regmatch_t match[2]; + if (regexec(&TitleRegex, body.buf, 2, match, 0)) return len; + body.buf[match[1].rm_eo] = '\0'; + showTitle(&body.buf[match[1].rm_so]); + title = true; + + return len; +} + +static CURLcode fetchTitle(const char *url) { + CURLcode code = curl_easy_setopt(curl, CURLOPT_URL, url); + if (code) return code; + + curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); + code = curl_easy_perform(curl); + if (code) return code; + + char *type; + code = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &type); + if (code) return code; + if (!type || strncmp(type, "text/html", 9)) return CURLE_OK; + + char *dest; + curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &dest); + dest = strdup(dest); + if (!dest) err(EX_OSERR, "strdup"); + + code = curl_easy_setopt(curl, CURLOPT_URL, dest); + if (code) return code; + free(dest); + + body.len = 0; + title = false; + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + code = curl_easy_perform(curl); + return code; +} + +int main(int argc, char *argv[]) { + EntityRegex = regex(EntityPattern, 0); + TitleRegex = regex(TitlePattern, REG_ICASE); + + setlocale(LC_CTYPE, ""); + setlinebuf(stdout); + + CURLcode code = curl_global_init(CURL_GLOBAL_ALL); + if (code) errx(EX_OSERR, "curl_global_init: %s", curl_easy_strerror(code)); + + curl = curl_easy_init(); + if (!curl) errx(EX_SOFTWARE, "curl_easy_init"); + + static char error[CURL_ERROR_SIZE]; + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error); + + curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + curl_easy_setopt( + curl, CURLOPT_USERAGENT, + "curl/7.54.0 facebookexternalhit/1.1 Twitterbot/1.0" + ); + curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3L); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handleBody); + + bool exclude = false; + regex_t excludeRegex; + + int opt; + while (0 < (opt = getopt(argc, argv, "x:v"))) { + switch (opt) { + break; case 'x': { + exclude = true; + excludeRegex = regex(optarg, REG_NOSUB); + } + break; case 'v': curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + break; default: return EX_USAGE; + } + } + + if (optind < argc) { + code = fetchTitle(argv[optind]); + if (!code) return EX_OK; + errx(EX_DATAERR, "curl_easy_perform: %s", error); + } + + char *buf = NULL; + size_t cap = 0; + + regex_t urlRegex = regex("https?://([^[:space:]>\"()]|[(][^)]*[)])+", 0); + while (0 < getline(&buf, &cap, stdin)) { + regmatch_t match = {0}; + for (char *ptr = buf; *ptr; ptr += match.rm_eo) { + if (regexec(&urlRegex, ptr, 1, &match, 0)) break; + ptr[match.rm_eo] = '\0'; + const char *url = &ptr[match.rm_so]; + if (!exclude || regexec(&excludeRegex, url, 0, NULL, 0)) { + code = fetchTitle(url); + if (code) warnx("curl_easy_perform: %s", error); + } + ptr[match.rm_eo] = ' '; + } + } + if (ferror(stdin)) err(EX_IOERR, "getline"); +} diff --git a/bin/ttpre.c b/bin/ttpre.c new file mode 100644 index 00000000..4917be37 --- /dev/null +++ b/bin/ttpre.c @@ -0,0 +1,66 @@ +/* Copyright (C) 2018 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <wchar.h> + +static void put(wchar_t ch) { + switch (ch) { + break; case L'&': printf("&"); + break; case L'<': printf("<"); + break; case L'>': printf(">"); + break; default: printf("%lc", (wint_t)ch); + } +} + +static void tag(const char *open) { + static const char *close = NULL; + if (close == open) return; + if (close) printf("</%s>", close); + if (open) printf("<%s>", open); + close = open; +} + +static void push(wchar_t ch) { + static wchar_t q[3]; + if (q[1] == L'\b' && q[0] == L'_') { + tag("i"); + put(q[2]); + q[0] = q[1] = q[2] = 0; + } else if (q[1] == L'\b' && q[0] == q[2]) { + tag("b"); + put(q[2]); + q[0] = q[1] = q[2] = 0; + } else if (q[0]) { + tag(NULL); + put(q[0]); + } + q[0] = q[1]; + q[1] = q[2]; + q[2] = ch; +} + +int main(void) { + setlocale(LC_CTYPE, ""); + printf("<pre>"); + wint_t ch; + while (WEOF != (ch = getwchar())) push(ch); + push(0); push(0); push(0); + printf("</pre>\n"); + return EXIT_SUCCESS; +} diff --git a/bin/up.sh b/bin/up.sh new file mode 100644 index 00000000..e65d4522 --- /dev/null +++ b/bin/up.sh @@ -0,0 +1,76 @@ +#!/bin/sh +set -eu + +readonly Host='temp.causal.agency' + +upload() { + local src ext ts rand url + src=$1 + ext=${src##*.} + ts=$(date +'%s') + rand=$(openssl rand -hex 4) + url=$(printf '%s/%x%s.%s' "$Host" "$ts" "$rand" "$ext") + scp -q "$src" "${Host}:/usr/local/www/${url}" + echo "https://${url}" +} + +temp() { + temp=$(mktemp -d) + trap "rm -r '$temp'" EXIT +} + +uploadText() { + temp + cat > "${temp}/input.txt" + upload "${temp}/input.txt" +} + +uploadCommand() { + temp + echo "$ $*" > "${temp}/exec.txt" + "$@" >> "${temp}/exec.txt" 2>&1 || true + upload "${temp}/exec.txt" +} + +uploadHi() { + temp + hi -f html -o document,anchor,tab=4 "$@" > "${temp}/hi.html" + upload "${temp}/hi.html" +} + +uploadScreen() { + temp + screencapture -i "$@" "${temp}/capture.png" + pngo "${temp}/capture.png" || true + upload "${temp}/capture.png" +} + +uploadTerminal() { + temp + cat > "${temp}/term.html" <<-EOF + <!DOCTYPE html> + <title>${1}</title> + <style> + $(scheme -s) + </style> + EOF + ptee "$@" | shotty -Bs >> "${temp}/term.html" + upload "${temp}/term.html" +} + +while getopts 'chst' opt; do + case "$opt" in + (c) fn=uploadCommand;; + (h) fn=uploadHi;; + (s) fn=uploadScreen;; + (t) fn=uploadTerminal;; + (?) exit 1;; + esac +done +shift $((OPTIND - 1)) +[ $# -eq 0 ] && : ${fn:=uploadText} +: ${fn:=upload} + +url=$($fn "$@") +printf '%s' "$url" | pbcopy || true +echo "$url" diff --git a/bin/when.y b/bin/when.y new file mode 100644 index 00000000..5367b3b3 --- /dev/null +++ b/bin/when.y @@ -0,0 +1,250 @@ +/* Copyright (C) 2019 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +%{ + +#include <ctype.h> +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <sysexits.h> +#include <time.h> + +static void yyerror(const char *str); +static int yylex(void); + +#define YYSTYPE struct tm + +static const char *Days[7] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", +}; + +static const char *Months[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", +}; + +static const struct tm Week = { .tm_mday = 7 }; + +static struct tm normalize(struct tm date) { + time_t time = timegm(&date); + struct tm *norm = gmtime(&time); + if (!norm) err(EX_OSERR, "gmtime"); + return *norm; +} + +static struct tm today(void) { + time_t now = time(NULL); + struct tm *local = localtime(&now); + if (!local) err(EX_OSERR, "localtime"); + struct tm date = { + .tm_year = local->tm_year, + .tm_mon = local->tm_mon, + .tm_mday = local->tm_mday, + }; + return normalize(date); +} + +static struct tm monthDay(int month, int day) { + struct tm date = today(); + date.tm_mon = month; + date.tm_mday = day; + return normalize(date); +} + +static struct tm monthDayYear(int month, int day, int year) { + struct tm date = today(); + date.tm_mon = month; + date.tm_mday = day; + date.tm_year = year - 1900; + return normalize(date); +} + +static struct tm weekDay(int day) { + struct tm date = today(); + date.tm_mday += day - date.tm_wday; + return normalize(date); +} + +static struct tm scalarAdd(struct tm a, struct tm b) { + a.tm_mday += b.tm_mday; + a.tm_mon += b.tm_mon; + a.tm_year += b.tm_year; + return a; +} + +static struct tm scalarSub(struct tm a, struct tm b) { + a.tm_mday -= b.tm_mday; + a.tm_mon -= b.tm_mon; + a.tm_year -= b.tm_year; + return a; +} + +static struct tm dateAdd(struct tm date, struct tm scalar) { + return normalize(scalarAdd(date, scalar)); +} + +static struct tm dateSub(struct tm date, struct tm scalar) { + return normalize(scalarSub(date, scalar)); +} + +static struct tm dateDiff(struct tm a, struct tm b) { + struct tm diff = { + .tm_year = a.tm_year - b.tm_year, + .tm_mon = a.tm_mon - b.tm_mon, + .tm_mday = a.tm_mday - b.tm_mday, + }; + if (a.tm_mon < b.tm_mon) { + diff.tm_year--; + diff.tm_mon += 12; + } + if (a.tm_mday < b.tm_mday) { + diff.tm_mon--; + diff.tm_mday = 0; + while (dateAdd(b, diff).tm_mday != a.tm_mday) diff.tm_mday++; + } + time_t atime = timegm(&a), btime = timegm(&b); + diff.tm_yday = (atime - btime) / 24 / 60 / 60; + return diff; +} + +static void printDate(struct tm date) { + printf( + "%s %s %d %d\n", + Days[date.tm_wday], Months[date.tm_mon], + date.tm_mday, 1900 + date.tm_year + ); +} + +static void printScalar(struct tm scalar) { + if (scalar.tm_year) printf("%dy ", scalar.tm_year); + if (scalar.tm_mon) printf("%dm ", scalar.tm_mon); + if (scalar.tm_mday % 7) { + printf("%dd ", scalar.tm_mday); + } else if (scalar.tm_mday) { + printf("%dw ", scalar.tm_mday / 7); + } + if (scalar.tm_yday && scalar.tm_mon) printf("(%dd) ", scalar.tm_yday); + printf("\n"); +} + +%} + +%token Number Month Day +%left '+' '-' +%right '<' '>' + +%% + +expr: + date { printDate($1); } + | scalar { printScalar($1); } + ; + +date: + dateLit + | '(' date ')' { $$ = $2; } + | '<' date { $$ = dateSub($2, Week); } + | '>' date { $$ = dateAdd($2, Week); } + | date '+' scalar { $$ = dateAdd($1, $3); } + | date '-' scalar { $$ = dateSub($1, $3); } + ; + +scalar: + scalarLit + | '(' scalar ')' { $$ = $2; } + | scalar '+' scalar { $$ = scalarAdd($1, $3); } + | scalar '-' scalar { $$ = scalarSub($1, $3); } + | date '-' date { $$ = dateDiff($1, $3); } + ; + +dateLit: + { $$ = today(); } + | '.' { $$ = today(); } + | Month Number { $$ = monthDay($1.tm_mon, $2.tm_sec); } + | Month Number Number { $$ = monthDayYear($1.tm_mon, $2.tm_sec, $3.tm_sec); } + | Day { $$ = weekDay($1.tm_wday); } + ; + +scalarLit: + Number 'd' { $$ = (struct tm) { .tm_mday = $1.tm_sec }; } + | Number 'w' { $$ = (struct tm) { .tm_mday = 7 * $1.tm_sec }; } + | Number 'm' { $$ = (struct tm) { .tm_mon = $1.tm_sec }; } + | Number 'y' { $$ = (struct tm) { .tm_year = $1.tm_sec }; } + ; + +%% + +static void yyerror(const char *str) { + warnx("%s", str); +} + +static const char *input; + +static int yylex(void) { + while (isspace(*input)) input++; + if (!*input) return EOF; + + if (isdigit(*input)) { + char *rest; + yylval.tm_sec = strtol(input, &rest, 10); + input = rest; + return Number; + } + + for (int i = 0; i < 7; ++i) { + if (strncasecmp(input, Days[i], 3)) continue; + while (isalpha(*input)) input++; + yylval.tm_wday = i; + return Day; + } + + for (int i = 0; i < 12; ++i) { + if (strncasecmp(input, Months[i], 3)) continue; + while (isalpha(*input)) input++; + yylval.tm_mon = i; + return Month; + } + + return *input++; +} + +int main(int argc, char *argv[]) { + if (argc > 1) { + input = argv[1]; + return yyparse(); + } + + struct tm date = today(); + printDate(date); + printf("\n"); + + char *line = NULL; + size_t cap = 0; + while (0 < getline(&line, &cap, stdin)) { + if (line[0] == '\n') continue; + + if (today().tm_mday != date.tm_mday) { + warnx("the date has changed"); + date = today(); + } + + input = line; + yyparse(); + printf("\n"); + } +} diff --git a/bin/xx.c b/bin/xx.c new file mode 100644 index 00000000..6d04f2f5 --- /dev/null +++ b/bin/xx.c @@ -0,0 +1,142 @@ +/* Copyright (C) 2017 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <ctype.h> +#include <err.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sysexits.h> +#include <unistd.h> + +typedef unsigned char byte; + +static bool zero(const byte *ptr, size_t size) { + for (size_t i = 0; i < size; ++i) { + if (ptr[i]) return false; + } + return true; +} + +static struct { + size_t cols; + size_t group; + size_t blank; + bool ascii; + bool offset; + bool skip; +} options = { 16, 8, 0, true, true, false }; + +static void dump(FILE *file) { + bool skip = false; + + byte buf[options.cols]; + size_t offset = 0; + for ( + size_t size; + (size = fread(buf, 1, sizeof(buf), file)); + offset += size + ) { + if (options.skip) { + if (zero(buf, size)) { + if (!skip) printf("*\n"); + skip = true; + continue; + } else { + skip = false; + } + } + + if (options.blank) { + if (offset && offset % options.blank == 0) { + printf("\n"); + } + } + + if (options.offset) { + printf("%08zX: ", offset); + } + + for (size_t i = 0; i < sizeof(buf); ++i) { + if (options.group) { + if (i && !(i % options.group)) { + printf(" "); + } + } + if (i < size) { + printf("%02hhX ", buf[i]); + } else { + printf(" "); + } + } + + if (options.ascii) { + printf(" "); + for (size_t i = 0; i < size; ++i) { + if (options.group) { + if (i && !(i % options.group)) { + printf(" "); + } + } + printf("%c", isprint(buf[i]) ? buf[i] : '.'); + } + } + + printf("\n"); + } +} + +static void undump(FILE *file) { + byte c; + int match; + while (0 < (match = fscanf(file, " %hhx", &c))) { + printf("%c", c); + } + if (!match) errx(EX_DATAERR, "invalid input"); +} + +int main(int argc, char *argv[]) { + bool reverse = false; + const char *path = NULL; + + int opt; + while (0 < (opt = getopt(argc, argv, "ac:g:p:rsz"))) { + switch (opt) { + break; case 'a': options.ascii ^= true; + break; case 'c': options.cols = strtoul(optarg, NULL, 0); + break; case 'g': options.group = strtoul(optarg, NULL, 0); + break; case 'p': options.blank = strtoul(optarg, NULL, 0); + break; case 'r': reverse = true; + break; case 's': options.offset ^= true; + break; case 'z': options.skip ^= true; + break; default: return EX_USAGE; + } + } + if (argc > optind) path = argv[optind]; + if (!options.cols) return EX_USAGE; + + FILE *file = path ? fopen(path, "r") : stdin; + if (!file) err(EX_NOINPUT, "%s", path); + + if (reverse) { + undump(file); + } else { + dump(file); + } + if (ferror(file)) err(EX_IOERR, "%s", path); + + return EX_OK; +} |