diff options
Diffstat (limited to 'bin')
367 files changed, 79735 insertions, 0 deletions
diff --git a/bin/cat/cat.1 b/bin/cat/cat.1 new file mode 100644 index 0000000..f27ce59 --- /dev/null +++ b/bin/cat/cat.1 @@ -0,0 +1,217 @@ +.\" $NetBSD: cat.1,v 1.41 2017/10/02 08:24:17 wiz Exp $ +.\" +.\" 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. +.\" +.\" @(#)cat.1 8.3 (Berkeley) 5/2/95 +.\" +.Dd June 15, 2014 +.Dt CAT 1 +.Os +.Sh NAME +.Nm cat +.Nd concatenate and print files +.Sh SYNOPSIS +.Nm +.Op Fl beflnstuv +.Op Fl B Ar bsize +.Op Fl +.Op Ar +.Sh DESCRIPTION +The +.Nm +utility reads files sequentially, writing them to the standard output. +The +.Ar file +operands are processed in command line order. +A single dash represents the standard input, +and may appear multiple times in the +.Ar file +list. +If no +.Ar file +operands are given, standard input is read. +.Pp +The word +.Dq concatenate +is just a verbose synonym for +.Dq catenate . +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl B Ar bsize +Read with a buffer size of +.Ar bsize +bytes, instead of the default buffer size which is the blocksize of the +output file. +.It Fl b +Implies the +.Fl n +option, but doesn't number blank lines. +.It Fl e +Implies the +.Fl v +option, and displays a dollar sign +.Pq Ql \&$ +at the end of each line +as well. +.It Fl f +Only attempt to display regular files. +.It Fl l +Set an exclusive advisory lock on the standard output file descriptor. +This lock is set using +.Xr fcntl 2 +with the +.Dv F_SETLKW +command. +If the output file is already locked, +.Nm +will block until the lock is acquired. +.It Fl n +Number the output lines, starting at 1. +.It Fl s +Squeeze multiple adjacent empty lines, causing the output to be +single spaced. +.It Fl t +Implies the +.Fl v +option, and displays tab characters as +.Ql ^I +as well. +.It Fl u +The +.Fl u +option guarantees that the output is unbuffered. +.It Fl v +Displays non-printing characters so they are visible. +Control characters print as +.Ql ^X +for control-X; the delete +character (octal 0177) prints as +.Ql ^? . +Non-ascii characters (with the high bit set) are printed as +.Ql M- +(for meta) followed by the character for the low 7 bits. +.El +.Sh EXIT STATUS +.Ex -std cat +.Sh EXAMPLES +The command: +.Bd -literal -offset indent +.Ic cat file1 +.Ed +.Pp +will print the contents of +.Ar file1 +to the standard output. +.Pp +The command: +.Bd -literal -offset indent +.Ic cat file1 file2 > file3 +.Ed +.Pp +will sequentially print the contents of +.Ar file1 +and +.Ar file2 +to the file +.Ar file3 , +truncating +.Ar file3 +if it already exists. +See the manual page for your shell (e.g., +.Xr sh 1 ) +for more information on redirection. +.Pp +The command: +.Bd -literal -offset indent +.Ic cat file1 - file2 - file3 +.Ed +.Pp +will print the contents of +.Ar file1 , +print data it receives from the standard input until it receives an +.Dv EOF +.Pq Sq ^D +character, print the contents of +.Ar file2 , +read and output contents of the standard input again, then finally output +the contents of +.Ar file3 . +Note that if the standard input referred to a file, the second dash +on the command-line would have no effect, since the entire contents of the file +would have already been read and printed by +.Nm +when it encountered the first +.Ql \&- +operand. +.Sh SEE ALSO +.Xr head 1 , +.Xr hexdump 1 , +.Xr lpr 1 , +.Xr more 1 , +.Xr pr 1 , +.Xr tac 1 , +.Xr tail 1 , +.Xr view 1 , +.Xr vis 1 , +.Xr fcntl 2 +.Rs +.%A Rob Pike +.%T "UNIX Style, or cat -v Considered Harmful" +.%J "USENIX Summer Conference Proceedings" +.%D 1983 +.Re +.Sh STANDARDS +The +.Nm +utility is expected to conform to the +.St -p1003.2-92 +specification. +.Pp +The flags +.Op Fl Bbeflnstv +are extensions to the specification. +.Sh HISTORY +A +.Nm +utility appeared in +.At v1 . +Dennis Ritchie designed and wrote the first man page. +It appears to have been +.Xr cat 1 . +.Sh BUGS +Because of the shell language mechanism used to perform output +redirection, the command +.Dq Li cat file1 file2 > file1 +will cause the original data in file1 to be destroyed! +This is performed by the shell before +.Nm +is run. diff --git a/bin/cat/cat.c b/bin/cat/cat.c new file mode 100644 index 0000000..563ba63 --- /dev/null +++ b/bin/cat/cat.c @@ -0,0 +1,321 @@ +/* $NetBSD: cat.c,v 1.57 2016/06/16 00:52:37 sevan Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kevin Fall. + * + * 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 HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +__COPYRIGHT( +"@(#) Copyright (c) 1989, 1993\ + The Regents of the University of California. All rights reserved."); +#if 0 +static char sccsid[] = "@(#)cat.c 8.2 (Berkeley) 4/27/95"; +#else +__RCSID("$NetBSD: cat.c,v 1.57 2016/06/16 00:52:37 sevan Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static int bflag, eflag, fflag, lflag, nflag, sflag, tflag, vflag; +static size_t bsize; +static int rval; +static const char *filename; + +void cook_args(char *argv[]); +void cook_buf(FILE *); +void raw_args(char *argv[]); +void raw_cat(int); + +int +main(int argc, char *argv[]) +{ + int ch; + struct flock stdout_lock; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + while ((ch = getopt(argc, argv, "B:beflnstuv")) != -1) + switch (ch) { + case 'B': + bsize = (size_t)strtol(optarg, NULL, 0); + break; + case 'b': + bflag = nflag = 1; /* -b implies -n */ + break; + case 'e': + eflag = vflag = 1; /* -e implies -v */ + break; + case 'f': + fflag = 1; + break; + case 'l': + lflag = 1; + break; + case 'n': + nflag = 1; + break; + case 's': + sflag = 1; + break; + case 't': + tflag = vflag = 1; /* -t implies -v */ + break; + case 'u': + setbuf(stdout, NULL); + break; + case 'v': + vflag = 1; + break; + default: + case '?': + (void)fprintf(stderr, + "Usage: %s [-beflnstuv] [-B bsize] [-] " + "[file ...]\n", getprogname()); + return EXIT_FAILURE; + } + argv += optind; + + if (lflag) { + stdout_lock.l_len = 0; + stdout_lock.l_start = 0; + stdout_lock.l_type = F_WRLCK; + stdout_lock.l_whence = SEEK_SET; + if (fcntl(STDOUT_FILENO, F_SETLKW, &stdout_lock) == -1) + err(EXIT_FAILURE, "stdout"); + } + + if (bflag || eflag || nflag || sflag || tflag || vflag) + cook_args(argv); + else + raw_args(argv); + if (fclose(stdout)) + err(EXIT_FAILURE, "stdout"); + return rval; +} + +void +cook_args(char **argv) +{ + FILE *fp; + + fp = stdin; + filename = "stdin"; + do { + if (*argv) { + if (!strcmp(*argv, "-")) + fp = stdin; + else if ((fp = fopen(*argv, + fflag ? "rf" : "r")) == NULL) { + warn("%s", *argv); + rval = EXIT_FAILURE; + ++argv; + continue; + } + filename = *argv++; + } + cook_buf(fp); + if (fp != stdin) + (void)fclose(fp); + else + clearerr(fp); + } while (*argv); +} + +void +cook_buf(FILE *fp) +{ + int ch, gobble, line, prev; + + line = gobble = 0; + for (prev = '\n'; (ch = getc(fp)) != EOF; prev = ch) { + if (prev == '\n') { + if (sflag) { + if (ch == '\n') { + if (gobble) + continue; + gobble = 1; + } else + gobble = 0; + } + if (nflag) { + if (!bflag || ch != '\n') { + (void)fprintf(stdout, + "%6d\t", ++line); + if (ferror(stdout)) + break; + } else if (eflag) { + (void)fprintf(stdout, + "%6s\t", ""); + if (ferror(stdout)) + break; + } + } + } + if (ch == '\n') { + if (eflag) + if (putchar('$') == EOF) + break; + } else if (ch == '\t') { + if (tflag) { + if (putchar('^') == EOF || putchar('I') == EOF) + break; + continue; + } + } else if (vflag) { + if (!isascii(ch)) { + if (putchar('M') == EOF || putchar('-') == EOF) + break; + ch = toascii(ch); + } + if (iscntrl(ch)) { + if (putchar('^') == EOF || + putchar(ch == '\177' ? '?' : + ch | 0100) == EOF) + break; + continue; + } + } + if (putchar(ch) == EOF) + break; + } + if (ferror(fp)) { + warn("%s", filename); + rval = EXIT_FAILURE; + clearerr(fp); + } + if (ferror(stdout)) + err(EXIT_FAILURE, "stdout"); +} + +void +raw_args(char **argv) +{ + int fd; + + fd = fileno(stdin); + filename = "stdin"; + do { + if (*argv) { + if (!strcmp(*argv, "-")) { + fd = fileno(stdin); + if (fd < 0) + goto skip; + } else if (fflag) { + struct stat st; + fd = open(*argv, O_RDONLY|O_NONBLOCK, 0); + if (fd < 0) + goto skip; + + if (fstat(fd, &st) == -1) { + close(fd); + goto skip; + } + if (!S_ISREG(st.st_mode)) { + close(fd); + warnx("%s: not a regular file", *argv); + goto skipnomsg; + } + } + else if ((fd = open(*argv, O_RDONLY, 0)) < 0) { +skip: + warn("%s", *argv); +skipnomsg: + rval = EXIT_FAILURE; + ++argv; + continue; + } + filename = *argv++; + } else if (fd < 0) { + err(EXIT_FAILURE, "stdin"); + } + raw_cat(fd); + if (fd != fileno(stdin)) + (void)close(fd); + } while (*argv); +} + +void +raw_cat(int rfd) +{ + static char *buf; + static char fb_buf[BUFSIZ]; + + ssize_t nr, nw, off; + int wfd; + + wfd = fileno(stdout); + if (wfd < 0) + err(EXIT_FAILURE, "stdout"); + if (buf == NULL) { + struct stat sbuf; + + if (bsize == 0) { + if (fstat(wfd, &sbuf) == 0 && sbuf.st_blksize > 0 && + (size_t)sbuf.st_blksize > sizeof(fb_buf)) + bsize = sbuf.st_blksize; + } + if (bsize > sizeof(fb_buf)) { + buf = malloc(bsize); + if (buf == NULL) + warnx("malloc, using %zu buffer", bsize); + } + if (buf == NULL) { + bsize = sizeof(fb_buf); + buf = fb_buf; + } + } + while ((nr = read(rfd, buf, bsize)) > 0) + for (off = 0; nr; nr -= nw, off += nw) + if ((nw = write(wfd, buf + off, (size_t)nr)) < 0) + err(EXIT_FAILURE, "stdout"); + if (nr < 0) { + warn("%s", filename); + rval = EXIT_FAILURE; + } +} diff --git a/bin/chgrp/chgrp.1 b/bin/chgrp/chgrp.1 new file mode 100644 index 0000000..ffdd5a6 --- /dev/null +++ b/bin/chgrp/chgrp.1 @@ -0,0 +1,164 @@ +.\" Copyright (c) 1983, 1990, 1993, 1994, 2003 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)chgrp.1 8.3 (Berkeley) 3/31/94 +.\" $NetBSD: chgrp.1,v 1.8 2017/07/04 06:52:20 wiz Exp $ +.\" +.Dd October 22, 2012 +.Dt CHGRP 1 +.Os +.Sh NAME +.Nm chgrp +.Nd change group +.Sh SYNOPSIS +.Nm +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl fhv +.Ar group +.Ar +.Nm +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl fhv +.Fl Fl reference=rfile +.Ar +.Sh DESCRIPTION +The +.Nm +utility sets the group ID of the file named by each +.Ar file +operand to the +.Ar group +ID specified by the group operand. +.Pp +Options: +.Bl -tag -width Ds +.It Fl H +If the +.Fl R +option is specified, symbolic links on the command line are followed. +(Symbolic links encountered in the tree traversal are not followed.) +.It Fl L +If the +.Fl R +option is specified, all symbolic links are followed. +.It Fl P +If the +.Fl R +option is specified, no symbolic links are followed. +.It Fl R +Change the group ID for the file hierarchies rooted +in the files instead of just the files themselves. +.It Fl f +The force option ignores errors, except for usage errors and doesn't +query about strange modes (unless the user does not have proper permissions). +.It Fl h +If +.Ar file +is a symbolic link, the group of the link is changed. +.It Fl v +Cause +.Nm +to be verbose, showing files as they are processed. +.El +.Pp +If +.Fl h +is not given, unless the +.Fl H +or +.Fl L +option is set, +.Nm +on a symbolic link always succeeds and has no effect. +The +.Fl H , +.Fl L +and +.Fl P +options are ignored unless the +.Fl R +option is specified. +In addition, these options override each other and the +command's actions are determined by the last one specified. +The default is as if the +.Fl P +option had been specified. +.Pp +The +.Ar group +operand can be either a group name from the group database, +or a numeric group ID. +Since it is valid to have a group name that is numeric (and +doesn't have the numeric ID that matches its name) the name lookup +is always done first. +Preceding the ID with a ``#'' character will force it to be taken +as a number. +.Pp +The user invoking +.Nm +must belong to the specified group and be the owner of the file, +or be the super-user. +.Pp +Unless invoked by the super-user, +.Nm +clears the set-user-id and set-group-id bits on a file to prevent +accidental or mischievous creation of set-user-id or set-group-id +programs. +.Sh FILES +.Bl -tag -width /etc/group -compact +.It Pa /etc/group +Group ID file +.El +.Sh EXIT STATUS +.Ex -std chgrp +.Sh SEE ALSO +.Xr chown 2 , +.Xr lchown 2 , +.Xr fts 3 , +.Xr group 5 , +.Xr passwd 5 , +.Xr symlink 7 , +.Xr chown 8 +.Sh STANDARDS +The +.Nm +utility is expected to be POSIX 1003.2 compatible. +.Pp +The +.Fl v +option and the use of ``#'' to force a numeric group ID +are extensions to +.St -p1003.2 . diff --git a/bin/chgrp/chown.8 b/bin/chgrp/chown.8 new file mode 100644 index 0000000..a4cd79d --- /dev/null +++ b/bin/chgrp/chown.8 @@ -0,0 +1,181 @@ +.\" Copyright (c) 1990, 1991, 1993, 1994, 2003 +.\" 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. +.\" +.\" from: @(#)chown.8 8.3 (Berkeley) 3/31/94 +.\" $NetBSD: chown.8,v 1.12 2017/07/04 06:53:12 wiz Exp $ +.\" +.Dd September 11, 2016 +.Dt CHOWN 8 +.Os +.Sh NAME +.Nm chown +.Nd change file owner and group +.Sh SYNOPSIS +.Nm +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl fhv +.Ar owner Ns Op Ar :group +.Ar +.Nm +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl fhv +.Ar :group +.Ar +.Nm +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl fhv +.Fl Fl reference=rfile +.Ar +.Sh DESCRIPTION +.Nm +sets the user ID and/or the group ID of the specified files. +Symbolic links named by arguments are silently left unchanged unless +.Fl h +is used. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl H +If the +.Fl R +option is specified, symbolic links on the command line are followed. +(Symbolic links encountered in the tree traversal are not followed.) +.It Fl L +If the +.Fl R +option is specified, all symbolic links are followed. +.It Fl P +If the +.Fl R +option is specified, no symbolic links are followed. +.It Fl R +Change the user ID and/or the group ID for the file hierarchies rooted +in the files instead of just the files themselves. +.It Fl f +Do not report any failure to change file owner or group, nor modify +the exit status to reflect such failures. +.It Fl h +If +.Ar file +is a symbolic link, the owner and/or group of the link is changed. +.It Fl v +Cause +.Nm +to be verbose, showing files as they are processed. +.El +.Pp +The +.Fl H , +.Fl L +and +.Fl P +options are ignored unless the +.Fl R +option is specified. +In addition, these options override each other and the +command's actions are determined by the last one specified. +The default is as if the +.Fl P +option had been specified. +.Pp +The +.Fl R +option cannot be used together with the +.Fl h +option. +.Pp +The +.Ar owner +and +.Ar group +operands are both optional, however, one must be specified. +If the +.Ar group +operand is specified, it must be preceded by a colon (``:'') character. +.Pp +The +.Ar owner +may be either a user name or a numeric user ID. +The +.Ar group +may be either a group name or a numeric group ID. +Since it is valid to have a user or group name that is numeric (and +does not have the numeric ID that matches its name) the name lookup +is always done first. +Preceding an ID with a ``#'' character will force it to be taken +as a number. +.Pp +The ownership of a file may only be altered by a super-user for +obvious security reasons. +.Pp +Unless invoked by the super-user, +.Nm +clears the set-user-id and set-group-id bits on a file to prevent +accidental or mischievous creation of set-user-id and set-group-id +programs. +.Sh EXIT STATUS +.Ex -std chown +.Sh COMPATIBILITY +Previous versions of the +.Nm +utility used the dot (``.'') character to distinguish the group name. +This has been changed to be a colon (``:'') character so that user and +group names may contain the dot character. +.Sh SEE ALSO +.Xr chflags 1 , +.Xr chgrp 1 , +.Xr find 1 , +.Xr chown 2 , +.Xr lchown 2 , +.Xr fts 3 , +.Xr symlink 7 +.Sh STANDARDS +The +.Nm +command is specified by POSIX.1-2017. +.Pp +The +.Fl v +and +.Fl f +options and the use of ``#'' to force a numeric lookup +are extensions to +.St -p1003.2 . +.Sh HISTORY +A +.Nm +utility appeared in +.At v1 . diff --git a/bin/chgrp/chown.c b/bin/chgrp/chown.c new file mode 100644 index 0000000..493d103 --- /dev/null +++ b/bin/chgrp/chown.c @@ -0,0 +1,284 @@ +/* $NetBSD: chown.c,v 1.8 2012/10/24 01:12:51 enami Exp $ */ + +/* + * Copyright (c) 1988, 1993, 1994, 2003 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994, 2003\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)chown.c 8.8 (Berkeley) 4/4/94"; +#else +__RCSID("$NetBSD: chown.c,v 1.8 2012/10/24 01:12:51 enami Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <locale.h> +#include <fts.h> +#include <grp.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <getopt.h> + +static void a_gid(const char *); +static void a_uid(const char *); +static id_t id(const char *, const char *); +__dead static void usage(void); + +static uid_t uid; +static gid_t gid; +static int ischown; +static const char *myname; + +int +main(int argc, char **argv) +{ + FTS *ftsp; + FTSENT *p; + int Hflag, Lflag, Rflag, ch, fflag, fts_options, hflag, rval, vflag; + char *cp, *reference; + int (*change_owner)(const char *, uid_t, gid_t); + + setprogname(*argv); + + (void)setlocale(LC_ALL, ""); + + myname = getprogname(); + ischown = (myname[2] == 'o'); + reference = NULL; + + Hflag = Lflag = Rflag = fflag = hflag = vflag = 0; + while ((ch = getopt(argc, argv, "HLPRfhv")) != -1) + switch (ch) { + case 'H': + Hflag = 1; + Lflag = 0; + break; + case 'L': + Lflag = 1; + Hflag = 0; + break; + case 'P': + Hflag = Lflag = 0; + break; + case 'R': + Rflag = 1; + break; + case 'f': + fflag = 1; + break; + case 'h': + /* + * In System V the -h option causes chown/chgrp to + * change the owner/group of the symbolic link. + * 4.4BSD's symbolic links didn't have owners/groups, + * so it was an undocumented noop. + * In NetBSD 1.3, lchown(2) is introduced. + */ + hflag = 1; + break; + case 'v': + vflag = 1; + break; + case '?': + default: + usage(); + } + argv += optind; + argc -= optind; + + if (argc == 0 || (argc == 1 && reference == NULL)) + usage(); + + fts_options = FTS_PHYSICAL; + if (Rflag) { + if (hflag) { + errx(EXIT_FAILURE, + "the -R and -h options " + "may not be specified together."); + } + if (Hflag) + fts_options |= FTS_COMFOLLOW; + if (Lflag) { + fts_options &= ~FTS_PHYSICAL; + fts_options |= FTS_LOGICAL; + } + } else if (!hflag) { + fts_options |= FTS_COMFOLLOW; + } + + uid = (uid_t)-1; + gid = (gid_t)-1; + if (ischown) { + if ((cp = strchr(*argv, ':')) != NULL) { + *cp++ = '\0'; + a_gid(cp); + } + a_uid(*argv); + } else { + a_gid(*argv); + } + argv++; + + if ((ftsp = fts_open(argv, fts_options, NULL)) == NULL) { + err(EXIT_FAILURE, "fts_open"); + } + + for (rval = EXIT_SUCCESS; (p = fts_read(ftsp)) != NULL;) { + change_owner = chown; + switch (p->fts_info) { + case FTS_D: + if (!Rflag) /* Change it at FTS_DP. */ + fts_set(ftsp, p, FTS_SKIP); + continue; + case FTS_DNR: /* Warn, chown, continue. */ + warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); + rval = EXIT_FAILURE; + break; + case FTS_ERR: /* Warn, continue. */ + case FTS_NS: + warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); + rval = EXIT_FAILURE; + continue; + case FTS_SL: /* Ignore unless -h. */ + /* + * All symlinks we found while doing a physical + * walk end up here. + */ + if (!hflag) + continue; + /* + * Note that if we follow a symlink, fts_info is + * not FTS_SL but FTS_F or whatever. And we should + * use lchown only for FTS_SL and should use chown + * for others. + */ + change_owner = lchown; + break; + case FTS_SLNONE: /* Ignore. */ + /* + * The only symlinks that end up here are ones that + * don't point to anything. Note that if we are + * doing a phisycal walk, we never reach here unless + * we asked to follow explicitly. + */ + continue; + default: + break; + } + + if ((*change_owner)(p->fts_accpath, uid, gid) && !fflag) { + warn("%s", p->fts_path); + rval = EXIT_FAILURE; + } else { + if (vflag) + printf("%s\n", p->fts_path); + } + } + if (errno) { + err(EXIT_FAILURE, "fts_read"); + } + exit(rval); + /* NOTREACHED */ +} + +static void +a_gid(const char *s) +{ + struct group *gr; + + if (*s == '\0') { /* Argument was "uid[:.]". */ + return; + } + gr = *s == '#' ? NULL : getgrnam(s); + if (gr == NULL) { + gid = id(s, "group"); + } else { + gid = gr->gr_gid; + } + return; +} + +static void +a_uid(const char *s) +{ + if (*s == '\0') { /* Argument was "[:.]gid". */ + return; + } + if (*s == '#' || uid_from_user(s, &uid) == -1) { + uid = id(s, "user"); + } + return; +} + +static id_t +id(const char *name, const char *type) +{ + id_t val; + char *ep; + + errno = 0; + if (*name == '#') { + name++; + } + val = (id_t)strtoul(name, &ep, 10); + if (errno) { + err(EXIT_FAILURE, "%s", name); + } + if (*ep != '\0') { + errx(EXIT_FAILURE, "%s: invalid %s name", name, type); + } + return (val); +} + +static void +usage(void) +{ + + (void)fprintf(stderr, + "Usage: %s [-fhv] %s file ...\n" + "\t%s -R [-H | -L | -P] [-fv] file ...\n", + myname, ischown ? "owner:group|owner|:group" : "group", + myname); + exit(EXIT_FAILURE); +} diff --git a/bin/chmod/chmod.1 b/bin/chmod/chmod.1 new file mode 100644 index 0000000..c9c1150 --- /dev/null +++ b/bin/chmod/chmod.1 @@ -0,0 +1,313 @@ +.\" $NetBSD: chmod.1,v 1.28 2017/07/04 06:47:27 wiz Exp $ +.\" +.\" Copyright (c) 1989, 1990, 1993, 1994 +.\" 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. +.\" +.\" @(#)chmod.1 8.4 (Berkeley) 3/31/94 +.\" +.Dd August 11, 2016 +.Dt CHMOD 1 +.Os +.Sh NAME +.Nm chmod +.Nd change file modes +.Sh SYNOPSIS +.Nm +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl fh +.Ar mode +.Ar +.Nm +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl fh +.Fl Fl reference=rfile +.Ar +.Sh DESCRIPTION +The +.Nm +utility modifies the file mode bits of the listed files +as specified by the +.Ar mode +operand, or +copied from a reference +.Ar rfile , +as specified with the +.Fl Fl reference +argument. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl H +If the +.Fl R +option is specified, symbolic links on the command line are followed. +(Symbolic links encountered in the tree traversal are not followed.) +.It Fl L +If the +.Fl R +option is specified, all symbolic links are followed. +.It Fl P +If the +.Fl R +option is specified, no symbolic links are followed. +.It Fl R +Change the modes of the file hierarchies rooted in the files +instead of just the files themselves. +.It Fl f +Do not display a diagnostic message or modify the exit status if +.Nm +fails to change the mode of a file. +.It Fl h +If +.Ar file +is symbolic link, the mode of the link is changed. +.El +.Pp +The +.Fl H , +.Fl L +and +.Fl P +options are ignored unless the +.Fl R +option is specified. +In addition, these options override each other and the +command's actions are determined by the last one specified. +The default is as if the +.Fl P +option had been specified. +.Pp +Only the owner of a file or the super-user is permitted to change +the mode of a file. +.Sh EXIT STATUS +.Ex -std chmod +.Sh MODES +Modes may be absolute or symbolic. +An absolute mode is an octal number constructed by +.Em or Ap ing +the following values: +.Pp +.Bl -tag -width 6n -compact -offset indent +.It Li 4000 +set-user-ID-on-execution +.It Li 2000 +set-group-ID-on-execution +.It Li 1000 +sticky bit, see +.Xr chmod 2 +.It Li 0400 +read by owner +.It Li 0200 +write by owner +.It Li 0100 +execute (or search for directories) by owner +.It Li 0070 +read, write, execute/search by group +.It Li 0007 +read, write, execute/search by others +.El +.Pp +The read, write, and execute/search values for group and others +are encoded as described for owner. +.Pp +The symbolic mode is described by the following grammar: +.Bd -literal -offset indent +mode ::= clause [, clause ...] +clause ::= [who ...] [action ...] last_action +action ::= op [perm ...] +last_action ::= op [perm ...] +who ::= a | u | g | o +op ::= + | \- | = +perm ::= r | s | t | w | x | X | u | g | o +.Ed +.Pp +The +.Ar who +symbols ``u'', ``g'', and ``o'' specify the user, group, and other parts +of the mode bits, respectively. +The +.Ar who +symbol ``a'' is equivalent to ``ugo''. +.Pp +The +.Ar perm +symbols represent the portions of the mode bits as follows: +.Pp +.Bl -tag -width Ds -compact -offset indent +.It r +The read bits. +.It s +The set-user-ID-on-execution and set-group-ID-on-execution bits. +.It t +The sticky bit. +.It w +The write bits. +.It x +The execute/search bits. +.It X +The execute/search bits if the file is a directory or any of the +execute/search bits are set in the original (unmodified) mode. +Operations with the +.Ar perm +symbol ``X'' are only meaningful in conjunction with the +.Ar op +symbol ``+'', and are ignored in all other cases. +.It u +The user permission bits in the mode of the original file. +.It g +The group permission bits in the mode of the original file. +.It o +The other permission bits in the mode of the original file. +.El +.Pp +The +.Ar op +symbols represent the operation performed, as follows: +.Bl -tag -width 4n +.It + +If no value is supplied for +.Ar perm , +the ``+'' operation has no effect. +If no value is supplied for +.Ar who , +each permission bit specified in +.Ar perm , +for which the corresponding bit in the file mode creation mask +is clear, is set. +Otherwise, the mode bits represented by the specified +.Ar who +and +.Ar perm +values are set. +.It \&\- +If no value is supplied for +.Ar perm , +the ``\-'' operation has no effect. +If no value is supplied for +.Ar who , +each permission bit specified in +.Ar perm , +for which the corresponding bit in the file mode creation mask +is clear, is cleared. +Otherwise, the mode bits represented by the specified +.Ar who +and +.Ar perm +values are cleared. +.It = +The mode bits specified by the +.Ar who +value are cleared, or, if no who value is specified, the owner, group +and other mode bits are cleared. +Then, if no value is supplied for +.Ar who , +each permission bit specified in +.Ar perm , +for which the corresponding bit in the file mode creation mask +is clear, is set. +Otherwise, the mode bits represented by the specified +.Ar who +and +.Ar perm +values are set. +.El +.Pp +Each +.Ar clause +specifies one or more operations to be performed on the mode +bits, and each operation is applied to the mode bits in the +order specified. +.Pp +Operations upon the other permissions only (specified by the symbol +``o'' by itself), in combination with the +.Ar perm +symbols ``s'' or ``t'', are ignored. +.Sh EXAMPLES +.Bl -tag -width "u=rwx,go=u-w" -compact +.It Li 644 +make a file readable by anyone and writable by the owner only. +.Pp +.It Li go-w +deny write permission to group and others. +.Pp +.It Li =rw,+X +set the read and write permissions to the usual defaults, but +retain any execute permissions that are currently set. +.Pp +.It Li +X +make a directory or file searchable/executable by everyone if it is +already searchable/executable by anyone. +.Pp +.It Li 755 +.It Li u=rwx,go=rx +.It Li u=rwx,go=u-w +make a file readable/executable by everyone and writable by the owner only. +.Pp +.It Li go= +clear all mode bits for group and others. +.Pp +.It Li g=u-w +set the group bits equal to the user bits, but clear the group write bit. +.El +.Sh SEE ALSO +.Xr chflags 1 , +.Xr install 1 , +.Xr chmod 2 , +.Xr stat 2 , +.Xr umask 2 , +.Xr fts 3 , +.Xr setmode 3 , +.Xr symlink 7 , +.Xr chown 8 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2-92 +compatible with the exception of the +.Ar perm +symbol +.Dq t +which is not included in that standard. +.Sh HISTORY +A +.Nm +utility appeared in +.At v1 . +.Sh BUGS +There's no +.Ar perm +option for the naughty bits. diff --git a/bin/chmod/chmod.c b/bin/chmod/chmod.c new file mode 100644 index 0000000..c10101e --- /dev/null +++ b/bin/chmod/chmod.c @@ -0,0 +1,239 @@ +/* $NetBSD: chmod.c,v 1.38 2012/10/22 18:00:46 christos Exp $ */ + +/* + * Copyright (c) 1989, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT( +"@(#) Copyright (c) 1989, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)chmod.c 8.8 (Berkeley) 4/1/94"; +#else +__RCSID("$NetBSD: chmod.c,v 1.38 2012/10/22 18:00:46 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <err.h> +#include <errno.h> +#include <fts.h> +#include <limits.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <getopt.h> + +__dead static void usage(void); + +struct option chmod_longopts[] = { + { "reference", required_argument, 0, + 1 }, + { NULL, 0, 0, + 0 }, +}; + +int +main(int argc, char *argv[]) +{ + FTS *ftsp; + FTSENT *p; + void *set; + mode_t mval; + int Hflag, Lflag, Rflag, ch, fflag, fts_options, hflag, rval; + char *mode, *reference; + int (*change_mode)(const char *, mode_t); + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + Hflag = Lflag = Rflag = fflag = hflag = 0; + reference = NULL; + while ((ch = getopt_long(argc, argv, "HLPRXfghorstuwx", + chmod_longopts, NULL)) != -1) + switch (ch) { + case 1: + reference = optarg; + break; + case 'H': + Hflag = 1; + Lflag = 0; + break; + case 'L': + Lflag = 1; + Hflag = 0; + break; + case 'P': + Hflag = Lflag = 0; + break; + case 'R': + Rflag = 1; + break; + case 'f': + fflag = 1; + break; + case 'h': + /* + * In System V the -h option causes chmod to + * change the mode of the symbolic link. + * 4.4BSD's symbolic links didn't have modes, + * so it was an undocumented noop. In NetBSD + * 1.3, lchmod(2) is introduced and this + * option does real work. + */ + hflag = 1; + break; + /* + * XXX + * "-[rwx]" are valid mode commands. If they are the entire + * argument, getopt has moved past them, so decrement optind. + * Regardless, we're done argument processing. + */ + case 'g': case 'o': case 'r': case 's': + case 't': case 'u': case 'w': case 'X': case 'x': + if (argv[optind - 1][0] == '-' && + argv[optind - 1][1] == ch && + argv[optind - 1][2] == '\0') + --optind; + goto done; + case '?': + default: + usage(); + } +done: argv += optind; + argc -= optind; + + if (argc == 0 || (argc == 1 && reference == NULL)) + usage(); + + fts_options = FTS_PHYSICAL; + if (Rflag) { + if (hflag) { + errx(EXIT_FAILURE, + "the -R and -h options may not be specified together."); + /* NOTREACHED */ + } + if (Hflag) + fts_options |= FTS_COMFOLLOW; + if (Lflag) { + fts_options &= ~FTS_PHYSICAL; + fts_options |= FTS_LOGICAL; + } + } else if (!hflag) + fts_options |= FTS_COMFOLLOW; + if (hflag) + change_mode = lchmod; + else + change_mode = chmod; + + if (reference == NULL) { + mode = *argv++; + if ((set = setmode(mode)) == NULL) { + err(EXIT_FAILURE, "Cannot set file mode `%s'", mode); + /* NOTREACHED */ + } + mval = 0; + } else { + struct stat st; + + if (stat(reference, &st) == -1) + err(EXIT_FAILURE, "Cannot stat `%s'", reference); + mval = st.st_mode; + set = NULL; + } + + if ((ftsp = fts_open(argv, fts_options, 0)) == NULL) { + err(EXIT_FAILURE, "fts_open"); + /* NOTREACHED */ + } + for (rval = 0; (p = fts_read(ftsp)) != NULL;) { + switch (p->fts_info) { + case FTS_D: + if (!Rflag) + (void)fts_set(ftsp, p, FTS_SKIP); + break; + case FTS_DNR: /* Warn, chmod, continue. */ + warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); + rval = 1; + break; + case FTS_DP: /* Already changed at FTS_D. */ + continue; + case FTS_ERR: /* Warn, continue. */ + case FTS_NS: + warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); + rval = 1; + continue; + case FTS_SL: /* Ignore. */ + case FTS_SLNONE: + /* + * The only symlinks that end up here are ones that + * don't point to anything and ones that we found + * doing a physical walk. + */ + if (!hflag) + continue; + /* else */ + /* FALLTHROUGH */ + default: + break; + } + if ((*change_mode)(p->fts_accpath, + set ? getmode(set, p->fts_statp->st_mode) : mval) + && !fflag) { + warn("%s", p->fts_path); + rval = 1; + } + } + if (errno) { + err(EXIT_FAILURE, "fts_read"); + /* NOTREACHED */ + } + exit(rval); + /* NOTREACHED */ +} + +static void +usage(void) +{ + (void)fprintf(stderr, + "Usage: %s [-R [-H | -L | -P]] [-fh] mode file ...\n" + "\t%s [-R [-H | -L | -P]] [-fh] --reference=rfile file ...\n", + getprogname(), getprogname()); + exit(1); + /* NOTREACHED */ +} diff --git a/bin/chown/chgrp.1 b/bin/chown/chgrp.1 new file mode 100644 index 0000000..9f289e3 --- /dev/null +++ b/bin/chown/chgrp.1 @@ -0,0 +1,169 @@ +.\" Copyright (c) 1983, 1990, 1993, 1994, 2003 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)chgrp.1 8.3 (Berkeley) 3/31/94 +.\" $NetBSD: chgrp.1,v 1.8 2017/07/04 06:52:20 wiz Exp $ +.\" +.Dd October 22, 2012 +.Dt CHGRP 1 +.Os +.Sh NAME +.Nm chgrp +.Nd change group +.Sh SYNOPSIS +.Nm +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl fhv +.Ar group +.Ar +.Nm +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl fhv +.Fl Fl reference=rfile +.Ar +.Sh DESCRIPTION +The +.Nm +utility sets the group ID of the file named by each +.Ar file +operand to the +.Ar group +ID specified by the group operand, +or to the group of the given +.Ar rfile , +specified by the +.Fl Fl reference +argument. +.Pp +Options: +.Bl -tag -width Ds +.It Fl H +If the +.Fl R +option is specified, symbolic links on the command line are followed. +(Symbolic links encountered in the tree traversal are not followed.) +.It Fl L +If the +.Fl R +option is specified, all symbolic links are followed. +.It Fl P +If the +.Fl R +option is specified, no symbolic links are followed. +.It Fl R +Change the group ID for the file hierarchies rooted +in the files instead of just the files themselves. +.It Fl f +The force option ignores errors, except for usage errors and doesn't +query about strange modes (unless the user does not have proper permissions). +.It Fl h +If +.Ar file +is a symbolic link, the group of the link is changed. +.It Fl v +Cause +.Nm +to be verbose, showing files as they are processed. +.El +.Pp +If +.Fl h +is not given, unless the +.Fl H +or +.Fl L +option is set, +.Nm +on a symbolic link always succeeds and has no effect. +The +.Fl H , +.Fl L +and +.Fl P +options are ignored unless the +.Fl R +option is specified. +In addition, these options override each other and the +command's actions are determined by the last one specified. +The default is as if the +.Fl P +option had been specified. +.Pp +The +.Ar group +operand can be either a group name from the group database, +or a numeric group ID. +Since it is valid to have a group name that is numeric (and +doesn't have the numeric ID that matches its name) the name lookup +is always done first. +Preceding the ID with a ``#'' character will force it to be taken +as a number. +.Pp +The user invoking +.Nm +must belong to the specified group and be the owner of the file, +or be the super-user. +.Pp +Unless invoked by the super-user, +.Nm +clears the set-user-id and set-group-id bits on a file to prevent +accidental or mischievous creation of set-user-id or set-group-id +programs. +.Sh FILES +.Bl -tag -width /etc/group -compact +.It Pa /etc/group +Group ID file +.El +.Sh EXIT STATUS +.Ex -std chgrp +.Sh SEE ALSO +.Xr chown 2 , +.Xr lchown 2 , +.Xr fts 3 , +.Xr group 5 , +.Xr passwd 5 , +.Xr symlink 7 , +.Xr chown 8 +.Sh STANDARDS +The +.Nm +utility is expected to be POSIX 1003.2 compatible. +.Pp +The +.Fl v +option and the use of ``#'' to force a numeric group ID +are extensions to +.St -p1003.2 . diff --git a/bin/chown/chown.8 b/bin/chown/chown.8 new file mode 100644 index 0000000..7bfac72 --- /dev/null +++ b/bin/chown/chown.8 @@ -0,0 +1,184 @@ +.\" Copyright (c) 1990, 1991, 1993, 1994, 2003 +.\" 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. +.\" +.\" from: @(#)chown.8 8.3 (Berkeley) 3/31/94 +.\" $NetBSD: chown.8,v 1.12 2017/07/04 06:53:12 wiz Exp $ +.\" +.Dd September 11, 2016 +.Dt CHOWN 8 +.Os +.Sh NAME +.Nm chown +.Nd change file owner and group +.Sh SYNOPSIS +.Nm +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl fhv +.Ar owner Ns Op Ar :group +.Ar +.Nm +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl fhv +.Ar :group +.Ar +.Nm +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl fhv +.Fl Fl reference=rfile +.Ar +.Sh DESCRIPTION +.Nm +sets the user ID and/or the group ID of the specified files. +Symbolic links named by arguments are silently left unchanged unless +.Fl h +is used. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl H +If the +.Fl R +option is specified, symbolic links on the command line are followed. +(Symbolic links encountered in the tree traversal are not followed.) +.It Fl L +If the +.Fl R +option is specified, all symbolic links are followed. +.It Fl P +If the +.Fl R +option is specified, no symbolic links are followed. +.It Fl R +Change the user ID and/or the group ID for the file hierarchies rooted +in the files instead of just the files themselves. +.It Fl f +Do not report any failure to change file owner or group, nor modify +the exit status to reflect such failures. +.It Fl h +If +.Ar file +is a symbolic link, the owner and/or group of the link is changed. +.It Fl v +Cause +.Nm +to be verbose, showing files as they are processed. +.El +.Pp +The +.Fl H , +.Fl L +and +.Fl P +options are ignored unless the +.Fl R +option is specified. +In addition, these options override each other and the +command's actions are determined by the last one specified. +The default is as if the +.Fl P +option had been specified. +.Pp +The +.Fl L +option cannot be used together with the +.Fl h +option. +.Pp +The +.Ar owner +and +.Ar group +operands are both optional, however, one must be specified; alternatively, +both the owner and group may be specified using a reference +.Ar rfile +specified using the +.Fl Fl reference +argument. +If the +.Ar group +operand is specified, it must be preceded by a colon (``:'') character. +.Pp +The +.Ar owner +may be either a user name or a numeric user ID. +The +.Ar group +may be either a group name or a numeric group ID. +Since it is valid to have a user or group name that is numeric (and +does not have the numeric ID that matches its name) the name lookup +is always done first. +Preceding an ID with a ``#'' character will force it to be taken +as a number. +.Pp +The ownership of a file may only be altered by a super-user for +obvious security reasons. +.Pp +Unless invoked by the super-user, +.Nm +clears the set-user-id and set-group-id bits on a file to prevent +accidental or mischievous creation of set-user-id and set-group-id +programs. +.Sh EXIT STATUS +.Ex -std chown +.Sh COMPATIBILITY +Previous versions of the +.Nm +utility used the dot (``.'') character to distinguish the group name. +This has been changed to be a colon (``:'') character so that user and +group names may contain the dot character. +.Sh SEE ALSO +.Xr chflags 1 , +.Xr chgrp 1 , +.Xr find 1 , +.Xr chown 2 , +.Xr lchown 2 , +.Xr fts 3 , +.Xr symlink 7 +.Sh STANDARDS +The +.Nm +command is expected to be POSIX 1003.2 compliant. +.Pp +The +.Fl v +option and the use of ``#'' to force a numeric lookup +are extensions to +.St -p1003.2 . +.Sh HISTORY +A +.Nm +utility appeared in +.At v1 . diff --git a/bin/chown/chown.c b/bin/chown/chown.c new file mode 100644 index 0000000..ee46eee --- /dev/null +++ b/bin/chown/chown.c @@ -0,0 +1,302 @@ +/* $NetBSD: chown.c,v 1.8 2012/10/24 01:12:51 enami Exp $ */ + +/* + * Copyright (c) 1988, 1993, 1994, 2003 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994, 2003\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)chown.c 8.8 (Berkeley) 4/4/94"; +#else +__RCSID("$NetBSD: chown.c,v 1.8 2012/10/24 01:12:51 enami Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <locale.h> +#include <fts.h> +#include <grp.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <getopt.h> + +static void a_gid(const char *); +static void a_uid(const char *); +static id_t id(const char *, const char *); +__dead static void usage(void); + +static uid_t uid; +static gid_t gid; +static int ischown; +static const char *myname; + +struct option chown_longopts[] = { + { "reference", required_argument, 0, + 1 }, + { NULL, 0, 0, + 0 }, +}; + +int +main(int argc, char **argv) +{ + FTS *ftsp; + FTSENT *p; + int Hflag, Lflag, Rflag, ch, fflag, fts_options, hflag, rval, vflag; + char *cp, *reference; + int (*change_owner)(const char *, uid_t, gid_t); + + setprogname(*argv); + + (void)setlocale(LC_ALL, ""); + + myname = getprogname(); + ischown = (myname[2] == 'o'); + reference = NULL; + + Hflag = Lflag = Rflag = fflag = hflag = vflag = 0; + while ((ch = getopt_long(argc, argv, "HLPRfhv", + chown_longopts, NULL)) != -1) + switch (ch) { + case 1: + reference = optarg; + break; + case 'H': + Hflag = 1; + Lflag = 0; + break; + case 'L': + Lflag = 1; + Hflag = 0; + break; + case 'P': + Hflag = Lflag = 0; + break; + case 'R': + Rflag = 1; + break; + case 'f': + fflag = 1; + break; + case 'h': + /* + * In System V the -h option causes chown/chgrp to + * change the owner/group of the symbolic link. + * 4.4BSD's symbolic links didn't have owners/groups, + * so it was an undocumented noop. + * In NetBSD 1.3, lchown(2) is introduced. + */ + hflag = 1; + break; + case 'v': + vflag = 1; + break; + case '?': + default: + usage(); + } + argv += optind; + argc -= optind; + + if (argc == 0 || (argc == 1 && reference == NULL)) + usage(); + + fts_options = FTS_PHYSICAL; + if (Rflag) { + if (Hflag) + fts_options |= FTS_COMFOLLOW; + if (Lflag) { + if (hflag) + errx(EXIT_FAILURE, + "the -L and -h options " + "may not be specified together."); + fts_options &= ~FTS_PHYSICAL; + fts_options |= FTS_LOGICAL; + } + } else if (!hflag) + fts_options |= FTS_COMFOLLOW; + + uid = (uid_t)-1; + gid = (gid_t)-1; + if (reference == NULL) { + if (ischown) { + if ((cp = strchr(*argv, ':')) != NULL) { + *cp++ = '\0'; + a_gid(cp); + } +#ifdef SUPPORT_DOT + else if ((cp = strrchr(*argv, '.')) != NULL) { + if (uid_from_user(*argv, &uid) == -1) { + *cp++ = '\0'; + a_gid(cp); + } + } +#endif + a_uid(*argv); + } else + a_gid(*argv); + argv++; + } else { + struct stat st; + + if (stat(reference, &st) == -1) + err(EXIT_FAILURE, "Cannot stat `%s'", reference); + if (ischown) + uid = st.st_uid; + gid = st.st_gid; + } + + if ((ftsp = fts_open(argv, fts_options, NULL)) == NULL) + err(EXIT_FAILURE, "fts_open"); + + for (rval = EXIT_SUCCESS; (p = fts_read(ftsp)) != NULL;) { + change_owner = chown; + switch (p->fts_info) { + case FTS_D: + if (!Rflag) /* Change it at FTS_DP. */ + fts_set(ftsp, p, FTS_SKIP); + continue; + case FTS_DNR: /* Warn, chown, continue. */ + warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); + rval = EXIT_FAILURE; + break; + case FTS_ERR: /* Warn, continue. */ + case FTS_NS: + warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); + rval = EXIT_FAILURE; + continue; + case FTS_SL: /* Ignore unless -h. */ + /* + * All symlinks we found while doing a physical + * walk end up here. + */ + if (!hflag) + continue; + /* + * Note that if we follow a symlink, fts_info is + * not FTS_SL but FTS_F or whatever. And we should + * use lchown only for FTS_SL and should use chown + * for others. + */ + change_owner = lchown; + break; + case FTS_SLNONE: /* Ignore. */ + /* + * The only symlinks that end up here are ones that + * don't point to anything. Note that if we are + * doing a phisycal walk, we never reach here unless + * we asked to follow explicitly. + */ + continue; + default: + break; + } + + if ((*change_owner)(p->fts_accpath, uid, gid) && !fflag) { + warn("%s", p->fts_path); + rval = EXIT_FAILURE; + } else { + if (vflag) + printf("%s\n", p->fts_path); + } + } + if (errno) + err(EXIT_FAILURE, "fts_read"); + exit(rval); + /* NOTREACHED */ +} + +static void +a_gid(const char *s) +{ + struct group *gr; + + if (*s == '\0') /* Argument was "uid[:.]". */ + return; + gr = *s == '#' ? NULL : getgrnam(s); + if (gr == NULL) + gid = id(s, "group"); + else + gid = gr->gr_gid; + return; +} + +static void +a_uid(const char *s) +{ + if (*s == '\0') /* Argument was "[:.]gid". */ + return; + if (*s == '#' || uid_from_user(s, &uid) == -1) { + uid = id(s, "user"); + } + return; +} + +static id_t +id(const char *name, const char *type) +{ + id_t val; + char *ep; + + errno = 0; + if (*name == '#') + name++; + val = (id_t)strtoul(name, &ep, 10); + if (errno) + err(EXIT_FAILURE, "%s", name); + if (*ep != '\0') + errx(EXIT_FAILURE, "%s: invalid %s name", name, type); + return (val); +} + +static void +usage(void) +{ + + (void)fprintf(stderr, + "Usage: %s [-R [-H | -L | -P]] [-fhv] %s file ...\n" + "\t%s [-R [-H | -L | -P]] [-fhv] --reference=rfile file ...\n", + myname, ischown ? "owner:group|owner|:group" : "group", + myname); + exit(EXIT_FAILURE); +} diff --git a/bin/cp/cp.1 b/bin/cp/cp.1 new file mode 100644 index 0000000..d038da4 --- /dev/null +++ b/bin/cp/cp.1 @@ -0,0 +1,263 @@ +.\" $NetBSD: cp.1,v 1.46 2018/12/23 01:29:23 gutteridge Exp $ +.\" +.\" Copyright (c) 1989, 1990, 1993, 1994 +.\" 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. +.\" +.\" @(#)cp.1 8.3 (Berkeley) 4/18/94 +.\" +.Dd December 22, 2018 +.Dt CP 1 +.Os +.Sh NAME +.Nm cp +.Nd copy files +.Sh SYNOPSIS +.Nm +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl f | i +.Op Fl alNpv +.Ar source_file target_file +.Nm cp +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl f | i +.Op Fl alNpv +.Ar source_file ... target_directory +.Sh DESCRIPTION +In the first synopsis form, the +.Nm +utility copies the contents of the +.Ar source_file +to the +.Ar target_file . +In the second synopsis form, +the contents of each named +.Ar source_file +is copied to the destination +.Ar target_directory . +The names of the files themselves are not changed. +If +.Nm +detects an attempt to copy a file to itself, the copy will fail. +.Pp +The following options are available: +.Bl -tag -width flag +.It Fl a +Archive mode. +Same as +.Fl RpP . +.It Fl f +For each existing destination pathname, attempt to overwrite it. +If permissions do not allow copy to succeed, remove it and create a new +file, without prompting for confirmation. +(The +.Fl i +option is ignored if the +.Fl f +option is specified.) +.It Fl H +If the +.Fl R +option is specified, symbolic links on the command line are followed. +(Symbolic links encountered in the tree traversal are not followed.) +.It Fl i +Causes +.Nm +to write a prompt to the standard error output before copying a file +that would overwrite an existing file. +If the response from the standard input begins with the character +.Sq Li y , +the file copy is attempted. +.It Fl L +If the +.Fl R +option is specified, all symbolic links are followed. +.It Fl l +Create hard links to regular files in a hierarchy instead of copying. +.It Fl N +When used with +.Fl p , +don't copy file flags. +.It Fl P +No symbolic links are followed. +This is the default. +.It Fl p +Causes +.Nm +to preserve in the copy as many of the modification time, access time, +file flags, file mode, user ID, group ID, and extended attributes, +as allowed by permissions. +.Pp +If the user ID and group ID cannot be preserved due to insufficient +permissions, no error message is displayed and the exit value is not +altered. +.Pp +If the source file has its set user ID bit on and the user ID cannot +be preserved, the set user ID bit is not preserved +in the copy's permissions. +If the source file has its set group ID bit on and the group ID cannot +be preserved, the set group ID bit is not preserved +in the copy's permissions. +If the source file has both its set user ID and set group ID bits on, +and either the user ID or group ID cannot be preserved, neither +the set user ID or set group ID bits are preserved in the copy's +permissions. +.Pp +Extended attributes from all accessible namespaces are copied; +others are ignored. +If an error occurs during this copy, a message is displayed and +.Nm +skips the other extended attributes for that file. +.It Fl R +If +.Ar source_file +designates a directory, +.Nm +copies the directory and the entire subtree connected at that point. +This option also causes symbolic links to be copied, rather than +followed, and for +.Nm +to create special files rather than copying them as normal files. +Created directories have the same mode as the corresponding source +directory, unmodified by the process's umask. +.Pp +Note that +.Nm +copies hard linked files as separate files. +If you need to preserve hard links, consider using a utility like +.Xr pax 1 +instead. +.It Fl v +Causes +.Nm +to be verbose, showing files as they are copied. +.El +.Pp +For each destination file that already exists, its contents are +overwritten if permissions allow, but its mode, user ID, and group +ID are unchanged. +.Pp +In the second synopsis form, +.Ar target_directory +must exist unless there is only one named +.Ar source_file +which is a directory and the +.Fl R +flag is specified. +.Pp +If the destination file does not exist, the mode of the source file is +used as modified by the file mode creation mask +.Ic ( umask , +see +.Xr csh 1 ) . +If the source file has its set user ID bit on, that bit is removed +unless both the source file and the destination file are owned by the +same user. +If the source file has its set group ID bit on, that bit is removed +unless both the source file and the destination file are in the same +group and the user is a member of that group. +If both the set user ID and set group ID bits are set, all of the above +conditions must be fulfilled or both bits are removed. +.Pp +Appropriate permissions are required for file creation or overwriting. +.Pp +Symbolic links are always followed unless the +.Fl R +flag is set, in which case symbolic links are not followed, by default. +The +.Fl H +or +.Fl L +flags (in conjunction with the +.Fl R +flag), as well as the +.Fl P +flag cause symbolic links to be followed as described above. +The +.Fl H +and +.Fl L +options are ignored unless the +.Fl R +option is specified. +In addition, these options override each other and the +command's actions are determined by the last one specified. +The default is as if the +.Fl P +option had been specified. +.Sh EXIT STATUS +.Ex -std cp +.Sh COMPATIBILITY +Historic versions of the +.Nm +utility had a +.Fl r +option. +This implementation supports that option, however, its use is strongly +discouraged, as it does not correctly copy special files, symbolic links, +or FIFOs. +.Sh SEE ALSO +.Xr mv 1 , +.Xr pax 1 , +.Xr rcp 1 , +.Xr umask 2 , +.Xr fts 3 , +.Xr symlink 7 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. +.Pp +The +.Fl a +and +.Fl l +flags are non-standard extensions. +They are intended to be compatible with the same options which +other implementations, namely GNU coreutils and +.Fx , +of this utility have. +.Pp +The +.Fl v +option is an extension to +.St -p1003.2 . +.Sh HISTORY +A +.Nm +utility appeared in +.At v1 . diff --git a/bin/cp/cp.c b/bin/cp/cp.c new file mode 100644 index 0000000..2f4fab1 --- /dev/null +++ b/bin/cp/cp.c @@ -0,0 +1,548 @@ +/* $NetBSD: cp.c,v 1.59 2016/03/05 19:48:55 uwe Exp $ */ + +/* + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * David Hitz of Auspex Systems 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT( +"@(#) Copyright (c) 1988, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)cp.c 8.5 (Berkeley) 4/29/95"; +#else +__RCSID("$NetBSD: cp.c,v 1.59 2016/03/05 19:48:55 uwe Exp $"); +#endif +#endif /* not lint */ + +/* + * Cp copies source files to target files. + * + * The global PATH_T structure "to" always contains the path to the + * current target file. Since fts(3) does not change directories, + * this path can be either absolute or dot-relative. + * + * The basic algorithm is to initialize "to" and use fts(3) to traverse + * the file hierarchy rooted in the argument list. A trivial case is the + * case of 'cp file1 file2'. The more interesting case is the case of + * 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the + * path (relative to the root of the traversal) is appended to dir (stored + * in "to") to form the final target path. + */ + +#include <sys/param.h> +#include <sys/stat.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fts.h> +#include <locale.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "extern.h" + +#define STRIP_TRAILING_SLASH(p) { \ + while ((p).p_end > (p).p_path + 1 && (p).p_end[-1] == '/') \ + *--(p).p_end = '\0'; \ +} + +static char empty[] = ""; +PATH_T to = { .p_end = to.p_path, .target_end = empty }; + +uid_t myuid; +int Hflag, Lflag, Rflag, Pflag, fflag, iflag, lflag, pflag, rflag, vflag, Nflag; +mode_t myumask; +sig_atomic_t pinfo; + +enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE }; + +static int copy(char *[], enum op, int); + +static void +progress(int sig __unused) +{ + + pinfo++; +} + +int +main(int argc, char *argv[]) +{ + struct stat to_stat, tmp_stat; + enum op type; + int ch, fts_options, r, have_trailing_slash; + char *target, **src; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + Hflag = Lflag = Pflag = Rflag = 0; + while ((ch = getopt(argc, argv, "HLNPRfailprv")) != -1) + switch (ch) { + case 'H': + Hflag = 1; + Lflag = Pflag = 0; + break; + case 'L': + Lflag = 1; + Hflag = Pflag = 0; + break; + case 'N': + Nflag = 1; + break; + case 'P': + Pflag = 1; + Hflag = Lflag = 0; + break; + case 'R': + Rflag = 1; + break; + case 'a': + Pflag = 1; + pflag = 1; + Rflag = 1; + Hflag = Lflag = 0; + break; + case 'f': + fflag = 1; + iflag = 0; + break; + case 'i': + iflag = 1; + fflag = 0; + break; + case 'l': + lflag = 1; + break; + case 'p': + pflag = 1; + break; + case 'r': + rflag = 1; + break; + case 'v': + vflag = 1; + break; + case '?': + default: + usage(); + /* NOTREACHED */ + } + argc -= optind; + argv += optind; + + if (argc < 2) + usage(); + + fts_options = FTS_NOCHDIR | FTS_PHYSICAL; + if (rflag) { + if (Rflag) { + errx(EXIT_FAILURE, + "the -R and -r options may not be specified together."); + /* NOTREACHED */ + } + if (Hflag || Lflag || Pflag) { + errx(EXIT_FAILURE, + "the -H, -L, and -P options may not be specified with the -r option."); + /* NOTREACHED */ + } + fts_options &= ~FTS_PHYSICAL; + fts_options |= FTS_LOGICAL; + } + + if (Rflag) { + if (Hflag) + fts_options |= FTS_COMFOLLOW; + if (Lflag) { + fts_options &= ~FTS_PHYSICAL; + fts_options |= FTS_LOGICAL; + } + } else if (!Pflag) { + fts_options &= ~FTS_PHYSICAL; + fts_options |= FTS_LOGICAL | FTS_COMFOLLOW; + } + + myuid = getuid(); + + /* Copy the umask for explicit mode setting. */ + myumask = umask(0); + (void)umask(myumask); + + /* Save the target base in "to". */ + target = argv[--argc]; + if (strlcpy(to.p_path, target, sizeof(to.p_path)) >= sizeof(to.p_path)) + errx(EXIT_FAILURE, "%s: name too long", target); + to.p_end = to.p_path + strlen(to.p_path); + have_trailing_slash = (to.p_end[-1] == '/'); + if (have_trailing_slash) + STRIP_TRAILING_SLASH(to); + to.target_end = to.p_end; + + /* Set end of argument list for fts(3). */ + argv[argc] = NULL; + + (void)signal(SIGINFO, progress); + + /* + * Cp has two distinct cases: + * + * cp [-R] source target + * cp [-R] source1 ... sourceN directory + * + * In both cases, source can be either a file or a directory. + * + * In (1), the target becomes a copy of the source. That is, if the + * source is a file, the target will be a file, and likewise for + * directories. + * + * In (2), the real target is not directory, but "directory/source". + */ + if (Pflag) + r = lstat(to.p_path, &to_stat); + else + r = stat(to.p_path, &to_stat); + if (r == -1 && errno != ENOENT) { + err(EXIT_FAILURE, "%s", to.p_path); + /* NOTREACHED */ + } + if (r == -1 || !S_ISDIR(to_stat.st_mode)) { + /* + * Case (1). Target is not a directory. + */ + if (argc > 1) + usage(); + /* + * Need to detect the case: + * cp -R dir foo + * Where dir is a directory and foo does not exist, where + * we want pathname concatenations turned on but not for + * the initial mkdir(). + */ + if (r == -1) { + if (rflag || (Rflag && (Lflag || Hflag))) + r = stat(*argv, &tmp_stat); + else + r = lstat(*argv, &tmp_stat); + if (r == -1) { + err(EXIT_FAILURE, "%s", *argv); + /* NOTREACHED */ + } + + if (S_ISDIR(tmp_stat.st_mode) && (Rflag || rflag)) + type = DIR_TO_DNE; + else + type = FILE_TO_FILE; + } else + type = FILE_TO_FILE; + + if (have_trailing_slash && type == FILE_TO_FILE) { + if (r == -1) + errx(1, "directory %s does not exist", + to.p_path); + else + errx(1, "%s is not a directory", to.p_path); + } + } else { + /* + * Case (2). Target is a directory. + */ + type = FILE_TO_DIR; + } + + /* + * make "cp -rp src/ dst" behave like "cp -rp src dst" not + * like "cp -rp src/. dst" + */ + for (src = argv; *src; src++) { + size_t len = strlen(*src); + while (len-- > 1 && (*src)[len] == '/') + (*src)[len] = '\0'; + } + + exit(copy(argv, type, fts_options)); + /* NOTREACHED */ +} + +static int dnestack[MAXPATHLEN]; /* unlikely we'll have more nested dirs */ +static ssize_t dnesp; +static void +pushdne(int dne) +{ + + dnestack[dnesp++] = dne; + assert(dnesp < MAXPATHLEN); +} + +static int +popdne(void) +{ + int rv; + + rv = dnestack[--dnesp]; + assert(dnesp >= 0); + return rv; +} + +static int +copy(char *argv[], enum op type, int fts_options) +{ + struct stat to_stat; + FTS *ftsp; + FTSENT *curr; + int base, dne, sval; + int this_failed, any_failed; + size_t nlen; + char *p, *target_mid; + + base = 0; /* XXX gcc -Wuninitialized (see comment below) */ + + if ((ftsp = fts_open(argv, fts_options, NULL)) == NULL) + err(EXIT_FAILURE, "%s", argv[0]); + /* NOTREACHED */ + for (any_failed = 0; (curr = fts_read(ftsp)) != NULL;) { + this_failed = 0; + switch (curr->fts_info) { + case FTS_NS: + case FTS_DNR: + case FTS_ERR: + warnx("%s: %s", curr->fts_path, + strerror(curr->fts_errno)); + this_failed = any_failed = 1; + continue; + case FTS_DC: /* Warn, continue. */ + warnx("%s: directory causes a cycle", curr->fts_path); + this_failed = any_failed = 1; + continue; + } + + /* + * If we are in case (2) or (3) above, we need to append the + * source name to the target name. + */ + if (type != FILE_TO_FILE) { + if ((curr->fts_namelen + + to.target_end - to.p_path + 1) > MAXPATHLEN) { + warnx("%s/%s: name too long (not copied)", + to.p_path, curr->fts_name); + this_failed = any_failed = 1; + continue; + } + + /* + * Need to remember the roots of traversals to create + * correct pathnames. If there's a directory being + * copied to a non-existent directory, e.g. + * cp -R a/dir noexist + * the resulting path name should be noexist/foo, not + * noexist/dir/foo (where foo is a file in dir), which + * is the case where the target exists. + * + * Also, check for "..". This is for correct path + * concatentation for paths ending in "..", e.g. + * cp -R .. /tmp + * Paths ending in ".." are changed to ".". This is + * tricky, but seems the easiest way to fix the problem. + * + * XXX + * Since the first level MUST be FTS_ROOTLEVEL, base + * is always initialized. + */ + if (curr->fts_level == FTS_ROOTLEVEL) { + if (type != DIR_TO_DNE) { + p = strrchr(curr->fts_path, '/'); + base = (p == NULL) ? 0 : + (int)(p - curr->fts_path + 1); + + if (!strcmp(&curr->fts_path[base], + "..")) + base += 1; + } else + base = curr->fts_pathlen; + } + + p = &curr->fts_path[base]; + nlen = curr->fts_pathlen - base; + target_mid = to.target_end; + if (*p != '/' && target_mid[-1] != '/') + *target_mid++ = '/'; + *target_mid = 0; + + if (target_mid - to.p_path + nlen >= PATH_MAX) { + warnx("%s%s: name too long (not copied)", + to.p_path, p); + this_failed = any_failed = 1; + continue; + } + (void)strncat(target_mid, p, nlen); + to.p_end = target_mid + nlen; + *to.p_end = 0; + STRIP_TRAILING_SLASH(to); + } + + sval = Pflag ? lstat(to.p_path, &to_stat) : stat(to.p_path, &to_stat); + /* Not an error but need to remember it happened */ + if (sval == -1) + dne = 1; + else { + if (to_stat.st_dev == curr->fts_statp->st_dev && + to_stat.st_ino == curr->fts_statp->st_ino) { + warnx("%s and %s are identical (not copied).", + to.p_path, curr->fts_path); + this_failed = any_failed = 1; + if (S_ISDIR(curr->fts_statp->st_mode)) + (void)fts_set(ftsp, curr, FTS_SKIP); + continue; + } + if (!S_ISDIR(curr->fts_statp->st_mode) && + S_ISDIR(to_stat.st_mode)) { + warnx("cannot overwrite directory %s with non-directory %s", + to.p_path, curr->fts_path); + this_failed = any_failed = 1; + continue; + } + dne = 0; + } + + switch (curr->fts_statp->st_mode & S_IFMT) { + case S_IFLNK: + /* Catch special case of a non dangling symlink */ + if((fts_options & FTS_LOGICAL) || + ((fts_options & FTS_COMFOLLOW) && curr->fts_level == 0)) { + if (copy_file(curr, dne)) + this_failed = any_failed = 1; + } else { + if (copy_link(curr, !dne)) + this_failed = any_failed = 1; + } + break; + case S_IFDIR: + if (!Rflag && !rflag) { + if (curr->fts_info == FTS_D) + warnx("%s is a directory (not copied).", + curr->fts_path); + (void)fts_set(ftsp, curr, FTS_SKIP); + this_failed = any_failed = 1; + break; + } + + /* + * Directories get noticed twice: + * In the first pass, create it if needed. + * In the second pass, after the children have been copied, set the permissions. + */ + if (curr->fts_info == FTS_D) /* First pass */ + { + /* + * If the directory doesn't exist, create the new + * one with the from file mode plus owner RWX bits, + * modified by the umask. Trade-off between being + * able to write the directory (if from directory is + * 555) and not causing a permissions race. If the + * umask blocks owner writes, we fail.. + */ + pushdne(dne); + if (dne) { + if (mkdir(to.p_path, + curr->fts_statp->st_mode | S_IRWXU) < 0) + err(EXIT_FAILURE, "%s", + to.p_path); + /* NOTREACHED */ + } else if (!S_ISDIR(to_stat.st_mode)) { + errno = ENOTDIR; + err(EXIT_FAILURE, "%s", + to.p_path); + /* NOTREACHED */ + } + } + else if (curr->fts_info == FTS_DP) /* Second pass */ + { + /* + * If not -p and directory didn't exist, set it to be + * the same as the from directory, umodified by the + * umask; arguably wrong, but it's been that way + * forever. + */ + if (pflag && setfile(curr->fts_statp, 0)) + this_failed = any_failed = 1; + else if ((dne = popdne())) + (void)chmod(to.p_path, + curr->fts_statp->st_mode); + } + else + { + warnx("directory %s encountered when not expected.", + curr->fts_path); + this_failed = any_failed = 1; + break; + } + + break; + case S_IFBLK: + case S_IFCHR: + if (Rflag) { + if (copy_special(curr->fts_statp, !dne)) + this_failed = any_failed = 1; + } else + if (copy_file(curr, dne)) + this_failed = any_failed = 1; + break; + case S_IFIFO: + if (Rflag) { + if (copy_fifo(curr->fts_statp, !dne)) + this_failed = any_failed = 1; + } else + if (copy_file(curr, dne)) + this_failed = any_failed = 1; + break; + default: + if (copy_file(curr, dne)) + this_failed = any_failed = 1; + break; + } + if (vflag && !this_failed) + (void)printf("%s -> %s\n", curr->fts_path, to.p_path); + } + if (errno) { + err(EXIT_FAILURE, "fts_read"); + /* NOTREACHED */ + } + (void)fts_close(ftsp); + return (any_failed); +} diff --git a/bin/cp/extern.h b/bin/cp/extern.h new file mode 100644 index 0000000..e393844 --- /dev/null +++ b/bin/cp/extern.h @@ -0,0 +1,61 @@ +/* $NetBSD: extern.h,v 1.17 2012/01/04 15:58:37 christos Exp $ */ + +/*- + * Copyright (c) 1991, 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. + * + * @(#)extern.h 8.2 (Berkeley) 4/1/94 + */ + +#ifndef _EXTERN_H_ +#define _EXTERN_H_ + +typedef struct { + char *p_end; /* pointer to NULL at end of path */ + char *target_end; /* pointer to end of target base */ + char p_path[MAXPATHLEN + 1]; /* pointer to the start of a path */ +} PATH_T; + +extern PATH_T to; +extern uid_t myuid; +extern int Rflag, rflag, Hflag, Lflag, Pflag, fflag, iflag, lflag, pflag, Nflag; +extern mode_t myumask; +extern sig_atomic_t pinfo; + +#include <sys/cdefs.h> + +__BEGIN_DECLS +int copy_fifo(struct stat *, int); +int copy_file(FTSENT *, int); +int copy_link(FTSENT *, int); +int copy_special(struct stat *, int); +int set_utimes(const char *, struct stat *); +int setfile(struct stat *, int); +void usage(void) __attribute__((__noreturn__)); +__END_DECLS + +#endif /* !_EXTERN_H_ */ diff --git a/bin/cp/utils.c b/bin/cp/utils.c new file mode 100644 index 0000000..6275fc6 --- /dev/null +++ b/bin/cp/utils.c @@ -0,0 +1,419 @@ +/* $NetBSD: utils.c,v 1.46 2018/07/17 13:04:58 darcy Exp $ */ + +/*- + * Copyright (c) 1991, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)utils.c 8.3 (Berkeley) 4/1/94"; +#else +__RCSID("$NetBSD: utils.c,v 1.46 2018/07/17 13:04:58 darcy Exp $"); +#endif +#endif /* not lint */ + +#include <sys/mman.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/extattr.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <fts.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "extern.h" + +#define MMAP_MAX_SIZE (8 * 1048576) +#define MMAP_MAX_WRITE (64 * 1024) + +int +set_utimes(const char *file, struct stat *fs) +{ + struct timespec ts[2]; + + ts[0] = fs->st_atimespec; + ts[1] = fs->st_mtimespec; + + if (lutimens(file, ts)) { + warn("lutimens: %s", file); + return (1); + } + return (0); +} + +struct finfo { + const char *from; + const char *to; + off_t size; +}; + +static void +progress(const struct finfo *fi, off_t written) +{ + int pcent = (int)((100.0 * written) / fi->size); + + pinfo = 0; + (void)fprintf(stderr, "%s => %s %llu/%llu bytes %d%% written\n", + fi->from, fi->to, (unsigned long long)written, + (unsigned long long)fi->size, pcent); +} + +int +copy_file(FTSENT *entp, int dne) +{ + static char buf[MAXBSIZE]; + struct stat to_stat, *fs; + int ch, checkch, from_fd, rcount, rval, to_fd, tolnk, wcount; + char *p; + off_t ptotal = 0; + + /* if hard linking then simply link and return */ + if (lflag) { + (void)unlink(to.p_path); + if (link(entp->fts_path, to.p_path)) { + warn("%s", to.p_path); + return (1); + } + return (0); + } + + if ((from_fd = open(entp->fts_path, O_RDONLY, 0)) == -1) { + warn("%s", entp->fts_path); + return (1); + } + + to_fd = -1; + fs = entp->fts_statp; + tolnk = ((Rflag && !(Lflag || Hflag)) || Pflag); + + /* + * If the file exists and we're interactive, verify with the user. + * If the file DNE, set the mode to be the from file, minus setuid + * bits, modified by the umask; arguably wrong, but it makes copying + * executables work right and it's been that way forever. (The + * other choice is 666 or'ed with the execute bits on the from file + * modified by the umask.) + */ + if (!dne) { + struct stat sb; + int sval; + + if (iflag) { + (void)fprintf(stderr, "overwrite %s? ", to.p_path); + checkch = ch = getchar(); + while (ch != '\n' && ch != EOF) + ch = getchar(); + if (checkch != 'y' && checkch != 'Y') { + (void)close(from_fd); + return (0); + } + } + + sval = tolnk ? + lstat(to.p_path, &sb) : stat(to.p_path, &sb); + if (sval == -1) { + warn("stat: %s", to.p_path); + (void)close(from_fd); + return (1); + } + + if (!(tolnk && S_ISLNK(sb.st_mode))) + to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0); + } else + to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT, + fs->st_mode & ~(S_ISUID | S_ISGID)); + + if (to_fd == -1 && (fflag || tolnk)) { + /* + * attempt to remove existing destination file name and + * create a new file + */ + (void)unlink(to.p_path); + to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT, + fs->st_mode & ~(S_ISUID | S_ISGID)); + } + + if (to_fd == -1) { + warn("%s", to.p_path); + (void)close(from_fd); + return (1); + } + + rval = 0; + + /* + * There's no reason to do anything other than close the file + * now if it's empty, so let's not bother. + */ + if (fs->st_size > 0) { + struct finfo fi; + + fi.from = entp->fts_path; + fi.to = to.p_path; + fi.size = fs->st_size; + + /* + * Mmap and write if less than 8M (the limit is so + * we don't totally trash memory on big files). + * This is really a minor hack, but it wins some CPU back. + */ + bool use_read; + + use_read = true; + if (fs->st_size <= MMAP_MAX_SIZE) { + size_t fsize = (size_t)fs->st_size; + p = mmap(NULL, fsize, PROT_READ, MAP_FILE|MAP_SHARED, + from_fd, (off_t)0); + if (p != MAP_FAILED) { + size_t remainder; + + use_read = false; + + (void) madvise(p, (size_t)fs->st_size, + MADV_SEQUENTIAL); + + /* + * Write out the data in small chunks to + * avoid locking the output file for a + * long time if the reading the data from + * the source is slow. + */ + remainder = fsize; + do { + ssize_t chunk; + + chunk = (remainder > MMAP_MAX_WRITE) ? + MMAP_MAX_WRITE : remainder; + if (write(to_fd, &p[fsize - remainder], + chunk) != chunk) { + warn("%s", to.p_path); + rval = 1; + break; + } + remainder -= chunk; + ptotal += chunk; + if (pinfo) + progress(&fi, ptotal); + } while (remainder > 0); + + if (munmap(p, fsize) < 0) { + warn("%s", entp->fts_path); + rval = 1; + } + } + } + + if (use_read) { + while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) { + wcount = write(to_fd, buf, (size_t)rcount); + if (rcount != wcount || wcount == -1) { + warn("%s", to.p_path); + rval = 1; + break; + } + ptotal += wcount; + if (pinfo) + progress(&fi, ptotal); + } + if (rcount < 0) { + warn("%s", entp->fts_path); + rval = 1; + } + } + } + + if (pflag && (fcpxattr(from_fd, to_fd) != 0)) + warn("%s: error copying extended attributes", to.p_path); + + (void)close(from_fd); + + if (rval == 1) { + (void)close(to_fd); + return (1); + } + + if (pflag && setfile(fs, to_fd)) + rval = 1; + /* + * If the source was setuid or setgid, lose the bits unless the + * copy is owned by the same user and group. + */ +#define RETAINBITS \ + (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO) + if (!pflag && dne + && fs->st_mode & (S_ISUID | S_ISGID) && fs->st_uid == myuid) { + if (fstat(to_fd, &to_stat)) { + warn("%s", to.p_path); + rval = 1; + } else if (fs->st_gid == to_stat.st_gid && + fchmod(to_fd, fs->st_mode & RETAINBITS & ~myumask)) { + warn("%s", to.p_path); + rval = 1; + } + } + if (close(to_fd)) { + warn("%s", to.p_path); + rval = 1; + } + /* set the mod/access times now after close of the fd */ + if (pflag && set_utimes(to.p_path, fs)) { + rval = 1; + } + return (rval); +} + +int +copy_link(FTSENT *p, int exists) +{ + int len; + char target[MAXPATHLEN]; + + if ((len = readlink(p->fts_path, target, sizeof(target)-1)) == -1) { + warn("readlink: %s", p->fts_path); + return (1); + } + target[len] = '\0'; + if (exists && unlink(to.p_path)) { + warn("unlink: %s", to.p_path); + return (1); + } + if (symlink(target, to.p_path)) { + warn("symlink: %s", target); + return (1); + } + return (pflag ? setfile(p->fts_statp, 0) : 0); +} + +int +copy_fifo(struct stat *from_stat, int exists) +{ + if (exists && unlink(to.p_path)) { + warn("unlink: %s", to.p_path); + return (1); + } + if (mkfifo(to.p_path, from_stat->st_mode)) { + warn("mkfifo: %s", to.p_path); + return (1); + } + return (pflag ? setfile(from_stat, 0) : 0); +} + +int +copy_special(struct stat *from_stat, int exists) +{ + if (exists && unlink(to.p_path)) { + warn("unlink: %s", to.p_path); + return (1); + } + if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) { + warn("mknod: %s", to.p_path); + return (1); + } + return (pflag ? setfile(from_stat, 0) : 0); +} + + +/* + * Function: setfile + * + * Purpose: + * Set the owner/group/permissions for the "to" file to the information + * in the stat structure. If fd is zero, also call set_utimes() to set + * the mod/access times. If fd is non-zero, the caller must do a utimes + * itself after close(fd). + */ +int +setfile(struct stat *fs, int fd) +{ + int rval, islink; + + rval = 0; + islink = S_ISLNK(fs->st_mode); + fs->st_mode &= S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO; + + /* + * Changing the ownership probably won't succeed, unless we're root + * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting + * the mode; current BSD behavior is to remove all setuid bits on + * chown. If chown fails, lose setuid/setgid bits. + */ + if (fd ? fchown(fd, fs->st_uid, fs->st_gid) : + lchown(to.p_path, fs->st_uid, fs->st_gid)) { + if (errno != EPERM) { + warn("chown: %s", to.p_path); + rval = 1; + } + fs->st_mode &= ~(S_ISUID | S_ISGID); + } + if (fd ? fchmod(fd, fs->st_mode) : lchmod(to.p_path, fs->st_mode)) { + warn("chmod: %s", to.p_path); + rval = 1; + } + + if (!islink && !Nflag) { + unsigned long fflags = fs->st_flags; + /* + * XXX + * NFS doesn't support chflags; ignore errors unless + * there's reason to believe we're losing bits. + * (Note, this still won't be right if the server + * supports flags and we were trying to *remove* flags + * on a file that we copied, i.e., that we didn't create.) + */ + errno = 0; + if ((fd ? fchflags(fd, fflags) : + chflags(to.p_path, fflags)) == -1) + if (errno != EOPNOTSUPP || fs->st_flags != 0) { + warn("chflags: %s", to.p_path); + rval = 1; + } + } + /* if fd is non-zero, caller must call set_utimes() after close() */ + if (fd == 0 && set_utimes(to.p_path, fs)) + rval = 1; + return (rval); +} + +void +usage(void) +{ + (void)fprintf(stderr, + "usage: %s [-R [-H | -L | -P]] [-f | -i] [-alNpv] src target\n" + " %s [-R [-H | -L | -P]] [-f | -i] [-alNpv] src1 ... srcN directory\n", + getprogname(), getprogname()); + exit(1); + /* NOTREACHED */ +} diff --git a/bin/date/date.1 b/bin/date/date.1 new file mode 100644 index 0000000..f15119a --- /dev/null +++ b/bin/date/date.1 @@ -0,0 +1,262 @@ +.\" $NetBSD: date.1,v 1.47 2018/01/27 18:59:38 wiz Exp $ +.\" +.\" 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. +.\" +.\" @(#)date.1 8.3 (Berkeley) 4/28/95 +.\" +.Dd January 25, 2018 +.Dt DATE 1 +.Os +.Sh NAME +.Nm date +.Nd display or set date and time +.Sh SYNOPSIS +.Nm +.Op Fl ajnu +.Op Fl d Ar date +.Op Fl r Ar seconds +.Op Cm + Ns Ar format +.Sm off +.Oo Oo Oo Oo Oo Oo +.Ar CC Oc +.Ar yy Oc +.Ar mm Oc +.Ar dd Oc +.Ar HH Oc Ar MM Oo +.Li \&. Ar SS Oc Oc +.Sm on +.Sh DESCRIPTION +.Nm +displays the current date and time when invoked without arguments. +Providing arguments will format the date and time in a user-defined +way or set the date. +Only the superuser may set the date. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl a +Use +.Xr adjtime 2 +to change the local system time slowly, +maintaining it as a monotonically increasing function. +.Fl a +implies +.Fl n . +.It Fl d Ar date +Parse the provided human-described date and time and display the result without +actually changing the system clock. +(See +.Xr parsedate 3 +for examples.) +.It Fl j +Parse the provided canonical representation of date and time (described below) +and display the result without actually changing the system clock. +.It Fl n +The utility +.Xr timed 8 +is used to synchronize the clocks on groups of machines. +By default, if +.Xr timed 8 +is running, +.Nm +will set the time on all of the machines in the local group. +The +.Fl n +option stops +.Nm +from setting the time for other than the current machine. +.It Fl r Ar seconds +Print out the date and time that is +.Ar seconds +from the Epoch. +.It Fl u +Display or set the date in UTC (universal) time. +.El +.Pp +An operand with a leading plus +.Pq Cm + +sign signals a user-defined format +string which specifies the format in which to display the date and time. +The format string may contain any of the conversion specifications described +in the +.Xr strftime 3 +manual page, as well as any arbitrary text. +A <newline> character is always output after the characters +specified by the format string. +The format string for the default display is: +.Bd -literal -offset indent +%a %b %e %H:%M:%S %Z %Y +.Ed +.Pp +If an operand does not have a leading plus sign, it is interpreted as +a value for setting the system's notion of the current date and time. +The canonical representation for setting the date and time is: +.Pp +.Bl -tag -width Ds -compact -offset indent +.It Ar CC +The first two digits of the year (the century). +.It Ar yy +The second two digits of the year. +If +.Ar yy +is specified, but +.Ar CC +is not, a value for +.Ar yy +between 69 and 99 results in a +.Ar CC +value of 19. +Otherwise, a +.Ar CC +value of 20 is used. +.It Ar mm +The month of the year, from 01 to 12. +.It Ar dd +The day of the month, from 01 to 31. +.It Ar HH +The hour of the day, from 00 to 23. +.It Ar MM +The minute of the hour, from 00 to 59. +.It Ar SS +The second of the minute, from 00 to 60. +.El +.Pp +Everything but the minutes is optional. +.Pp +Time changes for Daylight Saving and Standard Time and leap seconds +and years are handled automatically. +.Sh ENVIRONMENT +The following environment variables affect the execution of +.Nm : +.Bl -tag -width iTZ +.It Ev TZ +The timezone to use when displaying dates. +See +.Xr environ 7 +for more information. +.El +.Sh FILES +.Bl -tag -width /usr/share/zoneinfo/posixrules -compact +.It Pa /etc/localtime +Symlink pointing to system's default timezone information file in +.Pa /usr/share/zoneinfo +directory. +.It Pa /usr/lib/locale/<L>/LC_TIME +Description of time locale <L>. +.It Pa /usr/share/zoneinfo +Time zone information directory. +.It Pa /usr/share/zoneinfo/posixrules +Used with POSIX-style TZ's. +.It Pa /usr/share/zoneinfo/GMT +For UTC leap seconds. +.It Pa /var/log/wtmp +A record of date resets and time changes. +.It Pa /var/log/messages +A record of the user setting the time. +.El +.Pp +If +.Pa /usr/share/zoneinfo/GMT +is absent, UTC leap seconds are loaded from +.Pa /usr/share/zoneinfo/posixrules . +.Sh EXAMPLES +The command: +.Bd -literal -offset indent +date '+DATE: %m/%d/%y%nTIME: %H:%M:%S' +.Ed +.Pp +will display: +.Bd -literal -offset indent +DATE: 11/21/87 +TIME: 13:36:16 +.Ed +.Pp +The command: +.Bd -literal -offset indent +date 8506131627 +.Ed +.Pp +sets the date to +.Dq Li "June 13, 1985, 4:27 PM" . +.Pp +The command: +.Bd -literal -offset indent +date 1432 +.Ed +.Pp +sets the time to +.Li "2:32 PM" , +without modifying the date. +.Sh DIAGNOSTICS +Exit status is 0 on success, 1 if unable to set the date, and 2 +if able to set the local date, but unable to set it globally. +.Pp +Occasionally, when +.Xr timed 8 +synchronizes the time on many hosts, the setting of a new time value may +require more than a few seconds. +On these occasions, +.Nm +prints: +.Ql Network time being set . +The message +.Ql Communication error with +.Xr timed 8 +occurs when the communication +between +.Nm +and +.Xr timed 8 +fails. +.Sh SEE ALSO +.Xr adjtime 2 , +.Xr gettimeofday 2 , +.Xr settimeofday 2 , +.Xr parsedate 3 , +.Xr strftime 3 , +.Xr utmp 5 , +.Xr environ 7 , +.Xr timed 8 +.Rs +.%T "TSP: The Time Synchronization Protocol for UNIX 4.3BSD" +.%A R. Gusella +.%A S. Zatti +.Re +.Sh STANDARDS +The +.Nm +utility is expected to be compatible with +.St -p1003.2 . +.Sh HISTORY +A +.Nm +utility appeared in +.At v1 . diff --git a/bin/date/date.c b/bin/date/date.c new file mode 100644 index 0000000..a067457 --- /dev/null +++ b/bin/date/date.c @@ -0,0 +1,364 @@ +/* $NetBSD: date.c,v 1.61 2014/09/01 21:42:21 dholland Exp $ */ + +/* + * Copyright (c) 1985, 1987, 1988, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT( +"@(#) Copyright (c) 1985, 1987, 1988, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)date.c 8.2 (Berkeley) 4/28/95"; +#else +__RCSID("$NetBSD: date.c,v 1.61 2014/09/01 21:42:21 dholland Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/time.h> + +#include <ctype.h> +#include <err.h> +#include <fcntl.h> +#include <errno.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <tzfile.h> +#include <unistd.h> +#include <util.h> + +#include "extern.h" + +static time_t tval; +static int aflag, jflag, rflag, nflag; + +__dead static void badcanotime(const char *, const char *, size_t); +static void setthetime(const char *); +__dead static void usage(void); + +int +main(int argc, char *argv[]) +{ + char *buf; + size_t bufsiz; + const char *format; + int ch; + long long val; + struct tm *tm; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + while ((ch = getopt(argc, argv, "ad:jnr:u")) != -1) { + switch (ch) { + case 'a': /* adjust time slowly */ + aflag = 1; + nflag = 1; + break; + case 'd': + rflag = 1; + tval = parsedate(optarg, NULL, NULL); + if (tval == -1) { + errx(EXIT_FAILURE, + "%s: Unrecognized date format", optarg); + } + break; + case 'j': /* don't set time */ + jflag = 1; + break; + case 'n': /* don't set network */ + nflag = 1; + break; + case 'r': /* user specified seconds */ + if (optarg[0] == '\0') { + errx(EXIT_FAILURE, "<empty>: Invalid number"); + } + errno = 0; + val = strtoll(optarg, &buf, 0); + if (errno) { + err(EXIT_FAILURE, "%s", optarg); + } + if (optarg[0] == '\0' || *buf != '\0') { + errx(EXIT_FAILURE, + "%s: Invalid number", optarg); + } + rflag = 1; + tval = (time_t)val; + break; + case 'u': /* do everything in UTC */ + (void)setenv("TZ", "UTC0", 1); + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (!rflag && time(&tval) == -1) + err(EXIT_FAILURE, "time"); + + + /* allow the operands in any order */ + if (*argv && **argv == '+') { + format = *argv; + ++argv; + } else + format = "+%a %b %e %H:%M:%S %Z %Y"; + + if (*argv) { + setthetime(*argv); + ++argv; + } + + if (*argv && **argv == '+') + format = *argv; + + if ((buf = malloc(bufsiz = 1024)) == NULL) + goto bad; + + if ((tm = localtime(&tval)) == NULL) + err(EXIT_FAILURE, "%lld: localtime", (long long)tval); + + while (strftime(buf, bufsiz, format, tm) == 0) + if ((buf = realloc(buf, bufsiz <<= 1)) == NULL) + goto bad; + + (void)printf("%s\n", buf + 1); + free(buf); + return 0; +bad: + err(EXIT_FAILURE, "Cannot allocate format buffer"); +} + +static void +badcanotime(const char *msg, const char *val, size_t where) +{ + warnx("%s in canonical time", msg); + warnx("%s", val); + warnx("%*s", (int)where + 1, "^"); + usage(); +} + +#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) + +static void +setthetime(const char *p) +{ + struct timeval tv; + time_t new_time; + struct tm *lt; + const char *dot, *t, *op; + size_t len; + int yearset; + + for (t = p, dot = NULL; *t; ++t) { + if (*t == '.') { + if (dot == NULL) { + dot = t; + } else { + badcanotime("Unexpected dot", p, t - p); + } + } else if (!isdigit((unsigned char)*t)) { + badcanotime("Expected digit", p, t - p); + } + } + + if ((lt = localtime(&tval)) == NULL) + err(EXIT_FAILURE, "%lld: localtime", (long long)tval); + + lt->tm_isdst = -1; /* Divine correct DST */ + + if (dot != NULL) { /* .ss */ + len = strlen(dot); + if (len > 3) { + badcanotime("Unexpected digit after seconds field", + p, strlen(p) - 1); + } else if (len < 3) { + badcanotime("Expected digit in seconds field", + p, strlen(p)); + } + ++dot; + lt->tm_sec = ATOI2(dot); + if (lt->tm_sec > 61) + badcanotime("Seconds out of range", p, strlen(p) - 1); + } else { + len = 0; + lt->tm_sec = 0; + } + + op = p; + yearset = 0; + switch (strlen(p) - len) { + case 12: /* cc */ + lt->tm_year = ATOI2(p) * 100 - TM_YEAR_BASE; + if (lt->tm_year < 0) + badcanotime("Year before 1900", op, p - op + 1); + yearset = 1; + /* FALLTHROUGH */ + case 10: /* yy */ + if (yearset) { + lt->tm_year += ATOI2(p); + } else { + yearset = ATOI2(p); + if (yearset < 69) + lt->tm_year = yearset + 2000 - TM_YEAR_BASE; + else + lt->tm_year = yearset + 1900 - TM_YEAR_BASE; + } + /* FALLTHROUGH */ + case 8: /* mm */ + lt->tm_mon = ATOI2(p); + if (lt->tm_mon > 12 || lt->tm_mon == 0) + badcanotime("Month out of range", op, p - op - 1); + --lt->tm_mon; /* time struct is 0 - 11 */ + /* FALLTHROUGH */ + case 6: /* dd */ + lt->tm_mday = ATOI2(p); + switch (lt->tm_mon) { + case 0: + case 2: + case 4: + case 6: + case 7: + case 9: + case 11: + if (lt->tm_mday > 31 || lt->tm_mday == 0) + badcanotime("Day out of range (max 31)", + op, p - op - 1); + break; + case 3: + case 5: + case 8: + case 10: + if (lt->tm_mday > 30 || lt->tm_mday == 0) + badcanotime("Day out of range (max 30)", + op, p - op - 1); + break; + case 1: + if (isleap(lt->tm_year + TM_YEAR_BASE)) { + if (lt->tm_mday > 29 || lt->tm_mday == 0) { + badcanotime("Day out of range " + "(max 29)", + op, p - op - 1); + } + } else { + if (lt->tm_mday > 28 || lt->tm_mday == 0) { + badcanotime("Day out of range " + "(max 28)", + op, p - op - 1); + } + } + break; + default: + /* + * If the month was given, it's already been + * checked. If a bad value came back from + * localtime, something's badly broken. + * (make this an assertion?) + */ + errx(EXIT_FAILURE, "localtime gave invalid month %d", + lt->tm_mon); + } + /* FALLTHROUGH */ + case 4: /* hh */ + lt->tm_hour = ATOI2(p); + if (lt->tm_hour > 23) + badcanotime("Hour out of range", op, p - op - 1); + /* FALLTHROUGH */ + case 2: /* mm */ + lt->tm_min = ATOI2(p); + if (lt->tm_min > 59) + badcanotime("Minute out of range", op, p - op - 1); + break; + case 0: /* was just .sss */ + if (len != 0) + break; + /* FALLTHROUGH */ + default: + if (strlen(p) - len > 12) { + badcanotime("Too many digits", p, 12); + } else { + badcanotime("Not enough digits", p, strlen(p) - len); + } + } + + /* convert broken-down time to UTC clock time */ + if ((new_time = mktime(lt)) == -1) { + /* Can this actually happen? */ + err(EXIT_FAILURE, "%s: mktime", op); + } + + /* if jflag is set, don't actually change the time, just return */ + if (jflag) { + tval = new_time; + return; + } + + /* set the time */ + if (nflag || netsettime(new_time)) { + logwtmp("|", "date", ""); + if (aflag) { + tv.tv_sec = new_time - tval; + tv.tv_usec = 0; + if (adjtime(&tv, NULL)) + err(EXIT_FAILURE, "adjtime"); + } else { + tval = new_time; + tv.tv_sec = tval; + tv.tv_usec = 0; + if (settimeofday(&tv, NULL)) + err(EXIT_FAILURE, "settimeofday"); + } + logwtmp("{", "date", ""); + } + + if ((p = getlogin()) == NULL) + p = "???"; + syslog(LOG_AUTH | LOG_NOTICE, "date set by %s", p); +} + +static void +usage(void) +{ + (void)fprintf(stderr, + "Usage: %s [-ajnu] [-d date] [-r seconds] [+format]", + getprogname()); + (void)fprintf(stderr, " [[[[[[CC]yy]mm]dd]HH]MM[.SS]]\n"); + exit(EXIT_FAILURE); + /* NOTREACHED */ +} diff --git a/bin/date/extern.h b/bin/date/extern.h new file mode 100644 index 0000000..bbb786f --- /dev/null +++ b/bin/date/extern.h @@ -0,0 +1,39 @@ +/* $NetBSD: extern.h,v 1.8 2006/11/17 22:11:28 christos Exp $ */ + +/*- + * 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. + * + * @(#)extern.h 8.1 (Berkeley) 5/31/93 + */ + +#ifndef _EXTERN_H_ +#define _EXTERN_H_ + +int netsettime(time_t); + +#endif /* !_EXTERN_H_ */ diff --git a/bin/date/netdate.c b/bin/date/netdate.c new file mode 100644 index 0000000..5b5857c --- /dev/null +++ b/bin/date/netdate.c @@ -0,0 +1,200 @@ +/* $NetBSD: netdate.c,v 1.30 2011/01/29 02:16:52 christos Exp $ */ + +/*- + * Copyright (c) 1990, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)netdate.c 8.2 (Berkeley) 4/28/95"; +#else +__RCSID("$NetBSD: netdate.c,v 1.30 2011/01/29 02:16:52 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/time.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <netdb.h> +#define TSPTYPES +#include <protocols/timed.h> + +#include <err.h> +#include <errno.h> +#include <poll.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "extern.h" + +#define WAITACK 2000 /* milliseconds */ +#define WAITDATEACK 5000 /* milliseconds */ + +static const char * +tsp_type_to_string(const struct tsp *msg) +{ + unsigned i; + + i = msg->tsp_type; + return i < TSPTYPENUMBER ? tsptype[i] : "unknown"; +} + +/* + * Set the date in the machines controlled by timedaemons by communicating the + * new date to the local timedaemon. If the timedaemon is in the master state, + * it performs the correction on all slaves. If it is in the slave state, it + * notifies the master that a correction is needed. + * Returns 0 on success. Returns > 0 on failure. + */ +int +netsettime(time_t tval) +{ + struct sockaddr_in dest; + struct tsp msg; + char hostname[MAXHOSTNAMELEN]; + struct servent *sp; + struct pollfd ready; + int found, s, timed_ack, waittime; + + if ((sp = getservbyname("timed", "udp")) == NULL) { + warnx("udp/timed: unknown service"); + return 2; + } + + (void)memset(&dest, 0, sizeof(dest)); +#ifdef BSD4_4 + dest.sin_len = sizeof(dest); +#endif + dest.sin_family = AF_INET; + dest.sin_port = sp->s_port; + dest.sin_addr.s_addr = htonl(INADDR_ANY); + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s == -1) { + if (errno != EAFNOSUPPORT) + warn("timed"); + return 2; + } + +#ifdef IP_PORTRANGE + { + static const int on = IP_PORTRANGE_LOW; + + if (setsockopt(s, IPPROTO_IP, IP_PORTRANGE, &on, + sizeof(on)) == -1) { + warn("setsockopt"); + goto bad; + } + } +#endif + + msg.tsp_type = TSP_SETDATE; + msg.tsp_vers = TSPVERSION; + if (gethostname(hostname, sizeof(hostname)) == -1) { + warn("gethostname"); + goto bad; + } + (void)strlcpy(msg.tsp_name, hostname, sizeof(msg.tsp_name)); + msg.tsp_seq = htons((in_port_t)0); + msg.tsp_time.tv_sec = htonl((in_addr_t)tval); /* XXX: y2038 */ + msg.tsp_time.tv_usec = htonl((in_addr_t)0); + if (connect(s, (const void *)&dest, sizeof(dest)) == -1) { + warn("connect"); + goto bad; + } + if (send(s, &msg, sizeof(msg), 0) == -1) { + if (errno != ECONNREFUSED) + warn("send"); + goto bad; + } + + timed_ack = -1; + waittime = WAITACK; + ready.fd = s; + ready.events = POLLIN; +loop: + found = poll(&ready, 1, waittime); + + { + socklen_t len; + int error; + + len = sizeof(error); + if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) == -1) { + warn("getsockopt"); + goto bad; + } + if (error) { + if (error != ECONNREFUSED) { + errno = error; + warn("send (delayed error)"); + } + goto bad; + } + } + + if (found > 0 && ready.revents & POLLIN) { + ssize_t ret; + + if ((ret = recv(s, &msg, sizeof(msg), 0)) == -1) { + if (errno != ECONNREFUSED) + warn("recv"); + goto bad; + } else if ((size_t)ret < sizeof(msg)) { + warnx("recv: incomplete packet"); + goto bad; + } + + msg.tsp_seq = ntohs(msg.tsp_seq); + msg.tsp_time.tv_sec = ntohl(msg.tsp_time.tv_sec); + msg.tsp_time.tv_usec = ntohl(msg.tsp_time.tv_usec); + switch (msg.tsp_type) { + case TSP_ACK: + timed_ack = TSP_ACK; + waittime = WAITDATEACK; + goto loop; + case TSP_DATEACK: + (void)close(s); + return 0; + default: + warnx("wrong ack received from timed: %s", + tsp_type_to_string(&msg)); + timed_ack = -1; + break; + } + } + if (timed_ack == -1) + warnx("can't reach time daemon, time set locally"); + +bad: + (void)close(s); + return 2; +} diff --git a/bin/dd/args.c b/bin/dd/args.c new file mode 100644 index 0000000..748f52c --- /dev/null +++ b/bin/dd/args.c @@ -0,0 +1,504 @@ +/* $NetBSD: args.c,v 1.40 2019/01/30 01:40:02 mrg Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego and Lance + * Visser of Convex Computer Corporation. + * + * 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> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)args.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: args.c,v 1.40 2019/01/30 01:40:02 mrg Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> + +#ifndef NO_IOFLAG +#include <fcntl.h> +#endif /* NO_IOFLAG */ +#include <err.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "dd.h" +#include "extern.h" + +static int c_arg(const void *, const void *); + +#ifdef NO_MSGFMT +static void f_msgfmt(char *) __dead; +#else +static void f_msgfmt(char *); +#endif /* NO_MSGFMT */ + +#ifdef NO_CONV +static void f_conv(char *) __dead; +#else +static void f_conv(char *); +static int c_conv(const void *, const void *); +#endif /* NO_CONV */ + +#ifdef NO_IOFLAG +static void f_iflag(char *) __dead; +static void f_oflag(char *) __dead; +#else +static void f_iflag(char *); +static void f_oflag(char *); +static u_int f_ioflag(char *, u_int); +static int c_ioflag(const void *, const void *); +#endif /* NO_IOFLAG */ + +static void f_bs(char *); +static void f_cbs(char *); +static void f_count(char *); +static void f_files(char *); +static void f_ibs(char *); +static void f_if(char *); +static void f_obs(char *); +static void f_of(char *); +static void f_seek(char *); +static void f_skip(char *); +static void f_progress(char *); + +static const struct arg { + const char *name; + void (*f)(char *); + u_int set, noset; +} args[] = { + /* the array needs to be sorted by the first column so + bsearch() can be used to find commands quickly */ + { "bs", f_bs, C_BS, C_BS|C_IBS|C_OBS|C_OSYNC }, + { "cbs", f_cbs, C_CBS, C_CBS }, + { "conv", f_conv, 0, 0 }, + { "count", f_count, C_COUNT, C_COUNT }, + { "files", f_files, C_FILES, C_FILES }, + { "ibs", f_ibs, C_IBS, C_BS|C_IBS }, + { "if", f_if, C_IF, C_IF }, + { "iflag", f_iflag, C_IFLAG, C_IFLAG }, + { "iseek", f_skip, C_SKIP, C_SKIP }, + { "msgfmt", f_msgfmt, 0, 0 }, + { "obs", f_obs, C_OBS, C_BS|C_OBS }, + { "of", f_of, C_OF, C_OF }, + { "oflag", f_oflag, C_OFLAG, C_OFLAG }, + { "oseek", f_seek, C_SEEK, C_SEEK }, + { "progress", f_progress, 0, 0 }, + { "seek", f_seek, C_SEEK, C_SEEK }, + { "skip", f_skip, C_SKIP, C_SKIP }, +}; + +/* + * args -- parse JCL syntax of dd. + */ +void +jcl(char **argv) +{ + struct arg *ap, tmp; + char *oper, *arg; + + in.dbsz = out.dbsz = 512; + + while ((oper = *++argv) != NULL) { + if ((oper = strdup(oper)) == NULL) { + errx(EXIT_FAILURE, + "unable to allocate space for the argument %s", + *argv); + /* NOTREACHED */ + } + if ((arg = strchr(oper, '=')) == NULL) { + errx(EXIT_FAILURE, "unknown operand %s", oper); + /* NOTREACHED */ + } + *arg++ = '\0'; + if (!*arg) { + errx(EXIT_FAILURE, "no value specified for %s", oper); + /* NOTREACHED */ + } + tmp.name = oper; + if (!(ap = bsearch(&tmp, args, + __arraycount(args), sizeof(*args), c_arg))) { + errx(EXIT_FAILURE, "unknown operand %s", tmp.name); + /* NOTREACHED */ + } + if (ddflags & ap->noset) { + errx(EXIT_FAILURE, + "%s: illegal argument combination or already set", + tmp.name); + /* NOTREACHED */ + } + ddflags |= ap->set; + ap->f(arg); + } + + /* Final sanity checks. */ + + if (ddflags & C_BS) { + /* + * Bs is turned off by any conversion -- we assume the user + * just wanted to set both the input and output block sizes + * and didn't want the bs semantics, so we don't warn. + */ + if (ddflags & (C_BLOCK | C_LCASE | C_SWAB | C_UCASE | + C_UNBLOCK | C_OSYNC | C_ASCII | C_EBCDIC | C_SPARSE)) { + ddflags &= ~C_BS; + ddflags |= C_IBS|C_OBS; + } + + /* Bs supersedes ibs and obs. */ + if (ddflags & C_BS && ddflags & (C_IBS|C_OBS)) + warnx("bs supersedes ibs and obs"); + } + + /* + * Ascii/ebcdic and cbs implies block/unblock. + * Block/unblock requires cbs and vice-versa. + */ + if (ddflags & (C_BLOCK|C_UNBLOCK)) { + if (!(ddflags & C_CBS)) { + errx(EXIT_FAILURE, "record operations require cbs"); + /* NOTREACHED */ + } + cfunc = ddflags & C_BLOCK ? block : unblock; + } else if (ddflags & C_CBS) { + if (ddflags & (C_ASCII|C_EBCDIC)) { + if (ddflags & C_ASCII) { + ddflags |= C_UNBLOCK; + cfunc = unblock; + } else { + ddflags |= C_BLOCK; + cfunc = block; + } + } else { + errx(EXIT_FAILURE, + "cbs meaningless if not doing record operations"); + /* NOTREACHED */ + } + } else + cfunc = def; + + /* Read, write and seek calls take off_t as arguments. + * + * The following check is not done because an off_t is a quad + * for current NetBSD implementations. + * + * if (in.offset > INT_MAX/in.dbsz || out.offset > INT_MAX/out.dbsz) + * errx(1, "seek offsets cannot be larger than %d", INT_MAX); + */ +} + +static int +c_arg(const void *a, const void *b) +{ + + return (strcmp(((const struct arg *)a)->name, + ((const struct arg *)b)->name)); +} + +static void +f_bs(char *arg) +{ + + in.dbsz = out.dbsz = strsuftoll("block size", arg, 1, UINT_MAX); +} + +static void +f_cbs(char *arg) +{ + + cbsz = strsuftoll("conversion record size", arg, 1, UINT_MAX); +} + +static void +f_count(char *arg) +{ + + cpy_cnt = strsuftoll("block count", arg, 0, LLONG_MAX); + if (!cpy_cnt) + terminate(0); +} + +static void +f_files(char *arg) +{ + + files_cnt = (u_int)strsuftoll("file count", arg, 0, UINT_MAX); + if (!files_cnt) + terminate(0); +} + +static void +f_ibs(char *arg) +{ + + if (!(ddflags & C_BS)) + in.dbsz = strsuftoll("input block size", arg, 1, UINT_MAX); +} + +static void +f_if(char *arg) +{ + + in.name = arg; +} + +#ifdef NO_MSGFMT +/* Build a small version (i.e. for a ramdisk root) */ +static void +f_msgfmt(char *arg) +{ + + errx(EXIT_FAILURE, "msgfmt option disabled"); + /* NOTREACHED */ +} +#else /* NO_MSGFMT */ +static void +f_msgfmt(char *arg) +{ + + /* + * If the format string is not valid, dd_write_msg() will print + * an error and exit. + */ + dd_write_msg(arg, 0); + + msgfmt = arg; +} +#endif /* NO_MSGFMT */ + +static void +f_obs(char *arg) +{ + + if (!(ddflags & C_BS)) + out.dbsz = strsuftoll("output block size", arg, 1, UINT_MAX); +} + +static void +f_of(char *arg) +{ + + out.name = arg; +} + +static void +f_seek(char *arg) +{ + + out.offset = strsuftoll("seek blocks", arg, 0, LLONG_MAX); +} + +static void +f_skip(char *arg) +{ + + in.offset = strsuftoll("skip blocks", arg, 0, LLONG_MAX); +} + +static void +f_progress(char *arg) +{ + + progress = strsuftoll("progress blocks", arg, 0, LLONG_MAX); +} + +#ifdef NO_CONV +/* Build a small version (i.e. for a ramdisk root) */ +static void +f_conv(char *arg) +{ + + errx(EXIT_FAILURE, "conv option disabled"); + /* NOTREACHED */ +} +#else /* NO_CONV */ + +static const struct conv { + const char *name; + u_int set, noset; + const u_char *ctab; +} clist[] = { + { "ascii", C_ASCII, C_EBCDIC, e2a_POSIX }, + { "block", C_BLOCK, C_UNBLOCK, NULL }, + { "ebcdic", C_EBCDIC, C_ASCII, a2e_POSIX }, + { "ibm", C_EBCDIC, C_ASCII, a2ibm_POSIX }, + { "lcase", C_LCASE, C_UCASE, NULL }, + { "noerror", C_NOERROR, 0, NULL }, + { "notrunc", C_NOTRUNC, 0, NULL }, + { "oldascii", C_ASCII, C_EBCDIC, e2a_32V }, + { "oldebcdic", C_EBCDIC, C_ASCII, a2e_32V }, + { "oldibm", C_EBCDIC, C_ASCII, a2ibm_32V }, + { "osync", C_OSYNC, C_BS, NULL }, + { "sparse", C_SPARSE, 0, NULL }, + { "swab", C_SWAB, 0, NULL }, + { "sync", C_SYNC, 0, NULL }, + { "ucase", C_UCASE, C_LCASE, NULL }, + { "unblock", C_UNBLOCK, C_BLOCK, NULL }, + /* If you add items to this table, be sure to add the + * conversions to the C_BS check in the jcl routine above. + */ +}; + +static void +f_conv(char *arg) +{ + struct conv *cp, tmp; + + while (arg != NULL) { + tmp.name = strsep(&arg, ","); + if (!(cp = bsearch(&tmp, clist, + __arraycount(clist), sizeof(*clist), c_conv))) { + errx(EXIT_FAILURE, "unknown conversion %s", tmp.name); + /* NOTREACHED */ + } + if (ddflags & cp->noset) { + errx(EXIT_FAILURE, + "%s: illegal conversion combination", tmp.name); + /* NOTREACHED */ + } + ddflags |= cp->set; + if (cp->ctab) + ctab = cp->ctab; + } +} + +static int +c_conv(const void *a, const void *b) +{ + + return (strcmp(((const struct conv *)a)->name, + ((const struct conv *)b)->name)); +} + +#endif /* NO_CONV */ + +static void +f_iflag(char *arg) +{ +/* Build a small version (i.e. for a ramdisk root) */ +#ifdef NO_IOFLAG + errx(EXIT_FAILURE, "iflag option disabled"); + /* NOTREACHED */ +#else + iflag = f_ioflag(arg, C_IFLAG); + return; +#endif +} + +static void +f_oflag(char *arg) +{ +/* Build a small version (i.e. for a ramdisk root) */ +#ifdef NO_IOFLAG + errx(EXIT_FAILURE, "oflag option disabled"); + /* NOTREACHED */ +#else + oflag = f_ioflag(arg, C_OFLAG); + return; +#endif +} + +#ifndef NO_IOFLAG +static const struct ioflag { + const char *name; + u_int set; + u_int allowed; +} olist[] = { + /* the array needs to be sorted by the first column so + bsearch() can be used to find commands quickly */ + { "alt_io", O_ALT_IO, C_IFLAG|C_OFLAG }, + { "append", O_APPEND, C_OFLAG }, + { "async", O_ASYNC, C_IFLAG|C_OFLAG }, + { "cloexec", O_CLOEXEC, C_IFLAG|C_OFLAG }, + { "creat", O_CREAT, C_OFLAG }, + { "direct", O_DIRECT, C_IFLAG|C_OFLAG }, + { "directory", O_DIRECTORY, C_NONE }, + { "dsync", O_DSYNC, C_OFLAG }, + { "excl", O_EXCL, C_OFLAG }, + { "exlock", O_EXLOCK, C_IFLAG|C_OFLAG }, + { "noctty", O_NOCTTY, C_IFLAG|C_OFLAG }, + { "nofollow", O_NOFOLLOW, C_IFLAG|C_OFLAG }, + { "nonblock", O_NONBLOCK, C_IFLAG|C_OFLAG }, + { "nosigpipe", O_NOSIGPIPE, C_IFLAG|C_OFLAG }, + { "rdonly", O_RDONLY, C_IFLAG }, + { "rdwr", O_RDWR, C_IFLAG }, + { "rsync", O_RSYNC, C_IFLAG }, + { "shlock", O_SHLOCK, C_IFLAG|C_OFLAG }, + { "sync", O_SYNC, C_IFLAG|C_OFLAG }, + { "trunc", O_TRUNC, C_OFLAG }, + { "wronly", O_WRONLY, C_OFLAG }, +}; + +static u_int +f_ioflag(char *arg, u_int flagtype) +{ + u_int ioflag = 0; + struct ioflag *cp, tmp; + const char *flagstr = (flagtype == C_IFLAG) ? "iflag" : "oflag"; + + while (arg != NULL) { + tmp.name = strsep(&arg, ","); + if (!(cp = bsearch(&tmp, olist, + __arraycount(olist), sizeof(*olist), c_ioflag))) { + errx(EXIT_FAILURE, "unknown %s %s", flagstr, tmp.name); + /* NOTREACHED */ + } + + if ((cp->set & O_ACCMODE) && (flagtype == C_OFLAG)) { + warnx("rdonly, rdwr and wronly are ignored for oflag"); + continue; + } + + if ((cp->allowed & flagtype) == 0) { + warnx("%s set for %s but makes no sense", + cp->name, flagstr); + } + + ioflag |= cp->set; + } + + + return ioflag; +} + +static int +c_ioflag(const void *a, const void *b) +{ + + return (strcmp(((const struct ioflag *)a)->name, + ((const struct ioflag *)b)->name)); +} +#endif /* NO_IOFLAG */ diff --git a/bin/dd/conv.c b/bin/dd/conv.c new file mode 100644 index 0000000..d4a8a09 --- /dev/null +++ b/bin/dd/conv.c @@ -0,0 +1,283 @@ +/* $NetBSD: conv.c,v 1.17 2003/08/07 09:05:10 agc Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego and Lance + * Visser of Convex Computer Corporation. + * + * 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> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)conv.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: conv.c,v 1.17 2003/08/07 09:05:10 agc Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/time.h> + +#include <err.h> +#include <string.h> +#include <stdlib.h> + +#include "dd.h" +#include "extern.h" + +/* + * def -- + * Copy input to output. Input is buffered until reaches obs, and then + * output until less than obs remains. Only a single buffer is used. + * Worst case buffer calculation is (ibs + obs - 1). + */ +void +def(void) +{ + uint64_t cnt; + u_char *inp; + const u_char *t; + + if ((t = ctab) != NULL) + for (inp = in.dbp - (cnt = in.dbrcnt); cnt--; ++inp) + *inp = t[*inp]; + + /* Make the output buffer look right. */ + out.dbp = in.dbp; + out.dbcnt = in.dbcnt; + + if (in.dbcnt >= out.dbsz) { + /* If the output buffer is full, write it. */ + dd_out(0); + + /* + * Ddout copies the leftover output to the beginning of + * the buffer and resets the output buffer. Reset the + * input buffer to match it. + */ + in.dbp = out.dbp; + in.dbcnt = out.dbcnt; + } +} + +void +def_close(void) +{ + + /* Just update the count, everything is already in the buffer. */ + if (in.dbcnt) + out.dbcnt = in.dbcnt; +} + +#ifdef NO_CONV +/* Build a smaller version (i.e. for a miniroot) */ +/* These can not be called, but just in case... */ +static const char no_block[] = "unblock and -DNO_CONV?"; +void block(void) { errx(EXIT_FAILURE, "%s", no_block + 2); } +void block_close(void) { errx(EXIT_FAILURE, "%s", no_block + 2); } +void unblock(void) { errx(EXIT_FAILURE, "%s", no_block); } +void unblock_close(void) { errx(EXIT_FAILURE, "%s", no_block); } +#else /* NO_CONV */ + +/* + * Copy variable length newline terminated records with a max size cbsz + * bytes to output. Records less than cbs are padded with spaces. + * + * max in buffer: MAX(ibs, cbsz) + * max out buffer: obs + cbsz + */ +void +block(void) +{ + static int intrunc; + int ch = 0; /* pacify gcc */ + uint64_t cnt, maxlen; + u_char *inp, *outp; + const u_char *t; + + /* + * Record truncation can cross block boundaries. If currently in a + * truncation state, keep tossing characters until reach a newline. + * Start at the beginning of the buffer, as the input buffer is always + * left empty. + */ + if (intrunc) { + for (inp = in.db, cnt = in.dbrcnt; + cnt && *inp++ != '\n'; --cnt); + if (!cnt) { + in.dbcnt = 0; + in.dbp = in.db; + return; + } + intrunc = 0; + /* Adjust the input buffer numbers. */ + in.dbcnt = cnt - 1; + in.dbp = inp + cnt - 1; + } + + /* + * Copy records (max cbsz size chunks) into the output buffer. The + * translation is done as we copy into the output buffer. + */ + for (inp = in.dbp - in.dbcnt, outp = out.dbp; in.dbcnt;) { + maxlen = MIN(cbsz, in.dbcnt); + if ((t = ctab) != NULL) + for (cnt = 0; + cnt < maxlen && (ch = *inp++) != '\n'; ++cnt) + *outp++ = t[ch]; + else + for (cnt = 0; + cnt < maxlen && (ch = *inp++) != '\n'; ++cnt) + *outp++ = ch; + /* + * Check for short record without a newline. Reassemble the + * input block. + */ + if (ch != '\n' && in.dbcnt < cbsz) { + (void)memmove(in.db, in.dbp - in.dbcnt, in.dbcnt); + break; + } + + /* Adjust the input buffer numbers. */ + in.dbcnt -= cnt; + if (ch == '\n') + --in.dbcnt; + + /* Pad short records with spaces. */ + if (cnt < cbsz) + (void)memset(outp, ctab ? ctab[' '] : ' ', cbsz - cnt); + else { + /* + * If the next character wouldn't have ended the + * block, it's a truncation. + */ + if (!in.dbcnt || *inp != '\n') + ++st.trunc; + + /* Toss characters to a newline. */ + for (; in.dbcnt && *inp++ != '\n'; --in.dbcnt); + if (!in.dbcnt) + intrunc = 1; + else + --in.dbcnt; + } + + /* Adjust output buffer numbers. */ + out.dbp += cbsz; + if ((out.dbcnt += cbsz) >= out.dbsz) + dd_out(0); + outp = out.dbp; + } + in.dbp = in.db + in.dbcnt; +} + +void +block_close(void) +{ + + /* + * Copy any remaining data into the output buffer and pad to a record. + * Don't worry about truncation or translation, the input buffer is + * always empty when truncating, and no characters have been added for + * translation. The bottom line is that anything left in the input + * buffer is a truncated record. Anything left in the output buffer + * just wasn't big enough. + */ + if (in.dbcnt) { + ++st.trunc; + (void)memmove(out.dbp, in.dbp - in.dbcnt, in.dbcnt); + (void)memset(out.dbp + in.dbcnt, + ctab ? ctab[' '] : ' ', cbsz - in.dbcnt); + out.dbcnt += cbsz; + } +} + +/* + * Convert fixed length (cbsz) records to variable length. Deletes any + * trailing blanks and appends a newline. + * + * max in buffer: MAX(ibs, cbsz) + cbsz + * max out buffer: obs + cbsz + */ +void +unblock(void) +{ + uint64_t cnt; + u_char *inp; + const u_char *t; + + /* Translation and case conversion. */ + if ((t = ctab) != NULL) + for (cnt = in.dbrcnt, inp = in.dbp - 1; cnt--; inp--) + *inp = t[*inp]; + /* + * Copy records (max cbsz size chunks) into the output buffer. The + * translation has to already be done or we might not recognize the + * spaces. + */ + for (inp = in.db; in.dbcnt >= cbsz; inp += cbsz, in.dbcnt -= cbsz) { + for (t = inp + cbsz - 1; t >= inp && *t == ' '; --t); + if (t >= inp) { + cnt = t - inp + 1; + (void)memmove(out.dbp, inp, cnt); + out.dbp += cnt; + out.dbcnt += cnt; + } + ++out.dbcnt; + *out.dbp++ = '\n'; + if (out.dbcnt >= out.dbsz) + dd_out(0); + } + if (in.dbcnt) + (void)memmove(in.db, in.dbp - in.dbcnt, in.dbcnt); + in.dbp = in.db + in.dbcnt; +} + +void +unblock_close(void) +{ + uint64_t cnt; + u_char *t; + + if (in.dbcnt) { + warnx("%s: short input record", in.name); + for (t = in.db + in.dbcnt - 1; t >= in.db && *t == ' '; --t); + if (t >= in.db) { + cnt = t - in.db + 1; + (void)memmove(out.dbp, in.db, cnt); + out.dbp += cnt; + out.dbcnt += cnt; + } + ++out.dbcnt; + *out.dbp++ = '\n'; + } +} + +#endif /* NO_CONV */ diff --git a/bin/dd/conv_tab.c b/bin/dd/conv_tab.c new file mode 100644 index 0000000..89d32da --- /dev/null +++ b/bin/dd/conv_tab.c @@ -0,0 +1,287 @@ +/* $NetBSD: conv_tab.c,v 1.9 2003/08/07 09:05:10 agc Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego and Lance + * Visser of Convex Computer Corporation. + * + * 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> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)conv_tab.c 8.1 (Berkeley) 5/31/93"; +#else +__RCSID("$NetBSD: conv_tab.c,v 1.9 2003/08/07 09:05:10 agc Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> + +/* + * There are currently six tables: + * + * ebcdic -> ascii 32V conv=oldascii + * ascii -> ebcdic 32V conv=oldebcdic + * ascii -> ibm ebcdic 32V conv=oldibm + * + * ebcdic -> ascii POSIX/S5 conv=ascii + * ascii -> ebcdic POSIX/S5 conv=ebcdic + * ascii -> ibm ebcdic POSIX/S5 conv=ibm + * + * Other tables are built from these if multiple conversions are being + * done. + * + * Tables used for conversions to/from IBM and EBCDIC to support an extension + * to POSIX P1003.2/D11. The tables referencing POSIX contain data extracted + * from tables 4-3 and 4-4 in P1003.2/Draft 11. The historic tables were + * constructed by running against a file with all possible byte values. + * + * More information can be obtained in "Correspondences of 8-Bit and Hollerith + * Codes for Computer Environments-A USASI Tutorial", Communications of the + * ACM, Volume 11, Number 11, November 1968, pp. 783-789. + */ + +u_char casetab[256]; + +/* EBCDIC to ASCII -- 32V compatible. */ +const u_char e2a_32V[] = { + 0000, 0001, 0002, 0003, 0234, 0011, 0206, 0177, /* 0000 */ + 0227, 0215, 0216, 0013, 0014, 0015, 0016, 0017, /* 0010 */ + 0020, 0021, 0022, 0023, 0235, 0205, 0010, 0207, /* 0020 */ + 0030, 0031, 0222, 0217, 0034, 0035, 0036, 0037, /* 0030 */ + 0200, 0201, 0202, 0203, 0204, 0012, 0027, 0033, /* 0040 */ + 0210, 0211, 0212, 0213, 0214, 0005, 0006, 0007, /* 0050 */ + 0220, 0221, 0026, 0223, 0224, 0225, 0226, 0004, /* 0060 */ + 0230, 0231, 0232, 0233, 0024, 0025, 0236, 0032, /* 0070 */ + 0040, 0240, 0241, 0242, 0243, 0244, 0245, 0246, /* 0100 */ + 0247, 0250, 0133, 0056, 0074, 0050, 0053, 0041, /* 0110 */ + 0046, 0251, 0252, 0253, 0254, 0255, 0256, 0257, /* 0120 */ + 0260, 0261, 0135, 0044, 0052, 0051, 0073, 0136, /* 0130 */ + 0055, 0057, 0262, 0263, 0264, 0265, 0266, 0267, /* 0140 */ + 0270, 0271, 0174, 0054, 0045, 0137, 0076, 0077, /* 0150 */ + 0272, 0273, 0274, 0275, 0276, 0277, 0300, 0301, /* 0160 */ + 0302, 0140, 0072, 0043, 0100, 0047, 0075, 0042, /* 0170 */ + 0303, 0141, 0142, 0143, 0144, 0145, 0146, 0147, /* 0200 */ + 0150, 0151, 0304, 0305, 0306, 0307, 0310, 0311, /* 0210 */ + 0312, 0152, 0153, 0154, 0155, 0156, 0157, 0160, /* 0220 */ + 0161, 0162, 0313, 0314, 0315, 0316, 0317, 0320, /* 0230 */ + 0321, 0176, 0163, 0164, 0165, 0166, 0167, 0170, /* 0240 */ + 0171, 0172, 0322, 0323, 0324, 0325, 0326, 0327, /* 0250 */ + 0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337, /* 0260 */ + 0340, 0341, 0342, 0343, 0344, 0345, 0346, 0347, /* 0270 */ + 0173, 0101, 0102, 0103, 0104, 0105, 0106, 0107, /* 0300 */ + 0110, 0111, 0350, 0351, 0352, 0353, 0354, 0355, /* 0310 */ + 0175, 0112, 0113, 0114, 0115, 0116, 0117, 0120, /* 0320 */ + 0121, 0122, 0356, 0357, 0360, 0361, 0362, 0363, /* 0330 */ + 0134, 0237, 0123, 0124, 0125, 0126, 0127, 0130, /* 0340 */ + 0131, 0132, 0364, 0365, 0366, 0367, 0370, 0371, /* 0350 */ + 0060, 0061, 0062, 0063, 0064, 0065, 0066, 0067, /* 0360 */ + 0070, 0071, 0372, 0373, 0374, 0375, 0376, 0377, /* 0370 */ +}; + +/* ASCII to EBCDIC -- 32V compatible. */ +const u_char a2e_32V[] = { + 0000, 0001, 0002, 0003, 0067, 0055, 0056, 0057, /* 0000 */ + 0026, 0005, 0045, 0013, 0014, 0015, 0016, 0017, /* 0010 */ + 0020, 0021, 0022, 0023, 0074, 0075, 0062, 0046, /* 0020 */ + 0030, 0031, 0077, 0047, 0034, 0035, 0036, 0037, /* 0030 */ + 0100, 0117, 0177, 0173, 0133, 0154, 0120, 0175, /* 0040 */ + 0115, 0135, 0134, 0116, 0153, 0140, 0113, 0141, /* 0050 */ + 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367, /* 0060 */ + 0370, 0371, 0172, 0136, 0114, 0176, 0156, 0157, /* 0070 */ + 0174, 0301, 0302, 0303, 0304, 0305, 0306, 0307, /* 0100 */ + 0310, 0311, 0321, 0322, 0323, 0324, 0325, 0326, /* 0110 */ + 0327, 0330, 0331, 0342, 0343, 0344, 0345, 0346, /* 0120 */ + 0347, 0350, 0351, 0112, 0340, 0132, 0137, 0155, /* 0130 */ + 0171, 0201, 0202, 0203, 0204, 0205, 0206, 0207, /* 0140 */ + 0210, 0211, 0221, 0222, 0223, 0224, 0225, 0226, /* 0150 */ + 0227, 0230, 0231, 0242, 0243, 0244, 0245, 0246, /* 0160 */ + 0247, 0250, 0251, 0300, 0152, 0320, 0241, 0007, /* 0170 */ + 0040, 0041, 0042, 0043, 0044, 0025, 0006, 0027, /* 0200 */ + 0050, 0051, 0052, 0053, 0054, 0011, 0012, 0033, /* 0210 */ + 0060, 0061, 0032, 0063, 0064, 0065, 0066, 0010, /* 0220 */ + 0070, 0071, 0072, 0073, 0004, 0024, 0076, 0341, /* 0230 */ + 0101, 0102, 0103, 0104, 0105, 0106, 0107, 0110, /* 0240 */ + 0111, 0121, 0122, 0123, 0124, 0125, 0126, 0127, /* 0250 */ + 0130, 0131, 0142, 0143, 0144, 0145, 0146, 0147, /* 0260 */ + 0150, 0151, 0160, 0161, 0162, 0163, 0164, 0165, /* 0270 */ + 0166, 0167, 0170, 0200, 0212, 0213, 0214, 0215, /* 0300 */ + 0216, 0217, 0220, 0232, 0233, 0234, 0235, 0236, /* 0310 */ + 0237, 0240, 0252, 0253, 0254, 0255, 0256, 0257, /* 0320 */ + 0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267, /* 0330 */ + 0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277, /* 0340 */ + 0312, 0313, 0314, 0315, 0316, 0317, 0332, 0333, /* 0350 */ + 0334, 0335, 0336, 0337, 0352, 0353, 0354, 0355, /* 0360 */ + 0356, 0357, 0372, 0373, 0374, 0375, 0376, 0377, /* 0370 */ +}; + +/* ASCII to IBM EBCDIC -- 32V compatible. */ +const u_char a2ibm_32V[] = { + 0000, 0001, 0002, 0003, 0067, 0055, 0056, 0057, /* 0000 */ + 0026, 0005, 0045, 0013, 0014, 0015, 0016, 0017, /* 0010 */ + 0020, 0021, 0022, 0023, 0074, 0075, 0062, 0046, /* 0020 */ + 0030, 0031, 0077, 0047, 0034, 0035, 0036, 0037, /* 0030 */ + 0100, 0132, 0177, 0173, 0133, 0154, 0120, 0175, /* 0040 */ + 0115, 0135, 0134, 0116, 0153, 0140, 0113, 0141, /* 0050 */ + 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367, /* 0060 */ + 0370, 0371, 0172, 0136, 0114, 0176, 0156, 0157, /* 0070 */ + 0174, 0301, 0302, 0303, 0304, 0305, 0306, 0307, /* 0100 */ + 0310, 0311, 0321, 0322, 0323, 0324, 0325, 0326, /* 0110 */ + 0327, 0330, 0331, 0342, 0343, 0344, 0345, 0346, /* 0120 */ + 0347, 0350, 0351, 0255, 0340, 0275, 0137, 0155, /* 0130 */ + 0171, 0201, 0202, 0203, 0204, 0205, 0206, 0207, /* 0140 */ + 0210, 0211, 0221, 0222, 0223, 0224, 0225, 0226, /* 0150 */ + 0227, 0230, 0231, 0242, 0243, 0244, 0245, 0246, /* 0160 */ + 0247, 0250, 0251, 0300, 0117, 0320, 0241, 0007, /* 0170 */ + 0040, 0041, 0042, 0043, 0044, 0025, 0006, 0027, /* 0200 */ + 0050, 0051, 0052, 0053, 0054, 0011, 0012, 0033, /* 0210 */ + 0060, 0061, 0032, 0063, 0064, 0065, 0066, 0010, /* 0220 */ + 0070, 0071, 0072, 0073, 0004, 0024, 0076, 0341, /* 0230 */ + 0101, 0102, 0103, 0104, 0105, 0106, 0107, 0110, /* 0240 */ + 0111, 0121, 0122, 0123, 0124, 0125, 0126, 0127, /* 0250 */ + 0130, 0131, 0142, 0143, 0144, 0145, 0146, 0147, /* 0260 */ + 0150, 0151, 0160, 0161, 0162, 0163, 0164, 0165, /* 0270 */ + 0166, 0167, 0170, 0200, 0212, 0213, 0214, 0215, /* 0300 */ + 0216, 0217, 0220, 0232, 0233, 0234, 0235, 0236, /* 0310 */ + 0237, 0240, 0252, 0253, 0254, 0255, 0256, 0257, /* 0320 */ + 0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267, /* 0330 */ + 0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277, /* 0340 */ + 0312, 0313, 0314, 0315, 0316, 0317, 0332, 0333, /* 0350 */ + 0334, 0335, 0336, 0337, 0352, 0353, 0354, 0355, /* 0360 */ + 0356, 0357, 0372, 0373, 0374, 0375, 0376, 0377, /* 0370 */ +}; + +/* EBCDIC to ASCII -- POSIX and System V compatible. */ +const u_char e2a_POSIX[] = { + 0000, 0001, 0002, 0003, 0234, 0011, 0206, 0177, /* 0000 */ + 0227, 0215, 0216, 0013, 0014, 0015, 0016, 0017, /* 0010 */ + 0020, 0021, 0022, 0023, 0235, 0205, 0010, 0207, /* 0020 */ + 0030, 0031, 0222, 0217, 0034, 0035, 0036, 0037, /* 0030 */ + 0200, 0201, 0202, 0203, 0204, 0012, 0027, 0033, /* 0040 */ + 0210, 0211, 0212, 0213, 0214, 0005, 0006, 0007, /* 0050 */ + 0220, 0221, 0026, 0223, 0224, 0225, 0226, 0004, /* 0060 */ + 0230, 0231, 0232, 0233, 0024, 0025, 0236, 0032, /* 0070 */ + 0040, 0240, 0241, 0242, 0243, 0244, 0245, 0246, /* 0100 */ + 0247, 0250, 0325, 0056, 0074, 0050, 0053, 0174, /* 0110 */ + 0046, 0251, 0252, 0253, 0254, 0255, 0256, 0257, /* 0120 */ + 0260, 0261, 0041, 0044, 0052, 0051, 0073, 0176, /* 0130 */ + 0055, 0057, 0262, 0263, 0264, 0265, 0266, 0267, /* 0140 */ + 0270, 0271, 0313, 0054, 0045, 0137, 0076, 0077, /* 0150 */ + 0272, 0273, 0274, 0275, 0276, 0277, 0300, 0301, /* 0160 */ + 0302, 0140, 0072, 0043, 0100, 0047, 0075, 0042, /* 0170 */ + 0303, 0141, 0142, 0143, 0144, 0145, 0146, 0147, /* 0200 */ + 0150, 0151, 0304, 0305, 0306, 0307, 0310, 0311, /* 0210 */ + 0312, 0152, 0153, 0154, 0155, 0156, 0157, 0160, /* 0220 */ + 0161, 0162, 0136, 0314, 0315, 0316, 0317, 0320, /* 0230 */ + 0321, 0345, 0163, 0164, 0165, 0166, 0167, 0170, /* 0240 */ + 0171, 0172, 0322, 0323, 0324, 0133, 0326, 0327, /* 0250 */ + 0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337, /* 0260 */ + 0340, 0341, 0342, 0343, 0344, 0135, 0346, 0347, /* 0270 */ + 0173, 0101, 0102, 0103, 0104, 0105, 0106, 0107, /* 0300 */ + 0110, 0111, 0350, 0351, 0352, 0353, 0354, 0355, /* 0310 */ + 0175, 0112, 0113, 0114, 0115, 0116, 0117, 0120, /* 0320 */ + 0121, 0122, 0356, 0357, 0360, 0361, 0362, 0363, /* 0330 */ + 0134, 0237, 0123, 0124, 0125, 0126, 0127, 0130, /* 0340 */ + 0131, 0132, 0364, 0365, 0366, 0367, 0370, 0371, /* 0350 */ + 0060, 0061, 0062, 0063, 0064, 0065, 0066, 0067, /* 0360 */ + 0070, 0071, 0372, 0373, 0374, 0375, 0376, 0377, /* 0370 */ +}; + +/* ASCII to EBCDIC -- POSIX and System V compatible. */ +const u_char a2e_POSIX[] = { + 0000, 0001, 0002, 0003, 0067, 0055, 0056, 0057, /* 0000 */ + 0026, 0005, 0045, 0013, 0014, 0015, 0016, 0017, /* 0010 */ + 0020, 0021, 0022, 0023, 0074, 0075, 0062, 0046, /* 0020 */ + 0030, 0031, 0077, 0047, 0034, 0035, 0036, 0037, /* 0030 */ + 0100, 0132, 0177, 0173, 0133, 0154, 0120, 0175, /* 0040 */ + 0115, 0135, 0134, 0116, 0153, 0140, 0113, 0141, /* 0050 */ + 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367, /* 0060 */ + 0370, 0371, 0172, 0136, 0114, 0176, 0156, 0157, /* 0070 */ + 0174, 0301, 0302, 0303, 0304, 0305, 0306, 0307, /* 0100 */ + 0310, 0311, 0321, 0322, 0323, 0324, 0325, 0326, /* 0110 */ + 0327, 0330, 0331, 0342, 0343, 0344, 0345, 0346, /* 0120 */ + 0347, 0350, 0351, 0255, 0340, 0275, 0232, 0155, /* 0130 */ + 0171, 0201, 0202, 0203, 0204, 0205, 0206, 0207, /* 0140 */ + 0210, 0211, 0221, 0222, 0223, 0224, 0225, 0226, /* 0150 */ + 0227, 0230, 0231, 0242, 0243, 0244, 0245, 0246, /* 0160 */ + 0247, 0250, 0251, 0300, 0117, 0320, 0137, 0007, /* 0170 */ + 0040, 0041, 0042, 0043, 0044, 0025, 0006, 0027, /* 0200 */ + 0050, 0051, 0052, 0053, 0054, 0011, 0012, 0033, /* 0210 */ + 0060, 0061, 0032, 0063, 0064, 0065, 0066, 0010, /* 0220 */ + 0070, 0071, 0072, 0073, 0004, 0024, 0076, 0341, /* 0230 */ + 0101, 0102, 0103, 0104, 0105, 0106, 0107, 0110, /* 0240 */ + 0111, 0121, 0122, 0123, 0124, 0125, 0126, 0127, /* 0250 */ + 0130, 0131, 0142, 0143, 0144, 0145, 0146, 0147, /* 0260 */ + 0150, 0151, 0160, 0161, 0162, 0163, 0164, 0165, /* 0270 */ + 0166, 0167, 0170, 0200, 0212, 0213, 0214, 0215, /* 0300 */ + 0216, 0217, 0220, 0152, 0233, 0234, 0235, 0236, /* 0310 */ + 0237, 0240, 0252, 0253, 0254, 0112, 0256, 0257, /* 0320 */ + 0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267, /* 0330 */ + 0270, 0271, 0272, 0273, 0274, 0241, 0276, 0277, /* 0340 */ + 0312, 0313, 0314, 0315, 0316, 0317, 0332, 0333, /* 0350 */ + 0334, 0335, 0336, 0337, 0352, 0353, 0354, 0355, /* 0360 */ + 0356, 0357, 0372, 0373, 0374, 0375, 0376, 0377, /* 0370 */ +}; + +/* ASCII to IBM EBCDIC -- POSIX and System V compatible. */ +const u_char a2ibm_POSIX[] = { + 0000, 0001, 0002, 0003, 0067, 0055, 0056, 0057, /* 0000 */ + 0026, 0005, 0045, 0013, 0014, 0015, 0016, 0017, /* 0010 */ + 0020, 0021, 0022, 0023, 0074, 0075, 0062, 0046, /* 0020 */ + 0030, 0031, 0077, 0047, 0034, 0035, 0036, 0037, /* 0030 */ + 0100, 0132, 0177, 0173, 0133, 0154, 0120, 0175, /* 0040 */ + 0115, 0135, 0134, 0116, 0153, 0140, 0113, 0141, /* 0050 */ + 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367, /* 0060 */ + 0370, 0371, 0172, 0136, 0114, 0176, 0156, 0157, /* 0070 */ + 0174, 0301, 0302, 0303, 0304, 0305, 0306, 0307, /* 0100 */ + 0310, 0311, 0321, 0322, 0323, 0324, 0325, 0326, /* 0110 */ + 0327, 0330, 0331, 0342, 0343, 0344, 0345, 0346, /* 0120 */ + 0347, 0350, 0351, 0255, 0340, 0275, 0137, 0155, /* 0130 */ + 0171, 0201, 0202, 0203, 0204, 0205, 0206, 0207, /* 0140 */ + 0210, 0211, 0221, 0222, 0223, 0224, 0225, 0226, /* 0150 */ + 0227, 0230, 0231, 0242, 0243, 0244, 0245, 0246, /* 0160 */ + 0247, 0250, 0251, 0300, 0117, 0320, 0241, 0007, /* 0170 */ + 0040, 0041, 0042, 0043, 0044, 0025, 0006, 0027, /* 0200 */ + 0050, 0051, 0052, 0053, 0054, 0011, 0012, 0033, /* 0210 */ + 0060, 0061, 0032, 0063, 0064, 0065, 0066, 0010, /* 0220 */ + 0070, 0071, 0072, 0073, 0004, 0024, 0076, 0341, /* 0230 */ + 0101, 0102, 0103, 0104, 0105, 0106, 0107, 0110, /* 0240 */ + 0111, 0121, 0122, 0123, 0124, 0125, 0126, 0127, /* 0250 */ + 0130, 0131, 0142, 0143, 0144, 0145, 0146, 0147, /* 0260 */ + 0150, 0151, 0160, 0161, 0162, 0163, 0164, 0165, /* 0270 */ + 0166, 0167, 0170, 0200, 0212, 0213, 0214, 0215, /* 0300 */ + 0216, 0217, 0220, 0232, 0233, 0234, 0235, 0236, /* 0310 */ + 0237, 0240, 0252, 0253, 0254, 0255, 0256, 0257, /* 0320 */ + 0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267, /* 0330 */ + 0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277, /* 0340 */ + 0312, 0313, 0314, 0315, 0316, 0317, 0332, 0333, /* 0350 */ + 0334, 0335, 0336, 0337, 0352, 0353, 0354, 0355, /* 0360 */ + 0356, 0357, 0372, 0373, 0374, 0375, 0376, 0377, /* 0370 */ +}; diff --git a/bin/dd/dd.1 b/bin/dd/dd.1 new file mode 100644 index 0000000..44ea896 --- /dev/null +++ b/bin/dd/dd.1 @@ -0,0 +1,588 @@ +.\" $NetBSD: dd.1,v 1.36 2019/01/30 10:28:50 wiz Exp $ +.\" +.\" Copyright (c) 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Keith Muller of the University of California, San Diego. +.\" +.\" 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. +.\" +.\" @(#)dd.1 8.2 (Berkeley) 1/13/94 +.\" +.Dd January 29, 2019 +.Dt DD 1 +.Os +.Sh NAME +.Nm dd +.Nd convert and copy a file +.Sh SYNOPSIS +.Nm +.Op operand ... +.Sh DESCRIPTION +The +.Nm +utility copies the standard input to the standard output. +Input data is read and written in 512-byte blocks. +If input reads are short, input from multiple reads are aggregated +to form the output block. +When finished, +.Nm +displays the number of complete and partial input and output blocks +and truncated input records to the standard error output. +.Pp +The following operands are available: +.Bl -tag -width of=file +.It Cm bs= Ns Ar n +Set both input and output block size, superseding the +.Cm ibs +and +.Cm obs +operands. +If no conversion values other than +.Cm noerror , +.Cm notrunc +or +.Cm sync +are specified, then each input block is copied to the output as a +single block without any aggregation of short blocks. +.It Cm cbs= Ns Ar n +Set the conversion record size to +.Va n +bytes. +The conversion record size is required by the record oriented conversion +values. +.It Cm count= Ns Ar n +Copy only +.Va n +input blocks. +.It Cm files= Ns Ar n +Copy +.Va n +input files before terminating. +This operand is only applicable when the input device is a tape. +.It Cm ibs= Ns Ar n +Set the input block size to +.Va n +bytes instead of the default 512. +.It Cm if= Ns Ar file +Read input from +.Ar file +instead of the standard input. +.It Cm iflag= Ns Ar flags +Use comma-separated +.Ar flags +when calling +.Xr open 2 +for the input file. +See the +.Sx INPUT AND OUTPUT FLAGS +section for details. +Default value is +.Va rdonly . +.It Cm iseek= Ns Ar n +Seek on the input file +.Ar n +blocks. +This is synonymous with +.Cm skip= Ns Ar n . +.It Cm msgfmt= Ns Ar fmt +Specify the message format +.Ar fmt +to be used when writing information to standard output. +Possible values are: +.Bl -tag -width xxxxx -offset indent -compact +.It quiet +turns off information summary report except for errors and +.Cm progress . +.It posix +default information summary report as specified by POSIX. +.It human +default information summary report extended with human-readable +values. +.El +.Pp +When +.Ar fmt +does not correspond to any value given above, +it contains a string that will be used as format specifier +for the information summary output. +Each conversion specification is introduced by the character +.Cm % . +The following ones are available: +.Bl -tag -width xx -offset indent -compact +.It b +total number of bytes transferred +.It B +total number of bytes transferred in +.Xr humanize_number 3 +format +.It e +speed transfer +.It E +speed transfer in +.Xr humanize_number 3 +format +.It i +number of partial input block(s) +.It I +number of full input block(s) +.It o +number of partial output block(s) +.It O +number of full output block(s) +.It s +time elapsed since the beginning in +.Do seconds.ms Dc +format +.It p +number of sparse output blocks +.It t +number of truncated blocks +.It w +number of odd-length swab blocks +.It P +singular/plural of +.Do block Dc +depending on number of sparse blocks +.It T +singular/plural of +.Do block Dc +depending on number of truncated blocks +.It W +singular/plural of +.Do block Dc +depending on number of swab blocks +.El +.It Cm obs= Ns Ar n +Set the output block size to +.Va n +bytes instead of the default 512. +.It Cm of= Ns Ar file +Write output to +.Ar file +instead of the standard output. +Any regular output file is truncated unless the +.Cm notrunc +conversion value is specified. +If an initial portion of the output file is skipped (see the +.Cm seek +operand) +the output file is truncated at that point. +.It Cm oflag= Ns Ar flags +Same as +.Cm iflag +but for the call to +.Xr open 2 +on the output file. +The default value is +.Va creat , +which must be explicitly added in +.Cm oflag +if this option is used in order to output to a nonexistent file. +The default or specified value is or'ed with +.Va rdwr +for a first +.Xr open 2 +attempt, then on failure with +.Va wronly +on a second attempt. +In both cases, +.Va trunc +is automatically added if none of +.Cm oseek , +.Cm seek , +or +.Cm conv=notrunc +operands are used. +See the +.Sx INPUT AND OUTPUT FLAGS +section for details. +.It Cm oseek= Ns Ar n +Seek on the output file +.Ar n +blocks. +This is synonymous with +.Cm seek= Ns Ar n . +.It Cm seek= Ns Ar n +Seek +.Va n +blocks from the beginning of the output before copying. +On non-tape devices, an +.Xr lseek 2 +operation is used. +Otherwise, existing blocks are read and the data discarded. +If the user does not have read permission for the tape, it is positioned +using the tape +.Xr ioctl 2 +function calls. +If the seek operation is past the end of file, space from the current +end of file to the specified offset is filled with blocks of +.Tn NUL +bytes. +.It Cm skip= Ns Ar n +Skip +.Va n +blocks from the beginning of the input before copying. +On input which supports seeks, an +.Xr lseek 2 +operation is used. +Otherwise, input data is read and discarded. +For pipes, the correct number of bytes is read. +For all other devices, the correct number of blocks is read without +distinguishing between a partial or complete block being read. +.It Cm progress= Ns Ar n +Switch on display of progress if +.Va n +is set to any non-zero value. +This will cause a +.Dq \&. +to be printed (to the standard error output) for every +.Va n +full or partial blocks written to the output file. +.Sm off +.It Cm conv= Cm value Op \&, Cm value \&... +.Sm on +Where +.Cm value +is one of the symbols from the following list. +.Bl -tag -width unblock +.It Cm ascii , oldascii +The same as the +.Cm unblock +value except that characters are translated from +.Tn EBCDIC +to +.Tn ASCII +before the +records are converted. +(These values imply +.Cm unblock +if the operand +.Cm cbs +is also specified.) +There are two conversion maps for +.Tn ASCII . +The value +.Cm ascii +specifies the recommended one which is compatible with +.At V . +The value +.Cm oldascii +specifies the one used in historic +.Tn AT&T +and pre- +.Bx 4.3 Reno +systems. +.It Cm block +Treats the input as a sequence of newline or end-of-file terminated variable +length records independent of input and output block boundaries. +Any trailing newline character is discarded. +Each input record is converted to a fixed length output record where the +length is specified by the +.Cm cbs +operand. +Input records shorter than the conversion record size are padded with spaces. +Input records longer than the conversion record size are truncated. +The number of truncated input records, if any, are reported to the standard +error output at the completion of the copy. +.It Cm ebcdic , ibm , oldebcdic , oldibm +The same as the +.Cm block +value except that characters are translated from +.Tn ASCII +to +.Tn EBCDIC +after the +records are converted. +(These values imply +.Cm block +if the operand +.Cm cbs +is also specified.) +There are four conversion maps for +.Tn EBCDIC . +The value +.Cm ebcdic +specifies the recommended one which is compatible with +.At V . +The value +.Cm ibm +is a slightly different mapping, which is compatible with the +.At V +.Cm ibm +value. +The values +.Cm oldebcdic +and +.Cm oldibm +are maps used in historic +.Tn AT&T +and pre +.Bx 4.3 Reno +systems. +.It Cm lcase +Transform uppercase characters into lowercase characters. +.It Cm noerror +Do not stop processing on an input error. +When an input error occurs, a diagnostic message followed by the current +input and output block counts will be written to the standard error output +in the same format as the standard completion message. +If the +.Cm sync +conversion is also specified, any missing input data will be replaced +with +.Tn NUL +bytes (or with spaces if a block oriented conversion value was +specified) and processed as a normal input buffer. +If the +.Cm sync +conversion is not specified, the input block is omitted from the output. +On input files which are not tapes or pipes, the file offset +will be positioned past the block in which the error occurred using +.Xr lseek 2 . +.It Cm notrunc +Do not truncate the output file. +This will preserve any blocks in the output file not explicitly written +by +.Nm . +The +.Cm notrunc +value is not supported for tapes. +.It Cm osync +Pad the final output block to the full output block size. +If the input file is not a multiple of the output block size +after conversion, this conversion forces the final output block +to be the same size as preceding blocks for use on devices that require +regularly sized blocks to be written. +This option is incompatible with use of the +.Cm bs= Ns Ar n +block size specification. +.It Cm sparse +If one or more non-final output blocks would consist solely of +.Dv NUL +bytes, try to seek the output file by the required space instead of +filling them with +.Dv NUL Ns s . +This results in a sparse file on some file systems. +.It Cm swab +Swap every pair of input bytes. +If an input buffer has an odd number of bytes, the last byte will be +ignored during swapping. +.It Cm sync +Pad every input block to the input buffer size. +Spaces are used for pad bytes if a block oriented conversion value is +specified, otherwise +.Tn NUL +bytes are used. +.It Cm ucase +Transform lowercase characters into uppercase characters. +.It Cm unblock +Treats the input as a sequence of fixed length records independent of input +and output block boundaries. +The length of the input records is specified by the +.Cm cbs +operand. +Any trailing space characters are discarded and a newline character is +appended. +.El +.El +.Pp +Where sizes are specified, a decimal number of bytes is expected. +Two or more numbers may be separated by an +.Dq x +to indicate a product. +Each number may have one of the following optional suffixes: +.Bl -tag -width 3n -offset indent -compact +.It b +Block; multiply by 512 +.It k +Kibi; multiply by 1024 (1 KiB) +.It m +Mebi; multiply by 1048576 (1 MiB) +.It g +Gibi; multiply by 1073741824 (1 GiB) +.It t +Tebi; multiply by 1099511627776 (1 TiB) +.It w +Word; multiply by the number of bytes in an integer +.El +.Pp +When finished, +.Nm +displays the number of complete and partial input and output blocks, +truncated input records and odd-length byte-swapping blocks to the +standard error output. +A partial input block is one where less than the input block size +was read. +A partial output block is one where less than the output block size +was written. +Partial output blocks to tape devices are considered fatal errors. +Otherwise, the rest of the block will be written. +Partial output blocks to character devices will produce a warning message. +A truncated input block is one where a variable length record oriented +conversion value was specified and the input line was too long to +fit in the conversion record or was not newline terminated. +.Pp +Normally, data resulting from input or conversion or both are aggregated +into output blocks of the specified size. +After the end of input is reached, any remaining output is written as +a block. +This means that the final output block may be shorter than the output +block size. +.Pp +If +.Nm +receives a +.Dv SIGINFO +signal +(see the +.Ic status +argument for +.Xr stty 1 ) , +the current input and output block counts will +be written to the standard error output +in the same format as the standard completion message. +If +.Nm +receives a +.Dv SIGINT +signal, the current input and output block counts will +be written to the standard error output +in the same format as the standard completion message and +.Nm +will exit. +.Sh INPUT AND OUTPUT FLAGS +There are flags valid for input only, for output only, or for either. +.Pp +The flags that apply to both input and output are: +.Bl -tag -width directory +.It Cm alt_io +Use Alternative I/O. +.It Cm async +Use +.Dv SIGIO +signaling for I/O. +.It Cm cloexec +Set the close-on-exec flag. +.It Cm direct +Directly access the data, skipping any caches. +.It Cm directory +Not available for +.Nm . +.It Cm exlock +Atomically obtain an exclusive lock. +.It Cm noctty +Do not consider the file as a potential controlling tty. +.It Cm nofollow +Do not follow symbolic links. +.It Cm nonblock +Do not block on open or I/O requests. +.It Cm nosigpipe +Return +.Er EPIPE +instead of raising +.Dv SIGPIPE . +.It Cm shlock +Atomically obtain a shared lock. +.It Cm sync +All I/O will be performed with full synchronization. +.El +.Pp +The flags that apply to only input are: +.Bl -tag -width directory +.It Cm rdonly +Set the read-only flag. +.It Cm rdwr +Set the read and write flags. +.It Cm rsync +Enable read synchronization, if the +.Cm sync +option is also set. +.El +.Pp +The flags that apply to only output are: +.Bl -tag -width directory +.It Cm append +Append to the output by default. +.It Cm creat +Create the output file. +.It Cm dsync +Wait for all data to be synchronously written. +.It Cm excl +Ensure that output is to a new file. +.It Cm trunc +Truncate the output file before writing. +.It Cm wronly +Set the write-only flag. +.El +See +.Xr open 2 +and +.Xr ioctl 2 +for more details. +.Sh EXIT STATUS +.Ex -std dd +.Sh EXAMPLES +To print summary information in human-readable form: +.Pp +.Dl dd if=/dev/zero of=/dev/null count=1 msgfmt=human +.Pp +To customize the information summary output and print it through +.Xr unvis 3 : +.Bd -literal -offset indent +dd if=/dev/zero of=/dev/null count=1 \e + msgfmt='speed:%E, in %s seconds\en' 2>&1 | unvis +.Ed +.Sh SEE ALSO +.Xr cp 1 , +.Xr mt 1 , +.Xr tr 1 +.Sh STANDARDS +The +.Nm +utility is expected to be a superset of the +.St -p1003.2 +standard. +The +.Cm files +and +.Cm msgfmt +operands and the +.Cm ascii , +.Cm ebcdic , +.Cm ibm , +.Cm oldascii , +.Cm oldebcdic +and +.Cm oldibm +values are extensions to the +.Tn POSIX +standard. +.Sh HISTORY +A +.Nm +utility appeared in +.At v5 . diff --git a/bin/dd/dd.c b/bin/dd/dd.c new file mode 100644 index 0000000..c4fb1e8 --- /dev/null +++ b/bin/dd/dd.c @@ -0,0 +1,616 @@ +/* $NetBSD: dd.c,v 1.51 2016/09/05 01:00:07 sevan Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego and Lance + * Visser of Convex Computer Corporation. + * + * 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> +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1991, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)dd.c 8.5 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: dd.c,v 1.51 2016/09/05 01:00:07 sevan Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/mtio.h> +#include <sys/time.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <locale.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "dd.h" +#include "extern.h" + +static void dd_close(void); +static void dd_in(void); +static void getfdtype(IO *); +static void redup_clean_fd(IO *); +static void setup(void); + +IO in, out; /* input/output state */ +STAT st; /* statistics */ +void (*cfunc)(void); /* conversion function */ +uint64_t cpy_cnt; /* # of blocks to copy */ +static off_t pending = 0; /* pending seek if sparse */ +u_int ddflags; /* conversion options */ +#ifdef NO_IOFLAG +#define iflag O_RDONLY +#define oflag O_CREAT +#else +u_int iflag = O_RDONLY; /* open(2) flags for input file */ +u_int oflag = O_CREAT; /* open(2) flags for output file */ +#endif /* NO_IOFLAG */ +uint64_t cbsz; /* conversion block size */ +u_int files_cnt = 1; /* # of files to copy */ +uint64_t progress = 0; /* display sign of life */ +const u_char *ctab; /* conversion table */ +sigset_t infoset; /* a set blocking SIGINFO */ +const char *msgfmt = "posix"; /* default summary() message format */ + +/* + * Ops for stdin/stdout and crunch'd dd. These are always host ops. + */ +static const struct ddfops ddfops_stdfd = { + .op_open = open, + .op_close = close, + .op_fcntl = fcntl, + .op_ioctl = ioctl, + .op_fstat = fstat, + .op_fsync = fsync, + .op_ftruncate = ftruncate, + .op_lseek = lseek, + .op_read = read, + .op_write = write, +}; +extern const struct ddfops ddfops_prog; + +int +main(int argc, char *argv[]) +{ + int ch; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + errx(EXIT_FAILURE, "usage: dd [operand ...]"); + /* NOTREACHED */ + } + } + argc -= (optind - 1); + argv += (optind - 1); + + jcl(argv); +#ifndef CRUNCHOPS + if (ddfops_prog.op_init && ddfops_prog.op_init() == -1) + err(1, "prog init"); +#endif + setup(); + + (void)signal(SIGINFO, summaryx); + (void)signal(SIGINT, terminate); + (void)sigemptyset(&infoset); + (void)sigaddset(&infoset, SIGINFO); + + (void)atexit(summary); + + while (files_cnt--) + dd_in(); + + dd_close(); + exit(0); + /* NOTREACHED */ +} + +static void +setup(void) +{ +#ifdef CRUNCHOPS + const struct ddfops *prog_ops = &ddfops_stdfd; +#else + const struct ddfops *prog_ops = &ddfops_prog; +#endif + + if (in.name == NULL) { + in.name = "stdin"; + in.fd = STDIN_FILENO; + in.ops = &ddfops_stdfd; + } else { + in.ops = prog_ops; + in.fd = ddop_open(in, in.name, iflag, 0); + if (in.fd < 0) + err(EXIT_FAILURE, "%s", in.name); + /* NOTREACHED */ + + /* Ensure in.fd is outside the stdio descriptor range */ + redup_clean_fd(&in); + } + + getfdtype(&in); + + if (files_cnt > 1 && !(in.flags & ISTAPE)) { + errx(EXIT_FAILURE, "files is not supported for non-tape devices"); + /* NOTREACHED */ + } + + if (out.name == NULL) { + /* No way to check for read access here. */ + out.fd = STDOUT_FILENO; + out.name = "stdout"; + out.ops = &ddfops_stdfd; + } else { + out.ops = prog_ops; + +#ifndef NO_IOFLAG + if ((oflag & O_TRUNC) && (ddflags & C_SEEK)) { + errx(EXIT_FAILURE, "oflag=trunc is incompatible " + "with seek or oseek operands, giving up."); + /* NOTREACHED */ + } + if ((oflag & O_TRUNC) && (ddflags & C_NOTRUNC)) { + errx(EXIT_FAILURE, "oflag=trunc is incompatible " + "with conv=notrunc operand, giving up."); + /* NOTREACHED */ + } +#endif /* NO_IOFLAG */ +#define OFLAGS \ + (oflag | (ddflags & (C_SEEK | C_NOTRUNC) ? 0 : O_TRUNC)) + out.fd = ddop_open(out, out.name, O_RDWR | OFLAGS, DEFFILEMODE); + /* + * May not have read access, so try again with write only. + * Without read we may have a problem if output also does + * not support seeks. + */ + if (out.fd < 0) { + out.fd = ddop_open(out, out.name, O_WRONLY | OFLAGS, + DEFFILEMODE); + out.flags |= NOREAD; + } + if (out.fd < 0) { + err(EXIT_FAILURE, "%s", out.name); + /* NOTREACHED */ + } + + /* Ensure out.fd is outside the stdio descriptor range */ + redup_clean_fd(&out); + } + + getfdtype(&out); + + /* + * Allocate space for the input and output buffers. If not doing + * record oriented I/O, only need a single buffer. + */ + if (!(ddflags & (C_BLOCK|C_UNBLOCK))) { + size_t dbsz = out.dbsz; + if (!(ddflags & C_BS)) + dbsz += in.dbsz - 1; + if ((in.db = malloc(dbsz)) == NULL) { + err(EXIT_FAILURE, NULL); + /* NOTREACHED */ + } + out.db = in.db; + } else if ((in.db = + malloc((u_int)(MAX(in.dbsz, cbsz) + cbsz))) == NULL || + (out.db = malloc((u_int)(out.dbsz + cbsz))) == NULL) { + err(EXIT_FAILURE, NULL); + /* NOTREACHED */ + } + in.dbp = in.db; + out.dbp = out.db; + + /* Position the input/output streams. */ + if (in.offset) + pos_in(); + if (out.offset) + pos_out(); + + /* + * Truncate the output file; ignore errors because it fails on some + * kinds of output files, tapes, for example. + */ + if ((ddflags & (C_OF | C_SEEK | C_NOTRUNC)) == (C_OF | C_SEEK)) + (void)ddop_ftruncate(out, out.fd, (off_t)out.offset * out.dbsz); + + /* + * If converting case at the same time as another conversion, build a + * table that does both at once. If just converting case, use the + * built-in tables. + */ + if (ddflags & (C_LCASE|C_UCASE)) { +#ifdef NO_CONV + /* Should not get here, but just in case... */ + errx(EXIT_FAILURE, "case conv and -DNO_CONV"); + /* NOTREACHED */ +#else /* NO_CONV */ + u_int cnt; + + if (ddflags & C_ASCII || ddflags & C_EBCDIC) { + if (ddflags & C_LCASE) { + for (cnt = 0; cnt < 256; ++cnt) + casetab[cnt] = tolower(ctab[cnt]); + } else { + for (cnt = 0; cnt < 256; ++cnt) + casetab[cnt] = toupper(ctab[cnt]); + } + } else { + if (ddflags & C_LCASE) { + for (cnt = 0; cnt < 256; ++cnt) + casetab[cnt] = tolower(cnt); + } else { + for (cnt = 0; cnt < 256; ++cnt) + casetab[cnt] = toupper(cnt); + } + } + + ctab = casetab; +#endif /* NO_CONV */ + } + + (void)gettimeofday(&st.start, NULL); /* Statistics timestamp. */ +} + +static void +getfdtype(IO *io) +{ + struct mtget mt; + struct stat sb; + + if (io->ops->op_fstat(io->fd, &sb)) { + err(EXIT_FAILURE, "%s", io->name); + /* NOTREACHED */ + } + if (S_ISCHR(sb.st_mode)) + io->flags |= io->ops->op_ioctl(io->fd, MTIOCGET, &mt) + ? ISCHR : ISTAPE; + else if (io->ops->op_lseek(io->fd, (off_t)0, SEEK_CUR) == -1 + && errno == ESPIPE) + io->flags |= ISPIPE; /* XXX fixed in 4.4BSD */ +} + +/* + * Move the parameter file descriptor to a descriptor that is outside the + * stdio descriptor range, if necessary. This is required to avoid + * accidentally outputting completion or error messages into the + * output file that were intended for the tty. + */ +static void +redup_clean_fd(IO *io) +{ + int fd = io->fd; + int newfd; + + if (fd != STDIN_FILENO && fd != STDOUT_FILENO && + fd != STDERR_FILENO) + /* File descriptor is ok, return immediately. */ + return; + + /* + * 3 is the first descriptor greater than STD*_FILENO. Any + * free descriptor valued 3 or above is acceptable... + */ + newfd = io->ops->op_fcntl(fd, F_DUPFD, 3); + if (newfd < 0) { + err(EXIT_FAILURE, "dupfd IO"); + /* NOTREACHED */ + } + + io->ops->op_close(fd); + io->fd = newfd; +} + +static void +dd_in(void) +{ + int flags; + int64_t n; + + for (flags = ddflags;;) { + if (cpy_cnt && (st.in_full + st.in_part) >= cpy_cnt) + return; + + /* + * Clear the buffer first if doing "sync" on input. + * If doing block operations use spaces. This will + * affect not only the C_NOERROR case, but also the + * last partial input block which should be padded + * with zero and not garbage. + */ + if (flags & C_SYNC) { + if (flags & (C_BLOCK|C_UNBLOCK)) + (void)memset(in.dbp, ' ', in.dbsz); + else + (void)memset(in.dbp, 0, in.dbsz); + } + + n = ddop_read(in, in.fd, in.dbp, in.dbsz); + if (n == 0) { + in.dbrcnt = 0; + return; + } + + /* Read error. */ + if (n < 0) { + + /* + * If noerror not specified, die. POSIX requires that + * the warning message be followed by an I/O display. + */ + if (!(flags & C_NOERROR)) { + err(EXIT_FAILURE, "%s", in.name); + /* NOTREACHED */ + } + warn("%s", in.name); + summary(); + + /* + * If it's not a tape drive or a pipe, seek past the + * error. If your OS doesn't do the right thing for + * raw disks this section should be modified to re-read + * in sector size chunks. + */ + if (!(in.flags & (ISPIPE|ISTAPE)) && + ddop_lseek(in, in.fd, (off_t)in.dbsz, SEEK_CUR)) + warn("%s", in.name); + + /* If sync not specified, omit block and continue. */ + if (!(ddflags & C_SYNC)) + continue; + + /* Read errors count as full blocks. */ + in.dbcnt += in.dbrcnt = in.dbsz; + ++st.in_full; + + /* Handle full input blocks. */ + } else if ((uint64_t)n == in.dbsz) { + in.dbcnt += in.dbrcnt = n; + ++st.in_full; + + /* Handle partial input blocks. */ + } else { + /* If sync, use the entire block. */ + if (ddflags & C_SYNC) + in.dbcnt += in.dbrcnt = in.dbsz; + else + in.dbcnt += in.dbrcnt = n; + ++st.in_part; + } + + /* + * POSIX states that if bs is set and no other conversions + * than noerror, notrunc or sync are specified, the block + * is output without buffering as it is read. + */ + if (ddflags & C_BS) { + out.dbcnt = in.dbcnt; + dd_out(1); + in.dbcnt = 0; + continue; + } + + if (ddflags & C_SWAB) { + if ((n = in.dbrcnt) & 1) { + ++st.swab; + --n; + } + swab(in.dbp, in.dbp, n); + } + + in.dbp += in.dbrcnt; + (*cfunc)(); + } +} + +/* + * Cleanup any remaining I/O and flush output. If necessary, output file + * is truncated. + */ +static void +dd_close(void) +{ + + if (cfunc == def) + def_close(); + else if (cfunc == block) + block_close(); + else if (cfunc == unblock) + unblock_close(); + if (ddflags & C_OSYNC && out.dbcnt < out.dbsz) { + (void)memset(out.dbp, 0, out.dbsz - out.dbcnt); + out.dbcnt = out.dbsz; + } + /* If there are pending sparse blocks, make sure + * to write out the final block un-sparse + */ + if ((out.dbcnt == 0) && pending) { + memset(out.db, 0, out.dbsz); + out.dbcnt = out.dbsz; + out.dbp = out.db + out.dbcnt; + pending -= out.dbsz; + } + if (out.dbcnt) + dd_out(1); + + /* + * Reporting nfs write error may be deferred until next + * write(2) or close(2) system call. So, we need to do an + * extra check. If an output is stdout, the file structure + * may be shared with other processes and close(2) just + * decreases the reference count. + */ + if (out.fd == STDOUT_FILENO && ddop_fsync(out, out.fd) == -1 + && errno != EINVAL) { + err(EXIT_FAILURE, "fsync stdout"); + /* NOTREACHED */ + } + if (ddop_close(out, out.fd) == -1) { + err(EXIT_FAILURE, "close"); + /* NOTREACHED */ + } +} + +void +dd_out(int force) +{ + static int warned; + int64_t cnt, n, nw; + u_char *outp; + + /* + * Write one or more blocks out. The common case is writing a full + * output block in a single write; increment the full block stats. + * Otherwise, we're into partial block writes. If a partial write, + * and it's a character device, just warn. If a tape device, quit. + * + * The partial writes represent two cases. 1: Where the input block + * was less than expected so the output block was less than expected. + * 2: Where the input block was the right size but we were forced to + * write the block in multiple chunks. The original versions of dd(1) + * never wrote a block in more than a single write, so the latter case + * never happened. + * + * One special case is if we're forced to do the write -- in that case + * we play games with the buffer size, and it's usually a partial write. + */ + outp = out.db; + for (n = force ? out.dbcnt : out.dbsz;; n = out.dbsz) { + for (cnt = n;; cnt -= nw) { + + if (!force && ddflags & C_SPARSE) { + int sparse, i; + sparse = 1; /* Is buffer sparse? */ + for (i = 0; i < cnt; i++) + if (outp[i] != 0) { + sparse = 0; + break; + } + if (sparse) { + pending += cnt; + outp += cnt; + nw = 0; + break; + } + } + if (pending != 0) { + if (ddop_lseek(out, + out.fd, pending, SEEK_CUR) == -1) + err(EXIT_FAILURE, "%s: seek error creating sparse file", + out.name); + } + nw = bwrite(&out, outp, cnt); + if (nw <= 0) { + if (nw == 0) + errx(EXIT_FAILURE, + "%s: end of device", out.name); + /* NOTREACHED */ + if (errno != EINTR) + err(EXIT_FAILURE, "%s", out.name); + /* NOTREACHED */ + nw = 0; + } + if (pending) { + st.bytes += pending; + st.sparse += pending/out.dbsz; + st.out_full += pending/out.dbsz; + pending = 0; + } + outp += nw; + st.bytes += nw; + if (nw == n) { + if ((uint64_t)n != out.dbsz) + ++st.out_part; + else + ++st.out_full; + break; + } + ++st.out_part; + if (nw == cnt) + break; + if (out.flags & ISCHR && !warned) { + warned = 1; + warnx("%s: short write on character device", out.name); + } + if (out.flags & ISTAPE) + errx(EXIT_FAILURE, + "%s: short write on tape device", out.name); + /* NOTREACHED */ + + } + if ((out.dbcnt -= n) < out.dbsz) + break; + } + + /* Reassemble the output block. */ + if (out.dbcnt) + (void)memmove(out.db, out.dbp - out.dbcnt, out.dbcnt); + out.dbp = out.db + out.dbcnt; + + if (progress && (st.out_full + st.out_part) % progress == 0) + (void)write(STDERR_FILENO, ".", 1); +} + +/* + * A protected against SIGINFO write + */ +ssize_t +bwrite(IO *io, const void *buf, size_t len) +{ + sigset_t oset; + ssize_t rv; + int oerrno; + + (void)sigprocmask(SIG_BLOCK, &infoset, &oset); + rv = io->ops->op_write(io->fd, buf, len); + oerrno = errno; + (void)sigprocmask(SIG_SETMASK, &oset, NULL); + errno = oerrno; + return (rv); +} diff --git a/bin/dd/dd.h b/bin/dd/dd.h new file mode 100644 index 0000000..2b2712d --- /dev/null +++ b/bin/dd/dd.h @@ -0,0 +1,126 @@ +/* $NetBSD: dd.h,v 1.16 2015/03/18 13:23:49 manu Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego and Lance + * Visser of Convex Computer Corporation. + * + * 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. + * + * @(#)dd.h 8.3 (Berkeley) 4/2/94 + */ + +#include <sys/stat.h> + +struct ddfops { + int (*op_init)(void); + + int (*op_open)(const char *, int, ...); + int (*op_close)(int); + + int (*op_fcntl)(int, int, ...); + int (*op_ioctl)(int, unsigned long, ...); + + int (*op_fstat)(int, struct stat *); + int (*op_fsync)(int); + int (*op_ftruncate)(int, off_t); + + off_t (*op_lseek)(int, off_t, int); + + ssize_t (*op_read)(int, void *, size_t); + ssize_t (*op_write)(int, const void *, size_t); +}; + +#define ddop_open(dir, a1, a2, ...) dir.ops->op_open(a1, a2, __VA_ARGS__) +#define ddop_close(dir, a1) dir.ops->op_close(a1) +#define ddop_fcntl(dir, a1, a2, ...) dir.ops->op_fcntl(a1, a2, __VA_ARGS__) +#define ddop_ioctl(dir, a1, a2, ...) dir.ops->op_ioctl(a1, a2, __VA_ARGS__) +#define ddop_fsync(dir, a1) dir.ops->op_fsync(a1) +#define ddop_ftruncate(dir, a1, a2) dir.ops->op_ftruncate(a1, a2) +#define ddop_lseek(dir, a1, a2, a3) dir.ops->op_lseek(a1, a2, a3) +#define ddop_read(dir, a1, a2, a3) dir.ops->op_read(a1, a2, a3) +#define ddop_write(dir, a1, a2, a3) dir.ops->op_write(a1, a2, a3) + +/* Input/output stream state. */ +typedef struct { + u_char *db; /* buffer address */ + u_char *dbp; /* current buffer I/O address */ + uint64_t dbcnt; /* current buffer byte count */ + int64_t dbrcnt; /* last read byte count */ + uint64_t dbsz; /* buffer size */ + +#define ISCHR 0x01 /* character device (warn on short) */ +#define ISPIPE 0x02 /* pipe (not truncatable) */ +#define ISTAPE 0x04 /* tape (not seekable) */ +#define NOREAD 0x08 /* not readable */ + u_int flags; + + const char *name; /* name */ + int fd; /* file descriptor */ + uint64_t offset; /* # of blocks to skip */ + struct ddfops const *ops; /* ops to use with fd */ +} IO; + +typedef struct { + uint64_t in_full; /* # of full input blocks */ + uint64_t in_part; /* # of partial input blocks */ + uint64_t out_full; /* # of full output blocks */ + uint64_t out_part; /* # of partial output blocks */ + uint64_t trunc; /* # of truncated records */ + uint64_t swab; /* # of odd-length swab blocks */ + uint64_t sparse; /* # of sparse output blocks */ + uint64_t bytes; /* # of bytes written */ + struct timeval start; /* start time of dd */ +} STAT; + +/* Flags (in ddflags, iflag and oflag). */ +#define C_NONE 0x00000 +#define C_ASCII 0x00001 +#define C_BLOCK 0x00002 +#define C_BS 0x00004 +#define C_CBS 0x00008 +#define C_COUNT 0x00010 +#define C_EBCDIC 0x00020 +#define C_FILES 0x00040 +#define C_IBS 0x00080 +#define C_IF 0x00100 +#define C_LCASE 0x00200 +#define C_NOERROR 0x00400 +#define C_NOTRUNC 0x00800 +#define C_OBS 0x01000 +#define C_OF 0x02000 +#define C_SEEK 0x04000 +#define C_SKIP 0x08000 +#define C_SWAB 0x10000 +#define C_SYNC 0x20000 +#define C_UCASE 0x40000 +#define C_UNBLOCK 0x80000 +#define C_OSYNC 0x100000 +#define C_SPARSE 0x200000 +#define C_IFLAG 0x400000 +#define C_OFLAG 0x800000 diff --git a/bin/dd/dd_hostops.c b/bin/dd/dd_hostops.c new file mode 100644 index 0000000..d6e7a89 --- /dev/null +++ b/bin/dd/dd_hostops.c @@ -0,0 +1,53 @@ +/* $NetBSD: dd_hostops.c,v 1.1 2011/02/04 19:42:12 pooka Exp $ */ + +/*- + * Copyright (c) 2010 The NetBSD Foundation, Inc. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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> +#ifndef lint +__RCSID("$NetBSD: dd_hostops.c,v 1.1 2011/02/04 19:42:12 pooka Exp $"); +#endif /* !lint */ + +#include <sys/types.h> +#include <sys/ioctl.h> + +#include <fcntl.h> +#include <unistd.h> + +#include "dd.h" + +const struct ddfops ddfops_prog = { + .op_open = open, + .op_close = close, + .op_fcntl = fcntl, + .op_ioctl = ioctl, + .op_fstat = fstat, + .op_fsync = fsync, + .op_ftruncate = ftruncate, + .op_lseek = lseek, + .op_read = read, + .op_write = write, +}; diff --git a/bin/dd/dd_rumpops.c b/bin/dd/dd_rumpops.c new file mode 100644 index 0000000..71f7db4 --- /dev/null +++ b/bin/dd/dd_rumpops.c @@ -0,0 +1,52 @@ +/* $NetBSD: dd_rumpops.c,v 1.1 2011/02/04 19:42:12 pooka Exp $ */ + +/*- + * Copyright (c) 2010 The NetBSD Foundation, Inc. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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> +#ifndef lint +__RCSID("$NetBSD: dd_rumpops.c,v 1.1 2011/02/04 19:42:12 pooka Exp $"); +#endif /* !lint */ + +#include <rump/rump_syscalls.h> +#include <rump/rumpclient.h> + +#include "dd.h" + +const struct ddfops ddfops_prog = { + .op_init = rumpclient_init, + + .op_open = rump_sys_open, + .op_close = rump_sys_close, + .op_fcntl = rump_sys_fcntl, + .op_ioctl = rump_sys_ioctl, + .op_fstat = rump_sys_fstat, + .op_fsync = rump_sys_fsync, + .op_ftruncate = rump_sys_ftruncate, + .op_lseek = rump_sys_lseek, + .op_read = rump_sys_read, + .op_write = rump_sys_write, +}; diff --git a/bin/dd/extern.h b/bin/dd/extern.h new file mode 100644 index 0000000..27b51a0 --- /dev/null +++ b/bin/dd/extern.h @@ -0,0 +1,86 @@ +/* $NetBSD: extern.h,v 1.23 2015/03/18 13:23:49 manu Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego and Lance + * Visser of Convex Computer Corporation. + * + * 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. + * + * @(#)extern.h 8.3 (Berkeley) 4/2/94 + */ + +#include <sys/cdefs.h> + +#ifdef NO_CONV +__dead void block(void); +__dead void block_close(void); +__dead void unblock(void); +__dead void unblock_close(void); +#else +void block(void); +void block_close(void); +void unblock(void); +void unblock_close(void); +#endif + +#ifndef NO_MSGFMT +int dd_write_msg(const char *, int); +#endif + +void dd_out(int); +void def(void); +void def_close(void); +void jcl(char **); +void pos_in(void); +void pos_out(void); +void summary(void); +void summaryx(int); +__dead void terminate(int); +void unblock(void); +void unblock_close(void); +ssize_t bwrite(IO *, const void *, size_t); + +extern IO in, out; +extern STAT st; +extern void (*cfunc)(void); +extern uint64_t cpy_cnt; +extern uint64_t cbsz; +extern u_int ddflags; +#ifndef NO_IOFLAG +extern u_int iflag; +extern u_int oflag; +#endif /* NO_IOFLAG */ +extern u_int files_cnt; +extern uint64_t progress; +extern const u_char *ctab; +extern const u_char a2e_32V[], a2e_POSIX[]; +extern const u_char e2a_32V[], e2a_POSIX[]; +extern const u_char a2ibm_32V[], a2ibm_POSIX[]; +extern u_char casetab[]; +extern const char *msgfmt; diff --git a/bin/dd/misc.c b/bin/dd/misc.c new file mode 100644 index 0000000..0fac98b --- /dev/null +++ b/bin/dd/misc.c @@ -0,0 +1,342 @@ +/* $NetBSD: misc.c,v 1.23 2011/11/07 22:24:23 jym Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego and Lance + * Visser of Convex Computer Corporation. + * + * 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> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)misc.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: misc.c,v 1.23 2011/11/07 22:24:23 jym Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/time.h> + +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <util.h> +#include <inttypes.h> + +#include "dd.h" +#include "extern.h" + +#define tv2mS(tv) ((tv).tv_sec * 1000LL + ((tv).tv_usec + 500) / 1000) + +static void posix_summary(void); +#ifndef NO_MSGFMT +static void custom_summary(void); +static void human_summary(void); +static void quiet_summary(void); + +static void buffer_write(const char *, size_t, int); +#endif /* NO_MSGFMT */ + +void +summary(void) +{ + + if (progress) + (void)write(STDERR_FILENO, "\n", 1); + +#ifdef NO_MSGFMT + return posix_summary(); +#else /* NO_MSGFMT */ + if (strncmp(msgfmt, "human", sizeof("human")) == 0) + return human_summary(); + + if (strncmp(msgfmt, "posix", sizeof("posix")) == 0) + return posix_summary(); + + if (strncmp(msgfmt, "quiet", sizeof("quiet")) == 0) + return quiet_summary(); + + return custom_summary(); +#endif /* NO_MSGFMT */ +} + +static void +posix_summary(void) +{ + char buf[100]; + int64_t mS; + struct timeval tv; + + if (progress) + (void)write(STDERR_FILENO, "\n", 1); + + (void)gettimeofday(&tv, NULL); + mS = tv2mS(tv) - tv2mS(st.start); + if (mS == 0) + mS = 1; + + /* Use snprintf(3) so that we don't reenter stdio(3). */ + (void)snprintf(buf, sizeof(buf), + "%llu+%llu records in\n%llu+%llu records out\n", + (unsigned long long)st.in_full, (unsigned long long)st.in_part, + (unsigned long long)st.out_full, (unsigned long long)st.out_part); + (void)write(STDERR_FILENO, buf, strlen(buf)); + if (st.swab) { + (void)snprintf(buf, sizeof(buf), "%llu odd length swab %s\n", + (unsigned long long)st.swab, + (st.swab == 1) ? "block" : "blocks"); + (void)write(STDERR_FILENO, buf, strlen(buf)); + } + if (st.trunc) { + (void)snprintf(buf, sizeof(buf), "%llu truncated %s\n", + (unsigned long long)st.trunc, + (st.trunc == 1) ? "block" : "blocks"); + (void)write(STDERR_FILENO, buf, strlen(buf)); + } + if (st.sparse) { + (void)snprintf(buf, sizeof(buf), "%llu sparse output %s\n", + (unsigned long long)st.sparse, + (st.sparse == 1) ? "block" : "blocks"); + (void)write(STDERR_FILENO, buf, strlen(buf)); + } + (void)snprintf(buf, sizeof(buf), + "%llu bytes transferred in %lu.%03d secs (%llu bytes/sec)\n", + (unsigned long long) st.bytes, + (long) (mS / 1000), + (int) (mS % 1000), + (unsigned long long) (st.bytes * 1000LL / mS)); + (void)write(STDERR_FILENO, buf, strlen(buf)); +} + +/* ARGSUSED */ +void +summaryx(int notused) +{ + + summary(); +} + +/* ARGSUSED */ +void +terminate(int signo) +{ + + summary(); + (void)raise_default_signal(signo); + _exit(127); +} + +#ifndef NO_MSGFMT +/* + * Buffer write(2) calls + */ +static void +buffer_write(const char *str, size_t size, int flush) +{ + static char wbuf[128]; + static size_t cnt = 0; /* Internal counter to allow wbuf to wrap */ + + unsigned int i; + + for (i = 0; i < size; i++) { + if (str != NULL) { + wbuf[cnt++] = str[i]; + } + if (cnt >= sizeof(wbuf)) { + (void)write(STDERR_FILENO, wbuf, cnt); + cnt = 0; + } + } + + if (flush != 0) { + (void)write(STDERR_FILENO, wbuf, cnt); + cnt = 0; + } +} + +/* + * Write summary to stderr according to format 'fmt'. If 'enable' is 0, it + * will not attempt to write anything. Can be used to validate the + * correctness of the 'fmt' string. + */ +int +dd_write_msg(const char *fmt, int enable) +{ + char hbuf[7], nbuf[32]; + const char *ptr; + int64_t mS; + struct timeval tv; + + (void)gettimeofday(&tv, NULL); + mS = tv2mS(tv) - tv2mS(st.start); + if (mS == 0) + mS = 1; + +#define ADDC(c) do { if (enable != 0) buffer_write(&c, 1, 0); } \ + while (/*CONSTCOND*/0) +#define ADDS(p) do { if (enable != 0) buffer_write(p, strlen(p), 0); } \ + while (/*CONSTCOND*/0) + + for (ptr = fmt; *ptr; ptr++) { + if (*ptr != '%') { + ADDC(*ptr); + continue; + } + + switch (*++ptr) { + case 'b': + (void)snprintf(nbuf, sizeof(nbuf), "%llu", + (unsigned long long)st.bytes); + ADDS(nbuf); + break; + case 'B': + if (humanize_number(hbuf, sizeof(hbuf), + st.bytes, "B", + HN_AUTOSCALE, HN_DECIMAL) == -1) + warnx("humanize_number (bytes transferred)"); + ADDS(hbuf); + break; + case 'e': + (void)snprintf(nbuf, sizeof(nbuf), "%llu", + (unsigned long long) (st.bytes * 1000LL / mS)); + ADDS(nbuf); + break; + case 'E': + if (humanize_number(hbuf, sizeof(hbuf), + st.bytes * 1000LL / mS, "B", + HN_AUTOSCALE, HN_DECIMAL) == -1) + warnx("humanize_number (bytes per second)"); + ADDS(hbuf); ADDS("/sec"); + break; + case 'i': + (void)snprintf(nbuf, sizeof(nbuf), "%llu", + (unsigned long long)st.in_part); + ADDS(nbuf); + break; + case 'I': + (void)snprintf(nbuf, sizeof(nbuf), "%llu", + (unsigned long long)st.in_full); + ADDS(nbuf); + break; + case 'o': + (void)snprintf(nbuf, sizeof(nbuf), "%llu", + (unsigned long long)st.out_part); + ADDS(nbuf); + break; + case 'O': + (void)snprintf(nbuf, sizeof(nbuf), "%llu", + (unsigned long long)st.out_full); + ADDS(nbuf); + break; + case 's': + (void)snprintf(nbuf, sizeof(nbuf), "%li.%03d", + (long) (mS / 1000), (int) (mS % 1000)); + ADDS(nbuf); + break; + case 'p': + (void)snprintf(nbuf, sizeof(nbuf), "%llu", + (unsigned long long)st.sparse); + ADDS(nbuf); + break; + case 't': + (void)snprintf(nbuf, sizeof(nbuf), "%llu", + (unsigned long long)st.trunc); + ADDS(nbuf); + break; + case 'w': + (void)snprintf(nbuf, sizeof(nbuf), "%llu", + (unsigned long long)st.swab); + ADDS(nbuf); + break; + case 'P': + ADDS("block"); + if (st.sparse != 1) ADDS("s"); + break; + case 'T': + ADDS("block"); + if (st.trunc != 1) ADDS("s"); + break; + case 'W': + ADDS("block"); + if (st.swab != 1) ADDS("s"); + break; + case '%': + ADDC(*ptr); + break; + default: + if (*ptr == '\0') + goto done; + errx(EXIT_FAILURE, "unknown specifier '%c' in " + "msgfmt string", *ptr); + /* NOTREACHED */ + } + } + +done: + /* flush buffer */ + buffer_write(NULL, 0, 1); + return 0; +} + +static void +custom_summary(void) +{ + + dd_write_msg(msgfmt, 1); +} + +static void +human_summary(void) +{ + (void)dd_write_msg("%I+%i records in\n%O+%o records out\n", 1); + if (st.swab) { + (void)dd_write_msg("%w odd length swab %W\n", 1); + } + if (st.trunc) { + (void)dd_write_msg("%t truncated %T\n", 1); + } + if (st.sparse) { + (void)dd_write_msg("%p sparse output %P\n", 1); + } + (void)dd_write_msg("%b bytes (%B) transferred in %s secs " + "(%e bytes/sec - %E)\n", 1); +} + +static void +quiet_summary(void) +{ + + /* stay quiet */ +} +#endif /* NO_MSGFMT */ diff --git a/bin/dd/position.c b/bin/dd/position.c new file mode 100644 index 0000000..36dd580 --- /dev/null +++ b/bin/dd/position.c @@ -0,0 +1,185 @@ +/* $NetBSD: position.c,v 1.18 2010/11/22 21:04:28 pooka Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego and Lance + * Visser of Convex Computer Corporation. + * + * 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> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)position.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: position.c,v 1.18 2010/11/22 21:04:28 pooka Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/mtio.h> +#include <sys/time.h> + +#include <err.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "dd.h" +#include "extern.h" + +/* + * Position input/output data streams before starting the copy. Device type + * dependent. Seekable devices use lseek, and the rest position by reading. + * Seeking past the end of file can cause null blocks to be written to the + * output. + */ +void +pos_in(void) +{ + int bcnt, cnt, nr, warned; + + /* If not a pipe or tape device, try to seek on it. */ + if (!(in.flags & (ISPIPE|ISTAPE))) { + if (ddop_lseek(in, in.fd, + (off_t)in.offset * (off_t)in.dbsz, SEEK_CUR) == -1) { + err(EXIT_FAILURE, "%s", in.name); + /* NOTREACHED */ + } + return; + /* NOTREACHED */ + } + + /* + * Read the data. If a pipe, read until satisfy the number of bytes + * being skipped. No differentiation for reading complete and partial + * blocks for other devices. + */ + for (bcnt = in.dbsz, cnt = in.offset, warned = 0; cnt;) { + if ((nr = ddop_read(in, in.fd, in.db, bcnt)) > 0) { + if (in.flags & ISPIPE) { + if (!(bcnt -= nr)) { + bcnt = in.dbsz; + --cnt; + } + } else + --cnt; + continue; + } + + if (nr == 0) { + if (files_cnt > 1) { + --files_cnt; + continue; + } + errx(EXIT_FAILURE, "skip reached end of input"); + /* NOTREACHED */ + } + + /* + * Input error -- either EOF with no more files, or I/O error. + * If noerror not set die. POSIX requires that the warning + * message be followed by an I/O display. + */ + if (ddflags & C_NOERROR) { + if (!warned) { + + warn("%s", in.name); + warned = 1; + summary(); + } + continue; + } + err(EXIT_FAILURE, "%s", in.name); + /* NOTREACHED */ + } +} + +void +pos_out(void) +{ + struct mtop t_op; + int n; + uint64_t cnt; + + /* + * If not a tape, try seeking on the file. Seeking on a pipe is + * going to fail, but don't protect the user -- they shouldn't + * have specified the seek operand. + */ + if (!(out.flags & ISTAPE)) { + if (ddop_lseek(out, out.fd, + (off_t)out.offset * (off_t)out.dbsz, SEEK_SET) == -1) + err(EXIT_FAILURE, "%s", out.name); + /* NOTREACHED */ + return; + } + + /* If no read access, try using mtio. */ + if (out.flags & NOREAD) { + t_op.mt_op = MTFSR; + t_op.mt_count = out.offset; + + if (ddop_ioctl(out, out.fd, MTIOCTOP, &t_op) < 0) + err(EXIT_FAILURE, "%s", out.name); + /* NOTREACHED */ + return; + } + + /* Read it. */ + for (cnt = 0; cnt < out.offset; ++cnt) { + if ((n = ddop_read(out, out.fd, out.db, out.dbsz)) > 0) + continue; + + if (n < 0) + err(EXIT_FAILURE, "%s", out.name); + /* NOTREACHED */ + + /* + * If reach EOF, fill with NUL characters; first, back up over + * the EOF mark. Note, cnt has not yet been incremented, so + * the EOF read does not count as a seek'd block. + */ + t_op.mt_op = MTBSR; + t_op.mt_count = 1; + if (ddop_ioctl(out, out.fd, MTIOCTOP, &t_op) == -1) + err(EXIT_FAILURE, "%s", out.name); + /* NOTREACHED */ + + while (cnt++ < out.offset) + if ((uint64_t)(n = bwrite(&out, + out.db, out.dbsz)) != out.dbsz) + err(EXIT_FAILURE, "%s", out.name); + /* NOTREACHED */ + break; + } +} diff --git a/bin/df/df.1 b/bin/df/df.1 new file mode 100644 index 0000000..6ac5ed0 --- /dev/null +++ b/bin/df/df.1 @@ -0,0 +1,206 @@ +.\" $NetBSD: df.1,v 1.49 2018/08/26 23:34:52 sevan Exp $ +.\" +.\" Copyright (c) 1989, 1990, 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. +.\" +.\" @(#)df.1 8.2 (Berkeley) 1/13/92 +.\" +.Dd August 27, 2018 +.Dt DF 1 +.Os +.Sh NAME +.Nm df +.Nd display free disk space +.Sh SYNOPSIS +.Nm +.Op Fl agln +.Op Fl Ghkm | Fl ihkm | Fl Pk +.Op Fl t Ar type +.Op Ar file | Ar file_system ... +.Sh DESCRIPTION +.Nm +displays statistics about the amount of free disk space on the specified +.Ar file_system +or on the file system of which +.Ar file +is a part. +By default, all sizes are reported in 512-byte block counts. +If neither a file or a +.Ar file_system +operand is specified, +statistics for all mounted file systems are displayed +(subject to the +.Fl l +and +.Fl t +options below). +.Pp +Note that the printed count of available blocks takes +.Va minfree +into account, and thus will be negative when the number of free blocks +on the filesystem is less than +.Va minfree . +.Pp +The following options are available: +.Bl -tag -width Ds +.It Fl a +Show all mount points, +including those that were mounted with the +.Dv MNT_IGNORE +flag. +.It Fl G +Display all the fields of the structure(s) returned by +.Xr statvfs 2 . +This option cannot be used with the +.Fl i +or +.Fl P +options, and it is modelled after the Solaris +.Fl g +option. +This option will override the +.Fl g , +.Fl h , +.Fl k , +and +.Fl m +options, as well as any setting of +.Ev BLOCKSIZE . +.It Fl g +The +.Fl g +option causes the numbers to be reported in gigabytes (1024*1024*1024 +bytes). +.It Fl h +"Human-readable" output. +Use unit suffixes: Byte, Kilobyte, Megabyte, +Gigabyte, Terabyte, Petabyte, Exabyte in order to reduce the number of +digits to four or less. +.It Fl i +Include statistics on the number of free inodes. +.It Fl k +By default, all sizes are reported in 512-byte block counts. +The +.Fl k +option causes the numbers to be reported in kilobytes (1024 bytes). +.It Fl l +Display statistics only about mounted file systems with the +.Dv MNT_LOCAL +flag set. +If a non-local file system is given as an argument, a +warning is issued and no information is given on that file system. +.It Fl m +The +.Fl m +option causes the numbers to be reported in megabytes (1024*1024 bytes). +.It Fl n +Print out the previously obtained statistics from the file systems. +This option should be used if it is possible that one or more +file systems are in a state such that they will not be able to provide +statistics without a long delay. +When this option is specified, +.Nm +will not request new statistics from the file systems, but will respond +with the possibly stale statistics that were previously obtained. +.It Fl P +Produce output in the following portable format: +.Pp +If both the +.Fl P +and +.Fl k +option are specified, the output will be preceded by the following header +line, formatted to match the data following it: +.Bd -literal +"Filesystem 1024-blocks Used Available Capacity Mounted on\en" +.Ed +.Pp +If the +.Fl P +option is specified without the +.Fl k +options, the output will be preceded by the following header line, +formatted to match the data following it: +.Bd -literal +"Filesystem <blksize>-blocks Used Available Capacity Mounted on\en" +.Ed +.Pp +The header line is followed by data formatted as follows: +.Bd -literal +"%s %d %d %d %d%% %s\en", <file system name>, <total space>, + <space used>, <space free>, <percentage used>, + <file system root> +.Ed +.Pp +Note that the +.Fl i +option may not be specified with +.Fl P . +.It Fl t Ar type +Is used to indicate the actions should only be taken on +filesystems of the specified type. +More than one type may be specified in a comma-separated list. +The list of filesystem types can be prefixed with +.Dq no +to specify the filesystem types for which action should +.Em not +be taken. +If a file system is given on the command line that is not of +the specified type, a warning is issued and no information is given on +that file system. +.El +.Sh ENVIRONMENT +.Bl -tag -width BLOCKSIZE +.It Ev BLOCKSIZE +If the environment variable +.Ev BLOCKSIZE +is set, and the +.Fl g , +.Fl h , +.Fl k +and +.Fl m +options are not specified, the block counts will be displayed in units of that +size block. +.El +.Sh SEE ALSO +.Xr quota 1 , +.Xr fstatvfs 2 , +.Xr getvfsstat 2 , +.Xr statvfs 2 , +.Xr getbsize 3 , +.Xr getmntinfo 3 , +.Xr fs 5 , +.Xr fstab 5 , +.Xr mount 8 , +.Xr quot 8 , +.Xr tunefs 8 +.Sh HISTORY +A +.Nm +utility appeared in +.At v1 . diff --git a/bin/df/df.c b/bin/df/df.c new file mode 100644 index 0000000..9392d0c --- /dev/null +++ b/bin/df/df.c @@ -0,0 +1,520 @@ +/* $NetBSD: df.c,v 1.93 2018/08/26 23:34:52 sevan Exp $ */ + +/* + * Copyright (c) 1980, 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT( +"@(#) Copyright (c) 1980, 1990, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)df.c 8.7 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: df.c,v 1.93 2018/08/26 23:34:52 sevan Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/mount.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <locale.h> +#include <util.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <util.h> + +static char *getmntpt(const char *); +static void prtstat(struct statvfs *, int); +static int selected(const char *, size_t); +static void maketypelist(char *); +static size_t regetmntinfo(struct statvfs **, size_t); +__dead static void usage(void); +static void prthumanval(int64_t, const char *); +static void prthuman(struct statvfs *, int64_t, int64_t); + +static int aflag, gflag, hflag, iflag, lflag, nflag, Pflag; +static long usize; +static char **typelist; + +int +main(int argc, char *argv[]) +{ + struct stat stbuf; + struct statvfs *mntbuf; + long mntsize; + int ch, i, maxwidth, width; + char *mntpt; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + while ((ch = getopt(argc, argv, "aGghiklmnPt:")) != -1) + switch (ch) { + case 'a': + aflag = 1; + break; + case 'g': + hflag = 0; + usize = 1024 * 1024 * 1024; + break; + case 'G': + gflag = 1; + break; + case 'h': + hflag = 1; + usize = 0; + break; + case 'i': + iflag = 1; + break; + case 'k': + hflag = 0; + usize = 1024; + break; + case 'l': + lflag = 1; + break; + case 'm': + hflag = 0; + usize = 1024 * 1024; + break; + case 'n': + nflag = 1; + break; + case 'P': + Pflag = 1; + break; + case 't': + if (typelist != NULL) + errx(EXIT_FAILURE, + "only one -t option may be specified."); + maketypelist(optarg); + break; + case '?': + default: + usage(); + } + + if (gflag && (Pflag || iflag)) + errx(EXIT_FAILURE, + "only one of -G and -P or -i may be specified"); + if (Pflag && iflag) + errx(EXIT_FAILURE, + "only one of -P and -i may be specified"); +#if 0 + /* + * The block size cannot be checked until after getbsize() is called. + */ + if (Pflag && (hflag || (usize != 1024 && usize != 512))) + errx(EXIT_FAILURE, + "non-standard block size incompatible with -P"); +#endif + argc -= optind; + argv += optind; + + mntsize = getmntinfo(&mntbuf, MNT_NOWAIT); + if (mntsize == 0) + err(EXIT_FAILURE, + "retrieving information on mounted file systems"); + + if (*argv == NULL) { + mntsize = regetmntinfo(&mntbuf, mntsize); + } else { + if ((mntbuf = malloc(argc * sizeof(*mntbuf))) == NULL) + err(EXIT_FAILURE, "can't allocate statvfs array"); + mntsize = 0; + for (/*EMPTY*/; *argv != NULL; argv++) { + if (stat(*argv, &stbuf) < 0) { + if ((mntpt = getmntpt(*argv)) == 0) { + warn("%s", *argv); + continue; + } + } else if (S_ISBLK(stbuf.st_mode)) { + if ((mntpt = getmntpt(*argv)) == 0) + mntpt = *argv; + } else + mntpt = *argv; + /* + * Statfs does not take a `wait' flag, so we cannot + * implement nflag here. + */ + if (!statvfs(mntpt, &mntbuf[mntsize])) + if (lflag && + (mntbuf[mntsize].f_flag & MNT_LOCAL) == 0) + warnx("Warning: %s is not a local %s", + *argv, "file system"); + else if + (!selected(mntbuf[mntsize].f_fstypename, + sizeof(mntbuf[mntsize].f_fstypename))) + warnx("Warning: %s mounted as a %s %s", + *argv, + mntbuf[mntsize].f_fstypename, + "file system"); + else + ++mntsize; + else + warn("%s", *argv); + } + } + + maxwidth = 0; + for (i = 0; i < mntsize; i++) { + width = (int)strlen(mntbuf[i].f_mntfromname); + if (width > maxwidth) + maxwidth = width; + } + for (i = 0; i < mntsize; i++) + prtstat(&mntbuf[i], maxwidth); + return 0; +} + +static char * +getmntpt(const char *name) +{ + size_t mntsize, i; + struct statvfs *mntbuf; + + mntsize = getmntinfo(&mntbuf, MNT_NOWAIT); + if (mntsize == 0) + err(EXIT_FAILURE, "Can't get mount information"); + for (i = 0; i < mntsize; i++) { + if (!strcmp(mntbuf[i].f_mntfromname, name)) + return mntbuf[i].f_mntonname; + } + return 0; +} + +static enum { IN_LIST, NOT_IN_LIST } which; + +static int +selected(const char *type, size_t len) +{ + char **av; + + /* If no type specified, it's always selected. */ + if (typelist == NULL) + return 1; + for (av = typelist; *av != NULL; ++av) + if (!strncmp(type, *av, len)) + return which == IN_LIST ? 1 : 0; + return which == IN_LIST ? 0 : 1; +} + +static void +maketypelist(char *fslist) +{ + size_t i; + char *nextcp, **av; + + if ((fslist == NULL) || (fslist[0] == '\0')) + errx(EXIT_FAILURE, "empty type list"); + + /* + * XXX + * Note: the syntax is "noxxx,yyy" for no xxx's and + * no yyy's, not the more intuitive "noyyy,noyyy". + */ + if (fslist[0] == 'n' && fslist[1] == 'o') { + fslist += 2; + which = NOT_IN_LIST; + } else + which = IN_LIST; + + /* Count the number of types. */ + for (i = 1, nextcp = fslist; + (nextcp = strchr(nextcp, ',')) != NULL; i++) + ++nextcp; + + /* Build an array of that many types. */ + if ((av = typelist = malloc((i + 1) * sizeof(*av))) == NULL) + err(EXIT_FAILURE, "can't allocate type array"); + av[0] = fslist; + for (i = 1, nextcp = fslist; + (nextcp = strchr(nextcp, ',')) != NULL; i++) { + *nextcp = '\0'; + av[i] = ++nextcp; + } + /* Terminate the array. */ + av[i] = NULL; +} + +/* + * Make a pass over the filesystem info in ``mntbuf'' filtering out + * filesystem types not in ``fsmask'' and possibly re-stating to get + * current (not cached) info. Returns the new count of valid statvfs bufs. + */ +static size_t +regetmntinfo(struct statvfs **mntbufp, size_t mntsize) +{ + size_t i, j; + struct statvfs *mntbuf; + + if (!lflag && typelist == NULL && aflag) + return nflag ? mntsize : (size_t)getmntinfo(mntbufp, MNT_WAIT); + + mntbuf = *mntbufp; + j = 0; + for (i = 0; i < mntsize; i++) { + if (!aflag && (mntbuf[i].f_flag & MNT_IGNORE) != 0) + continue; + if (lflag && (mntbuf[i].f_flag & MNT_LOCAL) == 0) + continue; + if (!selected(mntbuf[i].f_fstypename, + sizeof(mntbuf[i].f_fstypename))) + continue; + if (nflag) + mntbuf[j] = mntbuf[i]; + else { + struct statvfs layerbuf = mntbuf[i]; + (void)statvfs(mntbuf[i].f_mntonname, &mntbuf[j]); + /* + * If the FS name changed, then new data is for + * a different layer and we don't want it. + */ + if (memcmp(layerbuf.f_mntfromname, + mntbuf[j].f_mntfromname, MNAMELEN)) + mntbuf[j] = layerbuf; + } + j++; + } + return j; +} + +static void +prthumanval(int64_t bytes, const char *pad) +{ + char buf[6]; + + (void)humanize_number(buf, sizeof(buf) - (bytes < 0 ? 0 : 1), + bytes, "", HN_AUTOSCALE, + HN_B | HN_NOSPACE | HN_DECIMAL); + + (void)printf("%s %6s", pad, buf); +} + +static void +prthuman(struct statvfs *sfsp, int64_t used, int64_t bavail) +{ + + prthumanval((int64_t)(sfsp->f_blocks * sfsp->f_frsize), " "); + prthumanval((int64_t)(used * sfsp->f_frsize), " "); + prthumanval((int64_t)(bavail * sfsp->f_frsize), " "); +} + +/* + * Convert statvfs returned filesystem size into BLOCKSIZE units. + * Attempts to avoid overflow for large filesystems. + */ +#define fsbtoblk(num, fsbs, bs) \ + (((fsbs) != 0 && (uint64_t)(fsbs) < (uint64_t)(bs)) ? \ + (int64_t)(num) / (int64_t)((bs) / (fsbs)) : \ + (int64_t)(num) * (int64_t)((fsbs) / (bs))) + +/* + * Print out status about a filesystem. + */ +static void +prtstat(struct statvfs *sfsp, int maxwidth) +{ + static long blocksize; + static int headerlen, timesthrough; + static const char *header; + static const char full[] = "100"; + static const char empty[] = " 0"; + int64_t used, availblks, inodes; + int64_t bavail; + char pb[64]; + + if (gflag) { + /* + * From SunOS-5.6: + * + * /var (/dev/dsk/c0t0d0s3 ): 8192 block size 1024 frag size + * 984242 total blocks 860692 free blocks 859708 available 249984 total files + * 248691 free files 8388611 filesys id + * ufs fstype 0x00000004 flag 255 filename length + * + */ + (void)printf("%10s (%-12s): %7ld block size %12ld frag size\n", + sfsp->f_mntonname, sfsp->f_mntfromname, + sfsp->f_bsize, /* On UFS/FFS systems this is + * also called the "optimal + * transfer block size" but it + * is of course the file + * system's block size too. + */ + sfsp->f_frsize); /* not so surprisingly the + * "fundamental file system + * block size" is the frag + * size. + */ + (void)printf("%10" PRId64 " total blocks %10" PRId64 + " free blocks %10" PRId64 " available\n", + (uint64_t)sfsp->f_blocks, (uint64_t)sfsp->f_bfree, + (uint64_t)sfsp->f_bavail); + (void)printf("%10" PRId64 " total files %10" PRId64 + " free files %12lx filesys id\n", + (uint64_t)sfsp->f_ffree, (uint64_t)sfsp->f_files, + sfsp->f_fsid); + (void)printf("%10s fstype %#15lx flag %17ld filename " + "length\n", sfsp->f_fstypename, sfsp->f_flag, + sfsp->f_namemax); + (void)printf("%10lu owner %17" PRId64 " syncwrites %12" PRId64 + " asyncwrites\n\n", (unsigned long)sfsp->f_owner, + sfsp->f_syncwrites, sfsp->f_asyncwrites); + + /* + * a concession by the structured programming police to the + * indentation police.... + */ + return; + } + if (maxwidth < 12) + maxwidth = 12; + if (++timesthrough == 1) { + switch (blocksize = usize) { + case 1024: + header = Pflag ? "1024-blocks" : "1K-blocks"; + headerlen = (int)strlen(header); + break; + case 1024 * 1024: + header = "1M-blocks"; + headerlen = (int)strlen(header); + break; + case 1024 * 1024 * 1024: + header = "1G-blocks"; + headerlen = (int)strlen(header); + break; + default: + if (hflag) { + header = "Size"; + headerlen = (int)strlen(header); + } else + header = getbsize(&headerlen, &blocksize); + break; + } + if (Pflag) { + /* + * either: + * "Filesystem 1024-blocks Used Available Capacity Mounted on\n" + * or: + * "Filesystem 512-blocks Used Available Capacity Mounted on\n" + */ + if (blocksize != 1024 && blocksize != 512) + errx(EXIT_FAILURE, + "non-standard block size incompatible with -P"); + (void)printf("Filesystem %s Used Available Capacity " + "Mounted on\n", header); + } else { + (void)printf("%-*.*s %s Used Avail %%Cap", + maxwidth - (headerlen - 10), + maxwidth - (headerlen - 10), + "Filesystem", header); + if (iflag) + (void)printf(" iUsed iAvail %%iCap"); + (void)printf(" Mounted on\n"); + } + } + used = sfsp->f_blocks - sfsp->f_bfree; + bavail = sfsp->f_bfree - sfsp->f_bresvd; + availblks = bavail + used; + if (Pflag) { + assert(hflag == 0); + assert(blocksize > 0); + /* + * "%s %d %d %d %s %s\n", <file system name>, <total space>, + * <space used>, <space free>, <percentage used>, + * <file system root> + */ + (void)printf("%s %" PRId64 " %" PRId64 " %" PRId64 " %s%% %s\n", + sfsp->f_mntfromname, + fsbtoblk(sfsp->f_blocks, sfsp->f_frsize, blocksize), + fsbtoblk(used, sfsp->f_frsize, blocksize), + fsbtoblk(bavail, sfsp->f_frsize, blocksize), + availblks == 0 ? full : strspct(pb, sizeof(pb), used, + availblks, 0), sfsp->f_mntonname); + /* + * another concession by the structured programming police to + * the indentation police.... + * + * Note iflag cannot be set when Pflag is set. + */ + return; + } + + (void)printf("%-*.*s ", maxwidth, maxwidth, sfsp->f_mntfromname); + + if (hflag) + prthuman(sfsp, used, bavail); + else + (void)printf("%10" PRId64 " %10" PRId64 " %10" PRId64, + fsbtoblk(sfsp->f_blocks, sfsp->f_frsize, blocksize), + fsbtoblk(used, sfsp->f_frsize, blocksize), + fsbtoblk(bavail, sfsp->f_frsize, blocksize)); + (void)printf(" %3s%%", + availblks == 0 ? full : + strspct(pb, sizeof(pb), used, availblks, 0)); + if (iflag) { + inodes = sfsp->f_files; + used = inodes - sfsp->f_ffree; + (void)printf(" %8jd %8jd %4s%%", + (intmax_t)used, (intmax_t)sfsp->f_ffree, + inodes == 0 ? (used == 0 ? empty : full) : + strspct(pb, sizeof(pb), used, inodes, 0)); + } + (void)printf(" %s\n", sfsp->f_mntonname); +} + +static void +usage(void) +{ + + (void)fprintf(stderr, + "Usage: %s [-agln] [-Ghkm|-ihkm|-Pk] [-t type] [file | " + "file_system ...]\n", + getprogname()); + exit(1); + /* NOTREACHED */ +} diff --git a/bin/echo/echo.1 b/bin/echo/echo.1 new file mode 100644 index 0000000..a7e374b --- /dev/null +++ b/bin/echo/echo.1 @@ -0,0 +1,71 @@ +.\" $NetBSD: echo.1,v 1.17 2017/07/04 06:48:41 wiz Exp $ +.\" +.\" Copyright (c) 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. +.\" +.\" @(#)echo.1 8.1 (Berkeley) 7/22/93 +.\" +.Dd August 14, 2016 +.Dt ECHO 1 +.Os +.Sh NAME +.Nm echo +.Nd write arguments to the standard output +.Sh SYNOPSIS +.Nm +.Op Fl n +.Op Ar string ... +.Sh DESCRIPTION +The +.Nm +utility writes any specified operands, separated by single blank (`` '') +characters and followed by a newline (``\en'') character, to the standard +output. +.Pp +The following option is available: +.Bl -tag -width flag +.It Fl n +Do not print the trailing newline character. +.El +.Sh EXIT STATUS +.Ex -std echo +.Sh SEE ALSO +.Xr printf 1 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. +.Sh HISTORY +An +.Nm +utility appeared in +.At v2 . diff --git a/bin/echo/echo.c b/bin/echo/echo.c new file mode 100644 index 0000000..2e59363 --- /dev/null +++ b/bin/echo/echo.c @@ -0,0 +1,81 @@ +/* $NetBSD: echo.c,v 1.19 2016/09/05 01:00:07 sevan Exp $ */ + +/* + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT( +"@(#) Copyright (c) 1989, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)echo.c 8.1 (Berkeley) 5/31/93"; +#else +__RCSID("$NetBSD: echo.c,v 1.19 2016/09/05 01:00:07 sevan Exp $"); +#endif +#endif /* not lint */ + +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* ARGSUSED */ +int +main(int argc, char *argv[]) +{ + int nflag; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + /* This utility may NOT do getopt(3) option parsing. */ + if (*++argv && !strcmp(*argv, "-n")) { + ++argv; + nflag = 1; + } + else + nflag = 0; + + while (*argv) { + (void)printf("%s", *argv); + if (*++argv) + (void)putchar(' '); + } + if (nflag == 0) + (void)putchar('\n'); + fflush(stdout); + if (ferror(stdout)) + exit(1); + exit(0); + /* NOTREACHED */ +} diff --git a/bin/ed/POSIX b/bin/ed/POSIX new file mode 100644 index 0000000..6bea801 --- /dev/null +++ b/bin/ed/POSIX @@ -0,0 +1,103 @@ +$NetBSD: POSIX,v 1.10 1999/11/18 19:16:34 kristerw Exp $ + +This version of ed(1) is not strictly POSIX compliant, as described in +the POSIX 1003.2 document. The following is a summary of the omissions, +extensions and possible deviations from POSIX 1003.2. + +OMISSIONS +--------- +1) Locale(3) is not supported yet. + +2) For backwards compatibility, the POSIX rule that says a range of + addresses cannot be used where only a single address is expected has + been relaxed. + +3) To support the BSD `s' command (see extension [1] below), + substitution patterns cannot be delimited by numbers or the characters + `r', `g' and `p'. In contrast, POSIX specifies any character expect + space or newline can used as a delimiter. + +EXTENSIONS +---------- +1) BSD commands have been implemented wherever they do not conflict with + the POSIX standard. The BSD-ism's included are: + i) `s' (i.e., s[n][rgp]*) to repeat a previous substitution, + ii) `W' for appending text to an existing file, + iii) `wq' for exiting after a write, + iv) `z' for scrolling through the buffer, and + v) BSD line addressing syntax (i.e., `^' and `%') is recognized. + +2) If crypt(3) is available, files can be read and written using DES + encryption. The `x' command prompts the user to enter a key used for + encrypting/ decrypting subsequent reads and writes. If only a newline + is entered as the key, then encryption is disabled. Otherwise, a key + is read in the same manner as a password entry. The key remains in + effect until encryption is disabled. For more information on the + encryption algorithm, see the bdes(1) man page. Encryption/decryption + should be fully compatible with SunOS des(1). + +3) The POSIX interactive global commands `G' and `V' are extended to + support multiple commands, including `a', `i' and `c'. The command + format is the same as for the global commands `g' and `v', i.e., one + command per line with each line, except for the last, ending in a + backslash (\). + +4) An extension to the POSIX file commands `E', `e', `r', `W' and `w' is + that <file> arguments are processed for backslash escapes, i.e., any + character preceded by a backslash is interpreted literally. If the + first unescaped character of a <file> argument is a bang (!), then the + rest of the line is interpreted as a shell command, and no escape + processing is performed by ed. + +5) For SunOS ed(1) compatibility, ed runs in restricted mode if invoked + as red. This limits editing of files in the local directory only and + prohibits shell commands. + +DEVIATIONS +---------- +1) Though ed is not a stream editor, it can be used to edit binary files. + To assist in binary editing, when a file containing at least one ASCII + NUL character is written, a newline is not appended if it did not + already contain one upon reading. In particular, reading /dev/null + prior to writing prevents appending a newline to a binary file. + + For example, to create a file with ed containing a single NUL character: + $ ed file + a + ^@ + . + r /dev/null + wq + + Similarly, to remove a newline from the end of binary `file': + $ ed file + r /dev/null + wq + +2) Since the behavior of `u' (undo) within a `g' (global) command list is + not specified by POSIX, it follows the behavior of the SunOS ed: + undo forces a global command list to be executed only once, rather than + for each line matching a global pattern. In addtion, each instance of + `u' within a global command undoes all previous commands (including + undo's) in the command list. This seems the best way, since the + alternatives are either too complicated to implement or too confusing + to use. + + The global/undo combination is useful for masking errors that + would otherwise cause a script to fail. For instance, an ed script + to remove any occurrences of either `censor1' or `censor2' might be + written as: + ed - file <<EOF + 1g/.*/u\ + ,s/censor1//g\ + ,s/censor2//g + ... + +3) The `m' (move) command within a `g' command list also follows the SunOS + ed implementation: any moved lines are removed from the global command's + `active' list. + +4) If ed is invoked with a name argument prefixed by a bang (!), then the + remainder of the argument is interpreted as a shell command. To invoke + ed on a file whose name starts with bang, prefix the name with a + backslash. diff --git a/bin/ed/README b/bin/ed/README new file mode 100644 index 0000000..474672b --- /dev/null +++ b/bin/ed/README @@ -0,0 +1,23 @@ +$NetBSD: README,v 1.10 2019/01/04 19:13:58 maya Exp $ + +ed is an 8-bit-clean, POSIX-compliant line editor. It should work with +any regular expression package that conforms to the POSIX interface +standard, such as GNU regex(3). + +If reliable signals are supported (e.g., POSIX sigaction(2)), it should +compile with little trouble. Otherwise, the macros SPL1() and SPL0() +should be redefined to disable interrupts. + +The following compiler directives are recognized: +DES - to add encryption support (requires crypt(3)) +BACKWARDS - for backwards compatibility +NEED_INSQUE - if insque(3) is missing + +The file `POSIX' describes extensions to and deviations from the POSIX +standard. + +The ./test directory contains regression tests for ed. The README +file in that directory explains how to run these. + +For a description of the ed algorithm, see Kernighan and Plauger's book +"Software Tools in Pascal," Addison-Wesley, 1981. diff --git a/bin/ed/buf.c b/bin/ed/buf.c new file mode 100644 index 0000000..f824f75 --- /dev/null +++ b/bin/ed/buf.c @@ -0,0 +1,319 @@ +/* $NetBSD: buf.c,v 1.27 2014/03/23 05:06:42 dholland Exp $ */ + +/* buf.c: This file contains the scratch-file buffer routines for the + ed line editor. */ +/*- + * Copyright (c) 1993 Andrew Moore, Talke Studio. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char *rcsid = "@(#)buf.c,v 1.4 1994/02/01 00:34:35 alm Exp"; +#else +__RCSID("$NetBSD: buf.c,v 1.27 2014/03/23 05:06:42 dholland Exp $"); +#endif +#endif /* not lint */ + +#include <sys/file.h> +#include <sys/stat.h> + +#include <paths.h> +#include <stdio.h> +#include <err.h> + +#include "ed.h" + + +FILE *sfp; /* scratch file pointer */ +off_t sfseek; /* scratch file position */ +int seek_write; /* seek before writing */ +line_t buffer_head; /* incore buffer */ + +/* get_sbuf_line: get a line of text from the scratch file; return pointer + to the text */ +char * +get_sbuf_line(line_t *lp) +{ + static char *sfbuf = NULL; /* buffer */ + static int sfbufsz = 0; /* buffer size */ + + int len, ct; + + if (lp == &buffer_head) + return NULL; + seek_write = 1; /* force seek on write */ + /* out of position */ + if (sfseek != lp->seek) { + sfseek = lp->seek; + if (fseek(sfp, sfseek, SEEK_SET) < 0) { + fprintf(stderr, "%s\n", strerror(errno)); + seterrmsg("cannot seek temp file"); + return NULL; + } + } + len = lp->len; + REALLOC(sfbuf, sfbufsz, len + 1, NULL); + if ((ct = fread(sfbuf, sizeof(char), len, sfp)) < 0 || ct != len) { + fprintf(stderr, "%s\n", strerror(errno)); + seterrmsg("cannot read temp file"); + return NULL; + } + sfseek += len; /* update file position */ + sfbuf[len] = '\0'; + return sfbuf; +} + + +/* put_sbuf_line: write a line of text to the scratch file and add a line node + to the editor buffer; return a pointer to the end of the text */ +char * +put_sbuf_line(char *cs) +{ + line_t *lp; + int len, ct; + char *s; + + if ((lp = (line_t *) malloc(sizeof(line_t))) == NULL) { + fprintf(stderr, "%s\n", strerror(errno)); + seterrmsg("out of memory"); + return NULL; + } + /* assert: cs is '\n' terminated */ + for (s = cs; *s != '\n'; s++) + ; + if (s - cs >= LINECHARS) { + seterrmsg("line too long"); + free(lp); + return NULL; + } + len = s - cs; + /* out of position */ + if (seek_write) { + if (fseek(sfp, 0L, SEEK_END) < 0) { + fprintf(stderr, "%s\n", strerror(errno)); + seterrmsg("cannot seek temp file"); + free(lp); + return NULL; + } + sfseek = ftell(sfp); + seek_write = 0; + } + /* assert: SPL1() */ + if ((ct = fwrite(cs, sizeof(char), len, sfp)) < 0 || ct != len) { + sfseek = -1; + fprintf(stderr, "%s\n", strerror(errno)); + seterrmsg("cannot write temp file"); + free(lp); + return NULL; + } + lp->len = len; + lp->seek = sfseek; + add_line_node(lp); + sfseek += len; /* update file position */ + return ++s; +} + + +/* add_line_node: add a line node in the editor buffer after the current line */ +void +add_line_node(line_t *lp) +{ + line_t *cp; + + cp = get_addressed_line_node(current_addr); /* this get_addressed_line_node last! */ + INSQUE(lp, cp); + addr_last++; + current_addr++; +} + + +/* get_line_node_addr: return line number of pointer */ +long +get_line_node_addr(line_t *lp) +{ + line_t *cp = &buffer_head; + long n = 0; + + while (cp != lp && (cp = cp->q_forw) != &buffer_head) + n++; + if (n && cp == &buffer_head) { + seterrmsg("invalid address"); + return ERR; + } + return n; +} + + +/* get_addressed_line_node: return pointer to a line node in the editor buffer */ +line_t * +get_addressed_line_node(long n) +{ + static line_t *lp = &buffer_head; + static long on = 0; + + SPL1(); + if (n > on) { + if (n <= (on + addr_last) >> 1) { + for (; on < n; on++) + lp = lp->q_forw; + } else { + lp = buffer_head.q_back; + for (on = addr_last; on > n; on--) + lp = lp->q_back; + } + } else { + if (n >= on >> 1) { + for (; on > n; on--) + lp = lp->q_back; + } else { + lp = &buffer_head; + for (on = 0; on < n; on++) + lp = lp->q_forw; + } + } + SPL0(); + return lp; +} + + +char *sfn = NULL; /* scratch file name */ + +/* open_sbuf: open scratch file */ +int +open_sbuf(void) +{ + int u, fd; + const char *tmp; + size_t s; + + isbinary = newline_added = 0; + fd = -1; + u = umask(077); + + if ((tmp = getenv("TMPDIR")) == NULL) + tmp = _PATH_TMP; + + if ((s = strlen(tmp)) == 0 || tmp[s - 1] == '/') + (void)asprintf(&sfn, "%sed.XXXXXX", tmp); + else + (void)asprintf(&sfn, "%s/ed.XXXXXX", tmp); + if (sfn == NULL) { + warn(NULL); + seterrmsg("could not allocate memory"); + umask(u); + return ERR; + } + + + if ((fd = mkstemp(sfn)) == -1 || (sfp = fdopen(fd, "w+")) == NULL) { + if (fd != -1) + close(fd); + warn("%s", sfn); + seterrmsg("cannot open temp file"); + umask(u); + return ERR; + } + umask(u); + return 0; +} + + +/* close_sbuf: close scratch file */ +int +close_sbuf(void) +{ + if (sfp) { + if (fclose(sfp) < 0) { + fprintf(stderr, "%s: %s\n", sfn, strerror(errno)); + seterrmsg("cannot close temp file"); + return ERR; + } + sfp = NULL; + if (sfn) { + unlink(sfn); + free(sfn); + sfn = NULL; + } + } + sfseek = seek_write = 0; + return 0; +} + + +/* quit: remove_lines scratch file and exit */ +void +quit(int n) +{ + if (sfp) { + fclose(sfp); + if (sfn) { + unlink(sfn); + free(sfn); + sfn = NULL; + } + } + exit(n); + /* NOTREACHED */ +} + + +unsigned char ctab[256]; /* character translation table */ + +/* init_buffers: open scratch buffer; initialize line queue */ +void +init_buffers(void) +{ + int i = 0; + + /* Read stdin one character at a time to avoid i/o contention + with shell escapes invoked by nonterminal input, e.g., + ed - <<EOF + !cat + hello, world + EOF */ + setbuffer(stdin, stdinbuf, 1); + if (open_sbuf() < 0) + quit(2); + REQUE(&buffer_head, &buffer_head); + for (i = 0; i < 256; i++) + ctab[i] = i; +} + + +/* translit_text: translate characters in a string */ +char * +translit_text(char *s, int len, int from, int to) +{ + static int i = 0; + + unsigned char *us; + + ctab[i] = i; /* restore table to initial state */ + ctab[i = from] = to; + for (us = (unsigned char *) s; len-- > 0; us++) + *us = ctab[*us]; + return s; +} diff --git a/bin/ed/cbc.c b/bin/ed/cbc.c new file mode 100644 index 0000000..7d20c6c --- /dev/null +++ b/bin/ed/cbc.c @@ -0,0 +1,460 @@ +/* $NetBSD: cbc.c,v 1.25 2018/02/08 09:05:16 dholland Exp $ */ + +/* cbc.c: This file contains the encryption routines for the ed line editor */ +/*- + * 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. + * + * from: @(#)bdes.c 5.5 (Berkeley) 6/27/91 + */ + +/*- + * Copyright (c) 1993 Andrew Moore, Talke Studio. + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. 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: @(#)bdes.c 5.5 (Berkeley) 6/27/91 + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char *rcsid = "@(#)cbc.c,v 1.2 1994/02/01 00:34:36 alm Exp"; +#else +__RCSID("$NetBSD: cbc.c,v 1.25 2018/02/08 09:05:16 dholland Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <ctype.h> +#include <errno.h> +#include <pwd.h> +#ifdef DES +#include <time.h> +#endif + +#include "ed.h" + + +/* + * Define a divisor for rand() that yields a uniform distribution in the + * range 0-255. + */ +#define RAND_DIV (((unsigned) RAND_MAX + 1) >> 8) + +/* + * BSD and System V systems offer special library calls that do + * block move_liness and fills, so if possible we take advantage of them + */ +#define MEMCPY(dest,src,len) memcpy((dest),(src),(len)) +#define MEMZERO(dest,len) memset((dest), 0, (len)) + +/* Hide the calls to the primitive encryption routines. */ +#define DES_KEY(buf) \ + if (des_setkey(buf)) \ + des_error("des_setkey"); +#define DES_XFORM(buf) \ + if (des_cipher(buf, buf, 0L, (inverse ? -1 : 1))) \ + des_error("des_cipher"); + +/* + * read/write - no error checking + */ +#define READ(buf, n, fp) fread(buf, sizeof(char), n, fp) +#define WRITE(buf, n, fp) fwrite(buf, sizeof(char), n, fp) + +/* + * some things to make references easier + */ +typedef char Desbuf[8]; +#define CHAR(x,i) (x[i]) +#define UCHAR(x,i) (x[i]) +#define BUFFER(x) (x) +#define UBUFFER(x) (x) + +#ifdef DES +/* + * global variables and related macros + */ + +static Desbuf ivec; /* initialization vector */ +static Desbuf pvec; /* padding vector */ +static char bits[] = { /* used to extract bits from a char */ + '\200', '\100', '\040', '\020', '\010', '\004', '\002', '\001' +}; +static int pflag; /* 1 to preserve parity bits */ + +static char des_buf[8]; /* shared buffer for get_des_char/put_des_char */ +static int des_ct = 0; /* count for get_des_char/put_des_char */ +static int des_n = 0; /* index for put_des_char/get_des_char */ +#endif + + +#ifdef DES +static void des_error(const char *); +static int hex_to_binary(int, int); +static void expand_des_key(char *, char *); +static void set_des_key(char *); +static int cbc_decode(char *, FILE *); +static int cbc_encode(char *, int, FILE *); +#endif + + +/* init_des_cipher: initialize DES */ +void +init_des_cipher(void) +{ +#ifdef DES + int i; + + des_ct = des_n = 0; + + /* initialize the initialization vector */ + MEMZERO(ivec, 8); + + /* initialize the padding vector */ + srand((unsigned) time((time_t *) 0)); + for (i = 0; i < 8; i++) + CHAR(pvec, i) = (char) (rand()/RAND_DIV); +#endif +} + + +/* get_des_char: return next char in an encrypted file */ +int +get_des_char(FILE *fp) +{ +#ifdef DES + if (des_n >= des_ct) { + des_n = 0; + des_ct = cbc_decode(des_buf, fp); + } + return (des_ct > 0) ? (unsigned char) des_buf[des_n++] : EOF; +#else + return EOF; +#endif +} + + +/* put_des_char: write a char to an encrypted file; return char written */ +int +put_des_char(int c, FILE *fp) +{ +#ifdef DES + if (des_n == sizeof des_buf) { + des_ct = cbc_encode(des_buf, des_n, fp); + des_n = 0; + } + return (des_ct >= 0) ? (unsigned char) (des_buf[des_n++] = c) : EOF; +#else + return EOF; +#endif +} + + +/* flush_des_file: flush an encrypted file's output; return status */ +int +flush_des_file(FILE *fp) +{ +#ifdef DES + if (des_n == sizeof des_buf) { + des_ct = cbc_encode(des_buf, des_n, fp); + des_n = 0; + } + return (des_ct >= 0 && cbc_encode(des_buf, des_n, fp) >= 0) ? 0 : EOF; +#else + return EOF; +#endif +} + +#ifdef DES +/* + * get keyword from tty or stdin + */ +int +get_keyword(void) +{ + char *p; /* used to obtain the key */ + Desbuf msgbuf; /* I/O buffer */ + + /* + * get the key + */ + if (*(p = getpass("Enter key: "))) { + + /* + * copy it, nul-padded, into the key area + */ + expand_des_key(BUFFER(msgbuf), p); + MEMZERO(p, _PASSWORD_LEN); + set_des_key(msgbuf); + MEMZERO(msgbuf, sizeof msgbuf); + return 1; + } + return 0; +} + + +/* + * print a warning message and, possibly, terminate + */ +static void +des_error(const char *s /* the message */) +{ + seterrmsg("%s", s ? s : strerror(errno)); +} + +/* + * map a hex character to an integer + */ +static int +hex_to_binary(int c /* char to be converted */, + int radix /* base (2 to 16) */) +{ + switch(c) { + case '0': return(0x0); + case '1': return(0x1); + case '2': return(radix > 2 ? 0x2 : -1); + case '3': return(radix > 3 ? 0x3 : -1); + case '4': return(radix > 4 ? 0x4 : -1); + case '5': return(radix > 5 ? 0x5 : -1); + case '6': return(radix > 6 ? 0x6 : -1); + case '7': return(radix > 7 ? 0x7 : -1); + case '8': return(radix > 8 ? 0x8 : -1); + case '9': return(radix > 9 ? 0x9 : -1); + case 'A': case 'a': return(radix > 10 ? 0xa : -1); + case 'B': case 'b': return(radix > 11 ? 0xb : -1); + case 'C': case 'c': return(radix > 12 ? 0xc : -1); + case 'D': case 'd': return(radix > 13 ? 0xd : -1); + case 'E': case 'e': return(radix > 14 ? 0xe : -1); + case 'F': case 'f': return(radix > 15 ? 0xf : -1); + } + /* + * invalid character + */ + return(-1); +} + +/* + * convert the key to a bit pattern + */ +static void +expand_des_key(char *obuf /* bit pattern */, char *inbuf /* the key itself */) +{ + int i, j; /* counter in a for loop */ + int nbuf[64]; /* used for hex/key translation */ + + /* + * leading '0x' or '0X' == hex key + */ + if (inbuf[0] == '0' && (inbuf[1] == 'x' || inbuf[1] == 'X')) { + inbuf = &inbuf[2]; + /* + * now translate it, bombing on any illegal hex digit + */ + for (i = 0; i < 16 && inbuf[i]; i++) + if ((nbuf[i] = hex_to_binary((int) inbuf[i], 16)) == -1) + des_error("bad hex digit in key"); + while (i < 16) + nbuf[i++] = 0; + for (i = 0; i < 8; i++) + obuf[i] = + ((nbuf[2*i]&0xf)<<4) | (nbuf[2*i+1]&0xf); + /* preserve parity bits */ + pflag = 1; + return; + } + /* + * leading '0b' or '0B' == binary key + */ + if (inbuf[0] == '0' && (inbuf[1] == 'b' || inbuf[1] == 'B')) { + inbuf = &inbuf[2]; + /* + * now translate it, bombing on any illegal binary digit + */ + for (i = 0; i < 16 && inbuf[i]; i++) + if ((nbuf[i] = hex_to_binary((int) inbuf[i], 2)) == -1) + des_error("bad binary digit in key"); + while (i < 64) + nbuf[i++] = 0; + for (i = 0; i < 8; i++) + for (j = 0; j < 8; j++) + obuf[i] = (obuf[i]<<1)|nbuf[8*i+j]; + /* preserve parity bits */ + pflag = 1; + return; + } + /* + * no special leader -- ASCII + */ + (void)strncpy(obuf, inbuf, 8); +} + +/***************** + * DES FUNCTIONS * + *****************/ +/* + * This sets the DES key and (if you're using the deszip version) + * the direction of the transformation. This uses the Sun + * to map the 64-bit key onto the 56 bits that the key schedule + * generation routines use: the old way, which just uses the user- + * supplied 64 bits as is, and the new way, which resets the parity + * bit to be the same as the low-order bit in each character. The + * new way generates a greater variety of key schedules, since many + * systems set the parity (high) bit of each character to 0, and the + * DES ignores the low order bit of each character. + */ +static void +set_des_key(Desbuf buf /* key block */) +{ + int i, j; /* counter in a for loop */ + int par; /* parity counter */ + + /* + * if the parity is not preserved, flip it + */ + if (!pflag) { + for (i = 0; i < 8; i++) { + par = 0; + for (j = 1; j < 8; j++) + if ((bits[j]&UCHAR(buf, i)) != 0) + par++; + if ((par&01) == 01) + UCHAR(buf, i) = UCHAR(buf, i)&0177; + else + UCHAR(buf, i) = (UCHAR(buf, i)&0177)|0200; + } + } + + DES_KEY(UBUFFER(buf)); +} + + +/* + * This encrypts using the Cipher Block Chaining mode of DES + */ +static int +cbc_encode(char *msgbuf, int n, FILE *fp) +{ + int inverse = 0; /* 0 to encrypt, 1 to decrypt */ + + /* + * do the transformation + */ + if (n == 8) { + for (n = 0; n < 8; n++) + CHAR(msgbuf, n) ^= CHAR(ivec, n); + DES_XFORM(UBUFFER(msgbuf)); + MEMCPY(BUFFER(ivec), BUFFER(msgbuf), 8); + return WRITE(BUFFER(msgbuf), 8, fp); + } + /* + * at EOF or last block -- in either case, the last byte contains + * the character representation of the number of bytes in it + */ +/* + MEMZERO(msgbuf + n, 8 - n); +*/ + /* + * Pad the last block randomly + */ + (void)MEMCPY(BUFFER(msgbuf + n), BUFFER(pvec), 8 - n); + CHAR(msgbuf, 7) = n; + for (n = 0; n < 8; n++) + CHAR(msgbuf, n) ^= CHAR(ivec, n); + DES_XFORM(UBUFFER(msgbuf)); + return WRITE(BUFFER(msgbuf), 8, fp); +} + +/* + * This decrypts using the Cipher Block Chaining mode of DES + */ +static int +cbc_decode(char *msgbuf /* I/O buffer */, + FILE *fp /* input file descriptor */) +{ + Desbuf inbuf; /* temp buffer for initialization vector */ + int n; /* number of bytes actually read */ + int c; /* used to test for EOF */ + int inverse = 1; /* 0 to encrypt, 1 to decrypt */ + + if ((n = READ(BUFFER(msgbuf), 8, fp)) == 8) { + /* + * do the transformation + */ + MEMCPY(BUFFER(inbuf), BUFFER(msgbuf), 8); + DES_XFORM(UBUFFER(msgbuf)); + for (c = 0; c < 8; c++) + UCHAR(msgbuf, c) ^= UCHAR(ivec, c); + MEMCPY(BUFFER(ivec), BUFFER(inbuf), 8); + /* + * if the last one, handle it specially + */ + if ((c = fgetc(fp)) == EOF) { + n = CHAR(msgbuf, 7); + if (n < 0 || n > 7) { + des_error("decryption failed (block corrupted)"); + return EOF; + } + } else + (void)ungetc(c, fp); + return n; + } + if (n > 0) + des_error("decryption failed (incomplete block)"); + else if (n < 0) + des_error("cannot read file"); + return EOF; +} +#endif /* DES */ diff --git a/bin/ed/ed.1 b/bin/ed/ed.1 new file mode 100644 index 0000000..025dab4 --- /dev/null +++ b/bin/ed/ed.1 @@ -0,0 +1,979 @@ +.\" $NetBSD: ed.1,v 1.35 2018/04/09 06:57:01 wiz Exp $ +.\" $OpenBSD: ed.1,v 1.42 2003/07/27 13:25:43 jmc Exp $ +.\" +.\" Copyright (c) 1993 Andrew Moore, Talke Studio. +.\" 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. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd April 5, 2018 +.Dt ED 1 +.Os +.Sh NAME +.Nm ed +.Nd text editor +.Sh SYNOPSIS +.Nm +.Op Fl +.Op Fl ESsx +.Op Fl p Ar string +.Op Ar file +.Sh DESCRIPTION +.Nm +is a line-oriented text editor. +It is used to create, display, modify, and otherwise manipulate text files. +If invoked with a +.Ar file +argument, then a copy of +.Ar file +is read into the editor's buffer. +Changes are made to this copy and not directly to +.Ar file +itself. +Upon quitting +.Nm , +any changes not explicitly saved with a +.Ic w +command are lost. +.Pp +Editing is done in two distinct modes: +.Em command +and +.Em input . +When first invoked, +.Nm +is in command mode. +In this mode, commands are read from the standard input and +executed to manipulate the contents of the editor buffer. +.Pp +A typical command might look like: +.Bd -literal -offset indent +,s/old/new/g +.Ed +.Pp +which replaces all occurrences of the string +.Pa old +with +.Pa new . +.Pp +When an input command, such as +.Ic a +(append), +.Ic i +(insert), +or +.Ic c +(change) is given, +.Nm +enters input mode. +This is the primary means of adding text to a file. +In this mode, no commands are available; +instead, the standard input is written directly to the editor buffer. +Lines consist of text up to and including a newline character. +Input mode is terminated by entering a single period +.Pq Ql \&. +on a line. +.Pp +All +.Nm +commands operate on whole lines or ranges of lines; e.g., +the +.Ic d +command deletes lines; the +.Ic m +command moves lines, and so on. +It is possible to modify only a portion of a line by means of replacement, +as in the example above. +However, even here, the +.Ic s +command is applied to whole lines at a time. +.Pp +In general, +.Nm +commands consist of zero or more line addresses, followed by a single +character command and possibly additional parameters; i.e., +commands have the structure: +.Bd -literal -offset indent +[address [,address]]command[parameters] +.Ed +.Pp +The address(es) indicate the line or range of lines to be affected by the +command. +If fewer addresses are given than the command accepts, then +default addresses are supplied. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl +Same as the +.Fl s +option (deprecated). +.It Fl E +Enables the use of extended regular expressions instead of the basic +regular expressions that are normally used. +.It Fl p Ar string +Specifies a command prompt. +This may be toggled on and off with the +.Ic P +command. +.It Fl S +Disables using of the +.Ic \&! +command (executing a subshell). +Intended to be used by batch jobs like +.Xr patch 1 . +.It Fl s +Suppress diagnostics. +This should be used if +.Nm +standard input is from a script. +.It Fl x +Prompt for an encryption key to be used in subsequent reads and writes +(see the +.Ic x +command). +.It Ar file +Specifies the name of a file to read. +If +.Ar file +is prefixed with a +bang +.Pq Ql \&! , +then it is interpreted as a shell command. +In this case, what is read is the standard output of +.Ar file +executed via +.Xr sh 1 . +To read a file whose name begins with a bang, prefix the +name with a backslash +.Pq Ql \e . +The default filename is set to +.Ar file +only if it is not prefixed with a bang. +.El +.Ss LINE ADDRESSING +An address represents the number of a line in the buffer. +.Nm +maintains a +.Em current address +which is typically supplied to commands as the default address +when none is specified. +When a file is first read, the current address is set to the last line +of the file. +In general, the current address is set to the last line affected by a command. +.Pp +A line address is +constructed from one of the bases in the list below, optionally followed +by a numeric offset. +The offset may include any combination of digits, operators (i.e., +.Sq + , +.Sq - , +and +.Sq ^ ) , +and whitespace. +Addresses are read from left to right, and their values are computed +relative to the current address. +.Pp +One exception to the rule that addresses represent line numbers is the +address +.Em 0 +(zero). +This means +.Dq before the first line , +and is legal wherever it makes sense. +.Pp +An address range is two addresses separated either by a comma or semi-colon. +The value of the first address in a range cannot exceed the +value of the second. +If only one address is given in a range, +then the second address is set to the given address. +If an +.Em n Ns No -tuple +of addresses is given where +.Em n > 2 , +then the corresponding range is determined by the last two addresses in the +.Em n Ns No -tuple. +If only one address is expected, then the last address is used. +.Pp +Each address in a comma-delimited range is interpreted relative to the +current address. +In a semi-colon-delimited range, the first address is +used to set the current address, and the second address is interpreted +relative to the first. +.Pp +The following address symbols are recognized: +.Bl -tag -width Ds +.It Em \&. +The current line (address) in the buffer. +.It Em $ +The last line in the buffer. +.It Em n +The +.Em n Ns No th +line in the buffer where +.Em n +is a number in the range +.Em [0,$] . +.It Em - No or Em ^ +The previous line. +This is equivalent to +.Em -1 +and may be repeated with cumulative effect. +.It Em -n No or Em ^n +The +.Em n Ns No th +previous line, where +.Em n +is a non-negative number. +.It Em + +The next line. +This is equivalent to +.Em +1 +and may be repeated with cumulative effect. +.It Em +n +The +.Em n Ns No th +next line, where +.Em n +is a non-negative number. +.It Em whitespace Em n +.Em whitespace +followed by a number +.Em n +is interpreted as +.Sq Em +n . +.It Em \&, No or Em % +The first through last lines in the buffer. +This is equivalent to the address range +.Em 1,$ . +.It Em \&; +The current through last lines in the buffer. +This is equivalent to the address range +.Em .,$ . +.It Em / Ns Ar re Ns Em / +The next line containing the regular expression +.Ar re . +The search wraps to the beginning of the buffer and continues down to the +current line, if necessary. +.Em // +repeats the last search. +.It Em \&? Ns Ar re Ns Em \&? +The previous line containing the regular expression +.Ar re . +The search wraps to the end of the buffer and continues up to the +current line, if necessary. +.Em ?? +repeats the last search. +.It Em \&\' Ns Ar lc +The line previously marked by a +.Ic k +(mark) command, where +.Ar lc +is a lower case letter. +.El +.Ss REGULAR EXPRESSIONS +Regular expressions are patterns used in selecting text. +For example, the +.Nm +command +.Bd -literal -offset indent +g/string/ +.Ed +.Pp +prints all lines containing +.Em string . +Regular expressions are also used by the +.Ic s +command for selecting old text to be replaced with new. +.Pp +In addition to specifying string literals, regular expressions can +represent classes of strings. +Strings thus represented are said to be matched by the +corresponding regular expression. +If it is possible for a regular expression to match several strings in +a line, then the leftmost longest match is the one selected. +.Pp +The following symbols are used in constructing regular expressions: +.Bl -tag -width Dsasdfsd +.It Em c +Any character +.Em c +not listed below, including +.Sq { , +.Sq } , +.Sq \&( , +.Sq \&) , +.Sq < , +and +.Sq > +matches itself. +.It Em \ec +Any backslash-escaped character +.Em c , +except for +.Sq { , +.Sq } , +.Sq \&( , +.Sq \&) , +.Sq < , +and +.Sq > +matches itself. +.It Em \&. +Matches any single character. +.It Em [char-class] +Matches any single character in the character class +.Em char-class . +See +.Sx CHARACTER CLASSES +below for further information. +.It Em [^char-class] +Matches any single character, other than newline, not in the +character class +.Em char-class . +.It Em ^ +If +.Em ^ +is the first character of a regular expression, then it +anchors the regular expression to the beginning of a line. +Otherwise, it matches itself. +.It Em $ +If +.Em $ +is the last character of a regular expression, +it anchors the regular expression to the end of a line. +Otherwise, it matches itself. +.It Em \e< +Anchors the single character regular expression or subexpression +immediately following it to the beginning of a word. +(This may not be available.) +.It Em \e> +Anchors the single character regular expression or subexpression +immediately following it to the end of a word. +(This may not be available.) +.It Em \e( Ns Ar re Ns Em \e) +Defines a subexpression +.Ar re . +Subexpressions may be nested. +A subsequent backreference of the form +.Em \en , +where +.Em n +is a number in the range [1,9], expands to the text matched by the +.Em n Ns No th +subexpression. +For example, the regular expression +.Em \e(.*\e)\e1 +matches any string consisting of identical adjacent substrings. +Subexpressions are ordered relative to their left delimiter. +.It Em * +Matches the single character regular expression or subexpression +immediately preceding it zero or more times. +If +.Em * +is the first character of a regular expression or subexpression, +then it matches itself. +The +.Em * +operator sometimes yields unexpected results. +For example, the regular expression +.Em b* +matches the beginning of the string +.Em abbb +(as opposed to the substring +.Em bbb ) , +since a null match is the only leftmost match. +.Sm off +.It Em \e{ No n,m Em \e}\ \e{ No n, Em \e}\ \& Em \e{ No n Em \e} +.Sm on +Matches the single character regular expression or subexpression +immediately preceding it at least +.Em n +and at most +.Em m +times. +If +.Em m +is omitted, then it matches at least +.Em n +times. +If the comma is also omitted, then it matches exactly +.Em n +times. +.El +.Pp +Additional regular expression operators may be defined depending on the +particular +.Xr regex 3 +implementation. +.Ss CHARACTER CLASSES +A character class specifies a set of characters. +It is written within square brackets +.Pq [] +and in its most basic form contains just the characters in the set. +.Pp +To include a +.Sq \&] +in a character class, it must be the first character. +A range of characters may be specified by separating the end characters +of the range with a +.Sq \&- , +e.g., +.Sq a-z +specifies the lower case characters. +.Pp +The following literals can also be used within character classes as +shorthand for particular sets of characters: +.Bl -tag -offset indent -compact -width [:blahblah:] +.It [:alnum:] +Alphanumeric characters. +.It [:cntrl:] +Control characters. +.It [:lower:] +Lowercase alphabetic characters. +.It [:space:] +Whitespace (space, tab, newline, form feed, etc.) +.It [:alpha:] +Alphabetic characters. +.It [:digit:] +Numeric characters (digits). +.It [:print:] +Printable characters. +.It [:upper:] +Uppercase alphabetic characters. +.It [:blank:] +Blank characters (space and tab). +.It [:graph:] +Graphical characters (printing nonblank characters). +.It [:punct:] +Punctuation characters. +.It [:xdigit:] +Hexadecimal digits. +.El +If +.Sq \&- +appears as the first or last character of a character class, then +it matches itself. +All other characters in a character class match themselves. +.Pp +Patterns in +a character class +of the form +.Em [.col-elm.] +or +.Em [=col-elm=] +where +.Em col-elm +is a +.Em collating element +are interpreted according to the locale +.\" .Xr locale 5 +(not currently supported). +See +.Xr regex 3 +for an explanation of these constructs. +.Ss COMMANDS +All +.Nm +commands are single characters, though some require additional parameters. +If a command's parameters extend over several lines, then +each line except for the last must be terminated with a backslash +.Pq Ql \e . +.Pp +In general, at most one command is allowed per line. +However, most commands accept a print suffix, which is any of +.Ic p +(print), +.Ic l +(list), +or +.Ic n +(enumerate), to print the last line affected by the command. +.Pp +An interrupt (typically ^C) has the effect of aborting the current command +and returning the editor to command mode. +.Pp +.Nm +recognizes the following commands. +The commands are shown together with +the default address or address range supplied if none is +specified (in parentheses), and other possible arguments on the right. +.Bl -tag -width Dxxs +.It (.) Ns Ic a +Appends text to the buffer after the addressed line. +Text is entered in input mode. +The current address is set to last line entered. +.It (.,.) Ns Ic c +Changes lines in the buffer. +The addressed lines are deleted from the buffer, +and text is appended in their place. +Text is entered in input mode. +The current address is set to last line entered. +.It (.,.) Ns Ic d +Deletes the addressed lines from the buffer. +If there is a line after the deleted range, then the current address is set +to this line. +Otherwise the current address is set to the line before the deleted range. +.It Ic e Ar file +Edits +.Ar file , +and sets the default filename. +If +.Ar file +is not specified, then the default filename is used. +Any lines in the buffer are deleted before the new file is read. +The current address is set to the last line read. +.It Ic e Ar !command +Edits the standard output of +.Ar command , +(see +.Ic \&! Ar command +below). +The default filename is unchanged. +Any lines in the buffer are deleted before the output of +.Em command +is read. +The current address is set to the last line read. +.It Ic E Ar file +Edits +.Ar file +unconditionally. +This is similar to the +.Ic e +command, except that unwritten changes are discarded without warning. +The current address is set to the last line read. +.It Ic f Ar file +Sets the default filename to +.Ar file . +If +.Ar file +is not specified, then the default unescaped filename is printed. +.It (1,$) Ns Ic g Ns Ar /re/command-list +Applies +.Ar command-list +to each of the addressed lines matching a regular expression +.Ar re . +The current address is set to the line currently matched before +.Ar command-list +is executed. +At the end of the +.Ic g +command, the current address is set to the last line affected by +.Ar command-list . +.Pp +Each command in +.Ar command-list +must be on a separate line, +and every line except for the last must be terminated by a backslash +.Pq Sq \e . +Any commands are allowed, except for +.Ic g , +.Ic G , +.Ic v , +and +.Ic V . +A newline alone in +.Ar command-list +is equivalent to a +.Ic p +command. +.It (1,$) Ns Ic G Ns Ar /re/ +Interactively edits the addressed lines matching a regular expression +.Ar re . +For each matching line, the line is printed, the current address is set, +and the user is prompted to enter a +.Ar command-list . +At the end of the +.Ic G +command, the current address is set to the last line affected by (the last) +.Ar command-list . +.Pp +The format of +.Ar command-list +is the same as that of the +.Ic g +command. +A newline alone acts as a null command list. +A single +.Sq & +repeats the last non-null command list. +.It Ic H +Toggles the printing of error explanations. +By default, explanations are not printed. +It is recommended that +.Nm +scripts begin with this command to aid in debugging. +.It Ic h +Prints an explanation of the last error. +.It (.) Ns Ic i +Inserts text in the buffer before the current line. +Text is entered in input mode. +The current address is set to the last line entered. +.It (.,.+1) Ns Ic j +Joins the addressed lines. +The addressed lines are deleted from the buffer and replaced by a single +line containing their joined text. +The current address is set to the resultant line. +.It (.) Ns Ic k Ns Ar lc +Marks a line with a lower case letter +.Ar lc . +The line can then be addressed as +.Ar \&'lc +(i.e., a single quote followed by +.Ar lc ) +in subsequent commands. +The mark is not cleared until the line is deleted or otherwise modified. +.It (.,.) Ns Ic l +Prints the addressed lines unambiguously. +If a single line fills more than one screen (as might be the case +when viewing a binary file, for instance), a +.Dq --More-- +prompt is printed on the last line. +.Nm +waits until the RETURN key is pressed before displaying the next screen. +The current address is set to the last line printed. +.It (.,.) Ns Ic m Ns No (.) +Moves lines in the buffer. +The addressed lines are moved to after the +right-hand destination address, which may be the address +.Em 0 +(zero). +The current address is set to the last line moved. +.It (.,.) Ns Ic n +Prints the addressed lines along with their line numbers. +The current address is set to the last line printed. +.It (.,.) Ns Ic p +Prints the addressed lines. +The current address is set to the last line printed. +.It Ic P +Toggles the command prompt on and off. +Unless a prompt was specified with the command-line option +.Fl p Ar string , +the command prompt is by default turned off. +.It Ic q +Quits +.Nm . +.It Ic Q +Quits +.Nm +unconditionally. +This is similar to the +.Ic q +command, except that unwritten changes are discarded without warning. +.It ($) Ns Ic r Ar file +Reads +.Ar file +to after the addressed line. +If +.Ar file +is not specified, then the default filename is used. +If there was no default filename prior to the command, +then the default filename is set to +.Ar file . +Otherwise, the default filename is unchanged. +The current address is set to the last line read. +.It ($) Ns Ic r Ar !command +Reads to after the addressed line the standard output of +.Ar command , +(see the +.Ic \&! +command below). +The default filename is unchanged. +The current address is set to the last line read. +.Sm off +.It (.,.) Ic s Ar /re/replacement/ , \ (.,.) \ +Ic s Ar /re/replacement/ Em g , Ar \ (.,.) \ +Ic s Ar /re/replacement/ Em n +.Sm on +Replaces text in the addressed lines matching a regular expression +.Ar re +with +.Ar replacement . +By default, only the first match in each line is replaced. +If the +.Em g +(global) suffix is given, then every match to be replaced. +The +.Em n +suffix, where +.Em n +is a positive number, causes only the +.Em n Ns No th +match to be replaced. +It is an error if no substitutions are performed on any of the addressed +lines. +The current address is set the last line affected. +.Pp +.Ar re +and +.Ar replacement +may be delimited by any character other than space and newline +(see the +.Ic s +command below). +If one or two of the last delimiters is omitted, then the last line +affected is printed as though the print suffix +.Em p +were specified. +.Pp +An unescaped +.Ql & +in +.Ar replacement +is replaced by the currently matched text. +The character sequence +.Em \em , +where +.Em m +is a number in the range [1,9], is replaced by the +.Em m Ns No th +backreference expression of the matched text. +If +.Ar replacement +consists of a single +.Ql % , +then +.Ar replacement +from the last substitution is used. +Newlines may be embedded in +.Ar replacement +if they are escaped with a backslash +.Pq Ql \e . +.It (.,.) Ns Ic s +Repeats the last substitution. +This form of the +.Ic s +command accepts a count suffix +.Em n , +or any combination of the characters +.Em r , +.Em g , +and +.Em p . +If a count suffix +.Em n +is given, then only the +.Em n Ns No th +match is replaced. +The +.Em r +suffix causes +the regular expression of the last search to be used instead of +that of the last substitution. +The +.Em g +suffix toggles the global suffix of the last substitution. +The +.Em p +suffix toggles the print suffix of the last substitution. +The current address is set to the last line affected. +.It (.,.) Ns Ic t Ns No (.) +Copies (i.e., transfers) the addressed lines to after the right-hand +destination address, which may be the address +.Em 0 +(zero). +The current address is set to the last line copied. +.It Ic u +Undoes the last command and restores the current address +to what it was before the command. +The global commands +.Ic g , +.Ic G , +.Ic v , +and +.Ic V +are treated as a single command by undo. +.Ic u +is its own inverse. +.It (1,$) Ns Ic v Ns Ar /re/command-list +Applies +.Ar command-list +to each of the addressed lines not matching a regular expression +.Ar re . +This is similar to the +.Ic g +command. +.It (1,$) Ns Ic V Ns Ar /re/ +Interactively edits the addressed lines not matching a regular expression +.Ar re . +This is similar to the +.Ic G +command. +.It (1,$) Ns Ic w Ar file +Writes the addressed lines to +.Ar file . +Any previous contents of +.Ar file +are lost without warning. +If there is no default filename, then the default filename is set to +.Ar file , +otherwise it is unchanged. +If no filename is specified, then the default filename is used. +The current address is unchanged. +.It (1,$) Ns Ic wq Ar file +Writes the addressed lines to +.Ar file , +and then executes a +.Ic q +command. +.It (1,$) Ns Ic w Ar !command +Writes the addressed lines to the standard input of +.Ar command , +(see the +.Ic \&! +command below). +The default filename and current address are unchanged. +.It (1,$) Ns Ic W Ar file +Appends the addressed lines to the end of +.Ar file . +This is similar to the +.Ic w +command, except that the previous contents of file are not clobbered. +The current address is unchanged. +.It Ic x +Prompts for an encryption key which is used in subsequent reads and writes. +If a newline alone is entered as the key, then encryption is turned off. +Otherwise, echoing is disabled while a key is read. +Encryption/decryption is done using the +.Xr bdes 1 +algorithm. +.It (.+1) Ns Ic z Ns Ar n +Scrolls +.Ar n +lines at a time starting at addressed line. +If +.Ar n +is not specified, then the current window size is used. +The current address is set to the last line printed. +.It ($) Ns Ic = +Prints the line number of the addressed line. +.It (.+1) Ns Ic newline +Prints the addressed line, and sets the current address to that line. +.It Ic \&! Ns Ar command +Executes +.Ar command +via +.Xr sh 1 . +If the first character of +.Ar command +is +.Ic \&! , +then it is replaced by text of the previous +.Ic !command . +.Nm +does not process +.Ar command +for +.Sq \e +(backslash) escapes. +However, an unescaped +.Sq % +is replaced by the default filename. +When the shell returns from execution, a +.Sq \&! +is printed to the standard output. +The current line is unchanged. +.El +.Sh LIMITATIONS +.Nm +processes +.Em file +arguments for backslash escapes, i.e., in a filename, +any characters preceded by a backslash +.Pq Ql \e +are interpreted literally. +.Pp +If a text (non-binary) file is not terminated by a newline character, +then +.Nm +appends one on reading/writing it. +In the case of a binary file, +.Nm +does not append a newline on reading/writing. +.Sh ENVIRONMENT +.Bl -tag -width iTMPDIR +.It Ev TMPDIR +The location used to store temporary files. +.El +.Sh FILES +.Bl -tag -width /tmp/ed.* -compact +.It Pa /tmp/ed.* +buffer file +.It Pa ed.hup +where +.Nm +attempts to write the buffer if the terminal hangs up +.El +.Sh DIAGNOSTICS +When an error occurs, +.Nm +prints a +.Dq \&? +and either returns to command mode or exits if its input is from a script. +An explanation of the last error can be printed with the +.Ic h +(help) command. +.Pp +Since the +.Ic g +(global) command masks any errors from failed searches and substitutions, +it can be used to perform conditional operations in scripts; e.g., +.Bd -literal -offset indent +g/old/s//new/ +.Ed +.Pp +replaces any occurrences of +.Em old +with +.Em new . +.Pp +If the +.Ic u +(undo) command occurs in a global command list, then +the command list is executed only once. +.Pp +If diagnostics are not disabled, attempting to quit +.Nm +or edit another file before writing a modified buffer results in an error. +If the command is entered a second time, it succeeds, +but any changes to the buffer are lost. +.Sh SEE ALSO +.Xr bdes 1 , +.Xr patch 1 , +.Xr sed 1 , +.Xr sh 1 , +.Xr vi 1 , +.Xr regex 3 +.Pp +USD:09-10 +.Rs +.%A B. W. Kernighan +.%A P. J. Plauger +.%B Software Tools in Pascal +.%I Addison-Wesley +.%D 1981 +.Re +.Sh HISTORY +An +.Nm +command appeared in +.At v1 . diff --git a/bin/ed/ed.h b/bin/ed/ed.h new file mode 100644 index 0000000..4f397c4 --- /dev/null +++ b/bin/ed/ed.h @@ -0,0 +1,265 @@ +/* $NetBSD: ed.h,v 1.38 2019/01/04 19:13:58 maya Exp $ */ + +/* ed.h: type and constant definitions for the ed editor. */ +/* + * Copyright (c) 1993 Andrew Moore + * 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. + * + * 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. + * + * @(#)ed.h,v 1.5 1994/02/01 00:34:39 alm Exp + */ +#include <sys/types.h> +#if defined(BSD) && BSD >= 199103 || defined(__386BSD__) +# include <sys/param.h> /* for MAXPATHLEN */ +#endif +#include <errno.h> +#if defined(sun) || defined(__NetBSD__) || defined(__APPLE__) +# include <limits.h> +#endif +#include <regex.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define ERR (-2) +#define EMOD (-3) +#define FATAL (-4) + +#ifndef MAXPATHLEN +# define MAXPATHLEN 255 /* _POSIX_PATH_MAX */ +#endif + +#define MINBUFSZ 512 /* minimum buffer size - must be > 0 */ +#define SE_MAX 30 /* max subexpressions in a regular expression */ +#ifdef INT_MAX +# define LINECHARS INT_MAX /* max chars per line */ +#else +# define LINECHARS MAXINT /* max chars per line */ +#endif + +/* gflags */ +#define GLB 001 /* global command */ +#define GPR 002 /* print after command */ +#define GLS 004 /* list after command */ +#define GNP 010 /* enumerate after command */ +#define GSG 020 /* global substitute */ + +typedef regex_t pattern_t; + +/* Line node */ +typedef struct line { + struct line *q_forw; + struct line *q_back; + off_t seek; /* address of line in scratch buffer */ + int len; /* length of line */ +} line_t; + + +typedef struct undo { + +/* type of undo nodes */ +#define UADD 0 +#define UDEL 1 +#define UMOV 2 +#define VMOV 3 + + int type; /* command type */ + line_t *h; /* head of list */ + line_t *t; /* tail of list */ +} undo_t; + +#ifndef max +# define max(a,b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef min +# define min(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#define INC_MOD(l, k) ((l) + 1 > (k) ? 0 : (l) + 1) +#define DEC_MOD(l, k) ((l) - 1 < 0 ? (k) : (l) - 1) + +/* SPL1: disable some interrupts (requires reliable signals) */ +#define SPL1() mutex++ + +/* SPL0: enable all interrupts; check sigflags (requires reliable signals) */ +#define SPL0() \ +if (--mutex == 0) { \ + if (sigflags & (1 << (SIGHUP - 1))) handle_hup(SIGHUP); \ + if (sigflags & (1 << (SIGINT - 1))) handle_int(SIGINT); \ +} + +/* STRTOL: convert a string to long */ +#define STRTOL(i, p) { \ + errno = 0 ; \ + if (((i = strtol(p, &p, 10)) == LONG_MIN || i == LONG_MAX) && \ + errno == ERANGE) { \ + seterrmsg("number out of range"); \ + i = 0; \ + return ERR; \ + } \ +} + +/* REALLOC: assure at least a minimum size for buffer b */ +#define REALLOC(b,n,i,err) \ +if ((i) > (n)) { \ + int ti = (n); \ + char *ts; \ + SPL1(); \ + if ((ts = (char *) realloc((b), ti += max((i), MINBUFSZ))) == NULL) { \ + fprintf(stderr, "%s\n", strerror(errno)); \ + seterrmsg("out of memory"); \ + SPL0(); \ + return err; \ + } \ + (n) = ti; \ + (b) = ts; \ + SPL0(); \ +} + +/* REQUE: link pred before succ */ +#define REQUE(pred, succ) (pred)->q_forw = (succ), (succ)->q_back = (pred) + +/* INSQUE: insert elem in circular queue after pred */ +#define INSQUE(elem, pred) \ +{ \ + REQUE((elem), (pred)->q_forw); \ + REQUE((pred), elem); \ +} + +/* remque: remove_lines elem from circular queue */ +#define REMQUE(elem) REQUE((elem)->q_back, (elem)->q_forw); + +/* NUL_TO_NEWLINE: overwrite ASCII NULs with newlines */ +#define NUL_TO_NEWLINE(s, l) translit_text(s, l, '\0', '\n') + +/* NEWLINE_TO_NUL: overwrite newlines with ASCII NULs */ +#define NEWLINE_TO_NUL(s, l) translit_text(s, l, '\n', '\0') + +#if defined(sun) && !defined(__SVR4) +# define strerror(n) sys_errlist[n] +#endif + +/* Local Function Declarations */ +void add_line_node(line_t *); +int append_lines(long); +int apply_subst_template(char *, regmatch_t *, int, int); +int build_active_list(int); +int check_addr_range(long, long); +void clear_active_list(void); +void clear_undo_stack(void); +int close_sbuf(void); +int copy_lines(long); +int delete_lines(long, long); +int display_lines(long, long, int); +line_t *dup_line_node(line_t *); +int exec_command(void); +long exec_global(int, int); +int extract_addr_range(void); +char *extract_pattern(int); +int extract_subst_tail(int *, long *); +char *extract_subst_template(void); +int filter_lines(long, long, char *); +int flush_des_file(FILE *); +line_t *get_addressed_line_node(long); +pattern_t *get_compiled_pattern(void); +int get_des_char(FILE *); +char *get_extended_line(int *, int); +char *get_filename(void); +int get_keyword(void); +long get_line_node_addr(line_t *); +long get_matching_node_addr(pattern_t *, int); +long get_marked_node_addr(int); +char *get_sbuf_line(line_t *); +int get_shell_command(void); +int get_stream_line(FILE *); +int get_tty_line(void); +__dead void handle_hup(int); +__dead void handle_int(int); +void handle_winch(int); +int has_trailing_escape(char *, char *); +void init_buffers(void); +void init_des_cipher(void); +int is_legal_filename(char *); +int join_lines(long, long); +int mark_line_node(line_t *, int); +int move_lines(long); +line_t *next_active_node(void); +long next_addr(void); +int open_sbuf(void); +char *parse_char_class(char *); +int pop_undo_stack(void); +undo_t *push_undo_stack(int, long, long); +int put_des_char(int, FILE *); +char *put_sbuf_line(char *); +int put_stream_line(FILE *, char *, int); +int put_tty_line(char *, int, long, int); +__dead void quit(int); +long read_file(char *, long); +long read_stream(FILE *, long); +int search_and_replace(pattern_t *, int, int); +int set_active_node(line_t *); +void signal_hup(int); +void signal_int(int); +char *strip_escapes(const char *); +int substitute_matching_text(pattern_t *, line_t *, int, int); +char *translit_text(char *, int, int, int); +void unmark_line_node(line_t *); +void unset_active_nodes(line_t *, line_t *); +long write_file(const char *, const char *, long, long); +long write_stream(FILE *, long, long); +void seterrmsg(const char *, ...) __printflike(1, 2); + +/* global buffers */ +extern char stdinbuf[]; +extern char *ibuf; +extern char *ibufp; +extern int ibufsz; + +/* global flags */ +extern int isbinary; +extern int isglobal; +extern int modified; +extern int mutex; +extern int sigflags; + +/* global vars */ +extern long addr_last; +extern long current_addr; +extern long first_addr; +extern int lineno; +extern long second_addr; +extern long rows; +extern int cols; +extern int scripted; +extern int ere; +extern int des; +extern int newline_added; /* io.c */ +extern int patlock; +extern char errmsg[]; /* re.c */ +extern long u_current_addr; /* undo.c */ +extern long u_addr_last; /* undo.c */ +#if defined(sun) && !defined(__SVR4) +extern char *sys_errlist[]; +#endif diff --git a/bin/ed/glbl.c b/bin/ed/glbl.c new file mode 100644 index 0000000..d79675d --- /dev/null +++ b/bin/ed/glbl.c @@ -0,0 +1,213 @@ +/* $NetBSD: glbl.c,v 1.10 2019/01/04 19:13:58 maya Exp $ */ + +/* glob.c: This file contains the global command routines for the ed line + editor */ +/*- + * Copyright (c) 1993 Andrew Moore, Talke Studio. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char *rcsid = "@(#)glob.c,v 1.1 1994/02/01 00:34:40 alm Exp"; +#else +__RCSID("$NetBSD: glbl.c,v 1.10 2019/01/04 19:13:58 maya Exp $"); +#endif +#endif /* not lint */ + +#include <sys/ioctl.h> +#include <sys/wait.h> + +#include "ed.h" + + +/* build_active_list: add line matching a pattern to the global-active list */ +int +build_active_list(int isgcmd) +{ + pattern_t *pat; + line_t *lp; + long n; + char *s; + char delimiter; + + if ((delimiter = *ibufp) == ' ' || delimiter == '\n') { + seterrmsg("invalid pattern delimiter"); + return ERR; + } else if ((pat = get_compiled_pattern()) == NULL) + return ERR; + else if (*ibufp == delimiter) + ibufp++; + clear_active_list(); + lp = get_addressed_line_node(first_addr); + for (n = first_addr; n <= second_addr; n++, lp = lp->q_forw) { + if ((s = get_sbuf_line(lp)) == NULL) + return ERR; + if (isbinary) + NUL_TO_NEWLINE(s, lp->len); + if ((!regexec(pat, s, 0, NULL, 0)) == isgcmd && + set_active_node(lp) < 0) + return ERR; + } + return 0; +} + + +/* exec_global: apply command list in the command buffer to the active + lines in a range; return command status */ +long +exec_global(int interact, int gflag) +{ + static char *ocmd = NULL; + static int ocmdsz = 0; + + line_t *lp = NULL; + int status; + int n; + char *cmd = NULL; +#ifdef BACKWARDS + char cmdp[] = "p\n"; + + if (!interact) { + if (!strcmp(ibufp, "\n")) + cmd = cmdp; /* null cmd-list == `p' */ + else if ((cmd = get_extended_line(&n, 0)) == NULL) + return ERR; + } +#else + if (!interact && (cmd = get_extended_line(&n, 0)) == NULL) + return ERR; +#endif + clear_undo_stack(); + while ((lp = next_active_node()) != NULL) { + if ((current_addr = get_line_node_addr(lp)) < 0) + return ERR; + if (interact) { + /* print current_addr; get a command in global syntax */ + if (display_lines(current_addr, current_addr, gflag) < 0) + return ERR; + while ((n = get_tty_line()) > 0 && + ibuf[n - 1] != '\n') + clearerr(stdin); + if (n < 0) + return ERR; + else if (n == 0) { + seterrmsg("unexpected end-of-file"); + return ERR; + } else if (n == 1 && !strcmp(ibuf, "\n")) + continue; + else if (n == 2 && !strcmp(ibuf, "&\n")) { + if (cmd == NULL) { + seterrmsg("no previous command"); + return ERR; + } else cmd = ocmd; + } else if ((cmd = get_extended_line(&n, 0)) == NULL) + return ERR; + else { + REALLOC(ocmd, ocmdsz, n + 1, ERR); + memcpy(ocmd, cmd, n + 1); + cmd = ocmd; + } + + } + ibufp = cmd; + for (; *ibufp;) + if ((status = extract_addr_range()) < 0 || + (status = exec_command()) < 0 || + (status > 0 && (status = display_lines( + current_addr, current_addr, status)) < 0)) + return status; + } + return 0; +} + + +line_t **active_list; /* list of lines active in a global command */ +long active_last; /* index of last active line in active_list */ +long active_size; /* size of active_list */ +long active_ptr; /* active_list index (non-decreasing) */ +long active_ndx; /* active_list index (modulo active_last) */ + +/* set_active_node: add a line node to the global-active list */ +int +set_active_node(line_t *lp) +{ + if (active_last + 1 > active_size) { + int ti = active_size; + line_t **ts; + SPL1(); + if ((ts = (line_t **) realloc(active_list, + (ti += MINBUFSZ) * sizeof(line_t **))) == NULL) { + fprintf(stderr, "%s\n", strerror(errno)); + seterrmsg("out of memory"); + SPL0(); + return ERR; + } + active_size = ti; + active_list = ts; + SPL0(); + } + active_list[active_last++] = lp; + return 0; +} + + +/* unset_active_nodes: remove a range of lines from the global-active list */ +void +unset_active_nodes(line_t *np, line_t *mp) +{ + line_t *lp; + long i; + + for (lp = np; lp != mp; lp = lp->q_forw) + for (i = 0; i < active_last; i++) + if (active_list[active_ndx] == lp) { + active_list[active_ndx] = NULL; + active_ndx = INC_MOD(active_ndx, active_last - 1); + break; + } else active_ndx = INC_MOD(active_ndx, active_last - 1); +} + + +/* next_active_node: return the next global-active line node */ +line_t * +next_active_node(void) +{ + while (active_ptr < active_last && active_list[active_ptr] == NULL) + active_ptr++; + return (active_ptr < active_last) ? active_list[active_ptr++] : NULL; +} + + +/* clear_active_list: clear the global-active list */ +void +clear_active_list(void) +{ + SPL1(); + active_size = active_last = active_ptr = active_ndx = 0; + free(active_list); + active_list = NULL; + SPL0(); +} diff --git a/bin/ed/io.c b/bin/ed/io.c new file mode 100644 index 0000000..7ef23b4 --- /dev/null +++ b/bin/ed/io.c @@ -0,0 +1,358 @@ +/* $NetBSD: io.c,v 1.10 2014/03/23 05:06:42 dholland Exp $ */ + +/* io.c: This file contains the i/o routines for the ed line editor */ +/*- + * Copyright (c) 1993 Andrew Moore, Talke Studio. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char *rcsid = "@(#)io.c,v 1.1 1994/02/01 00:34:41 alm Exp"; +#else +__RCSID("$NetBSD: io.c,v 1.10 2014/03/23 05:06:42 dholland Exp $"); +#endif +#endif /* not lint */ + +#include "ed.h" + + +/* read_file: read a named file/pipe into the buffer; return line count */ +long +read_file(char *fn, long n) +{ + FILE *fp; + long size; + + + fp = (*fn == '!') ? popen(fn + 1, "r") : fopen(strip_escapes(fn), "r"); + if (fp == NULL) { + fprintf(stderr, "%s: %s\n", fn, strerror(errno)); + seterrmsg("cannot open input file"); + return ERR; + } else if ((size = read_stream(fp, n)) < 0) + return ERR; + else if (((*fn == '!') ? pclose(fp) : fclose(fp)) < 0) { + fprintf(stderr, "%s: %s\n", fn, strerror(errno)); + seterrmsg("cannot close input file"); + return ERR; + } + if (!scripted) + fprintf(stderr, "%lu\n", size); + return current_addr - n; +} + + +char *sbuf; /* file i/o buffer */ +int sbufsz; /* file i/o buffer size */ +int newline_added; /* if set, newline appended to input file */ + +/* read_stream: read a stream into the editor buffer; return status */ +long +read_stream(FILE *fp, long n) +{ + line_t *lp = get_addressed_line_node(n); + undo_t *up = NULL; + unsigned long size = 0; + int o_newline_added = newline_added; + int o_isbinary = isbinary; + int appended = (n == addr_last); + int len; + + isbinary = newline_added = 0; + if (des) + init_des_cipher(); + for (current_addr = n; (len = get_stream_line(fp)) > 0; size += len) { + SPL1(); + if (put_sbuf_line(sbuf) == NULL) { + SPL0(); + return ERR; + } + lp = lp->q_forw; + if (up) + up->t = lp; + else if ((up = push_undo_stack(UADD, current_addr, + current_addr)) == NULL) { + SPL0(); + return ERR; + } + SPL0(); + } + if (len < 0) + return ERR; + if (appended && size && o_isbinary && o_newline_added) + fputs("newline inserted\n", stderr); + else if (newline_added && (!appended || (!isbinary && !o_isbinary))) + fputs("newline appended\n", stderr); + if (isbinary && newline_added && !appended) + size += 1; + if (!size) + newline_added = 1; + newline_added = appended ? newline_added : o_newline_added; + isbinary = isbinary | o_isbinary; + if (des) + size += 8 - size % 8; /* adjust DES size */ + return size; +} + + +/* get_stream_line: read a line of text from a stream; return line length */ +int +get_stream_line(FILE *fp) +{ + int c; + int i = 0; + + while (((c = des ? get_des_char(fp) : getc(fp)) != EOF || (!feof(fp) && + !ferror(fp))) && c != '\n') { + REALLOC(sbuf, sbufsz, i + 1, ERR); + if (!(sbuf[i++] = c)) + isbinary = 1; + } + REALLOC(sbuf, sbufsz, i + 2, ERR); + if (c == '\n') + sbuf[i++] = c; + else if (ferror(fp)) { + fprintf(stderr, "%s\n", strerror(errno)); + seterrmsg("cannot read input file"); + return ERR; + } else if (i) { + sbuf[i++] = '\n'; + newline_added = 1; + } + sbuf[i] = '\0'; + return (isbinary && newline_added && i) ? --i : i; +} + + +/* write_file: write a range of lines to a named file/pipe; return line count */ +long +write_file(const char *fn, const char *mode, long n, long m) +{ + FILE *fp; + long size; + + fp = (*fn == '!') ? popen(fn+1, "w") : fopen(strip_escapes(fn), mode); + if (fp == NULL) { + fprintf(stderr, "%s: %s\n", fn, strerror(errno)); + seterrmsg("cannot open output file"); + return ERR; + } else if ((size = write_stream(fp, n, m)) < 0) + return ERR; + else if (((*fn == '!') ? pclose(fp) : fclose(fp)) < 0) { + fprintf(stderr, "%s: %s\n", fn, strerror(errno)); + seterrmsg("cannot close output file"); + return ERR; + } + if (!scripted) + fprintf(stderr, "%lu\n", size); + return n ? m - n + 1 : 0; +} + + +/* write_stream: write a range of lines to a stream; return status */ +long +write_stream(FILE *fp, long n, long m) +{ + line_t *lp = get_addressed_line_node(n); + unsigned long size = 0; + char *s; + int len; + + if (des) + init_des_cipher(); + for (; n && n <= m; n++, lp = lp->q_forw) { + if ((s = get_sbuf_line(lp)) == NULL) + return ERR; + len = lp->len; + if (n != addr_last || !isbinary || !newline_added) + s[len++] = '\n'; + if (put_stream_line(fp, s, len) < 0) + return ERR; + size += len; + } + if (des) { + flush_des_file(fp); /* flush buffer */ + size += 8 - size % 8; /* adjust DES size */ + } + return size; +} + + +/* put_stream_line: write a line of text to a stream; return status */ +int +put_stream_line(FILE *fp, char *s, int len) +{ + while (len--) + if ((des ? put_des_char(*s++, fp) : fputc(*s++, fp)) < 0) { + fprintf(stderr, "%s\n", strerror(errno)); + seterrmsg("cannot write file"); + return ERR; + } + return 0; +} + +/* get_extended_line: get a an extended line from stdin */ +char * +get_extended_line(int *sizep, int nonl) +{ + static char *cvbuf = NULL; /* buffer */ + static int cvbufsz = 0; /* buffer size */ + + int l, n; + char *t = ibufp; + + while (*t++ != '\n') + ; + if ((l = t - ibufp) < 2 || !has_trailing_escape(ibufp, ibufp + l - 1)) { + *sizep = l; + return ibufp; + } + *sizep = -1; + REALLOC(cvbuf, cvbufsz, l, NULL); + memcpy(cvbuf, ibufp, l); + *(cvbuf + --l - 1) = '\n'; /* strip trailing esc */ + if (nonl) l--; /* strip newline */ + for (;;) { + if ((n = get_tty_line()) < 0) + return NULL; + else if (n == 0 || ibuf[n - 1] != '\n') { + seterrmsg("unexpected end-of-file"); + return NULL; + } + REALLOC(cvbuf, cvbufsz, l + n, NULL); + memcpy(cvbuf + l, ibuf, n); + l += n; + if (n < 2 || !has_trailing_escape(cvbuf, cvbuf + l - 1)) + break; + *(cvbuf + --l - 1) = '\n'; /* strip trailing esc */ + if (nonl) l--; /* strip newline */ + } + REALLOC(cvbuf, cvbufsz, l + 1, NULL); + cvbuf[l] = '\0'; + *sizep = l; + return cvbuf; +} + + +/* get_tty_line: read a line of text from stdin; return line length */ +int +get_tty_line(void) +{ + int oi = 0; + int i = 0; + int c; + + for (;;) + switch (c = getchar()) { + default: + oi = 0; + REALLOC(ibuf, ibufsz, i + 2, ERR); + if (!(ibuf[i++] = c)) isbinary = 1; + if (c != '\n') + continue; + lineno++; + ibuf[i] = '\0'; + ibufp = ibuf; + return i; + case EOF: + if (ferror(stdin)) { + fprintf(stderr, "stdin: %s\n", strerror(errno)); + seterrmsg("cannot read stdin"); + clearerr(stdin); + ibufp = NULL; + return ERR; + } else { + clearerr(stdin); + if (i != oi) { + oi = i; + continue; + } else if (i) + ibuf[i] = '\0'; + ibufp = ibuf; + return i; + } + } +} + + + +#define ESCAPES "\a\b\f\n\r\t\v\\" +#define ESCCHARS "abfnrtv\\" + +/* put_tty_line: print text to stdout */ +int +put_tty_line(char *s, int l, long n, int gflag) +{ + int col = 0; + char *cp; +#ifndef BACKWARDS + int lc = 0; +#endif + + if (gflag & GNP) { + printf("%ld\t", n); + col = 8; + } + for (; l--; s++) { + if ((gflag & GLS) && ++col > cols) { + fputs("\\\n", stdout); + col = 1; +#ifndef BACKWARDS + if (!scripted && !isglobal && ++lc > rows) { + lc = 0; + fputs("Press <RETURN> to continue... ", stdout); + fflush(stdout); + if (get_tty_line() < 0) + return ERR; + } +#endif + } + if (gflag & GLS) { + if (31 < *s && *s < 127 && *s != '\\') + putchar(*s); + else { + putchar('\\'); + col++; + if (*s && (cp = strchr(ESCAPES, *s)) != NULL) + putchar(ESCCHARS[cp - ESCAPES]); + else { + putchar((((unsigned char) *s & 0300) >> 6) + '0'); + putchar((((unsigned char) *s & 070) >> 3) + '0'); + putchar(((unsigned char) *s & 07) + '0'); + col += 2; + } + } + + } else + putchar(*s); + } +#ifndef BACKWARDS + if (gflag & GLS) + putchar('$'); +#endif + putchar('\n'); + return 0; +} diff --git a/bin/ed/main.c b/bin/ed/main.c new file mode 100644 index 0000000..f86148a --- /dev/null +++ b/bin/ed/main.c @@ -0,0 +1,1433 @@ +/* $NetBSD: main.c,v 1.30 2018/06/18 14:56:24 christos Exp $ */ + +/* main.c: This file contains the main control and user-interface routines + for the ed line editor. */ +/*- + * Copyright (c) 1993 Andrew Moore, Talke Studio. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT( +"@(#) Copyright (c) 1993 Andrew Moore, Talke Studio.\ + All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char *rcsid = "@(#)main.c,v 1.1 1994/02/01 00:34:42 alm Exp"; +#else +__RCSID("$NetBSD: main.c,v 1.30 2018/06/18 14:56:24 christos Exp $"); +#endif +#endif /* not lint */ + +/* + * CREDITS + * + * This program is based on the editor algorithm described in + * Brian W. Kernighan and P. J. Plauger's book "Software Tools + * in Pascal," Addison-Wesley, 1981. + * + * The buffering algorithm is attributed to Rodney Ruddock of + * the University of Guelph, Guelph, Ontario. + * + * The cbc.c encryption code is adapted from + * the bdes program by Matt Bishop of Dartmouth College, + * Hanover, NH. + * + */ + +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <termios.h> +#include <ctype.h> +#include <setjmp.h> +#include <pwd.h> + +#include "ed.h" + + +#ifdef _POSIX_SOURCE +sigjmp_buf env; +#else +jmp_buf env; +#endif + +/* static buffers */ +char stdinbuf[1]; /* stdin buffer */ +char *shcmd; /* shell command buffer */ +int shcmdsz; /* shell command buffer size */ +int shcmdi; /* shell command buffer index */ +char *ibuf; /* ed command-line buffer */ +int ibufsz; /* ed command-line buffer size */ +char *ibufp; /* pointer to ed command-line buffer */ + +/* global flags */ +int des = 0; /* if set, use crypt(3) for i/o */ +int garrulous = 0; /* if set, print all error messages */ +int isbinary; /* if set, buffer contains ASCII NULs */ +int isglobal; /* if set, doing a global command */ +int modified; /* if set, buffer modified since last write */ +int mutex = 0; /* if set, signals set "sigflags" */ +int red = 0; /* if set, restrict shell/directory access */ +int ere = 0; /* if set, use extended regexes */ +int scripted = 0; /* if set, suppress diagnostics */ +int secure = 0; /* is set, ! is not allowed */ +int sigflags = 0; /* if set, signals received while mutex set */ +int sigactive = 0; /* if set, signal handlers are enabled */ + +char old_filename[MAXPATHLEN + 1] = ""; /* default filename */ +long current_addr; /* current address in editor buffer */ +long addr_last; /* last address in editor buffer */ +int lineno; /* script line number */ +const char *prompt; /* command-line prompt */ +const char *dps = "*"; /* default command-line prompt */ + + +static const char usage[] = "Usage: %s [-] [-ESsx] [-p string] [name]\n"; + +/* ed: line editor */ +int +main(int ac, char *av[]) +{ + int c, n; + long status = 0; + volatile int argc = ac; + char ** volatile argv = av; + + red = (n = strlen(argv[0])) > 2 && argv[0][n - 3] == 'r'; +top: + while ((c = getopt(argc, argv, "p:sxES")) != -1) + switch(c) { + case 'p': /* set prompt */ + prompt = optarg; + break; + case 's': /* run script */ + scripted = 1; + break; + case 'x': /* use crypt */ +#ifdef DES + des = get_keyword(); +#else + fprintf(stderr, "crypt unavailable\n?\n"); +#endif + break; + + case 'E': + ere = REG_EXTENDED; + break; + case 'S': /* ! is not allowed */ + secure = 1; + break; + default: + fprintf(stderr, usage, getprogname()); + exit(1); + /* NOTREACHED */ + } + argv += optind; + argc -= optind; + if (argc && **argv == '-') { + scripted = 1; + if (argc > 1) { + optind = 1; + goto top; + } + argv++; + argc--; + } + /* assert: reliable signals! */ +#ifdef SIGWINCH + handle_winch(SIGWINCH); + if (isatty(0)) signal(SIGWINCH, handle_winch); +#endif + signal(SIGHUP, signal_hup); + signal(SIGQUIT, SIG_IGN); + signal(SIGINT, signal_int); +#ifdef _POSIX_SOURCE + if ((status = sigsetjmp(env, 1)) != 0) +#else + if ((status = setjmp(env)) != 0) +#endif + { + fputs("\n?\n", stderr); + seterrmsg("interrupt"); + } else { + init_buffers(); + sigactive = 1; /* enable signal handlers */ + if (argc && **argv && is_legal_filename(*argv)) { + if (read_file(*argv, 0) < 0 && !isatty(0)) + quit(2); + else if (**argv != '!') + strlcpy(old_filename, *argv, + sizeof(old_filename) - 2); + } else if (argc) { + fputs("?\n", stderr); + if (**argv == '\0') + seterrmsg("invalid filename"); + if (!isatty(0)) + quit(2); + } + } + for (;;) { + if (status < 0 && garrulous) + fprintf(stderr, "%s\n", errmsg); + if (prompt) { + printf("%s", prompt); + fflush(stdout); + } + if ((n = get_tty_line()) < 0) { + status = ERR; + continue; + } else if (n == 0) { + if (modified && !scripted) { + fputs("?\n", stderr); + seterrmsg("warning: file modified"); + if (!isatty(0)) { + if (garrulous) { + fprintf(stderr, + "script, line %d: %s\n", + lineno, errmsg); + } + quit(2); + } + clearerr(stdin); + modified = 0; + status = EMOD; + continue; + } else + quit(0); + } else if (ibuf[n - 1] != '\n') { + /* discard line */ + seterrmsg("unexpected end-of-file"); + clearerr(stdin); + status = ERR; + continue; + } + isglobal = 0; + if ((status = extract_addr_range()) >= 0 && + (status = exec_command()) >= 0) { + if (status == 0) + continue; + status = display_lines(current_addr, current_addr, + status); + if (status >= 0) + continue; + } + switch (status) { + case EOF: + quit(0); + case EMOD: + modified = 0; + fputs("?\n", stderr); /* give warning */ + seterrmsg("warning: file modified"); + if (!isatty(0)) { + if (garrulous) { + fprintf(stderr, + "script, line %d: %s\n", + lineno, errmsg); + } + quit(2); + } + break; + case FATAL: + if (garrulous) { + if (!isatty(0)) { + fprintf(stderr, + "script, line %d: %s\n", + lineno, errmsg); + } else { + fprintf(stderr, "%s\n", errmsg); + } + } + quit(3); + default: + fputs("?\n", stderr); + if (!isatty(0)) { + if (garrulous) { + fprintf(stderr, "script, line %d: %s\n", + lineno, errmsg); + } + quit(2); + } + break; + } + } + /* NOTREACHED */ +} + +long first_addr, second_addr, addr_cnt; + +/* extract_addr_range: get line addresses from the command buffer until an + illegal address is seen; return status */ +int +extract_addr_range(void) +{ + long addr; + + addr_cnt = 0; + first_addr = second_addr = current_addr; + while ((addr = next_addr()) >= 0) { + addr_cnt++; + first_addr = second_addr; + second_addr = addr; + if (*ibufp != ',' && *ibufp != ';') + break; + else if (*ibufp++ == ';') + current_addr = addr; + } + if ((addr_cnt = min(addr_cnt, 2)) == 1 || second_addr != addr) + first_addr = second_addr; + return (addr == ERR) ? ERR : 0; +} + + +#define SKIP_BLANKS() while (isspace((unsigned char)*ibufp) && *ibufp != '\n') \ + ibufp++ + +#define MUST_BE_FIRST() \ + if (!first) { seterrmsg("invalid address"); return ERR; } + +/* next_addr: return the next line address in the command buffer */ +long +next_addr(void) +{ + char *hd; + long addr = current_addr; + long n; + int first = 1; + int c; + + SKIP_BLANKS(); + for (hd = ibufp;; first = 0) + switch (c = *ibufp) { + case '+': + case '\t': + case ' ': + case '-': + case '^': + ibufp++; + SKIP_BLANKS(); + if (isdigit((unsigned char)*ibufp)) { + STRTOL(n, ibufp); + addr += (c == '-' || c == '^') ? -n : n; + } else if (!isspace((unsigned char)c)) + addr += (c == '-' || c == '^') ? -1 : 1; + break; + case '0': case '1': case '2': + case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + MUST_BE_FIRST(); + STRTOL(addr, ibufp); + break; + case '.': + case '$': + MUST_BE_FIRST(); + ibufp++; + addr = (c == '.') ? current_addr : addr_last; + break; + case '/': + case '?': + MUST_BE_FIRST(); + if ((addr = get_matching_node_addr( + get_compiled_pattern(), c == '/')) < 0) + return ERR; + else if (c == *ibufp) + ibufp++; + break; + case '\'': + MUST_BE_FIRST(); + ibufp++; + if ((addr = get_marked_node_addr((unsigned char)*ibufp++)) < 0) + return ERR; + break; + case '%': + case ',': + case ';': + if (first) { + ibufp++; + addr_cnt++; + second_addr = (c == ';') ? current_addr : 1; + addr = addr_last; + break; + } + /* FALL THROUGH */ + default: + if (ibufp == hd) + return EOF; + else if (addr < 0 || addr_last < addr) { + seterrmsg("invalid address"); + return ERR; + } else + return addr; + } + /* NOTREACHED */ +} + + +#ifdef BACKWARDS +/* GET_THIRD_ADDR: get a legal address from the command buffer */ +#define GET_THIRD_ADDR(addr) \ +{ \ + long ol1, ol2; \ +\ + ol1 = first_addr, ol2 = second_addr; \ + if (extract_addr_range() < 0) \ + return ERR; \ + else if (addr_cnt == 0) { \ + seterrmsg("destination expected"); \ + return ERR; \ + } else if (second_addr < 0 || addr_last < second_addr) { \ + seterrmsg("invalid address"); \ + return ERR; \ + } \ + addr = second_addr; \ + first_addr = ol1, second_addr = ol2; \ +} +#else /* BACKWARDS */ +/* GET_THIRD_ADDR: get a legal address from the command buffer */ +#define GET_THIRD_ADDR(addr) \ +{ \ + long ol1, ol2; \ +\ + ol1 = first_addr, ol2 = second_addr; \ + if (extract_addr_range() < 0) \ + return ERR; \ + if (second_addr < 0 || addr_last < second_addr) { \ + seterrmsg("invalid address"); \ + return ERR; \ + } \ + addr = second_addr; \ + first_addr = ol1, second_addr = ol2; \ +} +#endif + + +/* GET_COMMAND_SUFFIX: verify the command suffix in the command buffer */ +#define GET_COMMAND_SUFFIX() { \ + int done = 0; \ + do { \ + switch(*ibufp) { \ + case 'p': \ + gflag |= GPR, ibufp++; \ + break; \ + case 'l': \ + gflag |= GLS, ibufp++; \ + break; \ + case 'n': \ + gflag |= GNP, ibufp++; \ + break; \ + default: \ + done++; \ + } \ + } while (!done); \ + if (*ibufp++ != '\n') { \ + seterrmsg("invalid command suffix"); \ + return ERR; \ + } \ +} + + +/* sflags */ +#define SGG 001 /* complement previous global substitute suffix */ +#define SGP 002 /* complement previous print suffix */ +#define SGR 004 /* use last regex instead of last pat */ +#define SGF 010 /* repeat last substitution */ + +int patlock = 0; /* if set, pattern not freed by get_compiled_pattern() */ + +long rows = 22; /* scroll length: ws_row - 2 */ + +/* exec_command: execute the next command in command buffer; return print + request, if any */ +int +exec_command(void) +{ + static pattern_t *pat = NULL; + static int sgflag = 0; + static long sgnum = 0; + + pattern_t *tpat; + char *fnp; + int gflag = 0; + int sflags = 0; + long addr = 0; + int n = 0; + int c; + + SKIP_BLANKS(); + switch(c = *ibufp++) { + case 'a': + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (append_lines(second_addr) < 0) + return ERR; + break; + case 'c': + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (delete_lines(first_addr, second_addr) < 0 || + append_lines(current_addr) < 0) + return ERR; + break; + case 'd': + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (delete_lines(first_addr, second_addr) < 0) + return ERR; + else if ((addr = INC_MOD(current_addr, addr_last)) != 0) + current_addr = addr; + break; + case 'e': + if (modified && !scripted) + return EMOD; + /* fall through */ + case 'E': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } else if (!isspace((unsigned char)*ibufp)) { + seterrmsg("unexpected command suffix"); + return ERR; + } else if ((fnp = get_filename()) == NULL) + return ERR; + GET_COMMAND_SUFFIX(); + if (delete_lines(1, addr_last) < 0) + return ERR; + clear_undo_stack(); + if (close_sbuf() < 0) + return ERR; + else if (open_sbuf() < 0) + return FATAL; + if (*fnp && *fnp != '!') strlcpy(old_filename, fnp, + sizeof(old_filename) - 2); +#ifdef BACKWARDS + if (*fnp == '\0' && *old_filename == '\0') { + seterrmsg("no current filename"); + return ERR; + } +#endif + if (read_file(*fnp ? fnp : old_filename, 0) < 0) + return ERR; + clear_undo_stack(); + modified = 0; + u_current_addr = u_addr_last = -1; + break; + case 'f': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } else if (!isspace((unsigned char)*ibufp)) { + seterrmsg("unexpected command suffix"); + return ERR; + } else if ((fnp = get_filename()) == NULL) + return ERR; + else if (*fnp == '!') { + seterrmsg("invalid redirection"); + return ERR; + } + GET_COMMAND_SUFFIX(); + if (*fnp) strlcpy(old_filename, fnp, sizeof(old_filename) - 2); + printf("%s\n", strip_escapes(old_filename)); + break; + case 'g': + case 'v': + case 'G': + case 'V': + if (isglobal) { + seterrmsg("cannot nest global commands"); + return ERR; + } else if (check_addr_range(1, addr_last) < 0) + return ERR; + else if (build_active_list(c == 'g' || c == 'G') < 0) + return ERR; + else if ((n = (c == 'G' || c == 'V')) != 0) + GET_COMMAND_SUFFIX(); + isglobal++; + if (exec_global(n, gflag) < 0) + return ERR; + break; + case 'h': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + if (*errmsg) fprintf(stderr, "%s\n", errmsg); + break; + case 'H': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + if ((garrulous = 1 - garrulous) && *errmsg) + fprintf(stderr, "%s\n", errmsg); + break; + case 'i': + if (second_addr == 0) { + seterrmsg("invalid address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (append_lines(second_addr - 1) < 0) + return ERR; + break; + case 'j': + if (check_addr_range(current_addr, current_addr + 1) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (first_addr != second_addr && + join_lines(first_addr, second_addr) < 0) + return ERR; + break; + case 'k': + c = *ibufp++; + if (second_addr == 0) { + seterrmsg("invalid address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + if (mark_line_node(get_addressed_line_node(second_addr), (unsigned char)c) < 0) + return ERR; + break; + case 'l': + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (display_lines(first_addr, second_addr, gflag | GLS) < 0) + return ERR; + gflag = 0; + break; + case 'm': + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_THIRD_ADDR(addr); + if (first_addr <= addr && addr < second_addr) { + seterrmsg("invalid destination"); + return ERR; + } + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (move_lines(addr) < 0) + return ERR; + break; + case 'n': + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (display_lines(first_addr, second_addr, gflag | GNP) < 0) + return ERR; + gflag = 0; + break; + case 'p': + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (display_lines(first_addr, second_addr, gflag | GPR) < 0) + return ERR; + gflag = 0; + break; + case 'P': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + prompt = prompt ? NULL : optarg ? optarg : dps; + break; + case 'q': + case 'Q': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + gflag = (modified && !scripted && c == 'q') ? EMOD : EOF; + break; + case 'r': + if (!isspace((unsigned char)*ibufp)) { + seterrmsg("unexpected command suffix"); + return ERR; + } else if (addr_cnt == 0) + second_addr = addr_last; + if ((fnp = get_filename()) == NULL) + return ERR; + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (*old_filename == '\0' && *fnp != '!') + strlcpy(old_filename, fnp, sizeof(old_filename) - 2); +#ifdef BACKWARDS + if (*fnp == '\0' && *old_filename == '\0') { + seterrmsg("no current filename"); + return ERR; + } +#endif + if ((addr = read_file(*fnp ? fnp : old_filename, second_addr)) < 0) + return ERR; + else if (addr && addr != addr_last) + modified = 1; + break; + case 's': + do { + switch(*ibufp) { + case '\n': + sflags |=SGF; + break; + case 'g': + sflags |= SGG; + ibufp++; + break; + case 'p': + sflags |= SGP; + ibufp++; + break; + case 'r': + sflags |= SGR; + ibufp++; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + STRTOL(sgnum, ibufp); + sflags |= SGF; + sgflag &= ~GSG; /* override GSG */ + break; + default: + if (sflags) { + seterrmsg("invalid command suffix"); + return ERR; + } + } + } while (sflags && *ibufp != '\n'); + if (sflags && !pat) { + seterrmsg("no previous substitution"); + return ERR; + } else if (sflags & SGG) + sgnum = 0; /* override numeric arg */ + if (*ibufp != '\n' && *(ibufp + 1) == '\n') { + seterrmsg("invalid pattern delimiter"); + return ERR; + } + tpat = pat; + SPL1(); + if ((!sflags || (sflags & SGR)) && + (tpat = get_compiled_pattern()) == NULL) { + SPL0(); + return ERR; + } else if (tpat != pat) { + if (pat) { + regfree(pat); + free(pat); + } + pat = tpat; + patlock = 1; /* reserve pattern */ + } + SPL0(); + if (!sflags && extract_subst_tail(&sgflag, &sgnum) < 0) + return ERR; + else if (isglobal) + sgflag |= GLB; + else + sgflag &= ~GLB; + if (sflags & SGG) + sgflag ^= GSG; + if (sflags & SGP) + sgflag ^= GPR, sgflag &= ~(GLS | GNP); + do { + switch(*ibufp) { + case 'p': + sgflag |= GPR, ibufp++; + break; + case 'l': + sgflag |= GLS, ibufp++; + break; + case 'n': + sgflag |= GNP, ibufp++; + break; + default: + n++; + } + } while (!n); + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (search_and_replace(pat, sgflag, sgnum) < 0) + return ERR; + break; + case 't': + if (check_addr_range(current_addr, current_addr) < 0) + return ERR; + GET_THIRD_ADDR(addr); + GET_COMMAND_SUFFIX(); + if (!isglobal) clear_undo_stack(); + if (copy_lines(addr) < 0) + return ERR; + break; + case 'u': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); + if (pop_undo_stack() < 0) + return ERR; + break; + case 'w': + case 'W': + if ((n = *ibufp) == 'q' || n == 'Q') { + gflag = EOF; + ibufp++; + } + if (!isspace((unsigned char)*ibufp)) { + seterrmsg("unexpected command suffix"); + return ERR; + } else if ((fnp = get_filename()) == NULL) + return ERR; + if (addr_cnt == 0 && !addr_last) + first_addr = second_addr = 0; + else if (check_addr_range(1, addr_last) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (*old_filename == '\0' && *fnp != '!') + strlcpy(old_filename, fnp, sizeof(old_filename) - 2); +#ifdef BACKWARDS + if (*fnp == '\0' && *old_filename == '\0') { + seterrmsg("no current filename"); + return ERR; + } +#endif + if ((addr = write_file(*fnp ? fnp : old_filename, + (c == 'W') ? "a" : "w", first_addr, second_addr)) < 0) + return ERR; + else if (addr == addr_last) + modified = 0; + else if (modified && !scripted && n == 'q') + gflag = EMOD; + break; + case 'x': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } + GET_COMMAND_SUFFIX(); +#ifdef DES + des = get_keyword(); +#else + seterrmsg("crypt unavailable"); + return ERR; +#endif + break; + case 'z': +#ifdef BACKWARDS + if (check_addr_range(first_addr = 1, current_addr + 1) < 0) +#else + if (check_addr_range(first_addr = 1, current_addr + !isglobal) < 0) +#endif + return ERR; + else if ('0' < *ibufp && *ibufp <= '9') + STRTOL(rows, ibufp); + GET_COMMAND_SUFFIX(); + if (display_lines(second_addr, min(addr_last, + second_addr + rows), gflag) < 0) + return ERR; + gflag = 0; + break; + case '=': + GET_COMMAND_SUFFIX(); + printf("%ld\n", addr_cnt ? second_addr : addr_last); + break; + case '!': + if (addr_cnt > 0) { + seterrmsg("unexpected address"); + return ERR; + } + if ((sflags = get_shell_command()) < 0) + return ERR; + GET_COMMAND_SUFFIX(); + if (sflags) printf("%s\n", shcmd + 1); + system(shcmd + 1); + if (!scripted) printf("!\n"); + break; + case '\n': +#ifdef BACKWARDS + if (check_addr_range(first_addr = 1, current_addr + 1) < 0 +#else + if (check_addr_range(first_addr = 1, current_addr + !isglobal) < 0 +#endif + || display_lines(second_addr, second_addr, 0) < 0) + return ERR; + break; + default: + seterrmsg("unknown command"); + return ERR; + } + return gflag; +} + + +/* check_addr_range: return status of address range check */ +int +check_addr_range(long n, long m) +{ + if (addr_cnt == 0) { + first_addr = n; + second_addr = m; + } + if (first_addr > second_addr || 1 > first_addr || + second_addr > addr_last) { + seterrmsg("invalid address"); + return ERR; + } + return 0; +} + + +/* get_matching_node_addr: return the address of the next line matching a + pattern in a given direction. wrap around begin/end of editor buffer if + necessary */ +long +get_matching_node_addr(pattern_t *pat, int dir) +{ + char *s; + long n = current_addr; + line_t *lp; + + if (!pat) return ERR; + do { + if ((n = dir ? INC_MOD(n, addr_last) : + DEC_MOD(n, addr_last)) != 0) { + lp = get_addressed_line_node(n); + if ((s = get_sbuf_line(lp)) == NULL) + return ERR; + if (isbinary) + NUL_TO_NEWLINE(s, lp->len); + if (!regexec(pat, s, 0, NULL, 0)) + return n; + } + } while (n != current_addr); + seterrmsg("no match"); + return ERR; +} + + +/* get_filename: return pointer to copy of filename in the command buffer */ +char * +get_filename(void) +{ + static char *file = NULL; + static int filesz = 0; + + int n; + + if (*ibufp != '\n') { + SKIP_BLANKS(); + if (*ibufp == '\n') { + seterrmsg("invalid filename"); + return NULL; + } else if ((ibufp = get_extended_line(&n, 1)) == NULL) + return NULL; + else if (*ibufp == '!') { + ibufp++; + if ((n = get_shell_command()) < 0) + return NULL; + if (n) printf("%s\n", shcmd + 1); + return shcmd; + } else if (n - 1 > MAXPATHLEN) { + seterrmsg("filename too long"); + return NULL; + } + } +#ifndef BACKWARDS + else if (*old_filename == '\0') { + seterrmsg("no current filename"); + return NULL; + } +#endif + REALLOC(file, filesz, MAXPATHLEN + 1, NULL); + for (n = 0; *ibufp != '\n';) + file[n++] = *ibufp++; + file[n] = '\0'; + return is_legal_filename(file) ? file : NULL; +} + + +/* get_shell_command: read a shell command from stdin; return substitution + status */ +int +get_shell_command(void) +{ + static char *buf = NULL; + static int n = 0; + + char *s; /* substitution char pointer */ + int i = 0; + int j = 0; + + if (red || secure) { + seterrmsg("shell access restricted"); + return ERR; + } else if ((s = ibufp = get_extended_line(&j, 1)) == NULL) + return ERR; + REALLOC(buf, n, j + 1, ERR); + buf[i++] = '!'; /* prefix command w/ bang */ + while (*ibufp != '\n') + switch (*ibufp) { + default: + REALLOC(buf, n, i + 2, ERR); + buf[i++] = *ibufp; + if (*ibufp++ == '\\') + buf[i++] = *ibufp++; + break; + case '!': + if (s != ibufp) { + REALLOC(buf, n, i + 1, ERR); + buf[i++] = *ibufp++; + } +#ifdef BACKWARDS + else if (shcmd == NULL || *(shcmd + 1) == '\0') +#else + else if (shcmd == NULL) +#endif + { + seterrmsg("no previous command"); + return ERR; + } else { + REALLOC(buf, n, i + shcmdi, ERR); + for (s = shcmd + 1; s < shcmd + shcmdi;) + buf[i++] = *s++; + s = ibufp++; + } + break; + case '%': + if (*old_filename == '\0') { + seterrmsg("no current filename"); + return ERR; + } + j = strlen(s = strip_escapes(old_filename)); + REALLOC(buf, n, i + j, ERR); + while (j--) + buf[i++] = *s++; + s = ibufp++; + break; + } + REALLOC(shcmd, shcmdsz, i + 1, ERR); + memcpy(shcmd, buf, i); + shcmd[shcmdi = i] = '\0'; + return *s == '!' || *s == '%'; +} + + +/* append_lines: insert text from stdin to after line n; stop when either a + single period is read or EOF; return status */ +int +append_lines(long n) +{ + int l; + char *lp = ibuf; + char *eot; + undo_t *up = NULL; + + for (current_addr = n;;) { + if (!isglobal) { + if ((l = get_tty_line()) < 0) + return ERR; + else if (l == 0 || ibuf[l - 1] != '\n') { + clearerr(stdin); + return l ? EOF : 0; + } + lp = ibuf; + } else if (*(lp = ibufp) == '\0') + return 0; + else { + while (*ibufp++ != '\n') + ; + l = ibufp - lp; + } + if (l == 2 && lp[0] == '.' && lp[1] == '\n') { + return 0; + } + eot = lp + l; + SPL1(); + do { + if ((lp = put_sbuf_line(lp)) == NULL) { + SPL0(); + return ERR; + } else if (up) + up->t = get_addressed_line_node(current_addr); + else if ((up = push_undo_stack(UADD, current_addr, + current_addr)) == NULL) { + SPL0(); + return ERR; + } + } while (lp != eot); + modified = 1; + SPL0(); + } + /* NOTREACHED */ +} + + +/* join_lines: replace a range of lines with the joined text of those lines */ +int +join_lines(long from, long to) +{ + static char *buf = NULL; + static int n; + + char *s; + int size = 0; + line_t *bp, *ep; + + ep = get_addressed_line_node(INC_MOD(to, addr_last)); + bp = get_addressed_line_node(from); + for (; bp != ep; bp = bp->q_forw) { + if ((s = get_sbuf_line(bp)) == NULL) + return ERR; + REALLOC(buf, n, size + bp->len, ERR); + memcpy(buf + size, s, bp->len); + size += bp->len; + } + REALLOC(buf, n, size + 2, ERR); + memcpy(buf + size, "\n", 2); + if (delete_lines(from, to) < 0) + return ERR; + current_addr = from - 1; + SPL1(); + if (put_sbuf_line(buf) == NULL || + push_undo_stack(UADD, current_addr, current_addr) == NULL) { + SPL0(); + return ERR; + } + modified = 1; + SPL0(); + return 0; +} + + +/* move_lines: move a range of lines */ +int +move_lines(long addr) +{ + line_t *b1, *a1, *b2, *a2; + long n = INC_MOD(second_addr, addr_last); + long p = first_addr - 1; + int done = (addr == first_addr - 1 || addr == second_addr); + + SPL1(); + if (done) { + a2 = get_addressed_line_node(n); + b2 = get_addressed_line_node(p); + current_addr = second_addr; + } else if (push_undo_stack(UMOV, p, n) == NULL || + push_undo_stack(UMOV, addr, INC_MOD(addr, addr_last)) == NULL) { + SPL0(); + return ERR; + } else { + a1 = get_addressed_line_node(n); + if (addr < first_addr) { + b1 = get_addressed_line_node(p); + b2 = get_addressed_line_node(addr); + /* this get_addressed_line_node last! */ + } else { + b2 = get_addressed_line_node(addr); + b1 = get_addressed_line_node(p); + /* this get_addressed_line_node last! */ + } + a2 = b2->q_forw; + REQUE(b2, b1->q_forw); + REQUE(a1->q_back, a2); + REQUE(b1, a1); + current_addr = addr + ((addr < first_addr) ? + second_addr - first_addr + 1 : 0); + } + if (isglobal) + unset_active_nodes(b2->q_forw, a2); + modified = 1; + SPL0(); + return 0; +} + + +/* copy_lines: copy a range of lines; return status */ +int +copy_lines(long addr) +{ + line_t *lp, *np = get_addressed_line_node(first_addr); + undo_t *up = NULL; + long n = second_addr - first_addr + 1; + long m = 0; + + current_addr = addr; + if (first_addr <= addr && addr < second_addr) { + n = addr - first_addr + 1; + m = second_addr - addr; + } + for (; n > 0; n=m, m=0, np = get_addressed_line_node(current_addr + 1)) + for (; n-- > 0; np = np->q_forw) { + SPL1(); + if ((lp = dup_line_node(np)) == NULL) { + SPL0(); + return ERR; + } + add_line_node(lp); + if (up) + up->t = lp; + else if ((up = push_undo_stack(UADD, current_addr, + current_addr)) == NULL) { + SPL0(); + return ERR; + } + modified = 1; + SPL0(); + } + return 0; +} + + +/* delete_lines: delete a range of lines */ +int +delete_lines(long from, long to) +{ + line_t *n, *p; + + SPL1(); + if (push_undo_stack(UDEL, from, to) == NULL) { + SPL0(); + return ERR; + } + n = get_addressed_line_node(INC_MOD(to, addr_last)); + p = get_addressed_line_node(from - 1); + /* this get_addressed_line_node last! */ + if (isglobal) + unset_active_nodes(p->q_forw, n); + REQUE(p, n); + addr_last -= to - from + 1; + current_addr = from - 1; + modified = 1; + SPL0(); + return 0; +} + + +/* display_lines: print a range of lines to stdout */ +int +display_lines(long from, long to, int gflag) +{ + line_t *bp; + line_t *ep; + char *s; + + if (!from) { + seterrmsg("invalid address"); + return ERR; + } + ep = get_addressed_line_node(INC_MOD(to, addr_last)); + bp = get_addressed_line_node(from); + for (; bp != ep; bp = bp->q_forw) { + if ((s = get_sbuf_line(bp)) == NULL) + return ERR; + if (put_tty_line(s, bp->len, current_addr = from++, gflag) < 0) + return ERR; + } + return 0; +} + + +#define MAXMARK 26 /* max number of marks */ + +line_t *mark[MAXMARK]; /* line markers */ +int markno; /* line marker count */ + +/* mark_line_node: set a line node mark */ +int +mark_line_node(line_t *lp, int n) +{ + if (!islower(n)) { + seterrmsg("invalid mark character"); + return ERR; + } else if (mark[n - 'a'] == NULL) + markno++; + mark[n - 'a'] = lp; + return 0; +} + + +/* get_marked_node_addr: return address of a marked line */ +long +get_marked_node_addr(int n) +{ + if (!islower(n)) { + seterrmsg("invalid mark character"); + return ERR; + } + return get_line_node_addr(mark[n - 'a']); +} + + +/* unmark_line_node: clear line node mark */ +void +unmark_line_node(line_t *lp) +{ + int i; + + for (i = 0; markno && i < MAXMARK; i++) + if (mark[i] == lp) { + mark[i] = NULL; + markno--; + } +} + + +/* dup_line_node: return a pointer to a copy of a line node */ +line_t * +dup_line_node(line_t *lp) +{ + line_t *np; + + if ((np = (line_t *) malloc(sizeof(line_t))) == NULL) { + fprintf(stderr, "%s\n", strerror(errno)); + seterrmsg("out of memory"); + return NULL; + } + np->seek = lp->seek; + np->len = lp->len; + return np; +} + + +/* has_trailing_escape: return the parity of escapes preceding a character + in a string */ +int +has_trailing_escape(char *s, char *t) +{ + return (s == t || *(t - 1) != '\\') ? 0 : !has_trailing_escape(s, t - 1); +} + + +/* strip_escapes: return copy of escaped string of at most length MAXPATHLEN */ +char * +strip_escapes(const char *s) +{ + static char *file = NULL; + static int filesz = 0; + + int i = 0; + + REALLOC(file, filesz, MAXPATHLEN + 1, NULL); + while ((i < (filesz - 1)) && + (file[i++] = (*s == '\\') != '\0' ? *++s : *s)) + s++; + file[filesz - 1] = '\0'; + return file; +} + + +void +signal_hup(int signo) +{ + if (mutex) + sigflags |= (1 << (signo - 1)); + else handle_hup(signo); +} + + +void +signal_int(int signo) +{ + if (mutex) + sigflags |= (1 << (signo - 1)); + else handle_int(signo); +} + + +void +handle_hup(int signo) +{ + char *hup = NULL; /* hup filename */ + char *s; + int n; + + if (!sigactive) + quit(1); + sigflags &= ~(1 << (signo - 1)); + if (addr_last && write_file("ed.hup", "w", 1, addr_last) < 0 && + (s = getenv("HOME")) != NULL && + (n = strlen(s)) + 8 <= MAXPATHLEN && /* "ed.hup" + '/' */ + (hup = (char *) malloc(n + 10)) != NULL) { + strcpy(hup, s); + if (hup[n - 1] != '/') + hup[n] = '/', hup[n+1] = '\0'; + strcat(hup, "ed.hup"); + write_file(hup, "w", 1, addr_last); + } + quit(2); +} + + +void +handle_int(int signo) +{ + if (!sigactive) + quit(1); + sigflags &= ~(1 << (signo - 1)); +#ifdef _POSIX_SOURCE + siglongjmp(env, -1); +#else + longjmp(env, -1); +#endif +} + + +int cols = 72; /* wrap column */ + +void +handle_winch(int signo) +{ + struct winsize ws; /* window size structure */ + + sigflags &= ~(1 << (signo - 1)); + if (ioctl(0, TIOCGWINSZ, (char *) &ws) >= 0) { + if (ws.ws_row > 2) rows = ws.ws_row - 2; + if (ws.ws_col > 8) cols = ws.ws_col - 8; + } +} + + +/* is_legal_filename: return a legal filename */ +int +is_legal_filename(char *s) +{ + if (red && (*s == '!' || !strcmp(s, "..") || strchr(s, '/'))) { + seterrmsg("shell access restricted"); + return 0; + } + return 1; +} diff --git a/bin/ed/re.c b/bin/ed/re.c new file mode 100644 index 0000000..bc4abc9 --- /dev/null +++ b/bin/ed/re.c @@ -0,0 +1,146 @@ +/* $NetBSD: re.c,v 1.21 2014/03/23 05:06:42 dholland Exp $ */ + +/* re.c: This file contains the regular expression interface routines for + the ed line editor. */ +/*- + * Copyright (c) 1993 Andrew Moore, Talke Studio. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char *rcsid = "@(#)re.c,v 1.6 1994/02/01 00:34:43 alm Exp"; +#else +__RCSID("$NetBSD: re.c,v 1.21 2014/03/23 05:06:42 dholland Exp $"); +#endif +#endif /* not lint */ + +#include <stdarg.h> +#include "ed.h" + + +char errmsg[MAXPATHLEN + 40] = ""; + +void +seterrmsg(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsnprintf(errmsg, sizeof(errmsg), fmt, ap); + va_end(ap); +} + +/* get_compiled_pattern: return pointer to compiled pattern from command + buffer */ +pattern_t * +get_compiled_pattern(void) +{ + static pattern_t *expr = NULL; + + char *exps; + char delimiter; + int n; + + if ((delimiter = *ibufp) == ' ') { + seterrmsg("invalid pattern delimiter"); + return NULL; + } else if (delimiter == '\n' || *++ibufp == '\n' || *ibufp == delimiter) { + if (!expr) seterrmsg("no previous pattern"); + return expr; + } else if ((exps = extract_pattern(delimiter)) == NULL) + return NULL; + /* buffer alloc'd && not reserved */ + if (expr && !patlock) + regfree(expr); + else if ((expr = (pattern_t *) malloc(sizeof(pattern_t))) == NULL) { + fprintf(stderr, "%s\n", strerror(errno)); + seterrmsg("out of memory"); + return NULL; + } + patlock = 0; + if ((n = regcomp(expr, exps, ere)) != 0) { + regerror(n, expr, errmsg, sizeof errmsg); + free(expr); + return expr = NULL; + } + return expr; +} + + +/* extract_pattern: copy a pattern string from the command buffer; return + pointer to the copy */ +char * +extract_pattern(int delimiter) +{ + static char *lhbuf = NULL; /* buffer */ + static int lhbufsz = 0; /* buffer size */ + + char *nd; + int len; + + for (nd = ibufp; *nd != delimiter && *nd != '\n'; nd++) + switch (*nd) { + default: + break; + case '[': + if ((nd = parse_char_class(nd + 1)) == NULL) { + seterrmsg("unbalanced brackets ([])"); + return NULL; + } + break; + case '\\': + if (*++nd == '\n') { + seterrmsg("trailing backslash (\\)"); + return NULL; + } + break; + } + len = nd - ibufp; + REALLOC(lhbuf, lhbufsz, len + 1, NULL); + memcpy(lhbuf, ibufp, len); + lhbuf[len] = '\0'; + ibufp = nd; + return (isbinary) ? NUL_TO_NEWLINE(lhbuf, len) : lhbuf; +} + + +/* parse_char_class: expand a POSIX character class */ +char * +parse_char_class(char *s) +{ + int c, d; + + if (*s == '^') + s++; + if (*s == ']') + s++; + for (; *s != ']' && *s != '\n'; s++) + if (*s == '[' && ((d = *(s+1)) == '.' || d == ':' || d == '=')) + for (s++, c = *++s; *s != ']' || c != d; s++) + if ((c = *s) == '\n') + return NULL; + return (*s == ']') ? s : NULL; +} diff --git a/bin/ed/sub.c b/bin/ed/sub.c new file mode 100644 index 0000000..933c6b9 --- /dev/null +++ b/bin/ed/sub.c @@ -0,0 +1,262 @@ +/* $NetBSD: sub.c,v 1.7 2014/03/23 05:06:42 dholland Exp $ */ + +/* sub.c: This file contains the substitution routines for the ed + line editor */ +/*- + * Copyright (c) 1993 Andrew Moore, Talke Studio. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char *rcsid = "@(#)sub.c,v 1.1 1994/02/01 00:34:44 alm Exp"; +#else +__RCSID("$NetBSD: sub.c,v 1.7 2014/03/23 05:06:42 dholland Exp $"); +#endif +#endif /* not lint */ + +#include "ed.h" + + +char *rhbuf; /* rhs substitution buffer */ +int rhbufsz; /* rhs substitution buffer size */ +int rhbufi; /* rhs substitution buffer index */ + +/* extract_subst_tail: extract substitution tail from the command buffer */ +int +extract_subst_tail(int *flagp, long *np) +{ + char delimiter; + + *flagp = *np = 0; + if ((delimiter = *ibufp) == '\n') { + rhbufi = 0; + *flagp = GPR; + return 0; + } else if (extract_subst_template() == NULL) + return ERR; + else if (*ibufp == '\n') { + *flagp = GPR; + return 0; + } else if (*ibufp == delimiter) + ibufp++; + if ('1' <= *ibufp && *ibufp <= '9') { + STRTOL(*np, ibufp); + return 0; + } else if (*ibufp == 'g') { + ibufp++; + *flagp = GSG; + return 0; + } + return 0; +} + + +/* extract_subst_template: return pointer to copy of substitution template + in the command buffer */ +char * +extract_subst_template(void) +{ + int n = 0; + int i = 0; + char c; + char delimiter = *ibufp++; + + if (*ibufp == '%' && *(ibufp + 1) == delimiter) { + ibufp++; + if (!rhbuf) { + seterrmsg("no previous substitution"); + } + return rhbuf; + } + while (*ibufp != delimiter) { + REALLOC(rhbuf, rhbufsz, i + 2, NULL); + if ((c = rhbuf[i++] = *ibufp++) == '\n' && *ibufp == '\0') { + i--, ibufp--; + break; + } else if (c != '\\') + ; + else if ((rhbuf[i++] = *ibufp++) != '\n') + ; + else if (!isglobal) { + while ((n = get_tty_line()) == 0 || + (n > 0 && ibuf[n - 1] != '\n')) + clearerr(stdin); + if (n < 0) + return NULL; + } + } + REALLOC(rhbuf, rhbufsz, i + 1, NULL); + rhbuf[rhbufi = i] = '\0'; + return rhbuf; +} + + +char *rbuf; /* substitute_matching_text buffer */ +int rbufsz; /* substitute_matching_text buffer size */ + +/* search_and_replace: for each line in a range, change text matching a pattern + according to a substitution template; return status */ +int +search_and_replace(pattern_t *pat, int gflag, int kth) +{ + undo_t *up; + char *txt; + char *eot; + long lc; + long xa = current_addr; + int nsubs = 0; + line_t *lp; + int len; + + current_addr = first_addr - 1; + for (lc = 0; lc <= second_addr - first_addr; lc++) { + lp = get_addressed_line_node(++current_addr); + if ((len = substitute_matching_text(pat, lp, gflag, kth)) < 0) + return ERR; + else if (len) { + up = NULL; + if (delete_lines(current_addr, current_addr) < 0) + return ERR; + txt = rbuf; + eot = rbuf + len; + SPL1(); + do { + if ((txt = put_sbuf_line(txt)) == NULL) { + SPL0(); + return ERR; + } else if (up) + up->t = get_addressed_line_node(current_addr); + else if ((up = push_undo_stack(UADD, + current_addr, current_addr)) == NULL) { + SPL0(); + return ERR; + } + } while (txt != eot); + SPL0(); + nsubs++; + xa = current_addr; + } + } + current_addr = xa; + if (nsubs == 0 && !(gflag & GLB)) { + seterrmsg("no match"); + return ERR; + } else if ((gflag & (GPR | GLS | GNP)) && + display_lines(current_addr, current_addr, gflag) < 0) + return ERR; + return 0; +} + + +/* substitute_matching_text: replace text matched by a pattern according to + a substitution template; return pointer to the modified text */ +int +substitute_matching_text(pattern_t *pat, line_t *lp, int gflag, int kth) +{ + int off = 0; + int changed = 0; + int matchno = 0; + int i = 0; + regmatch_t rm[SE_MAX]; + char *txt; + char *eot; + + if ((txt = get_sbuf_line(lp)) == NULL) + return ERR; + if (isbinary) + NUL_TO_NEWLINE(txt, lp->len); + eot = txt + lp->len; + if (!regexec(pat, txt, SE_MAX, rm, 0)) { + do { + if (!kth || kth == ++matchno) { + changed++; + i = rm[0].rm_so; + REALLOC(rbuf, rbufsz, off + i, ERR); + if (isbinary) + NEWLINE_TO_NUL(txt, rm[0].rm_eo); + memcpy(rbuf + off, txt, i); + off += i; + if ((off = apply_subst_template(txt, rm, off, + pat->re_nsub)) < 0) + return ERR; + } else { + i = rm[0].rm_eo; + REALLOC(rbuf, rbufsz, off + i, ERR); + if (isbinary) + NEWLINE_TO_NUL(txt, i); + memcpy(rbuf + off, txt, i); + off += i; + } + txt += rm[0].rm_eo; + } while (*txt && (!changed || ((gflag & GSG) && rm[0].rm_eo)) + && !regexec(pat, txt, SE_MAX, rm, REG_NOTBOL)); + i = eot - txt; + REALLOC(rbuf, rbufsz, off + i + 2, ERR); + if (i > 0 && !rm[0].rm_eo && (gflag & GSG)) { + seterrmsg("infinite substitution loop"); + return ERR; + } + if (isbinary) + NEWLINE_TO_NUL(txt, i); + memcpy(rbuf + off, txt, i); + memcpy(rbuf + off + i, "\n", 2); + } + return changed ? off + i + 1 : 0; +} + + +/* apply_subst_template: modify text according to a substitution template; + return offset to end of modified text */ +int +apply_subst_template(char *boln, regmatch_t *rm, int off, int re_nsub) +{ + int j = 0; + int k = 0; + int n; + char *sub = rhbuf; + + for (; sub - rhbuf < rhbufi; sub++) + if (*sub == '&') { + j = rm[0].rm_so; + k = rm[0].rm_eo; + REALLOC(rbuf, rbufsz, off + k - j, ERR); + while (j < k) + rbuf[off++] = boln[j++]; + } else if (*sub == '\\' && '1' <= *++sub && *sub <= '9' && + (n = *sub - '0') <= re_nsub) { + j = rm[n].rm_so; + k = rm[n].rm_eo; + REALLOC(rbuf, rbufsz, off + k - j, ERR); + while (j < k) + rbuf[off++] = boln[j++]; + } else { + REALLOC(rbuf, rbufsz, off + 1, ERR); + rbuf[off++] = *sub; + } + REALLOC(rbuf, rbufsz, off + 1, ERR); + rbuf[off] = '\0'; + return off; +} diff --git a/bin/ed/test/=.err b/bin/ed/test/=.err new file mode 100644 index 0000000..6a60559 --- /dev/null +++ b/bin/ed/test/=.err @@ -0,0 +1 @@ +1,$= diff --git a/bin/ed/test/Makefile b/bin/ed/test/Makefile new file mode 100644 index 0000000..fe631a8 --- /dev/null +++ b/bin/ed/test/Makefile @@ -0,0 +1,28 @@ +# $NetBSD: Makefile,v 1.12 2003/10/26 03:50:07 lukem Exp $ + +.include <bsd.own.mk> + +ED?= ../obj/ed + +all: check + @: + +check: build test + @if grep -h '\*\*\*' errs.o scripts.o; then :; else \ + echo "tests completed successfully."; \ + fi + +build: mkscripts.sh + @if [ -f errs.o ]; then :; else \ + echo "building test scripts for $(ED) ..."; \ + ${HOST_SH} ${.CURDIR}/mkscripts.sh $(ED); \ + fi + +test: build ckscripts.sh + @echo testing $(ED) ... + @${HOST_SH} ckscripts.sh $(ED) + +clean: + rm -f *.ed *.red *.[oz] *~ + +.include <bsd.prog.mk> diff --git a/bin/ed/test/README b/bin/ed/test/README new file mode 100644 index 0000000..73d7f2e --- /dev/null +++ b/bin/ed/test/README @@ -0,0 +1,32 @@ +$NetBSD: README,v 1.8 1995/03/21 09:05:18 cgd Exp $ + +The files in this directory with suffixes `.t', `.d', `.r' and `.err' are +used for testing ed. To run the tests, set the ED variable in the Makefile +for the path name of the program to be tested (e.g., /bin/ed), and type +`make'. The tests do not exhaustively verify POSIX compliance nor do +they verify correct 8-bit or long line support. + +The test file suffixes have the following meanings: +.t Template - a list of ed commands from which an ed script is + constructed +.d Data - read by an ed script +.r Result - the expected output after processing data via an ed + script. +.err Error - invalid ed commands that should generate an error + +The output of the tests is written to the two files err.o and scripts.o. +At the end of the tests, these files are grep'ed for error messages, +which look like: + *** The script u.ed exited abnormally *** +or: + *** Output u.o of script u.ed is incorrect *** + +The POSIX requirement that an address range not be used where at most +a single address is expected has been relaxed in this version of ed. +Therefore, the following scripts which test for compliance with this +POSIX rule exit abnormally: +=-err.ed +a1-err.ed +i1-err.ed +k1-err.ed +r1-err.ed diff --git a/bin/ed/test/TODO b/bin/ed/test/TODO new file mode 100644 index 0000000..c516d1e --- /dev/null +++ b/bin/ed/test/TODO @@ -0,0 +1,17 @@ +$NetBSD: TODO,v 1.3 1995/03/21 09:05:20 cgd Exp $ + +Some missing tests: +0) g/./s^@^@ - okay: NULs in commands +1) g/./s/^@/ - okay: NULs in patterns +2) a + hello^V^Jworld + . - okay: embedded newlines in insert mode +3) ed "" - error: invalid filename +4) red .. - error: restricted +5) red / - error: restricted +5) red !xx - error: restricted +6) ed -x - verify: 8-bit clean +7) ed - verify: long-line support +8) ed - verify: interactive/help mode +9) G/pat/ - verify: global interactive command +10) V/pat/ - verify: global interactive command diff --git a/bin/ed/test/a.d b/bin/ed/test/a.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/a.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/a.r b/bin/ed/test/a.r new file mode 100644 index 0000000..26257bd --- /dev/null +++ b/bin/ed/test/a.r @@ -0,0 +1,8 @@ +hello world +line 1 +hello world! +line 2 +line 3 +line 4 +line5 +hello world!! diff --git a/bin/ed/test/a.t b/bin/ed/test/a.t new file mode 100644 index 0000000..ac98c40 --- /dev/null +++ b/bin/ed/test/a.t @@ -0,0 +1,9 @@ +0a +hello world +. +2a +hello world! +. +$a +hello world!! +. diff --git a/bin/ed/test/a1.err b/bin/ed/test/a1.err new file mode 100644 index 0000000..e80815f --- /dev/null +++ b/bin/ed/test/a1.err @@ -0,0 +1,3 @@ +1,$a +hello world +. diff --git a/bin/ed/test/a2.err b/bin/ed/test/a2.err new file mode 100644 index 0000000..ec4b00b --- /dev/null +++ b/bin/ed/test/a2.err @@ -0,0 +1,3 @@ +aa +hello world +. diff --git a/bin/ed/test/addr.d b/bin/ed/test/addr.d new file mode 100644 index 0000000..8f7ba1b --- /dev/null +++ b/bin/ed/test/addr.d @@ -0,0 +1,9 @@ +line 1 +line 2 +line 3 +line 4 +line5 +1ine6 +line7 +line8 +line9 diff --git a/bin/ed/test/addr.r b/bin/ed/test/addr.r new file mode 100644 index 0000000..04caf17 --- /dev/null +++ b/bin/ed/test/addr.r @@ -0,0 +1,2 @@ +line 2 +line9 diff --git a/bin/ed/test/addr.t b/bin/ed/test/addr.t new file mode 100644 index 0000000..750b224 --- /dev/null +++ b/bin/ed/test/addr.t @@ -0,0 +1,5 @@ +1 d +1 1 d +1,2,d +1;+ + ,d +1,2;., + 2d diff --git a/bin/ed/test/addr1.err b/bin/ed/test/addr1.err new file mode 100644 index 0000000..29d6383 --- /dev/null +++ b/bin/ed/test/addr1.err @@ -0,0 +1 @@ +100 diff --git a/bin/ed/test/addr2.err b/bin/ed/test/addr2.err new file mode 100644 index 0000000..e96acb9 --- /dev/null +++ b/bin/ed/test/addr2.err @@ -0,0 +1 @@ +-100 diff --git a/bin/ed/test/ascii.d b/bin/ed/test/ascii.d Binary files differnew file mode 100644 index 0000000..c866266 --- /dev/null +++ b/bin/ed/test/ascii.d diff --git a/bin/ed/test/ascii.r b/bin/ed/test/ascii.r Binary files differnew file mode 100644 index 0000000..c866266 --- /dev/null +++ b/bin/ed/test/ascii.r diff --git a/bin/ed/test/ascii.t b/bin/ed/test/ascii.t new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/bin/ed/test/ascii.t diff --git a/bin/ed/test/bang1.d b/bin/ed/test/bang1.d new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/bin/ed/test/bang1.d diff --git a/bin/ed/test/bang1.err b/bin/ed/test/bang1.err new file mode 100644 index 0000000..630af90 --- /dev/null +++ b/bin/ed/test/bang1.err @@ -0,0 +1 @@ +.!date diff --git a/bin/ed/test/bang1.r b/bin/ed/test/bang1.r new file mode 100644 index 0000000..dcf02b2 --- /dev/null +++ b/bin/ed/test/bang1.r @@ -0,0 +1 @@ +okay diff --git a/bin/ed/test/bang1.t b/bin/ed/test/bang1.t new file mode 100644 index 0000000..d7b1fea --- /dev/null +++ b/bin/ed/test/bang1.t @@ -0,0 +1,5 @@ +!read one +hello, world +a +okay +. diff --git a/bin/ed/test/bang2.err b/bin/ed/test/bang2.err new file mode 100644 index 0000000..79d8956 --- /dev/null +++ b/bin/ed/test/bang2.err @@ -0,0 +1 @@ +!! diff --git a/bin/ed/test/c.d b/bin/ed/test/c.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/c.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/c.r b/bin/ed/test/c.r new file mode 100644 index 0000000..0fb3e4f --- /dev/null +++ b/bin/ed/test/c.r @@ -0,0 +1,4 @@ +at the top +between top/middle +in the middle +at the bottom diff --git a/bin/ed/test/c.t b/bin/ed/test/c.t new file mode 100644 index 0000000..ebdd536 --- /dev/null +++ b/bin/ed/test/c.t @@ -0,0 +1,12 @@ +1c +at the top +. +4c +in the middle +. +$c +at the bottom +. +2,3c +between top/middle +. diff --git a/bin/ed/test/c1.err b/bin/ed/test/c1.err new file mode 100644 index 0000000..658ec38 --- /dev/null +++ b/bin/ed/test/c1.err @@ -0,0 +1,3 @@ +cc +hello world +. diff --git a/bin/ed/test/c2.err b/bin/ed/test/c2.err new file mode 100644 index 0000000..24b3227 --- /dev/null +++ b/bin/ed/test/c2.err @@ -0,0 +1,3 @@ +0c +hello world +. diff --git a/bin/ed/test/ckscripts.sh b/bin/ed/test/ckscripts.sh new file mode 100755 index 0000000..86a19b1 --- /dev/null +++ b/bin/ed/test/ckscripts.sh @@ -0,0 +1,37 @@ +#!/bin/sh - +# $NetBSD: ckscripts.sh,v 1.9 1995/04/23 10:07:34 cgd Exp $ +# +# This script runs the .ed scripts generated by mkscripts.sh +# and compares their output against the .r files, which contain +# the correct output + +PATH="/bin:/usr/bin:/usr/local/bin/:." +ED=$1 +[ ! -x $ED ] && { echo "$ED: cannot execute"; exit 1; } + +# Run the *.red scripts first, since these don't generate output; +# they exit with non-zero status +for i in *.red; do + echo $i + if $i; then + echo "*** The script $i exited abnormally ***" + fi +done >errs.o 2>&1 + +# Run the remainding scripts; they exit with zero status +for i in *.ed; do +# base=`expr $i : '\([^.]*\)'` +# base=`echo $i | sed 's/\..*//'` + base=`$ED - \!"echo $i" <<-EOF + s/\..* + EOF` + if $base.ed; then + if cmp -s $base.o $base.r; then :; else + echo "*** Output $base.o of script $i is incorrect ***" + fi + else + echo "*** The script $i exited abnormally ***" + fi +done >scripts.o 2>&1 + +grep -h '\*\*\*' errs.o scripts.o diff --git a/bin/ed/test/d.d b/bin/ed/test/d.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/d.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/d.err b/bin/ed/test/d.err new file mode 100644 index 0000000..f03f694 --- /dev/null +++ b/bin/ed/test/d.err @@ -0,0 +1 @@ +dd diff --git a/bin/ed/test/d.r b/bin/ed/test/d.r new file mode 100644 index 0000000..b7e242c --- /dev/null +++ b/bin/ed/test/d.r @@ -0,0 +1 @@ +line 2 diff --git a/bin/ed/test/d.t b/bin/ed/test/d.t new file mode 100644 index 0000000..c7c473f --- /dev/null +++ b/bin/ed/test/d.t @@ -0,0 +1,3 @@ +1d +2;+1d +$d diff --git a/bin/ed/test/e1.d b/bin/ed/test/e1.d new file mode 100644 index 0000000..3b18e51 --- /dev/null +++ b/bin/ed/test/e1.d @@ -0,0 +1 @@ +hello world diff --git a/bin/ed/test/e1.err b/bin/ed/test/e1.err new file mode 100644 index 0000000..827cc29 --- /dev/null +++ b/bin/ed/test/e1.err @@ -0,0 +1 @@ +ee e1.err diff --git a/bin/ed/test/e1.r b/bin/ed/test/e1.r new file mode 100644 index 0000000..e656728 --- /dev/null +++ b/bin/ed/test/e1.r @@ -0,0 +1 @@ +E e1.t diff --git a/bin/ed/test/e1.t b/bin/ed/test/e1.t new file mode 100644 index 0000000..e656728 --- /dev/null +++ b/bin/ed/test/e1.t @@ -0,0 +1 @@ +E e1.t diff --git a/bin/ed/test/e2.d b/bin/ed/test/e2.d new file mode 100644 index 0000000..aa44630 --- /dev/null +++ b/bin/ed/test/e2.d @@ -0,0 +1 @@ +E !echo hello world- diff --git a/bin/ed/test/e2.err b/bin/ed/test/e2.err new file mode 100644 index 0000000..779a64b --- /dev/null +++ b/bin/ed/test/e2.err @@ -0,0 +1 @@ +.e e2.err diff --git a/bin/ed/test/e2.r b/bin/ed/test/e2.r new file mode 100644 index 0000000..59ebf11 --- /dev/null +++ b/bin/ed/test/e2.r @@ -0,0 +1 @@ +hello world- diff --git a/bin/ed/test/e2.t b/bin/ed/test/e2.t new file mode 100644 index 0000000..aa44630 --- /dev/null +++ b/bin/ed/test/e2.t @@ -0,0 +1 @@ +E !echo hello world- diff --git a/bin/ed/test/e3.d b/bin/ed/test/e3.d new file mode 100644 index 0000000..aa44630 --- /dev/null +++ b/bin/ed/test/e3.d @@ -0,0 +1 @@ +E !echo hello world- diff --git a/bin/ed/test/e3.err b/bin/ed/test/e3.err new file mode 100644 index 0000000..80a7fdc --- /dev/null +++ b/bin/ed/test/e3.err @@ -0,0 +1 @@ +ee.err diff --git a/bin/ed/test/e3.r b/bin/ed/test/e3.r new file mode 100644 index 0000000..aa44630 --- /dev/null +++ b/bin/ed/test/e3.r @@ -0,0 +1 @@ +E !echo hello world- diff --git a/bin/ed/test/e3.t b/bin/ed/test/e3.t new file mode 100644 index 0000000..1c50726 --- /dev/null +++ b/bin/ed/test/e3.t @@ -0,0 +1 @@ +E diff --git a/bin/ed/test/e4.d b/bin/ed/test/e4.d new file mode 100644 index 0000000..aa44630 --- /dev/null +++ b/bin/ed/test/e4.d @@ -0,0 +1 @@ +E !echo hello world- diff --git a/bin/ed/test/e4.r b/bin/ed/test/e4.r new file mode 100644 index 0000000..aa44630 --- /dev/null +++ b/bin/ed/test/e4.r @@ -0,0 +1 @@ +E !echo hello world- diff --git a/bin/ed/test/e4.t b/bin/ed/test/e4.t new file mode 100644 index 0000000..d905d9d --- /dev/null +++ b/bin/ed/test/e4.t @@ -0,0 +1 @@ +e diff --git a/bin/ed/test/f1.err b/bin/ed/test/f1.err new file mode 100644 index 0000000..e60975a --- /dev/null +++ b/bin/ed/test/f1.err @@ -0,0 +1 @@ +.f f1.err diff --git a/bin/ed/test/f2.err b/bin/ed/test/f2.err new file mode 100644 index 0000000..26d1c5e --- /dev/null +++ b/bin/ed/test/f2.err @@ -0,0 +1 @@ +ff1.err diff --git a/bin/ed/test/g1.d b/bin/ed/test/g1.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/g1.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/g1.err b/bin/ed/test/g1.err new file mode 100644 index 0000000..f95ea22 --- /dev/null +++ b/bin/ed/test/g1.err @@ -0,0 +1 @@ +g/./s //x/ diff --git a/bin/ed/test/g1.r b/bin/ed/test/g1.r new file mode 100644 index 0000000..578a44b --- /dev/null +++ b/bin/ed/test/g1.r @@ -0,0 +1,15 @@ +line5 +help! world +order +line 4 +help! world +order +line 3 +help! world +order +line 2 +help! world +order +line 1 +help! world +order diff --git a/bin/ed/test/g1.t b/bin/ed/test/g1.t new file mode 100644 index 0000000..2d0b54f --- /dev/null +++ b/bin/ed/test/g1.t @@ -0,0 +1,6 @@ +g/./m0 +g/./s/$/\ +hello world +g/hello /s/lo/p!/\ +a\ +order diff --git a/bin/ed/test/g2.d b/bin/ed/test/g2.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/g2.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/g2.err b/bin/ed/test/g2.err new file mode 100644 index 0000000..0ff6a5a --- /dev/null +++ b/bin/ed/test/g2.err @@ -0,0 +1 @@ +g//s/./x/ diff --git a/bin/ed/test/g2.r b/bin/ed/test/g2.r new file mode 100644 index 0000000..3b18e51 --- /dev/null +++ b/bin/ed/test/g2.r @@ -0,0 +1 @@ +hello world diff --git a/bin/ed/test/g2.t b/bin/ed/test/g2.t new file mode 100644 index 0000000..831ee83 --- /dev/null +++ b/bin/ed/test/g2.t @@ -0,0 +1,2 @@ +g/[2-4]/-1,+1c\ +hello world diff --git a/bin/ed/test/g3.d b/bin/ed/test/g3.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/g3.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/g3.err b/bin/ed/test/g3.err new file mode 100644 index 0000000..01058d8 --- /dev/null +++ b/bin/ed/test/g3.err @@ -0,0 +1 @@ +g diff --git a/bin/ed/test/g3.r b/bin/ed/test/g3.r new file mode 100644 index 0000000..cc6fbdd --- /dev/null +++ b/bin/ed/test/g3.r @@ -0,0 +1,5 @@ +linc 3 +xine 1 +xine 2 +xinc 4 +xinc5 diff --git a/bin/ed/test/g3.t b/bin/ed/test/g3.t new file mode 100644 index 0000000..2d052a6 --- /dev/null +++ b/bin/ed/test/g3.t @@ -0,0 +1,4 @@ +g/./s//x/\ +3m0 +g/./s/e/c/\ +2,3m1 diff --git a/bin/ed/test/g4.d b/bin/ed/test/g4.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/g4.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/g4.r b/bin/ed/test/g4.r new file mode 100644 index 0000000..350882d --- /dev/null +++ b/bin/ed/test/g4.r @@ -0,0 +1,7 @@ +hello +zine 1 +line 2 +line 3 +line 4 +line5 +world diff --git a/bin/ed/test/g4.t b/bin/ed/test/g4.t new file mode 100644 index 0000000..ec61816 --- /dev/null +++ b/bin/ed/test/g4.t @@ -0,0 +1,13 @@ +g/./s/./x/\ +u\ +s/./y/\ +u\ +s/./z/\ +u +u +0a +hello +. +$a +world +. diff --git a/bin/ed/test/g5.d b/bin/ed/test/g5.d new file mode 100644 index 0000000..a92d664 --- /dev/null +++ b/bin/ed/test/g5.d @@ -0,0 +1,3 @@ +line 1 +line 2 +line 3 diff --git a/bin/ed/test/g5.r b/bin/ed/test/g5.r new file mode 100644 index 0000000..15a2675 --- /dev/null +++ b/bin/ed/test/g5.r @@ -0,0 +1,9 @@ +line 1 +line 2 +line 3 +line 2 +line 3 +line 1 +line 3 +line 1 +line 2 diff --git a/bin/ed/test/g5.t b/bin/ed/test/g5.t new file mode 100644 index 0000000..e213481 --- /dev/null +++ b/bin/ed/test/g5.t @@ -0,0 +1,2 @@ +g/./1,3t$\ +1d diff --git a/bin/ed/test/h.err b/bin/ed/test/h.err new file mode 100644 index 0000000..a71e506 --- /dev/null +++ b/bin/ed/test/h.err @@ -0,0 +1 @@ +.h diff --git a/bin/ed/test/i.d b/bin/ed/test/i.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/i.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/i.r b/bin/ed/test/i.r new file mode 100644 index 0000000..5f27af0 --- /dev/null +++ b/bin/ed/test/i.r @@ -0,0 +1,8 @@ +hello world +hello world! +line 1 +line 2 +line 3 +line 4 +hello world!! +line5 diff --git a/bin/ed/test/i.t b/bin/ed/test/i.t new file mode 100644 index 0000000..d1d9805 --- /dev/null +++ b/bin/ed/test/i.t @@ -0,0 +1,9 @@ +1i +hello world +. +2i +hello world! +. +$i +hello world!! +. diff --git a/bin/ed/test/i1.err b/bin/ed/test/i1.err new file mode 100644 index 0000000..aaddede --- /dev/null +++ b/bin/ed/test/i1.err @@ -0,0 +1,3 @@ +1,$i +hello world +. diff --git a/bin/ed/test/i2.err b/bin/ed/test/i2.err new file mode 100644 index 0000000..b63f5ac --- /dev/null +++ b/bin/ed/test/i2.err @@ -0,0 +1,3 @@ +ii +hello world +. diff --git a/bin/ed/test/i3.err b/bin/ed/test/i3.err new file mode 100644 index 0000000..6d200c8 --- /dev/null +++ b/bin/ed/test/i3.err @@ -0,0 +1,3 @@ +0i +hello world +. diff --git a/bin/ed/test/j.d b/bin/ed/test/j.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/j.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/j.r b/bin/ed/test/j.r new file mode 100644 index 0000000..66f36a8 --- /dev/null +++ b/bin/ed/test/j.r @@ -0,0 +1,4 @@ +line 1 +line 2line 3 +line 4 +line5 diff --git a/bin/ed/test/j.t b/bin/ed/test/j.t new file mode 100644 index 0000000..9b5d28d --- /dev/null +++ b/bin/ed/test/j.t @@ -0,0 +1,2 @@ +1,1j +2,3j diff --git a/bin/ed/test/k.d b/bin/ed/test/k.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/k.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/k.r b/bin/ed/test/k.r new file mode 100644 index 0000000..eeb38db --- /dev/null +++ b/bin/ed/test/k.r @@ -0,0 +1,5 @@ +line 3 +hello world +line 4 +line5 +line 2 diff --git a/bin/ed/test/k.t b/bin/ed/test/k.t new file mode 100644 index 0000000..53d588d --- /dev/null +++ b/bin/ed/test/k.t @@ -0,0 +1,10 @@ +2ka +1d +'am$ +1ka +0a +hello world +. +'ad +u +'am0 diff --git a/bin/ed/test/k1.err b/bin/ed/test/k1.err new file mode 100644 index 0000000..eba1f3d --- /dev/null +++ b/bin/ed/test/k1.err @@ -0,0 +1 @@ +1,$ka diff --git a/bin/ed/test/k2.err b/bin/ed/test/k2.err new file mode 100644 index 0000000..b34a18d --- /dev/null +++ b/bin/ed/test/k2.err @@ -0,0 +1 @@ +kA diff --git a/bin/ed/test/k3.err b/bin/ed/test/k3.err new file mode 100644 index 0000000..70190c4 --- /dev/null +++ b/bin/ed/test/k3.err @@ -0,0 +1 @@ +0ka diff --git a/bin/ed/test/k4.err b/bin/ed/test/k4.err new file mode 100644 index 0000000..3457642 --- /dev/null +++ b/bin/ed/test/k4.err @@ -0,0 +1,6 @@ +a +hello +. +.ka +'ad +'ap diff --git a/bin/ed/test/l.d b/bin/ed/test/l.d new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/bin/ed/test/l.d diff --git a/bin/ed/test/l.r b/bin/ed/test/l.r new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/bin/ed/test/l.r diff --git a/bin/ed/test/l.t b/bin/ed/test/l.t new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/bin/ed/test/l.t diff --git a/bin/ed/test/m.d b/bin/ed/test/m.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/m.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/m.err b/bin/ed/test/m.err new file mode 100644 index 0000000..3aec4c3 --- /dev/null +++ b/bin/ed/test/m.err @@ -0,0 +1,4 @@ +a +hello world +. +1,$m1 diff --git a/bin/ed/test/m.r b/bin/ed/test/m.r new file mode 100644 index 0000000..186cf54 --- /dev/null +++ b/bin/ed/test/m.r @@ -0,0 +1,5 @@ +line5 +line 1 +line 2 +line 3 +line 4 diff --git a/bin/ed/test/m.t b/bin/ed/test/m.t new file mode 100644 index 0000000..c39c088 --- /dev/null +++ b/bin/ed/test/m.t @@ -0,0 +1,7 @@ +1,2m$ +1,2m$ +1,2m$ +$m0 +$m0 +2,3m1 +2,3m3 diff --git a/bin/ed/test/mkscripts.sh b/bin/ed/test/mkscripts.sh new file mode 100755 index 0000000..5e1a095 --- /dev/null +++ b/bin/ed/test/mkscripts.sh @@ -0,0 +1,75 @@ +#!/bin/sh - +# $NetBSD: mkscripts.sh,v 1.10 1995/04/23 10:07:36 cgd Exp $ +# +# This script generates ed test scripts (.ed) from .t files + +PATH="/bin:/usr/bin:/usr/local/bin/:." +ED=$1 +[ ! -x $ED ] && { echo "$ED: cannot execute"; exit 1; } + +for i in *.t; do +# base=${i%.*} +# base=`echo $i | sed 's/\..*//'` +# base=`expr $i : '\([^.]*\)'` +# ( +# echo "#!/bin/sh -" +# echo "$ED - <<\EOT" +# echo "r $base.d" +# cat $i +# echo "w $base.o" +# echo EOT +# ) >$base.ed +# chmod +x $base.ed +# The following is pretty ugly way of doing the above, and not appropriate +# use of ed but the point is that it can be done... + base=`$ED - \!"echo $i" <<-EOF + s/\..* + EOF` + $ED - <<-EOF + a + #!/bin/sh - + $ED - <<\EOT + H + r $base.d + w $base.o + EOT + . + -2r $i + w $base.ed + !chmod +x $base.ed + EOF +done + +for i in *.err; do +# base=${i%.*} +# base=`echo $i | sed 's/\..*//'` +# base=`expr $i : '\([^.]*\)'` +# ( +# echo "#!/bin/sh -" +# echo "$ED - <<\EOT" +# echo H +# echo "r $base.err" +# cat $i +# echo "w $base.o" +# echo EOT +# ) >$base-err.ed +# chmod +x $base-err.ed +# The following is pretty ugly way of doing the above, and not appropriate +# use of ed but the point is that it can be done... + base=`$ED - \!"echo $i" <<-EOF + s/\..* + EOF` + $ED - <<-EOF + a + #!/bin/sh - + $ED - <<\EOT + H + r $base.err + w $base.o + EOT + . + -2r $i + w ${base}.red + !chmod +x ${base}.red + EOF +done diff --git a/bin/ed/test/n.d b/bin/ed/test/n.d new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/bin/ed/test/n.d diff --git a/bin/ed/test/n.r b/bin/ed/test/n.r new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/bin/ed/test/n.r diff --git a/bin/ed/test/n.t b/bin/ed/test/n.t new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/bin/ed/test/n.t diff --git a/bin/ed/test/nl.err b/bin/ed/test/nl.err new file mode 100644 index 0000000..8949a85 --- /dev/null +++ b/bin/ed/test/nl.err @@ -0,0 +1 @@ +,1 diff --git a/bin/ed/test/nl1.d b/bin/ed/test/nl1.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/nl1.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/nl1.r b/bin/ed/test/nl1.r new file mode 100644 index 0000000..9d8854c --- /dev/null +++ b/bin/ed/test/nl1.r @@ -0,0 +1,8 @@ + + +hello world +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/nl1.t b/bin/ed/test/nl1.t new file mode 100644 index 0000000..ea192e9 --- /dev/null +++ b/bin/ed/test/nl1.t @@ -0,0 +1,8 @@ +1 + + +0a + + +hello world +. diff --git a/bin/ed/test/nl2.d b/bin/ed/test/nl2.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/nl2.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/nl2.r b/bin/ed/test/nl2.r new file mode 100644 index 0000000..fe99e41 --- /dev/null +++ b/bin/ed/test/nl2.r @@ -0,0 +1,6 @@ +line 1 +line 2 +line 3 +line 4 +line5 +hello world diff --git a/bin/ed/test/nl2.t b/bin/ed/test/nl2.t new file mode 100644 index 0000000..73fd27b --- /dev/null +++ b/bin/ed/test/nl2.t @@ -0,0 +1,4 @@ +a +hello world +. +0;/./ diff --git a/bin/ed/test/p.d b/bin/ed/test/p.d new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/bin/ed/test/p.d diff --git a/bin/ed/test/p.r b/bin/ed/test/p.r new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/bin/ed/test/p.r diff --git a/bin/ed/test/p.t b/bin/ed/test/p.t new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/bin/ed/test/p.t diff --git a/bin/ed/test/q.d b/bin/ed/test/q.d new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/bin/ed/test/q.d diff --git a/bin/ed/test/q.r b/bin/ed/test/q.r new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/bin/ed/test/q.r diff --git a/bin/ed/test/q.t b/bin/ed/test/q.t new file mode 100644 index 0000000..123a2c8 --- /dev/null +++ b/bin/ed/test/q.t @@ -0,0 +1,5 @@ +w q.o +a +hello +. +q diff --git a/bin/ed/test/q1.err b/bin/ed/test/q1.err new file mode 100644 index 0000000..0a7e178 --- /dev/null +++ b/bin/ed/test/q1.err @@ -0,0 +1 @@ +.q diff --git a/bin/ed/test/r1.d b/bin/ed/test/r1.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/r1.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/r1.err b/bin/ed/test/r1.err new file mode 100644 index 0000000..269aa7c --- /dev/null +++ b/bin/ed/test/r1.err @@ -0,0 +1 @@ +1,$r r1.err diff --git a/bin/ed/test/r1.r b/bin/ed/test/r1.r new file mode 100644 index 0000000..a3ff506 --- /dev/null +++ b/bin/ed/test/r1.r @@ -0,0 +1,7 @@ +line 1 +hello world +line 2 +line 3 +line 4 +line5 +hello world diff --git a/bin/ed/test/r1.t b/bin/ed/test/r1.t new file mode 100644 index 0000000..d787a92 --- /dev/null +++ b/bin/ed/test/r1.t @@ -0,0 +1,3 @@ +1;r !echo hello world +1 +r !echo hello world diff --git a/bin/ed/test/r2.d b/bin/ed/test/r2.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/r2.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/r2.err b/bin/ed/test/r2.err new file mode 100644 index 0000000..1c44fa3 --- /dev/null +++ b/bin/ed/test/r2.err @@ -0,0 +1 @@ +r a-good-book diff --git a/bin/ed/test/r2.r b/bin/ed/test/r2.r new file mode 100644 index 0000000..ac152ba --- /dev/null +++ b/bin/ed/test/r2.r @@ -0,0 +1,10 @@ +line 1 +line 2 +line 3 +line 4 +line5 +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/r2.t b/bin/ed/test/r2.t new file mode 100644 index 0000000..4286f42 --- /dev/null +++ b/bin/ed/test/r2.t @@ -0,0 +1 @@ +r diff --git a/bin/ed/test/r3.d b/bin/ed/test/r3.d new file mode 100644 index 0000000..593eec6 --- /dev/null +++ b/bin/ed/test/r3.d @@ -0,0 +1 @@ +r r3.t diff --git a/bin/ed/test/r3.r b/bin/ed/test/r3.r new file mode 100644 index 0000000..86d5f90 --- /dev/null +++ b/bin/ed/test/r3.r @@ -0,0 +1,2 @@ +r r3.t +r r3.t diff --git a/bin/ed/test/r3.t b/bin/ed/test/r3.t new file mode 100644 index 0000000..593eec6 --- /dev/null +++ b/bin/ed/test/r3.t @@ -0,0 +1 @@ +r r3.t diff --git a/bin/ed/test/s1.d b/bin/ed/test/s1.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/s1.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/s1.err b/bin/ed/test/s1.err new file mode 100644 index 0000000..d7ca0cf --- /dev/null +++ b/bin/ed/test/s1.err @@ -0,0 +1 @@ +s . x diff --git a/bin/ed/test/s1.r b/bin/ed/test/s1.r new file mode 100644 index 0000000..4eb0980 --- /dev/null +++ b/bin/ed/test/s1.r @@ -0,0 +1,5 @@ +liene 1 +(liene) (2) +(liene) (3) +liene (4) +(()liene5) diff --git a/bin/ed/test/s1.t b/bin/ed/test/s1.t new file mode 100644 index 0000000..b0028bb --- /dev/null +++ b/bin/ed/test/s1.t @@ -0,0 +1,6 @@ +s/\([^ ][^ ]*\)/(\1)/g +2s +/3/s +/\(4\)/sr +/\(.\)/srg +%s/i/&e/ diff --git a/bin/ed/test/s10.err b/bin/ed/test/s10.err new file mode 100644 index 0000000..0d8d83d --- /dev/null +++ b/bin/ed/test/s10.err @@ -0,0 +1,4 @@ +a +hello +. +s/[h[.]/x/ diff --git a/bin/ed/test/s2.d b/bin/ed/test/s2.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/s2.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/s2.err b/bin/ed/test/s2.err new file mode 100644 index 0000000..b5c851d --- /dev/null +++ b/bin/ed/test/s2.err @@ -0,0 +1,4 @@ +a +a +. +s/x*/a/g diff --git a/bin/ed/test/s2.r b/bin/ed/test/s2.r new file mode 100644 index 0000000..ca305c8 --- /dev/null +++ b/bin/ed/test/s2.r @@ -0,0 +1,5 @@ +li(n)e 1 +i(n)e 200 +li(n)e 3 +li(n)e 4 +li(n)e500 diff --git a/bin/ed/test/s2.t b/bin/ed/test/s2.t new file mode 100644 index 0000000..f365849 --- /dev/null +++ b/bin/ed/test/s2.t @@ -0,0 +1,4 @@ +,s/./(&)/3 +s/$/00 +2s//%/g +s/^l diff --git a/bin/ed/test/s3.d b/bin/ed/test/s3.d new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/bin/ed/test/s3.d diff --git a/bin/ed/test/s3.err b/bin/ed/test/s3.err new file mode 100644 index 0000000..d68c7d0 --- /dev/null +++ b/bin/ed/test/s3.err @@ -0,0 +1 @@ +s/[xyx/a/ diff --git a/bin/ed/test/s3.r b/bin/ed/test/s3.r new file mode 100644 index 0000000..d6cada2 --- /dev/null +++ b/bin/ed/test/s3.r @@ -0,0 +1 @@ +hello world diff --git a/bin/ed/test/s3.t b/bin/ed/test/s3.t new file mode 100644 index 0000000..fbf8803 --- /dev/null +++ b/bin/ed/test/s3.t @@ -0,0 +1,6 @@ +a +hello/[]world +. +s/[/]/ / +s/[[:digit:][]/ / +s/[]]/ / diff --git a/bin/ed/test/s4.err b/bin/ed/test/s4.err new file mode 100644 index 0000000..35b609f --- /dev/null +++ b/bin/ed/test/s4.err @@ -0,0 +1 @@ +s/\a\b\c/xyz/ diff --git a/bin/ed/test/s5.err b/bin/ed/test/s5.err new file mode 100644 index 0000000..89104c5 --- /dev/null +++ b/bin/ed/test/s5.err @@ -0,0 +1 @@ +s//xyz/ diff --git a/bin/ed/test/s6.err b/bin/ed/test/s6.err new file mode 100644 index 0000000..b478595 --- /dev/null +++ b/bin/ed/test/s6.err @@ -0,0 +1 @@ +s diff --git a/bin/ed/test/s7.err b/bin/ed/test/s7.err new file mode 100644 index 0000000..30ba4fd --- /dev/null +++ b/bin/ed/test/s7.err @@ -0,0 +1,5 @@ +a +hello world +. +/./ +sr diff --git a/bin/ed/test/s8.err b/bin/ed/test/s8.err new file mode 100644 index 0000000..5665767 --- /dev/null +++ b/bin/ed/test/s8.err @@ -0,0 +1,4 @@ +a +hello +. +s/[h[=]/x/ diff --git a/bin/ed/test/s9.err b/bin/ed/test/s9.err new file mode 100644 index 0000000..1ff16dd --- /dev/null +++ b/bin/ed/test/s9.err @@ -0,0 +1,4 @@ +a +hello +. +s/[h[:]/x/ diff --git a/bin/ed/test/t.d b/bin/ed/test/t.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/t.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/t.r b/bin/ed/test/t.r new file mode 100644 index 0000000..2b28547 --- /dev/null +++ b/bin/ed/test/t.r @@ -0,0 +1,16 @@ +line 1 +line 1 +line 1 +line 2 +line 2 +line 3 +line 4 +line5 +line 1 +line 1 +line 1 +line 2 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/t1.d b/bin/ed/test/t1.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/t1.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/t1.err b/bin/ed/test/t1.err new file mode 100644 index 0000000..c49c556 --- /dev/null +++ b/bin/ed/test/t1.err @@ -0,0 +1 @@ +tt diff --git a/bin/ed/test/t1.r b/bin/ed/test/t1.r new file mode 100644 index 0000000..2b28547 --- /dev/null +++ b/bin/ed/test/t1.r @@ -0,0 +1,16 @@ +line 1 +line 1 +line 1 +line 2 +line 2 +line 3 +line 4 +line5 +line 1 +line 1 +line 1 +line 2 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/t1.t b/bin/ed/test/t1.t new file mode 100644 index 0000000..6b66163 --- /dev/null +++ b/bin/ed/test/t1.t @@ -0,0 +1,3 @@ +1t0 +2,3t2 +,t$ diff --git a/bin/ed/test/t2.d b/bin/ed/test/t2.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/t2.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/t2.err b/bin/ed/test/t2.err new file mode 100644 index 0000000..c202051 --- /dev/null +++ b/bin/ed/test/t2.err @@ -0,0 +1 @@ +t0;-1 diff --git a/bin/ed/test/t2.r b/bin/ed/test/t2.r new file mode 100644 index 0000000..0c75ff5 --- /dev/null +++ b/bin/ed/test/t2.r @@ -0,0 +1,6 @@ +line 1 +line5 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/t2.t b/bin/ed/test/t2.t new file mode 100644 index 0000000..5175abd --- /dev/null +++ b/bin/ed/test/t2.t @@ -0,0 +1 @@ +t0;/./ diff --git a/bin/ed/test/u.d b/bin/ed/test/u.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/u.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/u.err b/bin/ed/test/u.err new file mode 100644 index 0000000..caa1ba1 --- /dev/null +++ b/bin/ed/test/u.err @@ -0,0 +1 @@ +.u diff --git a/bin/ed/test/u.r b/bin/ed/test/u.r new file mode 100644 index 0000000..ad558d8 --- /dev/null +++ b/bin/ed/test/u.r @@ -0,0 +1,9 @@ +line 1 +hello +hello world!! +line 2 +line 3 +line 4 +line5 +hello +hello world!! diff --git a/bin/ed/test/u.t b/bin/ed/test/u.t new file mode 100644 index 0000000..131cb6e --- /dev/null +++ b/bin/ed/test/u.t @@ -0,0 +1,31 @@ +1;r u.t +u +a +hello +world +. +g/./s//x/\ +a\ +hello\ +world +u +u +u +a +hello world! +. +u +1,$d +u +2,3d +u +c +hello world!! +. +u +u +-1;.,+1j +u +u +u +.,+1t$ diff --git a/bin/ed/test/v.d b/bin/ed/test/v.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/v.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/v.r b/bin/ed/test/v.r new file mode 100644 index 0000000..714db63 --- /dev/null +++ b/bin/ed/test/v.r @@ -0,0 +1,11 @@ +line5 +order +hello world +line 1 +order +line 2 +order +line 3 +order +line 4 +order diff --git a/bin/ed/test/v.t b/bin/ed/test/v.t new file mode 100644 index 0000000..608a77f --- /dev/null +++ b/bin/ed/test/v.t @@ -0,0 +1,6 @@ +v/[ ]/m0 +v/[ ]/s/$/\ +hello world +v/hello /s/lo/p!/\ +a\ +order diff --git a/bin/ed/test/w.d b/bin/ed/test/w.d new file mode 100644 index 0000000..92f337e --- /dev/null +++ b/bin/ed/test/w.d @@ -0,0 +1,5 @@ +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/w.r b/bin/ed/test/w.r new file mode 100644 index 0000000..ac152ba --- /dev/null +++ b/bin/ed/test/w.r @@ -0,0 +1,10 @@ +line 1 +line 2 +line 3 +line 4 +line5 +line 1 +line 2 +line 3 +line 4 +line5 diff --git a/bin/ed/test/w.t b/bin/ed/test/w.t new file mode 100644 index 0000000..c2e18bd --- /dev/null +++ b/bin/ed/test/w.t @@ -0,0 +1,2 @@ +w !cat >\!.z +r \!.z diff --git a/bin/ed/test/w1.err b/bin/ed/test/w1.err new file mode 100644 index 0000000..e2c8a60 --- /dev/null +++ b/bin/ed/test/w1.err @@ -0,0 +1 @@ +w /to/some/far-away/place diff --git a/bin/ed/test/w2.err b/bin/ed/test/w2.err new file mode 100644 index 0000000..9daf89c --- /dev/null +++ b/bin/ed/test/w2.err @@ -0,0 +1 @@ +ww.o diff --git a/bin/ed/test/w3.err b/bin/ed/test/w3.err new file mode 100644 index 0000000..39bbf4c --- /dev/null +++ b/bin/ed/test/w3.err @@ -0,0 +1 @@ +wqp w.o diff --git a/bin/ed/test/x.err b/bin/ed/test/x.err new file mode 100644 index 0000000..0953f01 --- /dev/null +++ b/bin/ed/test/x.err @@ -0,0 +1 @@ +.x diff --git a/bin/ed/test/z.err b/bin/ed/test/z.err new file mode 100644 index 0000000..6a51a2d --- /dev/null +++ b/bin/ed/test/z.err @@ -0,0 +1,2 @@ +z +z diff --git a/bin/ed/undo.c b/bin/ed/undo.c new file mode 100644 index 0000000..b17eac9 --- /dev/null +++ b/bin/ed/undo.c @@ -0,0 +1,150 @@ +/* $NetBSD: undo.c,v 1.7 2019/01/04 19:13:58 maya Exp $ */ + +/* undo.c: This file contains the undo routines for the ed line editor */ +/*- + * Copyright (c) 1993 Andrew Moore, Talke Studio. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char *rcsid = "@(#)undo.c,v 1.1 1994/02/01 00:34:44 alm Exp"; +#else +__RCSID("$NetBSD: undo.c,v 1.7 2019/01/04 19:13:58 maya Exp $"); +#endif +#endif /* not lint */ + +#include "ed.h" + + +#define USIZE 100 /* undo stack size */ +undo_t *ustack = NULL; /* undo stack */ +long usize = 0; /* stack size variable */ +long u_p = 0; /* undo stack pointer */ + +/* push_undo_stack: return pointer to initialized undo node */ +undo_t * +push_undo_stack(int type, long from, long to) +{ + undo_t *t; + + t = ustack; + if (u_p < usize || + (t = (undo_t *) realloc(ustack, (usize += USIZE) * sizeof(undo_t))) != NULL) { + ustack = t; + ustack[u_p].type = type; + ustack[u_p].t = get_addressed_line_node(to); + ustack[u_p].h = get_addressed_line_node(from); + return ustack + u_p++; + } + /* out of memory - release undo stack */ + fprintf(stderr, "%s\n", strerror(errno)); + seterrmsg("out of memory"); + clear_undo_stack(); + free(ustack); + ustack = NULL; + usize = 0; + return NULL; +} + + +/* USWAP: swap undo nodes */ +#define USWAP(x,y) { \ + undo_t utmp; \ + utmp = x, x = y, y = utmp; \ +} + + +long u_current_addr = -1; /* if >= 0, undo enabled */ +long u_addr_last = -1; /* if >= 0, undo enabled */ + +/* pop_undo_stack: undo last change to the editor buffer */ +int +pop_undo_stack(void) +{ + long n; + long o_current_addr = current_addr; + long o_addr_last = addr_last; + + if (u_current_addr == -1 || u_addr_last == -1) { + seterrmsg("nothing to undo"); + return ERR; + } else if (u_p) + modified = 1; + get_addressed_line_node(0); /* this get_addressed_line_node last! */ + SPL1(); + for (n = u_p; n-- > 0;) { + switch(ustack[n].type) { + case UADD: + REQUE(ustack[n].h->q_back, ustack[n].t->q_forw); + break; + case UDEL: + REQUE(ustack[n].h->q_back, ustack[n].h); + REQUE(ustack[n].t, ustack[n].t->q_forw); + break; + case UMOV: + case VMOV: + REQUE(ustack[n - 1].h, ustack[n].h->q_forw); + REQUE(ustack[n].t->q_back, ustack[n - 1].t); + REQUE(ustack[n].h, ustack[n].t); + n--; + break; + default: + /*NOTREACHED*/ + ; + } + ustack[n].type ^= 1; + } + /* reverse undo stack order */ + for (n = u_p; n-- > (u_p + 1)/ 2;) + USWAP(ustack[n], ustack[u_p - 1 - n]); + if (isglobal) + clear_active_list(); + current_addr = u_current_addr, u_current_addr = o_current_addr; + addr_last = u_addr_last, u_addr_last = o_addr_last; + SPL0(); + return 0; +} + + +/* clear_undo_stack: clear the undo stack */ +void +clear_undo_stack(void) +{ + line_t *lp, *ep, *tl; + + while (u_p--) + if (ustack[u_p].type == UDEL) { + ep = ustack[u_p].t->q_forw; + for (lp = ustack[u_p].h; lp != ep; lp = tl) { + unmark_line_node(lp); + tl = lp->q_forw; + free(lp); + } + } + u_p = 0; + u_current_addr = current_addr; + u_addr_last = addr_last; +} diff --git a/bin/expr/expr.1 b/bin/expr/expr.1 new file mode 100644 index 0000000..74d0469 --- /dev/null +++ b/bin/expr/expr.1 @@ -0,0 +1,280 @@ +.\" $NetBSD: expr.1,v 1.37 2017/07/03 21:33:23 wiz Exp $ +.\" +.\" Copyright (c) 2000,2003 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by J.T. Conklin <jtc@NetBSD.org> and Jaromir Dolecek <jdolecek@NetBSD.org>. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. +.\" +.Dd August 23, 2016 +.Dt EXPR 1 +.Os +.Sh NAME +.Nm expr +.Nd evaluate expression +.Sh SYNOPSIS +.Nm +.Ar expression +.Sh DESCRIPTION +The +.Nm +utility evaluates +.Ar expression +and writes the result on standard output. +.Pp +All operators are separate arguments to the +.Nm +utility. +Characters special to the command interpreter must be escaped. +.Pp +Operators are listed below in order of increasing precedence. +Operators with equal precedence are grouped within { } symbols. +.Bl -tag -width indent +.It Ar expr1 Li \&| Ar expr2 +Returns the evaluation of +.Ar expr1 +if it is neither an empty string nor zero; +otherwise, returns the evaluation of +.Ar expr2 . +.It Ar expr1 Li & Ar expr2 +Returns the evaluation of +.Ar expr1 +if neither expression evaluates to an empty string or zero; +otherwise, returns zero. +.It Ar expr1 Li "{=, >, \*[Ge], <, \*[Le], !=}" Ar expr2 +Returns the results of integer comparison if both arguments are integers; +otherwise, returns the results of string comparison using the locale-specific +collation sequence. +The result of each comparison is 1 if the specified relation is true, +or 0 if the relation is false. +.It Ar expr1 Li "{+, -}" Ar expr2 +Returns the results of addition or subtraction of integer-valued arguments. +.It Ar expr1 Li "{*, /, %}" Ar expr2 +Returns the results of multiplication, integer division, or remainder of integer-valued arguments. +.It Ar expr1 Li \&: Ar expr2 +The +.Dq \&: +operator matches +.Ar expr1 +against +.Ar expr2 , +which must be a regular expression. +The regular expression is anchored +to the beginning of the string with an implicit +.Dq ^ . +.Pp +If the match succeeds and the pattern contains at least one regular +expression subexpression +.Dq "\e(...\e)" , +the string corresponding to +.Dq "\e1" +is returned; +otherwise the matching operator returns the number of characters matched. +If the match fails and the pattern contains a regular expression subexpression +the null string is returned; +otherwise 0. +.It "( " Ar expr No " )" +Parentheses are used for grouping in the usual manner. +.El +.Pp +Additionally, the following keywords are recognized: +.Bl -tag -width indent +.It length Ar expr +Returns the length of the specified string in bytes. +.El +.Pp +Operator precedence (from highest to lowest): +.Bl -enum -compact -offset indent +.It +parentheses +.It +length +.It +.Dq \&: +.It +.Dq "*" , +.Dq "/" , +and +.Dq "%" +.It +.Dq "+" +and +.Dq "-" +.It +compare operators +.It +.Dq & +.It +.Dq \&| +.El +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Bl -tag -width Ds -compact +.It 0 +the expression is neither an empty string nor 0. +.It 1 +the expression is an empty string or 0. +.It 2 +the expression is invalid. +.It >2 +an error occurred (such as memory allocation failure). +.El +.Sh EXAMPLES +.Bl -enum +.It +The following example adds one to variable +.Dq a : +.Dl a=`expr $a + 1` +.It +The following example returns zero, due to subtraction having higher precedence +than the +.Dq & +operator: +.Dl expr 1 '&' 1 - 1 +.It +The following example returns the filename portion of a pathname stored +in variable +.Dq a : +.Dl expr "/$a" Li : '.*/\e(.*\e)' +.It +The following example returns the number of characters in variable +.Dq a : +.Dl expr $a Li : '.*' +.El +.Sh COMPATIBILITY +This implementation of +.Nm +internally uses 64 bit representation of integers and checks for +over- and underflows. +It also treats +.Dq / +(the division mark) and option +.Dq -- +correctly depending upon context. +.Pp +.Nm +on other systems (including +.Nx +up to and including +.Nx 1.5 ) +might not be so graceful. +Arithmetic results might be arbitrarily +limited on such systems, most commonly to 32 bit quantities. +This means such +.Nm +can only process values between -2147483648 and +2147483647. +.Pp +On other systems, +.Nm +might also not work correctly for regular expressions where +either side contains +.Dq / +(a single forward slash), like this: +.Bd -literal -offset indent +expr / : '.*/\e(.*\e)' +.Ed +.Pp +If this is the case, you might use +.Dq // +(a double forward slash) +to avoid confusion with the division operator: +.Bd -literal -offset indent +expr "//$a" : '.*/\e(.*\e)' +.Ed +.Pp +According to +.St -p1003.2 , +.Nm +has to recognize special option +.Dq -- , +treat it as a delimiter to mark the end of command +line options, and ignore it. +Some +.Nm +implementations do not recognize it at all; others +might ignore it even in cases where doing so results in syntax +error. +There should be same result for both following examples, +but it might not always be: +.Bl -enum -compact -offset indent +.It +expr -- : . +.It +expr -- -- : . +.El +Although +.Nx +.Nm +handles both cases correctly, you should not depend on this behavior +for portability reasons and avoid passing a bare +.Dq -- +as the first +argument. +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2 . +The +.Ar length +keyword is an extension for compatibility with GNU +.Nm . +.Sh HISTORY +An +.Nm +utility first appeared in the Programmer's Workbench (PWB/UNIX). +A public domain version of +.Nm +written by +.An Pace Willisson +.Aq pace@blitz.com +appeared in +.Bx 386 0.1 . +.Sh AUTHORS +Initial implementation by +.An Pace Willisson Aq Mt pace@blitz.com +was largely rewritten by +.An -nosplit +.An J.T. Conklin Aq Mt jtc@NetBSD.org . +It was rewritten again for +.Nx 1.6 +by +.An -nosplit +.An Jaromir Dolecek Aq Mt jdolecek@NetBSD.org . +.Sh NOTES +The empty string +.Do Dc +cannot be matched with the intuitive: +.Bd -literal -offset indent +expr '' : '$' +.Ed +.Pp +The reason is that the returned number of matched characters (zero) +is indistinguishable from a failed match, so this returns failure. +To match the empty string, use something like: +.Bd -literal -offset indent +expr x'' : 'x$' +.Ed diff --git a/bin/expr/expr.y b/bin/expr/expr.y new file mode 100644 index 0000000..2bc4ad1 --- /dev/null +++ b/bin/expr/expr.y @@ -0,0 +1,467 @@ +/* $NetBSD: expr.y,v 1.45 2018/06/27 17:23:36 kamil Exp $ */ + +/*_ + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Jaromir Dolecek <jdolecek@NetBSD.org> and J.T. Conklin <jtc@NetBSD.org>. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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> +#ifndef lint +__RCSID("$NetBSD: expr.y,v 1.45 2018/06/27 17:23:36 kamil Exp $"); +#endif /* not lint */ + +#include <sys/types.h> + +#include <err.h> +#include <errno.h> +#include <limits.h> +#include <locale.h> +#include <regex.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static const char * const *av; + +static void yyerror(const char *, ...) __dead; +static int yylex(void); +static int is_zero_or_null(const char *); +static int is_integer(const char *); +static int64_t perform_arith_op(const char *, const char *, const char *); + +#define YYSTYPE const char * + +%} +%token STRING +%left SPEC_OR +%left SPEC_AND +%left COMPARE +%left ADD_SUB_OPERATOR +%left MUL_DIV_MOD_OPERATOR +%left SPEC_REG +%left LENGTH +%left LEFT_PARENT RIGHT_PARENT + +%% + +exp: expr = { + (void) printf("%s\n", $1); + return (is_zero_or_null($1)); + } + ; + +expr: item { $$ = $1; } + | expr SPEC_OR expr = { + /* + * Return evaluation of first expression if it is neither + * an empty string nor zero; otherwise, returns the evaluation + * of second expression. + */ + if (!is_zero_or_null($1)) + $$ = $1; + else + $$ = $3; + } + | expr SPEC_AND expr = { + /* + * Returns the evaluation of first expr if neither expression + * evaluates to an empty string or zero; otherwise, returns + * zero. + */ + if (!is_zero_or_null($1) && !is_zero_or_null($3)) + $$ = $1; + else + $$ = "0"; + } + | expr SPEC_REG expr = { + /* + * The ``:'' operator matches first expr against the second, + * which must be a regular expression. + */ + regex_t rp; + regmatch_t rm[2]; + int eval; + + /* compile regular expression */ + if ((eval = regcomp(&rp, $3, REG_BASIC)) != 0) { + char errbuf[256]; + (void)regerror(eval, &rp, errbuf, sizeof(errbuf)); + yyerror("%s", errbuf); + /* NOT REACHED */ + } + + /* compare string against pattern -- remember that patterns + are anchored to the beginning of the line */ + if (regexec(&rp, $1, 2, rm, 0) == 0 && rm[0].rm_so == 0) { + char *val; + if (rm[1].rm_so >= 0) { + (void) asprintf(&val, "%.*s", + (int) (rm[1].rm_eo - rm[1].rm_so), + $1 + rm[1].rm_so); + } else { + (void) asprintf(&val, "%d", + (int)(rm[0].rm_eo - rm[0].rm_so)); + } + if (val == NULL) + err(1, NULL); + $$ = val; + } else { + if (rp.re_nsub == 0) { + $$ = "0"; + } else { + $$ = ""; + } + } + + } + | expr ADD_SUB_OPERATOR expr = { + /* Returns the results of addition, subtraction */ + char *val; + int64_t res; + + res = perform_arith_op($1, $2, $3); + (void) asprintf(&val, "%lld", (long long int) res); + if (val == NULL) + err(1, NULL); + $$ = val; + } + + | expr MUL_DIV_MOD_OPERATOR expr = { + /* + * Returns the results of multiply, divide or remainder of + * numeric-valued arguments. + */ + char *val; + int64_t res; + + res = perform_arith_op($1, $2, $3); + (void) asprintf(&val, "%lld", (long long int) res); + if (val == NULL) + err(1, NULL); + $$ = val; + + } + | expr COMPARE expr = { + /* + * Returns the results of integer comparison if both arguments + * are integers; otherwise, returns the results of string + * comparison using the locale-specific collation sequence. + * The result of each comparison is 1 if the specified relation + * is true, or 0 if the relation is false. + */ + + int64_t l, r; + int res; + + res = 0; + + /* + * Slight hack to avoid differences in the compare code + * between string and numeric compare. + */ + if (is_integer($1) && is_integer($3)) { + /* numeric comparison */ + l = strtoll($1, NULL, 10); + r = strtoll($3, NULL, 10); + } else { + /* string comparison */ + l = strcoll($1, $3); + r = 0; + } + + switch($2[0]) { + case '=': /* equal */ + res = (l == r); + break; + case '>': /* greater or greater-equal */ + if ($2[1] == '=') + res = (l >= r); + else + res = (l > r); + break; + case '<': /* lower or lower-equal */ + if ($2[1] == '=') + res = (l <= r); + else + res = (l < r); + break; + case '!': /* not equal */ + /* the check if this is != was done in yylex() */ + res = (l != r); + } + + $$ = (res) ? "1" : "0"; + + } + | LEFT_PARENT expr RIGHT_PARENT { $$ = $2; } + | LENGTH expr { + /* + * Return length of 'expr' in bytes. + */ + char *ln; + + asprintf(&ln, "%ld", (long) strlen($2)); + if (ln == NULL) + err(1, NULL); + $$ = ln; + } + ; + +item: STRING + | ADD_SUB_OPERATOR + | MUL_DIV_MOD_OPERATOR + | COMPARE + | SPEC_OR + | SPEC_AND + | SPEC_REG + | LENGTH + ; +%% + +/* + * Returns 1 if the string is empty or contains only numeric zero. + */ +static int +is_zero_or_null(const char *str) +{ + char *endptr; + + return str[0] == '\0' + || ( strtoll(str, &endptr, 10) == 0LL + && endptr[0] == '\0'); +} + +/* + * Returns 1 if the string is an integer. + */ +static int +is_integer(const char *str) +{ + char *endptr; + + (void) strtoll(str, &endptr, 10); + /* note we treat empty string as valid number */ + return (endptr[0] == '\0'); +} + +static int64_t +perform_arith_op(const char *left, const char *op, const char *right) +{ + int64_t res, l, r; + + res = 0; + + if (!is_integer(left)) { + yyerror("non-integer argument '%s'", left); + /* NOTREACHED */ + } + if (!is_integer(right)) { + yyerror("non-integer argument '%s'", right); + /* NOTREACHED */ + } + + errno = 0; + l = strtoll(left, NULL, 10); + if (errno == ERANGE) { + yyerror("value '%s' is %s is %lld", left, + (l > 0) ? "too big, maximum" : "too small, minimum", + (l > 0) ? LLONG_MAX : LLONG_MIN); + /* NOTREACHED */ + } + + errno = 0; + r = strtoll(right, NULL, 10); + if (errno == ERANGE) { + yyerror("value '%s' is %s is %lld", right, + (l > 0) ? "too big, maximum" : "too small, minimum", + (l > 0) ? LLONG_MAX : LLONG_MIN); + /* NOTREACHED */ + } + + switch(op[0]) { + case '+': + /* + * Check for over-& underflow. + */ + if ((l >= 0 && r <= INT64_MAX - l) || + (l <= 0 && r >= INT64_MIN - l)) { + res = l + r; + } else { + yyerror("integer overflow or underflow occurred for " + "operation '%s %s %s'", left, op, right); + } + break; + case '-': + /* + * Check for over-& underflow. + */ + if ((r > 0 && l < INT64_MIN + r) || + (r < 0 && l > INT64_MAX + r)) { + yyerror("integer overflow or underflow occurred for " + "operation '%s %s %s'", left, op, right); + } else { + res = l - r; + } + break; + case '/': + if (r == 0) + yyerror("second argument to '%s' must not be zero", op); + if (l == INT64_MIN && r == -1) + yyerror("integer overflow or underflow occurred for " + "operation '%s %s %s'", left, op, right); + res = l / r; + + break; + case '%': + if (r == 0) + yyerror("second argument to '%s' must not be zero", op); + if (l == INT64_MIN && r == -1) + yyerror("integer overflow or underflow occurred for " + "operation '%s %s %s'", left, op, right); + res = l % r; + break; + case '*': + /* + * Check for over-& underflow. + */ + + /* + * Simplify the conditions: + * - remove the case of both negative arguments + * unless the operation will cause an overflow + */ + if (l < 0 && r < 0 && l != INT64_MIN && r != INT64_MIN) { + l = -l; + r = -r; + } + + /* - remove the case of legative l and positive r */ + if (l < 0 && r >= 0) { + /* Use res as a temporary variable */ + res = l; + l = r; + r = res; + } + + if ((l < 0 && r < 0) || + (r > 0 && l > INT64_MAX / r) || + (r <= 0 && l != 0 && r < INT64_MIN / l)) { + yyerror("integer overflow or underflow occurred for " + "operation '%s %s %s'", left, op, right); + /* NOTREACHED */ + } else { + res = l * r; + } + break; + } + return res; +} + +static const char *x = "|&=<>+-*/%:()"; +static const int x_token[] = { + SPEC_OR, SPEC_AND, COMPARE, COMPARE, COMPARE, ADD_SUB_OPERATOR, + ADD_SUB_OPERATOR, MUL_DIV_MOD_OPERATOR, MUL_DIV_MOD_OPERATOR, + MUL_DIV_MOD_OPERATOR, SPEC_REG, LEFT_PARENT, RIGHT_PARENT +}; + +static int handle_ddash = 1; + +int +yylex(void) +{ + const char *p = *av++; + int retval; + + if (!p) + retval = 0; + else if (p[1] == '\0') { + const char *w = strchr(x, p[0]); + if (w) { + retval = x_token[w-x]; + } else { + retval = STRING; + } + } else if (p[1] == '=' && p[2] == '\0' + && (p[0] == '>' || p[0] == '<' || p[0] == '!')) + retval = COMPARE; + else if (handle_ddash && p[0] == '-' && p[1] == '-' && p[2] == '\0') { + /* ignore "--" if passed as first argument and isn't followed + * by another STRING */ + retval = yylex(); + if (retval != STRING && retval != LEFT_PARENT + && retval != RIGHT_PARENT) { + /* is not followed by string or parenthesis, use as + * STRING */ + retval = STRING; + av--; /* was increased in call to yylex() above */ + p = "--"; + } else { + /* "--" is to be ignored */ + p = yylval; + } + } else if (strcmp(p, "length") == 0) + retval = LENGTH; + else + retval = STRING; + + handle_ddash = 0; + yylval = p; + + return retval; +} + +/* + * Print error message and exit with error 2 (syntax error). + */ +static __printflike(1, 2) void +yyerror(const char *fmt, ...) +{ + va_list arg; + + va_start(arg, fmt); + verrx(2, fmt, arg); + va_end(arg); +} + +int +main(int argc, const char * const *argv) +{ + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + if (argc == 1) { + (void)fprintf(stderr, "usage: %s expression\n", + getprogname()); + exit(2); + } + + av = argv + 1; + + return yyparse(); +} diff --git a/bin/kill/kill.1 b/bin/kill/kill.1 new file mode 100644 index 0000000..86b05fd --- /dev/null +++ b/bin/kill/kill.1 @@ -0,0 +1,156 @@ +.\" $NetBSD: kill.1,v 1.28 2017/04/22 23:01:36 christos Exp $ +.\" +.\" 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 +.\" +.Dd April 22, 2017 +.Dt 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 signal_name +.Ar pid +\&... +.Nm +.Fl signal_number +.Ar pid +\&... +.Sh DESCRIPTION +The +.Nm +utility sends a signal to the process(es) specified +by the pid operand(s). +.Pp +Only the super-user may send signals to other users' processes. +.Pp +The options are as follows: +.Bl -tag -width Ds +.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 +Display the name of the signal corresponding to +.Ar exit_status . +.Ar exit_status +may be the exit status of a command killed by a signal +(see the +special +.Xr sh 1 +parameter +.Sq ?\& ) +or a signal number. +.Pp +If no operand is given, display the names of all the signals. +.It Fl signal_name +A symbolic signal name specifying the signal to be sent instead of the +default +.Dv TERM . +.It Fl 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 Ds -compact +.It -1 +If superuser, broadcast the signal to all processes; otherwise broadcast +to all processes belonging to the user. +.It 0 +Broadcast the signal to all processes in the current process group +belonging to the user. +.El +.Pp +Some of the more commonly used signals: +.Bl -tag -width Ds -compact +.It 0 +0 (does not affect the process; can be used to test whether the +process exists) +.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 +.Nm +is a built-in to +.Xr csh 1 ; +it allows job specifiers of the form ``%...'' as arguments +so process id's are not as often used as +.Nm +arguments. +See +.Xr csh 1 +for details. +.Sh DIAGNOSTICS +.Ex -std +.Sh SEE ALSO +.Xr csh 1 , +.Xr pgrep 1 , +.Xr pkill 1 , +.Xr ps 1 , +.Xr kill 2 , +.Xr sigaction 2 , +.Xr signal 7 +.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. diff --git a/bin/kill/kill.c b/bin/kill/kill.c new file mode 100644 index 0000000..6c17cb3 --- /dev/null +++ b/bin/kill/kill.c @@ -0,0 +1,320 @@ +/* $NetBSD: kill.c,v 1.30 2018/12/12 20:22:43 kre Exp $ */ + +/* + * 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. + */ + +#include <sys/cdefs.h> +#if !defined(lint) && !defined(SHELL) +__COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)kill.c 8.4 (Berkeley) 4/28/95"; +#else +__RCSID("$NetBSD: kill.c,v 1.30 2018/12/12 20:22:43 kre Exp $"); +#endif +#endif /* not lint */ + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <inttypes.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> +#include <locale.h> +#include <sys/ioctl.h> + +#ifdef SHELL /* sh (aka ash) builtin */ +int killcmd(int, char *argv[]); +#define main killcmd +#include "../../bin/sh/bltin/bltin.h" +#endif /* SHELL */ + +__dead static void nosig(const char *); +void printsignals(FILE *, int); +static int signum(const char *); +static pid_t processnum(const char *); +__dead static void usage(void); + +int +main(int argc, char *argv[]) +{ + int errors; + int numsig; + pid_t pid; + const char *sn; + + setprogname(argv[0]); + setlocale(LC_ALL, ""); + if (argc < 2) + usage(); + + numsig = SIGTERM; + + argc--, argv++; + + /* + * Process exactly 1 option, if there is one. + */ + if (argv[0][0] == '-') { + switch (argv[0][1]) { + case 'l': + if (argv[0][2] != '\0') + sn = argv[0] + 2; + else { + argc--; argv++; + sn = argv[0]; + } + if (argc > 1) + usage(); + if (argc == 1) { + if (isdigit((unsigned char)*sn) == 0) + usage(); + numsig = signum(sn); + if (numsig >= 128) + numsig -= 128; + if (numsig == 0 || signalnext(numsig) == -1) + nosig(sn); + sn = signalname(numsig); + if (sn == NULL) + errx(EXIT_FAILURE, + "unknown signal number: %d", numsig); + printf("%s\n", sn); + exit(0); + } + printsignals(stdout, 0); + exit(0); + + case 's': + if (argv[0][2] != '\0') + sn = argv[0] + 2; + else { + argc--, argv++; + if (argc < 1) { + warnx( + "option requires an argument -- s"); + usage(); + } + sn = argv[0]; + } + if (strcmp(sn, "0") == 0) + numsig = 0; + else if ((numsig = signalnumber(sn)) == 0) { + if (sn != argv[0]) + goto trysignal; + nosig(sn); + } + argc--, argv++; + break; + + case '-': + if (argv[0][2] == '\0') { + /* process this one again later */ + break; + } + /* FALL THROUGH */ + case '\0': + usage(); + break; + + default: + trysignal: + sn = *argv + 1; + if (((numsig = signalnumber(sn)) == 0)) { + if (isdigit((unsigned char)*sn)) + numsig = signum(sn); + else + nosig(sn); + } + + if (numsig != 0 && signalnext(numsig) == -1) + nosig(sn); + argc--, argv++; + break; + } + } + + /* deal with the optional '--' end of options option */ + if (argc > 0 && strcmp(*argv, "--") == 0) + argc--, argv++; + + if (argc == 0) + usage(); + + for (errors = 0; argc; argc--, argv++) { +#ifdef SHELL + extern int getjobpgrp(const char *); + if (*argv[0] == '%') { + pid = getjobpgrp(*argv); + if (pid == 0) { + warnx("illegal job id: %s", *argv); + errors = 1; + continue; + } + } else +#endif + if ((pid = processnum(*argv)) == (pid_t)-1) { + errors = 1; + continue; + } + + if (kill(pid, numsig) == -1) { + warn("%s", *argv); + errors = 1; + } +#ifdef SHELL + /* + * Wakeup the process if it was suspended, so it can + * exit without an explicit 'fg'. + * (kernel handles this for SIGKILL) + */ + if (numsig == SIGTERM || numsig == SIGHUP) + kill(pid, SIGCONT); +#endif + } + + exit(errors); + /* NOTREACHED */ +} + +static int +signum(const char *sn) +{ + intmax_t n; + char *ep; + + n = strtoimax(sn, &ep, 10); + + /* check for correctly parsed number */ + if (*ep || n <= INT_MIN || n >= INT_MAX ) + errx(EXIT_FAILURE, "illegal signal number: %s", sn); + /* NOTREACHED */ + + return (int)n; +} + +static pid_t +processnum(const char *s) +{ + intmax_t n; + char *ep; + + n = strtoimax(s, &ep, 10); + + /* check for correctly parsed number */ + if (*ep || n == INTMAX_MIN || n == INTMAX_MAX || (pid_t)n != n || + n == -1) { + warnx("illegal process%s id: %s", (n < 0 ? " group" : ""), s); + n = -1; + } + + return (pid_t)n; +} + +static void +nosig(const char *name) +{ + + warnx("unknown signal %s; valid signals:", name); + printsignals(stderr, 0); + exit(1); + /* NOTREACHED */ +} + +#ifndef SHELL +/* + * Print the names of all the signals (neatly) to fp + * "len" gives the number of chars already printed to + * the current output line (in kill.c, always 0) + */ +void +printsignals(FILE *fp, int len) +{ + int sig; + int nl, pad; + const char *name; + int termwidth = 80; + + if ((name = getenv("COLUMNS")) != 0) + termwidth = atoi(name); + else if (isatty(fileno(fp))) { + struct winsize win; + + if (ioctl(fileno(fp), TIOCGWINSZ, &win) == 0 && win.ws_col > 0) + termwidth = win.ws_col; + } + + pad = (len | 7) + 1 - len; + + for (sig = 0; (sig = signalnext(sig)) != 0; ) { + name = signalname(sig); + if (name == NULL) + continue; + + nl = strlen(name); + + if (len > 0 && nl + len + pad >= termwidth) { + fprintf(fp, "\n"); + len = 0; + pad = 0; + } else if (pad > 0 && len != 0) + fprintf(fp, "%*s", pad, ""); + else + pad = 0; + + len += nl + pad; + pad = (nl | 7) + 1 - nl; + + fprintf(fp, "%s", name); + } + if (len != 0) + fprintf(fp, "\n"); +} +#endif + +static void +usage(void) +{ + const char *pn = getprogname(); + + fprintf(stderr, "usage: %s [-s signal_name] pid ...\n" + " %s -l [exit_status]\n" + " %s -signal_name pid ...\n" + " %s -signal_number pid ...\n", + pn, pn, pn, pn); + exit(1); + /* NOTREACHED */ +} diff --git a/bin/ln/ln.1 b/bin/ln/ln.1 new file mode 100644 index 0000000..2eca74f --- /dev/null +++ b/bin/ln/ln.1 @@ -0,0 +1,320 @@ +.\" $NetBSD: ln.1,v 1.28 2017/04/20 22:57:30 christos Exp $ +.\"- +.\" 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. +.\" +.\" @(#)ln.1 8.2 (Berkeley) 12/30/93 +.\" $FreeBSD: head/bin/ln/ln.1 244791 2012-12-28 22:06:33Z gjb $ +.\" +.Dd April 20, 2017 +.Dt LN 1 +.Os +.Sh NAME +.\" .Nm ln , +.\" .Nm link +.Nm ln +.Nd link files +.Sh SYNOPSIS +.Nm +.Op Fl L | Fl P | Fl s Op Fl F +.Op Fl f | iw +.Op Fl hnv +.Ar source_file +.Op Ar target_file +.Nm +.Op Fl L | Fl P | Fl s Op Fl F +.Op Fl f | iw +.Op Fl hnv +.Ar source_file ... +.Ar target_dir +.\" .Nm link +.\" .Ar source_file Ar target_file +.Sh DESCRIPTION +The +.Nm +utility creates a new directory entry (linked file) for the file name +specified by +.Ar target_file . +The +.Ar target_file +will be created with the same file modes as the +.Ar source_file . +It is useful for maintaining multiple copies of a file in many places +at once without using up storage for the +.Dq copies ; +instead, a link +.Dq points +to the original copy. +There are two types of links; hard links and symbolic links. +How a link +.Dq points +to a file is one of the differences between a hard and symbolic link. +.Pp +The options are as follows: +.Bl -tag -width flag +.It Fl F +If the target file already exists and is a directory, then remove it +so that the link may occur. +The +.Fl F +option should be used with either +.Fl f +or +.Fl i +options. +If none is specified, +.Fl f +is implied. +The +.Fl F +option is a no-op unless +.Fl s +option is specified. +.It Fl L +When creating a hard link to a symbolic link, +create a hard link to the target of the symbolic link. +This is the default. +This option cancels the +.Fl P +option. +.It Fl P +When creating a hard link to a symbolic link, +create a hard link to the symbolic link itself. +This option cancels the +.Fl L +option. +.It Fl f +If the target file already exists, +then unlink it so that the link may occur. +(The +.Fl f +option overrides any previous +.Fl i +and +.Fl w +options.) +.It Fl h +If the +.Ar target_file +or +.Ar target_dir +is a symbolic link, do not follow it. +This is most useful with the +.Fl f +option, to replace a symlink which may point to a directory. +.It Fl i +Cause +.Nm +to write a prompt to standard error if the target file exists. +If the response from the standard input begins with the character +.Sq Li y +or +.Sq Li Y , +then unlink the target file so that the link may occur. +Otherwise, do not attempt the link. +(The +.Fl i +option overrides any previous +.Fl f +options.) +.It Fl n +Same as +.Fl h , +for compatibility with other +.Nm +implementations. +.It Fl s +Create a symbolic link. +.It Fl v +Cause +.Nm +to be verbose, showing files as they are processed. +.It Fl w +Warn if the source of a symbolic link does not currently exist. +.El +.Pp +By default, +.Nm +makes +.Em hard +links. +A hard link to a file is indistinguishable from the original directory entry; +any changes to a file are effectively independent of the name used to reference +the file. +Directories may not be hardlinked, and hard links may not span file systems. +.Pp +A symbolic link contains the name of the file to +which it is linked. +The referenced file is used when an +.Xr open 2 +operation is performed on the link. +A +.Xr stat 2 +on a symbolic link will return the linked-to file; an +.Xr lstat 2 +must be done to obtain information about the link. +The +.Xr readlink 2 +call may be used to read the contents of a symbolic link. +Symbolic links may span file systems and may refer to directories. +.Pp +Given one or two arguments, +.Nm +creates a link to an existing file +.Ar source_file . +If +.Ar target_file +is given, the link has that name; +.Ar target_file +may also be a directory in which to place the link; +otherwise it is placed in the current directory. +If only the directory is specified, the link will be made +to the last component of +.Ar source_file . +.Pp +Given more than two arguments, +.Nm +makes links in +.Ar target_dir +to all the named source files. +The links made will have the same name as the files being linked to. +.\" .Pp +.\" When the utility is called as +.\" .Nm link , +.\" exactly two arguments must be supplied, +.\" neither of which may specify a directory. +.\" No options may be supplied in this simple mode of operation, +.\" which performs a +.\" .Xr link 2 +.\" operation using the two passed arguments. +.Sh EXAMPLES +Create a symbolic link named +.Pa /home/src +and point it to +.Pa /usr/src : +.Pp +.Dl # ln -s /usr/src /home/src +.Pp +Hard link +.Pa /usr/local/bin/fooprog +to file +.Pa /usr/local/bin/fooprog-1.0 : +.Pp +.Dl # ln /usr/local/bin/fooprog-1.0 /usr/local/bin/fooprog +.Pp +As an exercise, try the following commands: +.Bd -literal -offset indent +# ls -i /bin/[ +11553 /bin/[ +# ls -i /bin/test +11553 /bin/test +.Ed +.Pp +Note that both files have the same inode; that is, +.Pa /bin/[ +is essentially an alias for the +.Xr test 1 +command. +This hard link exists so +.Xr test 1 +may be invoked from shell scripts, for example, using the +.Li "if [ ]" +construct. +.Pp +In the next example, the second call to +.Nm +removes the original +.Pa foo +and creates a replacement pointing to +.Pa baz : +.Bd -literal -offset indent +# mkdir bar baz +# ln -s bar foo +# ln -shf baz foo +.Ed +.Pp +Without the +.Fl h +option, this would instead leave +.Pa foo +pointing to +.Pa bar +and inside +.Pa foo +create a new symlink +.Pa baz +pointing to itself. +This results from directory-walking. +.Pp +An easy rule to remember is that the argument order for +.Nm +is the same as for +.Xr cp 1 : +The first argument needs to exist, the second one is created. +.Sh COMPATIBILITY +The +.Fl h , +.Fl i , +.Fl n , +.Fl v +and +.Fl w +options are non-standard and their use in scripts is not recommended. +They are provided solely for compatibility with other +.Nm +implementations. +.Pp +The +.Fl F +option is a +.Fx +extension and should not be used in portable scripts. +.Sh SEE ALSO +.Xr link 2 , +.Xr lstat 2 , +.Xr readlink 2 , +.Xr stat 2 , +.Xr symlink 2 , +.Xr symlink 7 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 . +.\" .Pp +.\" The simplified +.\" .Nm link +.\" command conforms to +.\" .St -susv2 . +.Sh HISTORY +An +.Nm +command appeared in +.At v1 . diff --git a/bin/ln/ln.c b/bin/ln/ln.c new file mode 100644 index 0000000..b057e39 --- /dev/null +++ b/bin/ln/ln.c @@ -0,0 +1,365 @@ +/* $NetBSD: ln.c,v 1.40 2018/08/26 23:01:06 sevan Exp $ */ + +/*- + * Copyright (c) 1987, 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. + */ + +#if 0 +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1987, 1993, 1994\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static char sccsid[] = "@(#)ln.c 8.2 (Berkeley) 3/31/94"; +#endif /* not lint */ +#endif +#include <sys/cdefs.h> +#ifdef __FBSDID +__FBSDID("$FreeBSD: head/bin/ln/ln.c 251261 2013-06-02 17:55:00Z eadler $"); +#endif +__RCSID("$NetBSD: ln.c,v 1.40 2018/08/26 23:01:06 sevan Exp $"); + +#include <sys/param.h> +#include <sys/stat.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static int fflag; /* Unlink existing files. */ +static int Fflag; /* Remove empty directories also. */ +static int hflag; /* Check new name for symlink first. */ +static int iflag; /* Interactive mode. */ +static int Pflag; /* Create hard links to symlinks. */ +static int sflag; /* Symbolic, not hard, link. */ +static int vflag; /* Verbose output. */ +static int wflag; /* Warn if symlink target does not + * exist, and -f is not enabled. */ +static char linkch; + +static int linkit(const char *, const char *, int); +static __dead void usage(void); + +int +main(int argc, char *argv[]) +{ + struct stat sb; + char *p, *targetdir; + int ch, exitval; + + /* + * Test for the special case where the utility is called as + * "link", for which the functionality provided is greatly + * simplified. + */ + if ((p = strrchr(argv[0], '/')) == NULL) + p = argv[0]; + else + ++p; + if (strcmp(p, "link") == 0) { + while (getopt(argc, argv, "") != -1) + usage(); + argc -= optind; + argv += optind; + if (argc != 2) + usage(); + if (link(argv[0], argv[1]) == -1) + err(EXIT_FAILURE, NULL); + exit(EXIT_SUCCESS); + } + + while ((ch = getopt(argc, argv, "FLPfhinsvw")) != -1) + switch (ch) { + case 'F': + Fflag = 1; + break; + case 'L': + Pflag = 0; + break; + case 'P': + Pflag = 1; + break; + case 'f': + fflag = 1; + iflag = 0; + wflag = 0; + break; + case 'h': + case 'n': + hflag = 1; + break; + case 'i': + iflag = 1; + fflag = 0; + break; + case 's': + sflag = 1; + break; + case 'v': + vflag = 1; + break; + case 'w': + wflag = 1; + break; + case '?': + default: + usage(); + } + + argv += optind; + argc -= optind; + + linkch = sflag ? '-' : '='; + if (sflag == 0) + Fflag = 0; + if (Fflag == 1 && iflag == 0) { + fflag = 1; + wflag = 0; /* Implied when fflag != 0 */ + } + + switch(argc) { + case 0: + usage(); + /* NOTREACHED */ + case 1: /* ln source */ + exit(linkit(argv[0], ".", 1)); + case 2: /* ln source target */ + exit(linkit(argv[0], argv[1], 0)); + default: + ; + } + /* ln source1 source2 directory */ + targetdir = argv[argc - 1]; + if (hflag && lstat(targetdir, &sb) == 0 && S_ISLNK(sb.st_mode)) { + /* + * We were asked not to follow symlinks, but found one at + * the target--simulate "not a directory" error + */ + errno = ENOTDIR; + err(1, "%s", targetdir); + } + if (stat(targetdir, &sb)) + err(1, "%s", targetdir); + if (!S_ISDIR(sb.st_mode)) + usage(); + for (exitval = 0; *argv != targetdir; ++argv) + exitval |= linkit(*argv, targetdir, 1); + exit(exitval); +} + +/* + * Two pathnames refer to the same directory entry if the directories match + * and the final components' names match. + */ +static int +samedirent(const char *path1, const char *path2) +{ + const char *file1, *file2; + char pathbuf[PATH_MAX]; + struct stat sb1, sb2; + + if (strcmp(path1, path2) == 0) + return 1; + file1 = strrchr(path1, '/'); + if (file1 != NULL) + file1++; + else + file1 = path1; + file2 = strrchr(path2, '/'); + if (file2 != NULL) + file2++; + else + file2 = path2; + if (strcmp(file1, file2) != 0) + return 0; + if (file1 - path1 >= PATH_MAX || file2 - path2 >= PATH_MAX) + return 0; + if (file1 == path1) + memcpy(pathbuf, ".", 2); + else { + memcpy(pathbuf, path1, file1 - path1); + pathbuf[file1 - path1] = '\0'; + } + if (stat(pathbuf, &sb1) != 0) + return 0; + if (file2 == path2) + memcpy(pathbuf, ".", 2); + else { + memcpy(pathbuf, path2, file2 - path2); + pathbuf[file2 - path2] = '\0'; + } + if (stat(pathbuf, &sb2) != 0) + return 0; + return sb1.st_dev == sb2.st_dev && sb1.st_ino == sb2.st_ino; +} + +static int +linkit(const char *source, const char *target, int isdir) +{ + struct stat sb; + const char *p; + int ch, exists, first; + char path[PATH_MAX]; + char wbuf[PATH_MAX]; + char bbuf[PATH_MAX]; + + if (!sflag) { + /* If source doesn't exist, quit now. */ + if ((Pflag ? lstat : stat)(source, &sb)) { + warn("%s", source); + return (1); + } + /* Only symbolic links to directories. */ + if (S_ISDIR(sb.st_mode)) { + errno = EISDIR; + warn("%s", source); + return (1); + } + } + + /* + * If the target is a directory (and not a symlink if hflag), + * append the source's name. + */ + if (isdir || + (lstat(target, &sb) == 0 && S_ISDIR(sb.st_mode)) || + (!hflag && stat(target, &sb) == 0 && S_ISDIR(sb.st_mode))) { + if (strlcpy(bbuf, source, sizeof(bbuf)) >= sizeof(bbuf) || + (p = basename(bbuf)) == NULL || + snprintf(path, sizeof(path), "%s/%s", target, p) >= + (ssize_t)sizeof(path)) { + errno = ENAMETOOLONG; + warn("%s", source); + return (1); + } + target = path; + } + + /* + * If the link source doesn't exist, and a symbolic link was + * requested, and -w was specified, give a warning. + */ + if (sflag && wflag) { + if (*source == '/') { + /* Absolute link source. */ + if (stat(source, &sb) != 0) + warn("warning: %s inaccessible", source); + } else { + /* + * Relative symlink source. Try to construct the + * absolute path of the source, by appending `source' + * to the parent directory of the target. + */ + strlcpy(bbuf, target, sizeof(bbuf)); + p = dirname(bbuf); + if (p != NULL) { + (void)snprintf(wbuf, sizeof(wbuf), "%s/%s", + p, source); + if (stat(wbuf, &sb) != 0) + warn("warning: %s", source); + } + } + } + + /* + * If the file exists, first check it is not the same directory entry. + */ + exists = !lstat(target, &sb); + if (exists) { + if (!sflag && samedirent(source, target)) { + warnx("%s and %s are the same directory entry", + source, target); + return (1); + } + } + /* + * Then unlink it forcibly if -f was specified + * and interactively if -i was specified. + */ + if (fflag && exists) { + if (Fflag && S_ISDIR(sb.st_mode)) { + if (rmdir(target)) { + warn("%s", target); + return (1); + } + } else if (unlink(target)) { + warn("%s", target); + return (1); + } + } else if (iflag && exists) { + fflush(stdout); + fprintf(stderr, "replace %s? ", target); + + first = ch = getchar(); + while(ch != '\n' && ch != EOF) + ch = getchar(); + if (first != 'y' && first != 'Y') { + fprintf(stderr, "not replaced\n"); + return (1); + } + + if (Fflag && S_ISDIR(sb.st_mode)) { + if (rmdir(target)) { + warn("%s", target); + return (1); + } + } else if (unlink(target)) { + warn("%s", target); + return (1); + } + } + + /* Attempt the link. */ + if (sflag ? symlink(source, target) : + linkat(AT_FDCWD, source, AT_FDCWD, target, + Pflag ? 0 : AT_SYMLINK_FOLLOW)) { + warn("%s", target); + return (1); + } + if (vflag) + (void)printf("%s %c> %s\n", target, linkch, source); + return (0); +} + +static __dead void +usage(void) +{ + (void)fprintf(stderr, + "usage: %s [-L | -P | -s [-F]] [-f | -iw] [-hnv] source_file [target_file]\n" + " %s [-L | -P | -s [-F]] [-f | -iw] [-hnv] source_file ... target_dir\n" + " link source_file target_file\n", getprogname(), getprogname()); + exit(1); +} diff --git a/bin/ls/cmp.c b/bin/ls/cmp.c new file mode 100644 index 0000000..21d50e1 --- /dev/null +++ b/bin/ls/cmp.c @@ -0,0 +1,199 @@ +/* $NetBSD: cmp.c,v 1.17 2003/08/07 09:05:14 agc Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Michael Fischbein. + * + * 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> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)cmp.c 8.1 (Berkeley) 5/31/93"; +#else +__RCSID("$NetBSD: cmp.c,v 1.17 2003/08/07 09:05:14 agc Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <fts.h> +#include <string.h> + +#include "ls.h" +#include "extern.h" + +#if defined(_POSIX_SOURCE) || defined(_POSIX_C_SOURCE) || \ + defined(_XOPEN_SOURCE) || defined(__NetBSD__) +#define ATIMENSEC_CMP(x, op, y) ((x)->st_atimensec op (y)->st_atimensec) +#define CTIMENSEC_CMP(x, op, y) ((x)->st_ctimensec op (y)->st_ctimensec) +#define MTIMENSEC_CMP(x, op, y) ((x)->st_mtimensec op (y)->st_mtimensec) +#else +#define ATIMENSEC_CMP(x, op, y) \ + ((x)->st_atimespec.tv_nsec op (y)->st_atimespec.tv_nsec) +#define CTIMENSEC_CMP(x, op, y) \ + ((x)->st_ctimespec.tv_nsec op (y)->st_ctimespec.tv_nsec) +#define MTIMENSEC_CMP(x, op, y) \ + ((x)->st_mtimespec.tv_nsec op (y)->st_mtimespec.tv_nsec) +#endif + +int +namecmp(const FTSENT *a, const FTSENT *b) +{ + + return (strcmp(a->fts_name, b->fts_name)); +} + +int +revnamecmp(const FTSENT *a, const FTSENT *b) +{ + + return (strcmp(b->fts_name, a->fts_name)); +} + +int +modcmp(const FTSENT *a, const FTSENT *b) +{ + + if (b->fts_statp->st_mtime > a->fts_statp->st_mtime) + return (1); + else if (b->fts_statp->st_mtime < a->fts_statp->st_mtime) + return (-1); + else if (MTIMENSEC_CMP(b->fts_statp, >, a->fts_statp)) + return (1); + else if (MTIMENSEC_CMP(b->fts_statp, <, a->fts_statp)) + return (-1); + else + return (namecmp(a, b)); +} + +int +revmodcmp(const FTSENT *a, const FTSENT *b) +{ + + if (b->fts_statp->st_mtime > a->fts_statp->st_mtime) + return (-1); + else if (b->fts_statp->st_mtime < a->fts_statp->st_mtime) + return (1); + else if (MTIMENSEC_CMP(b->fts_statp, >, a->fts_statp)) + return (-1); + else if (MTIMENSEC_CMP(b->fts_statp, <, a->fts_statp)) + return (1); + else + return (revnamecmp(a, b)); +} + +int +acccmp(const FTSENT *a, const FTSENT *b) +{ + + if (b->fts_statp->st_atime > a->fts_statp->st_atime) + return (1); + else if (b->fts_statp->st_atime < a->fts_statp->st_atime) + return (-1); + else if (ATIMENSEC_CMP(b->fts_statp, >, a->fts_statp)) + return (1); + else if (ATIMENSEC_CMP(b->fts_statp, <, a->fts_statp)) + return (-1); + else + return (namecmp(a, b)); +} + +int +revacccmp(const FTSENT *a, const FTSENT *b) +{ + + if (b->fts_statp->st_atime > a->fts_statp->st_atime) + return (-1); + else if (b->fts_statp->st_atime < a->fts_statp->st_atime) + return (1); + else if (ATIMENSEC_CMP(b->fts_statp, >, a->fts_statp)) + return (-1); + else if (ATIMENSEC_CMP(b->fts_statp, <, a->fts_statp)) + return (1); + else + return (revnamecmp(a, b)); +} + +int +statcmp(const FTSENT *a, const FTSENT *b) +{ + + if (b->fts_statp->st_ctime > a->fts_statp->st_ctime) + return (1); + else if (b->fts_statp->st_ctime < a->fts_statp->st_ctime) + return (-1); + else if (CTIMENSEC_CMP(b->fts_statp, >, a->fts_statp)) + return (1); + else if (CTIMENSEC_CMP(b->fts_statp, <, a->fts_statp)) + return (-1); + else + return (namecmp(a, b)); +} + +int +revstatcmp(const FTSENT *a, const FTSENT *b) +{ + + if (b->fts_statp->st_ctime > a->fts_statp->st_ctime) + return (-1); + else if (b->fts_statp->st_ctime < a->fts_statp->st_ctime) + return (1); + else if (CTIMENSEC_CMP(b->fts_statp, >, a->fts_statp)) + return (-1); + else if (CTIMENSEC_CMP(b->fts_statp, <, a->fts_statp)) + return (1); + else + return (revnamecmp(a, b)); +} + +int +sizecmp(const FTSENT *a, const FTSENT *b) +{ + + if (b->fts_statp->st_size > a->fts_statp->st_size) + return (1); + if (b->fts_statp->st_size < a->fts_statp->st_size) + return (-1); + else + return (namecmp(a, b)); +} + +int +revsizecmp(const FTSENT *a, const FTSENT *b) +{ + + if (b->fts_statp->st_size > a->fts_statp->st_size) + return (-1); + if (b->fts_statp->st_size < a->fts_statp->st_size) + return (1); + else + return (revnamecmp(a, b)); +} diff --git a/bin/ls/extern.h b/bin/ls/extern.h new file mode 100644 index 0000000..0a9eea0 --- /dev/null +++ b/bin/ls/extern.h @@ -0,0 +1,53 @@ +/* $NetBSD: extern.h,v 1.17 2011/08/29 14:44:21 joerg Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)extern.h 8.1 (Berkeley) 5/31/93 + */ + +int acccmp(const FTSENT *, const FTSENT *); +int revacccmp(const FTSENT *, const FTSENT *); +int modcmp(const FTSENT *, const FTSENT *); +int revmodcmp(const FTSENT *, const FTSENT *); +int namecmp(const FTSENT *, const FTSENT *); +int revnamecmp(const FTSENT *, const FTSENT *); +int statcmp(const FTSENT *, const FTSENT *); +int revstatcmp(const FTSENT *, const FTSENT *); +int sizecmp(const FTSENT *, const FTSENT *); +int revsizecmp(const FTSENT *, const FTSENT *); + +int ls_main(int, char *[]); + +int printescaped(const char *); +void printacol(DISPLAY *); +void printcol(DISPLAY *); +void printlong(DISPLAY *); +void printscol(DISPLAY *); +void printstream(DISPLAY *); +int safe_print(const char *); diff --git a/bin/ls/ls.1 b/bin/ls/ls.1 new file mode 100644 index 0000000..1bd9161 --- /dev/null +++ b/bin/ls/ls.1 @@ -0,0 +1,516 @@ +.\" $NetBSD: ls.1,v 1.80 2017/07/03 21:33:23 wiz Exp $ +.\" +.\" Copyright (c) 1980, 1990, 1991, 1993, 1994 +.\" 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. +.\" +.\" @(#)ls.1 8.7 (Berkeley) 7/29/94 +.\" +.Dd August 10, 2016 +.Dt LS 1 +.Os +.Sh NAME +.Nm ls +.Nd list directory contents +.Sh SYNOPSIS +.Nm +.Op Fl 1AaBbCcdFfghikLlMmnOoPpqRrSsTtuWwXx +.Op Ar +.Sh DESCRIPTION +For each +.Ar file +operand that names a file of a type other than +directory, +.Nm +displays its name as well as any requested, +associated information. +For each +.Ar file +operand that names a file of type directory, +.Nm +displays the names of files contained +within that directory, as well as any requested, associated +information. +.Pp +If no operands are given, the contents of the current +directory are displayed. +If more than one operand is given, +non-directory operands are displayed first; directory +and non-directory operands are sorted separately and in +lexicographical order. +.Pp +The following options are available: +.Bl -tag -width indent +.It Fl \&1 +(The numeric digit +.Dq one ) . +Force output to be one entry per line. +This is the default when output is not to a terminal. +.It Fl A +List all entries except for +.Ql \&. +and +.Ql \&.. . +Always set for the super-user. +.It Fl a +Include directory entries whose names begin with a +dot +.Pq Sq \&. . +.It Fl B +Force printing of non-graphic characters in file names as \exxx, where xxx +is the numeric value of the character in octal. +.It Fl b +As +.Fl B , +but use C escape codes whenever possible. +.It Fl C +Force multi-column output; this is the default when output is to a terminal. +.It Fl c +Use time when file status was last changed, +instead of time of last modification of the file for printing +.Pq Fl l +or sorting +.Pq Fl t . +Overrides +.Fl u . +.It Fl d +Directories are listed as plain files (not searched recursively) and +symbolic links in the argument list are not followed. +Turns off +.Fl R +if also given. +.It Fl F +Display a slash +.Pq Sq \&/ +immediately after each pathname that is a directory, +an asterisk +.Pq Sq \&* +after each that is executable, +an at sign +.Pq Sq \&@ +after each symbolic link, +a percent sign +.Pq Sq \&% +after each whiteout, +an equal sign +.Pq Sq \&= +after each socket, +and a vertical bar +.Pq Sq \&| +after each that is a +.Tn FIFO . +.It Fl f +Output is not sorted. +This option implies +.Fl a . +.It Fl g +The same as +.Fl l , +except that the owner is not printed. +.It Fl h +Modifies the +.Fl l +and +.Fl s +options, causing the sizes to be reported in bytes displayed in a human +readable format. +Overrides +.Fl k +and +.Fl M . +.It Fl i +For each file, print the file's file serial number (inode number). +.It Fl k +Modifies the +.Fl s +option, causing the sizes to be reported in kilobytes. +Overrides +.Fl h . +.It Fl L +For each file, if it's a link, evaluate file information and file type +of the referenced file and not the link itself; however still print +the link name, unless used with +.Fl l , +for example. +.It Fl l +(The lowercase letter +.Dq ell ) . +List in long format. +(See below.) +.It Fl M +Modifies the +.Fl l +and +.Fl s +options, causing the sizes or block counts reported to be separated with +commas (or a locale appropriate separator) resulting in a more readable +output. +Overrides +.Fl h ; +does not override +.Fl k . +.It Fl m +Stream output format; list files across the page, separated by commas. +.It Fl n +The same as +.Fl l , +except that +the owner and group IDs are displayed numerically rather than converting +to a owner or group name. +.It Fl O +Output only leaf files (not directories), eliding other +.Nm +output. +.It Fl o +Include the file flags in a long +.Pq Fl l +output. +If no file flags are set, +.Dq - +is displayed. +(See +.Xr chflags 1 +for a list of possible flags and their meanings.) +.It Fl P +Print the full pathname for each file. +.It Fl p +Display a slash +.Pq Sq \&/ +immediately after each pathname that is a directory. +.It Fl q +Force printing of non-printable characters in file names as +the character +.Sq \&? ; +this is the default when output is to a terminal. +.It Fl R +Recursively list subdirectories encountered. +See also +.Fl d . +.It Fl r +Reverse the order of the sort to get reverse +lexicographical order or the smallest or oldest entries first. +.It Fl S +Sort by size, largest file first. +.It Fl s +Display the number of file system blocks actually used by each file, in units +of 512 bytes or +.Ev BLOCKSIZE +(see +.Sx ENVIRONMENT ) +where partial units are rounded up to the +next integer value. +If the output is to a terminal, a total sum for all the file +sizes is output on a line before the listing. +.It Fl T +When used with the +.Fl l +(the lowercase letter +.Dq ell ) +option, display complete time information for the file, including +month, day, hour, minute, second, and year. +.It Fl t +Sort by time modified (most recently modified +first) before sorting the operands by lexicographical +order. +.It Fl u +Use time of last access, +instead of last modification +of the file for printing +.Pq Fl l +or sorting +.Pq Fl t . +Overrides +.Fl c . +.It Fl W +Display whiteouts when scanning directories. +.It Fl w +Force raw printing of non-printable characters. +This is the default when output is not to a terminal. +.It Fl x +Multi-column output sorted across the page rather than down the page. +.It Fl X +Don't cross mount points when recursing. +.El +.Pp +The +.Fl B , +.Fl b , +.Fl q , +and +.Fl w +options all override each other; the last one specified determines +the format used for non-printable characters. +.Pp +The +.Fl 1 , +.Fl C , +.Fl g , +.Fl l , +.Fl m , +and +.Fl x +options all override each other; the last one specified determines +the format used with the exception that if both +.Fl l +and +.Fl g +are specified, +.Fl l +will always override +.Fl g , +even if +.Fl g +was specified last. +.Pp +By default, +.Nm +lists one entry per line to standard +output; the exceptions are to terminals or when the +.Fl C +or +.Fl m +options are specified. +.Pp +File information is displayed with one or more +.Aq blank +characters separating the information associated with the +.Fl i , +.Fl l , +and +.Fl s +options. +.Ss The Long Format +If the +.Fl l +option is given, the following information +is displayed for each file: +.Bl -item -offset indent -compact +.It +file mode +.It +number of links +.It +owner name +.It +group name +.It +file flags (if +.Fl o +given) +.It +number of bytes in the file +.It +abbreviated month file was last modified +.It +day-of-month file was last modified +.It +hour and minute file was last modified +.It +pathname +.El +.Pp +In addition, for each directory whose contents are displayed, the total +number of file system blocks in units of 512 bytes or +.Ev BLOCKSIZE +(see +.Sx ENVIRONMENT ) +used by the files in the directory is displayed on a line by itself +immediately before the information for the files in the directory. +.Pp +If the owner or group names are not a known owner or group name, +or the +.Fl n +option is given, +the numeric ID's are displayed. +.Pp +If the file is a character special or block special file, +the major and minor device numbers for the file are displayed +in the size field. +If the file is a symbolic link the pathname of the +linked-to file is preceded by +.Dq \-> . +.Pp +The file mode printed under the +.Fl l +option consists of the entry type, owner permissions, group +permissions, and other permissions. +The entry type character describes the type of file, as +follows: +.Pp +.Bl -tag -width 4n -offset indent -compact +.It Sy \- +Regular file. +.It Sy a +Archive state 1. +.It Sy A +Archive state 2. +.It Sy b +Block special file. +.It Sy c +Character special file. +.It Sy d +Directory. +.It Sy l +Symbolic link. +.It Sy p +FIFO. +.It Sy s +Socket link. +.It Sy w +Whiteout. +.El +.Pp +The next three fields +are three characters each: +owner permissions, +group permissions, and +other permissions. +Each field has three character positions: +.Bl -enum -offset indent +.It +If +.Sy r , +the file is readable; if +.Sy \- , +it is not readable. +.It +If +.Sy w , +the file is writable; if +.Sy \- , +it is not writable. +.It +The first of the following that applies: +.Bl -tag -width 4n -offset indent +.It Sy S +If in the owner permissions, the file is not executable and +set-user-ID mode is set. +If in the group permissions, the file is not executable +and set-group-ID mode is set. +.It Sy s +If in the owner permissions, the file is executable +and set-user-ID mode is set. +If in the group permissions, the file is executable +and setgroup-ID mode is set. +.It Sy x +The file is executable or the directory is +searchable. +.It Sy \- +The file is neither readable, writable, executable, +nor set-user-ID nor set-group-ID mode, nor sticky. +(See below.) +.El +.Pp +These next two apply only to the third character in the last group +(other permissions). +.Bl -tag -width 4n -offset indent +.It Sy T +The sticky bit is set +(mode +.Li 1000 ) , +but not execute or search permission. +(See +.Xr chmod 1 +or +.Xr sticky 7 . ) +.It Sy t +The sticky bit is set (mode +.Li 1000 ) , +and is searchable or executable. +(See +.Xr chmod 1 +or +.Xr sticky 7 . ) +.El +.El +.Pp +The number of bytes displayed for a directory is a function of the +number of +.Xr dirent 3 +structures in the directory, not all of which may be allocated to +any existing file. +.Sh ENVIRONMENT +The following environment variables affect the execution of +.Nm : +.Bl -tag -width BLOCKSIZE +.It Ev BLOCKSIZE +If the environment variable +.Ev BLOCKSIZE +is set, and the +.Fl k +option is not specified, the block counts +(see +.Fl l +and +.Fl s ) +will be displayed in units of that size block. +.It Ev COLUMNS +If this variable contains a string representing a +decimal integer, it is used as the +column position width for displaying +multiple-text-column output. +The +.Nm +utility calculates how +many pathname text columns to display +based on the width provided. +(See +.Fl C . ) +.It Ev TZ +The timezone to use when displaying dates. +See +.Xr environ 7 +for more information. +.El +.Sh EXIT STATUS +.Ex -std +.Sh COMPATIBILITY +The group field is now automatically included in the long listing for +files in order to be compatible with the +.St -p1003.2 +specification. +.Sh SEE ALSO +.Xr chflags 1 , +.Xr chmod 1 , +.Xr stat 2 , +.Xr dirent 3 , +.Xr getbsize 3 , +.Xr sticky 7 , +.Xr symlink 7 +.Sh STANDARDS +The +.Nm +utility is expected to be a superset of the +.St -p1003.2 +specification. +.Sh HISTORY +An +.Nm +utility appeared in +.At v1 . diff --git a/bin/ls/ls.c b/bin/ls/ls.c new file mode 100644 index 0000000..282207f --- /dev/null +++ b/bin/ls/ls.c @@ -0,0 +1,715 @@ +/* $NetBSD: ls.c,v 1.76 2017/02/06 21:06:04 rin Exp $ */ + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Michael Fischbein. + * + * 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> +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)ls.c 8.7 (Berkeley) 8/5/94"; +#else +__RCSID("$NetBSD: ls.c,v 1.76 2017/02/06 21:06:04 rin Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <fts.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <termios.h> +#include <pwd.h> +#include <grp.h> +#include <util.h> + +#include "ls.h" +#include "extern.h" + +static void display(FTSENT *, FTSENT *); +static int mastercmp(const FTSENT **, const FTSENT **); +static void traverse(int, char **, int); + +static void (*printfcn)(DISPLAY *); +static int (*sortfcn)(const FTSENT *, const FTSENT *); + +#define BY_NAME 0 +#define BY_SIZE 1 +#define BY_TIME 2 + +long blocksize; /* block size units */ +int termwidth = 80; /* default terminal width */ +int sortkey = BY_NAME; +int rval = EXIT_SUCCESS; /* exit value - set if error encountered */ + +/* flags */ +int f_accesstime; /* use time of last access */ +int f_column; /* columnated format */ +int f_columnacross; /* columnated format, sorted across */ +int f_flags; /* show flags associated with a file */ +int f_grouponly; /* long listing without owner */ +int f_humanize; /* humanize the size field */ +int f_commas; /* separate size field with comma */ +int f_inode; /* print inode */ +int f_listdir; /* list actual directory, not contents */ +int f_listdot; /* list files beginning with . */ +int f_longform; /* long listing format */ +int f_nonprint; /* show unprintables as ? */ +int f_nosort; /* don't sort output */ +int f_numericonly; /* don't convert uid/gid to name */ +int f_octal; /* print octal escapes for nongraphic characters */ +int f_octal_escape; /* like f_octal but use C escapes if possible */ +int f_recursive; /* ls subdirectories also */ +int f_reversesort; /* reverse whatever sort is used */ +int f_sectime; /* print the real time for all files */ +int f_singlecol; /* use single column output */ +int f_size; /* list size in short listing */ +int f_statustime; /* use time of last mode change */ +int f_stream; /* stream format */ +int f_type; /* add type character for non-regular files */ +int f_typedir; /* add type character for directories */ +int f_whiteout; /* show whiteout entries */ +int f_fullpath; /* print full pathname, not filename */ +int f_leafonly; /* when recursing, print leaf names only */ + +__dead static void +usage(void) +{ + + (void)fprintf(stderr, + "usage: %s [-1AaBbCcdFfghikLlMmnOoPpqRrSsTtuWwXx] [file ...]\n", + getprogname()); + exit(EXIT_FAILURE); + /* NOTREACHED */ +} + +int +ls_main(int argc, char *argv[]) +{ + static char dot[] = ".", *dotav[] = { dot, NULL }; + struct winsize win; + int ch, fts_options; + int kflag = 0; + const char *p; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + /* Terminal defaults to -Cq, non-terminal defaults to -1. */ + if (isatty(STDOUT_FILENO)) { + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 && + win.ws_col > 0) + termwidth = win.ws_col; + f_column = f_nonprint = 1; + } else + f_singlecol = 1; + + /* Root is -A automatically. */ + if (!getuid()) + f_listdot = 1; + + fts_options = FTS_PHYSICAL; + while ((ch = getopt(argc, argv, "1AaBbCcdFfghikLlMmnOoPpqRrSsTtuWwXx")) + != -1) { + switch (ch) { + /* + * The -1, -C, -l, -m and -x options all override each other so + * shell aliasing works correctly. + */ + case '1': + f_singlecol = 1; + f_column = f_columnacross = f_longform = f_stream = 0; + break; + case 'C': + f_column = 1; + f_columnacross = f_longform = f_singlecol = f_stream = + 0; + break; + case 'g': + if (f_grouponly != -1) + f_grouponly = 1; + f_longform = 1; + f_column = f_columnacross = f_singlecol = f_stream = 0; + break; + case 'l': + f_longform = 1; + f_column = f_columnacross = f_singlecol = f_stream = 0; + /* Never let -g take precedence over -l. */ + f_grouponly = -1; + break; + case 'm': + f_stream = 1; + f_column = f_columnacross = f_longform = f_singlecol = + 0; + break; + case 'x': + f_columnacross = 1; + f_column = f_longform = f_singlecol = f_stream = 0; + break; + /* The -c and -u options override each other. */ + case 'c': + f_statustime = 1; + f_accesstime = 0; + break; + case 'u': + f_accesstime = 1; + f_statustime = 0; + break; + case 'F': + f_type = 1; + break; + case 'L': + fts_options &= ~FTS_PHYSICAL; + fts_options |= FTS_LOGICAL; + break; + case 'R': + f_recursive = 1; + break; + case 'f': + f_nosort = 1; + /* FALLTHROUGH */ + case 'a': + fts_options |= FTS_SEEDOT; + /* FALLTHROUGH */ + case 'A': + f_listdot = 1; + break; + /* The -B option turns off the -b, -q and -w options. */ + case 'B': + f_nonprint = 0; + f_octal = 1; + f_octal_escape = 0; + break; + /* The -b option turns off the -B, -q and -w options. */ + case 'b': + f_nonprint = 0; + f_octal = 0; + f_octal_escape = 1; + break; + /* The -d option turns off the -R option. */ + case 'd': + f_listdir = 1; + f_recursive = 0; + break; + case 'i': + f_inode = 1; + break; + case 'k': + blocksize = 1024; + kflag = 1; + f_humanize = 0; + break; + /* The -h option forces all sizes to be measured in bytes. */ + case 'h': + f_humanize = 1; + kflag = 0; + f_commas = 0; + break; + case 'M': + f_humanize = 0; + f_commas = 1; + break; + case 'n': + f_numericonly = 1; + f_longform = 1; + f_column = f_columnacross = f_singlecol = f_stream = 0; + break; + case 'O': + f_leafonly = 1; + break; + case 'o': + f_flags = 1; + break; + case 'P': + f_fullpath = 1; + break; + case 'p': + f_typedir = 1; + break; + /* The -q option turns off the -B, -b and -w options. */ + case 'q': + f_nonprint = 1; + f_octal = 0; + f_octal_escape = 0; + break; + case 'r': + f_reversesort = 1; + break; + case 'S': + sortkey = BY_SIZE; + break; + case 's': + f_size = 1; + break; + case 'T': + f_sectime = 1; + break; + case 't': + sortkey = BY_TIME; + break; + case 'W': + f_whiteout = 1; + break; + /* The -w option turns off the -B, -b and -q options. */ + case 'w': + f_nonprint = 0; + f_octal = 0; + f_octal_escape = 0; + break; + case 'X': + fts_options |= FTS_XDEV; + break; + default: + case '?': + usage(); + } + } + argc -= optind; + argv += optind; + + if (f_column || f_columnacross || f_stream) { + if ((p = getenv("COLUMNS")) != NULL) + termwidth = atoi(p); + } + + /* + * If both -g and -l options, let -l take precedence. + */ + if (f_grouponly == -1) + f_grouponly = 0; + + /* + * If not -F, -i, -l, -p, -S, -s or -t options, don't require stat + * information. + */ + if (!f_inode && !f_longform && !f_size && !f_type && !f_typedir && + sortkey == BY_NAME) + fts_options |= FTS_NOSTAT; + + /* + * If not -F, -d or -l options, follow any symbolic links listed on + * the command line. + */ + if (!f_longform && !f_listdir && !f_type) + fts_options |= FTS_COMFOLLOW; + + /* + * If -W, show whiteout entries + */ +#ifdef FTS_WHITEOUT + if (f_whiteout) + fts_options |= FTS_WHITEOUT; +#endif + + /* If -i, -l, or -s, figure out block size. */ + if (f_inode || f_longform || f_size) { + if (!kflag) + (void)getbsize(NULL, &blocksize); + blocksize /= 512; + } + + /* Select a sort function. */ + if (f_reversesort) { + switch (sortkey) { + case BY_NAME: + sortfcn = revnamecmp; + break; + case BY_SIZE: + sortfcn = revsizecmp; + break; + case BY_TIME: + if (f_accesstime) + sortfcn = revacccmp; + else if (f_statustime) + sortfcn = revstatcmp; + else /* Use modification time. */ + sortfcn = revmodcmp; + break; + } + } else { + switch (sortkey) { + case BY_NAME: + sortfcn = namecmp; + break; + case BY_SIZE: + sortfcn = sizecmp; + break; + case BY_TIME: + if (f_accesstime) + sortfcn = acccmp; + else if (f_statustime) + sortfcn = statcmp; + else /* Use modification time. */ + sortfcn = modcmp; + break; + } + } + + /* Select a print function. */ + if (f_singlecol) + printfcn = printscol; + else if (f_columnacross) + printfcn = printacol; + else if (f_longform) + printfcn = printlong; + else if (f_stream) + printfcn = printstream; + else + printfcn = printcol; + + if (argc) + traverse(argc, argv, fts_options); + else + traverse(1, dotav, fts_options); + return rval; + /* NOTREACHED */ +} + +static int output; /* If anything output. */ + +/* + * Traverse() walks the logical directory structure specified by the argv list + * in the order specified by the mastercmp() comparison function. During the + * traversal it passes linked lists of structures to display() which represent + * a superset (may be exact set) of the files to be displayed. + */ +static void +traverse(int argc, char *argv[], int options) +{ + FTS *ftsp; + FTSENT *p, *chp; + int ch_options, error; + + if ((ftsp = + fts_open(argv, options, f_nosort ? NULL : mastercmp)) == NULL) + err(EXIT_FAILURE, NULL); + + display(NULL, fts_children(ftsp, 0)); + if (f_listdir) { + (void)fts_close(ftsp); + return; + } + + /* + * If not recursing down this tree and don't need stat info, just get + * the names. + */ + ch_options = !f_recursive && options & FTS_NOSTAT ? FTS_NAMEONLY : 0; + + while ((p = fts_read(ftsp)) != NULL) + switch (p->fts_info) { + case FTS_DC: + warnx("%s: directory causes a cycle", p->fts_name); + break; + case FTS_DNR: + case FTS_ERR: + warnx("%s: %s", p->fts_name, strerror(p->fts_errno)); + rval = EXIT_FAILURE; + break; + case FTS_D: + if (p->fts_level != FTS_ROOTLEVEL && + p->fts_name[0] == '.' && !f_listdot) + break; + + /* + * If already output something, put out a newline as + * a separator. If multiple arguments, precede each + * directory with its name. + */ + if (!f_leafonly) { + if (output) + (void)printf("\n%s:\n", p->fts_path); + else if (argc > 1) { + (void)printf("%s:\n", p->fts_path); + output = 1; + } + } + + chp = fts_children(ftsp, ch_options); + display(p, chp); + + if (!f_recursive && chp != NULL) + (void)fts_set(ftsp, p, FTS_SKIP); + break; + } + error = errno; + (void)fts_close(ftsp); + errno = error; + if (errno) + err(EXIT_FAILURE, "fts_read"); +} + +/* + * Display() takes a linked list of FTSENT structures and passes the list + * along with any other necessary information to the print function. P + * points to the parent directory of the display list. + */ +static void +display(FTSENT *p, FTSENT *list) +{ + struct stat *sp; + DISPLAY d; + FTSENT *cur; + NAMES *np; + u_int64_t btotal, stotal; + off_t maxsize; + blkcnt_t maxblock; + ino_t maxinode; + int maxmajor, maxminor; + uint32_t maxnlink; + int bcfile, entries, flen, glen, ulen, maxflags, maxgroup; + unsigned int maxlen; + int maxuser, needstats; + const char *user, *group; + char buf[21]; /* 64 bits == 20 digits, +1 for NUL */ + char nuser[12], ngroup[12]; + char *flags = NULL; + + /* + * If list is NULL there are two possibilities: that the parent + * directory p has no children, or that fts_children() returned an + * error. We ignore the error case since it will be replicated + * on the next call to fts_read() on the post-order visit to the + * directory p, and will be signalled in traverse(). + */ + if (list == NULL) + return; + + needstats = f_inode || f_longform || f_size; + flen = 0; + maxinode = maxnlink = 0; + bcfile = 0; + maxuser = maxgroup = maxflags = maxlen = 0; + btotal = stotal = maxblock = maxsize = 0; + maxmajor = maxminor = 0; + for (cur = list, entries = 0; cur; cur = cur->fts_link) { + if (cur->fts_info == FTS_ERR || cur->fts_info == FTS_NS) { + warnx("%s: %s", + cur->fts_name, strerror(cur->fts_errno)); + cur->fts_number = NO_PRINT; + rval = EXIT_FAILURE; + continue; + } + + /* + * P is NULL if list is the argv list, to which different rules + * apply. + */ + if (p == NULL) { + /* Directories will be displayed later. */ + if (cur->fts_info == FTS_D && !f_listdir) { + cur->fts_number = NO_PRINT; + continue; + } + } else { + /* Only display dot file if -a/-A set. */ + if (cur->fts_name[0] == '.' && !f_listdot) { + cur->fts_number = NO_PRINT; + continue; + } + } + if (cur->fts_namelen > maxlen) + maxlen = cur->fts_namelen; + if (needstats) { + sp = cur->fts_statp; + if (sp->st_blocks > maxblock) + maxblock = sp->st_blocks; + if (sp->st_ino > maxinode) + maxinode = sp->st_ino; + if (sp->st_nlink > maxnlink) + maxnlink = sp->st_nlink; + if (sp->st_size > maxsize) + maxsize = sp->st_size; + if (S_ISCHR(sp->st_mode) || S_ISBLK(sp->st_mode)) { + bcfile = 1; + if (major(sp->st_rdev) > maxmajor) + maxmajor = major(sp->st_rdev); + if (minor(sp->st_rdev) > maxminor) + maxminor = minor(sp->st_rdev); + } + + btotal += sp->st_blocks; + stotal += sp->st_size; + if (f_longform) { + if (f_numericonly || + (user = user_from_uid(sp->st_uid, 0)) == + NULL) { + (void)snprintf(nuser, sizeof(nuser), + "%u", sp->st_uid); + user = nuser; + } + if (f_numericonly || + (group = group_from_gid(sp->st_gid, 0)) == + NULL) { + (void)snprintf(ngroup, sizeof(ngroup), + "%u", sp->st_gid); + group = ngroup; + } + if ((ulen = strlen(user)) > maxuser) + maxuser = ulen; + if ((glen = strlen(group)) > maxgroup) + maxgroup = glen; + if (f_flags) { + flags = + flags_to_string((u_long)sp->st_flags, "-"); + if ((flen = strlen(flags)) > maxflags) + maxflags = flen; + } else + flen = 0; + + if ((np = malloc(sizeof(NAMES) + + ulen + glen + flen + 2)) == NULL) + err(EXIT_FAILURE, NULL); + + np->user = &np->data[0]; + (void)strcpy(np->user, user); + np->group = &np->data[ulen + 1]; + (void)strcpy(np->group, group); + + if (f_flags) { + np->flags = &np->data[ulen + glen + 2]; + (void)strcpy(np->flags, flags); + free(flags); + } + cur->fts_pointer = np; + } + } + ++entries; + } + + if (!entries) + return; + + d.list = list; + d.entries = entries; + d.maxlen = maxlen; + if (needstats) { + d.btotal = btotal; + d.stotal = stotal; + if (f_humanize) { + d.s_block = 4; /* min buf length for humanize_number */ + } else { + (void)snprintf(buf, sizeof(buf), "%lld", + (long long)howmany(maxblock, blocksize)); + d.s_block = strlen(buf); + if (f_commas) /* allow for commas before every third digit */ + d.s_block += (d.s_block - 1) / 3; + } + d.s_flags = maxflags; + d.s_group = maxgroup; + (void)snprintf(buf, sizeof(buf), "%llu", + (unsigned long long)maxinode); + d.s_inode = strlen(buf); + (void)snprintf(buf, sizeof(buf), "%u", maxnlink); + d.s_nlink = strlen(buf); + if (f_humanize) { + d.s_size = 4; /* min buf length for humanize_number */ + } else { + (void)snprintf(buf, sizeof(buf), "%lld", + (long long)maxsize); + d.s_size = strlen(buf); + if (f_commas) /* allow for commas before every third digit */ + d.s_size += (d.s_size - 1) / 3; + } + d.s_user = maxuser; + if (bcfile) { + (void)snprintf(buf, sizeof(buf), "%d", maxmajor); + d.s_major = strlen(buf); + (void)snprintf(buf, sizeof(buf), "%d", maxminor); + d.s_minor = strlen(buf); + if (d.s_major + d.s_minor + 2 > d.s_size) + d.s_size = d.s_major + d.s_minor + 2; + else if (d.s_size - d.s_minor - 2 > d.s_major) + d.s_major = d.s_size - d.s_minor - 2; + } else { + d.s_major = 0; + d.s_minor = 0; + } + } + + printfcn(&d); + output = 1; + + if (f_longform) + for (cur = list; cur; cur = cur->fts_link) + free(cur->fts_pointer); +} + +/* + * Ordering for mastercmp: + * If ordering the argv (fts_level = FTS_ROOTLEVEL) return non-directories + * as larger than directories. Within either group, use the sort function. + * All other levels use the sort function. Error entries remain unsorted. + */ +static int +mastercmp(const FTSENT **a, const FTSENT **b) +{ + int a_info, b_info; + + a_info = (*a)->fts_info; + if (a_info == FTS_ERR) + return (0); + b_info = (*b)->fts_info; + if (b_info == FTS_ERR) + return (0); + + if (a_info == FTS_NS || b_info == FTS_NS) { + if (b_info != FTS_NS) + return (1); + else if (a_info != FTS_NS) + return (-1); + else + return (namecmp(*a, *b)); + } + + if (a_info != b_info && !f_listdir && + (*a)->fts_level == FTS_ROOTLEVEL) { + if (a_info == FTS_D) + return (1); + else if (b_info == FTS_D) + return (-1); + } + return (sortfcn(*a, *b)); +} diff --git a/bin/ls/ls.h b/bin/ls/ls.h new file mode 100644 index 0000000..d9b6505 --- /dev/null +++ b/bin/ls/ls.h @@ -0,0 +1,81 @@ +/* $NetBSD: ls.h,v 1.19 2014/02/20 18:56:36 christos Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Michael Fischbein. + * + * 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. + * + * @(#)ls.h 8.1 (Berkeley) 5/31/93 + */ + +#define NO_PRINT 1 + +extern long blocksize; /* block size units */ + +extern int f_accesstime; /* use time of last access */ +extern int f_flags; /* show flags associated with a file */ +extern int f_grouponly; /* long listing without owner */ +extern int f_humanize; /* humanize size field */ +extern int f_commas; /* separate size field with commas */ +extern int f_inode; /* print inode */ +extern int f_longform; /* long listing format */ +extern int f_octal; /* print octal escapes for nongraphic characters */ +extern int f_octal_escape; /* like f_octal but use C escapes if possible */ +extern int f_sectime; /* print the real time for all files */ +extern int f_size; /* list size in short listing */ +extern int f_statustime; /* use time of last mode change */ +extern int f_type; /* add type character for non-regular files */ +extern int f_typedir; /* add type character for directories */ +extern int f_nonprint; /* show unprintables as ? */ +extern int f_fullpath; /* print full pathname, not filename */ +extern int f_leafonly; /* when recursing, print leaf names only */ + +typedef struct { + FTSENT *list; + u_int64_t btotal; + u_int64_t stotal; + int entries; + unsigned int maxlen; + int s_block; + int s_flags; + int s_group; + int s_inode; + int s_nlink; + int s_size; + int s_user; + int s_major; + int s_minor; +} DISPLAY; + +typedef struct { + char *user; + char *group; + char *flags; + char data[1]; +} NAMES; diff --git a/bin/ls/main.c b/bin/ls/main.c new file mode 100644 index 0000000..c1316aa --- /dev/null +++ b/bin/ls/main.c @@ -0,0 +1,50 @@ +/* $NetBSD: main.c,v 1.5 2016/09/05 01:00:07 sevan Exp $ */ + +/*- + * Copyright (c) 1999 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Luke Mewburn. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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> + +#ifndef lint +__RCSID("$NetBSD: main.c,v 1.5 2016/09/05 01:00:07 sevan Exp $"); +#endif /* not lint */ + +#include <sys/types.h> +#include <fts.h> + +#include "ls.h" +#include "extern.h" + +int +main(int argc, char *argv[]) +{ + + return ls_main(argc, argv); + /* NOTREACHED */ +} diff --git a/bin/ls/print.c b/bin/ls/print.c new file mode 100644 index 0000000..b3aecd8 --- /dev/null +++ b/bin/ls/print.c @@ -0,0 +1,497 @@ +/* $NetBSD: print.c,v 1.55 2014/05/10 09:39:18 martin Exp $ */ + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Michael Fischbein. + * + * 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> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)print.c 8.5 (Berkeley) 7/28/94"; +#else +__RCSID("$NetBSD: print.c,v 1.55 2014/05/10 09:39:18 martin Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/stat.h> + +#include <err.h> +#include <errno.h> +#include <inttypes.h> +#include <fts.h> +#include <grp.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <tzfile.h> +#include <unistd.h> +#include <util.h> + +#include "ls.h" +#include "extern.h" + +extern int termwidth; + +static int printaname(FTSENT *, int, int); +static void printlink(FTSENT *); +static void printtime(time_t); +static void printtotal(DISPLAY *dp); +static int printtype(u_int); + +static time_t now; + +#define IS_NOPRINT(p) ((p)->fts_number == NO_PRINT) + +static int +safe_printpath(const FTSENT *p) { + int chcnt; + + if (f_fullpath) { + chcnt = safe_print(p->fts_path); + chcnt += safe_print("/"); + } else + chcnt = 0; + return chcnt + safe_print(p->fts_name); +} + +static int +printescapedpath(const FTSENT *p) { + int chcnt; + + if (f_fullpath) { + chcnt = printescaped(p->fts_path); + chcnt += printescaped("/"); + } else + chcnt = 0; + + return chcnt + printescaped(p->fts_name); +} + +static int +printpath(const FTSENT *p) { + if (f_fullpath) + return printf("%s/%s", p->fts_path, p->fts_name); + else + return printf("%s", p->fts_name); +} + +void +printscol(DISPLAY *dp) +{ + FTSENT *p; + + for (p = dp->list; p; p = p->fts_link) { + if (IS_NOPRINT(p)) + continue; + (void)printaname(p, dp->s_inode, dp->s_block); + (void)putchar('\n'); + } +} + +void +printlong(DISPLAY *dp) +{ + struct stat *sp; + FTSENT *p; + NAMES *np; + char buf[20], szbuf[5]; + + now = time(NULL); + + if (!f_leafonly) + printtotal(dp); /* "total: %u\n" */ + + for (p = dp->list; p; p = p->fts_link) { + if (IS_NOPRINT(p)) + continue; + sp = p->fts_statp; + if (f_inode) + (void)printf("%*"PRIu64" ", dp->s_inode, sp->st_ino); + if (f_size) { + if (f_humanize) { + if ((humanize_number(szbuf, sizeof(szbuf), + sp->st_blocks * S_BLKSIZE, + "", HN_AUTOSCALE, + (HN_DECIMAL | HN_B | HN_NOSPACE))) == -1) + err(1, "humanize_number"); + (void)printf("%*s ", dp->s_block, szbuf); + } else { + (void)printf(f_commas ? "%'*llu " : "%*llu ", + dp->s_block, + (unsigned long long)howmany(sp->st_blocks, + blocksize)); + } + } + (void)strmode(sp->st_mode, buf); + np = p->fts_pointer; + (void)printf("%s %*lu ", buf, dp->s_nlink, + (unsigned long)sp->st_nlink); + if (!f_grouponly) + (void)printf("%-*s ", dp->s_user, np->user); + (void)printf("%-*s ", dp->s_group, np->group); + if (f_flags) + (void)printf("%-*s ", dp->s_flags, np->flags); + if (S_ISCHR(sp->st_mode) || S_ISBLK(sp->st_mode)) + (void)printf("%*lld, %*lld ", + dp->s_major, (long long)major(sp->st_rdev), + dp->s_minor, (long long)minor(sp->st_rdev)); + else + if (f_humanize) { + if ((humanize_number(szbuf, sizeof(szbuf), + sp->st_size, "", HN_AUTOSCALE, + (HN_DECIMAL | HN_B | HN_NOSPACE))) == -1) + err(1, "humanize_number"); + (void)printf("%*s ", dp->s_size, szbuf); + } else { + (void)printf(f_commas ? "%'*llu " : "%*llu ", + dp->s_size, (unsigned long long) + sp->st_size); + } + if (f_accesstime) + printtime(sp->st_atime); + else if (f_statustime) + printtime(sp->st_ctime); + else + printtime(sp->st_mtime); + if (f_octal || f_octal_escape) + (void)safe_printpath(p); + else if (f_nonprint) + (void)printescapedpath(p); + else + (void)printpath(p); + + if (f_type || (f_typedir && S_ISDIR(sp->st_mode))) + (void)printtype(sp->st_mode); + if (S_ISLNK(sp->st_mode)) + printlink(p); + (void)putchar('\n'); + } +} + +void +printcol(DISPLAY *dp) +{ + static FTSENT **array; + static int lastentries = -1; + FTSENT *p; + int base, chcnt, col, colwidth, num; + int numcols, numrows, row; + + colwidth = dp->maxlen; + if (f_inode) + colwidth += dp->s_inode + 1; + if (f_size) { + if (f_humanize) + colwidth += dp->s_size + 1; + else + colwidth += dp->s_block + 1; + } + if (f_type || f_typedir) + colwidth += 1; + + colwidth += 1; + + if (termwidth < 2 * colwidth) { + printscol(dp); + return; + } + + /* + * Have to do random access in the linked list -- build a table + * of pointers. + */ + if (dp->entries > lastentries) { + FTSENT **newarray; + + newarray = realloc(array, dp->entries * sizeof(FTSENT *)); + if (newarray == NULL) { + warn(NULL); + printscol(dp); + return; + } + lastentries = dp->entries; + array = newarray; + } + for (p = dp->list, num = 0; p; p = p->fts_link) + if (p->fts_number != NO_PRINT) + array[num++] = p; + + numcols = termwidth / colwidth; + colwidth = termwidth / numcols; /* spread out if possible */ + numrows = num / numcols; + if (num % numcols) + ++numrows; + + printtotal(dp); /* "total: %u\n" */ + + for (row = 0; row < numrows; ++row) { + for (base = row, chcnt = col = 0; col < numcols; ++col) { + chcnt = printaname(array[base], dp->s_inode, + f_humanize ? dp->s_size : dp->s_block); + if ((base += numrows) >= num) + break; + while (chcnt++ < colwidth) + (void)putchar(' '); + } + (void)putchar('\n'); + } +} + +void +printacol(DISPLAY *dp) +{ + FTSENT *p; + int chcnt, col, colwidth; + int numcols; + + colwidth = dp->maxlen; + if (f_inode) + colwidth += dp->s_inode + 1; + if (f_size) { + if (f_humanize) + colwidth += dp->s_size + 1; + else + colwidth += dp->s_block + 1; + } + if (f_type || f_typedir) + colwidth += 1; + + colwidth += 1; + + if (termwidth < 2 * colwidth) { + printscol(dp); + return; + } + + numcols = termwidth / colwidth; + colwidth = termwidth / numcols; /* spread out if possible */ + + printtotal(dp); /* "total: %u\n" */ + + chcnt = col = 0; + for (p = dp->list; p; p = p->fts_link) { + if (IS_NOPRINT(p)) + continue; + if (col >= numcols) { + chcnt = col = 0; + (void)putchar('\n'); + } + chcnt = printaname(p, dp->s_inode, + f_humanize ? dp->s_size : dp->s_block); + while (chcnt++ < colwidth) + (void)putchar(' '); + col++; + } + (void)putchar('\n'); +} + +void +printstream(DISPLAY *dp) +{ + FTSENT *p; + int col; + int extwidth; + + extwidth = 0; + if (f_inode) + extwidth += dp->s_inode + 1; + if (f_size) { + if (f_humanize) + extwidth += dp->s_size + 1; + else + extwidth += dp->s_block + 1; + } + if (f_type) + extwidth += 1; + + for (col = 0, p = dp->list; p != NULL; p = p->fts_link) { + if (IS_NOPRINT(p)) + continue; + if (col > 0) { + (void)putchar(','), col++; + if (col + 1 + extwidth + (int)p->fts_namelen >= termwidth) + (void)putchar('\n'), col = 0; + else + (void)putchar(' '), col++; + } + col += printaname(p, dp->s_inode, + f_humanize ? dp->s_size : dp->s_block); + } + (void)putchar('\n'); +} + +/* + * print [inode] [size] name + * return # of characters printed, no trailing characters. + */ +static int +printaname(FTSENT *p, int inodefield, int sizefield) +{ + struct stat *sp; + int chcnt; + char szbuf[5]; + + sp = p->fts_statp; + chcnt = 0; + if (f_inode) + chcnt += printf("%*"PRIu64" ", inodefield, sp->st_ino); + if (f_size) { + if (f_humanize) { + if ((humanize_number(szbuf, sizeof(szbuf), sp->st_size, + "", HN_AUTOSCALE, + (HN_DECIMAL | HN_B | HN_NOSPACE))) == -1) + err(1, "humanize_number"); + chcnt += printf("%*s ", sizefield, szbuf); + } else { + chcnt += printf(f_commas ? "%'*llu " : "%*llu ", + sizefield, (unsigned long long) + howmany(sp->st_blocks, blocksize)); + } + } + if (f_octal || f_octal_escape) + chcnt += safe_printpath(p); + else if (f_nonprint) + chcnt += printescapedpath(p); + else + chcnt += printpath(p); + if (f_type || (f_typedir && S_ISDIR(sp->st_mode))) + chcnt += printtype(sp->st_mode); + return (chcnt); +} + +static void +printtime(time_t ftime) +{ + int i; + const char *longstring; + + if ((longstring = ctime(&ftime)) == NULL) { + /* 012345678901234567890123 */ + longstring = "????????????????????????"; + } + for (i = 4; i < 11; ++i) + (void)putchar(longstring[i]); + +#define SIXMONTHS ((DAYSPERNYEAR / 2) * SECSPERDAY) + if (f_sectime) + for (i = 11; i < 24; i++) + (void)putchar(longstring[i]); + else if (ftime + SIXMONTHS > now && ftime - SIXMONTHS < now) + for (i = 11; i < 16; ++i) + (void)putchar(longstring[i]); + else { + (void)putchar(' '); + for (i = 20; i < 24; ++i) + (void)putchar(longstring[i]); + } + (void)putchar(' '); +} + +/* + * Display total used disk space in the form "total: %u\n". + * Note: POSIX (IEEE Std 1003.1-2001) says this should be always in 512 blocks, + * but we humanise it with -h, or separate it with commas with -M, and use 1024 + * with -k. + */ +static void +printtotal(DISPLAY *dp) +{ + char szbuf[5]; + + if (dp->list->fts_level != FTS_ROOTLEVEL && (f_longform || f_size)) { + if (f_humanize) { + if ((humanize_number(szbuf, sizeof(szbuf), (int64_t)dp->stotal, + "", HN_AUTOSCALE, + (HN_DECIMAL | HN_B | HN_NOSPACE))) == -1) + err(1, "humanize_number"); + (void)printf("total %s\n", szbuf); + } else { + (void)printf(f_commas ? "total %'llu\n" : + "total %llu\n", (unsigned long long) + howmany(dp->btotal, blocksize)); + } + } +} + +static int +printtype(u_int mode) +{ + switch (mode & S_IFMT) { + case S_IFDIR: + (void)putchar('/'); + return (1); + case S_IFIFO: + (void)putchar('|'); + return (1); + case S_IFLNK: + (void)putchar('@'); + return (1); + case S_IFSOCK: + (void)putchar('='); + return (1); + case S_IFWHT: + (void)putchar('%'); + return (1); + } + if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { + (void)putchar('*'); + return (1); + } + return (0); +} + +static void +printlink(FTSENT *p) +{ + int lnklen; + char name[MAXPATHLEN + 1], path[MAXPATHLEN + 1]; + + if (p->fts_level == FTS_ROOTLEVEL) + (void)snprintf(name, sizeof(name), "%s", p->fts_name); + else + (void)snprintf(name, sizeof(name), + "%s/%s", p->fts_parent->fts_accpath, p->fts_name); + if ((lnklen = readlink(name, path, sizeof(path) - 1)) == -1) { + (void)fprintf(stderr, "\nls: %s: %s\n", name, strerror(errno)); + return; + } + path[lnklen] = '\0'; + (void)printf(" -> "); + if (f_octal || f_octal_escape) + (void)safe_print(path); + else if (f_nonprint) + (void)printescaped(path); + else + (void)printf("%s", path); +} diff --git a/bin/ls/util.c b/bin/ls/util.c new file mode 100644 index 0000000..61b0fda --- /dev/null +++ b/bin/ls/util.c @@ -0,0 +1,168 @@ +/* $NetBSD: util.c,v 1.34 2011/08/29 14:44:21 joerg Exp $ */ + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Michael Fischbein. + * + * 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> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)util.c 8.5 (Berkeley) 4/28/95"; +#else +__RCSID("$NetBSD: util.c,v 1.34 2011/08/29 14:44:21 joerg Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <err.h> +#include <fts.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <vis.h> +#include <wchar.h> +#include <wctype.h> + +#include "ls.h" +#include "extern.h" + +int +safe_print(const char *src) +{ + size_t len; + char *name; + int flags; + + flags = VIS_NL | VIS_OCTAL | VIS_WHITE; + if (f_octal_escape) + flags |= VIS_CSTYLE; + + len = strlen(src); + if (len != 0 && SIZE_T_MAX/len <= 4) { + errx(EXIT_FAILURE, "%s: name too long", src); + /* NOTREACHED */ + } + + name = (char *)malloc(4*len+1); + if (name != NULL) { + len = strvis(name, src, flags); + (void)printf("%s", name); + free(name); + return len; + } else + errx(EXIT_FAILURE, "out of memory!"); + /* NOTREACHED */ +} + +/* + * The reasons why we don't use putwchar(wc) here are: + * - If wc == L'\0', we need to restore the initial shift state, but + * the C language standard doesn't say that putwchar(L'\0') does. + * - It isn't portable to mix a wide-oriented function (i.e. getwchar) + * with byte-oriented functions (printf et al.) in same FILE. + */ +static int +printwc(wchar_t wc, mbstate_t *pst) +{ + size_t size; + char buf[MB_LEN_MAX]; + + size = wcrtomb(buf, wc, pst); + if (size == (size_t)-1) /* This shouldn't happen, but for sure */ + return 0; + if (wc == L'\0') { + /* The following condition must be always true, but for sure */ + if (size > 0 && buf[size - 1] == '\0') + --size; + } + if (size > 0) + fwrite(buf, 1, size, stdout); + return wc == L'\0' ? 0 : wcwidth(wc); +} + +int +printescaped(const char *src) +{ + int n = 0; + mbstate_t src_state, stdout_state; + /* The following +1 is to pass '\0' at the end of src to mbrtowc(). */ + const char *endptr = src + strlen(src) + 1; + + /* + * We have to reset src_state each time in this function, because + * the codeset of src pathname may not match with current locale. + * Note that if we pass NULL instead of src_state to mbrtowc(), + * there is no way to reset the state. + */ + memset(&src_state, 0, sizeof(src_state)); + memset(&stdout_state, 0, sizeof(stdout_state)); + while (src < endptr) { + wchar_t wc; + size_t rv, span = endptr - src; + +#if 0 + /* + * XXX - we should fix libc instead. + * Theoretically this should work, but our current + * implementation of iso2022 module doesn't actually work + * as expected, if there are redundant escape sequences + * which exceed 32 bytes. + */ + if (span > MB_CUR_MAX) + span = MB_CUR_MAX; +#endif + rv = mbrtowc(&wc, src, span, &src_state); + if (rv == 0) { /* assert(wc == L'\0'); */ + /* The following may output a shift sequence. */ + n += printwc(wc, &stdout_state); + break; + } + if (rv == (size_t)-1) { /* probably errno == EILSEQ */ + n += printwc(L'?', &stdout_state); + /* try to skip 1byte, because there is no better way */ + src++; + memset(&src_state, 0, sizeof(src_state)); + } else if (rv == (size_t)-2) { + if (span < MB_CUR_MAX) { /* incomplete char */ + n += printwc(L'?', &stdout_state); + break; + } + src += span; /* a redundant shift sequence? */ + } else { + n += printwc(iswprint(wc) ? wc : L'?', &stdout_state); + src += rv; + } + } + return n; +} diff --git a/bin/mkdir/mkdir.1 b/bin/mkdir/mkdir.1 new file mode 100644 index 0000000..ca797ff --- /dev/null +++ b/bin/mkdir/mkdir.1 @@ -0,0 +1,97 @@ +.\" $NetBSD: mkdir.1,v 1.21 2017/07/04 06:49:35 wiz Exp $ +.\" +.\" 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. +.\" +.\" @(#)mkdir.1 8.2 (Berkeley) 1/25/94 +.\" +.Dd August 10, 2016 +.Dt MKDIR 1 +.Os +.Sh NAME +.Nm mkdir +.Nd make directories +.Sh SYNOPSIS +.Nm +.Op Fl p +.Op Fl m Ar mode +.Ar directory_name ... +.Sh DESCRIPTION +.Nm +creates the directories named as operands, in the order specified, +using mode +.Li rwxrwxrwx (\&0777) +as modified by the current +.Xr umask 2 . +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl m +Set the file permission bits of the final created directory to +the specified mode. +The mode argument can be in any of the formats specified to the +.Xr chmod 1 +utility. +If a symbolic mode is specified, the operation characters +.Dq + +and +.Dq - +are interpreted relative to an initial mode of +.Dq a=rwx . +.It Fl p +Create intermediate directories as required. +If this option is not specified, the full path prefix of each +operand must already exist. +Intermediate directories are created with permission bits of +.Li rwxrwxrwx (\&0777) +as modified by the current umask, plus write and search +permission for the owner. +Do not consider it an error if the argument directory already exists. +.El +.Pp +The user must have write permission in the parent directory. +.Sh EXIT STATUS +.Ex -std mkdir +.Sh SEE ALSO +.Xr chmod 1 , +.Xr rmdir 1 , +.Xr mkdir 2 , +.Xr umask 2 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. +.Sh HISTORY +A +.Nm +utility appeared in +.At v1 . diff --git a/bin/mkdir/mkdir.c b/bin/mkdir/mkdir.c new file mode 100644 index 0000000..cb60b88 --- /dev/null +++ b/bin/mkdir/mkdir.c @@ -0,0 +1,223 @@ +/* $NetBSD: mkdir.c,v 1.38 2011/08/29 14:45:28 joerg Exp $ */ + +/* + * Copyright (c) 1983, 1992, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1983, 1992, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mkdir.c 8.2 (Berkeley) 1/25/94"; +#else +__RCSID("$NetBSD: mkdir.c,v 1.38 2011/08/29 14:45:28 joerg Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <err.h> +#include <errno.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static int mkpath(char *, mode_t, mode_t); +__dead static void usage(void); + +int +main(int argc, char *argv[]) +{ + int ch, exitval, pflag; + void *set; + mode_t mode, dir_mode; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + /* + * The default file mode is a=rwx (0777) with selected permissions + * removed in accordance with the file mode creation mask. For + * intermediate path name components, the mode is the default modified + * by u+wx so that the subdirectories can always be created. + */ + mode = (S_IRWXU | S_IRWXG | S_IRWXO) & ~umask(0); + dir_mode = mode | S_IWUSR | S_IXUSR; + + pflag = 0; + while ((ch = getopt(argc, argv, "m:p")) != -1) + switch (ch) { + case 'p': + pflag = 1; + break; + case 'm': + if ((set = setmode(optarg)) == NULL) { + err(EXIT_FAILURE, "Cannot set file mode `%s'", + optarg); + /* NOTREACHED */ + } + mode = getmode(set, S_IRWXU | S_IRWXG | S_IRWXO); + free(set); + break; + case '?': + default: + usage(); + /* NOTREACHED */ + } + argc -= optind; + argv += optind; + + if (*argv == NULL) { + usage(); + /* NOTREACHED */ + } + + for (exitval = EXIT_SUCCESS; *argv != NULL; ++argv) { +#ifdef notdef + char *slash; + + /* Kernel takes care of this */ + /* Remove trailing slashes, per POSIX. */ + slash = strrchr(*argv, '\0'); + while (--slash > *argv && *slash == '/') + *slash = '\0'; +#endif + + if (pflag) { + if (mkpath(*argv, mode, dir_mode) < 0) + exitval = EXIT_FAILURE; + } else { + if (mkdir(*argv, mode) < 0) { + warn("%s", *argv); + exitval = EXIT_FAILURE; + } else { + /* + * The mkdir() and umask() calls both honor + * only the file permission bits, so if you try + * to set a mode including the sticky, setuid, + * setgid bits you lose them. So chmod(). + */ + if ((mode & ~(S_IRWXU|S_IRWXG|S_IRWXO)) != 0 && + chmod(*argv, mode) == -1) { + warn("%s", *argv); + exitval = EXIT_FAILURE; + } + } + } + } + exit(exitval); + /* NOTREACHED */ +} + +/* + * mkpath -- create directories. + * path - path + * mode - file mode of terminal directory + * dir_mode - file mode of intermediate directories + */ +static int +mkpath(char *path, mode_t mode, mode_t dir_mode) +{ + struct stat sb; + char *slash; + int done, rv; + + done = 0; + slash = path; + + for (;;) { + slash += strspn(slash, "/"); + slash += strcspn(slash, "/"); + + done = (*slash == '\0'); + *slash = '\0'; + + rv = mkdir(path, done ? mode : dir_mode); + if (rv < 0) { + /* + * Can't create; path exists or no perms. + * stat() path to determine what's there now. + */ + int sverrno; + + sverrno = errno; + if (stat(path, &sb) < 0) { + /* Not there; use mkdir()s error */ + errno = sverrno; + warn("%s", path); + return -1; + } + if (!S_ISDIR(sb.st_mode)) { + /* Is there, but isn't a directory */ + errno = ENOTDIR; + warn("%s", path); + return -1; + } + } else if (done) { + /* + * Created ok, and this is the last element + */ + /* + * The mkdir() and umask() calls both honor only the + * file permission bits, so if you try to set a mode + * including the sticky, setuid, setgid bits you lose + * them. So chmod(). + */ + if ((mode & ~(S_IRWXU|S_IRWXG|S_IRWXO)) != 0 && + chmod(path, mode) == -1) { + warn("%s", path); + return -1; + } + } + + if (done) { + break; + } + *slash = '/'; + } + + return 0; +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "usage: %s [-p] [-m mode] dirname ...\n", + getprogname()); + exit(EXIT_FAILURE); + /* NOTREACHED */ +} diff --git a/bin/mv/mv.1 b/bin/mv/mv.1 new file mode 100644 index 0000000..7cc3d1c --- /dev/null +++ b/bin/mv/mv.1 @@ -0,0 +1,149 @@ +.\" $NetBSD: mv.1,v 1.30 2017/07/04 06:50:04 wiz Exp $ +.\" +.\" 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. +.\" +.\" @(#)mv.1 8.1 (Berkeley) 5/31/93 +.\" +.Dd August 10, 2016 +.Dt MV 1 +.Os +.Sh NAME +.Nm mv +.Nd move files +.Sh SYNOPSIS +.Nm +.Op Fl fiv +.Ar source target +.Nm +.Op Fl fiv +.Ar source ... directory +.Sh DESCRIPTION +In its first form, the +.Nm +utility renames the file named by the +.Ar source +operand to the destination path named by the +.Ar target +operand. +This form is assumed when the last operand does not name an already +existing directory. +.Pp +In its second form, +.Nm +moves each file named by a +.Ar source +operand to a destination file in the existing directory named by the +.Ar directory +operand. +The destination path for each operand is the pathname produced by the +concatenation of the last operand, a slash, and the final pathname +component of the named file. +.Pp +The following options are available: +.Bl -tag -width flag +.It Fl f +Do not prompt for confirmation before overwriting the destination +path. +.It Fl i +Causes +.Nm +to write a prompt to standard error before moving a file that would +overwrite an existing file. +If the response from the standard input begins with the character ``y'', +the move is attempted. +.It Fl v +Cause +.Nm +to be verbose, showing files as they are processed. +.El +.Pp +The last of any +.Fl f +or +.Fl i +options is the one which affects +.Nm Ns 's +behavior. +.Pp +It is an error for any of the +.Ar source +operands to specify a nonexistent file or directory. +.Pp +It is an error for the +.Ar source +operand to specify a directory if the +.Ar target +exists and is not a directory. +.Pp +If the destination path does not have a mode which permits writing, +.Nm +prompts the user for confirmation as specified for the +.Fl i +option. +.Pp +Should the +.Xr rename 2 +call fail because +.Ar source +and +.Ar target +are on different file systems, +.Nm +will remove the destination file, copy the source file to the +destination, and then remove the source. +The effect is roughly equivalent to: +.Bd -literal -offset indent +rm -f destination_path && \e +cp -PRp source_file destination_path && \e +rm -rf source_file +.Ed +.Sh EXIT STATUS +.Ex -std mv +.Sh SEE ALSO +.Xr cp 1 , +.Xr rename 2 , +.Xr symlink 7 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. +.Pp +The +.Fl v +option is an extension to +.St -p1003.2 . +.Sh HISTORY +An +.Nm +utility appeared in +.At v1 . diff --git a/bin/mv/mv.c b/bin/mv/mv.c new file mode 100644 index 0000000..e318d48 --- /dev/null +++ b/bin/mv/mv.c @@ -0,0 +1,427 @@ +/* $NetBSD: mv.c,v 1.45 2016/02/28 10:59:29 mrg Exp $ */ + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Ken Smith of The State University of New York at Buffalo. + * + * 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> +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mv.c 8.2 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: mv.c,v 1.45 2016/02/28 10:59:29 mrg Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/extattr.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <grp.h> +#include <locale.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "pathnames.h" + +static int fflg, iflg, vflg; +static int stdin_ok; +static sig_atomic_t pinfo; + +static int copy(char *, char *); +static int do_move(char *, char *); +static int fastcopy(char *, char *, struct stat *); +__dead static void usage(void); + +static void +progress(int sig __unused) +{ + + pinfo++; +} + +int +main(int argc, char *argv[]) +{ + int ch, len, rval; + char *p, *endp; + struct stat sb; + char path[MAXPATHLEN + 1]; + size_t baselen; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + while ((ch = getopt(argc, argv, "ifv")) != -1) + switch (ch) { + case 'i': + fflg = 0; + iflg = 1; + break; + case 'f': + iflg = 0; + fflg = 1; + break; + case 'v': + vflg = 1; + break; + default: + usage(); + } + argc -= optind; + argv += optind; + + if (argc < 2) + usage(); + + stdin_ok = isatty(STDIN_FILENO); + + (void)signal(SIGINFO, progress); + + /* + * If the stat on the target fails or the target isn't a directory, + * try the move. More than 2 arguments is an error in this case. + */ + if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) { + if (argc > 2) + usage(); + exit(do_move(argv[0], argv[1])); + } + + /* It's a directory, move each file into it. */ + baselen = strlcpy(path, argv[argc - 1], sizeof(path)); + if (baselen >= sizeof(path)) + errx(1, "%s: destination pathname too long", argv[argc - 1]); + endp = &path[baselen]; + if (!baselen || *(endp - 1) != '/') { + *endp++ = '/'; + ++baselen; + } + for (rval = 0; --argc; ++argv) { + p = *argv + strlen(*argv) - 1; + while (*p == '/' && p != *argv) + *p-- = '\0'; + if ((p = strrchr(*argv, '/')) == NULL) + p = *argv; + else + ++p; + + if ((baselen + (len = strlen(p))) >= MAXPATHLEN) { + warnx("%s: destination pathname too long", *argv); + rval = 1; + } else { + memmove(endp, p, len + 1); + if (do_move(*argv, path)) + rval = 1; + } + } + exit(rval); + /* NOTREACHED */ +} + +static int +do_move(char *from, char *to) +{ + struct stat sb; + char modep[15]; + + /* + * (1) If the destination path exists, the -f option is not specified + * and either of the following conditions are true: + * + * (a) The permissions of the destination path do not permit + * writing and the standard input is a terminal. + * (b) The -i option is specified. + * + * the mv utility shall write a prompt to standard error and + * read a line from standard input. If the response is not + * affirmative, mv shall do nothing more with the current + * source file... + */ + if (!fflg && !access(to, F_OK)) { + int ask = 1; + int ch; + + if (iflg) { + if (access(from, F_OK)) { + warn("rename %s", from); + return (1); + } + (void)fprintf(stderr, "overwrite %s? ", to); + } else if (stdin_ok && access(to, W_OK) && !stat(to, &sb)) { + if (access(from, F_OK)) { + warn("rename %s", from); + return (1); + } + strmode(sb.st_mode, modep); + (void)fprintf(stderr, "override %s%s%s/%s for %s? ", + modep + 1, modep[9] == ' ' ? "" : " ", + user_from_uid(sb.st_uid, 0), + group_from_gid(sb.st_gid, 0), to); + } else + ask = 0; + if (ask) { + if ((ch = getchar()) != EOF && ch != '\n') { + int ch2; + while ((ch2 = getchar()) != EOF && ch2 != '\n') + continue; + } + if (ch != 'y' && ch != 'Y') + return (0); + } + } + + /* + * (2) If rename() succeeds, mv shall do nothing more with the + * current source file. If it fails for any other reason than + * EXDEV, mv shall write a diagnostic message to the standard + * error and do nothing more with the current source file. + * + * (3) If the destination path exists, and it is a file of type + * directory and source_file is not a file of type directory, + * or it is a file not of type directory, and source file is + * a file of type directory, mv shall write a diagnostic + * message to standard error, and do nothing more with the + * current source file... + */ + if (!rename(from, to)) { + if (vflg) + printf("%s -> %s\n", from, to); + return (0); + } + + if (errno != EXDEV) { + warn("rename %s to %s", from, to); + return (1); + } + + /* + * (4) If the destination path exists, mv shall attempt to remove it. + * If this fails for any reason, mv shall write a diagnostic + * message to the standard error and do nothing more with the + * current source file... + */ + if (!lstat(to, &sb)) { + if ((S_ISDIR(sb.st_mode)) ? rmdir(to) : unlink(to)) { + warn("can't remove %s", to); + return (1); + } + } + + /* + * (5) The file hierarchy rooted in source_file shall be duplicated + * as a file hierarchy rooted in the destination path... + */ + if (lstat(from, &sb)) { + warn("%s", from); + return (1); + } + + return (S_ISREG(sb.st_mode) ? + fastcopy(from, to, &sb) : copy(from, to)); +} + +static int +fastcopy(char *from, char *to, struct stat *sbp) +{ +#if defined(__NetBSD__) + struct timespec ts[2]; +#else + struct timeval tval[2]; +#endif + static blksize_t blen; + static char *bp; + int from_fd, to_fd; + ssize_t nread; + off_t total = 0; + + if ((from_fd = open(from, O_RDONLY, 0)) < 0) { + warn("%s", from); + return (1); + } + if ((to_fd = + open(to, O_CREAT | O_TRUNC | O_WRONLY, sbp->st_mode)) < 0) { + warn("%s", to); + (void)close(from_fd); + return (1); + } + if (!blen && !(bp = malloc(blen = sbp->st_blksize))) { + warn(NULL); + blen = 0; + (void)close(from_fd); + (void)close(to_fd); + return (1); + } + while ((nread = read(from_fd, bp, blen)) > 0) { + if (write(to_fd, bp, nread) != nread) { + warn("%s", to); + goto err; + } + total += nread; + if (pinfo) { + int pcent = (int)((100.0 * total) / sbp->st_size); + + pinfo = 0; + (void)fprintf(stderr, "%s => %s %llu/%llu bytes %d%% " + "written\n", from, to, (unsigned long long)total, + (unsigned long long)sbp->st_size, pcent); + } + } + if (nread < 0) { + warn("%s", from); +err: if (unlink(to)) + warn("%s: remove", to); + (void)close(from_fd); + (void)close(to_fd); + return (1); + } + + if (fcpxattr(from_fd, to_fd) == -1) + warn("%s: error copying extended attributes", to); + + (void)close(from_fd); +#ifdef BSD4_4 +#if defined(__NetBSD__) + ts[0] = sbp->st_atimespec; + ts[1] = sbp->st_mtimespec; +#else + TIMESPEC_TO_TIMEVAL(&tval[0], &sbp->st_atimespec); + TIMESPEC_TO_TIMEVAL(&tval[1], &sbp->st_mtimespec); +#endif +#else + tval[0].tv_sec = sbp->st_atime; + tval[1].tv_sec = sbp->st_mtime; + tval[0].tv_usec = 0; + tval[1].tv_usec = 0; +#endif +#ifdef __SVR4 + if (utimes(to, tval)) +#else +#if defined(__NetBSD__) + if (futimens(to_fd, ts)) +#else + if (futimes(to_fd, tval)) +#endif +#endif + warn("%s: set times", to); + if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) { + if (errno != EPERM) + warn("%s: set owner/group", to); + sbp->st_mode &= ~(S_ISUID | S_ISGID); + } + if (fchmod(to_fd, sbp->st_mode)) + warn("%s: set mode", to); + if (fchflags(to_fd, sbp->st_flags) && (errno != EOPNOTSUPP)) + warn("%s: set flags (was: 0%07o)", to, sbp->st_flags); + + if (close(to_fd)) { + warn("%s", to); + return (1); + } + + if (unlink(from)) { + warn("%s: remove", from); + return (1); + } + + if (vflg) + printf("%s -> %s\n", from, to); + + return (0); +} + +static int +copy(char *from, char *to) +{ + pid_t pid; + int status; + + if ((pid = vfork()) == 0) { + execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", "--", from, to, NULL); + warn("%s", _PATH_CP); + _exit(1); + } + if (waitpid(pid, &status, 0) == -1) { + warn("%s: waitpid", _PATH_CP); + return (1); + } + if (!WIFEXITED(status)) { + warnx("%s: did not terminate normally", _PATH_CP); + return (1); + } + if (WEXITSTATUS(status)) { + warnx("%s: terminated with %d (non-zero) status", + _PATH_CP, WEXITSTATUS(status)); + return (1); + } + if (!(pid = vfork())) { + execl(_PATH_RM, "mv", "-rf", "--", from, NULL); + warn("%s", _PATH_RM); + _exit(1); + } + if (waitpid(pid, &status, 0) == -1) { + warn("%s: waitpid", _PATH_RM); + return (1); + } + if (!WIFEXITED(status)) { + warnx("%s: did not terminate normally", _PATH_RM); + return (1); + } + if (WEXITSTATUS(status)) { + warnx("%s: terminated with %d (non-zero) status", + _PATH_RM, WEXITSTATUS(status)); + return (1); + } + return (0); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: %s [-fiv] source target\n" + " %s [-fiv] source ... directory\n", getprogname(), + getprogname()); + exit(1); + /* NOTREACHED */ +} diff --git a/bin/mv/pathnames.h b/bin/mv/pathnames.h new file mode 100644 index 0000000..a38e4e9 --- /dev/null +++ b/bin/mv/pathnames.h @@ -0,0 +1,40 @@ +/* $NetBSD: pathnames.h,v 1.8 2004/08/19 22:26:07 christos Exp $ */ + +/* + * 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. + * + * @(#)pathnames.h 8.1 (Berkeley) 5/31/93 + */ + +#ifdef RESCUEDIR +#define _PATH_RM RESCUEDIR "/rm" +#define _PATH_CP RESCUEDIR "/cp" +#else +#define _PATH_RM "/bin/rm" +#define _PATH_CP "/bin/cp" +#endif diff --git a/bin/pax/ar_io.c b/bin/pax/ar_io.c new file mode 100644 index 0000000..d839fa0 --- /dev/null +++ b/bin/pax/ar_io.c @@ -0,0 +1,1725 @@ +/* $NetBSD: ar_io.c,v 1.58 2017/10/02 21:57:59 joerg Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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 HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +#if 0 +static char sccsid[] = "@(#)ar_io.c 8.2 (Berkeley) 4/18/94"; +#else +__RCSID("$NetBSD: ar_io.c,v 1.58 2017/10/02 21:57:59 joerg Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/ioctl.h> +#ifdef HAVE_SYS_MTIO_H +#include <sys/mtio.h> +#endif +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#ifdef SUPPORT_RMT +#define __RMTLIB_PRIVATE +#include <rmt.h> +#endif /* SUPPORT_RMT */ +#include "pax.h" +#include "options.h" +#include "extern.h" + +/* + * Routines which deal directly with the archive I/O device/file. + */ + +#define DMOD 0666 /* default mode of created archives */ +#define EXT_MODE O_RDONLY /* open mode for list/extract */ +#define AR_MODE (O_WRONLY | O_CREAT | O_TRUNC) /* mode for archive */ +#define APP_MODE O_RDWR /* mode for append */ +static char STDO[] = "<STDOUT>"; /* pseudo name for stdout */ +static char STDN[] = "<STDIN>"; /* pseudo name for stdin */ +static char NONE[] = "<NONE>"; /* pseudo name for none */ +static int arfd = -1; /* archive file descriptor */ +static int artyp = ISREG; /* archive type: file/FIFO/tape */ +static int arvol = 1; /* archive volume number */ +static int lstrval = -1; /* return value from last i/o */ +static int io_ok; /* i/o worked on volume after resync */ +static int did_io; /* did i/o ever occur on volume? */ +static int done; /* set via tty termination */ +static struct stat arsb; /* stat of archive device at open */ +static int invld_rec; /* tape has out of spec record size */ +static int wr_trail = 1; /* trailer was rewritten in append */ +static int can_unlnk = 0; /* do we unlink null archives? */ +const char *arcname; /* printable name of archive */ +const char *gzip_program; /* name of gzip program */ +static pid_t zpid = -1; /* pid of child process */ +time_t starttime; /* time the run started */ +int force_one_volume; /* 1 if we ignore volume changes */ + +#ifdef HAVE_SYS_MTIO_H +static int get_phys(void); +#endif +extern sigset_t s_mask; +static void ar_start_gzip(int, const char *, int); +static const char *timefmt(char *, size_t, off_t, time_t, const char *); +static const char *sizefmt(char *, size_t, off_t); + +#ifdef SUPPORT_RMT +#ifdef SYS_NO_RESTART +static int rmtread_with_restart(int, void *, int); +static int rmtwrite_with_restart(int, void *, int); +#else +#define rmtread_with_restart(a, b, c) rmtread((a), (b), (c)) +#define rmtwrite_with_restart(a, b, c) rmtwrite((a), (b), (c)) +#endif +#endif /* SUPPORT_RMT */ + +/* + * ar_open() + * Opens the next archive volume. Determines the type of the device and + * sets up block sizes as required by the archive device and the format. + * Note: we may be called with name == NULL on the first open only. + * Return: + * -1 on failure, 0 otherwise + */ + +int +ar_open(const char *name) +{ +#ifdef HAVE_SYS_MTIO_H + struct mtget mb; +#endif + + if (arfd != -1) + (void)close(arfd); + arfd = -1; + can_unlnk = did_io = io_ok = invld_rec = 0; + artyp = ISREG; + flcnt = 0; + +#ifdef SUPPORT_RMT + if (name && strchr(name, ':') != NULL && !forcelocal) { + artyp = ISRMT; + if ((arfd = rmtopen(name, O_RDWR, DMOD)) == -1) { + syswarn(0, errno, "Failed open on %s", name); + return -1; + } + if (!isrmt(arfd)) { + rmtclose(arfd); + tty_warn(0, "Not a remote file: %s", name); + return -1; + } + blksz = rdblksz = 8192; + lstrval = 1; + return 0; + } +#endif /* SUPPORT_RMT */ + + /* + * open based on overall operation mode + */ + switch (act) { + case LIST: + case EXTRACT: + if (name == NULL) { + arfd = STDIN_FILENO; + arcname = STDN; + } else if ((arfd = open(name, EXT_MODE, DMOD)) < 0) + syswarn(0, errno, "Failed open to read on %s", name); + if (arfd != -1 && gzip_program != NULL) + ar_start_gzip(arfd, gzip_program, 0); + break; + case ARCHIVE: + if (name == NULL) { + arfd = STDOUT_FILENO; + arcname = STDO; + } else if ((arfd = open(name, AR_MODE, DMOD)) < 0) + syswarn(0, errno, "Failed open to write on %s", name); + else + can_unlnk = 1; + if (arfd != -1 && gzip_program != NULL) + ar_start_gzip(arfd, gzip_program, 1); + break; + case APPND: + if (name == NULL) { + arfd = STDOUT_FILENO; + arcname = STDO; + } else if ((arfd = open(name, APP_MODE, DMOD)) < 0) + syswarn(0, errno, "Failed open to read/write on %s", + name); + break; + case COPY: + /* + * arfd not used in COPY mode + */ + arcname = NONE; + lstrval = 1; + return 0; + } + if (arfd < 0) + return -1; + + if (chdname != NULL) + if (dochdir(chdname) == -1) + return -1; + /* + * set up is based on device type + */ + if (fstat(arfd, &arsb) < 0) { + syswarn(0, errno, "Failed stat on %s", arcname); + (void)close(arfd); + arfd = -1; + can_unlnk = 0; + return -1; + } + if (S_ISDIR(arsb.st_mode)) { + tty_warn(0, "Cannot write an archive on top of a directory %s", + arcname); + (void)close(arfd); + arfd = -1; + can_unlnk = 0; + return -1; + } + + if (S_ISCHR(arsb.st_mode)) { +#ifdef HAVE_SYS_MTIO_H + artyp = ioctl(arfd, MTIOCGET, &mb) ? ISCHR : ISTAPE; +#else + tty_warn(1, "System does not have tape support"); + artyp = ISREG; +#endif + } else if (S_ISBLK(arsb.st_mode)) + artyp = ISBLK; + else if ((lseek(arfd, (off_t)0L, SEEK_CUR) == -1) && (errno == ESPIPE)) + artyp = ISPIPE; + else + artyp = ISREG; + + /* + * Special handling for empty files. + */ + if (artyp == ISREG && arsb.st_size == 0) { + switch (act) { + case LIST: + case EXTRACT: + return -1; + case APPND: + act = -ARCHIVE; + return -1; + case ARCHIVE: + break; + } + } + + /* + * make sure beyond any doubt that we can unlink only regular files + * we created + */ + if (artyp != ISREG) + can_unlnk = 0; + + /* + * if we are writing, we are done + */ + if (act == ARCHIVE) { + blksz = rdblksz = wrblksz; + lstrval = 1; + return 0; + } + + /* + * set default blksz on read. APPNDs writes rdblksz on the last volume + * On all new archive volumes, we shift to wrblksz (if the user + * specified one, otherwize we will continue to use rdblksz). We + * must set blocksize based on what kind of device the archive is + * stored. + */ + switch(artyp) { + case ISTAPE: + /* + * Tape drives come in at least two flavors. Those that support + * variable sized records and those that have fixed sized + * records. They must be treated differently. For tape drives + * that support variable sized records, we must make large + * reads to make sure we get the entire record, otherwise we + * will just get the first part of the record (up to size we + * asked). Tapes with fixed sized records may or may not return + * multiple records in a single read. We really do not care + * what the physical record size is UNLESS we are going to + * append. (We will need the physical block size to rewrite + * the trailer). Only when we are appending do we go to the + * effort to figure out the true PHYSICAL record size. + */ + blksz = rdblksz = MAXBLK; + break; + case ISPIPE: + case ISBLK: + case ISCHR: + /* + * Blocksize is not a major issue with these devices (but must + * be kept a multiple of 512). If the user specified a write + * block size, we use that to read. Under append, we must + * always keep blksz == rdblksz. Otherwise we go ahead and use + * the device optimal blocksize as (and if) returned by stat + * and if it is within pax specs. + */ + if ((act == APPND) && wrblksz) { + blksz = rdblksz = wrblksz; + break; + } + + if ((arsb.st_blksize > 0) && (arsb.st_blksize < MAXBLK) && + ((arsb.st_blksize % BLKMULT) == 0)) + rdblksz = arsb.st_blksize; + else + rdblksz = DEVBLK; + /* + * For performance go for large reads when we can without harm + */ + if ((act == APPND) || (artyp == ISCHR)) + blksz = rdblksz; + else + blksz = MAXBLK; + break; + case ISREG: + /* + * if the user specified wrblksz works, use it. Under appends + * we must always keep blksz == rdblksz + */ + if ((act == APPND) && wrblksz && ((arsb.st_size%wrblksz)==0)){ + blksz = rdblksz = wrblksz; + break; + } + /* + * See if we can find the blocking factor from the file size + */ + for (rdblksz = MAXBLK; rdblksz > 0; rdblksz -= BLKMULT) + if ((arsb.st_size % rdblksz) == 0) + break; + /* + * When we cannot find a match, we may have a flawed archive. + */ + if (rdblksz <= 0) + rdblksz = FILEBLK; + /* + * for performance go for large reads when we can + */ + if (act == APPND) + blksz = rdblksz; + else + blksz = MAXBLK; + break; + default: + /* + * should never happen, worst case, slow... + */ + blksz = rdblksz = BLKMULT; + break; + } + lstrval = 1; + return 0; +} + +/* + * ar_close() + * closes archive device, increments volume number, and prints i/o summary + */ +void +ar_close(void) +{ + int status; + + if (arfd < 0) { + did_io = io_ok = flcnt = 0; + return; + } + + + /* + * Close archive file. This may take a LONG while on tapes (we may be + * forced to wait for the rewind to complete) so tell the user what is + * going on (this avoids the user hitting control-c thinking pax is + * broken). + */ + if ((vflag || Vflag) && (artyp == ISTAPE)) { + if (vfpart) + (void)putc('\n', listf); + (void)fprintf(listf, + "%s: Waiting for tape drive close to complete...", + argv0); + (void)fflush(listf); + } + + /* + * if nothing was written to the archive (and we created it), we remove + * it + */ + if (can_unlnk && (fstat(arfd, &arsb) == 0) && (S_ISREG(arsb.st_mode)) && + (arsb.st_size == 0)) { + (void)unlink(arcname); + can_unlnk = 0; + } + + /* + * for a quick extract/list, pax frequently exits before the child + * process is done + */ + if ((act == LIST || act == EXTRACT) && nflag && zpid > 0) + kill(zpid, SIGINT); + +#ifdef SUPPORT_RMT + if (artyp == ISRMT) + (void)rmtclose(arfd); + else +#endif /* SUPPORT_RMT */ + (void)close(arfd); + + /* Do not exit before child to ensure data integrity */ + if (zpid > 0) + waitpid(zpid, &status, 0); + + if ((vflag || Vflag) && (artyp == ISTAPE)) { + (void)fputs("done.\n", listf); + vfpart = 0; + (void)fflush(listf); + } + arfd = -1; + + if (!io_ok && !did_io) { + flcnt = 0; + return; + } + did_io = io_ok = 0; + + /* + * The volume number is only increased when the last device has data + * and we have already determined the archive format. + */ + if (frmt != NULL) + ++arvol; + + if (!vflag && !Vflag) { + flcnt = 0; + return; + } + + /* + * Print out a summary of I/O for this archive volume. + */ + if (vfpart) { + (void)putc('\n', listf); + vfpart = 0; + } + + /* mimic cpio's block count first */ + if (frmt && strcmp(NM_CPIO, argv0) == 0) { + (void)fprintf(listf, OFFT_F " blocks\n", + (rdcnt ? rdcnt : wrcnt) / 5120); + } + + ar_summary(0); + + (void)fflush(listf); + flcnt = 0; +} + +/* + * ar_drain() + * drain any archive format independent padding from an archive read + * from a socket or a pipe. This is to prevent the process on the + * other side of the pipe from getting a SIGPIPE (pax will stop + * reading an archive once a format dependent trailer is detected). + */ +void +ar_drain(void) +{ + int res; + char drbuf[MAXBLK]; + + /* + * we only drain from a pipe/socket. Other devices can be closed + * without reading up to end of file. We sure hope that pipe is closed + * on the other side so we will get an EOF. + */ + if ((artyp != ISPIPE) || (lstrval <= 0)) + return; + + /* + * keep reading until pipe is drained + */ +#ifdef SUPPORT_RMT + if (artyp == ISRMT) { + while ((res = rmtread_with_restart(arfd, + drbuf, sizeof(drbuf))) > 0) + continue; + } else { +#endif /* SUPPORT_RMT */ + while ((res = read_with_restart(arfd, + drbuf, sizeof(drbuf))) > 0) + continue; +#ifdef SUPPORT_RMT + } +#endif /* SUPPORT_RMT */ + lstrval = res; +} + +/* + * ar_set_wr() + * Set up device right before switching from read to write in an append. + * device dependent code (if required) to do this should be added here. + * For all archive devices we are already positioned at the place we want + * to start writing when this routine is called. + * Return: + * 0 if all ready to write, -1 otherwise + */ + +int +ar_set_wr(void) +{ + off_t cpos; + + /* + * we must make sure the trailer is rewritten on append, ar_next() + * will stop us if the archive containing the trailer was not written + */ + wr_trail = 0; + + /* + * Add any device dependent code as required here + */ + if (artyp != ISREG) + return 0; + /* + * Ok we have an archive in a regular file. If we were rewriting a + * file, we must get rid of all the stuff after the current offset + * (it was not written by pax). + */ + if (((cpos = lseek(arfd, (off_t)0L, SEEK_CUR)) < 0) || + (ftruncate(arfd, cpos) < 0)) { + syswarn(1, errno, "Unable to truncate archive file"); + return -1; + } + return 0; +} + +/* + * ar_app_ok() + * check if the last volume in the archive allows appends. We cannot check + * this until we are ready to write since there is no spec that says all + * volumes in a single archive have to be of the same type... + * Return: + * 0 if we can append, -1 otherwise. + */ + +int +ar_app_ok(void) +{ + if (artyp == ISPIPE) { + tty_warn(1, + "Cannot append to an archive obtained from a pipe."); + return -1; + } + + if (!invld_rec) + return 0; + tty_warn(1, + "Cannot append, device record size %d does not support %s spec", + rdblksz, argv0); + return -1; +} + +#ifdef SYS_NO_RESTART +/* + * read_with_restart() + * Equivalent to read() but does retry on signals. + * This function is not needed on 4.2BSD and later. + * Return: + * Number of bytes written. -1 indicates an error. + */ + +int +read_with_restart(int fd, void *buf, int bsz) +{ + int r; + + while (((r = read(fd, buf, bsz)) < 0) && errno == EINTR) + continue; + + return r; +} + +/* + * rmtread_with_restart() + * Equivalent to rmtread() but does retry on signals. + * This function is not needed on 4.2BSD and later. + * Return: + * Number of bytes written. -1 indicates an error. + */ +static int +rmtread_with_restart(int fd, void *buf, int bsz) +{ + int r; + + while (((r = rmtread(fd, buf, bsz)) < 0) && errno == EINTR) + continue; + + return r; +} +#endif + +/* + * xread() + * Equivalent to read() but does retry on partial read, which may occur + * on signals. + * Return: + * Number of bytes read. 0 for end of file, -1 for an error. + */ + +int +xread(int fd, void *buf, int bsz) +{ + char *b = buf; + int nread = 0; + int r; + + do { +#ifdef SUPPORT_RMT + if ((r = rmtread_with_restart(fd, b, bsz)) <= 0) + break; +#else + if ((r = read_with_restart(fd, b, bsz)) <= 0) + break; +#endif /* SUPPORT_RMT */ + b += r; + bsz -= r; + nread += r; + } while (bsz > 0); + + return nread ? nread : r; +} + +#ifdef SYS_NO_RESTART +/* + * write_with_restart() + * Equivalent to write() but does retry on signals. + * This function is not needed on 4.2BSD and later. + * Return: + * Number of bytes written. -1 indicates an error. + */ + +int +write_with_restart(int fd, void *buf, int bsz) +{ + int r; + + while (((r = write(fd, buf, bsz)) < 0) && errno == EINTR) + ; + + return r; +} + +/* + * rmtwrite_with_restart() + * Equivalent to write() but does retry on signals. + * This function is not needed on 4.2BSD and later. + * Return: + * Number of bytes written. -1 indicates an error. + */ + +static int +rmtwrite_with_restart(int fd, void *buf, int bsz) +{ + int r; + + while (((r = rmtwrite(fd, buf, bsz)) < 0) && errno == EINTR) + ; + + return r; +} +#endif + +/* + * xwrite() + * Equivalent to write() but does retry on partial write, which may occur + * on signals. + * Return: + * Number of bytes written. -1 indicates an error. + */ + +int +xwrite(int fd, void *buf, int bsz) +{ + char *b = buf; + int written = 0; + int r; + + do { +#ifdef SUPPORT_RMT + if ((r = rmtwrite_with_restart(fd, b, bsz)) <= 0) + break; +#else + if ((r = write_with_restart(fd, b, bsz)) <= 0) + break; +#endif /* SUPPORT_RMT */ + b += r; + bsz -= r; + written += r; + } while (bsz > 0); + + return written ? written : r; +} + +/* + * ar_read() + * read up to a specified number of bytes from the archive into the + * supplied buffer. When dealing with tapes we may not always be able to + * read what we want. + * Return: + * Number of bytes in buffer. 0 for end of file, -1 for a read error. + */ + +int +ar_read(char *buf, int cnt) +{ + int res = 0; + + /* + * if last i/o was in error, no more reads until reset or new volume + */ + if (lstrval <= 0) + return lstrval; + + /* + * how we read must be based on device type + */ + switch (artyp) { +#ifdef SUPPORT_RMT + case ISRMT: + if ((res = rmtread_with_restart(arfd, buf, cnt)) > 0) { + io_ok = 1; + return res; + } + break; +#endif /* SUPPORT_RMT */ + case ISTAPE: + if ((res = read_with_restart(arfd, buf, cnt)) > 0) { + /* + * CAUTION: tape systems may not always return the same + * sized records so we leave blksz == MAXBLK. The + * physical record size that a tape drive supports is + * very hard to determine in a uniform and portable + * manner. + */ + io_ok = 1; + if (res != rdblksz) { + /* + * Record size changed. If this happens on + * any record after the first, we probably have + * a tape drive which has a fixed record size + * (we are getting multiple records in a single + * read). Watch out for record blocking that + * violates pax spec (must be a multiple of + * BLKMULT). + */ + rdblksz = res; + if (rdblksz % BLKMULT) + invld_rec = 1; + } + return res; + } + break; + case ISREG: + case ISBLK: + case ISCHR: + case ISPIPE: + default: + /* + * Files are so easy to deal with. These other things cannot + * be trusted at all. So when we are dealing with character + * devices and pipes we just take what they have ready for us + * and return. Trying to do anything else with them runs the + * risk of failure. + */ + if ((res = read_with_restart(arfd, buf, cnt)) > 0) { + io_ok = 1; + return res; + } + break; + } + + /* + * We are in trouble at this point, something is broken... + */ + lstrval = res; + if (res < 0) + syswarn(1, errno, "Failed read on archive volume %d", arvol); + else + tty_warn(0, "End of archive volume %d reached", arvol); + return res; +} + +/* + * ar_write() + * Write a specified number of bytes in supplied buffer to the archive + * device so it appears as a single "block". Deals with errors and tries + * to recover when faced with short writes. + * Return: + * Number of bytes written. 0 indicates end of volume reached and with no + * flaws (as best that can be detected). A -1 indicates an unrecoverable + * error in the archive occurred. + */ + +int +ar_write(char *buf, int bsz) +{ + int res; + off_t cpos; + + /* + * do not allow pax to create a "bad" archive. Once a write fails on + * an archive volume prevent further writes to it. + */ + if (lstrval <= 0) + return lstrval; + + if ((res = xwrite(arfd, buf, bsz)) == bsz) { + wr_trail = 1; + io_ok = 1; + return bsz; + } + /* + * write broke, see what we can do with it. We try to send any partial + * writes that may violate pax spec to the next archive volume. + */ + if (res < 0) + lstrval = res; + else + lstrval = 0; + + switch (artyp) { + case ISREG: + if ((res > 0) && (res % BLKMULT)) { + /* + * try to fix up partial writes which are not BLKMULT + * in size by forcing the runt record to next archive + * volume + */ + if ((cpos = lseek(arfd, (off_t)0L, SEEK_CUR)) < 0) + break; + cpos -= (off_t)res; + if (ftruncate(arfd, cpos) < 0) + break; + res = lstrval = 0; + break; + } + if (res >= 0) + break; + /* + * if file is out of space, handle it like a return of 0 + */ + if ((errno == ENOSPC) || (errno == EFBIG)) + res = lstrval = 0; +#ifdef EDQUOT + if (errno == EDQUOT) + res = lstrval = 0; +#endif + break; + case ISTAPE: + case ISCHR: + case ISBLK: +#ifdef SUPPORT_RMT + case ISRMT: +#endif /* SUPPORT_RMT */ + if (res >= 0) + break; + if (errno == EACCES) { + tty_warn(0, + "Write failed, archive is write protected."); + res = lstrval = 0; + return 0; + } + /* + * see if we reached the end of media, if so force a change to + * the next volume + */ + if ((errno == ENOSPC) || (errno == EIO) || (errno == ENXIO)) + res = lstrval = 0; + break; + case ISPIPE: + default: + /* + * we cannot fix errors to these devices + */ + break; + } + + /* + * Better tell the user the bad news... + * if this is a block aligned archive format, we may have a bad archive + * if the format wants the header to start at a BLKMULT boundary. While + * we can deal with the mis-aligned data, it violates spec and other + * archive readers will likely fail. if the format is not block + * aligned, the user may be lucky (and the archive is ok). + */ + if (res >= 0) { + if (res > 0) + wr_trail = 1; + io_ok = 1; + } + + /* + * If we were trying to rewrite the trailer and it didn't work, we + * must quit right away. + */ + if (!wr_trail && (res <= 0)) { + tty_warn(1, + "Unable to append, trailer re-write failed. Quitting."); + return res; + } + + if (res == 0) + tty_warn(0, "End of archive volume %d reached", arvol); + else if (res < 0) + syswarn(1, errno, "Failed write to archive volume: %d", arvol); + else if (!frmt->blkalgn || ((res % frmt->blkalgn) == 0)) + tty_warn(0, + "WARNING: partial archive write. Archive MAY BE FLAWED"); + else + tty_warn(1,"WARNING: partial archive write. Archive IS FLAWED"); + return res; +} + +/* + * ar_rdsync() + * Try to move past a bad spot on a flawed archive as needed to continue + * I/O. Clears error flags to allow I/O to continue. + * Return: + * 0 when ok to try i/o again, -1 otherwise. + */ + +int +ar_rdsync(void) +{ + long fsbz; + off_t cpos; + off_t mpos; +#ifdef HAVE_SYS_MTIO_H + struct mtop mb; +#endif + + /* + * Fail resync attempts at user request (done) or if this is going to be + * an update/append to a existing archive. if last i/o hit media end, + * we need to go to the next volume not try a resync + */ + if ((done > 0) || (lstrval == 0)) + return -1; + + if ((act == APPND) || (act == ARCHIVE)) { + tty_warn(1, "Cannot allow updates to an archive with flaws."); + return -1; + } + if (io_ok) + did_io = 1; + + switch(artyp) { +#ifdef SUPPORT_RMT + case ISRMT: +#endif /* SUPPORT_RMT */ + case ISTAPE: +#ifdef HAVE_SYS_MTIO_H + /* + * if the last i/o was a successful data transfer, we assume + * the fault is just a bad record on the tape that we are now + * past. If we did not get any data since the last resync try + * to move the tape forward one PHYSICAL record past any + * damaged tape section. Some tape drives are stubborn and need + * to be pushed. + */ + if (io_ok) { + io_ok = 0; + lstrval = 1; + break; + } + mb.mt_op = MTFSR; + mb.mt_count = 1; +#ifdef SUPPORT_RMT + if (artyp == ISRMT) { + if (rmtioctl(arfd, MTIOCTOP, &mb) < 0) + break; + } else { +#endif /* SUPPORT_RMT */ + if (ioctl(arfd, MTIOCTOP, &mb) < 0) + break; +#ifdef SUPPORT_RMT + } +#endif /* SUPPORT_RMT */ + lstrval = 1; +#else + tty_warn(1, "System does not have tape support"); +#endif + break; + case ISREG: + case ISCHR: + case ISBLK: + /* + * try to step over the bad part of the device. + */ + io_ok = 0; + if (((fsbz = arsb.st_blksize) <= 0) || (artyp != ISREG)) + fsbz = BLKMULT; + if ((cpos = lseek(arfd, (off_t)0L, SEEK_CUR)) < 0) + break; + mpos = fsbz - (cpos % (off_t)fsbz); + if (lseek(arfd, mpos, SEEK_CUR) < 0) + break; + lstrval = 1; + break; + case ISPIPE: + default: + /* + * cannot recover on these archive device types + */ + io_ok = 0; + break; + } + if (lstrval <= 0) { + tty_warn(1, "Unable to recover from an archive read failure."); + return -1; + } + tty_warn(0, "Attempting to recover from an archive read failure."); + return 0; +} + +/* + * ar_fow() + * Move the I/O position within the archive forward the specified number of + * bytes as supported by the device. If we cannot move the requested + * number of bytes, return the actual number of bytes moved in skipped. + * Return: + * 0 if moved the requested distance, -1 on complete failure, 1 on + * partial move (the amount moved is in skipped) + */ + +int +ar_fow(off_t sksz, off_t *skipped) +{ + off_t cpos; + off_t mpos; + + *skipped = 0; + if (sksz <= 0) + return 0; + + /* + * we cannot move forward at EOF or error + */ + if (lstrval <= 0) + return lstrval; + + /* + * Safer to read forward on devices where it is hard to find the end of + * the media without reading to it. With tapes we cannot be sure of the + * number of physical blocks to skip (we do not know physical block + * size at this point), so we must only read forward on tapes! + */ + if (artyp == ISTAPE || artyp == ISPIPE +#ifdef SUPPORT_RMT + || artyp == ISRMT +#endif /* SUPPORT_RMT */ + ) + return 0; + + /* + * figure out where we are in the archive + */ + if ((cpos = lseek(arfd, (off_t)0L, SEEK_CUR)) >= 0) { + /* + * we can be asked to move farther than there are bytes in this + * volume, if so, just go to file end and let normal buf_fill() + * deal with the end of file (it will go to next volume by + * itself) + */ + mpos = cpos + sksz; + if (artyp == ISREG && mpos > arsb.st_size) + mpos = arsb.st_size; + if ((mpos = lseek(arfd, mpos, SEEK_SET)) >= 0) { + *skipped = mpos - cpos; + return 0; + } + } else { + if (artyp != ISREG) + return 0; /* non-seekable device */ + } + syswarn(1, errno, "Forward positioning operation on archive failed"); + lstrval = -1; + return -1; +} + +/* + * ar_rev() + * move the i/o position within the archive backwards the specified byte + * count as supported by the device. With tapes drives we RESET rdblksz to + * the PHYSICAL blocksize. + * NOTE: We should only be called to move backwards so we can rewrite the + * last records (the trailer) of an archive (APPEND). + * Return: + * 0 if moved the requested distance, -1 on complete failure + */ + +int +ar_rev(off_t sksz) +{ + off_t cpos; +#ifdef HAVE_SYS_MTIO_H + int phyblk; + struct mtop mb; +#endif + + /* + * make sure we do not have try to reverse on a flawed archive + */ + if (lstrval < 0) + return lstrval; + + switch(artyp) { + case ISPIPE: + if (sksz <= 0) + break; + /* + * cannot go backwards on these critters + */ + tty_warn(1, "Reverse positioning on pipes is not supported."); + lstrval = -1; + return -1; + case ISREG: + case ISBLK: + case ISCHR: + default: + if (sksz <= 0) + break; + + /* + * For things other than files, backwards movement has a very + * high probability of failure as we really do not know the + * true attributes of the device we are talking to (the device + * may not even have the ability to lseek() in any direction). + * First we figure out where we are in the archive. + */ + if ((cpos = lseek(arfd, (off_t)0L, SEEK_CUR)) < 0) { + syswarn(1, errno, + "Unable to obtain current archive byte offset"); + lstrval = -1; + return -1; + } + + /* + * we may try to go backwards past the start when the archive + * is only a single record. If this happens and we are on a + * multi-volume archive, we need to go to the end of the + * previous volume and continue our movement backwards from + * there. + */ + if ((cpos -= sksz) < (off_t)0L) { + if (arvol > 1) { + /* + * this should never happen + */ + tty_warn(1, + "Reverse position on previous volume."); + lstrval = -1; + return -1; + } + cpos = (off_t)0L; + } + if (lseek(arfd, cpos, SEEK_SET) < 0) { + syswarn(1, errno, "Unable to seek archive backwards"); + lstrval = -1; + return -1; + } + break; + case ISTAPE: +#ifdef SUPPORT_RMT + case ISRMT: +#endif /* SUPPORT_RMT */ +#ifdef HAVE_SYS_MTIO_H + /* + * Calculate and move the proper number of PHYSICAL tape + * blocks. If the sksz is not an even multiple of the physical + * tape size, we cannot do the move (this should never happen). + * (We also cannot handle trailers spread over two vols). + * get_phys() also makes sure we are in front of the filemark. + */ + if ((phyblk = get_phys()) <= 0) { + lstrval = -1; + return -1; + } + + /* + * make sure future tape reads only go by physical tape block + * size (set rdblksz to the real size). + */ + rdblksz = phyblk; + + /* + * if no movement is required, just return (we must be after + * get_phys() so the physical blocksize is properly set) + */ + if (sksz <= 0) + break; + + /* + * ok we have to move. Make sure the tape drive can do it. + */ + if (sksz % phyblk) { + tty_warn(1, + "Tape drive unable to backspace requested amount"); + lstrval = -1; + return -1; + } + + /* + * move backwards the requested number of bytes + */ + mb.mt_op = MTBSR; + mb.mt_count = sksz/phyblk; + if ( +#ifdef SUPPORT_RMT + rmtioctl(arfd, MTIOCTOP, &mb) +#else + ioctl(arfd, MTIOCTOP, &mb) +#endif /* SUPPORT_RMT */ + < 0) { + syswarn(1, errno, "Unable to backspace tape %ld blocks.", + (long) mb.mt_count); + lstrval = -1; + return -1; + } +#else + tty_warn(1, "System does not have tape support"); +#endif + break; + } + lstrval = 1; + return 0; +} + +#ifdef HAVE_SYS_MTIO_H +/* + * get_phys() + * Determine the physical block size on a tape drive. We need the physical + * block size so we know how many bytes we skip over when we move with + * mtio commands. We also make sure we are BEFORE THE TAPE FILEMARK when + * return. + * This is one really SLOW routine... + * Return: + * physical block size if ok (ok > 0), -1 otherwise + */ + +static int +get_phys(void) +{ + int padsz = 0; + int res; + int phyblk; + struct mtop mb; + char scbuf[MAXBLK]; + + /* + * move to the file mark, and then back up one record and read it. + * this should tell us the physical record size the tape is using. + */ + if (lstrval == 1) { + /* + * we know we are at file mark when we get back a 0 from + * read() + */ +#ifdef SUPPORT_RMT + while ((res = rmtread_with_restart(arfd, + scbuf, sizeof(scbuf))) > 0) +#else + while ((res = read_with_restart(arfd, + scbuf, sizeof(scbuf))) > 0) +#endif /* SUPPORT_RMT */ + padsz += res; + if (res < 0) { + syswarn(1, errno, "Unable to locate tape filemark."); + return -1; + } + } + + /* + * move backwards over the file mark so we are at the end of the + * last record. + */ + mb.mt_op = MTBSF; + mb.mt_count = 1; + if ( +#ifdef SUPPORT_RMT + rmtioctl(arfd, MTIOCTOP, &mb) +#else + ioctl(arfd, MTIOCTOP, &mb) +#endif /* SUPPORT_RMT */ + < 0) { + syswarn(1, errno, "Unable to backspace over tape filemark."); + return -1; + } + + /* + * move backwards so we are in front of the last record and read it to + * get physical tape blocksize. + */ + mb.mt_op = MTBSR; + mb.mt_count = 1; + if ( +#ifdef SUPPORT_RMT + rmtioctl(arfd, MTIOCTOP, &mb) +#else + ioctl(arfd, MTIOCTOP, &mb) +#endif /* SUPPORT_RMT */ + < 0) { + syswarn(1, errno, "Unable to backspace over last tape block."); + return -1; + } + if ((phyblk = +#ifdef SUPPORT_RMT + rmtread_with_restart(arfd, scbuf, sizeof(scbuf)) +#else + read_with_restart(arfd, scbuf, sizeof(scbuf)) +#endif /* SUPPORT_RMT */ + ) <= 0) { + syswarn(1, errno, "Cannot determine archive tape blocksize."); + return -1; + } + + /* + * read forward to the file mark, then back up in front of the filemark + * (this is a bit paranoid, but should be safe to do). + */ + while ((res = +#ifdef SUPPORT_RMT + rmtread_with_restart(arfd, scbuf, sizeof(scbuf)) +#else + read_with_restart(arfd, scbuf, sizeof(scbuf)) +#endif /* SUPPORT_RMT */ + ) > 0) + ; + if (res < 0) { + syswarn(1, errno, "Unable to locate tape filemark."); + return -1; + } + mb.mt_op = MTBSF; + mb.mt_count = 1; + if ( +#ifdef SUPPORT_RMT + rmtioctl(arfd, MTIOCTOP, &mb) +#else + ioctl(arfd, MTIOCTOP, &mb) +#endif /* SUPPORT_RMT */ + < 0) { + syswarn(1, errno, "Unable to backspace over tape filemark."); + return -1; + } + + /* + * set lstrval so we know that the filemark has not been seen + */ + lstrval = 1; + + /* + * return if there was no padding + */ + if (padsz == 0) + return phyblk; + + /* + * make sure we can move backwards over the padding. (this should + * never fail). + */ + if (padsz % phyblk) { + tty_warn(1, "Tape drive unable to backspace requested amount"); + return -1; + } + + /* + * move backwards over the padding so the head is where it was when + * we were first called (if required). + */ + mb.mt_op = MTBSR; + mb.mt_count = padsz/phyblk; + if ( +#ifdef SUPPORT_RMT + rmtioctl(arfd, MTIOCTOP, &mb) +#else + ioctl(arfd, MTIOCTOP, &mb) +#endif /* SUPPORT_RMT */ + < 0) { + syswarn(1, errno, + "Unable to backspace tape over %ld pad blocks", + (long)mb.mt_count); + return -1; + } + return phyblk; +} +#endif + +/* + * ar_next() + * prompts the user for the next volume in this archive. For some devices + * we may allow the media to be changed. Otherwise a new archive is + * prompted for. By pax spec, if there is no controlling tty or an eof is + * read on tty input, we must quit pax. + * Return: + * 0 when ready to continue, -1 when all done + */ + +int +ar_next(void) +{ + char buf[PAXPATHLEN+2]; + static char *arcfree = NULL; + sigset_t o_mask; + + /* + * WE MUST CLOSE THE DEVICE. A lot of devices must see last close, (so + * things like writing EOF etc will be done) (Watch out ar_close() can + * also be called via a signal handler, so we must prevent a race. + */ + if (sigprocmask(SIG_BLOCK, &s_mask, &o_mask) < 0) + syswarn(0, errno, "Unable to set signal mask"); + ar_close(); + if (sigprocmask(SIG_SETMASK, &o_mask, NULL) < 0) + syswarn(0, errno, "Unable to restore signal mask"); + + if (done || !wr_trail || force_one_volume) + return -1; + + if (!is_gnutar) + tty_prnt("\nATTENTION! %s archive volume change required.\n", + argv0); + + /* + * if i/o is on stdin or stdout, we cannot reopen it (we do not know + * the name), the user will be forced to type it in. + */ + if (strcmp(arcname, STDO) && strcmp(arcname, STDN) && (artyp != ISREG) + && (artyp != ISPIPE)) { + if (artyp == ISTAPE +#ifdef SUPPORT_RMT + || artyp == ISRMT +#endif /* SUPPORT_RMT */ + ) { + tty_prnt("%s ready for archive tape volume: %d\n", + arcname, arvol); + tty_prnt("Load the NEXT TAPE on the tape drive"); + } else { + tty_prnt("%s ready for archive volume: %d\n", + arcname, arvol); + tty_prnt("Load the NEXT STORAGE MEDIA (if required)"); + } + + if ((act == ARCHIVE) || (act == APPND)) + tty_prnt(" and make sure it is WRITE ENABLED.\n"); + else + tty_prnt("\n"); + + for(;;) { + tty_prnt("Type \"y\" to continue, \".\" to quit %s,", + argv0); + tty_prnt(" or \"s\" to switch to new device.\nIf you"); + tty_prnt(" cannot change storage media, type \"s\"\n"); + tty_prnt("Is the device ready and online? > "); + + if ((tty_read(buf,sizeof(buf))<0) || !strcmp(buf,".")){ + done = 1; + lstrval = -1; + tty_prnt("Quitting %s!\n", argv0); + vfpart = 0; + return -1; + } + + if ((buf[0] == '\0') || (buf[1] != '\0')) { + tty_prnt("%s unknown command, try again\n",buf); + continue; + } + + switch (buf[0]) { + case 'y': + case 'Y': + /* + * we are to continue with the same device + */ + if (ar_open(arcname) >= 0) + return 0; + tty_prnt("Cannot re-open %s, try again\n", + arcname); + continue; + case 's': + case 'S': + /* + * user wants to open a different device + */ + tty_prnt("Switching to a different archive\n"); + break; + default: + tty_prnt("%s unknown command, try again\n",buf); + continue; + } + break; + } + } else { + if (is_gnutar) { + tty_warn(1, "Unexpected EOF on archive file"); + return -1; + } + tty_prnt("Ready for archive volume: %d\n", arvol); + } + + /* + * have to go to a different archive + */ + for (;;) { + tty_prnt("Input archive name or \".\" to quit %s.\n", argv0); + tty_prnt("Archive name > "); + + if ((tty_read(buf, sizeof(buf)) < 0) || !strcmp(buf, ".")) { + done = 1; + lstrval = -1; + tty_prnt("Quitting %s!\n", argv0); + vfpart = 0; + return -1; + } + if (buf[0] == '\0') { + tty_prnt("Empty file name, try again\n"); + continue; + } + if (!strcmp(buf, "..")) { + tty_prnt("Illegal file name: .. try again\n"); + continue; + } + if (strlen(buf) > PAXPATHLEN) { + tty_prnt("File name too long, try again\n"); + continue; + } + + /* + * try to open new archive + */ + if (ar_open(buf) >= 0) { + if (arcfree) { + (void)free(arcfree); + arcfree = NULL; + } + if ((arcfree = strdup(buf)) == NULL) { + done = 1; + lstrval = -1; + tty_warn(0, "Cannot save archive name."); + return -1; + } + arcname = arcfree; + break; + } + tty_prnt("Cannot open %s, try again\n", buf); + continue; + } + return 0; +} + +/* + * ar_start_gzip() + * starts the compression/decompression process as a child, using magic + * to keep the fd the same in the calling function (parent). possible + * programs are GZIP_CMD, BZIP2_CMD, and COMPRESS_CMD. + */ +void +ar_start_gzip(int fd, const char *gzp, int wr) +{ + int fds[2]; + const char *gzip_flags; + + if (pipe(fds) < 0) + err(1, "could not pipe"); + zpid = fork(); + if (zpid < 0) + err(1, "could not fork"); + + /* parent */ + if (zpid) { + if (wr) + dup2(fds[1], fd); + else + dup2(fds[0], fd); + close(fds[0]); + close(fds[1]); + } else { + if (wr) { + dup2(fds[0], STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + gzip_flags = "-c"; + } else { + dup2(fds[1], STDOUT_FILENO); + dup2(fd, STDIN_FILENO); + gzip_flags = "-dc"; + } + close(fds[0]); + close(fds[1]); + if (execlp(gzp, gzp, gzip_flags, NULL) < 0) + err(1, "could not exec"); + /* NOTREACHED */ + } +} + +static const char * +timefmt(char *buf, size_t size, off_t sz, time_t tm, const char *unitstr) +{ + (void)snprintf(buf, size, "%lu secs (" OFFT_F " %s/sec)", + (unsigned long)tm, (OFFT_T)(sz / tm), unitstr); + return buf; +} + +static const char * +sizefmt(char *buf, size_t size, off_t sz) +{ + (void)snprintf(buf, size, OFFT_F " bytes", (OFFT_T)sz); + return buf; +} + +void +ar_summary(int n) +{ + time_t secs; + char buf[BUFSIZ]; + char tbuf[MAXPATHLEN/4]; /* XXX silly size! */ + char s1buf[MAXPATHLEN/8]; /* XXX very silly size! */ + char s2buf[MAXPATHLEN/8]; /* XXX very silly size! */ + FILE *outf; + + if (act == LIST) + outf = stdout; + else + outf = stderr; + + /* + * If we are called from a signal (n != 0), use snprintf(3) so that we + * don't reenter stdio(3). + */ + (void)time(&secs); + if ((secs -= starttime) == 0) + secs = 1; + + /* + * If we have not determined the format yet, we just say how many bytes + * we have skipped over looking for a header to id. there is no way we + * could have written anything yet. + */ + if (frmt == NULL && act != COPY) { + snprintf(buf, sizeof(buf), + "unknown format, %s skipped in %s\n", + sizefmt(s1buf, sizeof(s1buf), rdcnt), + timefmt(tbuf, sizeof(tbuf), rdcnt, secs, "bytes")); + if (n == 0) + (void)fprintf(outf, "%s: %s", argv0, buf); + else + (void)write(STDERR_FILENO, buf, strlen(buf)); + return; + } + + + if (n != 0 && *archd.name) { + snprintf(buf, sizeof(buf), "Working on `%s' (%s)\n", + archd.name, sizefmt(s1buf, sizeof(s1buf), archd.sb.st_size)); + (void)write(STDERR_FILENO, buf, strlen(buf)); + } + + + if (act == COPY) { + snprintf(buf, sizeof(buf), + "%lu files in %s\n", + (unsigned long)flcnt, + timefmt(tbuf, sizeof(tbuf), flcnt, secs, "files")); + } else { + snprintf(buf, sizeof(buf), + "%s vol %d, %lu files, %s read, %s written in %s\n", + frmt->name, arvol-1, (unsigned long)flcnt, + sizefmt(s1buf, sizeof(s1buf), rdcnt), + sizefmt(s2buf, sizeof(s2buf), wrcnt), + timefmt(tbuf, sizeof(tbuf), rdcnt + wrcnt, secs, "bytes")); + } + if (n == 0) + (void)fprintf(outf, "%s: %s", argv0, buf); + else + (void)write(STDERR_FILENO, buf, strlen(buf)); +} + +/* + * ar_dochdir(name) + * change directory to name, and remember where we came from and + * where we change to (for ar_open). + * + * Maybe we could try to be smart and only do the actual chdir + * when necessary to write a file read from the archive, but this + * is not easy to get right given the pax code structure. + * + * Be sure to not leak descriptors! + * + * We are called N * M times when extracting, and N times when + * writing archives, where + * N: number of -C options + * M: number of files in archive + * + * Returns 0 if all went well, else -1. + */ + +int +ar_dochdir(const char *name) +{ + /* First fdochdir() back... */ + if (fdochdir(cwdfd) == -1) + return -1; + if (dochdir(name) == -1) + return -1; + return 0; +} diff --git a/bin/pax/ar_subs.c b/bin/pax/ar_subs.c new file mode 100644 index 0000000..d4ba54b --- /dev/null +++ b/bin/pax/ar_subs.c @@ -0,0 +1,1449 @@ +/* $NetBSD: ar_subs.c,v 1.56 2011/08/31 16:24:54 plunky Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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 HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +#if 0 +static char sccsid[] = "@(#)ar_subs.c 8.2 (Berkeley) 4/18/94"; +#else +__RCSID("$NetBSD: ar_subs.c,v 1.56 2011/08/31 16:24:54 plunky Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <signal.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <fcntl.h> +#include <errno.h> +#include <time.h> +#include <unistd.h> +#include <stdlib.h> +#include "pax.h" +#include "pat_rep.h" +#include "extern.h" + +static int path_check(ARCHD *, int); +static int wr_archive(ARCHD *, int is_app); +static int get_arc(void); +static int next_head(ARCHD *); +#if !HAVE_NBTOOL_CONFIG_H +static int fdochroot(int); +#endif +extern sigset_t s_mask; + +/* + * Routines which control the overall operation modes of pax as specified by + * the user: list, append, read ... + */ + +static char hdbuf[BLKMULT]; /* space for archive header on read */ +u_long flcnt; /* number of files processed */ +ARCHD archd; + +static char cwdpath[MAXPATHLEN]; /* current working directory path */ +static size_t cwdpathlen; /* current working directory path len */ + +int +updatepath(void) +{ + if (getcwd(cwdpath, sizeof(cwdpath)) == NULL) { + syswarn(1, errno, "Cannot get working directory"); + return -1; + } + cwdpathlen = strlen(cwdpath); + return 0; +} + +int +fdochdir(int fcwd) +{ + if (fchdir(fcwd) == -1) { + syswarn(1, errno, "Cannot chdir to `.'"); + return -1; + } + return updatepath(); +} + +int +dochdir(const char *name) +{ + if (chdir(name) == -1) + syswarn(1, errno, "Cannot chdir to `%s'", name); + return updatepath(); +} + +#if !HAVE_NBTOOL_CONFIG_H +static int +fdochroot(int fcwd) +{ + if (fchroot(fcwd) != 0) { + syswarn(1, errno, "Can't fchroot to \".\""); + return -1; + } + return updatepath(); +} +#endif + +/* + * mkdir(), but if we failed, check if someone else made it for us + * already and don't error out. + */ +int +domkdir(const char *fname, mode_t mode) +{ + int error; + struct stat sb; + + if ((error = mkdir(fname, mode)) != -1) + return error; + + switch (errno) { + case EISDIR: + return 0; + case EEXIST: + case EACCES: + case ENOSYS: /* Grr Solaris */ + case EROFS: + error = errno; + if (stat(fname, &sb) != -1 && S_ISDIR(sb.st_mode)) + return 0; + errno = error; + /*FALLTHROUGH*/ + default: + return -1; + } +} + +static int +path_check(ARCHD *arcn, int level) +{ + char buf[MAXPATHLEN]; + char *p; + + if ((p = strrchr(arcn->name, '/')) == NULL) + return 0; + *p = '\0'; + + if (realpath(arcn->name, buf) == NULL) { + int error; + error = path_check(arcn, level + 1); + *p = '/'; + if (error == 0) + return 0; + if (level == 0) + syswarn(1, 0, "Cannot resolve `%s'", arcn->name); + return -1; + } + if (strncmp(buf, cwdpath, cwdpathlen) != 0) { + *p = '/'; + syswarn(1, 0, "Attempt to write file `%s' that resolves into " + "`%s/%s' outside current working directory `%s' ignored", + arcn->name, buf, p + 1, cwdpath); + return -1; + } + *p = '/'; + return 0; +} + +/* + * list() + * list the contents of an archive which match user supplied pattern(s) + * (if no pattern is supplied, list entire contents). + */ + +int +list(void) +{ + ARCHD *arcn; + int res; + time_t now; + + arcn = &archd; + /* + * figure out archive type; pass any format specific options to the + * archive option processing routine; call the format init routine. We + * also save current time for ls_list() so we do not make a system + * call for each file we need to print. If verbose (vflag) start up + * the name and group caches. + */ + if ((get_arc() < 0) || ((*frmt->options)() < 0) || + ((*frmt->st_rd)() < 0)) + return 1; + + now = time(NULL); + + /* + * step through the archive until the format says it is done + */ + while (next_head(arcn) == 0) { + if (arcn->type == PAX_GLL || arcn->type == PAX_GLF) { + /* + * we need to read, to get the real filename + */ + off_t cnt; + if (!(*frmt->rd_data)(arcn, -arcn->type, &cnt)) + (void)rd_skip(cnt + arcn->pad); + continue; + } + + /* + * check for pattern, and user specified options match. + * When all patterns are matched we are done. + */ + if ((res = pat_match(arcn)) < 0) + break; + + if ((res == 0) && (sel_chk(arcn) == 0)) { + /* + * pattern resulted in a selected file + */ + if (pat_sel(arcn) < 0) + break; + + /* + * modify the name as requested by the user if name + * survives modification, do a listing of the file + */ + if ((res = mod_name(arcn, RENM)) < 0) + break; + if (res == 0) { + if (arcn->name[0] == '/' && !check_Aflag()) { + memmove(arcn->name, arcn->name + 1, + strlen(arcn->name)); + } + ls_list(arcn, now, stdout); + } + /* + * if there's an error writing to stdout then we must + * stop now -- we're probably writing to a pipe that + * has been closed by the reader. + */ + if (ferror(stdout)) { + syswarn(1, errno, "Listing incomplete."); + break; + } + } + /* + * skip to next archive format header using values calculated + * by the format header read routine + */ + if (rd_skip(arcn->skip + arcn->pad) == 1) + break; + } + + /* + * all done, let format have a chance to cleanup, and make sure that + * the patterns supplied by the user were all matched + */ + (void)(*frmt->end_rd)(); + (void)sigprocmask(SIG_BLOCK, &s_mask, NULL); + ar_close(); + pat_chk(); + + return 0; +} + +/* + * extract() + * extract the member(s) of an archive as specified by user supplied + * pattern(s) (no patterns extracts all members) + */ + +int +extract(void) +{ + ARCHD *arcn; + int res; + off_t cnt; + struct stat sb; + int fd; + time_t now; + + arcn = &archd; + /* + * figure out archive type; pass any format specific options to the + * archive option processing routine; call the format init routine; + * start up the directory modification time and access mode database + */ + if ((get_arc() < 0) || ((*frmt->options)() < 0) || + ((*frmt->st_rd)() < 0) || (dir_start() < 0)) + return 1; + + now = time(NULL); +#if !HAVE_NBTOOL_CONFIG_H + if (do_chroot) + (void)fdochroot(cwdfd); +#endif + + /* + * When we are doing interactive rename, we store the mapping of names + * so we can fix up hard links files later in the archive. + */ + if (iflag && (name_start() < 0)) + return 1; + + /* + * step through each entry on the archive until the format read routine + * says it is done + */ + while (next_head(arcn) == 0) { + int write_to_hard_link = 0; + + if (arcn->type == PAX_GLL || arcn->type == PAX_GLF) { + /* + * we need to read, to get the real filename + */ + if (!(*frmt->rd_data)(arcn, -arcn->type, &cnt)) + (void)rd_skip(cnt + arcn->pad); + continue; + } + + /* + * check for pattern, and user specified options match. When + * all the patterns are matched we are done + */ + if ((res = pat_match(arcn)) < 0) + break; + + if ((res > 0) || (sel_chk(arcn) != 0)) { + /* + * file is not selected. skip past any file + * data and padding and go back for the next + * archive member + */ + (void)rd_skip(arcn->skip + arcn->pad); + continue; + } + + if (kflag && (lstat(arcn->name, &sb) == 0)) { + (void)rd_skip(arcn->skip + arcn->pad); + continue; + } + + /* + * with -u or -D only extract when the archive member is newer + * than the file with the same name in the file system (no + * test of being the same type is required). + * NOTE: this test is done BEFORE name modifications as + * specified by pax. this operation can be confusing to the + * user who might expect the test to be done on an existing + * file AFTER the name mod. In honesty the pax spec is probably + * flawed in this respect. ignore this for GNU long links. + */ + if ((uflag || Dflag) && ((lstat(arcn->name, &sb) == 0))) { + if (uflag && Dflag) { + if ((arcn->sb.st_mtime <= sb.st_mtime) && + (arcn->sb.st_ctime <= sb.st_ctime)) { + (void)rd_skip(arcn->skip + arcn->pad); + continue; + } + } else if (Dflag) { + if (arcn->sb.st_ctime <= sb.st_ctime) { + (void)rd_skip(arcn->skip + arcn->pad); + continue; + } + } else if (arcn->sb.st_mtime <= sb.st_mtime) { + (void)rd_skip(arcn->skip + arcn->pad); + continue; + } + } + + /* + * this archive member is now been selected. modify the name. + */ + if ((pat_sel(arcn) < 0) || ((res = mod_name(arcn, RENM)) < 0)) + break; + if (res > 0) { + /* + * a bad name mod, skip and purge name from link table + */ + purg_lnk(arcn); + (void)rd_skip(arcn->skip + arcn->pad); + continue; + } + + if (arcn->name[0] == '/' && !check_Aflag()) { + memmove(arcn->name, arcn->name + 1, strlen(arcn->name)); + } + /* + * Non standard -Y and -Z flag. When the existing file is + * same age or newer skip; ignore this for GNU long links. + */ + if ((Yflag || Zflag) && ((lstat(arcn->name, &sb) == 0))) { + if (Yflag && Zflag) { + if ((arcn->sb.st_mtime <= sb.st_mtime) && + (arcn->sb.st_ctime <= sb.st_ctime)) { + (void)rd_skip(arcn->skip + arcn->pad); + continue; + } + } else if (Yflag) { + if (arcn->sb.st_ctime <= sb.st_ctime) { + (void)rd_skip(arcn->skip + arcn->pad); + continue; + } + } else if (arcn->sb.st_mtime <= sb.st_mtime) { + (void)rd_skip(arcn->skip + arcn->pad); + continue; + } + } + + if (vflag) { + if (vflag > 1) + ls_list(arcn, now, listf); + else { + (void)safe_print(arcn->name, listf); + vfpart = 1; + } + } + + /* + * if required, chdir around. + */ + if ((arcn->pat != NULL) && (arcn->pat->chdname != NULL) && + !to_stdout) + dochdir(arcn->pat->chdname); + + if (secure && path_check(arcn, 0) != 0) { + (void)rd_skip(arcn->skip + arcn->pad); + continue; + } + + + /* + * all ok, extract this member based on type + */ + if ((arcn->type != PAX_REG) && (arcn->type != PAX_CTG)) { + /* + * process archive members that are not regular files. + * throw out padding and any data that might follow the + * header (as determined by the format). + */ + if ((arcn->type == PAX_HLK) || + (arcn->type == PAX_HRG)) + res = lnk_creat(arcn, &write_to_hard_link); + else + res = node_creat(arcn); + + if (!write_to_hard_link) { + (void)rd_skip(arcn->skip + arcn->pad); + if (res < 0) + purg_lnk(arcn); + + if (vflag && vfpart) { + (void)putc('\n', listf); + vfpart = 0; + } + continue; + } + } + if (to_stdout) + fd = STDOUT_FILENO; + else { + /* + * We have a file with data here. If we cannot create + * it, skip over the data and purge the name from hard + * link table. + */ + if ((fd = file_creat(arcn, write_to_hard_link)) < 0) { + (void)fflush(listf); + (void)rd_skip(arcn->skip + arcn->pad); + purg_lnk(arcn); + continue; + } + } + /* + * extract the file from the archive and skip over padding and + * any unprocessed data + */ + res = (*frmt->rd_data)(arcn, fd, &cnt); + if (!to_stdout) + file_close(arcn, fd); + if (vflag && vfpart) { + (void)putc('\n', listf); + vfpart = 0; + } + if (!res) + (void)rd_skip(cnt + arcn->pad); + + /* + * if required, chdir around. + */ + if ((arcn->pat != NULL) && (arcn->pat->chdname != NULL)) + fdochdir(cwdfd); + } + + /* + * all done, restore directory modes and times as required; make sure + * all patterns supplied by the user were matched; block off signals + * to avoid chance for multiple entry into the cleanup code. + */ + (void)(*frmt->end_rd)(); + (void)sigprocmask(SIG_BLOCK, &s_mask, NULL); + ar_close(); + proc_dir(); + pat_chk(); + + return 0; +} + +/* + * wr_archive() + * Write an archive. used in both creating a new archive and appends on + * previously written archive. + */ + +static int +wr_archive(ARCHD *arcn, int is_app) +{ + int res; + int hlk; + int wr_one; + off_t cnt; + int (*wrf)(ARCHD *); + int fd = -1; + time_t now; + + /* + * if this format supports hard link storage, start up the database + * that detects them. + */ + if (((hlk = frmt->hlk) == 1) && (lnk_start() < 0)) + return 1; + + /* + * start up the file traversal code and format specific write + */ + if ((ftree_start() < 0) || ((*frmt->st_wr)() < 0)) + return 1; + wrf = frmt->wr; + + now = time(NULL); + + /* + * When we are doing interactive rename, we store the mapping of names + * so we can fix up hard links files later in the archive. + */ + if (iflag && (name_start() < 0)) + return 1; + + /* + * if this is not append, and there are no files, we do no write a trailer + */ + wr_one = is_app; + + /* + * while there are files to archive, process them one at at time + */ + while (next_file(arcn) == 0) { + /* + * check if this file meets user specified options match. + */ + if (sel_chk(arcn) != 0) + continue; + /* + * Here we handle the exclusion -X gnu style patterns which + * are implemented like a pattern list. We don't modify the + * name as this will be done below again, and we don't want + * to double modify it. + */ + if ((res = mod_name(arcn, 0)) < 0) + break; + if (res == 1) + continue; + fd = -1; + if (uflag) { + /* + * only archive if this file is newer than a file with + * the same name that is already stored on the archive + */ + if ((res = chk_ftime(arcn)) < 0) + break; + if (res > 0) + continue; + } + + /* + * this file is considered selected now. see if this is a hard + * link to a file already stored + */ + ftree_sel(arcn); + if (hlk && (chk_lnk(arcn) < 0)) + break; + + if ((arcn->type == PAX_REG) || (arcn->type == PAX_HRG) || + (arcn->type == PAX_CTG)) { + /* + * we will have to read this file. by opening it now we + * can avoid writing a header to the archive for a file + * we were later unable to read (we also purge it from + * the link table). + */ + if ((fd = open(arcn->org_name, O_RDONLY, 0)) < 0) { + syswarn(1, errno, "Unable to open %s to read", + arcn->org_name); + purg_lnk(arcn); + continue; + } + } + + /* + * Now modify the name as requested by the user + */ + if ((res = mod_name(arcn, RENM)) < 0) { + /* + * name modification says to skip this file, close the + * file and purge link table entry + */ + rdfile_close(arcn, &fd); + purg_lnk(arcn); + break; + } + + if (arcn->name[0] == '/' && !check_Aflag()) { + memmove(arcn->name, arcn->name + 1, strlen(arcn->name)); + } + + if ((res > 0) || (docrc && (set_crc(arcn, fd) < 0))) { + /* + * unable to obtain the crc we need, close the file, + * purge link table entry + */ + rdfile_close(arcn, &fd); + purg_lnk(arcn); + continue; + } + + if (vflag) { + if (vflag > 1) + ls_list(arcn, now, listf); + else { + (void)safe_print(arcn->name, listf); + vfpart = 1; + } + } + ++flcnt; + + /* + * looks safe to store the file, have the format specific + * routine write routine store the file header on the archive + */ + if ((res = (*wrf)(arcn)) < 0) { + rdfile_close(arcn, &fd); + break; + } + wr_one = 1; + if (res > 0) { + /* + * format write says no file data needs to be stored + * so we are done messing with this file + */ + if (vflag && vfpart) { + (void)putc('\n', listf); + vfpart = 0; + } + rdfile_close(arcn, &fd); + continue; + } + + /* + * Add file data to the archive, quit on write error. if we + * cannot write the entire file contents to the archive we + * must pad the archive to replace the missing file data + * (otherwise during an extract the file header for the file + * which FOLLOWS this one will not be where we expect it to + * be). + */ + res = (*frmt->wr_data)(arcn, fd, &cnt); + rdfile_close(arcn, &fd); + if (vflag && vfpart) { + (void)putc('\n', listf); + vfpart = 0; + } + if (res < 0) + break; + + /* + * pad as required, cnt is number of bytes not written + */ + if (((cnt > 0) && (wr_skip(cnt) < 0)) || + ((arcn->pad > 0) && (wr_skip(arcn->pad) < 0))) + break; + } + + /* + * tell format to write trailer; pad to block boundary; reset directory + * mode/access times, and check if all patterns supplied by the user + * were matched. block off signals to avoid chance for multiple entry + * into the cleanup code + */ + if (wr_one) { + (*frmt->end_wr)(); + wr_fin(); + } + (void)sigprocmask(SIG_BLOCK, &s_mask, NULL); + ar_close(); + if (tflag) + proc_dir(); + ftree_chk(); + + return 0; +} + +/* + * append() + * Add file to previously written archive. Archive format specified by the + * user must agree with archive. The archive is read first to collect + * modification times (if -u) and locate the archive trailer. The archive + * is positioned in front of the record with the trailer and wr_archive() + * is called to add the new members. + * PAX IMPLEMENTATION DETAIL NOTE: + * -u is implemented by adding the new members to the end of the archive. + * Care is taken so that these do not end up as links to the older + * version of the same file already stored in the archive. It is expected + * when extraction occurs these newer versions will over-write the older + * ones stored "earlier" in the archive (this may be a bad assumption as + * it depends on the implementation of the program doing the extraction). + * It is really difficult to splice in members without either re-writing + * the entire archive (from the point were the old version was), or having + * assistance of the format specification in terms of a special update + * header that invalidates a previous archive record. The posix spec left + * the method used to implement -u unspecified. This pax is able to + * over write existing files that it creates. + */ + +int +append(void) +{ + ARCHD *arcn; + int res; + FSUB *orgfrmt; + int udev; + off_t tlen; + + arcn = &archd; + orgfrmt = frmt; + + /* + * Do not allow an append operation if the actual archive is of a + * different format than the user specified format. + */ + if (get_arc() < 0) + return 1; + if ((orgfrmt != NULL) && (orgfrmt != frmt)) { + tty_warn(1, "Cannot mix current archive format %s with %s", + frmt->name, orgfrmt->name); + return 1; + } + + /* + * pass the format any options and start up format + */ + if (((*frmt->options)() < 0) || ((*frmt->st_rd)() < 0)) + return 1; + + /* + * if we only are adding members that are newer, we need to save the + * mod times for all files we see. + */ + if (uflag && (ftime_start() < 0)) + return 1; + + /* + * some archive formats encode hard links by recording the device and + * file serial number (inode) but copy the file anyway (multiple times) + * to the archive. When we append, we run the risk that newly added + * files may have the same device and inode numbers as those recorded + * on the archive but during a previous run. If this happens, when the + * archive is extracted we get INCORRECT hard links. We avoid this by + * remapping the device numbers so that newly added files will never + * use the same device number as one found on the archive. remapping + * allows new members to safely have links among themselves. remapping + * also avoids problems with file inode (serial number) truncations + * when the inode number is larger than storage space in the archive + * header. See the remap routines for more details. + */ + if ((udev = frmt->udev) && (dev_start() < 0)) + return 1; + + /* + * reading the archive may take a long time. If verbose tell the user + */ + if (vflag || Vflag) { + (void)fprintf(listf, + "%s: Reading archive to position at the end...", argv0); + vfpart = 1; + } + + /* + * step through the archive until the format says it is done + */ + while (next_head(arcn) == 0) { + /* + * check if this file meets user specified options. + */ + if (sel_chk(arcn) != 0) { + if (rd_skip(arcn->skip + arcn->pad) == 1) + break; + continue; + } + + if (uflag) { + /* + * see if this is the newest version of this file has + * already been seen, if so skip. + */ + if ((res = chk_ftime(arcn)) < 0) + break; + if (res > 0) { + if (rd_skip(arcn->skip + arcn->pad) == 1) + break; + continue; + } + } + + /* + * Store this device number. Device numbers seen during the + * read phase of append will cause newly appended files with a + * device number seen in the old part of the archive to be + * remapped to an unused device number. + */ + if ((udev && (add_dev(arcn) < 0)) || + (rd_skip(arcn->skip + arcn->pad) == 1)) + break; + } + + /* + * done, finish up read and get the number of bytes to back up so we + * can add new members. The format might have used the hard link table, + * purge it. + */ + tlen = (*frmt->end_rd)(); + lnk_end(); + + /* + * try to position for write, if this fails quit. if any error occurs, + * we will refuse to write + */ + if (appnd_start(tlen) < 0) + return 1; + + /* + * tell the user we are done reading. + */ + if ((vflag || Vflag) && vfpart) { + (void)safe_print("done.\n", listf); + vfpart = 0; + } + + /* + * go to the writing phase to add the new members + */ + res = wr_archive(arcn, 1); + if (res == 1) { + /* + * wr_archive failed in some way, but before any files were + * added. These are the only steps needed to cleanup (and + * not truncate the archive). + */ + wr_fin(); + (void)sigprocmask(SIG_BLOCK, &s_mask, NULL); + ar_close(); + } + return res; +} + +/* + * archive() + * write a new archive + */ + +int +archive(void) +{ + + /* + * if we only are adding members that are newer, we need to save the + * mod times for all files; set up for writing; pass the format any + * options write the archive + */ + if ((uflag && (ftime_start() < 0)) || (wr_start() < 0)) + return 1; + if ((*frmt->options)() < 0) + return 1; + + return wr_archive(&archd, 0); +} + +/* + * copy() + * copy files from one part of the file system to another. this does not + * use any archive storage. The EFFECT OF THE COPY IS THE SAME as if an + * archive was written and then extracted in the destination directory + * (except the files are forced to be under the destination directory). + */ + +int +copy(void) +{ + ARCHD *arcn; + int res; + int fddest; + char *dest_pt; + size_t dlen; + size_t drem; + int fdsrc = -1; + struct stat sb; + char dirbuf[PAXPATHLEN+1]; + + arcn = &archd; + /* + * set up the destination dir path and make sure it is a directory. We + * make sure we have a trailing / on the destination + */ + dlen = strlcpy(dirbuf, dirptr, sizeof(dirbuf)); + if (dlen >= sizeof(dirbuf) || + (dlen == sizeof(dirbuf) - 1 && dirbuf[dlen - 1] != '/')) { + tty_warn(1, "directory name is too long %s", dirptr); + return 1; + } + dest_pt = dirbuf + dlen; + if (*(dest_pt-1) != '/') { + *dest_pt++ = '/'; + ++dlen; + } + *dest_pt = '\0'; + drem = PAXPATHLEN - dlen; + + if (stat(dirptr, &sb) < 0) { + syswarn(1, errno, "Cannot access destination directory %s", + dirptr); + return 1; + } + if (!S_ISDIR(sb.st_mode)) { + tty_warn(1, "Destination is not a directory %s", dirptr); + return 1; + } + + /* + * start up the hard link table; file traversal routines and the + * modification time and access mode database + */ + if ((lnk_start() < 0) || (ftree_start() < 0) || (dir_start() < 0)) + return 1; + + /* + * When we are doing interactive rename, we store the mapping of names + * so we can fix up hard links files later in the archive. + */ + if (iflag && (name_start() < 0)) + return 1; + + /* + * set up to cp file trees + */ + cp_start(); + + /* + * while there are files to archive, process them + */ + while (next_file(arcn) == 0) { + fdsrc = -1; + + /* + * check if this file meets user specified options + */ + if (sel_chk(arcn) != 0) + continue; + + /* + * if there is already a file in the destination directory with + * the same name and it is newer, skip the one stored on the + * archive. + * NOTE: this test is done BEFORE name modifications as + * specified by pax. this can be confusing to the user who + * might expect the test to be done on an existing file AFTER + * the name mod. In honesty the pax spec is probably flawed in + * this respect + */ + if (uflag || Dflag) { + /* + * create the destination name + */ + if (strlcpy(dest_pt, arcn->name + (*arcn->name == '/'), + drem + 1) > drem) { + tty_warn(1, "Destination pathname too long %s", + arcn->name); + continue; + } + + /* + * if existing file is same age or newer skip + */ + res = lstat(dirbuf, &sb); + *dest_pt = '\0'; + + if (res == 0) { + if (uflag && Dflag) { + if ((arcn->sb.st_mtime<=sb.st_mtime) && + (arcn->sb.st_ctime<=sb.st_ctime)) + continue; + } else if (Dflag) { + if (arcn->sb.st_ctime <= sb.st_ctime) + continue; + } else if (arcn->sb.st_mtime <= sb.st_mtime) + continue; + } + } + + /* + * this file is considered selected. See if this is a hard link + * to a previous file; modify the name as requested by the + * user; set the final destination. + */ + ftree_sel(arcn); + if ((chk_lnk(arcn) < 0) || ((res = mod_name(arcn, RENM)) < 0)) + break; + if ((res > 0) || (set_dest(arcn, dirbuf, dlen) < 0)) { + /* + * skip file, purge from link table + */ + purg_lnk(arcn); + continue; + } + + /* + * Non standard -Y and -Z flag. When the exisiting file is + * same age or newer skip + */ + if ((Yflag || Zflag) && ((lstat(arcn->name, &sb) == 0))) { + if (Yflag && Zflag) { + if ((arcn->sb.st_mtime <= sb.st_mtime) && + (arcn->sb.st_ctime <= sb.st_ctime)) + continue; + } else if (Yflag) { + if (arcn->sb.st_ctime <= sb.st_ctime) + continue; + } else if (arcn->sb.st_mtime <= sb.st_mtime) + continue; + } + + if (vflag) { + (void)safe_print(arcn->name, listf); + vfpart = 1; + } + ++flcnt; + + /* + * try to create a hard link to the src file if requested + * but make sure we are not trying to overwrite ourselves. + */ + if (lflag) + res = cross_lnk(arcn); + else + res = chk_same(arcn); + if (res <= 0) { + if (vflag && vfpart) { + (void)putc('\n', listf); + vfpart = 0; + } + continue; + } + + /* + * have to create a new file + */ + if ((arcn->type != PAX_REG) && (arcn->type != PAX_CTG)) { + /* + * create a link or special file + */ + if ((arcn->type == PAX_HLK) || + (arcn->type == PAX_HRG)) { + int payload; + + res = lnk_creat(arcn, &payload); + } else { + res = node_creat(arcn); + } + if (res < 0) + purg_lnk(arcn); + if (vflag && vfpart) { + (void)putc('\n', listf); + vfpart = 0; + } + continue; + } + + /* + * have to copy a regular file to the destination directory. + * first open source file and then create the destination file + */ + if ((fdsrc = open(arcn->org_name, O_RDONLY, 0)) < 0) { + syswarn(1, errno, "Unable to open %s to read", + arcn->org_name); + purg_lnk(arcn); + continue; + } + if ((fddest = file_creat(arcn, 0)) < 0) { + rdfile_close(arcn, &fdsrc); + purg_lnk(arcn); + continue; + } + + /* + * copy source file data to the destination file + */ + cp_file(arcn, fdsrc, fddest); + file_close(arcn, fddest); + rdfile_close(arcn, &fdsrc); + + if (vflag && vfpart) { + (void)putc('\n', listf); + vfpart = 0; + } + } + + /* + * restore directory modes and times as required; make sure all + * patterns were selected block off signals to avoid chance for + * multiple entry into the cleanup code. + */ + (void)sigprocmask(SIG_BLOCK, &s_mask, NULL); + ar_close(); + proc_dir(); + ftree_chk(); + + return 0; +} + +/* + * next_head() + * try to find a valid header in the archive. Uses format specific + * routines to extract the header and id the trailer. Trailers may be + * located within a valid header or in an invalid header (the location + * is format specific. The inhead field from the option table tells us + * where to look for the trailer). + * We keep reading (and resyncing) until we get enough contiguous data + * to check for a header. If we cannot find one, we shift by a byte + * add a new byte from the archive to the end of the buffer and try again. + * If we get a read error, we throw out what we have (as we must have + * contiguous data) and start over again. + * ASSUMED: headers fit within a BLKMULT header. + * Return: + * 0 if we got a header, -1 if we are unable to ever find another one + * (we reached the end of input, or we reached the limit on retries. see + * the specs for rd_wrbuf() for more details) + */ + +static int +next_head(ARCHD *arcn) +{ + int ret; + char *hdend; + int res; + int shftsz; + int hsz; + int in_resync = 0; /* set when we are in resync mode */ + int cnt = 0; /* counter for trailer function */ + int first = 1; /* on 1st read, EOF isn't premature. */ + + /* + * set up initial conditions, we want a whole frmt->hsz block as we + * have no data yet. + */ + res = hsz = frmt->hsz; + hdend = hdbuf; + shftsz = hsz - 1; + for(;;) { + /* + * keep looping until we get a contiguous FULL buffer + * (frmt->hsz is the proper size) + */ + for (;;) { + if ((ret = rd_wrbuf(hdend, res)) == res) + break; + + /* + * If we read 0 bytes (EOF) from an archive when we + * expect to find a header, we have stepped upon + * an archive without the customary block of zeroes + * end marker. It's just stupid to error out on + * them, so exit gracefully. + */ + if (first && ret == 0) + return -1; + first = 0; + + /* + * some kind of archive read problem, try to resync the + * storage device, better give the user the bad news. + */ + if ((ret == 0) || (rd_sync() < 0)) { + tty_warn(1, + "Premature end of file on archive read"); + return -1; + } + if (!in_resync) { + if (act == APPND) { + tty_warn(1, + "Archive I/O error, cannot continue"); + return -1; + } + tty_warn(1, + "Archive I/O error. Trying to recover."); + ++in_resync; + } + + /* + * oh well, throw it all out and start over + */ + res = hsz; + hdend = hdbuf; + } + + /* + * ok we have a contiguous buffer of the right size. Call the + * format read routine. If this was not a valid header and this + * format stores trailers outside of the header, call the + * format specific trailer routine to check for a trailer. We + * have to watch out that we do not mis-identify file data or + * block padding as a header or trailer. Format specific + * trailer functions must NOT check for the trailer while we + * are running in resync mode. Some trailer functions may tell + * us that this block cannot contain a valid header either, so + * we then throw out the entire block and start over. + */ + if ((*frmt->rd)(arcn, hdbuf) == 0) + break; + + if (!frmt->inhead) { + /* + * this format has trailers outside of valid headers + */ + if ((ret = (*frmt->trail)(hdbuf,in_resync,&cnt)) == 0){ + /* + * valid trailer found, drain input as required + */ + ar_drain(); + return -1; + } + + if (ret == 1) { + /* + * we are in resync and we were told to throw + * the whole block out because none of the + * bytes in this block can be used to form a + * valid header + */ + res = hsz; + hdend = hdbuf; + continue; + } + } + + /* + * Brute force section. + * not a valid header. We may be able to find a header yet. So + * we shift over by one byte, and set up to read one byte at a + * time from the archive and place it at the end of the buffer. + * We will keep moving byte at a time until we find a header or + * get a read error and have to start over. + */ + if (!in_resync) { + if (act == APPND) { + tty_warn(1, + "Unable to append, archive header flaw"); + return -1; + } + tty_warn(1, + "Invalid header, starting valid header search."); + ++in_resync; + } + memmove(hdbuf, hdbuf+1, shftsz); + res = 1; + hdend = hdbuf + shftsz; + } + + /* + * ok got a valid header, check for trailer if format encodes it in the + * the header. NOTE: the parameters are different than trailer routines + * which encode trailers outside of the header! + */ + if (frmt->inhead && ((*frmt->subtrail)(arcn) == 0)) { + /* + * valid trailer found, drain input as required + */ + ar_drain(); + return -1; + } + + ++flcnt; + return 0; +} + +/* + * get_arc() + * Figure out what format an archive is. Handles archive with flaws by + * brute force searches for a legal header in any supported format. The + * format id routines have to be careful to NOT mis-identify a format. + * ASSUMED: headers fit within a BLKMULT header. + * Return: + * 0 if archive found -1 otherwise + */ + +static int +get_arc(void) +{ + int i; + int hdsz = 0; + int res; + int minhd = BLKMULT; + char *hdend; + int notice = 0; + + /* + * find the smallest header size in all archive formats and then set up + * to read the archive. + */ + for (i = 0; ford[i] >= 0; ++i) { + if (fsub[ford[i]].hsz < minhd) + minhd = fsub[ford[i]].hsz; + } + if (rd_start() < 0) + return -1; + res = BLKMULT; + hdsz = 0; + hdend = hdbuf; + for(;;) { + for (;;) { + /* + * fill the buffer with at least the smallest header + */ + i = rd_wrbuf(hdend, res); + if (i > 0) + hdsz += i; + if (hdsz >= minhd) + break; + + /* + * if we cannot recover from a read error quit + */ + if ((i == 0) || (rd_sync() < 0)) + goto out; + + /* + * when we get an error none of the data we already + * have can be used to create a legal header (we just + * got an error in the middle), so we throw it all out + * and refill the buffer with fresh data. + */ + res = BLKMULT; + hdsz = 0; + hdend = hdbuf; + if (!notice) { + if (act == APPND) + return -1; + tty_warn(1, + "Cannot identify format. Searching..."); + ++notice; + } + } + + /* + * we have at least the size of the smallest header in any + * archive format. Look to see if we have a match. The array + * ford[] is used to specify the header id order to reduce the + * chance of incorrectly id'ing a valid header (some formats + * may be subsets of each other and the order would then be + * important). + */ + for (i = 0; ford[i] >= 0; ++i) { + if ((*fsub[ford[i]].id)(hdbuf, hdsz) < 0) + continue; + frmt = &(fsub[ford[i]]); + /* + * yuck, to avoid slow special case code in the extract + * routines, just push this header back as if it was + * not seen. We have left extra space at start of the + * buffer for this purpose. This is a bit ugly, but + * adding all the special case code is far worse. + */ + pback(hdbuf, hdsz); + return 0; + } + + /* + * We have a flawed archive, no match. we start searching, but + * we never allow additions to flawed archives + */ + if (!notice) { + if (act == APPND) + return -1; + tty_warn(1, "Cannot identify format. Searching..."); + ++notice; + } + + /* + * brute force search for a header that we can id. + * we shift through byte at a time. this is slow, but we cannot + * determine the nature of the flaw in the archive in a + * portable manner + */ + if (--hdsz > 0) { + memmove(hdbuf, hdbuf+1, hdsz); + res = BLKMULT - hdsz; + hdend = hdbuf + hdsz; + } else { + res = BLKMULT; + hdend = hdbuf; + hdsz = 0; + } + } + + out: + /* + * we cannot find a header, bow, apologize and quit + */ + tty_warn(1, "Sorry, unable to determine archive format."); + return -1; +} diff --git a/bin/pax/buf_subs.c b/bin/pax/buf_subs.c new file mode 100644 index 0000000..e4b97af --- /dev/null +++ b/bin/pax/buf_subs.c @@ -0,0 +1,1022 @@ +/* $NetBSD: buf_subs.c,v 1.29 2018/03/19 03:11:39 msaitoh Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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 HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +#if 0 +static char sccsid[] = "@(#)buf_subs.c 8.2 (Berkeley) 4/18/94"; +#else +__RCSID("$NetBSD: buf_subs.c,v 1.29 2018/03/19 03:11:39 msaitoh Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include "pax.h" +#include "extern.h" + +/* + * routines which implement archive and file buffering + */ + +#define MINFBSZ 512 /* default block size for hole detect */ +#define MAXFLT 10 /* default media read error limit */ + +/* + * Need to change bufmem to dynamic allocation when the upper + * limit on blocking size is removed (though that will violate pax spec) + * MAXBLK define and tests will also need to be updated. + */ +static char bufmem[MAXBLK+BLKMULT]; /* i/o buffer + pushback id space */ +static char *buf; /* normal start of i/o buffer */ +static char *bufend; /* end or last char in i/o buffer */ +static char *bufpt; /* read/write point in i/o buffer */ +int blksz = MAXBLK; /* block input/output size in bytes */ +int wrblksz; /* user spec output size in bytes */ +int maxflt = MAXFLT; /* MAX consecutive media errors */ +int rdblksz; /* first read blksize (tapes only) */ +off_t wrlimit; /* # of bytes written per archive vol */ +off_t wrcnt; /* # of bytes written on current vol */ +off_t rdcnt; /* # of bytes read on current vol */ + +/* + * wr_start() + * set up the buffering system to operate in a write mode + * Return: + * 0 if ok, -1 if the user specified write block size violates pax spec + */ + +int +wr_start(void) +{ + buf = &(bufmem[BLKMULT]); + /* + * Check to make sure the write block size meets pax specs. If the user + * does not specify a blocksize, we use the format default blocksize. + * We must be picky on writes, so we do not allow the user to create an + * archive that might be hard to read elsewhere. If all ok, we then + * open the first archive volume + */ + if (!wrblksz) + wrblksz = frmt->bsz; + if (wrblksz > MAXBLK) { + tty_warn(1, "Write block size of %d too large, maximum is: %d", + wrblksz, MAXBLK); + return -1; + } + if (wrblksz % BLKMULT) { + tty_warn(1, "Write block size of %d is not a %d byte multiple", + wrblksz, BLKMULT); + return -1; + } + + /* + * we only allow wrblksz to be used with all archive operations + */ + blksz = rdblksz = wrblksz; + if ((ar_open(arcname) < 0) && (ar_next() < 0)) + return -1; + wrcnt = 0; + bufend = buf + wrblksz; + bufpt = buf; + return 0; +} + +/* + * rd_start() + * set up buffering system to read an archive + * Return: + * 0 if ok, -1 otherwise + */ + +int +rd_start(void) +{ + /* + * leave space for the header pushback (see get_arc()). If we are + * going to append and user specified a write block size, check it + * right away + */ + buf = &(bufmem[BLKMULT]); + if ((act == APPND) && wrblksz) { + if (wrblksz > MAXBLK) { + tty_warn(1, + "Write block size %d too large, maximum is: %d", + wrblksz, MAXBLK); + return -1; + } + if (wrblksz % BLKMULT) { + tty_warn(1, + "Write block size %d is not a %d byte multiple", + wrblksz, BLKMULT); + return -1; + } + } + + /* + * open the archive + */ + if ((ar_open(arcname) < 0) && (ar_next() < 0)) + return -1; + bufend = buf + rdblksz; + bufpt = bufend; + rdcnt = 0; + return 0; +} + +/* + * cp_start() + * set up buffer system for copying within the file system + */ + +void +cp_start(void) +{ + buf = &(bufmem[BLKMULT]); + rdblksz = blksz = MAXBLK; +} + +/* + * appnd_start() + * Set up the buffering system to append new members to an archive that + * was just read. The last block(s) of an archive may contain a format + * specific trailer. To append a new member, this trailer has to be + * removed from the archive. The first byte of the trailer is replaced by + * the start of the header of the first file added to the archive. The + * format specific end read function tells us how many bytes to move + * backwards in the archive to be positioned BEFORE the trailer. Two + * different positions have to be adjusted, the O.S. file offset (e.g. the + * position of the tape head) and the write point within the data we have + * stored in the read (soon to become write) buffer. We may have to move + * back several records (the number depends on the size of the archive + * record and the size of the format trailer) to read up the record where + * the first byte of the trailer is recorded. Trailers may span (and + * overlap) record boundaries. + * We first calculate which record has the first byte of the trailer. We + * move the OS file offset back to the start of this record and read it + * up. We set the buffer write pointer to be at this byte (the byte where + * the trailer starts). We then move the OS file pointer back to the + * start of this record so a flush of this buffer will replace the record + * in the archive. + * A major problem is rewriting this last record. For archives stored + * on disk files, this is trivial. However, many devices are really picky + * about the conditions under which they will allow a write to occur. + * Often devices restrict the conditions where writes can be made, + * so it may not be feasable to append archives stored on all types of + * devices. + * Return: + * 0 for success, -1 for failure + */ + +int +appnd_start(off_t skcnt) +{ + int res; + off_t cnt; + + if (exit_val != 0) { + tty_warn(0, "Cannot append to an archive that may have flaws."); + return -1; + } + /* + * if the user did not specify a write blocksize, inherit the size used + * in the last archive volume read. (If a is set we still use rdblksz + * until next volume, cannot shift sizes within a single volume). + */ + if (!wrblksz) + wrblksz = blksz = rdblksz; + else + blksz = rdblksz; + + /* + * make sure that this volume allows appends + */ + if (ar_app_ok() < 0) + return -1; + + /* + * Calculate bytes to move back and move in front of record where we + * need to start writing from. Remember we have to add in any padding + * that might be in the buffer after the trailer in the last block. We + * travel skcnt + padding ROUNDED UP to blksize. + */ + skcnt += bufend - bufpt; + if ((cnt = (skcnt/blksz) * blksz) < skcnt) + cnt += blksz; + if (ar_rev((off_t)cnt) < 0) + goto out; + + /* + * We may have gone too far if there is valid data in the block we are + * now in front of, read up the block and position the pointer after + * the valid data. + */ + if ((cnt -= skcnt) > 0) { + /* + * watch out for stupid tape drives. ar_rev() will set rdblksz + * to be real physical blocksize so we must loop until we get + * the old rdblksz (now in blksz). If ar_rev() fouls up the + * determination of the physical block size, we will fail. + */ + bufpt = buf; + bufend = buf + blksz; + while (bufpt < bufend) { + if ((res = ar_read(bufpt, rdblksz)) <= 0) + goto out; + bufpt += res; + } + if (ar_rev((off_t)(bufpt - buf)) < 0) + goto out; + bufpt = buf + cnt; + bufend = buf + blksz; + } else { + /* + * buffer is empty + */ + bufend = buf + blksz; + bufpt = buf; + } + rdblksz = blksz; + rdcnt -= skcnt; + wrcnt = 0; + + /* + * At this point we are ready to write. If the device requires special + * handling to write at a point were previously recorded data resides, + * that is handled in ar_set_wr(). From now on we operate under normal + * ARCHIVE mode (write) conditions + */ + if (ar_set_wr() < 0) + return -1; + act = ARCHIVE; + return 0; + + out: + tty_warn(1, "Unable to rewrite archive trailer, cannot append."); + return -1; +} + +/* + * rd_sync() + * A read error occurred on this archive volume. Resync the buffer and + * try to reset the device (if possible) so we can continue to read. Keep + * trying to do this until we get a valid read, or we reach the limit on + * consecutive read faults (at which point we give up). The user can + * adjust the read error limit through a command line option. + * Returns: + * 0 on success, and -1 on failure + */ + +int +rd_sync(void) +{ + int errcnt = 0; + int res; + + /* + * if the user says bail out on first fault, we are out of here... + */ + if (maxflt == 0) + return -1; + if (act == APPND) { + tty_warn(1, + "Unable to append when there are archive read errors."); + return -1; + } + + /* + * poke at device and try to get past media error + */ + if (ar_rdsync() < 0) { + if (ar_next() < 0) + return -1; + else + rdcnt = 0; + } + + for (;;) { + if ((res = ar_read(buf, blksz)) > 0) { + /* + * All right! got some data, fill that buffer + */ + bufpt = buf; + bufend = buf + res; + rdcnt += res; + return 0; + } + + /* + * Oh well, yet another failed read... + * if error limit reached, ditch. otherwise poke device to move past + * bad media and try again. if media is badly damaged, we ask + * the poor (and upset user at this point) for the next archive + * volume. remember the goal on reads is to get the most we + * can extract out of the archive. + */ + if ((maxflt > 0) && (++errcnt > maxflt)) + tty_warn(0, + "Archive read error limit (%d) reached",maxflt); + else if (ar_rdsync() == 0) + continue; + if (ar_next() < 0) + break; + rdcnt = 0; + errcnt = 0; + } + return -1; +} + +/* + * pback() + * push the data used during the archive id phase back into the I/O + * buffer. This is required as we cannot be sure that the header does NOT + * overlap a block boundary (as in the case we are trying to recover a + * flawed archived). This was not designed to be used for any other + * purpose. (What software engineering, HA!) + * WARNING: do not even THINK of pback greater than BLKMULT, unless the + * pback space is increased. + */ + +void +pback(char *pt, int cnt) +{ + bufpt -= cnt; + memcpy(bufpt, pt, cnt); + return; +} + +/* + * rd_skip() + * skip forward in the archive during an archive read. Used to get quickly + * past file data and padding for files the user did NOT select. + * Return: + * 0 if ok, -1 failure, and 1 when EOF on the archive volume was detected. + */ + +int +rd_skip(off_t skcnt) +{ + off_t res; + off_t cnt; + off_t skipped = 0; + + /* + * consume what data we have in the buffer. If we have to move forward + * whole records, we call the low level skip function to see if we can + * move within the archive without doing the expensive reads on data we + * do not want. + */ + if (skcnt == 0) + return 0; + res = MIN((bufend - bufpt), skcnt); + bufpt += res; + skcnt -= res; + + /* + * if skcnt is now 0, then no additional i/o is needed + */ + if (skcnt == 0) + return 0; + + /* + * We have to read more, calculate complete and partial record reads + * based on rdblksz. we skip over "cnt" complete records + */ + res = skcnt%rdblksz; + cnt = (skcnt/rdblksz) * rdblksz; + + /* + * if the skip fails, we will have to resync. ar_fow will tell us + * how much it can skip over. We will have to read the rest. + */ + if (ar_fow(cnt, &skipped) < 0) + return -1; + res += cnt - skipped; + rdcnt += skipped; + + /* + * what is left we have to read (which may be the whole thing if + * ar_fow() told us the device can only read to skip records); + */ + while (res > 0L) { + cnt = bufend - bufpt; + /* + * if the read fails, we will have to resync + */ + if ((cnt <= 0) && ((cnt = buf_fill()) < 0)) + return -1; + if (cnt == 0) + return 1; + cnt = MIN(cnt, res); + bufpt += cnt; + res -= cnt; + } + return 0; +} + +/* + * wr_fin() + * flush out any data (and pad if required) the last block. We always pad + * with zero (even though we do not have to). Padding with 0 makes it a + * lot easier to recover if the archive is damaged. zero paddding SHOULD + * BE a requirement.... + */ + +void +wr_fin(void) +{ + if (bufpt > buf) { + memset(bufpt, 0, bufend - bufpt); + bufpt = bufend; + (void)buf_flush(blksz); + } +} + +/* + * wr_rdbuf() + * fill the write buffer from data passed to it in a buffer (usually used + * by format specific write routines to pass a file header). On failure we + * punt. We do not allow the user to continue to write flawed archives. + * We assume these headers are not very large (the memory copy we use is + * a bit expensive). + * Return: + * 0 if buffer was filled ok, -1 o.w. (buffer flush failure) + */ + +int +wr_rdbuf(char *out, int outcnt) +{ + int cnt; + + /* + * while there is data to copy into the write buffer. when the + * write buffer fills, flush it to the archive and continue + */ + while (outcnt > 0) { + cnt = bufend - bufpt; + if ((cnt <= 0) && ((cnt = buf_flush(blksz)) < 0)) + return -1; + /* + * only move what we have space for + */ + cnt = MIN(cnt, outcnt); + memcpy(bufpt, out, cnt); + bufpt += cnt; + out += cnt; + outcnt -= cnt; + } + return 0; +} + +/* + * rd_wrbuf() + * copy from the read buffer into a supplied buffer a specified number of + * bytes. If the read buffer is empty fill it and continue to copy. + * usually used to obtain a file header for processing by a format + * specific read routine. + * Return + * number of bytes copied to the buffer, 0 indicates EOF on archive volume, + * -1 is a read error + */ + +int +rd_wrbuf(char *in, int cpcnt) +{ + int res; + int cnt; + int incnt = cpcnt; + + /* + * loop until we fill the buffer with the requested number of bytes + */ + while (incnt > 0) { + cnt = bufend - bufpt; + if ((cnt <= 0) && ((cnt = buf_fill()) <= 0)) { + /* + * read error, return what we got (or the error if + * no data was copied). The caller must know that an + * error occurred and has the best knowledge what to + * do with it + */ + if ((res = cpcnt - incnt) > 0) + return res; + return cnt; + } + + /* + * calculate how much data to copy based on whats left and + * state of buffer + */ + cnt = MIN(cnt, incnt); + memcpy(in, bufpt, cnt); + bufpt += cnt; + incnt -= cnt; + in += cnt; + } + return cpcnt; +} + +/* + * wr_skip() + * skip forward during a write. In other words add padding to the file. + * we add zero filled padding as it makes flawed archives much easier to + * recover from. the caller tells us how many bytes of padding to add + * This routine was not designed to add HUGE amount of padding, just small + * amounts (a few 512 byte blocks at most) + * Return: + * 0 if ok, -1 if there was a buf_flush failure + */ + +int +wr_skip(off_t skcnt) +{ + int cnt; + + /* + * loop while there is more padding to add + */ + while (skcnt > 0L) { + cnt = bufend - bufpt; + if ((cnt <= 0) && ((cnt = buf_flush(blksz)) < 0)) + return -1; + cnt = MIN(cnt, skcnt); + memset(bufpt, 0, cnt); + bufpt += cnt; + skcnt -= cnt; + } + return 0; +} + +/* + * wr_rdfile() + * fill write buffer with the contents of a file. We are passed an open + * file descriptor to the file an the archive structure that describes the + * file we are storing. The variable "left" is modified to contain the + * number of bytes of the file we were NOT able to write to the archive. + * it is important that we always write EXACTLY the number of bytes that + * the format specific write routine told us to. The file can also get + * bigger, so reading to the end of file would create an improper archive, + * we just detect this case and warn the user. We never create a bad + * archive if we can avoid it. Of course trying to archive files that are + * active is asking for trouble. It we fail, we pass back how much we + * could NOT copy and let the caller deal with it. + * Return: + * 0 ok, -1 if archive write failure. a short read of the file returns a + * 0, but "left" is set to be greater than zero. + */ + +int +wr_rdfile(ARCHD *arcn, int ifd, off_t *left) +{ + int cnt; + int res = 0; + off_t size = arcn->sb.st_size; + struct stat origsb, sb; + + /* + * by default, remember the previously obtained stat information + * (in arcn->sb) for comparing the mtime after reading. + * if Mflag is set, use the actual mtime instead. + */ + origsb = arcn->sb; + if (Mflag && (fstat(ifd, &origsb) < 0)) + syswarn(1, errno, "Failed stat on %s", arcn->org_name); + + /* + * while there are more bytes to write + */ + while (size > 0L) { + cnt = bufend - bufpt; + if ((cnt <= 0) && ((cnt = buf_flush(blksz)) < 0)) { + *left = size; + return -1; + } + cnt = MIN(cnt, size); + if ((res = read_with_restart(ifd, bufpt, cnt)) <= 0) + break; + size -= res; + bufpt += res; + } + + /* + * better check the file did not change during this operation + * or the file read failed. + */ + if (res < 0) + syswarn(1, errno, "Read fault on %s", arcn->org_name); + else if (size != 0L) + tty_warn(1, "File changed size during read %s", arcn->org_name); + else if (fstat(ifd, &sb) < 0) + syswarn(1, errno, "Failed stat on %s", arcn->org_name); + else if (origsb.st_mtime != sb.st_mtime) + tty_warn(1, "File %s was modified during copy to archive", + arcn->org_name); + *left = size; + return 0; +} + +/* + * rd_wrfile() + * extract the contents of a file from the archive. If we are unable to + * extract the entire file (due to failure to write the file) we return + * the numbers of bytes we did NOT process. This way the caller knows how + * many bytes to skip past to find the next archive header. If the failure + * was due to an archive read, we will catch that when we try to skip. If + * the format supplies a file data crc value, we calculate the actual crc + * so that it can be compared to the value stored in the header + * NOTE: + * We call a special function to write the file. This function attempts to + * restore file holes (blocks of zeros) into the file. When files are + * sparse this saves space, and is a LOT faster. For non sparse files + * the performance hit is small. As of this writing, no archive supports + * information on where the file holes are. + * Return: + * 0 ok, -1 if archive read failure. if we cannot write the entire file, + * we return a 0 but "left" is set to be the amount unwritten + */ + +int +rd_wrfile(ARCHD *arcn, int ofd, off_t *left) +{ + int cnt = 0; + off_t size = arcn->sb.st_size; + int res = 0; + char *fnm = arcn->name; + int isem = 1; + int rem; + int sz = MINFBSZ; + struct stat sb; + u_long crc = 0L; + + /* + * pass the blocksize of the file being written to the write routine, + * if the size is zero, use the default MINFBSZ + */ + if (ofd < 0) + sz = PAXPATHLEN+1; + else if (fstat(ofd, &sb) == 0) { + if (sb.st_blksize > 0) + sz = (int)sb.st_blksize; + } else + syswarn(0, errno, + "Unable to obtain block size for file %s", fnm); + rem = sz; + *left = 0L; + + /* + * Copy the archive to the file the number of bytes specified. We have + * to assume that we want to recover file holes as none of the archive + * formats can record the location of file holes. + */ + while (size > 0L) { + cnt = bufend - bufpt; + /* + * if we get a read error, we do not want to skip, as we may + * miss a header, so we do not set left, but if we get a write + * error, we do want to skip over the unprocessed data. + */ + if ((cnt <= 0) && ((cnt = buf_fill()) <= 0)) + break; + cnt = MIN(cnt, size); + if ((res = file_write(ofd,bufpt,cnt,&rem,&isem,sz,fnm)) <= 0) { + *left = size; + break; + } + + if (docrc) { + /* + * update the actual crc value + */ + cnt = res; + while (--cnt >= 0) + crc += *bufpt++ & 0xff; + } else + bufpt += res; + size -= res; + } + + /* + * if the last block has a file hole (all zero), we must make sure this + * gets updated in the file. We force the last block of zeros to be + * written. just closing with the file offset moved forward may not put + * a hole at the end of the file. + */ + if (ofd >= 0 && isem && (arcn->sb.st_size > 0L)) + file_flush(ofd, fnm, isem); + + /* + * if we failed from archive read, we do not want to skip + */ + if ((size > 0L) && (*left == 0L)) + return -1; + + /* + * some formats record a crc on file data. If so, then we compare the + * calculated crc to the crc stored in the archive + */ + if (docrc && (size == 0L) && (arcn->crc != crc)) + tty_warn(1,"Actual crc does not match expected crc %s", + arcn->name); + return 0; +} + +/* + * cp_file() + * copy the contents of one file to another. used during -rw phase of pax + * just as in rd_wrfile() we use a special write function to write the + * destination file so we can properly copy files with holes. + */ + +void +cp_file(ARCHD *arcn, int fd1, int fd2) +{ + int cnt; + off_t cpcnt = 0L; + int res = 0; + char *fnm = arcn->name; + int no_hole = 0; + int isem = 1; + int rem; + int sz = MINFBSZ; + struct stat sb, origsb; + + /* + * check for holes in the source file. If none, we will use regular + * write instead of file write. + */ + if (((off_t)(arcn->sb.st_blocks * BLKMULT)) >= arcn->sb.st_size) + ++no_hole; + + /* + * by default, remember the previously obtained stat information + * (in arcn->sb) for comparing the mtime after reading. + * if Mflag is set, use the actual mtime instead. + */ + origsb = arcn->sb; + if (Mflag && (fstat(fd1, &origsb) < 0)) + syswarn(1, errno, "Failed stat on %s", arcn->org_name); + + /* + * pass the blocksize of the file being written to the write routine, + * if the size is zero, use the default MINFBSZ + */ + if (fstat(fd2, &sb) == 0) { + if (sb.st_blksize > 0) + sz = sb.st_blksize; + } else + syswarn(0, errno, + "Unable to obtain block size for file %s", fnm); + rem = sz; + + /* + * read the source file and copy to destination file until EOF + */ + for(;;) { + if ((cnt = read_with_restart(fd1, buf, blksz)) <= 0) + break; + if (no_hole) + res = xwrite(fd2, buf, cnt); + else + res = file_write(fd2, buf, cnt, &rem, &isem, sz, fnm); + if (res != cnt) + break; + cpcnt += cnt; + } + + /* + * check to make sure the copy is valid. + */ + if (res < 0) + syswarn(1, errno, "Failed write during copy of %s to %s", + arcn->org_name, arcn->name); + else if (cpcnt != arcn->sb.st_size) + tty_warn(1, "File %s changed size during copy to %s", + arcn->org_name, arcn->name); + else if (fstat(fd1, &sb) < 0) + syswarn(1, errno, "Failed stat of %s", arcn->org_name); + else if (origsb.st_mtime != sb.st_mtime) + tty_warn(1, "File %s was modified during copy to %s", + arcn->org_name, arcn->name); + + /* + * if the last block has a file hole (all zero), we must make sure this + * gets updated in the file. We force the last block of zeros to be + * written. just closing with the file offset moved forward may not put + * a hole at the end of the file. + */ + if (!no_hole && isem && (arcn->sb.st_size > 0L)) + file_flush(fd2, fnm, isem); + return; +} + +/* + * buf_fill() + * fill the read buffer with the next record (or what we can get) from + * the archive volume. + * Return: + * Number of bytes of data in the read buffer, -1 for read error, and + * 0 when finished (user specified termination in ar_next()). + */ + +int +buf_fill(void) +{ + int cnt; + static int fini = 0; + + if (fini) + return 0; + + for(;;) { + /* + * try to fill the buffer. on error the next archive volume is + * opened and we try again. + */ + if ((cnt = ar_read(buf, blksz)) > 0) { + bufpt = buf; + bufend = buf + cnt; + rdcnt += cnt; + return cnt; + } + + /* + * errors require resync, EOF goes to next archive + * but in case we have not determined yet the format, + * this means that we have a very short file, so we + * are done again. + */ + if (cnt < 0) + break; + if (frmt == NULL || ar_next() < 0) { + fini = 1; + return 0; + } + rdcnt = 0; + } + exit_val = 1; + return -1; +} + +/* + * buf_flush() + * force the write buffer to the archive. We are passed the number of + * bytes in the buffer at the point of the flush. When we change archives + * the record size might change. (either larger or smaller). + * Return: + * 0 if all is ok, -1 when a write error occurs. + */ + +int +buf_flush(int bufcnt) +{ + int cnt; + int push = 0; + int totcnt = 0; + + /* + * if we have reached the user specified byte count for each archive + * volume, prompt for the next volume. (The non-standard -R flag). + * NOTE: If the wrlimit is smaller than wrcnt, we will always write + * at least one record. We always round limit UP to next blocksize. + */ + if ((wrlimit > 0) && (wrcnt > wrlimit)) { + tty_warn(0, + "User specified archive volume byte limit reached."); + if (ar_next() < 0) { + wrcnt = 0; + exit_val = 1; + return -1; + } + wrcnt = 0; + + /* + * The new archive volume might have changed the size of the + * write blocksize. if so we figure out if we need to write + * (one or more times), or if there is now free space left in + * the buffer (it is no longer full). bufcnt has the number of + * bytes in the buffer, (the blocksize, at the point we were + * CALLED). Push has the amount of "extra" data in the buffer + * if the block size has shrunk from a volume change. + */ + bufend = buf + blksz; + if (blksz > bufcnt) + return 0; + if (blksz < bufcnt) + push = bufcnt - blksz; + } + + /* + * We have enough data to write at least one archive block + */ + for (;;) { + /* + * write a block and check if it all went out ok + */ + cnt = ar_write(buf, blksz); + if (cnt == blksz) { + /* + * the write went ok + */ + wrcnt += cnt; + totcnt += cnt; + if (push > 0) { + /* we have extra data to push to the front. + * check for more than 1 block of push, and if + * so we loop back to write again + */ + memcpy(buf, bufend, push); + bufpt = buf + push; + if (push >= blksz) { + push -= blksz; + continue; + } + } else + bufpt = buf; + return totcnt; + } else if (cnt > 0) { + /* + * Oh drat we got a partial write! + * if format doesnt care about alignment let it go, + * we warned the user in ar_write().... but this means + * the last record on this volume violates pax spec.... + */ + totcnt += cnt; + wrcnt += cnt; + bufpt = buf + cnt; + cnt = bufcnt - cnt; + memcpy(buf, bufpt, cnt); + bufpt = buf + cnt; + if (!frmt->blkalgn || ((cnt % frmt->blkalgn) == 0)) + return totcnt; + break; + } + + /* + * All done, go to next archive + */ + wrcnt = 0; + if (ar_next() < 0) + break; + + /* + * The new archive volume might also have changed the block + * size. if so, figure out if we have too much or too little + * data for using the new block size + */ + bufend = buf + blksz; + if (blksz > bufcnt) + return 0; + if (blksz < bufcnt) + push = bufcnt - blksz; + } + + /* + * write failed, stop pax. we must not create a bad archive! + */ + exit_val = 1; + return -1; +} diff --git a/bin/pax/cpio.1 b/bin/pax/cpio.1 new file mode 100644 index 0000000..45f5854 --- /dev/null +++ b/bin/pax/cpio.1 @@ -0,0 +1,307 @@ +.\" $NetBSD: cpio.1,v 1.15 2017/07/03 21:33:23 wiz Exp $ +.\" +.\" Copyright (c) 1997 SigmaSoft, Th. Lockert +.\" 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. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" OpenBSD: cpio.1,v 1.14 2000/11/10 17:52:02 aaron Exp +.\" +.Dd June 18, 2011 +.Dt CPIO 1 +.Os +.Sh NAME +.Nm cpio +.Nd copy file archives in and out +.Sh SYNOPSIS +.Nm cpio +.Fl o +.Op Fl AaBcLvZz +.Op Fl C Ar bytes +.Op Fl F Ar archive +.Op Fl H Ar format +.Op Fl O Ar archive +.Ar "< name-list" +.Op Ar "> archive" +.Nm cpio +.Fl i +.Op Fl 6BbcdfmrSstuvZz +.Op Fl C Ar bytes +.Op Fl E Ar file +.Op Fl F Ar archive +.Op Fl H Ar format +.Op Fl I Ar archive +.Op Ar "pattern ..." +.Op Ar "< archive" +.Nm cpio +.Fl p +.Op Fl adLlmuv +.Ar destination-directory +.Ar "< name-list" +.Sh DESCRIPTION +The +.Nm +command copies files to and from a +.Nm +archive. +If the archive is of the form: +.Ar [[user@]host:]file +then the archive will be processed using +.Xr rmt 8 . +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl o , Fl Fl create +Create an archive. +Reads the list of files to store in the +archive from standard input, and writes the archive on standard +output. +.Bl -tag -width Ds +.It Fl a , Fl Fl reset-access-time +Reset the access times on files that have been copied to the +archive. +.It Fl A , Fl Fl append +Append to the specified archive. +.It Fl B +Set block size of output to 5120 bytes. +.It Fl c +Use ASCII format for +.Nm +header for portability. +.It Fl C Ar bytes +Set the block size of output to +.Ar bytes . +.It Fl F Ar archive +.It Fl O Ar archive +Use the specified file name as the archive to write to. +.It Fl H Ar format +Write the archive in the specified format. +Recognized formats are: +.Pp +.Bl -tag -width sv4cpio -compact +.It Ar bcpio +Old binary +.Nm +format. +.It Ar cpio +Old octal character +.Nm +format. +.It Ar sv4cpio +SVR4 hex +.Nm +format. +.It Ar tar +Old tar format. +.It Ar ustar +POSIX ustar format. +.El +.It Fl L +Follow symbolic links. +.It Fl v +Be verbose about operations. +List filenames as they are written to the archive. +.It Fl Fl xz +Compress/decompress archive using +.Xr xz 1 +format. +.It Fl Z +Compress archive using +.Xr compress 1 +format. +.It Fl z +Compress/decompress archive using +.Xr gzip 1 +format. +.El +.It Fl i , Fl Fl extract +Restore files from an archive. +Reads the archive file from +standard input and extracts files matching the +.Ar patterns +that were specified on the command line. +.Bl -tag -width Ds +.It Fl b +Do byte and word swapping after reading in data from the +archive, for restoring archives created on systems with +a different byte order. +.It Fl B +Set the block size of the archive being read to 5120 bytes. +.It Fl c +Expect the archive headers to be in ASCII format. +.It Fl C Ar bytes +Read archive written with a block size of +.Ar bytes . +.It Fl d , Fl Fl make-directories +Create any intermediate directories as needed during +restore. +.It Fl E Ar file , Fl Fl pattern-file Ar file +Read list of file name patterns to extract or list from +.Ar file . +.It Fl f , Fl Fl nonmatching +Restore all files except those matching the +.Ar patterns +given on the command line. +.It Fl F Ar archive , Fl Fl file Ar archive +.It Fl I Ar archive +Use the specified file as the input for the archive. +.It Fl H Ar format , Fl Fl format Ar format +Read an archive of the specified format. +Recognized formats are: +.Pp +.Bl -tag -width sv4cpio -compact +.It Ar bcpio +Old binary +.Nm +format. +.It Ar cpio +Old octal character +.Nm +format. +.It Ar sv4cpio +SVR4 hex +.Nm +format. +.It Ar tar +Old tar format. +.It Ar ustar +POSIX ustar format. +.El +.It Fl m +Restore modification times on files. +.It Fl r , Fl Fl rename +Rename restored files interactively. +.It Fl s +Swap bytes after reading data from the archive. +.It Fl S , Fl Fl swap-halfwords +Swap words after reading data from the archive. +.It Fl t , Fl Fl list +Only list the contents of the archive, no files or +directories will be created. +.It Fl u , Fl Fl unconditional +Overwrite files even when the file in the archive is +older than the one that will be overwritten. +.It Fl v , Fl Fl verbose +Be verbose about operations. +List filenames as they are copied in from the archive. +.It Fl z +Uncompress archive using +.Xr gzip 1 +format. +.It Fl Z +Uncompress archive using +.Xr compress 1 +format. +.It Fl 6 +Process old-style +.Nm +format archives. +.El +.It Fl p , Fl Fl pass-through +Copy files from one location to another in a single pass. +The list of files to copy are read from standard input and +written out to a directory relative to the specified +.Ar directory +argument. +.Bl -tag -width Ds +.It Fl a +Reset the access times on files that have been copied. +.It Fl d +Create any intermediate directories as needed to write +the files at the new location. +.It Fl l , Fl Fl link +When possible, link files rather than creating an +extra copy. +.It Fl L , Fl Fl dereference +Follow symbolic links. +.It Fl m , Fl Fl preserve-modification-time +Restore modification times on files. +.It Fl u , Fl Fl unconditional +Overwrite files even when the original file being copied is +older than the one that will be overwritten. +.It Fl v , Fl Fl verbose +Be verbose about operations. +List filenames as they are copied. +.It Fl Fl force-local +Do not interpret filenames that contain a +.Sq \&: +as remote files. +.It Fl Fl insecure +Normally +.Nm +ignores filenames that contain +.Dq .. +as a path component. +With this option, files that contain +.Dq .. +can be processed. +.El +.El +.Sh EXIT STATUS +.Nm +will exit with one of the following values: +.Bl -tag -width 2n +.It 0 +All files were processed successfully. +.It 1 +An error occurred. +.El +.Pp +Whenever +.Nm +cannot create a file or a link when extracting an archive or cannot +find a file while writing an archive, or cannot preserve the user +ID, group ID, file mode, or access and modification times when the +.Fl p +option is specified, a diagnostic message is written to standard +error and a non-zero exit value will be returned, but processing +will continue. +In the case where +.Nm +cannot create a link to a file, +.Nm +will not create a second copy of the file. +.Pp +If the extraction of a file from an archive is prematurely terminated +by a signal or error, +.Nm +may have only partially extracted the file the user wanted. +Additionally, the file modes of extracted files and directories may +have incorrect file bits, and the modification and access times may +be wrong. +.Pp +If the creation of an archive is prematurely terminated by a signal +or error, +.Nm +may have only partially created the archive which may violate the +specific archive format specification. +.Sh SEE ALSO +.Xr pax 1 , +.Xr tar 1 +.Sh AUTHORS +.An Keith Muller +at the University of California, San Diego. +.Sh BUGS +The +.Fl s +and +.Fl S +options are currently not implemented. diff --git a/bin/pax/cpio.c b/bin/pax/cpio.c new file mode 100644 index 0000000..1a38ba2 --- /dev/null +++ b/bin/pax/cpio.c @@ -0,0 +1,1134 @@ +/* $NetBSD: cpio.c,v 1.22 2012/08/09 08:09:21 christos Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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 HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +#if 0 +static char sccsid[] = "@(#)cpio.c 8.1 (Berkeley) 5/31/93"; +#else +__RCSID("$NetBSD: cpio.c,v 1.22 2012/08/09 08:09:21 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <string.h> +#include <ctype.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include "pax.h" +#include "cpio.h" +#include "extern.h" + +static int rd_nm(ARCHD *, int); +static int rd_ln_nm(ARCHD *); +static int com_rd(ARCHD *); + +/* + * Routines which support the different cpio versions + */ + +int cpio_swp_head; /* binary cpio header byte swap */ + +/* + * Routines common to all versions of cpio + */ + +/* + * cpio_strd() + * Fire up the hard link detection code + * Return: + * 0 if ok -1 otherwise (the return values of lnk_start()) + */ + +int +cpio_strd(void) +{ + return lnk_start(); +} + +/* + * cpio_subtrail() + * Called to determine if a header block is a valid trailer. We are + * passed the block, the in_sync flag (which tells us we are in resync + * mode; looking for a valid header), and cnt (which starts at zero) + * which is used to count the number of empty blocks we have seen so far. + * Return: + * 0 if a valid trailer, -1 if not a valid trailer, + */ + +int +cpio_subtrail(ARCHD *arcn) +{ + /* + * look for trailer id in file we are about to process + */ + if ((strcmp(arcn->name, TRAILER) == 0) && (arcn->sb.st_size == 0)) + return 0; + return -1; +} + +/* + * com_rd() + * operations common to all cpio read functions. + * Return: + * 0 + */ + +static int +com_rd(ARCHD *arcn) +{ + arcn->skip = 0; + arcn->pat = NULL; + arcn->org_name = arcn->name; + switch(arcn->sb.st_mode & C_IFMT) { + case C_ISFIFO: + arcn->type = PAX_FIF; + break; + case C_ISDIR: + arcn->type = PAX_DIR; + break; + case C_ISBLK: + arcn->type = PAX_BLK; + break; + case C_ISCHR: + arcn->type = PAX_CHR; + break; + case C_ISLNK: + arcn->type = PAX_SLK; + break; + case C_ISOCK: + arcn->type = PAX_SCK; + break; + case C_ISCTG: + case C_ISREG: + default: + /* + * we have file data, set up skip (pad is set in the format + * specific sections) + */ + arcn->sb.st_mode = (arcn->sb.st_mode & 0xfff) | C_ISREG; + arcn->type = PAX_REG; + arcn->skip = arcn->sb.st_size; + break; + } + if (chk_lnk(arcn) < 0) + return -1; + return 0; +} + +/* + * cpio_end_wr() + * write the special file with the name trailer in the proper format + * Return: + * result of the write of the trailer from the cpio specific write func + */ + +int +cpio_endwr(void) +{ + ARCHD last; + + /* + * create a trailer request and call the proper format write function + */ + memset(&last, 0, sizeof(last)); + last.nlen = sizeof(TRAILER) - 1; + last.type = PAX_REG; + last.sb.st_nlink = 1; + (void)strcpy(last.name, TRAILER); + return (*frmt->wr)(&last); +} + +/* + * rd_nam() + * read in the file name which follows the cpio header + * Return: + * 0 if ok, -1 otherwise + */ + +static int +rd_nm(ARCHD *arcn, int nsz) +{ + /* + * do not even try bogus values + */ + if ((nsz <= 0) || (nsz > (int)sizeof(arcn->name))) { + tty_warn(1, "Cpio file name length %d is out of range", nsz); + return -1; + } + + /* + * read the name and make sure it is not empty and is \0 terminated + */ + if ((rd_wrbuf(arcn->name,nsz) != nsz) || (arcn->name[nsz-1] != '\0') || + (arcn->name[0] == '\0')) { + tty_warn(1, "Cpio file name in header is corrupted"); + return -1; + } + return 0; +} + +/* + * rd_ln_nm() + * read in the link name for a file with links. The link name is stored + * like file data (and is NOT \0 terminated!) + * Return: + * 0 if ok, -1 otherwise + */ + +static int +rd_ln_nm(ARCHD *arcn) +{ + /* + * check the length specified for bogus values + */ + if ((arcn->sb.st_size == 0) || + (arcn->sb.st_size >= (off_t)sizeof(arcn->ln_name))) { + tty_warn(1, "Cpio link name length is invalid: " OFFT_F, + (OFFT_T) arcn->sb.st_size); + return -1; + } + + /* + * read in the link name and \0 terminate it + */ + if (rd_wrbuf(arcn->ln_name, (int)arcn->sb.st_size) != + (int)arcn->sb.st_size) { + tty_warn(1, "Cpio link name read error"); + return -1; + } + arcn->ln_nlen = arcn->sb.st_size; + arcn->ln_name[arcn->ln_nlen] = '\0'; + + /* + * watch out for those empty link names + */ + if (arcn->ln_name[0] == '\0') { + tty_warn(1, "Cpio link name is corrupt"); + return -1; + } + return 0; +} + +/* + * Routines common to the extended byte oriented cpio format + */ + +/* + * cpio_id() + * determine if a block given to us is a valid extended byte oriented + * cpio header + * Return: + * 0 if a valid header, -1 otherwise + */ + +int +cpio_id(char *blk, int size) +{ + if ((size < (int)sizeof(HD_CPIO)) || + (strncmp(blk, AMAGIC, sizeof(AMAGIC) - 1) != 0)) + return -1; + return 0; +} + +/* + * cpio_rd() + * determine if a buffer is a byte oriented extended cpio archive entry. + * convert and store the values in the ARCHD parameter. + * Return: + * 0 if a valid header, -1 otherwise. + */ + +int +cpio_rd(ARCHD *arcn, char *buf) +{ + int nsz; + HD_CPIO *hd; + + /* + * check that this is a valid header, if not return -1 + */ + if (cpio_id(buf, sizeof(HD_CPIO)) < 0) + return -1; + hd = (HD_CPIO *)buf; + + /* + * byte oriented cpio (posix) does not have padding! extract the octal + * ascii fields from the header + */ + arcn->pad = 0L; + arcn->sb.st_dev = (dev_t)asc_u32(hd->c_dev, sizeof(hd->c_dev), OCT); + arcn->sb.st_ino = (ino_t)asc_u32(hd->c_ino, sizeof(hd->c_ino), OCT); + arcn->sb.st_mode = (mode_t)asc_u32(hd->c_mode, sizeof(hd->c_mode), OCT); + arcn->sb.st_uid = (uid_t)asc_u32(hd->c_uid, sizeof(hd->c_uid), OCT); + arcn->sb.st_gid = (gid_t)asc_u32(hd->c_gid, sizeof(hd->c_gid), OCT); + arcn->sb.st_nlink = (nlink_t)asc_u32(hd->c_nlink, sizeof(hd->c_nlink), + OCT); + arcn->sb.st_rdev = (dev_t)asc_u32(hd->c_rdev, sizeof(hd->c_rdev), OCT); + arcn->sb.st_mtime = (time_t)(int32_t)asc_u32(hd->c_mtime, sizeof(hd->c_mtime), + OCT); + arcn->sb.st_ctime = arcn->sb.st_atime = arcn->sb.st_mtime; + arcn->sb.st_size = (off_t)ASC_OFFT(hd->c_filesize, + sizeof(hd->c_filesize), OCT); + + /* + * check name size and if valid, read in the name of this entry (name + * follows header in the archive) + */ + if ((nsz = (int)asc_u32(hd->c_namesize,sizeof(hd->c_namesize),OCT)) < 2) + return -1; + arcn->nlen = nsz - 1; + if (rd_nm(arcn, nsz) < 0) + return -1; + + if (((arcn->sb.st_mode&C_IFMT) != C_ISLNK)||(arcn->sb.st_size == 0)) { + /* + * no link name to read for this file + */ + arcn->ln_nlen = 0; + arcn->ln_name[0] = '\0'; + return com_rd(arcn); + } + + /* + * check link name size and read in the link name. Link names are + * stored like file data. + */ + if (rd_ln_nm(arcn) < 0) + return -1; + + /* + * we have a valid header (with a link) + */ + return com_rd(arcn); +} + +/* + * cpio_endrd() + * no cleanup needed here, just return size of the trailer (for append) + * Return: + * size of trailer header in this format + */ + +off_t +cpio_endrd(void) +{ + return (off_t)(sizeof(HD_CPIO) + sizeof(TRAILER)); +} + +/* + * cpio_stwr() + * start up the device mapping table + * Return: + * 0 if ok, -1 otherwise (what dev_start() returns) + */ + +int +cpio_stwr(void) +{ + return dev_start(); +} + +/* + * cpio_wr() + * copy the data in the ARCHD to buffer in extended byte oriented cpio + * format. + * Return + * 0 if file has data to be written after the header, 1 if file has NO + * data to write after the header, -1 if archive write failed + */ + +int +cpio_wr(ARCHD *arcn) +{ + HD_CPIO *hd; + int nsz; + char hdblk[sizeof(HD_CPIO)]; + + /* + * check and repair truncated device and inode fields in the header + */ + if (map_dev(arcn, (u_long)CPIO_MASK, (u_long)CPIO_MASK) < 0) + return -1; + + arcn->pad = 0L; + nsz = arcn->nlen + 1; + hd = (HD_CPIO *)hdblk; + if ((arcn->type != PAX_BLK) && (arcn->type != PAX_CHR)) + arcn->sb.st_rdev = 0; + + switch(arcn->type) { + case PAX_CTG: + case PAX_REG: + case PAX_HRG: + /* + * set data size for file data + */ + if (OFFT_ASC(arcn->sb.st_size, hd->c_filesize, + sizeof(hd->c_filesize), OCT)) { + tty_warn(1,"File is too large for cpio format %s", + arcn->org_name); + return 1; + } + break; + case PAX_SLK: + /* + * set data size to hold link name + */ + if (u32_asc((uintmax_t)arcn->ln_nlen, hd->c_filesize, + sizeof(hd->c_filesize), OCT)) + goto out; + break; + default: + /* + * all other file types have no file data + */ + if (u32_asc((uintmax_t)0, hd->c_filesize, sizeof(hd->c_filesize), + OCT)) + goto out; + break; + } + + /* + * copy the values to the header using octal ascii + */ + if (u32_asc((uintmax_t)MAGIC, hd->c_magic, sizeof(hd->c_magic), OCT) || + u32_asc((uintmax_t)arcn->sb.st_dev, hd->c_dev, sizeof(hd->c_dev), + OCT) || + u32_asc((uintmax_t)arcn->sb.st_ino, hd->c_ino, sizeof(hd->c_ino), + OCT) || + u32_asc((uintmax_t)arcn->sb.st_mode, hd->c_mode, sizeof(hd->c_mode), + OCT) || + u32_asc((uintmax_t)arcn->sb.st_uid, hd->c_uid, sizeof(hd->c_uid), + OCT) || + u32_asc((uintmax_t)arcn->sb.st_gid, hd->c_gid, sizeof(hd->c_gid), + OCT) || + u32_asc((uintmax_t)arcn->sb.st_nlink, hd->c_nlink, sizeof(hd->c_nlink), + OCT) || + u32_asc((uintmax_t)arcn->sb.st_rdev, hd->c_rdev, sizeof(hd->c_rdev), + OCT) || + u32_asc((uintmax_t)arcn->sb.st_mtime,hd->c_mtime,sizeof(hd->c_mtime), + OCT) || + u32_asc((uintmax_t)nsz, hd->c_namesize, sizeof(hd->c_namesize), OCT)) + goto out; + + /* + * write the file name to the archive + */ + if ((wr_rdbuf(hdblk, (int)sizeof(HD_CPIO)) < 0) || + (wr_rdbuf(arcn->name, nsz) < 0)) { + tty_warn(1, "Unable to write cpio header for %s", + arcn->org_name); + return -1; + } + + /* + * if this file has data, we are done. The caller will write the file + * data, if we are link tell caller we are done, go to next file + */ + if ((arcn->type == PAX_CTG) || (arcn->type == PAX_REG)) + return 0; + if (arcn->type != PAX_SLK) + return 1; + + /* + * write the link name to the archive, tell the caller to go to the + * next file as we are done. + */ + if (wr_rdbuf(arcn->ln_name, arcn->ln_nlen) < 0) { + tty_warn(1,"Unable to write cpio link name for %s", + arcn->org_name); + return -1; + } + return 1; + + out: + /* + * header field is out of range + */ + tty_warn(1, "Cpio header field is too small to store file %s", + arcn->org_name); + return 1; +} + +/* + * Routines common to the system VR4 version of cpio (with/without file CRC) + */ + +/* + * vcpio_id() + * determine if a block given to us is a valid system VR4 cpio header + * WITHOUT crc. WATCH it the magic cookies are in OCTAL, the header + * uses HEX + * Return: + * 0 if a valid header, -1 otherwise + */ + +int +vcpio_id(char *blk, int size) +{ + if ((size < (int)sizeof(HD_VCPIO)) || + (strncmp(blk, AVMAGIC, sizeof(AVMAGIC) - 1) != 0)) + return -1; + return 0; +} + +/* + * crc_id() + * determine if a block given to us is a valid system VR4 cpio header + * WITH crc. WATCH it the magic cookies are in OCTAL the header uses HEX + * Return: + * 0 if a valid header, -1 otherwise + */ + +int +crc_id(char *blk, int size) +{ + if ((size < (int)sizeof(HD_VCPIO)) || + (strncmp(blk, AVCMAGIC, sizeof(AVCMAGIC) - 1) != 0)) + return -1; + return 0; +} + +/* + * crc_strd() + * set file data CRC calculations. Fire up the hard link detection code + * Return: + * 0 if ok -1 otherwise (the return values of lnk_start()) + */ + +int +crc_strd(void) +{ + docrc = 1; + return lnk_start(); +} + +/* + * vcpio_rd() + * determine if a buffer is a system VR4 archive entry. (with/without CRC) + * convert and store the values in the ARCHD parameter. + * Return: + * 0 if a valid header, -1 otherwise. + */ + +int +vcpio_rd(ARCHD *arcn, char *buf) +{ + HD_VCPIO *hd; + dev_t devminor; + dev_t devmajor; + int nsz; + + /* + * during the id phase it was determined if we were using CRC, use the + * proper id routine. + */ + if (docrc) { + if (crc_id(buf, sizeof(HD_VCPIO)) < 0) + return -1; + } else { + if (vcpio_id(buf, sizeof(HD_VCPIO)) < 0) + return -1; + } + + hd = (HD_VCPIO *)buf; + arcn->pad = 0L; + + /* + * extract the hex ascii fields from the header + */ + arcn->sb.st_ino = (ino_t)asc_u32(hd->c_ino, sizeof(hd->c_ino), HEX); + arcn->sb.st_mode = (mode_t)asc_u32(hd->c_mode, sizeof(hd->c_mode), HEX); + arcn->sb.st_uid = (uid_t)asc_u32(hd->c_uid, sizeof(hd->c_uid), HEX); + arcn->sb.st_gid = (gid_t)asc_u32(hd->c_gid, sizeof(hd->c_gid), HEX); + arcn->sb.st_mtime = (time_t)(int32_t)asc_u32(hd->c_mtime,sizeof(hd->c_mtime),HEX); + arcn->sb.st_ctime = arcn->sb.st_atime = arcn->sb.st_mtime; + arcn->sb.st_size = (off_t)ASC_OFFT(hd->c_filesize, + sizeof(hd->c_filesize), HEX); + arcn->sb.st_nlink = (nlink_t)asc_u32(hd->c_nlink, sizeof(hd->c_nlink), + HEX); + devmajor = (dev_t)asc_u32(hd->c_maj, sizeof(hd->c_maj), HEX); + devminor = (dev_t)asc_u32(hd->c_min, sizeof(hd->c_min), HEX); + arcn->sb.st_dev = TODEV(devmajor, devminor); + devmajor = (dev_t)asc_u32(hd->c_rmaj, sizeof(hd->c_maj), HEX); + devminor = (dev_t)asc_u32(hd->c_rmin, sizeof(hd->c_min), HEX); + arcn->sb.st_rdev = TODEV(devmajor, devminor); + arcn->crc = asc_u32(hd->c_chksum, sizeof(hd->c_chksum), HEX); + + /* + * check the length of the file name, if ok read it in, return -1 if + * bogus + */ + if ((nsz = (int)asc_u32(hd->c_namesize,sizeof(hd->c_namesize),HEX)) < 2) + return -1; + arcn->nlen = nsz - 1; + if (rd_nm(arcn, nsz) < 0) + return -1; + + /* + * skip padding. header + filename is aligned to 4 byte boundaries + */ + if (rd_skip((off_t)(VCPIO_PAD(sizeof(HD_VCPIO) + nsz))) < 0) + return -1; + + /* + * if not a link (or a file with no data), calculate pad size (for + * padding which follows the file data), clear the link name and return + */ + if (((arcn->sb.st_mode&C_IFMT) != C_ISLNK)||(arcn->sb.st_size == 0)) { + /* + * we have a valid header (not a link) + */ + arcn->ln_nlen = 0; + arcn->ln_name[0] = '\0'; + arcn->pad = VCPIO_PAD(arcn->sb.st_size); + return com_rd(arcn); + } + + /* + * read in the link name and skip over the padding + */ + if ((rd_ln_nm(arcn) < 0) || + (rd_skip((off_t)(VCPIO_PAD(arcn->sb.st_size))) < 0)) + return -1; + + /* + * we have a valid header (with a link) + */ + return com_rd(arcn); +} + +/* + * vcpio_endrd() + * no cleanup needed here, just return size of the trailer (for append) + * Return: + * size of trailer header in this format + */ + +off_t +vcpio_endrd(void) +{ + return (off_t)(sizeof(HD_VCPIO) + sizeof(TRAILER) + + (VCPIO_PAD(sizeof(HD_VCPIO) + sizeof(TRAILER)))); +} + +/* + * crc_stwr() + * start up the device mapping table, enable crc file calculation + * Return: + * 0 if ok, -1 otherwise (what dev_start() returns) + */ + +int +crc_stwr(void) +{ + docrc = 1; + return dev_start(); +} + +/* + * vcpio_wr() + * copy the data in the ARCHD to buffer in system VR4 cpio + * (with/without crc) format. + * Return + * 0 if file has data to be written after the header, 1 if file has + * NO data to write after the header, -1 if archive write failed + */ + +int +vcpio_wr(ARCHD *arcn) +{ + HD_VCPIO *hd; + unsigned int nsz; + char hdblk[sizeof(HD_VCPIO)]; + + /* + * check and repair truncated device and inode fields in the cpio + * header + */ + if (map_dev(arcn, (u_long)VCPIO_MASK, (u_long)VCPIO_MASK) < 0) + return -1; + nsz = arcn->nlen + 1; + hd = (HD_VCPIO *)hdblk; + if ((arcn->type != PAX_BLK) && (arcn->type != PAX_CHR)) + arcn->sb.st_rdev = 0; + + /* + * add the proper magic value depending whether we were asked for + * file data crc's, and the crc if needed. + */ + if (docrc) { + if (u32_asc((uintmax_t)VCMAGIC, hd->c_magic, sizeof(hd->c_magic), + OCT) || + u32_asc((uintmax_t)arcn->crc,hd->c_chksum,sizeof(hd->c_chksum), + HEX)) + goto out; + } else { + if (u32_asc((uintmax_t)VMAGIC, hd->c_magic, sizeof(hd->c_magic), + OCT) || + u32_asc((uintmax_t)0, hd->c_chksum, sizeof(hd->c_chksum),HEX)) + goto out; + } + + switch(arcn->type) { + case PAX_CTG: + case PAX_REG: + case PAX_HRG: + /* + * caller will copy file data to the archive. tell him how + * much to pad. + */ + arcn->pad = VCPIO_PAD(arcn->sb.st_size); + if (OFFT_ASC(arcn->sb.st_size, hd->c_filesize, + sizeof(hd->c_filesize), HEX)) { + tty_warn(1,"File is too large for sv4cpio format %s", + arcn->org_name); + return 1; + } + break; + case PAX_SLK: + /* + * no file data for the caller to process, the file data has + * the size of the link + */ + arcn->pad = 0L; + if (u32_asc((uintmax_t)arcn->ln_nlen, hd->c_filesize, + sizeof(hd->c_filesize), HEX)) + goto out; + break; + default: + /* + * no file data for the caller to process + */ + arcn->pad = 0L; + if (u32_asc((uintmax_t)0, hd->c_filesize, sizeof(hd->c_filesize), + HEX)) + goto out; + break; + } + + /* + * set the other fields in the header + */ + if (u32_asc((uintmax_t)arcn->sb.st_ino, hd->c_ino, sizeof(hd->c_ino), + HEX) || + u32_asc((uintmax_t)arcn->sb.st_mode, hd->c_mode, sizeof(hd->c_mode), + HEX) || + u32_asc((uintmax_t)arcn->sb.st_uid, hd->c_uid, sizeof(hd->c_uid), + HEX) || + u32_asc((uintmax_t)arcn->sb.st_gid, hd->c_gid, sizeof(hd->c_gid), + HEX) || + u32_asc((uintmax_t)arcn->sb.st_mtime, hd->c_mtime, sizeof(hd->c_mtime), + HEX) || + u32_asc((uintmax_t)arcn->sb.st_nlink, hd->c_nlink, sizeof(hd->c_nlink), + HEX) || + u32_asc((uintmax_t)MAJOR(arcn->sb.st_dev),hd->c_maj, sizeof(hd->c_maj), + HEX) || + u32_asc((uintmax_t)MINOR(arcn->sb.st_dev),hd->c_min, sizeof(hd->c_min), + HEX) || + u32_asc((uintmax_t)MAJOR(arcn->sb.st_rdev),hd->c_rmaj,sizeof(hd->c_maj), + HEX) || + u32_asc((uintmax_t)MINOR(arcn->sb.st_rdev),hd->c_rmin,sizeof(hd->c_min), + HEX) || + u32_asc((uintmax_t)nsz, hd->c_namesize, sizeof(hd->c_namesize), HEX)) + goto out; + + /* + * write the header, the file name and padding as required. + */ + if ((wr_rdbuf(hdblk, (int)sizeof(HD_VCPIO)) < 0) || + (wr_rdbuf(arcn->name, (int)nsz) < 0) || + (wr_skip((off_t)(VCPIO_PAD(sizeof(HD_VCPIO) + nsz))) < 0)) { + tty_warn(1,"Could not write sv4cpio header for %s", + arcn->org_name); + return -1; + } + + /* + * if we have file data, tell the caller we are done, copy the file + */ + if ((arcn->type == PAX_CTG) || (arcn->type == PAX_REG) || + (arcn->type == PAX_HRG)) + return 0; + + /* + * if we are not a link, tell the caller we are done, go to next file + */ + if (arcn->type != PAX_SLK) + return 1; + + /* + * write the link name, tell the caller we are done. + */ + if ((wr_rdbuf(arcn->ln_name, arcn->ln_nlen) < 0) || + (wr_skip((off_t)(VCPIO_PAD(arcn->ln_nlen))) < 0)) { + tty_warn(1,"Could not write sv4cpio link name for %s", + arcn->org_name); + return -1; + } + return 1; + + out: + /* + * header field is out of range + */ + tty_warn(1,"Sv4cpio header field is too small for file %s", + arcn->org_name); + return 1; +} + +/* + * Routines common to the old binary header cpio + */ + +/* + * bcpio_id() + * determine if a block given to us is a old binary cpio header + * (with/without header byte swapping) + * Return: + * 0 if a valid header, -1 otherwise + */ + +int +bcpio_id(char *blk, int size) +{ + if (size < (int)sizeof(HD_BCPIO)) + return -1; + + /* + * check both normal and byte swapped magic cookies + */ + if (((u_short)SHRT_EXT(blk)) == MAGIC) + return 0; + if (((u_short)RSHRT_EXT(blk)) == MAGIC) { + if (!cpio_swp_head) + ++cpio_swp_head; + return 0; + } + return -1; +} + +/* + * bcpio_rd() + * determine if a buffer is a old binary archive entry. (it may have byte + * swapped header) convert and store the values in the ARCHD parameter. + * This is a very old header format and should not really be used. + * Return: + * 0 if a valid header, -1 otherwise. + */ + +int +bcpio_rd(ARCHD *arcn, char *buf) +{ + HD_BCPIO *hd; + int nsz; + + /* + * check the header + */ + if (bcpio_id(buf, sizeof(HD_BCPIO)) < 0) + return -1; + + arcn->pad = 0L; + hd = (HD_BCPIO *)buf; + if (cpio_swp_head) { + /* + * header has swapped bytes on 16 bit boundaries + */ + arcn->sb.st_dev = (dev_t)(RSHRT_EXT(hd->h_dev)); + arcn->sb.st_ino = (ino_t)(RSHRT_EXT(hd->h_ino)); + arcn->sb.st_mode = (mode_t)(RSHRT_EXT(hd->h_mode)); + arcn->sb.st_uid = (uid_t)(RSHRT_EXT(hd->h_uid)); + arcn->sb.st_gid = (gid_t)(RSHRT_EXT(hd->h_gid)); + arcn->sb.st_nlink = (nlink_t)(RSHRT_EXT(hd->h_nlink)); + arcn->sb.st_rdev = (dev_t)(RSHRT_EXT(hd->h_rdev)); + arcn->sb.st_mtime = (time_t)(RSHRT_EXT(hd->h_mtime_1)); + arcn->sb.st_mtime = (arcn->sb.st_mtime << 16) | + ((time_t)(RSHRT_EXT(hd->h_mtime_2))); + arcn->sb.st_size = (off_t)(RSHRT_EXT(hd->h_filesize_1)); + arcn->sb.st_size = (arcn->sb.st_size << 16) | + ((off_t)(RSHRT_EXT(hd->h_filesize_2))); + nsz = (int)(RSHRT_EXT(hd->h_namesize)); + } else { + arcn->sb.st_dev = (dev_t)(SHRT_EXT(hd->h_dev)); + arcn->sb.st_ino = (ino_t)(SHRT_EXT(hd->h_ino)); + arcn->sb.st_mode = (mode_t)(SHRT_EXT(hd->h_mode)); + arcn->sb.st_uid = (uid_t)(SHRT_EXT(hd->h_uid)); + arcn->sb.st_gid = (gid_t)(SHRT_EXT(hd->h_gid)); + arcn->sb.st_nlink = (nlink_t)(SHRT_EXT(hd->h_nlink)); + arcn->sb.st_rdev = (dev_t)(SHRT_EXT(hd->h_rdev)); + arcn->sb.st_mtime = (time_t)(SHRT_EXT(hd->h_mtime_1)); + arcn->sb.st_mtime = (arcn->sb.st_mtime << 16) | + ((time_t)(SHRT_EXT(hd->h_mtime_2))); + arcn->sb.st_size = (off_t)(SHRT_EXT(hd->h_filesize_1)); + arcn->sb.st_size = (arcn->sb.st_size << 16) | + ((off_t)(SHRT_EXT(hd->h_filesize_2))); + nsz = (int)(SHRT_EXT(hd->h_namesize)); + } + arcn->sb.st_ctime = arcn->sb.st_atime = arcn->sb.st_mtime; + + /* + * check the file name size, if bogus give up. otherwise read the file + * name + */ + if (nsz < 2) + return -1; + arcn->nlen = nsz - 1; + if (rd_nm(arcn, nsz) < 0) + return -1; + + /* + * header + file name are aligned to 2 byte boundaries, skip if needed + */ + if (rd_skip((off_t)(BCPIO_PAD(sizeof(HD_BCPIO) + nsz))) < 0) + return -1; + + /* + * if not a link (or a file with no data), calculate pad size (for + * padding which follows the file data), clear the link name and return + */ + if (((arcn->sb.st_mode & C_IFMT) != C_ISLNK)||(arcn->sb.st_size == 0)){ + /* + * we have a valid header (not a link) + */ + arcn->ln_nlen = 0; + arcn->ln_name[0] = '\0'; + arcn->pad = BCPIO_PAD(arcn->sb.st_size); + return com_rd(arcn); + } + + if ((rd_ln_nm(arcn) < 0) || + (rd_skip((off_t)(BCPIO_PAD(arcn->sb.st_size))) < 0)) + return -1; + + /* + * we have a valid header (with a link) + */ + return com_rd(arcn); +} + +/* + * bcpio_endrd() + * no cleanup needed here, just return size of the trailer (for append) + * Return: + * size of trailer header in this format + */ + +off_t +bcpio_endrd(void) +{ + return (off_t)(sizeof(HD_BCPIO) + sizeof(TRAILER) + + (BCPIO_PAD(sizeof(HD_BCPIO) + sizeof(TRAILER)))); +} + +/* + * bcpio_wr() + * copy the data in the ARCHD to buffer in old binary cpio format + * There is a real chance of field overflow with this critter. So we + * always check the conversion is ok. nobody in their right mind + * should write an archive in this format... + * Return + * 0 if file has data to be written after the header, 1 if file has NO + * data to write after the header, -1 if archive write failed + */ + +int +bcpio_wr(ARCHD *arcn) +{ + HD_BCPIO *hd; + int nsz; + char hdblk[sizeof(HD_BCPIO)]; + off_t t_offt; + int t_int; + time_t t_timet; + + /* + * check and repair truncated device and inode fields in the cpio + * header + */ + if (map_dev(arcn, (u_long)BCPIO_MASK, (u_long)BCPIO_MASK) < 0) + return -1; + + if ((arcn->type != PAX_BLK) && (arcn->type != PAX_CHR)) + arcn->sb.st_rdev = 0; + hd = (HD_BCPIO *)hdblk; + + switch(arcn->type) { + case PAX_CTG: + case PAX_REG: + case PAX_HRG: + /* + * caller will copy file data to the archive. tell him how + * much to pad. + */ + arcn->pad = BCPIO_PAD(arcn->sb.st_size); + hd->h_filesize_1[0] = CHR_WR_0(arcn->sb.st_size); + hd->h_filesize_1[1] = CHR_WR_1(arcn->sb.st_size); + hd->h_filesize_2[0] = CHR_WR_2(arcn->sb.st_size); + hd->h_filesize_2[1] = CHR_WR_3(arcn->sb.st_size); + t_offt = (off_t)(SHRT_EXT(hd->h_filesize_1)); + t_offt = (t_offt<<16) | ((off_t)(SHRT_EXT(hd->h_filesize_2))); + if (arcn->sb.st_size != t_offt) { + tty_warn(1,"File is too large for bcpio format %s", + arcn->org_name); + return 1; + } + break; + case PAX_SLK: + /* + * no file data for the caller to process, the file data has + * the size of the link + */ + arcn->pad = 0L; + hd->h_filesize_1[0] = CHR_WR_0(arcn->ln_nlen); + hd->h_filesize_1[1] = CHR_WR_1(arcn->ln_nlen); + hd->h_filesize_2[0] = CHR_WR_2(arcn->ln_nlen); + hd->h_filesize_2[1] = CHR_WR_3(arcn->ln_nlen); + t_int = (int)(SHRT_EXT(hd->h_filesize_1)); + t_int = (t_int << 16) | ((int)(SHRT_EXT(hd->h_filesize_2))); + if (arcn->ln_nlen != t_int) + goto out; + break; + default: + /* + * no file data for the caller to process + */ + arcn->pad = 0L; + hd->h_filesize_1[0] = (char)0; + hd->h_filesize_1[1] = (char)0; + hd->h_filesize_2[0] = (char)0; + hd->h_filesize_2[1] = (char)0; + break; + } + + /* + * build up the rest of the fields + */ + hd->h_magic[0] = CHR_WR_2(MAGIC); + hd->h_magic[1] = CHR_WR_3(MAGIC); + hd->h_dev[0] = CHR_WR_2(arcn->sb.st_dev); + hd->h_dev[1] = CHR_WR_3(arcn->sb.st_dev); + if (arcn->sb.st_dev != (dev_t)(SHRT_EXT(hd->h_dev))) + goto out; + hd->h_ino[0] = CHR_WR_2(arcn->sb.st_ino); + hd->h_ino[1] = CHR_WR_3(arcn->sb.st_ino); + if (arcn->sb.st_ino != (ino_t)(SHRT_EXT(hd->h_ino))) + goto out; + hd->h_mode[0] = CHR_WR_2(arcn->sb.st_mode); + hd->h_mode[1] = CHR_WR_3(arcn->sb.st_mode); + if (arcn->sb.st_mode != (mode_t)(SHRT_EXT(hd->h_mode))) + goto out; + hd->h_uid[0] = CHR_WR_2(arcn->sb.st_uid); + hd->h_uid[1] = CHR_WR_3(arcn->sb.st_uid); + if (arcn->sb.st_uid != (uid_t)(SHRT_EXT(hd->h_uid))) + goto out; + hd->h_gid[0] = CHR_WR_2(arcn->sb.st_gid); + hd->h_gid[1] = CHR_WR_3(arcn->sb.st_gid); + if (arcn->sb.st_gid != (gid_t)(SHRT_EXT(hd->h_gid))) + goto out; + hd->h_nlink[0] = CHR_WR_2(arcn->sb.st_nlink); + hd->h_nlink[1] = CHR_WR_3(arcn->sb.st_nlink); + if (arcn->sb.st_nlink != (nlink_t)(SHRT_EXT(hd->h_nlink))) + goto out; + hd->h_rdev[0] = CHR_WR_2(arcn->sb.st_rdev); + hd->h_rdev[1] = CHR_WR_3(arcn->sb.st_rdev); + if (arcn->sb.st_rdev != (dev_t)(SHRT_EXT(hd->h_rdev))) + goto out; + hd->h_mtime_1[0] = CHR_WR_0(arcn->sb.st_mtime); + hd->h_mtime_1[1] = CHR_WR_1(arcn->sb.st_mtime); + hd->h_mtime_2[0] = CHR_WR_2(arcn->sb.st_mtime); + hd->h_mtime_2[1] = CHR_WR_3(arcn->sb.st_mtime); + t_timet = (time_t)(SHRT_EXT(hd->h_mtime_1)); + t_timet = (t_timet << 16) | ((time_t)(SHRT_EXT(hd->h_mtime_2))); + if (arcn->sb.st_mtime != t_timet) + goto out; + nsz = arcn->nlen + 1; + hd->h_namesize[0] = CHR_WR_2(nsz); + hd->h_namesize[1] = CHR_WR_3(nsz); + if (nsz != (int)(SHRT_EXT(hd->h_namesize))) + goto out; + + /* + * write the header, the file name and padding as required. + */ + if ((wr_rdbuf(hdblk, (int)sizeof(HD_BCPIO)) < 0) || + (wr_rdbuf(arcn->name, nsz) < 0) || + (wr_skip((off_t)(BCPIO_PAD(sizeof(HD_BCPIO) + nsz))) < 0)) { + tty_warn(1, "Could not write bcpio header for %s", + arcn->org_name); + return -1; + } + + /* + * if we have file data, tell the caller we are done + */ + if ((arcn->type == PAX_CTG) || (arcn->type == PAX_REG) || + (arcn->type == PAX_HRG)) + return 0; + + /* + * if we are not a link, tell the caller we are done, go to next file + */ + if (arcn->type != PAX_SLK) + return 1; + + /* + * write the link name, tell the caller we are done. + */ + if ((wr_rdbuf(arcn->ln_name, arcn->ln_nlen) < 0) || + (wr_skip((off_t)(BCPIO_PAD(arcn->ln_nlen))) < 0)) { + tty_warn(1,"Could not write bcpio link name for %s", + arcn->org_name); + return -1; + } + return 1; + + out: + /* + * header field is out of range + */ + tty_warn(1,"Bcpio header field is too small for file %s", + arcn->org_name); + return 1; +} diff --git a/bin/pax/cpio.h b/bin/pax/cpio.h new file mode 100644 index 0000000..bbf40ed --- /dev/null +++ b/bin/pax/cpio.h @@ -0,0 +1,149 @@ +/* $NetBSD: cpio.h,v 1.6 2003/10/13 07:41:22 agc Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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. + * + * @(#)cpio.h 8.1 (Berkeley) 5/31/93 + */ + +/* + * Defines common to all versions of cpio + */ +#define TRAILER "TRAILER!!!" /* name in last archive record */ + +/* + * Header encoding of the different file types + */ +#define C_ISDIR 040000 /* Directory */ +#define C_ISFIFO 010000 /* FIFO */ +#define C_ISREG 0100000 /* Regular file */ +#define C_ISBLK 060000 /* Block special file */ +#define C_ISCHR 020000 /* Character special file */ +#define C_ISCTG 0110000 /* Reserved for contiguous files */ +#define C_ISLNK 0120000 /* Reserved for symbolic links */ +#define C_ISOCK 0140000 /* Reserved for sockets */ +#define C_IFMT 0170000 /* type of file */ + +/* + * Data Interchange Format - Extended cpio header format - POSIX 1003.1-1990 + */ +typedef struct { + char c_magic[6]; /* magic cookie */ + char c_dev[6]; /* device number */ + char c_ino[6]; /* inode number */ + char c_mode[6]; /* file type/access */ + char c_uid[6]; /* owners uid */ + char c_gid[6]; /* owners gid */ + char c_nlink[6]; /* # of links at archive creation */ + char c_rdev[6]; /* block/char major/minor # */ + char c_mtime[11]; /* modification time */ + char c_namesize[6]; /* length of pathname */ + char c_filesize[11]; /* length of file in bytes */ +} HD_CPIO; + +#define MAGIC 070707 /* transportable archive id */ + +#ifdef _PAX_ +#define AMAGIC "070707" /* ascii equivalent string of MAGIC */ +#define CPIO_MASK 0x3ffff /* bits valid in the dev/ino fields */ + /* used for dev/inode remaps */ +#endif /* _PAX_ */ + +/* + * Binary cpio header structure + * + * CAUTION! CAUTION! CAUTION! + * Each field really represents a 16 bit short (NOT ASCII). Described as + * an array of chars in an attempt to improve portability!! + */ +typedef struct { + u_char h_magic[2]; + u_char h_dev[2]; + u_char h_ino[2]; + u_char h_mode[2]; + u_char h_uid[2]; + u_char h_gid[2]; + u_char h_nlink[2]; + u_char h_rdev[2]; + u_char h_mtime_1[2]; + u_char h_mtime_2[2]; + u_char h_namesize[2]; + u_char h_filesize_1[2]; + u_char h_filesize_2[2]; +} HD_BCPIO; + +#ifdef _PAX_ +/* + * extraction and creation macros for binary cpio + */ +#define SHRT_EXT(ch) ((((unsigned)(ch)[0])<<8) | (((unsigned)(ch)[1])&0xff)) +#define RSHRT_EXT(ch) ((((unsigned)(ch)[1])<<8) | (((unsigned)(ch)[0])&0xff)) +#define CHR_WR_0(val) ((char)(((val) >> 24) & 0xff)) +#define CHR_WR_1(val) ((char)(((val) >> 16) & 0xff)) +#define CHR_WR_2(val) ((char)(((val) >> 8) & 0xff)) +#define CHR_WR_3(val) ((char)((val) & 0xff)) + +/* + * binary cpio masks and pads + */ +#define BCPIO_PAD(x) ((2 - ((x) & 1)) & 1) /* pad to next 2 byte word */ +#define BCPIO_MASK 0xffff /* mask for dev/ino fields */ +#endif /* _PAX_ */ + +/* + * System VR4 cpio header structure (with/without file data crc) + */ +typedef struct { + char c_magic[6]; /* magic cookie */ + char c_ino[8]; /* inode number */ + char c_mode[8]; /* file type/access */ + char c_uid[8]; /* owners uid */ + char c_gid[8]; /* owners gid */ + char c_nlink[8]; /* # of links at archive creation */ + char c_mtime[8]; /* modification time */ + char c_filesize[8]; /* length of file in bytes */ + char c_maj[8]; /* block/char major # */ + char c_min[8]; /* block/char minor # */ + char c_rmaj[8]; /* special file major # */ + char c_rmin[8]; /* special file minor # */ + char c_namesize[8]; /* length of pathname */ + char c_chksum[8]; /* 0 OR CRC of bytes of FILE data */ +} HD_VCPIO; + +#define VMAGIC 070701 /* sVr4 new portable archive id */ +#define VCMAGIC 070702 /* sVr4 new portable archive id CRC */ +#ifdef _PAX_ +#define AVMAGIC "070701" /* ascii string of above */ +#define AVCMAGIC "070702" /* ascii string of above */ +#define VCPIO_PAD(x) ((4 - ((x) & 3)) & 3) /* pad to next 4 byte word */ +#define VCPIO_MASK 0xffffffff /* mask for dev/ino fields */ +#endif /* _PAX_ */ diff --git a/bin/pax/dumptar.c b/bin/pax/dumptar.c new file mode 100644 index 0000000..5538702 --- /dev/null +++ b/bin/pax/dumptar.c @@ -0,0 +1,131 @@ +/* $NetBSD: dumptar.c,v 1.3 2016/05/30 17:34:35 dholland Exp $ */ + +/*- + * Copyright (c) 2004 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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 <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <err.h> +#include <assert.h> +#include <sys/stat.h> +#include <sys/mman.h> + +#include "tar.h" + +#define ussum(a) 1 + +/* + * Ensure null termination. + */ +static char * +buf(const char *p, size_t s) +{ + static char buf[1024]; + + assert(s < sizeof(buf)); + memcpy(buf, p, s); + buf[s] = '\0'; + return buf; +} + +static int +intarg(const char *p, size_t s) +{ + char *ep, *b = buf(p, s); + int r = (int)strtol(b, &ep, 8); + return r; +} + +static int +usdump(void *p) +{ + HD_USTAR *t = p; + int size = intarg(t->size, sizeof(t->size)); + size = ((size + 511) / 512) * 512 + 512; + + (void)fprintf(stdout, "*****\n"); +#define PR(a) \ + (void)fprintf(stdout, #a "=%s\n", buf(t->a, sizeof(t->a))); +#define IPR(a) \ + (void)fprintf(stdout, #a "=%d\n", intarg(t->a, sizeof(t->a))); +#define OPR(a) \ + (void)fprintf(stdout, #a "=%o\n", intarg(t->a, sizeof(t->a))); + PR(name); + OPR(mode); + IPR(uid); + IPR(gid); + IPR(size); + OPR(mtime); + OPR(chksum); + (void)fprintf(stdout, "typeflag=%c\n", t->typeflag); + PR(linkname); + PR(magic); + PR(version); + PR(uname); + PR(gname); + OPR(devmajor); + OPR(devminor); + PR(prefix); + return size; +} + +int +main(int argc, char *argv[]) +{ + int fd; + struct stat st; + char *p, *ep; + + if (argc != 2) { + (void)fprintf(stderr, "Usage: %s <filename>\n", getprogname()); + return 1; + } + + if ((fd = open(argv[1], O_RDONLY)) == -1) + err(1, "Cannot open `%s'", argv[1]); + + if (fstat(fd, &st) == -1) + err(1, "Cannot fstat `%s'", argv[1]); + + if ((p = mmap(NULL, (size_t)st.st_size, PROT_READ, + MAP_FILE|MAP_PRIVATE, fd, (off_t)0)) == MAP_FAILED) + err(1, "Cannot mmap `%s'", argv[1]); + (void)close(fd); + + ep = (char *)p + (size_t)st.st_size; + + for (; p < ep + sizeof(HD_USTAR);) { + if (ussum(p)) + p += usdump(p); + } + return 0; +} diff --git a/bin/pax/extern.h b/bin/pax/extern.h new file mode 100644 index 0000000..298600c --- /dev/null +++ b/bin/pax/extern.h @@ -0,0 +1,326 @@ +/* $NetBSD: extern.h,v 1.59 2012/08/09 08:09:21 christos Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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. + * + * @(#)extern.h 8.2 (Berkeley) 4/18/94 + */ + +/* + * External references from each source file + */ + +#include <sys/cdefs.h> +#include <err.h> + +/* + * ar_io.c + */ +extern const char *arcname; +extern int curdirfd; +extern const char *gzip_program; +extern time_t starttime; +extern int force_one_volume; +extern char *chdname; +extern int forcelocal; +extern int secure; + +int ar_open(const char *); +void ar_close(void); +void ar_drain(void); +int ar_set_wr(void); +int ar_app_ok(void); +#ifdef SYS_NO_RESTART +int read_with_restart(int, void *, int); +int write_with_restart(int, void *, int); +#else +#define read_with_restart read +#define write_with_restart write +#endif +int xread(int, void *, int); +int xwrite(int, void *, int); +int ar_read(char *, int); +int ar_write(char *, int); +int ar_rdsync(void); +int ar_fow(off_t, off_t *); +int ar_rev(off_t ); +int ar_next(void); +void ar_summary(int); +int ar_dochdir(const char *); + +/* + * ar_subs.c + */ +extern u_long flcnt; +extern ARCHD archd; +int updatepath(void); +int dochdir(const char *); +int fdochdir(int); +int domkdir(const char *, mode_t); +int list(void); +int extract(void); +int append(void); +int archive(void); +int copy(void); + +/* + * buf_subs.c + */ +extern int blksz; +extern int wrblksz; +extern int maxflt; +extern int rdblksz; +extern off_t wrlimit; +extern off_t rdcnt; +extern off_t wrcnt; +int wr_start(void); +int rd_start(void); +void cp_start(void); +int appnd_start(off_t); +int rd_sync(void); +void pback(char *, int); +int rd_skip(off_t); +void wr_fin(void); +int wr_rdbuf(char *, int); +int rd_wrbuf(char *, int); +int wr_skip(off_t); +int wr_rdfile(ARCHD *, int, off_t *); +int rd_wrfile(ARCHD *, int, off_t *); +void cp_file(ARCHD *, int, int); +int buf_fill(void); +int buf_flush(int); + +/* + * cpio.c + */ +extern int cpio_swp_head; +int cpio_strd(void); +int cpio_subtrail(ARCHD *); +int cpio_endwr(void); +int cpio_id(char *, int); +int cpio_rd(ARCHD *, char *); +off_t cpio_endrd(void); +int cpio_stwr(void); +int cpio_wr(ARCHD *); +int vcpio_id(char *, int); +int crc_id(char *, int); +int crc_strd(void); +int vcpio_rd(ARCHD *, char *); +off_t vcpio_endrd(void); +int crc_stwr(void); +int vcpio_wr(ARCHD *); +int bcpio_id(char *, int); +int bcpio_rd(ARCHD *, char *); +off_t bcpio_endrd(void); +int bcpio_wr(ARCHD *); + +/* + * file_subs.c + */ +extern char *gnu_name_string, *gnu_link_string; +extern size_t gnu_name_length, gnu_link_length; +extern char *xtmp_name; +int file_creat(ARCHD *, int); +void file_close(ARCHD *, int); +int lnk_creat(ARCHD *, int *); +int cross_lnk(ARCHD *); +int chk_same(ARCHD *); +int node_creat(ARCHD *); +int unlnk_exist(char *, int); +int chk_path(char *, uid_t, gid_t); +void set_ftime(char *fnm, time_t mtime, time_t atime, int frc, int slk); +int set_ids(char *, uid_t, gid_t); +void set_pmode(char *, mode_t); +void set_chflags(char *fnm, u_int32_t flags); +int file_write(int, char *, int, int *, int *, int, char *); +void file_flush(int, char *, int); +void rdfile_close(ARCHD *, int *); +int set_crc(ARCHD *, int); + +/* + * ftree.c + */ +int ftree_start(void); +int ftree_add(char *, int); +void ftree_sel(ARCHD *); +void ftree_chk(void); +int next_file(ARCHD *); + +/* + * gen_subs.c + */ +void ls_list(ARCHD *, time_t, FILE *); +void ls_tty(ARCHD *); +void safe_print(const char *, FILE *); +uint32_t asc_u32(char *, int, int); +int u32_asc(uintmax_t, char *, int, int); +uintmax_t asc_umax(char *, int, int); +int umax_asc(uintmax_t, char *, int, int); +int check_Aflag(void); + +/* + * getoldopt.c + */ +struct option; +int getoldopt(int, char **, const char *, struct option *, int *); + +/* + * options.c + */ +extern FSUB fsub[]; +extern int ford[]; +extern int sep; +extern int havechd; +void options(int, char **); +OPLIST * opt_next(void); +int bad_opt(void); +int mkpath(char *); +char *chdname; +#if !HAVE_NBTOOL_CONFIG_H +int do_chroot; +#endif + +/* + * pat_rep.c + */ +int rep_add(char *); +int pat_add(char *, char *, int); +void pat_chk(void); +int pat_sel(ARCHD *); +int pat_match(ARCHD *); +int mod_name(ARCHD *, int); +int set_dest(ARCHD *, char *, int); + +/* + * pax.c + */ +extern int act; +extern FSUB *frmt; +extern int Aflag; +extern int cflag; +extern int cwdfd; +extern int dflag; +extern int iflag; +extern int kflag; +extern int lflag; +extern int nflag; +extern int tflag; +extern int uflag; +extern int vflag; +extern int Dflag; +extern int Hflag; +extern int Lflag; +extern int Mflag; +extern int Vflag; +extern int Xflag; +extern int Yflag; +extern int Zflag; +extern int vfpart; +extern int patime; +extern int pmtime; +extern int nodirs; +extern int pfflags; +extern int pmode; +extern int pids; +extern int rmleadslash; +extern int exit_val; +extern int docrc; +extern int to_stdout; +extern char *dirptr; +extern char *ltmfrmt; +extern const char *argv0; +extern FILE *listf; +extern char *tempfile; +extern char *tempbase; + +/* + * sel_subs.c + */ +int sel_chk(ARCHD *); +int grp_add(char *); +int usr_add(char *); +int trng_add(char *); + +/* + * tables.c + */ +int lnk_start(void); +int chk_lnk(ARCHD *); +void purg_lnk(ARCHD *); +void lnk_end(void); +int ftime_start(void); +int chk_ftime(ARCHD *); +int name_start(void); +int add_name(char *, int, char *); +void sub_name(char *, int *, size_t); +int dev_start(void); +int add_dev(ARCHD *); +int map_dev(ARCHD *, u_long, u_long); +int atdir_start(void); +void atdir_end(void); +void add_atdir(char *, dev_t, ino_t, time_t, time_t); +int get_atdir(dev_t, ino_t, time_t *, time_t *); +int dir_start(void); +void add_dir(char *, int, struct stat *, int); +void proc_dir(void); +u_int st_hash(char *, int, int); + +/* + * tar.c + */ +extern int is_gnutar; +int tar_endwr(void); +off_t tar_endrd(void); +int tar_trail(char *, int, int *); +int tar_id(char *, int); +int tar_opt(void); +int tar_rd(ARCHD *, char *); +int tar_wr(ARCHD *); +int ustar_strd(void); +int ustar_stwr(void); +int ustar_id(char *, int); +int ustar_rd(ARCHD *, char *); +int ustar_wr(ARCHD *); +int tar_gnutar_X_compat(const char *); +int tar_gnutar_minus_minus_exclude(const char *); + +/* + * tty_subs.c + */ +int tty_init(void); +void tty_prnt(const char *, ...) + __attribute__((format (printf, 1, 2))); +int tty_read(char *, int); +void tty_warn(int, const char *, ...) + __attribute__((format (printf, 2, 3))); +void syswarn(int, int, const char *, ...) + __attribute__((format (printf, 3, 4))); diff --git a/bin/pax/file_subs.c b/bin/pax/file_subs.c new file mode 100644 index 0000000..cd421d0 --- /dev/null +++ b/bin/pax/file_subs.c @@ -0,0 +1,1156 @@ +/* $NetBSD: file_subs.c,v 1.63 2013/07/29 17:46:36 christos Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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 HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +#if 0 +static char sccsid[] = "@(#)file_subs.c 8.1 (Berkeley) 5/31/93"; +#else +__RCSID("$NetBSD: file_subs.c,v 1.63 2013/07/29 17:46:36 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <unistd.h> +#include <sys/param.h> +#include <fcntl.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include <sys/uio.h> +#include <stdlib.h> +#include "pax.h" +#include "extern.h" +#include "options.h" + +char *xtmp_name; + +static int +mk_link(char *,struct stat *,char *, int); + +static int warn_broken; + +/* + * routines that deal with file operations such as: creating, removing; + * and setting access modes, uid/gid and times of files + */ +#define SET_BITS (S_ISUID | S_ISGID) +#define FILE_BITS (S_IRWXU | S_IRWXG | S_IRWXO) +#define A_BITS (FILE_BITS | SET_BITS | S_ISVTX) + +/* + * The S_ISVTX (sticky bit) can be set by non-superuser on directories + * but not other kinds of files. + */ +#define FILEBITS(dir) ((dir) ? (FILE_BITS | S_ISVTX) : FILE_BITS) +#define SETBITS(dir) ((dir) ? SET_BITS : (SET_BITS | S_ISVTX)) + +static mode_t +apply_umask(mode_t mode) +{ + static mode_t cached_umask; + static int cached_umask_valid; + + if (!cached_umask_valid) { + cached_umask = umask(0); + umask(cached_umask); + cached_umask_valid = 1; + } + + return mode & ~cached_umask; +} + +/* + * file_creat() + * Create and open a file. + * Return: + * file descriptor or -1 for failure + */ + +int +file_creat(ARCHD *arcn, int write_to_hardlink) +{ + int fd = -1; + int oerrno; + + /* + * Some horribly busted tar implementations, have directory nodes + * that end in a /, but they mark as files. Compensate for that + * by not creating a directory node at this point, but a file node, + * and not creating the temp file. + */ + if (arcn->nlen != 0 && arcn->name[arcn->nlen - 1] == '/') { + if (!warn_broken) { + tty_warn(0, "Archive was created with a broken tar;" + " file `%s' is a directory, but marked as plain.", + arcn->name); + warn_broken = 1; + } + return -1; + } + + /* + * In "cpio" archives it's usually the last record of a set of + * hardlinks which includes the contents of the file. We cannot + * use a tempory file in that case because we couldn't link it + * with the existing other hardlinks after restoring the contents + * to it. And it's also useless to create the hardlink under a + * temporary name because the other hardlinks would have partial + * contents while restoring. + */ + if (write_to_hardlink) + return (open(arcn->name, O_TRUNC | O_EXCL | O_RDWR, 0)); + + /* + * Create a temporary file name so that the file doesn't have partial + * contents while restoring. + */ + arcn->tmp_name = malloc(arcn->nlen + 8); + if (arcn->tmp_name == NULL) { + syswarn(1, errno, "Cannot malloc %d bytes", arcn->nlen + 8); + return -1; + } + if (xtmp_name != NULL) + abort(); + xtmp_name = arcn->tmp_name; + + for (;;) { + /* + * try to create the temporary file we use to restore the + * contents info. if this fails, keep checking all the nodes + * in the path until chk_path() finds that it cannot fix + * anything further. if that happens we just give up. + */ + (void)snprintf(arcn->tmp_name, arcn->nlen + 8, "%s.XXXXXX", + arcn->name); + fd = mkstemp(arcn->tmp_name); + if (fd >= 0) + break; + oerrno = errno; + if (nodirs || chk_path(arcn->name,arcn->sb.st_uid,arcn->sb.st_gid) < 0) { + (void)fflush(listf); + syswarn(1, oerrno, "Cannot create %s", arcn->tmp_name); + xtmp_name = NULL; + free(arcn->tmp_name); + arcn->tmp_name = NULL; + return -1; + } + } + return fd; +} + +/* + * file_close() + * Close file descriptor to a file just created by pax. Sets modes, + * ownership and times as required. + * Return: + * 0 for success, -1 for failure + */ + +void +file_close(ARCHD *arcn, int fd) +{ + char *tmp_name; + int res; + + if (fd < 0) + return; + + tmp_name = (arcn->tmp_name != NULL) ? arcn->tmp_name : arcn->name; + + if (close(fd) < 0) + syswarn(0, errno, "Cannot close file descriptor on %s", + tmp_name); + + /* + * set owner/groups first as this may strip off mode bits we want + * then set file permission modes. Then set file access and + * modification times. + */ + if (pids) + res = set_ids(tmp_name, arcn->sb.st_uid, arcn->sb.st_gid); + else + res = 0; + + /* + * IMPORTANT SECURITY NOTE: + * if not preserving mode or we cannot set uid/gid, then PROHIBIT + * set uid/gid bits but restore the file modes (since mkstemp doesn't). + */ + if (!pmode || res) + arcn->sb.st_mode &= ~SETBITS(0); + if (pmode) + set_pmode(tmp_name, arcn->sb.st_mode); + else + set_pmode(tmp_name, + apply_umask((arcn->sb.st_mode & FILEBITS(0)))); + if (patime || pmtime) + set_ftime(tmp_name, arcn->sb.st_mtime, + arcn->sb.st_atime, 0, 0); + + /* Did we write directly to the target file? */ + if (arcn->tmp_name == NULL) + return; + + /* + * Finally, now the temp file is fully instantiated rename it to + * the desired file name. + */ + if (rename(tmp_name, arcn->name) < 0) { + syswarn(0, errno, "Cannot rename %s to %s", + tmp_name, arcn->name); + (void)unlink(tmp_name); + } + +#if HAVE_STRUCT_STAT_ST_FLAGS + if (pfflags && arcn->type != PAX_SLK) + set_chflags(arcn->name, arcn->sb.st_flags); +#endif + + free(arcn->tmp_name); + arcn->tmp_name = NULL; + xtmp_name = NULL; +} + +/* + * lnk_creat() + * Create a hard link to arcn->ln_name from arcn->name. arcn->ln_name + * must exist; + * Return: + * 0 if ok, -1 otherwise + */ + +int +lnk_creat(ARCHD *arcn, int *payload) +{ + struct stat sb; + + /* + * Check if this hardlink carries the "payload". In "cpio" archives + * it's usually the last record of a set of hardlinks which includes + * the contents of the file. + * + */ + *payload = S_ISREG(arcn->sb.st_mode) && + (arcn->sb.st_size > 0) && (arcn->sb.st_size <= arcn->skip); + + /* + * We may be running as root, so we have to be sure that link target + * is not a directory, so we lstat and check. XXX: This is still racy. + */ + if (lstat(arcn->ln_name, &sb) != -1 && S_ISDIR(sb.st_mode)) { + tty_warn(1, "A hard link to the directory %s is not allowed", + arcn->ln_name); + return -1; + } + + return mk_link(arcn->ln_name, &sb, arcn->name, 0); +} + +/* + * cross_lnk() + * Create a hard link to arcn->org_name from arcn->name. Only used in copy + * with the -l flag. No warning or error if this does not succeed (we will + * then just create the file) + * Return: + * 1 if copy() should try to create this file node + * 0 if cross_lnk() ok, -1 for fatal flaw (like linking to self). + */ + +int +cross_lnk(ARCHD *arcn) +{ + /* + * try to make a link to original file (-l flag in copy mode). make + * sure we do not try to link to directories in case we are running as + * root (and it might succeed). + */ + if (arcn->type == PAX_DIR) + return 1; + return mk_link(arcn->org_name, &(arcn->sb), arcn->name, 1); +} + +/* + * chk_same() + * In copy mode if we are not trying to make hard links between the src + * and destinations, make sure we are not going to overwrite ourselves by + * accident. This slows things down a little, but we have to protect all + * those people who make typing errors. + * Return: + * 1 the target does not exist, go ahead and copy + * 0 skip it file exists (-k) or may be the same as source file + */ + +int +chk_same(ARCHD *arcn) +{ + struct stat sb; + + /* + * if file does not exist, return. if file exists and -k, skip it + * quietly + */ + if (lstat(arcn->name, &sb) < 0) + return 1; + if (kflag) + return 0; + + /* + * better make sure the user does not have src == dest by mistake + */ + if ((arcn->sb.st_dev == sb.st_dev) && (arcn->sb.st_ino == sb.st_ino)) { + tty_warn(1, "Unable to copy %s, file would overwrite itself", + arcn->name); + return 0; + } + return 1; +} + +/* + * mk_link() + * try to make a hard link between two files. if ign set, we do not + * complain. + * Return: + * 0 if successful (or we are done with this file but no error, such as + * finding the from file exists and the user has set -k). + * 1 when ign was set to indicates we could not make the link but we + * should try to copy/extract the file as that might work (and is an + * allowed option). -1 an error occurred. + */ + +static int +mk_link(char *to, struct stat *to_sb, char *from, int ign) +{ + struct stat sb; + int oerrno; + + /* + * if from file exists, it has to be unlinked to make the link. If the + * file exists and -k is set, skip it quietly + */ + if (lstat(from, &sb) == 0) { + if (kflag) + return 0; + + /* + * make sure it is not the same file, protect the user + */ + if ((to_sb->st_dev==sb.st_dev)&&(to_sb->st_ino == sb.st_ino)) { + tty_warn(1, "Cannot link file %s to itself", to); + return -1; + } + + /* + * try to get rid of the file, based on the type + */ + if (S_ISDIR(sb.st_mode) && strcmp(from, ".") != 0) { + if (rmdir(from) < 0) { + syswarn(1, errno, "Cannot remove %s", from); + return -1; + } + } else if (unlink(from) < 0) { + if (!ign) { + syswarn(1, errno, "Cannot remove %s", from); + return -1; + } + return 1; + } + } + + /* + * from file is gone (or did not exist), try to make the hard link. + * if it fails, check the path and try it again (if chk_path() says to + * try again) + */ + for (;;) { + if (link(to, from) == 0) + break; + oerrno = errno; + if (chk_path(from, to_sb->st_uid, to_sb->st_gid) == 0) + continue; + if (!ign) { + syswarn(1, oerrno, "Cannot link to %s from %s", to, + from); + return -1; + } + return 1; + } + + /* + * all right the link was made + */ + return 0; +} + +/* + * node_creat() + * create an entry in the file system (other than a file or hard link). + * If successful, sets uid/gid modes and times as required. + * Return: + * 0 if ok, -1 otherwise + */ + +int +node_creat(ARCHD *arcn) +{ + int res; + int ign = 0; + int oerrno; + int pass = 0; + mode_t file_mode; + struct stat sb; + char target[MAXPATHLEN]; + char *nm = arcn->name; + int len; + + /* + * create node based on type, if that fails try to unlink the node and + * try again. finally check the path and try again. As noted in the + * file and link creation routines, this method seems to exhibit the + * best performance in general use workloads. + */ + file_mode = arcn->sb.st_mode & FILEBITS(arcn->type == PAX_DIR); + + for (;;) { + switch (arcn->type) { + case PAX_DIR: + /* + * If -h (or -L) was given in tar-mode, follow the + * potential symlink chain before trying to create the + * directory. + */ + if (strcmp(NM_TAR, argv0) == 0 && Lflag) { + while (lstat(nm, &sb) == 0 && + S_ISLNK(sb.st_mode)) { + len = readlink(nm, target, + sizeof target - 1); + if (len == -1) { + syswarn(0, errno, + "cannot follow symlink %s " + "in chain for %s", + nm, arcn->name); + res = -1; + goto badlink; + } + target[len] = '\0'; + nm = target; + } + } + res = domkdir(nm, file_mode); +badlink: + if (ign) + res = 0; + break; + case PAX_CHR: + file_mode |= S_IFCHR; + res = mknod(nm, file_mode, arcn->sb.st_rdev); + break; + case PAX_BLK: + file_mode |= S_IFBLK; + res = mknod(nm, file_mode, arcn->sb.st_rdev); + break; + case PAX_FIF: + res = mkfifo(nm, file_mode); + break; + case PAX_SCK: + /* + * Skip sockets, operation has no meaning under BSD + */ + tty_warn(0, + "%s skipped. Sockets cannot be copied or extracted", + nm); + return (-1); + case PAX_SLK: + res = symlink(arcn->ln_name, nm); + break; + case PAX_CTG: + case PAX_HLK: + case PAX_HRG: + case PAX_REG: + default: + /* + * we should never get here + */ + tty_warn(0, "%s has an unknown file type, skipping", + nm); + return (-1); + } + + /* + * if we were able to create the node break out of the loop, + * otherwise try to unlink the node and try again. if that + * fails check the full path and try a final time. + */ + if (res == 0) + break; + + /* + * we failed to make the node + */ + oerrno = errno; + switch (pass++) { + case 0: + if ((ign = unlnk_exist(nm, arcn->type)) < 0) + return (-1); + continue; + + case 1: + if (nodirs || + chk_path(nm, arcn->sb.st_uid, + arcn->sb.st_gid) < 0) { + syswarn(1, oerrno, "Cannot create %s", nm); + return (-1); + } + continue; + } + + /* + * it must be a file that exists but we can't create or + * remove, but we must avoid the infinite loop. + */ + break; + } + + /* + * we were able to create the node. set uid/gid, modes and times + */ + if (pids) + res = set_ids(nm, arcn->sb.st_uid, arcn->sb.st_gid); + else + res = 0; + + /* + * IMPORTANT SECURITY NOTE: + * if not preserving mode or we cannot set uid/gid, then PROHIBIT any + * set uid/gid bits + */ + if (!pmode || res) + arcn->sb.st_mode &= ~SETBITS(arcn->type == PAX_DIR); + if (pmode) + set_pmode(arcn->name, arcn->sb.st_mode); + + if (arcn->type == PAX_DIR && strcmp(NM_CPIO, argv0) != 0) { + /* + * Dirs must be processed again at end of extract to set times + * and modes to agree with those stored in the archive. However + * to allow extract to continue, we may have to also set owner + * rights. This allows nodes in the archive that are children + * of this directory to be extracted without failure. Both time + * and modes will be fixed after the entire archive is read and + * before pax exits. + */ + if (access(nm, R_OK | W_OK | X_OK) < 0) { + if (lstat(nm, &sb) < 0) { + syswarn(0, errno,"Cannot access %s (stat)", + arcn->name); + set_pmode(nm,file_mode | S_IRWXU); + } else { + /* + * We have to add rights to the dir, so we make + * sure to restore the mode. The mode must be + * restored AS CREATED and not as stored if + * pmode is not set. + */ + set_pmode(nm, ((sb.st_mode & + FILEBITS(arcn->type == PAX_DIR)) | + S_IRWXU)); + if (!pmode) + arcn->sb.st_mode = sb.st_mode; + } + + /* + * we have to force the mode to what was set here, + * since we changed it from the default as created. + */ + add_dir(nm, arcn->nlen, &(arcn->sb), 1); + } else if (pmode || patime || pmtime) + add_dir(nm, arcn->nlen, &(arcn->sb), 0); + } + + if (patime || pmtime) + set_ftime(arcn->name, arcn->sb.st_mtime, + arcn->sb.st_atime, 0, (arcn->type == PAX_SLK) ? 1 : 0); + +#if HAVE_STRUCT_STAT_ST_FLAGS + if (pfflags && arcn->type != PAX_SLK) + set_chflags(arcn->name, arcn->sb.st_flags); +#endif + return 0; +} + +/* + * unlnk_exist() + * Remove node from file system with the specified name. We pass the type + * of the node that is going to replace it. When we try to create a + * directory and find that it already exists, we allow processing to + * continue as proper modes etc will always be set for it later on. + * Return: + * 0 is ok to proceed, no file with the specified name exists + * -1 we were unable to remove the node, or we should not remove it (-k) + * 1 we found a directory and we were going to create a directory. + */ + +int +unlnk_exist(char *name, int type) +{ + struct stat sb; + + /* + * the file does not exist, or -k we are done + */ + if (lstat(name, &sb) < 0) + return 0; + if (kflag) + return -1; + + if (S_ISDIR(sb.st_mode)) { + /* + * try to remove a directory, if it fails and we were going to + * create a directory anyway, tell the caller (return a 1). + * + * don't try to remove the directory if the name is "." + * otherwise later file/directory creation fails. + */ + if (strcmp(name, ".") == 0) + return 1; + if (rmdir(name) < 0) { + if (type == PAX_DIR) + return 1; + syswarn(1, errno, "Cannot remove directory %s", name); + return -1; + } + return 0; + } + + /* + * try to get rid of all non-directory type nodes + */ + if (unlink(name) < 0) { + (void)fflush(listf); + syswarn(1, errno, "Cannot unlink %s", name); + return -1; + } + return 0; +} + +/* + * chk_path() + * We were trying to create some kind of node in the file system and it + * failed. chk_path() makes sure the path up to the node exists and is + * writable. When we have to create a directory that is missing along the + * path somewhere, the directory we create will be set to the same + * uid/gid as the file has (when uid and gid are being preserved). + * NOTE: this routine is a real performance loss. It is only used as a + * last resort when trying to create entries in the file system. + * Return: + * -1 when it could find nothing it is allowed to fix. + * 0 otherwise + */ + +int +chk_path(char *name, uid_t st_uid, gid_t st_gid) +{ + char *spt = name; + struct stat sb; + int retval = -1; + + /* + * watch out for paths with nodes stored directly in / (e.g. /bozo) + */ + if (*spt == '/') + ++spt; + + for(;;) { + /* + * work forward from the first / and check each part of + * the path + */ + spt = strchr(spt, '/'); + if (spt == NULL) + break; + *spt = '\0'; + + /* + * if it exists we assume it is a directory, it is not within + * the spec (at least it seems to read that way) to alter the + * file system for nodes NOT EXPLICITLY stored on the archive. + * If that assumption is changed, you would test the node here + * and figure out how to get rid of it (probably like some + * recursive unlink()) or fix up the directory permissions if + * required (do an access()). + */ + if (lstat(name, &sb) == 0) { + *(spt++) = '/'; + continue; + } + + /* + * the path fails at this point, see if we can create the + * needed directory and continue on + */ + if (domkdir(name, S_IRWXU | S_IRWXG | S_IRWXO) == -1) { + *spt = '/'; + retval = -1; + break; + } + + /* + * we were able to create the directory. We will tell the + * caller that we found something to fix, and it is ok to try + * and create the node again. + */ + retval = 0; + if (pids) + (void)set_ids(name, st_uid, st_gid); + + /* + * make sure the user doesn't have some strange umask that + * causes this newly created directory to be unusable. We fix + * the modes and restore them back to the creation default at + * the end of pax + */ + if ((access(name, R_OK | W_OK | X_OK) < 0) && + (lstat(name, &sb) == 0)) { + set_pmode(name, ((sb.st_mode & FILEBITS(0)) | + S_IRWXU)); + add_dir(name, spt - name, &sb, 1); + } + *(spt++) = '/'; + continue; + } + /* + * We perform one final check here, because if someone else + * created the directory in parallel with us, we might return + * the wrong error code, even if the directory exists now. + */ + if (retval == -1 && stat(name, &sb) == 0 && S_ISDIR(sb.st_mode)) + retval = 0; + return retval; +} + +/* + * set_ftime() + * Set the access time and modification time for a named file. If frc + * is non-zero we force these times to be set even if the user did not + * request access and/or modification time preservation (this is also + * used by -t to reset access times). + * When ign is zero, only those times the user has asked for are set, the + * other ones are left alone. We do not assume the un-documented feature + * of many utimes() implementations that consider a 0 time value as a do + * not set request. + * + * Unfortunately, there are systems where lutimes() is present but does + * not work on some filesystem types, which cannot be detected at + * compile time. This requires passing down symlink knowledge into + * this function to obtain correct operation. Linux with XFS is one + * example of such a system. + */ + +void +set_ftime(char *fnm, time_t mtime, time_t atime, int frc, int slk) +{ + struct timeval tv[2]; + struct stat sb; + + tv[0].tv_sec = atime; + tv[0].tv_usec = 0; + tv[1].tv_sec = mtime; + tv[1].tv_usec = 0; + if (!frc && (!patime || !pmtime)) { + /* + * if we are not forcing, only set those times the user wants + * set. We get the current values of the times if we need them. + */ + if (lstat(fnm, &sb) == 0) { +#if BSD4_4 && !HAVE_NBTOOL_CONFIG_H + if (!patime) + TIMESPEC_TO_TIMEVAL(&tv[0], &sb.st_atimespec); + if (!pmtime) + TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtimespec); +#else + if (!patime) + tv[0].tv_sec = sb.st_atime; + if (!pmtime) + tv[1].tv_sec = sb.st_mtime; +#endif + } else + syswarn(0, errno, "Cannot obtain file stats %s", fnm); + } + + /* + * set the times + */ +#if HAVE_LUTIMES + if (lutimes(fnm, tv) == 0) + return; + if (errno != ENOSYS) /* XXX linux: lutimes is per-FS */ + goto bad; +#endif + if (slk) + return; + if (utimes(fnm, tv) == -1) + goto bad; + return; +bad: + syswarn(1, errno, "Access/modification time set failed on: %s", fnm); +} + +/* + * set_ids() + * set the uid and gid of a file system node + * Return: + * 0 when set, -1 on failure + */ + +int +set_ids(char *fnm, uid_t uid, gid_t gid) +{ + if (geteuid() == 0) + if (lchown(fnm, uid, gid)) { + (void)fflush(listf); + syswarn(1, errno, "Cannot set file uid/gid of %s", + fnm); + return -1; + } + return 0; +} + +/* + * set_pmode() + * Set file access mode + */ + +void +set_pmode(char *fnm, mode_t mode) +{ + mode &= A_BITS; + if (lchmod(fnm, mode)) { + (void)fflush(listf); + syswarn(1, errno, "Cannot set permissions on %s", fnm); + } + return; +} + +/* + * set_chflags() + * Set 4.4BSD file flags + */ +void +set_chflags(char *fnm, u_int32_t flags) +{ + +#if 0 + if (chflags(fnm, flags) < 0 && errno != EOPNOTSUPP) + syswarn(1, errno, "Cannot set file flags on %s", fnm); +#endif + return; +} + +/* + * file_write() + * Write/copy a file (during copy or archive extract). This routine knows + * how to copy files with lseek holes in it. (Which are read as file + * blocks containing all 0's but do not have any file blocks associated + * with the data). Typical examples of these are files created by dbm + * variants (.pag files). While the file size of these files are huge, the + * actual storage is quite small (the files are sparse). The problem is + * the holes read as all zeros so are probably stored on the archive that + * way (there is no way to determine if the file block is really a hole, + * we only know that a file block of all zero's can be a hole). + * At this writing, no major archive format knows how to archive files + * with holes. However, on extraction (or during copy, -rw) we have to + * deal with these files. Without detecting the holes, the files can + * consume a lot of file space if just written to disk. This replacement + * for write when passed the basic allocation size of a file system block, + * uses lseek whenever it detects the input data is all 0 within that + * file block. In more detail, the strategy is as follows: + * While the input is all zero keep doing an lseek. Keep track of when we + * pass over file block boundaries. Only write when we hit a non zero + * input. once we have written a file block, we continue to write it to + * the end (we stop looking at the input). When we reach the start of the + * next file block, start checking for zero blocks again. Working on file + * block boundaries significantly reduces the overhead when copying files + * that are NOT very sparse. This overhead (when compared to a write) is + * almost below the measurement resolution on many systems. Without it, + * files with holes cannot be safely copied. It does has a side effect as + * it can put holes into files that did not have them before, but that is + * not a problem since the file contents are unchanged (in fact it saves + * file space). (Except on paging files for diskless clients. But since we + * cannot determine one of those file from here, we ignore them). If this + * ever ends up on a system where CTG files are supported and the holes + * are not desired, just do a conditional test in those routines that + * call file_write() and have it call write() instead. BEFORE CLOSING THE + * FILE, make sure to call file_flush() when the last write finishes with + * an empty block. A lot of file systems will not create an lseek hole at + * the end. In this case we drop a single 0 at the end to force the + * trailing 0's in the file. + * ---Parameters--- + * rem: how many bytes left in this file system block + * isempt: have we written to the file block yet (is it empty) + * sz: basic file block allocation size + * cnt: number of bytes on this write + * str: buffer to write + * Return: + * number of bytes written, -1 on write (or lseek) error. + */ + +int +file_write(int fd, char *str, int cnt, int *rem, int *isempt, int sz, + char *name) +{ + char *pt; + char *end; + int wcnt; + char *st = str; + char **strp; + size_t *lenp; + + /* + * while we have data to process + */ + while (cnt) { + if (!*rem) { + /* + * We are now at the start of file system block again + * (or what we think one is...). start looking for + * empty blocks again + */ + *isempt = 1; + *rem = sz; + } + + /* + * only examine up to the end of the current file block or + * remaining characters to write, whatever is smaller + */ + wcnt = MIN(cnt, *rem); + cnt -= wcnt; + *rem -= wcnt; + if (*isempt) { + /* + * have not written to this block yet, so we keep + * looking for zero's + */ + pt = st; + end = st + wcnt; + + /* + * look for a zero filled buffer + */ + while ((pt < end) && (*pt == '\0')) + ++pt; + + if (pt == end) { + /* + * skip, buf is empty so far + */ + if (fd > -1 && + lseek(fd, (off_t)wcnt, SEEK_CUR) < 0) { + syswarn(1, errno, "File seek on %s", + name); + return -1; + } + st = pt; + continue; + } + /* + * drat, the buf is not zero filled + */ + *isempt = 0; + } + + /* + * have non-zero data in this file system block, have to write + */ + switch (fd) { + case -PAX_GLF: + strp = &gnu_name_string; + lenp = &gnu_name_length; + break; + case -PAX_GLL: + strp = &gnu_link_string; + lenp = &gnu_link_length; + break; + default: + strp = NULL; + lenp = NULL; + break; + } + if (strp) { + char *nstr = *strp ? realloc(*strp, *lenp + wcnt + 1) : + malloc(wcnt + 1); + if (nstr == NULL) { + tty_warn(1, "Out of memory"); + return -1; + } + (void)strlcpy(&nstr[*lenp], st, wcnt + 1); + *strp = nstr; + *lenp += wcnt; + } else if (xwrite(fd, st, wcnt) != wcnt) { + syswarn(1, errno, "Failed write to file %s", name); + return -1; + } + st += wcnt; + } + return st - str; +} + +/* + * file_flush() + * when the last file block in a file is zero, many file systems will not + * let us create a hole at the end. To get the last block with zeros, we + * write the last BYTE with a zero (back up one byte and write a zero). + */ + +void +file_flush(int fd, char *fname, int isempt) +{ + static char blnk[] = "\0"; + + /* + * silly test, but make sure we are only called when the last block is + * filled with all zeros. + */ + if (!isempt) + return; + + /* + * move back one byte and write a zero + */ + if (lseek(fd, (off_t)-1, SEEK_CUR) < 0) { + syswarn(1, errno, "Failed seek on file %s", fname); + return; + } + + if (write_with_restart(fd, blnk, 1) < 0) + syswarn(1, errno, "Failed write to file %s", fname); + return; +} + +/* + * rdfile_close() + * close a file we have been reading (to copy or archive). If we have to + * reset access time (tflag) do so (the times are stored in arcn). + */ + +void +rdfile_close(ARCHD *arcn, int *fd) +{ + /* + * make sure the file is open + */ + if (*fd < 0) + return; + + (void)close(*fd); + *fd = -1; + if (!tflag) + return; + + /* + * user wants last access time reset + */ + set_ftime(arcn->org_name, arcn->sb.st_mtime, arcn->sb.st_atime, 1, 0); + return; +} + +/* + * set_crc() + * read a file to calculate its crc. This is a real drag. Archive formats + * that have this, end up reading the file twice (we have to write the + * header WITH the crc before writing the file contents. Oh well... + * Return: + * 0 if was able to calculate the crc, -1 otherwise + */ + +int +set_crc(ARCHD *arcn, int fd) +{ + int i; + int res; + off_t cpcnt = 0L; + u_long size; + unsigned long crc = 0L; + char tbuf[FILEBLK]; + struct stat sb; + + if (fd < 0) { + /* + * hmm, no fd, should never happen. well no crc then. + */ + arcn->crc = 0L; + return 0; + } + + if ((size = (u_long)arcn->sb.st_blksize) > (u_long)sizeof(tbuf)) + size = (u_long)sizeof(tbuf); + + /* + * read all the bytes we think that there are in the file. If the user + * is trying to archive an active file, forget this file. + */ + for(;;) { + if ((res = read(fd, tbuf, size)) <= 0) + break; + cpcnt += res; + for (i = 0; i < res; ++i) + crc += (tbuf[i] & 0xff); + } + + /* + * safety check. we want to avoid archiving files that are active as + * they can create inconsistent archive copies. + */ + if (cpcnt != arcn->sb.st_size) + tty_warn(1, "File changed size %s", arcn->org_name); + else if (fstat(fd, &sb) < 0) + syswarn(1, errno, "Failed stat on %s", arcn->org_name); + else if (arcn->sb.st_mtime != sb.st_mtime) + tty_warn(1, "File %s was modified during read", arcn->org_name); + else if (lseek(fd, (off_t)0L, SEEK_SET) < 0) + syswarn(1, errno, "File rewind failed on: %s", arcn->org_name); + else { + arcn->crc = crc; + return 0; + } + return -1; +} diff --git a/bin/pax/ftree.c b/bin/pax/ftree.c new file mode 100644 index 0000000..0d45429 --- /dev/null +++ b/bin/pax/ftree.c @@ -0,0 +1,741 @@ +/* $NetBSD: ftree.c,v 1.42 2012/09/27 00:44:59 christos Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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. + */ + +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Luke Mewburn of Wasabi Systems. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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 HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +#if 0 +static char sccsid[] = "@(#)ftree.c 8.2 (Berkeley) 4/18/94"; +#else +__RCSID("$NetBSD: ftree.c,v 1.42 2012/09/27 00:44:59 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <ctype.h> +#include <errno.h> +#include <fts.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "pax.h" +#include "ftree.h" +#include "extern.h" +#include "options.h" +#ifndef SMALL +#include "mtree.h" +#endif /* SMALL */ + +/* + * routines to interface with the fts library function. + * + * file args supplied to pax are stored on a single linked list (of type FTREE) + * and given to fts to be processed one at a time. pax "selects" files from + * the expansion of each arg into the corresponding file tree (if the arg is a + * directory, otherwise the node itself is just passed to pax). The selection + * is modified by the -n and -u flags. The user is informed when a specific + * file arg does not generate any selected files. -n keeps expanding the file + * tree arg until one of its files is selected, then skips to the next file + * arg. when the user does not supply the file trees as command line args to + * pax, they are read from stdin + */ + +static FTS *ftsp = NULL; /* current FTS handle */ +static int ftsopts; /* options to be used on fts_open */ +static char *farray[2]; /* array for passing each arg to fts */ +static FTREE *fthead = NULL; /* head of linked list of file args */ +static FTREE *fttail = NULL; /* tail of linked list of file args */ +static FTREE *ftcur = NULL; /* current file arg being processed */ +static FTSENT *ftent = NULL; /* current file tree entry */ +static int ftree_skip; /* when set skip to next file arg */ +#ifndef SMALL +static NODE *ftnode = NULL; /* mtree(8) specfile; used by -M */ +#endif /* SMALL */ + +static int ftree_arg(void); + +#define FTS_ERRNO(x) (x)->fts_errno + +/* + * ftree_start() + * initialize the options passed to fts_open() during this run of pax + * options are based on the selection of pax options by the user + * fts_start() also calls fts_arg() to open the first valid file arg. We + * also attempt to reset directory access times when -t (tflag) is set. + * Return: + * 0 if there is at least one valid file arg to process, -1 otherwise + */ + +int +ftree_start(void) +{ + +#ifndef SMALL + /* + * if -M is given, the list of filenames on stdin is actually + * an mtree(8) specfile, so parse the specfile into a NODE * + * tree at ftnode, for use by next_file() + */ + if (Mflag) { + if (fthead != NULL) { + tty_warn(1, + "The -M flag is only supported when reading file list from stdin"); + return -1; + } + ftnode = spec(stdin); + if (ftnode != NULL && + (ftnode->type != F_DIR || strcmp(ftnode->name, ".") != 0)) { + tty_warn(1, + "First node of specfile is not `.' directory"); + return -1; + } + return 0; + } +#endif /* SMALL */ + + /* + * set up the operation mode of fts, open the first file arg. We must + * use FTS_NOCHDIR, as the user may have to open multiple archives and + * if fts did a chdir off into the boondocks, we may create an archive + * volume in an place where the user did not expect to. + */ + ftsopts = FTS_NOCHDIR; + + /* + * optional user flags that effect file traversal + * -H command line symlink follow only (half follow) + * -L follow sylinks (logical) + * -P do not follow sylinks (physical). This is the default. + * -X do not cross over mount points + * -t preserve access times on files read. + * -n select only the first member of a file tree when a match is found + * -d do not extract subtrees rooted at a directory arg. + */ + if (Lflag) + ftsopts |= FTS_LOGICAL; + else + ftsopts |= FTS_PHYSICAL; + if (Hflag) + ftsopts |= FTS_COMFOLLOW; + if (Xflag) + ftsopts |= FTS_XDEV; + + if ((fthead == NULL) && ((farray[0] = malloc(PAXPATHLEN+2)) == NULL)) { + tty_warn(1, "Unable to allocate memory for file name buffer"); + return -1; + } + + if (ftree_arg() < 0) + return -1; + if (tflag && (atdir_start() < 0)) + return -1; + return 0; +} + +/* + * ftree_add() + * add the arg to the linked list of files to process. Each will be + * processed by fts one at a time + * Return: + * 0 if added to the linked list, -1 if failed + */ + +int +ftree_add(char *str, int isdir) +{ + FTREE *ft; + int len; + + /* + * simple check for bad args + */ + if ((str == NULL) || (*str == '\0')) { + tty_warn(0, "Invalid file name argument"); + return -1; + } + + /* + * allocate FTREE node and add to the end of the linked list (args are + * processed in the same order they were passed to pax). Get rid of any + * trailing / the user may pass us. (watch out for / by itself). + */ + if ((ft = (FTREE *)malloc(sizeof(FTREE))) == NULL) { + tty_warn(0, "Unable to allocate memory for filename"); + return -1; + } + + if (((len = strlen(str) - 1) > 0) && (str[len] == '/')) + str[len] = '\0'; + ft->fname = str; + ft->refcnt = -isdir; + ft->fow = NULL; + if (fthead == NULL) { + fttail = fthead = ft; + return 0; + } + fttail->fow = ft; + fttail = ft; + return 0; +} + +/* + * ftree_sel() + * this entry has been selected by pax. bump up reference count and handle + * -n and -d processing. + */ + +void +ftree_sel(ARCHD *arcn) +{ + /* + * set reference bit for this pattern. This linked list is only used + * when file trees are supplied pax as args. The list is not used when + * the trees are read from stdin. + */ + if (ftcur != NULL) + ftcur->refcnt = 1; + + /* + * if -n we are done with this arg, force a skip to the next arg when + * pax asks for the next file in next_file(). + * if -M we don't use fts(3), so the rest of this function is moot. + * if -d we tell fts only to match the directory (if the arg is a dir) + * and not the entire file tree rooted at that point. + */ + if (nflag) + ftree_skip = 1; + + if (Mflag || !dflag || (arcn->type != PAX_DIR)) + return; + + if (ftent != NULL) + (void)fts_set(ftsp, ftent, FTS_SKIP); +} + +/* + * ftree_chk() + * called at end on pax execution. Prints all those file args that did not + * have a selected member (reference count still 0) + */ + +void +ftree_chk(void) +{ + FTREE *ft; + int wban = 0; + + /* + * make sure all dir access times were reset. + */ + if (tflag) + atdir_end(); + + /* + * walk down list and check reference count. Print out those members + * that never had a match + */ + for (ft = fthead; ft != NULL; ft = ft->fow) { + if (ft->refcnt != 0) + continue; + if (wban == 0) { + tty_warn(1, + "WARNING! These file names were not selected:"); + ++wban; + } + (void)fprintf(stderr, "%s\n", ft->fname); + } +} + +/* + * ftree_arg() + * Get the next file arg for fts to process. Can be from either the linked + * list or read from stdin when the user did not them as args to pax. Each + * arg is processed until the first successful fts_open(). + * Return: + * 0 when the next arg is ready to go, -1 if out of file args (or EOF on + * stdin). + */ + +static int +ftree_arg(void) +{ + /* + * close off the current file tree + */ + if (ftsp != NULL) { + (void)fts_close(ftsp); + ftsp = NULL; + ftent = NULL; + } + + /* + * keep looping until we get a valid file tree to process. Stop when we + * reach the end of the list (or get an eof on stdin) + */ + for(;;) { + if (fthead == NULL) { + int i, c = EOF; + /* + * the user didn't supply any args, get the file trees + * to process from stdin; + */ + for (i = 0; i < PAXPATHLEN + 2;) { + c = getchar(); + if (c == EOF) + break; + else if (c == sep) { + if (i != 0) + break; + } else + farray[0][i++] = c; + } + if (i == 0) + return -1; + farray[0][i] = '\0'; + } else { + /* + * the user supplied the file args as arguments to pax + */ + if (ftcur == NULL) + ftcur = fthead; + else if ((ftcur = ftcur->fow) == NULL) + return -1; + + if (ftcur->refcnt < 0) { + /* + * chdir entry. + * Change directory and retry loop. + */ + if (ar_dochdir(ftcur->fname)) + return (-1); + continue; + } + farray[0] = ftcur->fname; + } + + /* + * watch it, fts wants the file arg stored in a array of char + * ptrs, with the last one a null. we use a two element array + * and set farray[0] to point at the buffer with the file name + * in it. We cannot pass all the file args to fts at one shot + * as we need to keep a handle on which file arg generates what + * files (the -n and -d flags need this). If the open is + * successful, return a 0. + */ + if ((ftsp = fts_open(farray, ftsopts, NULL)) != NULL) + break; + } + return 0; +} + +/* + * next_file() + * supplies the next file to process in the supplied archd structure. + * Return: + * 0 when contents of arcn have been set with the next file, -1 when done. + */ + +int +next_file(ARCHD *arcn) +{ +#ifndef SMALL + static char curdir[PAXPATHLEN+2], curpath[PAXPATHLEN+2]; + static int curdirlen; + + struct stat statbuf; + FTSENT Mftent; +#endif /* SMALL */ + int cnt; + time_t atime, mtime; + char *curlink; +#define MFTENT_DUMMY_DEV UINT_MAX + + curlink = NULL; +#ifndef SMALL + /* + * if parsing an mtree(8) specfile, build up `dummy' ftsent + * from specfile info, and jump below to complete setup of arcn. + */ + if (Mflag) { + int skipoptional; + + next_ftnode: + skipoptional = 0; + if (ftnode == NULL) /* tree is empty */ + return (-1); + + /* get current name */ + if (snprintf(curpath, sizeof(curpath), "%s%s%s", + curdir, curdirlen ? "/" : "", ftnode->name) + >= (int)sizeof(curpath)) { + tty_warn(1, "line %lu: %s: %s", (u_long)ftnode->lineno, + curdir, strerror(ENAMETOOLONG)); + return (-1); + } + ftnode->flags |= F_VISIT; /* mark node visited */ + + /* construct dummy FTSENT */ + Mftent.fts_path = curpath; + Mftent.fts_statp = &statbuf; + Mftent.fts_pointer = ftnode; + ftent = &Mftent; + /* look for existing file */ + if (lstat(Mftent.fts_path, &statbuf) == -1) { + if (ftnode->flags & F_OPT) + skipoptional = 1; + + /* missing: fake up stat info */ + memset(&statbuf, 0, sizeof(statbuf)); + statbuf.st_dev = MFTENT_DUMMY_DEV; + statbuf.st_ino = ftnode->lineno; + statbuf.st_size = 0; +#define NODETEST(t, m) \ + if (!(t)) { \ + tty_warn(1, "line %lu: %s: %s not specified", \ + (u_long)ftnode->lineno, \ + ftent->fts_path, m); \ + return -1; \ + } + statbuf.st_mode = nodetoino(ftnode->type); + NODETEST(ftnode->flags & F_TYPE, "type"); + NODETEST(ftnode->flags & F_MODE, "mode"); + if (!(ftnode->flags & F_TIME)) + statbuf.st_mtime = starttime; + NODETEST(ftnode->flags & (F_GID | F_GNAME), "group"); + NODETEST(ftnode->flags & (F_UID | F_UNAME), "user"); + if (ftnode->type == F_BLOCK || ftnode->type == F_CHAR) + NODETEST(ftnode->flags & F_DEV, + "device number"); + if (ftnode->type == F_LINK) + NODETEST(ftnode->flags & F_SLINK, "symlink"); + /* don't require F_FLAGS or F_SIZE */ +#undef NODETEST + } else { + if (ftnode->flags & F_TYPE && nodetoino(ftnode->type) + != (statbuf.st_mode & S_IFMT)) { + tty_warn(1, + "line %lu: %s: type mismatch: specfile %s, tree %s", + (u_long)ftnode->lineno, ftent->fts_path, + inotype(nodetoino(ftnode->type)), + inotype(statbuf.st_mode)); + return -1; + } + if (ftnode->type == F_DIR && (ftnode->flags & F_OPT)) + skipoptional = 1; + } + /* + * override settings with those from specfile + */ + if (ftnode->flags & F_MODE) { + statbuf.st_mode &= ~ALLPERMS; + statbuf.st_mode |= (ftnode->st_mode & ALLPERMS); + } + if (ftnode->flags & (F_GID | F_GNAME)) + statbuf.st_gid = ftnode->st_gid; + if (ftnode->flags & (F_UID | F_UNAME)) + statbuf.st_uid = ftnode->st_uid; +#if HAVE_STRUCT_STAT_ST_FLAGS + if (ftnode->flags & F_FLAGS) + statbuf.st_flags = ftnode->st_flags; +#endif + if (ftnode->flags & F_TIME) +#if BSD4_4 && !HAVE_NBTOOL_CONFIG_H + statbuf.st_mtimespec = ftnode->st_mtimespec; +#else + statbuf.st_mtime = ftnode->st_mtimespec.tv_sec; +#endif + if (ftnode->flags & F_DEV) + statbuf.st_rdev = ftnode->st_rdev; + if (ftnode->flags & F_SLINK) + curlink = ftnode->slink; + /* ignore F_SIZE */ + + /* + * find next node + */ + if (ftnode->type == F_DIR && ftnode->child != NULL) { + /* directory with unseen child */ + ftnode = ftnode->child; + curdirlen = strlcpy(curdir, curpath, sizeof(curdir)); + } else do { + if (ftnode->next != NULL) { + /* next node at current level */ + ftnode = ftnode->next; + } else { /* move back to parent */ + /* reset time only on first cd.. */ + if (Mftent.fts_pointer == ftnode && tflag && + (get_atdir(MFTENT_DUMMY_DEV, ftnode->lineno, + &mtime, &atime) == 0)) { + set_ftime(ftent->fts_path, + mtime, atime, 1, 0); + } + ftnode = ftnode->parent; + if (ftnode->parent == ftnode) + ftnode = NULL; + else { + curdirlen -= strlen(ftnode->name) + 1; + curdir[curdirlen] = '\0'; + } + } + } while (ftnode != NULL && ftnode->flags & F_VISIT); + if (skipoptional) /* skip optional entries */ + goto next_ftnode; + goto got_ftent; + } +#endif /* SMALL */ + + /* + * ftree_sel() might have set the ftree_skip flag if the user has the + * -n option and a file was selected from this file arg tree. (-n says + * only one member is matched for each pattern) ftree_skip being 1 + * forces us to go to the next arg now. + */ + if (ftree_skip) { + /* + * clear and go to next arg + */ + ftree_skip = 0; + if (ftree_arg() < 0) + return -1; + } + + if (ftsp == NULL) + return -1; + /* + * loop until we get a valid file to process + */ + for(;;) { + if ((ftent = fts_read(ftsp)) == NULL) { + /* + * out of files in this tree, go to next arg, if none + * we are done + */ + if (ftree_arg() < 0) + return -1; + continue; + } + + /* + * handle each type of fts_read() flag + */ + switch(ftent->fts_info) { + case FTS_D: + case FTS_DEFAULT: + case FTS_F: + case FTS_SL: + case FTS_SLNONE: + /* + * these are all ok + */ + break; + case FTS_DP: + /* + * already saw this directory. If the user wants file + * access times reset, we use this to restore the + * access time for this directory since this is the + * last time we will see it in this file subtree + * remember to force the time (this is -t on a read + * directory, not a created directory). + */ + if (!tflag || (get_atdir( + ftent->fts_statp->st_dev, ftent->fts_statp->st_ino, + &mtime, &atime) < 0)) + continue; + set_ftime(ftent->fts_path, mtime, atime, 1, 0); + continue; + case FTS_DC: + /* + * fts claims a file system cycle + */ + tty_warn(1,"File system cycle found at %s", + ftent->fts_path); + continue; + case FTS_DNR: + syswarn(1, FTS_ERRNO(ftent), + "Unable to read directory %s", ftent->fts_path); + continue; + case FTS_ERR: + syswarn(1, FTS_ERRNO(ftent), + "File system traversal error"); + continue; + case FTS_NS: + case FTS_NSOK: + syswarn(1, FTS_ERRNO(ftent), + "Unable to access %s", ftent->fts_path); + continue; + } + +#ifndef SMALL + got_ftent: +#endif /* SMALL */ + /* + * ok got a file tree node to process. copy info into arcn + * structure (initialize as required) + */ + arcn->skip = 0; + arcn->pad = 0; + arcn->ln_nlen = 0; + arcn->ln_name[0] = '\0'; + arcn->sb = *(ftent->fts_statp); + + /* + * file type based set up and copy into the arcn struct + * SIDE NOTE: + * we try to reset the access time on all files and directories + * we may read when the -t flag is specified. files are reset + * when we close them after copying. we reset the directories + * when we are done with their file tree (we also clean up at + * end in case we cut short a file tree traversal). However + * there is no way to reset access times on symlinks. + */ + switch(S_IFMT & arcn->sb.st_mode) { + case S_IFDIR: + arcn->type = PAX_DIR; + if (!tflag) + break; + add_atdir(ftent->fts_path, arcn->sb.st_dev, + arcn->sb.st_ino, arcn->sb.st_mtime, + arcn->sb.st_atime); + break; + case S_IFCHR: + arcn->type = PAX_CHR; + break; + case S_IFBLK: + arcn->type = PAX_BLK; + break; + case S_IFREG: + /* + * only regular files with have data to store on the + * archive. all others will store a zero length skip. + * the skip field is used by pax for actual data it has + * to read (or skip over). + */ + arcn->type = PAX_REG; + arcn->skip = arcn->sb.st_size; + break; + case S_IFLNK: + arcn->type = PAX_SLK; + if (curlink != NULL) { + cnt = strlcpy(arcn->ln_name, curlink, + sizeof(arcn->ln_name)); + /* + * have to read the symlink path from the file + */ + } else if ((cnt = + readlink(ftent->fts_path, arcn->ln_name, + sizeof(arcn->ln_name) - 1)) < 0) { + syswarn(1, errno, "Unable to read symlink %s", + ftent->fts_path); + continue; + } + /* + * set link name length, watch out readlink does not + * always null terminate the link path + */ + arcn->ln_name[cnt] = '\0'; + arcn->ln_nlen = cnt; + break; +#ifdef S_IFSOCK + case S_IFSOCK: + /* + * under BSD storing a socket is senseless but we will + * let the format specific write function make the + * decision of what to do with it. + */ + arcn->type = PAX_SCK; + break; +#endif + case S_IFIFO: + arcn->type = PAX_FIF; + break; + } + break; + } + + /* + * copy file name, set file name length + */ + arcn->nlen = strlcpy(arcn->name, ftent->fts_path, sizeof(arcn->name)); + arcn->org_name = arcn->fts_name; + strlcpy(arcn->fts_name, ftent->fts_path, sizeof arcn->fts_name); + if (strcmp(NM_CPIO, argv0) == 0) { + /* + * cpio does *not* descend directories listed in the + * arguments, unlike pax/tar, so needs special handling + * here. failure to do so results in massive amounts + * of duplicated files in the output. We kill fts after + * the first name is extracted, what a waste. + */ + ftcur->refcnt = 1; + (void)ftree_arg(); + } + return 0; +} diff --git a/bin/pax/ftree.h b/bin/pax/ftree.h new file mode 100644 index 0000000..490cf51 --- /dev/null +++ b/bin/pax/ftree.h @@ -0,0 +1,48 @@ +/* $NetBSD: ftree.h,v 1.5 2003/10/13 07:41:22 agc Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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. + * + * @(#)ftree.h 8.1 (Berkeley) 5/31/93 + */ + +/* + * Data structure used by the ftree.c routines to store the file args to be + * handed to fts(). It keeps a reference count of which args generated a + * "selected" member + */ + +typedef struct ftree { + char *fname; /* file tree name */ + int refcnt; /* has tree had a selected file? */ + struct ftree *fow; /* pointer to next entry on list */ +} FTREE; diff --git a/bin/pax/gen_subs.c b/bin/pax/gen_subs.c new file mode 100644 index 0000000..9228c69 --- /dev/null +++ b/bin/pax/gen_subs.c @@ -0,0 +1,437 @@ +/* $NetBSD: gen_subs.c,v 1.37 2018/11/30 00:53:11 christos Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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 HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +#if 0 +static char sccsid[] = "@(#)gen_subs.c 8.1 (Berkeley) 5/31/93"; +#else +__RCSID("$NetBSD: gen_subs.c,v 1.37 2018/11/30 00:53:11 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/param.h> + +#include <ctype.h> +#include <grp.h> +#include <pwd.h> +#include <vis.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <tzfile.h> +#include <unistd.h> + +#include "pax.h" +#include "extern.h" + +/* + * a collection of general purpose subroutines used by pax + */ + +/* + * constants used by ls_list() when printing out archive members + */ +#define MODELEN 20 +#define DATELEN 64 +#define SIXMONTHS ((DAYSPERNYEAR / 2) * SECSPERDAY) +#define CURFRMT "%b %e %H:%M" +#define OLDFRMT "%b %e %Y" +#ifndef UT_NAMESIZE +#define UT_NAMESIZE 8 +#endif +#define UT_GRPSIZE 6 + +/* + * convert time to string + */ +static void +formattime(char *buf, size_t buflen, time_t when) +{ + int error; + struct tm tm; + (void)localtime_r(&when, &tm); + + if (when + SIXMONTHS <= time(NULL)) + error = strftime(buf, buflen, OLDFRMT, &tm); + else + error = strftime(buf, buflen, CURFRMT, &tm); + + if (error == 0) + buf[0] = '\0'; +} + +/* + * ls_list() + * list the members of an archive in ls format + */ + +void +ls_list(ARCHD *arcn, time_t now, FILE *fp) +{ + struct stat *sbp; + char f_mode[MODELEN]; + char f_date[DATELEN]; + const char *user, *group; + + /* + * if not verbose, just print the file name + */ + if (!vflag) { + (void)fprintf(fp, "%s\n", arcn->name); + (void)fflush(fp); + return; + } + + /* + * user wants long mode + */ + sbp = &(arcn->sb); + strmode(sbp->st_mode, f_mode); + + /* + * time format based on age compared to the time pax was started. + */ + formattime(f_date, sizeof(f_date), arcn->sb.st_mtime); + /* + * print file mode, link count, uid, gid and time + */ + user = user_from_uid(sbp->st_uid, 0); + group = group_from_gid(sbp->st_gid, 0); + (void)fprintf(fp, "%s%2lu %-*s %-*s ", f_mode, + (unsigned long)sbp->st_nlink, + UT_NAMESIZE, user ? user : "", UT_GRPSIZE, group ? group : ""); + + /* + * print device id's for devices, or sizes for other nodes + */ + if ((arcn->type == PAX_CHR) || (arcn->type == PAX_BLK)) + (void)fprintf(fp, "%4lu,%4lu ", (long) MAJOR(sbp->st_rdev), + (long) MINOR(sbp->st_rdev)); + else { + (void)fprintf(fp, OFFT_FP("9") " ", (OFFT_T)sbp->st_size); + } + + /* + * print name and link info for hard and soft links + */ + (void)fprintf(fp, "%s %s", f_date, arcn->name); + if ((arcn->type == PAX_HLK) || (arcn->type == PAX_HRG)) + (void)fprintf(fp, " == %s\n", arcn->ln_name); + else if (arcn->type == PAX_SLK) + (void)fprintf(fp, " -> %s\n", arcn->ln_name); + else + (void)fputc('\n', fp); + (void)fflush(fp); +} + +/* + * tty_ls() + * print a short summary of file to tty. + */ + +void +ls_tty(ARCHD *arcn) +{ + char f_date[DATELEN]; + char f_mode[MODELEN]; + + formattime(f_date, sizeof(f_date), arcn->sb.st_mtime); + strmode(arcn->sb.st_mode, f_mode); + tty_prnt("%s%s %s\n", f_mode, f_date, arcn->name); + return; +} + +void +safe_print(const char *str, FILE *fp) +{ + char visbuf[5]; + const char *cp; + + /* + * if printing to a tty, use vis(3) to print special characters. + */ + if (isatty(fileno(fp))) { + for (cp = str; *cp; cp++) { + (void)vis(visbuf, cp[0], VIS_CSTYLE, cp[1]); + (void)fputs(visbuf, fp); + } + } else { + (void)fputs(str, fp); + } +} + +/* + * asc_u32() + * convert hex/octal character string into a uint32_t. We do not have to + * check for overflow! (the headers in all supported formats are not large + * enough to create an overflow). + * NOTE: strings passed to us are NOT TERMINATED. + * Return: + * uint32_t value + */ + +uint32_t +asc_u32(char *str, int len, int base) +{ + char *stop; + uint32_t tval = 0; + + stop = str + len; + + /* + * skip over leading blanks and zeros + */ + while ((str < stop) && ((*str == ' ') || (*str == '0'))) + ++str; + + /* + * for each valid digit, shift running value (tval) over to next digit + * and add next digit + */ + if (base == HEX) { + while (str < stop) { + if ((*str >= '0') && (*str <= '9')) + tval = (tval << 4) + (*str++ - '0'); + else if ((*str >= 'A') && (*str <= 'F')) + tval = (tval << 4) + 10 + (*str++ - 'A'); + else if ((*str >= 'a') && (*str <= 'f')) + tval = (tval << 4) + 10 + (*str++ - 'a'); + else + break; + } + } else { + while ((str < stop) && (*str >= '0') && (*str <= '7')) + tval = (tval << 3) + (*str++ - '0'); + } + return tval; +} + +/* + * u32_asc() + * convert an uintmax_t into an hex/oct ascii string. pads with LEADING + * ascii 0's to fill string completely + * NOTE: the string created is NOT TERMINATED. + */ + +int +u32_asc(uintmax_t val, char *str, int len, int base) +{ + char *pt; + uint32_t digit; + uintmax_t p; + + p = val & TOP_HALF; + if (p && p != TOP_HALF) + return -1; + + val &= BOTTOM_HALF; + + /* + * WARNING str is not '\0' terminated by this routine + */ + pt = str + len - 1; + + /* + * do a tailwise conversion (start at right most end of string to place + * least significant digit). Keep shifting until conversion value goes + * to zero (all digits were converted) + */ + if (base == HEX) { + while (pt >= str) { + if ((digit = (val & 0xf)) < 10) + *pt-- = '0' + (char)digit; + else + *pt-- = 'a' + (char)(digit - 10); + if ((val = (val >> 4)) == (u_long)0) + break; + } + } else { + while (pt >= str) { + *pt-- = '0' + (char)(val & 0x7); + if ((val = (val >> 3)) == 0) + break; + } + } + + /* + * pad with leading ascii ZEROS. We return -1 if we ran out of space. + */ + while (pt >= str) + *pt-- = '0'; + if (val != 0) + return -1; + return 0; +} + +/* + * asc_umax() + * convert hex/octal/base-256 value into a uintmax. + * NOTE: strings passed to us are NOT TERMINATED. + * Return: + * uintmax_t value; UINTMAX_MAX for overflow/negative + */ + +uintmax_t +asc_umax(char *str, int len, int base) +{ + char *stop; + uintmax_t tval = 0; + + stop = str + len; + + /* + * if the highest bit of first byte is set, it's base-256 encoded + * (base-256 is basically (n-1)-bit big endian signed + */ + if (str < stop && (*str & 0x80)) { + /* + * uintmax_t can't be negative, so fail on negative numbers + */ + if (*str & 0x40) + return UINTMAX_MAX; + + tval = *str++ & 0x3f; + while (str < stop) { + /* + * check for overflow + */ + if (tval > (UINTMAX_MAX/256)) + return UINTMAX_MAX; + tval = (tval << 8) | ((*str++) & 0xFF); + } + + return tval; + } + + /* + * skip over leading blanks and zeros + */ + while ((str < stop) && ((*str == ' ') || (*str == '0'))) + ++str; + + /* + * for each valid digit, shift running value (tval) over to next digit + * and add next digit + */ + if (base == HEX) { + while (str < stop) { + if ((*str >= '0') && (*str <= '9')) + tval = (tval << 4) + (*str++ - '0'); + else if ((*str >= 'A') && (*str <= 'F')) + tval = (tval << 4) + 10 + (*str++ - 'A'); + else if ((*str >= 'a') && (*str <= 'f')) + tval = (tval << 4) + 10 + (*str++ - 'a'); + else + break; + } + } else { + while ((str < stop) && (*str >= '0') && (*str <= '7')) + tval = (tval << 3) + (*str++ - '0'); + } + return tval; +} + +/* + * umax_asc() + * convert an uintmax_t into a hex/oct ascii string. pads with + * LEADING ascii 0's to fill string completely + * NOTE: the string created is NOT TERMINATED. + */ + +int +umax_asc(uintmax_t val, char *str, int len, int base) +{ + char *pt; + uintmax_t digit; + + /* + * WARNING str is not '\0' terminated by this routine + */ + pt = str + len - 1; + + /* + * do a tailwise conversion (start at right most end of string to place + * least significant digit). Keep shifting until conversion value goes + * to zero (all digits were converted) + */ + if (base == HEX) { + while (pt >= str) { + if ((digit = (val & 0xf)) < 10) + *pt-- = '0' + (char)digit; + else + *pt-- = 'a' + (char)(digit - 10); + if ((val = (val >> 4)) == 0) + break; + } + } else { + while (pt >= str) { + *pt-- = '0' + (char)(val & 0x7); + if ((val = (val >> 3)) == 0) + break; + } + } + + /* + * pad with leading ascii ZEROS. We return -1 if we ran out of space. + */ + while (pt >= str) + *pt-- = '0'; + if (val != 0) + return -1; + return 0; +} + +int +check_Aflag(void) +{ + + if (Aflag > 0) + return 1; + if (Aflag == 0) { + Aflag = -1; + tty_warn(0, + "Removing leading / from absolute path names in the archive"); + } + return 0; +} diff --git a/bin/pax/getoldopt.c b/bin/pax/getoldopt.c new file mode 100644 index 0000000..2d02e7e --- /dev/null +++ b/bin/pax/getoldopt.c @@ -0,0 +1,92 @@ +/* $NetBSD: getoldopt.c,v 1.23 2012/08/09 11:05:59 christos Exp $ */ + +/* + * Plug-compatible replacement for getopt() for parsing tar-like + * arguments. If the first argument begins with "-", it uses getopt; + * otherwise, it uses the old rules used by tar, dump, and ps. + * + * Written 25 August 1985 by John Gilmore (ihnp4!hoptoad!gnu) and placed + * in the Public Domain for your edification and enjoyment. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +__RCSID("$NetBSD: getoldopt.c,v 1.23 2012/08/09 11:05:59 christos Exp $"); +#endif /* not lint */ + +#if HAVE_NBTOOL_CONFIG_H +#include "compat_getopt.h" +#else +#include <getopt.h> +#endif +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdint.h> +#include <sys/stat.h> +#include "pax.h" +#include "extern.h" + +int +getoldopt(int argc, char **argv, const char *optstring, + struct option *longopts, int *idx) +{ + static char *key; /* Points to next keyletter */ + static char use_getopt; /* !=0 if argv[1][0] was '-' */ + char c; + char *place; + + optarg = NULL; + + if (key == NULL) { /* First time */ + if (argc < 2) return -1; + key = argv[1]; + if (*key == '-') + use_getopt++; + else + optind = 2; + } + + c = '\0'; + if (!use_getopt) { + c = *key++; + if (c == '\0') { + key--; + use_getopt = 1; + } + } + if (use_getopt) { + if (longopts != NULL) { + return getopt_long(argc, argv, optstring, + longopts, idx); + } else { + return getopt(argc, argv, optstring); + } + } + + place = strchr(optstring, c); + + if (place == NULL || c == ':') { + fprintf(stderr, "%s: unknown option %c\n", argv[0], c); + return '?'; + } + + place++; + if (*place == ':') { + if (optind < argc) { + optarg = argv[optind]; + optind++; + } else { + fprintf(stderr, "%s: %c argument missing\n", + argv[0], c); + return '?'; + } + } + + return c; +} diff --git a/bin/pax/options.c b/bin/pax/options.c new file mode 100644 index 0000000..74e1480 --- /dev/null +++ b/bin/pax/options.c @@ -0,0 +1,2229 @@ +/* $NetBSD: options.c,v 1.118 2015/12/19 18:45:52 christos Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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 HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +#if 0 +static char sccsid[] = "@(#)options.c 8.2 (Berkeley) 4/18/94"; +#else +__RCSID("$NetBSD: options.c,v 1.118 2015/12/19 18:45:52 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <ctype.h> +#include <errno.h> +#if HAVE_NBTOOL_CONFIG_H +#include "compat_getopt.h" +#else +#include <getopt.h> +#endif +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <inttypes.h> +#include <paths.h> +#include <util.h> +#include "pax.h" +#include "options.h" +#include "cpio.h" +#include "tar.h" +#include "extern.h" +#ifndef SMALL +#include "mtree.h" +#endif /* SMALL */ + +/* + * Routines which handle command line options + */ +struct stat tst; /* Timestamp to set if non-0 */ + +static int nopids; /* tar mode: suppress "pids" for -p option */ +static char flgch[] = FLGCH; /* list of all possible flags (pax) */ +static OPLIST *ophead = NULL; /* head for format specific options -x */ +static OPLIST *optail = NULL; /* option tail */ + +static int opt_add(const char *); +static int no_op(void); +static void printflg(unsigned int); +static int c_frmt(const void *, const void *); +static off_t str_offt(char *); +static char *get_line(FILE *fp); +#ifndef SMALL +static int set_tstamp(const char *, struct stat *); +#endif +static void pax_options(int, char **); +__dead static void pax_usage(void); +static void tar_options(int, char **); +__dead static void tar_usage(void); +#ifndef NO_CPIO +static void cpio_options(int, char **); +__dead static void cpio_usage(void); +#endif + +/* errors from get_line */ +#define GETLINE_FILE_CORRUPT 1 +#define GETLINE_OUT_OF_MEM 2 +static int get_line_error; + +#define BZIP2_CMD "bzip2" /* command to run as bzip2 */ +#define GZIP_CMD "gzip" /* command to run as gzip */ +#define XZ_CMD "xz" /* command to run as xz */ +#define COMPRESS_CMD "compress" /* command to run as compress */ + +/* + * Long options. + */ +#define OPT_USE_COMPRESS_PROGRAM 0 +#define OPT_CHECKPOINT 1 +#define OPT_UNLINK 2 +#define OPT_HELP 3 +#define OPT_ATIME_PRESERVE 4 +#define OPT_IGNORE_FAILED_READ 5 +#define OPT_REMOVE_FILES 6 +#define OPT_NULL 7 +#define OPT_TOTALS 8 +#define OPT_VERSION 9 +#define OPT_EXCLUDE 10 +#define OPT_BLOCK_COMPRESS 11 +#define OPT_NORECURSE 12 +#define OPT_FORCE_LOCAL 13 +#define OPT_INSECURE 14 +#define OPT_STRICT 15 +#define OPT_SPARSE 16 +#define OPT_XZ 17 +#define OPT_GNU 18 +#define OPT_TIMESTAMP 19 +#if !HAVE_NBTOOL_CONFIG_H +#define OPT_CHROOT 20 +#endif + +/* + * Format specific routine table - MUST BE IN SORTED ORDER BY NAME + * (see pax.h for description of each function) + * + * name, blksz, hdsz, udev, hlk, blkagn, inhead, id, st_read, + * read, end_read, st_write, write, end_write, trail, + * subtrail, rd_data, wr_data, options + */ + +FSUB fsub[] = { +#ifndef NO_CPIO +/* 0: OLD BINARY CPIO */ + { "bcpio", 5120, sizeof(HD_BCPIO), 1, 0, 0, 1, bcpio_id, cpio_strd, + bcpio_rd, bcpio_endrd, cpio_stwr, bcpio_wr, cpio_endwr, NULL, + cpio_subtrail, rd_wrfile, wr_rdfile, bad_opt }, + +/* 1: OLD OCTAL CHARACTER CPIO */ + { "cpio", 5120, sizeof(HD_CPIO), 1, 0, 0, 1, cpio_id, cpio_strd, + cpio_rd, cpio_endrd, cpio_stwr, cpio_wr, cpio_endwr, NULL, + cpio_subtrail, rd_wrfile, wr_rdfile, bad_opt }, + +/* 2: SVR4 HEX CPIO */ + { "sv4cpio", 5120, sizeof(HD_VCPIO), 1, 0, 0, 1, vcpio_id, cpio_strd, + vcpio_rd, vcpio_endrd, cpio_stwr, vcpio_wr, cpio_endwr, NULL, + cpio_subtrail, rd_wrfile, wr_rdfile, bad_opt }, + +/* 3: SVR4 HEX CPIO WITH CRC */ + { "sv4crc", 5120, sizeof(HD_VCPIO), 1, 0, 0, 1, crc_id, crc_strd, + vcpio_rd, vcpio_endrd, crc_stwr, vcpio_wr, cpio_endwr, NULL, + cpio_subtrail, rd_wrfile, wr_rdfile, bad_opt }, +#endif +/* 4: OLD TAR */ + { "tar", 10240, BLKMULT, 0, 1, BLKMULT, 0, tar_id, no_op, + tar_rd, tar_endrd, no_op, tar_wr, tar_endwr, tar_trail, + NULL, rd_wrfile, wr_rdfile, tar_opt }, + +/* 5: POSIX USTAR */ + { "ustar", 10240, BLKMULT, 0, 1, BLKMULT, 0, ustar_id, ustar_strd, + ustar_rd, tar_endrd, ustar_stwr, ustar_wr, tar_endwr, tar_trail, + NULL, rd_wrfile, wr_rdfile, bad_opt } +}; +#ifndef NO_CPIO +#define F_BCPIO 0 /* old binary cpio format */ +#define F_CPIO 1 /* old octal character cpio format */ +#define F_SV4CPIO 2 /* SVR4 hex cpio format */ +#define F_SV4CRC 3 /* SVR4 hex with crc cpio format */ +#define F_TAR 4 /* old V7 UNIX tar format */ +#define F_USTAR 5 /* ustar format */ +#else +#define F_TAR 0 /* old V7 UNIX tar format */ +#define F_USTAR 1 /* ustar format */ +#endif +#define DEFLT F_USTAR /* default write format from list above */ + +/* + * ford is the archive search order used by get_arc() to determine what kind + * of archive we are dealing with. This helps to properly id archive formats + * some formats may be subsets of others.... + */ +int ford[] = {F_USTAR, F_TAR, +#ifndef NO_CPIO + F_SV4CRC, F_SV4CPIO, F_CPIO, F_BCPIO, +#endif + -1}; + +/* + * filename record separator + */ +int sep = '\n'; + +/* + * Do we have -C anywhere? + */ +int havechd = 0; + +/* + * options() + * figure out if we are pax, tar or cpio. Call the appropriate options + * parser + */ + +void +options(int argc, char **argv) +{ + + /* + * Are we acting like pax, tar or cpio (based on argv[0]) + */ + if ((argv0 = strrchr(argv[0], '/')) != NULL) + argv0++; + else + argv0 = argv[0]; + + if (strstr(argv0, NM_TAR)) { + argv0 = NM_TAR; + tar_options(argc, argv); +#ifndef NO_CPIO + } else if (strstr(argv0, NM_CPIO)) { + argv0 = NM_CPIO; + cpio_options(argc, argv); +#endif + } else { + argv0 = NM_PAX; + pax_options(argc, argv); + } +} + +struct option pax_longopts[] = { + { "insecure", no_argument, 0, + OPT_INSECURE }, + { "force-local", no_argument, 0, + OPT_FORCE_LOCAL }, + { "use-compress-program", required_argument, 0, + OPT_USE_COMPRESS_PROGRAM }, + { "xz", no_argument, 0, + OPT_XZ }, + { "gnu", no_argument, 0, + OPT_GNU }, + { "timestamp", required_argument, 0, + OPT_TIMESTAMP }, + { 0, 0, 0, + 0 }, +}; + +/* + * pax_options() + * look at the user specified flags. set globals as required and check if + * the user specified a legal set of flags. If not, complain and exit + */ + +static void +pax_options(int argc, char **argv) +{ + int c; + size_t i; + u_int64_t flg = 0; + u_int64_t bflg = 0; + char *pt; + FSUB tmp; + + /* + * process option flags + */ + while ((c = getopt_long(argc, argv, + "0ab:cdf:ijklno:p:rs:tuvwx:zAB:DE:G:HLMN:OPT:U:VXYZ", + pax_longopts, NULL)) != -1) { + switch (c) { + case '0': + sep = '\0'; + break; + case 'a': + /* + * append + */ + flg |= AF; + break; + case 'b': + /* + * specify blocksize + */ + flg |= BF; + if ((wrblksz = (int)str_offt(optarg)) <= 0) { + tty_warn(1, "Invalid block size %s", optarg); + pax_usage(); + } + break; + case 'c': + /* + * inverse match on patterns + */ + cflag = 1; + flg |= CF; + break; + case 'd': + /* + * match only dir on extract, not the subtree at dir + */ + dflag = 1; + flg |= DF; + break; + case 'f': + /* + * filename where the archive is stored + */ + arcname = optarg; + flg |= FF; + break; + case 'i': + /* + * interactive file rename + */ + iflag = 1; + flg |= IF; + break; + case 'j': + /* + * pass through bzip2 + */ + gzip_program = BZIP2_CMD; + break; + case 'k': + /* + * do not clobber files that exist + */ + kflag = 1; + flg |= KF; + break; + case 'l': + /* + * try to link src to dest with copy (-rw) + */ + lflag = 1; + flg |= LF; + break; + case 'n': + /* + * select first match for a pattern only + */ + nflag = 1; + flg |= NF; + break; + case 'o': + /* + * pass format specific options + */ + flg |= OF; + if (opt_add(optarg) < 0) + pax_usage(); + break; + case 'p': + /* + * specify file characteristic options + */ + for (pt = optarg; *pt != '\0'; ++pt) { + switch(*pt) { + case 'a': + /* + * do not preserve access time + */ + patime = 0; + break; + case 'e': + /* + * preserve user id, group id, file + * mode, access/modification times + * and file flags. + */ + pids = 1; + pmode = 1; + patime = 1; + pmtime = 1; + pfflags = 1; + break; +#if 0 + case 'f': + /* + * do not preserve file flags + */ + pfflags = 0; + break; +#endif + case 'm': + /* + * do not preserve modification time + */ + pmtime = 0; + break; + case 'o': + /* + * preserve uid/gid + */ + pids = 1; + break; + case 'p': + /* + * preserve file mode bits + */ + pmode = 1; + break; + default: + tty_warn(1, "Invalid -p string: %c", + *pt); + pax_usage(); + break; + } + } + flg |= PF; + break; + case 'r': + /* + * read the archive + */ + flg |= RF; + break; + case 's': + /* + * file name substitution name pattern + */ + if (rep_add(optarg) < 0) { + pax_usage(); + break; + } + flg |= SF; + break; + case 't': + /* + * preserve access time on filesystem nodes we read + */ + tflag = 1; + flg |= TF; + break; + case 'u': + /* + * ignore those older files + */ + uflag = 1; + flg |= UF; + break; + case 'v': + /* + * verbose operation mode + */ + vflag = 1; + flg |= VF; + break; + case 'w': + /* + * write an archive + */ + flg |= WF; + break; + case 'x': + /* + * specify an archive format on write + */ + tmp.name = optarg; + frmt = (FSUB *)bsearch((void *)&tmp, (void *)fsub, + sizeof(fsub)/sizeof(FSUB), sizeof(FSUB), c_frmt); + if (frmt != NULL) { + flg |= XF; + break; + } + tty_warn(1, "Unknown -x format: %s", optarg); + (void)fputs("pax: Known -x formats are:", stderr); + for (i = 0; i < (sizeof(fsub)/sizeof(FSUB)); ++i) + (void)fprintf(stderr, " %s", fsub[i].name); + (void)fputs("\n\n", stderr); + pax_usage(); + break; + case 'z': + /* + * use gzip. Non standard option. + */ + gzip_program = GZIP_CMD; + break; + case 'A': + Aflag = 1; + flg |= CAF; + break; + case 'B': + /* + * non-standard option on number of bytes written on a + * single archive volume. + */ + if ((wrlimit = str_offt(optarg)) <= 0) { + tty_warn(1, "Invalid write limit %s", optarg); + pax_usage(); + } + if (wrlimit % BLKMULT) { + tty_warn(1, + "Write limit is not a %d byte multiple", + BLKMULT); + pax_usage(); + } + flg |= CBF; + break; + case 'D': + /* + * On extraction check file inode change time before the + * modification of the file name. Non standard option. + */ + Dflag = 1; + flg |= CDF; + break; + case 'E': + /* + * non-standard limit on read faults + * 0 indicates stop after first error, values + * indicate a limit, "none" try forever + */ + flg |= CEF; + if (strcmp(none, optarg) == 0) + maxflt = -1; + else if ((maxflt = atoi(optarg)) < 0) { + tty_warn(1, + "Error count value must be positive"); + pax_usage(); + } + break; + case 'G': + /* + * non-standard option for selecting files within an + * archive by group (gid or name) + */ + if (grp_add(optarg) < 0) { + pax_usage(); + break; + } + flg |= CGF; + break; + case 'H': + /* + * follow command line symlinks only + */ + Hflag = 1; + flg |= CHF; + break; + case 'L': + /* + * follow symlinks + */ + Lflag = 1; + flg |= CLF; + break; +#ifdef SMALL + case 'M': + case 'N': + tty_warn(1, "Support for -%c is not compiled in", c); + exit(1); +#else /* !SMALL */ + case 'M': + /* + * Treat list of filenames on stdin as an + * mtree(8) specfile. Non standard option. + */ + Mflag = 1; + flg |= CMF; + break; + case 'N': + /* + * Use alternative directory for user db lookups. + */ + if (!setup_getid(optarg)) { + tty_warn(1, + "Unable to use user and group databases in `%s'", + optarg); + pax_usage(); + } + break; +#endif /* !SMALL */ + case 'O': + /* + * Force one volume. Non standard option. + */ + force_one_volume = 1; + break; + case 'P': + /* + * do NOT follow symlinks (default) + */ + Lflag = 0; + flg |= CPF; + break; + case 'T': + /* + * non-standard option for selecting files within an + * archive by modification time range (lower,upper) + */ + if (trng_add(optarg) < 0) { + pax_usage(); + break; + } + flg |= CTF; + break; + case 'U': + /* + * non-standard option for selecting files within an + * archive by user (uid or name) + */ + if (usr_add(optarg) < 0) { + pax_usage(); + break; + } + flg |= CUF; + break; + case 'V': + /* + * somewhat verbose operation mode (no listing) + */ + Vflag = 1; + flg |= VSF; + break; + case 'X': + /* + * do not pass over mount points in the file system + */ + Xflag = 1; + flg |= CXF; + break; + case 'Y': + /* + * On extraction check file inode change time after the + * modification of the file name. Non standard option. + */ + Yflag = 1; + flg |= CYF; + break; + case 'Z': + /* + * On extraction check modification time after the + * modification of the file name. Non standard option. + */ + Zflag = 1; + flg |= CZF; + break; + case OPT_INSECURE: + secure = 0; + break; + case OPT_FORCE_LOCAL: + forcelocal = 1; + break; + case OPT_USE_COMPRESS_PROGRAM: + gzip_program = optarg; + break; + case OPT_XZ: + gzip_program = XZ_CMD; + break; + case OPT_GNU: + is_gnutar = 1; + break; +#ifndef SMALL + case OPT_TIMESTAMP: + if (set_tstamp(optarg, &tst) == -1) { + tty_warn(1, "Invalid timestamp `%s'", optarg); + tar_usage(); + } + break; +#endif + case '?': + default: + pax_usage(); + break; + } + } + + /* + * figure out the operation mode of pax read,write,extract,copy,append + * or list. check that we have not been given a bogus set of flags + * for the operation mode. + */ + if (ISLIST(flg)) { + act = LIST; + listf = stdout; + bflg = flg & BDLIST; + } else if (ISEXTRACT(flg)) { + act = EXTRACT; + bflg = flg & BDEXTR; + } else if (ISARCHIVE(flg)) { + act = ARCHIVE; + bflg = flg & BDARCH; + } else if (ISAPPND(flg)) { + act = APPND; + bflg = flg & BDARCH; + } else if (ISCOPY(flg)) { + act = COPY; + bflg = flg & BDCOPY; + } else + pax_usage(); + if (bflg) { + printflg(flg); + pax_usage(); + } + + /* + * if we are writing (ARCHIVE) we use the default format if the user + * did not specify a format. when we write during an APPEND, we will + * adopt the format of the existing archive if none was supplied. + */ + if (!(flg & XF) && (act == ARCHIVE)) + frmt = &(fsub[DEFLT]); + + /* + * process the args as they are interpreted by the operation mode + */ + switch (act) { + case LIST: + case EXTRACT: + for (; optind < argc; optind++) + if (pat_add(argv[optind], NULL, 0) < 0) + pax_usage(); + break; + case COPY: + if (optind >= argc) { + tty_warn(0, "Destination directory was not supplied"); + pax_usage(); + } + --argc; + dirptr = argv[argc]; + if (mkpath(dirptr) < 0) + exit(1); + /* FALLTHROUGH */ + case ARCHIVE: + case APPND: + for (; optind < argc; optind++) + if (ftree_add(argv[optind], 0) < 0) + pax_usage(); + /* + * no read errors allowed on updates/append operation! + */ + maxflt = 0; + break; + } +} + + +/* + * tar_options() + * look at the user specified flags. set globals as required and check if + * the user specified a legal set of flags. If not, complain and exit + */ + +struct option tar_longopts[] = { + { "block-size", required_argument, 0, 'b' }, + { "bunzip2", no_argument, 0, 'j' }, + { "bzip2", no_argument, 0, 'j' }, + { "create", no_argument, 0, 'c' }, /* F */ + /* -e -- no corresponding long option */ + { "file", required_argument, 0, 'f' }, + { "dereference", no_argument, 0, 'h' }, + { "keep-old-files", no_argument, 0, 'k' }, + { "one-file-system", no_argument, 0, 'l' }, + { "modification-time", no_argument, 0, 'm' }, + { "old-archive", no_argument, 0, 'o' }, + { "portability", no_argument, 0, 'o' }, + { "same-permissions", no_argument, 0, 'p' }, + { "preserve-permissions", no_argument, 0, 'p' }, + { "preserve", no_argument, 0, 'p' }, + { "fast-read", no_argument, 0, 'q' }, + { "append", no_argument, 0, 'r' }, /* F */ + { "update", no_argument, 0, 'u' }, /* F */ + { "list", no_argument, 0, 't' }, /* F */ + { "verbose", no_argument, 0, 'v' }, + { "interactive", no_argument, 0, 'w' }, + { "confirmation", no_argument, 0, 'w' }, + { "extract", no_argument, 0, 'x' }, /* F */ + { "get", no_argument, 0, 'x' }, /* F */ + { "gzip", no_argument, 0, 'z' }, + { "gunzip", no_argument, 0, 'z' }, + { "read-full-blocks", no_argument, 0, 'B' }, + { "directory", required_argument, 0, 'C' }, + { "xz", no_argument, 0, 'J' }, + { "to-stdout", no_argument, 0, 'O' }, + { "absolute-paths", no_argument, 0, 'P' }, + { "sparse", no_argument, 0, 'S' }, + { "files-from", required_argument, 0, 'T' }, + { "summary", no_argument, 0, 'V' }, + { "stats", no_argument, 0, 'V' }, + { "exclude-from", required_argument, 0, 'X' }, + { "compress", no_argument, 0, 'Z' }, + { "uncompress", no_argument, 0, 'Z' }, + { "strict", no_argument, 0, + OPT_STRICT }, + { "atime-preserve", no_argument, 0, + OPT_ATIME_PRESERVE }, + { "unlink", no_argument, 0, + OPT_UNLINK }, + { "use-compress-program", required_argument, 0, + OPT_USE_COMPRESS_PROGRAM }, + { "force-local", no_argument, 0, + OPT_FORCE_LOCAL }, + { "insecure", no_argument, 0, + OPT_INSECURE }, + { "exclude", required_argument, 0, + OPT_EXCLUDE }, + { "no-recursion", no_argument, 0, + OPT_NORECURSE }, +#if !HAVE_NBTOOL_CONFIG_H + { "chroot", no_argument, 0, + OPT_CHROOT }, +#endif + { "timestamp", required_argument, 0, + OPT_TIMESTAMP }, +#if 0 /* Not implemented */ + { "catenate", no_argument, 0, 'A' }, /* F */ + { "concatenate", no_argument, 0, 'A' }, /* F */ + { "diff", no_argument, 0, 'd' }, /* F */ + { "compare", no_argument, 0, 'd' }, /* F */ + { "checkpoint", no_argument, 0, + OPT_CHECKPOINT }, + { "help", no_argument, 0, + OPT_HELP }, + { "info-script", required_argument, 0, 'F' }, + { "new-volume-script", required_argument, 0, 'F' }, + { "incremental", no_argument, 0, 'G' }, + { "listed-incremental", required_argument, 0, 'g' }, + { "ignore-zeros", no_argument, 0, 'i' }, + { "ignore-failed-read", no_argument, 0, + OPT_IGNORE_FAILED_READ }, + { "starting-file", no_argument, 0, 'K' }, + { "tape-length", required_argument, 0, 'L' }, + { "multi-volume", no_argument, 0, 'M' }, + { "after-date", required_argument, 0, 'N' }, + { "newer", required_argument, 0, 'N' }, + { "record-number", no_argument, 0, 'R' }, + { "remove-files", no_argument, 0, + OPT_REMOVE_FILES }, + { "same-order", no_argument, 0, 's' }, + { "preserve-order", no_argument, 0, 's' }, + { "null", no_argument, 0, + OPT_NULL }, + { "totals", no_argument, 0, + OPT_TOTALS }, + { "volume-name", required_argument, 0, 'V' }, /* XXX */ + { "label", required_argument, 0, 'V' }, /* XXX */ + { "version", no_argument, 0, + OPT_VERSION }, + { "verify", no_argument, 0, 'W' }, + { "block-compress", no_argument, 0, + OPT_BLOCK_COMPRESS }, +#endif + { 0, 0, 0, 0 }, +}; + +static void +tar_set_action(int op) +{ + if (act != ERROR && act != op) + tar_usage(); + act = op; +} + +static void +tar_options(int argc, char **argv) +{ + int c; + int fstdin = 0; + int Oflag = 0; + int nincfiles = 0; + int incfiles_max = 0; + struct incfile { + char *file; + char *dir; + }; + struct incfile *incfiles = NULL; + + /* + * Set default values. + */ + rmleadslash = 1; + is_gnutar = 1; + + /* + * process option flags + */ + while ((c = getoldopt(argc, argv, + "+b:cef:hjklmopqrs:tuvwxzBC:HI:JOPST:X:Z014578", + tar_longopts, NULL)) + != -1) { + switch(c) { + case 'b': + /* + * specify blocksize in 512-byte blocks + */ + if ((wrblksz = (int)str_offt(optarg)) <= 0) { + tty_warn(1, "Invalid block size %s", optarg); + tar_usage(); + } + wrblksz *= 512; /* XXX - check for int oflow */ + break; + case 'c': + /* + * create an archive + */ + tar_set_action(ARCHIVE); + break; + case 'e': + /* + * stop after first error + */ + maxflt = 0; + break; + case 'f': + /* + * filename where the archive is stored + */ + if ((optarg[0] == '-') && (optarg[1]== '\0')) { + /* + * treat a - as stdin + */ + fstdin = 1; + arcname = NULL; + break; + } + fstdin = 0; + arcname = optarg; + break; + case 'h': + /* + * follow symlinks + */ + Lflag = 1; + break; + case 'j': + /* + * pass through bzip2. not a standard option + */ + gzip_program = BZIP2_CMD; + break; + case 'k': + /* + * do not clobber files that exist + */ + kflag = 1; + break; + case 'l': + /* + * do not pass over mount points in the file system + */ + Xflag = 1; + break; + case 'm': + /* + * do not preserve modification time + */ + pmtime = 0; + break; + case 'o': + /* + * This option does several things based on whether + * this is a create or extract operation. + */ + if (act == ARCHIVE) { + /* GNU tar: write V7 format archives. */ + Oflag = 1; + /* 4.2BSD: don't add directory entries. */ + if (opt_add("write_opt=nodir") < 0) + tar_usage(); + + } else { + /* SUS: don't preserve owner/group. */ + pids = 0; + nopids = 1; + } + break; + case 'p': + /* + * preserve user id, group id, file + * mode, access/modification times + */ + if (!nopids) + pids = 1; + pmode = 1; + patime = 1; + pmtime = 1; + break; + case 'q': + /* + * select first match for a pattern only + */ + nflag = 1; + break; + case 'r': + case 'u': + /* + * append to the archive + */ + tar_set_action(APPND); + break; + case 's': + /* + * file name substitution name pattern + */ + if (rep_add(optarg) < 0) { + tar_usage(); + break; + } + break; + case 't': + /* + * list contents of the tape + */ + tar_set_action(LIST); + break; + case 'v': + /* + * verbose operation mode + */ + vflag = 1; + break; + case 'w': + /* + * interactive file rename + */ + iflag = 1; + break; + case 'x': + /* + * extract an archive, preserving mode, + * and mtime if possible. + */ + tar_set_action(EXTRACT); + pmtime = 1; + break; + case 'z': + /* + * use gzip. Non standard option. + */ + gzip_program = GZIP_CMD; + break; + case 'B': + /* + * Nothing to do here, this is pax default + */ + break; + case 'C': + havechd++; + chdname = optarg; + break; + case 'H': + /* + * follow command line symlinks only + */ + Hflag = 1; + break; + case 'I': + case 'T': + if (++nincfiles > incfiles_max) { + incfiles_max = nincfiles + 3; + incfiles = realloc(incfiles, + sizeof(*incfiles) * incfiles_max); + if (incfiles == NULL) { + tty_warn(0, "Unable to allocate space " + "for option list"); + exit(1); + } + } + incfiles[nincfiles - 1].file = optarg; + incfiles[nincfiles - 1].dir = chdname; + break; + case 'J': + gzip_program = XZ_CMD; + break; + case 'O': + Oflag = 1; + break; + case 'P': + /* + * do not remove leading '/' from pathnames + */ + rmleadslash = 0; + Aflag = 1; + break; + case 'S': + /* do nothing; we already generate sparse files */ + break; + case 'V': + /* + * semi-verbose operation mode (no listing) + */ + Vflag = 1; + break; + case 'X': + /* + * GNU tar compat: exclude the files listed in optarg + */ + if (tar_gnutar_X_compat(optarg) != 0) + tar_usage(); + break; + case 'Z': + /* + * use compress. + */ + gzip_program = COMPRESS_CMD; + break; + case '0': + arcname = DEV_0; + break; + case '1': + arcname = DEV_1; + break; + case '4': + arcname = DEV_4; + break; + case '5': + arcname = DEV_5; + break; + case '7': + arcname = DEV_7; + break; + case '8': + arcname = DEV_8; + break; + case OPT_ATIME_PRESERVE: + patime = 1; + break; + case OPT_UNLINK: + /* Just ignore -- we always unlink first. */ + break; + case OPT_USE_COMPRESS_PROGRAM: + gzip_program = optarg; + break; + case OPT_FORCE_LOCAL: + forcelocal = 1; + break; + case OPT_INSECURE: + secure = 0; + break; + case OPT_STRICT: + /* disable gnu extensions */ + is_gnutar = 0; + break; + case OPT_EXCLUDE: + if (tar_gnutar_minus_minus_exclude(optarg) != 0) + tar_usage(); + break; + case OPT_NORECURSE: + dflag = 1; + break; +#if !HAVE_NBTOOL_CONFIG_H + case OPT_CHROOT: + do_chroot = 1; + break; +#endif +#ifndef SMALL + case OPT_TIMESTAMP: + if (set_tstamp(optarg, &tst) == -1) { + tty_warn(1, "Invalid timestamp `%s'", optarg); + tar_usage(); + } + break; +#endif + default: + tar_usage(); + break; + } + } + argc -= optind; + argv += optind; + + /* Tar requires an action. */ + if (act == ERROR) + tar_usage(); + + /* Traditional tar behaviour (pax uses stderr unless in list mode) */ + if (fstdin == 1 && act == ARCHIVE) + listf = stderr; + else + listf = stdout; + + /* Traditional tar behaviour (pax wants to read file list from stdin) */ + if ((act == ARCHIVE || act == APPND) && argc == 0 && nincfiles == 0) + exit(0); + /* + * if we are writing (ARCHIVE) specify tar, otherwise run like pax + * (unless -o specified) + */ + if (act == ARCHIVE || act == APPND) + frmt = &(fsub[Oflag ? F_TAR : F_USTAR]); + else if (Oflag) { + if (act == EXTRACT) + to_stdout = 1; + else { + tty_warn(1, "The -O/-o options are only valid when " + "writing or extracting an archive"); + tar_usage(); + } + } + + /* + * process the args as they are interpreted by the operation mode + */ + switch (act) { + case LIST: + case EXTRACT: + default: + { + int sawpat = 0; + int dirisnext = 0; + char *file, *dir = NULL; + int mustfreedir = 0; + + while (nincfiles || *argv != NULL) { + /* + * If we queued up any include files, + * pull them in now. Otherwise, check + * for -I and -C positional flags. + * Anything else must be a file to + * extract. + */ + if (nincfiles) { + file = incfiles->file; + dir = incfiles->dir; + mustfreedir = 0; + incfiles++; + nincfiles--; + } else if (strcmp(*argv, "-I") == 0) { + if (*++argv == NULL) + break; + file = *argv++; + dir = chdname; + mustfreedir = 0; + } else { + file = NULL; + dir = NULL; + mustfreedir = 0; + } + if (file != NULL) { + FILE *fp; + char *str; + + if (strcmp(file, "-") == 0) + fp = stdin; + else if ((fp = fopen(file, "r")) == NULL) { + tty_warn(1, "Unable to open file '%s' for read", file); + tar_usage(); + } + while ((str = get_line(fp)) != NULL) { + if (dirisnext) { + if (dir && mustfreedir) + free(dir); + dir = str; + mustfreedir = 1; + dirisnext = 0; + continue; + } + if (strcmp(str, "-C") == 0) { + havechd++; + dirisnext = 1; + free(str); + continue; + } + if (strncmp(str, "-C ", 3) == 0) { + havechd++; + if (dir && mustfreedir) + free(dir); + dir = strdup(str + 3); + mustfreedir = 1; + free(str); + continue; + } + if (pat_add(str, dir, NOGLOB_MTCH) < 0) + tar_usage(); + sawpat = 1; + } + /* Bomb if given -C w/out a dir. */ + if (dirisnext) + tar_usage(); + if (dir && mustfreedir) + free(dir); + if (strcmp(file, "-") != 0) + fclose(fp); + if (get_line_error) { + tty_warn(1, "Problem with file '%s'", file); + tar_usage(); + } + } else if (strcmp(*argv, "-C") == 0) { + if (*++argv == NULL) + break; + chdname = *argv++; + havechd++; + } else if (pat_add(*argv++, chdname, 0) < 0) + tar_usage(); + else + sawpat = 1; + } + /* + * if patterns were added, we are doing chdir() + * on a file-by-file basis, else, just one + * global chdir (if any) after opening input. + */ + if (sawpat > 0) + chdname = NULL; + } + break; + case ARCHIVE: + case APPND: + if (chdname != NULL) { /* initial chdir() */ + if (ftree_add(chdname, 1) < 0) + tar_usage(); + } + + while (nincfiles || *argv != NULL) { + char *file, *dir; + + /* + * If we queued up any include files, pull them in + * now. Otherwise, check for -I and -C positional + * flags. Anything else must be a file to include + * in the archive. + */ + if (nincfiles) { + file = incfiles->file; + dir = incfiles->dir; + incfiles++; + nincfiles--; + } else if (strcmp(*argv, "-I") == 0) { + if (*++argv == NULL) + break; + file = *argv++; + dir = NULL; + } else { + file = NULL; + dir = NULL; + } + if (file != NULL) { + FILE *fp; + char *str; + int dirisnext = 0; + + /* Set directory if needed */ + if (dir) { + if (ftree_add(dir, 1) < 0) + tar_usage(); + } + + if (strcmp(file, "-") == 0) + fp = stdin; + else if ((fp = fopen(file, "r")) == NULL) { + tty_warn(1, "Unable to open file '%s' for read", file); + tar_usage(); + } + while ((str = get_line(fp)) != NULL) { + if (dirisnext) { + if (ftree_add(str, 1) < 0) + tar_usage(); + dirisnext = 0; + continue; + } + if (strcmp(str, "-C") == 0) { + dirisnext = 1; + continue; + } + if (strncmp(str, "-C ", 3) == 0) { + if (ftree_add(str + 3, 1) < 0) + tar_usage(); + continue; + } + if (ftree_add(str, 0) < 0) + tar_usage(); + } + /* Bomb if given -C w/out a dir. */ + if (dirisnext) + tar_usage(); + if (strcmp(file, "-") != 0) + fclose(fp); + if (get_line_error) { + tty_warn(1, "Problem with file '%s'", + file); + tar_usage(); + } + } else if (strcmp(*argv, "-C") == 0) { + if (*++argv == NULL) + break; + if (ftree_add(*argv++, 1) < 0) + tar_usage(); + } else if (ftree_add(*argv++, 0) < 0) + tar_usage(); + } + /* + * no read errors allowed on updates/append operation! + */ + maxflt = 0; + break; + } + if (!fstdin && ((arcname == NULL) || (*arcname == '\0'))) { + arcname = getenv("TAPE"); + if ((arcname == NULL) || (*arcname == '\0')) + arcname = _PATH_DEFTAPE; + } +} + +int +mkpath(char *path) +{ + char *slash; + int done = 0; + + slash = path; + + while (!done) { + slash += strspn(slash, "/"); + slash += strcspn(slash, "/"); + + done = (*slash == '\0'); + *slash = '\0'; + + if (domkdir(path, 0777) == -1) + goto out; + + if (!done) + *slash = '/'; + } + + return 0; +out: + /* Can't create or or not a directory */ + syswarn(1, errno, "Cannot create directory `%s'", path); + return -1; +} + + +#ifndef NO_CPIO +struct option cpio_longopts[] = { + { "reset-access-time", no_argument, 0, 'a' }, + { "make-directories", no_argument, 0, 'd' }, + { "nonmatching", no_argument, 0, 'f' }, + { "extract", no_argument, 0, 'i' }, + { "link", no_argument, 0, 'l' }, + { "preserve-modification-time", no_argument, 0, 'm' }, + { "create", no_argument, 0, 'o' }, + { "pass-through", no_argument, 0, 'p' }, + { "rename", no_argument, 0, 'r' }, + { "list", no_argument, 0, 't' }, + { "unconditional", no_argument, 0, 'u' }, + { "verbose", no_argument, 0, 'v' }, + { "append", no_argument, 0, 'A' }, + { "pattern-file", required_argument, 0, 'E' }, + { "file", required_argument, 0, 'F' }, + { "force-local", no_argument, 0, + OPT_FORCE_LOCAL }, + { "format", required_argument, 0, 'H' }, + { "dereference", no_argument, 0, 'L' }, + { "swap-halfwords", no_argument, 0, 'S' }, + { "summary", no_argument, 0, 'V' }, + { "stats", no_argument, 0, 'V' }, + { "insecure", no_argument, 0, + OPT_INSECURE }, + { "sparse", no_argument, 0, + OPT_SPARSE }, + { "xz", no_argument, 0, + OPT_XZ }, + +#ifdef notyet +/* Not implemented */ + { "null", no_argument, 0, '0' }, + { "swap", no_argument, 0, 'b' }, + { "numeric-uid-gid", no_argument, 0, 'n' }, + { "swap-bytes", no_argument, 0, 's' }, + { "message", required_argument, 0, 'M' }, + { "owner", required_argument, 0 'R' }, + { "dot", no_argument, 0, 'V' }, /* xxx */ + { "block-size", required_argument, 0, + OPT_BLOCK_SIZE }, + { "no-absolute-pathnames", no_argument, 0, + OPT_NO_ABSOLUTE_PATHNAMES }, + { "no-preserve-owner", no_argument, 0, + OPT_NO_PRESERVE_OWNER }, + { "only-verify-crc", no_argument, 0, + OPT_ONLY_VERIFY_CRC }, + { "rsh-command", required_argument, 0, + OPT_RSH_COMMAND }, + { "version", no_argument, 0, + OPT_VERSION }, +#endif + { 0, 0, 0, 0 }, +}; + +static void +cpio_set_action(int op) +{ + if ((act == APPND && op == ARCHIVE) || (act == ARCHIVE && op == APPND)) + act = APPND; + else if (act == EXTRACT && op == LIST) + act = op; + else if (act != ERROR && act != op) + cpio_usage(); + else + act = op; +} + +/* + * cpio_options() + * look at the user specified flags. set globals as required and check if + * the user specified a legal set of flags. If not, complain and exit + */ + +static void +cpio_options(int argc, char **argv) +{ + FSUB tmp; + u_int64_t flg = 0; + u_int64_t bflg = 0; + int c; + size_t i; + FILE *fp; + char *str; + + uflag = 1; + kflag = 1; + pids = 1; + pmode = 1; + pmtime = 0; + arcname = NULL; + dflag = 1; + nodirs = 1; + /* + * process option flags + */ + while ((c = getoldopt(argc, argv, + "+abcdfiklmoprstuvzABC:E:F:H:I:LM:O:R:SVZ6", + cpio_longopts, NULL)) != -1) { + switch(c) { + case 'a': + /* + * preserve access time on filesystem nodes we read + */ + tflag = 1; + flg |= TF; + break; +#ifdef notyet + case 'b': + /* + * swap bytes and half-words when reading data + */ + break; +#endif + case 'c': + /* + * ASCII cpio header + */ + frmt = &fsub[F_SV4CPIO]; + break; + case 'd': + /* + * create directories as needed + * pax does this by default .. + */ + nodirs = 0; + break; + case 'f': + /* + * inverse match on patterns + */ + cflag = 1; + flg |= CF; + break; + case 'i': + /* + * read the archive + */ + cpio_set_action(EXTRACT); + flg |= RF; + break; +#ifdef notyet + case 'k': + break; +#endif + case 'l': + /* + * try to link src to dest with copy (-rw) + */ + lflag = 1; + flg |= LF; + break; + case 'm': + /* + * preserve mtime + */ + flg |= PF; + pmtime = 1; + break; + case 'o': + /* + * write an archive + */ + cpio_set_action(ARCHIVE); + frmt = &(fsub[F_SV4CRC]); + flg |= WF; + break; + case 'p': + /* + * cpio -p is like pax -rw + */ + cpio_set_action(COPY); + flg |= RF | WF; + break; + case 'r': + /* + * interactive file rename + */ + iflag = 1; + flg |= IF; + break; +#ifdef notyet + case 's': + /* + * swap bytes after reading data + */ + break; +#endif + case 't': + /* + * list contents of archive + */ + cpio_set_action(LIST); + listf = stdout; + flg &= ~RF; + break; + case 'u': + /* + * don't ignore those older files + */ + uflag = 0; + kflag = 0; + flg |= UF; + break; + case 'v': + /* + * verbose operation mode + */ + vflag = 1; + flg |= VF; + break; + case 'z': + /* + * use gzip. Non standard option. + */ + gzip_program = GZIP_CMD; + break; + case 'A': + /* + * append to an archive + */ + cpio_set_action(APPND); + flg |= AF; + break; + case 'B': + /* + * set blocksize to 5120 + */ + blksz = 5120; + break; + case 'C': + /* + * specify blocksize + */ + if ((blksz = (int)str_offt(optarg)) <= 0) { + tty_warn(1, "Invalid block size %s", optarg); + cpio_usage(); + } + break; + case 'E': + /* + * file with patterns to extract or list + */ + if ((fp = fopen(optarg, "r")) == NULL) { + tty_warn(1, "Unable to open file '%s' for read", + optarg); + cpio_usage(); + } + while ((str = get_line(fp)) != NULL) { + pat_add(str, NULL, 0); + } + fclose(fp); + if (get_line_error) { + tty_warn(1, "Problem with file '%s'", optarg); + cpio_usage(); + } + break; + case 'H': + /* + * specify an archive format on write + */ + tmp.name = optarg; + frmt = (FSUB *)bsearch((void *)&tmp, (void *)fsub, + sizeof(fsub)/sizeof(FSUB), sizeof(FSUB), c_frmt); + if (frmt != NULL) { + flg |= XF; + break; + } + tty_warn(1, "Unknown -H format: %s", optarg); + (void)fputs("cpio: Known -H formats are:", stderr); + for (i = 0; i < (sizeof(fsub)/sizeof(FSUB)); ++i) + (void)fprintf(stderr, " %s", fsub[i].name); + (void)fputs("\n\n", stderr); + cpio_usage(); + break; + case 'F': + case 'I': + case 'O': + /* + * filename where the archive is stored + */ + if ((optarg[0] == '-') && (optarg[1]== '\0')) { + /* + * treat a - as stdin + */ + arcname = NULL; + break; + } + arcname = optarg; + break; + case 'L': + /* + * follow symlinks + */ + Lflag = 1; + flg |= CLF; + break; +#ifdef notyet + case 'M': + arg = optarg; + break; + case 'R': + arg = optarg; + break; +#endif + case 'S': + /* + * swap halfwords after reading data + */ + cpio_swp_head = 1; + break; +#ifdef notyet + case 'V': /* print a '.' for each file processed */ + break; +#endif + case 'V': + /* + * semi-verbose operation mode (no listing) + */ + Vflag = 1; + flg |= VF; + break; + case 'Z': + /* + * use compress. Non standard option. + */ + gzip_program = COMPRESS_CMD; + break; + case '6': + /* + * process Version 6 cpio format + */ + frmt = &(fsub[F_BCPIO]); + break; + case OPT_FORCE_LOCAL: + forcelocal = 1; + break; + case OPT_INSECURE: + secure = 0; + break; + case OPT_SPARSE: + /* do nothing; we already generate sparse files */ + break; + case OPT_XZ: + gzip_program = XZ_CMD; + break; + default: + cpio_usage(); + break; + } + } + + /* + * figure out the operation mode of cpio. check that we have not been + * given a bogus set of flags for the operation mode. + */ + if (ISLIST(flg)) { + act = LIST; + bflg = flg & BDLIST; + } else if (ISEXTRACT(flg)) { + act = EXTRACT; + bflg = flg & BDEXTR; + } else if (ISARCHIVE(flg)) { + act = ARCHIVE; + bflg = flg & BDARCH; + } else if (ISAPPND(flg)) { + act = APPND; + bflg = flg & BDARCH; + } else if (ISCOPY(flg)) { + act = COPY; + bflg = flg & BDCOPY; + } else + cpio_usage(); + if (bflg) { + cpio_usage(); + } + + /* + * if we are writing (ARCHIVE) we use the default format if the user + * did not specify a format. when we write during an APPEND, we will + * adopt the format of the existing archive if none was supplied. + */ + if (!(flg & XF) && (act == ARCHIVE)) + frmt = &(fsub[F_BCPIO]); + + /* + * process the args as they are interpreted by the operation mode + */ + switch (act) { + case LIST: + case EXTRACT: + for (; optind < argc; optind++) + if (pat_add(argv[optind], NULL, 0) < 0) + cpio_usage(); + break; + case COPY: + if (optind >= argc) { + tty_warn(0, "Destination directory was not supplied"); + cpio_usage(); + } + --argc; + dirptr = argv[argc]; + /* FALLTHROUGH */ + case ARCHIVE: + case APPND: + if (argc != optind) { + for (; optind < argc; optind++) + if (ftree_add(argv[optind], 0) < 0) + cpio_usage(); + break; + } + /* + * no read errors allowed on updates/append operation! + */ + maxflt = 0; + while ((str = get_line(stdin)) != NULL) { + ftree_add(str, 0); + } + if (get_line_error) { + tty_warn(1, "Problem while reading stdin"); + cpio_usage(); + } + break; + default: + cpio_usage(); + break; + } +} +#endif + +/* + * printflg() + * print out those invalid flag sets found to the user + */ + +static void +printflg(unsigned int flg) +{ + int nxt; + + (void)fprintf(stderr,"%s: Invalid combination of options:", argv0); + while ((nxt = ffs(flg)) != 0) { + flg &= ~(1 << (nxt - 1)); + (void)fprintf(stderr, " -%c", flgch[nxt - 1]); + } + (void)putc('\n', stderr); +} + +/* + * c_frmt() + * comparison routine used by bsearch to find the format specified + * by the user + */ + +static int +c_frmt(const void *a, const void *b) +{ + return strcmp(((const FSUB *)a)->name, ((const FSUB *)b)->name); +} + +/* + * opt_next() + * called by format specific options routines to get each format specific + * flag and value specified with -o + * Return: + * pointer to next OPLIST entry or NULL (end of list). + */ + +OPLIST * +opt_next(void) +{ + OPLIST *opt; + + if ((opt = ophead) != NULL) + ophead = ophead->fow; + return opt; +} + +/* + * bad_opt() + * generic routine used to complain about a format specific options + * when the format does not support options. + */ + +int +bad_opt(void) +{ + OPLIST *opt; + + if (ophead == NULL) + return 0; + /* + * print all we were given + */ + tty_warn(1," These format options are not supported for %s", + frmt->name); + while ((opt = opt_next()) != NULL) + (void)fprintf(stderr, "\t%s = %s\n", opt->name, opt->value); + if (strcmp(NM_TAR, argv0) == 0) + tar_usage(); +#ifndef NO_CPIO + else if (strcmp(NM_CPIO, argv0) == 0) + cpio_usage(); +#endif + else + pax_usage(); + return 0; +} + +/* + * opt_add() + * breaks the value supplied to -o into a option name and value. options + * are given to -o in the form -o name-value,name=value + * multiple -o may be specified. + * Return: + * 0 if format in name=value format, -1 if -o is passed junk + */ + +int +opt_add(const char *str) +{ + OPLIST *opt; + char *frpt; + char *pt; + char *endpt; + char *dstr; + + if ((str == NULL) || (*str == '\0')) { + tty_warn(0, "Invalid option name"); + return -1; + } + if ((dstr = strdup(str)) == NULL) { + tty_warn(0, "Unable to allocate space for option list"); + return -1; + } + frpt = endpt = dstr; + + /* + * break into name and values pieces and stuff each one into a + * OPLIST structure. When we know the format, the format specific + * option function will go through this list + */ + while ((frpt != NULL) && (*frpt != '\0')) { + if ((endpt = strchr(frpt, ',')) != NULL) + *endpt = '\0'; + if ((pt = strchr(frpt, '=')) == NULL) { + tty_warn(0, "Invalid options format"); + free(dstr); + return -1; + } + if ((opt = (OPLIST *)malloc(sizeof(OPLIST))) == NULL) { + tty_warn(0, "Unable to allocate space for option list"); + free(dstr); + return -1; + } + *pt++ = '\0'; + opt->name = frpt; + opt->value = pt; + opt->fow = NULL; + if (endpt != NULL) + frpt = endpt + 1; + else + frpt = NULL; + if (ophead == NULL) { + optail = ophead = opt; + continue; + } + optail->fow = opt; + optail = opt; + } + return 0; +} + +/* + * str_offt() + * Convert an expression of the following forms to an off_t > 0. + * 1) A positive decimal number. + * 2) A positive decimal number followed by a b (mult by 512). + * 3) A positive decimal number followed by a k (mult by 1024). + * 4) A positive decimal number followed by a m (mult by 512). + * 5) A positive decimal number followed by a w (mult by sizeof int) + * 6) Two or more positive decimal numbers (with/without k,b or w). + * separated by x (also * for backwards compatibility), specifying + * the product of the indicated values. + * Return: + * 0 for an error, a positive value o.w. + */ + +static off_t +str_offt(char *val) +{ + char *expr; + off_t num, t; + + num = STRTOOFFT(val, &expr, 0); + if ((num == OFFT_MAX) || (num <= 0) || (expr == val)) + return 0; + + switch(*expr) { + case 'b': + t = num; + num *= 512; + if (t > num) + return 0; + ++expr; + break; + case 'k': + t = num; + num *= 1024; + if (t > num) + return 0; + ++expr; + break; + case 'm': + t = num; + num *= 1048576; + if (t > num) + return 0; + ++expr; + break; + case 'w': + t = num; + num *= sizeof(int); + if (t > num) + return 0; + ++expr; + break; + } + + switch(*expr) { + case '\0': + break; + case '*': + case 'x': + t = num; + num *= str_offt(expr + 1); + if (t > num) + return 0; + break; + default: + return 0; + } + return num; +} + +static char * +get_line(FILE *f) +{ + char *name, *temp; + size_t len; + + name = fgetln(f, &len); + if (!name) { + get_line_error = ferror(f) ? GETLINE_FILE_CORRUPT : 0; + return 0; + } + if (name[len-1] != '\n') + len++; + temp = malloc(len); + if (!temp) { + get_line_error = GETLINE_OUT_OF_MEM; + return 0; + } + memcpy(temp, name, len-1); + temp[len-1] = 0; + return temp; +} + +#ifndef SMALL +/* + * set_tstamp() + * Use a specific timestamp for all individual files created in the + * archive + */ +static int +set_tstamp(const char *b, struct stat *st) +{ + time_t when; + char *eb; + long long l; + + if (stat(b, st) != -1) + return 0; + +#ifndef HAVE_NBTOOL_CONFIG_H + errno = 0; + if ((when = parsedate(b, NULL, NULL)) == -1 && errno != 0) +#endif + { + errno = 0; + l = strtoll(b, &eb, 0); + if (b == eb || *eb || errno) + return -1; + when = (time_t)l; + } + + st->st_ino = 1; +#if HAVE_STRUCT_STAT_BIRTHTIME + st->st_birthtime = +#endif + st->st_mtime = st->st_ctime = st->st_atime = when; + return 0; +} +#endif + +/* + * no_op() + * for those option functions where the archive format has nothing to do. + * Return: + * 0 + */ + +static int +no_op(void) +{ + return 0; +} + +/* + * pax_usage() + * print the usage summary to the user + */ + +static void +pax_usage(void) +{ + fprintf(stderr, +"usage: pax [-0cdjnvzVO] [-E limit] [-f archive] [-N dbdir] [-s replstr] ...\n" +" [-U user] ... [-G group] ... [-T [from_date][,to_date]] ...\n" +" [pattern ...]\n"); + fprintf(stderr, +" pax -r [-cdijknuvzADOVYZ] [-E limit] [-f archive] [-N dbdir]\n" +" [-o options] ... [-p string] ... [-s replstr] ... [-U user] ...\n" +" [-G group] ... [-T [from_date][,to_date]] ... [pattern ...]\n"); + fprintf(stderr, +" pax -w [-dijtuvzAHLMOPVX] [-b blocksize] [[-a] [-f archive]] [-x format]\n" +" [-B bytes] [-N dbdir] [-o options] ... [-s replstr] ...\n" +" [-U user] ... [-G group] ...\n" +" [-T [from_date][,to_date][/[c][m]]] ... [file ...]\n"); + fprintf(stderr, +" pax -r -w [-dijklntuvzADHLMOPVXYZ] [-N dbdir] [-p string] ...\n" +" [-s replstr] ... [-U user] ... [-G group] ...\n" +" [-T [from_date][,to_date][/[c][m]]] ... [file ...] directory\n"); + exit(1); + /* NOTREACHED */ +} + +/* + * tar_usage() + * print the usage summary to the user + */ + +static void +tar_usage(void) +{ + (void)fputs("usage: tar [-]{crtux}[-befhjklmopqvwzHJOPSXZ014578] " + "[archive] [blocksize]\n" + " [-C directory] [-T file] [-s replstr] " + "[file ...]\n", stderr); + exit(1); + /* NOTREACHED */ +} + +#ifndef NO_CPIO +/* + * cpio_usage() + * print the usage summary to the user + */ + +static void +cpio_usage(void) +{ + + (void)fputs("usage: cpio -o [-aABcLvzZ] [-C bytes] [-F archive] " + "[-H format] [-O archive]\n" + " < name-list [> archive]\n" + " cpio -i [-bBcdfmrsStuvzZ6] [-C bytes] [-E file] " + "[-F archive] [-H format] \n" + " [-I archive] " + "[pattern ...] [< archive]\n" + " cpio -p [-adlLmuv] destination-directory " + "< name-list\n", stderr); + exit(1); + /* NOTREACHED */ +} +#endif diff --git a/bin/pax/options.h b/bin/pax/options.h new file mode 100644 index 0000000..6517174 --- /dev/null +++ b/bin/pax/options.h @@ -0,0 +1,116 @@ +/* $NetBSD: options.h,v 1.11 2007/04/23 18:40:22 christos Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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) 4/18/94 + */ + +/* + * argv[0] names. Used for tar and cpio emulation + */ + +#define NM_TAR "tar" +#define NM_CPIO "cpio" +#define NM_PAX "pax" + +/* special value for -E */ +#define none "none" + +/* + * Constants used to specify the legal sets of flags in pax. For each major + * operation mode of pax, a set of illegal flags is defined. If any one of + * those illegal flags are found set, we scream and exit + */ + +/* + * flags (one for each option). + */ +#define AF 0x000000001ULL +#define BF 0x000000002ULL +#define CF 0x000000004ULL +#define DF 0x000000008ULL +#define FF 0x000000010ULL +#define IF 0x000000020ULL +#define KF 0x000000040ULL +#define LF 0x000000080ULL +#define NF 0x000000100ULL +#define OF 0x000000200ULL +#define PF 0x000000400ULL +#define RF 0x000000800ULL +#define SF 0x000001000ULL +#define TF 0x000002000ULL +#define UF 0x000004000ULL +#define VF 0x000008000ULL +#define WF 0x000010000ULL +#define XF 0x000020000ULL +#define CAF 0x000040000ULL /* nonstandard extension */ +#define CBF 0x000080000ULL /* nonstandard extension */ +#define CDF 0x000100000ULL /* nonstandard extension */ +#define CEF 0x000200000ULL /* nonstandard extension */ +#define CGF 0x000400000ULL /* nonstandard extension */ +#define CHF 0x000800000ULL /* nonstandard extension */ +#define CLF 0x001000000ULL /* nonstandard extension */ +#define CMF 0x002000000ULL /* nonstandard extension */ +#define CPF 0x004000000ULL /* nonstandard extension */ +#define CTF 0x008000000ULL /* nonstandard extension */ +#define CUF 0x010000000ULL /* nonstandard extension */ +#define VSF 0x020000000ULL /* non-standard */ +#define CXF 0x040000000ULL +#define CYF 0x080000000ULL /* nonstandard extension */ +#define CZF 0x100000000ULL /* nonstandard extension */ + +/* + * ascii string indexed by bit position above (alter the above and you must + * alter this string) used to tell the user what flags caused us to complain + */ +#define FLGCH "abcdfiklnoprstuvwxABDEGHLMPTUVXYZ" + +/* + * legal pax operation bit patterns + */ + +#define ISLIST(x) (((x) & (RF|WF)) == 0) +#define ISEXTRACT(x) (((x) & (RF|WF)) == RF) +#define ISARCHIVE(x) (((x) & (AF|RF|WF)) == WF) +#define ISAPPND(x) (((x) & (AF|RF|WF)) == (AF|WF)) +#define ISCOPY(x) (((x) & (RF|WF)) == (RF|WF)) +#define ISWRITE(x) (((x) & (RF|WF)) == WF) + +/* + * Illegal option flag subsets based on pax operation + */ + +#define BDEXTR (AF|BF|LF|TF|WF|XF|CBF|CHF|CLF|CMF|CPF|CXF) +#define BDARCH (CF|KF|LF|NF|PF|RF|CDF|CEF|CYF|CZF) +#define BDCOPY (AF|BF|FF|OF|XF|CAF|CBF|CEF) +#define BDLIST (AF|BF|IF|KF|LF|OF|PF|RF|TF|UF|WF|XF|CBF|CDF|CHF|CLF|CMF|CPF|CXF|CYF|CZF) diff --git a/bin/pax/pat_rep.c b/bin/pax/pat_rep.c new file mode 100644 index 0000000..7dbcda7 --- /dev/null +++ b/bin/pax/pat_rep.c @@ -0,0 +1,1139 @@ +/* $NetBSD: pat_rep.c,v 1.30 2018/06/13 15:14:40 christos Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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 HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +#if 0 +static char sccsid[] = "@(#)pat_rep.c 8.2 (Berkeley) 4/18/94"; +#else +__RCSID("$NetBSD: pat_rep.c,v 1.30 2018/06/13 15:14:40 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include "pax.h" +#include "pat_rep.h" +#include "extern.h" + +/* + * routines to handle pattern matching, name modification (regular expression + * substitution and interactive renames), and destination name modification for + * copy (-rw). Both file name and link names are adjusted as required in these + * routines. + */ + +#define MAXSUBEXP 10 /* max subexpressions, DO NOT CHANGE */ +static PATTERN *pathead = NULL; /* file pattern match list head */ +static PATTERN *pattail = NULL; /* file pattern match list tail */ +static REPLACE *rephead = NULL; /* replacement string list head */ +static REPLACE *reptail = NULL; /* replacement string list tail */ + +static int rep_name(char *, size_t, int *, int); +static int tty_rename(ARCHD *); +static int fix_path(char *, int *, char *, int); +static int fn_match(char *, char *, char **, int); +static char * range_match(char *, int); +static int checkdotdot(const char *); +static int resub(regex_t *, regmatch_t *, char *, char *, char *, char *); + +/* + * rep_add() + * parses the -s replacement string; compiles the regular expression + * and stores the compiled value and its replacement string together in + * replacement string list. Input to this function is of the form: + * /old/new/pg + * The first char in the string specifies the delimiter used by this + * replacement string. "Old" is a regular expression in "ed" format which + * is compiled by regcomp() and is applied to filenames. "new" is the + * substitution string; p and g are options flags for printing and global + * replacement (over the single filename) + * Return: + * 0 if a proper replacement string and regular expression was added to + * the list of replacement patterns; -1 otherwise. + */ + +int +rep_add(char *str) +{ + char *pt1; + char *pt2; + REPLACE *rep; + int res; + char rebuf[BUFSIZ]; + + /* + * throw out the bad parameters + */ + if ((str == NULL) || (*str == '\0')) { + tty_warn(1, "Empty replacement string"); + return -1; + } + + /* + * first character in the string specifies what the delimiter is for + * this expression. + */ + for (pt1 = str+1; *pt1; pt1++) { + if (*pt1 == '\\') { + pt1++; + continue; + } + if (*pt1 == *str) + break; + } + if (*pt1 == 0) { + tty_warn(1, "Invalid replacement string %s", str); + return -1; + } + + /* + * allocate space for the node that handles this replacement pattern + * and split out the regular expression and try to compile it + */ + if ((rep = (REPLACE *)malloc(sizeof(REPLACE))) == NULL) { + tty_warn(1, "Unable to allocate memory for replacement string"); + return -1; + } + + *pt1 = '\0'; + if ((res = regcomp(&(rep->rcmp), str+1, 0)) != 0) { + regerror(res, &(rep->rcmp), rebuf, sizeof(rebuf)); + tty_warn(1, "%s while compiling regular expression %s", rebuf, + str); + (void)free((char *)rep); + return -1; + } + + /* + * put the delimiter back in case we need an error message and + * locate the delimiter at the end of the replacement string + * we then point the node at the new substitution string + */ + *pt1++ = *str; + for (pt2 = pt1; *pt2; pt2++) { + if (*pt2 == '\\') { + pt2++; + continue; + } + if (*pt2 == *str) + break; + } + if (*pt2 == 0) { + regfree(&(rep->rcmp)); + (void)free((char *)rep); + tty_warn(1, "Invalid replacement string %s", str); + return -1; + } + + *pt2 = '\0'; + + /* Make sure to dup replacement, who knows where it came from! */ + if ((rep->nstr = strdup(pt1)) == NULL) { + regfree(&(rep->rcmp)); + (void)free((char *)rep); + tty_warn(1, "Unable to allocate memory for replacement string"); + return -1; + } + + pt1 = pt2++; + rep->flgs = 0; + + /* + * set the options if any + */ + while (*pt2 != '\0') { + switch(*pt2) { + case 'g': + case 'G': + rep->flgs |= GLOB; + break; + case 'p': + case 'P': + rep->flgs |= PRNT; + break; + case 's': + case 'S': + rep->flgs |= SYML; + break; + default: + regfree(&(rep->rcmp)); + (void)free((char *)rep); + *pt1 = *str; + tty_warn(1, "Invalid replacement string option %s", + str); + return -1; + } + ++pt2; + } + + /* + * all done, link it in at the end + */ + rep->fow = NULL; + if (rephead == NULL) { + reptail = rephead = rep; + return 0; + } + reptail->fow = rep; + reptail = rep; + return 0; +} + +/* + * pat_add() + * add a pattern match to the pattern match list. Pattern matches are used + * to select which archive members are extracted. (They appear as + * arguments to pax in the list and read modes). If no patterns are + * supplied to pax, all members in the archive will be selected (and the + * pattern match list is empty). + * + * Return: + * 0 if the pattern was added to the list, -1 otherwise + */ + +int +pat_add(char *str, char *chdn, int flags) +{ + PATTERN *pt; + + /* + * throw out the junk + */ + if ((str == NULL) || (*str == '\0')) { + tty_warn(1, "Empty pattern string"); + return -1; + } + + /* + * allocate space for the pattern and store the pattern. the pattern is + * part of argv so do not bother to copy it, just point at it. Add the + * node to the end of the pattern list + */ + if ((pt = (PATTERN *)malloc(sizeof(PATTERN))) == NULL) { + tty_warn(1, "Unable to allocate memory for pattern string"); + return -1; + } + + pt->pstr = str; + pt->pend = NULL; + pt->plen = strlen(str); + pt->fow = NULL; + pt->flgs = flags; + pt->chdname = chdn; + if (pathead == NULL) { + pattail = pathead = pt; + return 0; + } + pattail->fow = pt; + pattail = pt; + return 0; +} + +/* + * pat_chk() + * complain if any the user supplied pattern did not result in a match to + * a selected archive member. + */ + +void +pat_chk(void) +{ + PATTERN *pt; + int wban = 0; + + /* + * walk down the list checking the flags to make sure MTCH was set, + * if not complain + */ + for (pt = pathead; pt != NULL; pt = pt->fow) { + if (pt->flgs & MTCH) + continue; + if (!wban) { + tty_warn(1, "WARNING! These patterns were not matched:"); + ++wban; + } + (void)fprintf(stderr, "%s\n", pt->pstr); + } +} + +/* + * pat_sel() + * the archive member which matches a pattern was selected. Mark the + * pattern as having selected an archive member. arcn->pat points at the + * pattern that was matched. arcn->pat is set in pat_match() + * + * NOTE: When the -c option is used, we are called when there was no match + * by pat_match() (that means we did match before the inverted sense of + * the logic). Now this seems really strange at first, but with -c we + * need to keep track of those patterns that cause an archive member to + * NOT be selected (it found an archive member with a specified pattern) + * Return: + * 0 if the pattern pointed at by arcn->pat was tagged as creating a + * match, -1 otherwise. + */ + +int +pat_sel(ARCHD *arcn) +{ + PATTERN *pt; + PATTERN **ppt; + int len; + + /* + * if no patterns just return + */ + if ((pathead == NULL) || ((pt = arcn->pat) == NULL)) + return 0; + + /* + * when we are NOT limited to a single match per pattern mark the + * pattern and return + */ + if (!nflag) { + pt->flgs |= MTCH; + return 0; + } + + /* + * we reach this point only when we allow a single selected match per + * pattern, if the pattern matches a directory and we do not have -d + * (dflag) we are done with this pattern. We may also be handed a file + * in the subtree of a directory. in that case when we are operating + * with -d, this pattern was already selected and we are done + */ + if (pt->flgs & DIR_MTCH) + return 0; + + if (!dflag && ((pt->pend != NULL) || (arcn->type == PAX_DIR))) { + /* + * ok we matched a directory and we are allowing + * subtree matches but because of the -n only its children will + * match. This is tagged as a DIR_MTCH type. + * WATCH IT, the code assumes that pt->pend points + * into arcn->name and arcn->name has not been modified. + * If not we will have a big mess. Yup this is another kludge + */ + + /* + * if this was a prefix match, remove trailing part of path + * so we can copy it. Future matches will be exact prefix match + */ + if (pt->pend != NULL) + *pt->pend = '\0'; + + if ((pt->pstr = strdup(arcn->name)) == NULL) { + tty_warn(1, "Pattern select out of memory"); + if (pt->pend != NULL) + *pt->pend = '/'; + pt->pend = NULL; + return -1; + } + + /* + * put the trailing / back in the source string + */ + if (pt->pend != NULL) { + *pt->pend = '/'; + pt->pend = NULL; + } + pt->plen = strlen(pt->pstr); + + /* + * strip off any trailing /, this should really never happen + */ + len = pt->plen - 1; + if (*(pt->pstr + len) == '/') { + *(pt->pstr + len) = '\0'; + pt->plen = len; + } + pt->flgs = DIR_MTCH | MTCH; + arcn->pat = pt; + return 0; + } + + /* + * we are then done with this pattern, so we delete it from the list + * because it can never be used for another match. + * Seems kind of strange to do for a -c, but the pax spec is really + * vague on the interaction of -c, -n, and -d. We assume that when -c + * and the pattern rejects a member (i.e. it matched it) it is done. + * In effect we place the order of the flags as having -c last. + */ + pt = pathead; + ppt = &pathead; + while ((pt != NULL) && (pt != arcn->pat)) { + ppt = &(pt->fow); + pt = pt->fow; + } + + if (pt == NULL) { + /* + * should never happen.... + */ + tty_warn(1, "Pattern list inconsistent"); + return -1; + } + *ppt = pt->fow; + (void)free((char *)pt); + arcn->pat = NULL; + return 0; +} + +/* + * pat_match() + * see if this archive member matches any supplied pattern, if a match + * is found, arcn->pat is set to point at the potential pattern. Later if + * this archive member is "selected" we process and mark the pattern as + * one which matched a selected archive member (see pat_sel()) + * Return: + * 0 if this archive member should be processed, 1 if it should be + * skipped and -1 if we are done with all patterns (and pax should quit + * looking for more members) + */ + +int +pat_match(ARCHD *arcn) +{ + PATTERN *pt; + + arcn->pat = NULL; + + /* + * if there are no more patterns and we have -n (and not -c) we are + * done. otherwise with no patterns to match, matches all + */ + if (pathead == NULL) { + if (nflag && !cflag) + return -1; + return 0; + } + + /* + * have to search down the list one at a time looking for a match. + */ + pt = pathead; + while (pt != NULL) { + /* + * check for a file name match unless we have DIR_MTCH set in + * this pattern then we want a prefix match + */ + if (pt->flgs & DIR_MTCH) { + /* + * this pattern was matched before to a directory + * as we must have -n set for this (but not -d). We can + * only match CHILDREN of that directory so we must use + * an exact prefix match (no wildcards). + */ + if ((arcn->name[pt->plen] == '/') && + (strncmp(pt->pstr, arcn->name, pt->plen) == 0)) + break; + } else if (fn_match(pt->pstr, arcn->name, &pt->pend, + pt->flgs & NOGLOB_MTCH) == 0) + break; + pt = pt->fow; + } + + /* + * return the result, remember that cflag (-c) inverts the sense of a + * match + */ + if (pt == NULL) + return cflag ? 0 : 1; + + /* + * we had a match, now when we invert the sense (-c) we reject this + * member. However we have to tag the pattern a being successful, (in a + * match, not in selecting an archive member) so we call pat_sel() + * here. + */ + arcn->pat = pt; + if (!cflag) + return 0; + + if (pat_sel(arcn) < 0) + return -1; + arcn->pat = NULL; + return 1; +} + +/* + * fn_match() + * Return: + * 0 if this archive member should be processed, 1 if it should be + * skipped and -1 if we are done with all patterns (and pax should quit + * looking for more members) + * Note: *pend may be changed to show where the prefix ends. + */ + +static int +fn_match(char *pattern, char *string, char **pend, int noglob) +{ + char c; + char test; + + *pend = NULL; + for (;;) { + switch (c = *pattern++) { + case '\0': + /* + * Ok we found an exact match + */ + if (*string == '\0') + return 0; + + /* + * Check if it is a prefix match + */ + if ((dflag == 1) || (*string != '/')) + return -1; + + /* + * It is a prefix match, remember where the trailing + * / is located + */ + *pend = string; + return 0; + case '?': + if (noglob) + goto regular; + if ((test = *string++) == '\0') + return (-1); + break; + case '*': + if (noglob) + goto regular; + c = *pattern; + /* + * Collapse multiple *'s. + */ + while (c == '*') + c = *++pattern; + + /* + * Optimized hack for pattern with a * at the end + */ + if (c == '\0') + return (0); + + /* + * General case, use recursion. + */ + while ((test = *string) != '\0') { + if (!fn_match(pattern, string, pend, noglob)) + return (0); + ++string; + } + return (-1); + case '[': + if (noglob) + goto regular; + /* + * range match + */ + if (((test = *string++) == '\0') || + ((pattern = range_match(pattern, test)) == NULL)) + return (-1); + break; + case '\\': + default: + regular: + if (c != *string++) + return (-1); + break; + } + } + /* NOTREACHED */ +} + +static char * +range_match(char *pattern, int test) +{ + char c; + char c2; + int negate; + int ok = 0; + + if ((negate = (*pattern == '!')) != 0) + ++pattern; + + while ((c = *pattern++) != ']') { + /* + * Illegal pattern + */ + if (c == '\0') + return (NULL); + + if ((*pattern == '-') && ((c2 = pattern[1]) != '\0') && + (c2 != ']')) { + if ((c <= test) && (test <= c2)) + ok = 1; + pattern += 2; + } else if (c == test) + ok = 1; + } + return (ok == negate ? NULL : pattern); +} + +/* + * mod_name() + * modify a selected file name. first attempt to apply replacement string + * expressions, then apply interactive file rename. We apply replacement + * string expressions to both filenames and file links (if we didn't the + * links would point to the wrong place, and we could never be able to + * move an archive that has a file link in it). When we rename files + * interactively, we store that mapping (old name to user input name) so + * if we spot any file links to the old file name in the future, we will + * know exactly how to fix the file link. + * Return: + * 0 continue to process file, 1 skip this file, -1 pax is finished + */ + +int +mod_name(ARCHD *arcn, int flags) +{ + int res = 0; + + if (secure) { + if (checkdotdot(arcn->name)) { + tty_warn(0, "Ignoring file containing `..' (%s)", + arcn->name); + return 1; + } +#ifdef notdef + if (checkdotdot(arcn->ln_name)) { + tty_warn(0, "Ignoring link containing `..' (%s)", + arcn->ln_name); + return 1; + } +#endif + } + + /* + * IMPORTANT: We have a problem. what do we do with symlinks? + * Modifying a hard link name makes sense, as we know the file it + * points at should have been seen already in the archive (and if it + * wasn't seen because of a read error or a bad archive, we lose + * anyway). But there are no such requirements for symlinks. On one + * hand the symlink that refers to a file in the archive will have to + * be modified to so it will still work at its new location in the + * file system. On the other hand a symlink that points elsewhere (and + * should continue to do so) should not be modified. There is clearly + * no perfect solution here. So we handle them like hardlinks. Clearly + * a replacement made by the interactive rename mapping is very likely + * to be correct since it applies to a single file and is an exact + * match. The regular expression replacements are a little harder to + * justify though. We claim that the symlink name is only likely + * to be replaced when it points within the file tree being moved and + * in that case it should be modified. what we really need to do is to + * call an oracle here. :) + */ + if (rephead != NULL) { + flags |= (flags & RENM) ? PRNT : 0; + /* + * we have replacement strings, modify the name and the link + * name if any. + */ + if ((res = rep_name(arcn->name, sizeof(arcn->name), + &(arcn->nlen), flags)) != 0) + return res; + + if (((arcn->type == PAX_SLK) || (arcn->type == PAX_HLK) || + (arcn->type == PAX_HRG)) && + ((res = rep_name(arcn->ln_name, + sizeof(arcn->ln_name), &(arcn->ln_nlen), + flags | (arcn->type == PAX_SLK ? SYML : 0))) != 0)) + return res; + } + + if (iflag) { + /* + * perform interactive file rename, then map the link if any + */ + if ((res = tty_rename(arcn)) != 0) + return res; + if ((arcn->type == PAX_SLK) || (arcn->type == PAX_HLK) || + (arcn->type == PAX_HRG)) + sub_name(arcn->ln_name, &(arcn->ln_nlen), sizeof(arcn->ln_name)); + } + + /* + * Strip off leading '/' if appropriate. + * Currently, this option is only set for the tar format. + */ + if (rmleadslash && arcn->name[0] == '/') { + if (arcn->name[1] == '\0') { + arcn->name[0] = '.'; + } else { + (void)memmove(arcn->name, &arcn->name[1], + strlen(arcn->name)); + arcn->nlen--; + } + if (rmleadslash < 2) { + rmleadslash = 2; + tty_warn(0, "Removing leading / from absolute path names in the archive"); + } + } + if (rmleadslash && arcn->ln_name[0] == '/' && + (arcn->type == PAX_HLK || arcn->type == PAX_HRG)) { + if (arcn->ln_name[1] == '\0') { + arcn->ln_name[0] = '.'; + } else { + (void)memmove(arcn->ln_name, &arcn->ln_name[1], + strlen(arcn->ln_name)); + arcn->ln_nlen--; + } + if (rmleadslash < 2) { + rmleadslash = 2; + tty_warn(0, "Removing leading / from absolute path names in the archive"); + } + } + + return res; +} + +/* + * tty_rename() + * Prompt the user for a replacement file name. A "." keeps the old name, + * a empty line skips the file, and an EOF on reading the tty, will cause + * pax to stop processing and exit. Otherwise the file name input, replaces + * the old one. + * Return: + * 0 process this file, 1 skip this file, -1 we need to exit pax + */ + +static int +tty_rename(ARCHD *arcn) +{ + char tmpname[PAXPATHLEN+2]; + int res; + + /* + * prompt user for the replacement name for a file, keep trying until + * we get some reasonable input. Archives may have more than one file + * on them with the same name (from updates etc). We print verbose info + * on the file so the user knows what is up. + */ + tty_prnt("\nATTENTION: %s interactive file rename operation.\n", argv0); + + for (;;) { + ls_tty(arcn); + tty_prnt("Input new name, or a \".\" to keep the old name, "); + tty_prnt("or a \"return\" to skip this file.\n"); + tty_prnt("Input > "); + if (tty_read(tmpname, sizeof(tmpname)) < 0) + return -1; + if (strcmp(tmpname, "..") == 0) { + tty_prnt("Try again, illegal file name: ..\n"); + continue; + } + if (strlen(tmpname) > PAXPATHLEN) { + tty_prnt("Try again, file name too long\n"); + continue; + } + break; + } + + /* + * empty file name, skips this file. a "." leaves it alone + */ + if (tmpname[0] == '\0') { + tty_prnt("Skipping file.\n"); + return 1; + } + if ((tmpname[0] == '.') && (tmpname[1] == '\0')) { + tty_prnt("Processing continues, name unchanged.\n"); + return 0; + } + + /* + * ok the name changed. We may run into links that point at this + * file later. we have to remember where the user sent the file + * in order to repair any links. + */ + tty_prnt("Processing continues, name changed to: %s\n", tmpname); + res = add_name(arcn->name, arcn->nlen, tmpname); + arcn->nlen = strlcpy(arcn->name, tmpname, sizeof(arcn->name)); + if (res < 0) + return -1; + return 0; +} + +/* + * set_dest() + * fix up the file name and the link name (if any) so this file will land + * in the destination directory (used during copy() -rw). + * Return: + * 0 if ok, -1 if failure (name too long) + */ + +int +set_dest(ARCHD *arcn, char *dest_dir, int dir_len) +{ + if (fix_path(arcn->name, &(arcn->nlen), dest_dir, dir_len) < 0) + return -1; + + /* + * It is really hard to deal with symlinks here, we cannot be sure + * if the name they point was moved (or will be moved). It is best to + * leave them alone. + */ + if ((arcn->type != PAX_HLK) && (arcn->type != PAX_HRG)) + return 0; + + if (fix_path(arcn->ln_name, &(arcn->ln_nlen), dest_dir, dir_len) < 0) + return -1; + return 0; +} + +/* + * fix_path + * concatenate dir_name and or_name and store the result in or_name (if + * it fits). This is one ugly function. + * Return: + * 0 if ok, -1 if the final name is too long + */ + +static int +fix_path( char *or_name, int *or_len, char *dir_name, int dir_len) +{ + char *src; + char *dest; + char *start; + int len; + + /* + * we shift the or_name to the right enough to tack in the dir_name + * at the front. We make sure we have enough space for it all before + * we start. since dest always ends in a slash, we skip of or_name + * if it also starts with one. + */ + start = or_name; + src = start + *or_len; + dest = src + dir_len; + if (*start == '/') { + ++start; + --dest; + } + if ((len = dest - or_name) > PAXPATHLEN) { + tty_warn(1, "File name %s/%s, too long", dir_name, start); + return -1; + } + *or_len = len; + + /* + * enough space, shift + */ + while (src >= start) + *dest-- = *src--; + src = dir_name + dir_len - 1; + + /* + * splice in the destination directory name + */ + while (src >= dir_name) + *dest-- = *src--; + + *(or_name + len) = '\0'; + return 0; +} + +/* + * rep_name() + * walk down the list of replacement strings applying each one in order. + * when we find one with a successful substitution, we modify the name + * as specified. if required, we print the results. if the resulting name + * is empty, we will skip this archive member. We use the regexp(3) + * routines (regexp() ought to win a prize as having the most cryptic + * library function manual page). + * --Parameters-- + * name is the file name we are going to apply the regular expressions to + * (and may be modified) + * namelen the size of the name buffer. + * nlen is the length of this name (and is modified to hold the length of + * the final string). + * prnt is a flag that says whether to print the final result. + * Return: + * 0 if substitution was successful, 1 if we are to skip the file (the name + * ended up empty) + */ + +static int +rep_name(char *name, size_t namelen, int *nlen, int flags) +{ + REPLACE *pt; + char *inpt; + char *outpt; + char *endpt; + char *rpt; + int found = 0; + int res; + regmatch_t pm[MAXSUBEXP]; + char nname[PAXPATHLEN+1]; /* final result of all replacements */ + char buf1[PAXPATHLEN+1]; /* where we work on the name */ + + /* + * copy the name into buf1, where we will work on it. We need to keep + * the orig string around so we can print out the result of the final + * replacement. We build up the final result in nname. inpt points at + * the string we apply the regular expression to. prnt is used to + * suppress printing when we handle replacements on the link field + * (the user already saw that substitution go by) + */ + pt = rephead; + (void)strlcpy(buf1, name, sizeof(buf1)); + inpt = buf1; + outpt = nname; + endpt = outpt + PAXPATHLEN; + + /* + * try each replacement string in order + */ + while (pt != NULL) { + do { + if ((flags & SYML) && (pt->flgs & SYML)) + continue; + /* + * check for a successful substitution, if not go to + * the next pattern, or cleanup if we were global + */ + if (regexec(&(pt->rcmp), inpt, MAXSUBEXP, pm, 0) != 0) + break; + + /* + * ok we found one. We have three parts, the prefix + * which did not match, the section that did and the + * tail (that also did not match). Copy the prefix to + * the final output buffer (watching to make sure we + * do not create a string too long). + */ + found = 1; + rpt = inpt + pm[0].rm_so; + + while ((inpt < rpt) && (outpt < endpt)) + *outpt++ = *inpt++; + if (outpt == endpt) + break; + + /* + * for the second part (which matched the regular + * expression) apply the substitution using the + * replacement string and place it the prefix in the + * final output. If we have problems, skip it. + */ + if ((res = + resub(&(pt->rcmp),pm,pt->nstr,inpt, outpt,endpt) + ) < 0) { + if (flags & PRNT) + tty_warn(1, "Replacement name error %s", + name); + return 1; + } + outpt += res; + + /* + * we set up to look again starting at the first + * character in the tail (of the input string right + * after the last character matched by the regular + * expression (inpt always points at the first char in + * the string to process). If we are not doing a global + * substitution, we will use inpt to copy the tail to + * the final result. Make sure we do not overrun the + * output buffer + */ + inpt += pm[0].rm_eo - pm[0].rm_so; + + if ((outpt == endpt) || (*inpt == '\0')) + break; + + /* + * if the user wants global we keep trying to + * substitute until it fails, then we are done. + */ + } while (pt->flgs & GLOB); + + if (found) + break; + + /* + * a successful substitution did NOT occur, try the next one + */ + pt = pt->fow; + } + + if (found) { + /* + * we had a substitution, copy the last tail piece (if there is + * room) to the final result + */ + while ((outpt < endpt) && (*inpt != '\0')) + *outpt++ = *inpt++; + + *outpt = '\0'; + if ((outpt == endpt) && (*inpt != '\0')) { + if (flags & PRNT) + tty_warn(1,"Replacement name too long %s >> %s", + name, nname); + return 1; + } + + /* + * inform the user of the result if wanted + */ + if ((flags & PRNT) && (pt->flgs & PRNT)) { + if (*nname == '\0') + (void)fprintf(stderr,"%s >> <empty string>\n", + name); + else + (void)fprintf(stderr,"%s >> %s\n", name, nname); + } + + /* + * if empty inform the caller this file is to be skipped + * otherwise copy the new name over the orig name and return + */ + if (*nname == '\0') + return 1; + if (flags & RENM) + *nlen = strlcpy(name, nname, namelen); + } + return 0; +} + + +/* + * checkdotdot() + * Return true if a component of the name contains a reference to ".." + */ +static int +checkdotdot(const char *name) +{ + const char *p; + /* 1. "..{[/],}" */ + if (name[0] == '.' && name[1] == '.' && + (name[2] == '/' || name[2] == '\0')) + return 1; + + /* 2. "*[/]..[/]*" */ + if (strstr(name, "/../") != NULL) + return 1; + + /* 3. "*[/].." */ + for (p = name; *p; p++) + continue; + if (p - name < 3) + return 0; + if (p[-1] == '.' && p[-2] == '.' && p[-3] == '/') + return 1; + + return 0; +} + + +/* + * resub() + * apply the replacement to the matched expression. expand out the old + * style ed(1) subexpression expansion. + * Return: + * -1 if error, or the number of characters added to the destination. + */ + +static int +resub(regex_t *rp, regmatch_t *pm, char *src, char *txt, char *dest, + char *destend) +{ + char *spt; + char *dpt; + char c; + regmatch_t *pmpt; + int len; + int subexcnt; + + spt = src; + dpt = dest; + subexcnt = rp->re_nsub; + while ((dpt < destend) && ((c = *spt++) != '\0')) { + /* + * see if we just have an ordinary replacement character + * or we refer to a subexpression. + */ + if (c == '&') { + pmpt = pm; + } else if ((c == '\\') && (*spt >= '1') && (*spt <= '9')) { + /* + * make sure there is a subexpression as specified + */ + if ((len = *spt++ - '0') > subexcnt) + return -1; + pmpt = pm + len; + } else { + /* + * Ordinary character, just copy it + */ + if ((c == '\\') && ((*spt == '\\') || (*spt == '&'))) + c = *spt++; + *dpt++ = c; + continue; + } + + /* + * continue if the subexpression is bogus + */ + if ((pmpt->rm_so < 0) || (pmpt->rm_eo < 0) || + ((len = pmpt->rm_eo - pmpt->rm_so) <= 0)) + continue; + + /* + * copy the subexpression to the destination. + * fail if we run out of space or the match string is damaged + */ + if (len > (destend - dpt)) + return -1; + strncpy(dpt, txt + pmpt->rm_so, len); + dpt += len; + } + return dpt - dest; +} diff --git a/bin/pax/pat_rep.h b/bin/pax/pat_rep.h new file mode 100644 index 0000000..f2e0cbe --- /dev/null +++ b/bin/pax/pat_rep.h @@ -0,0 +1,51 @@ +/* $NetBSD: pat_rep.h,v 1.7 2008/02/24 20:42:46 joerg Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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. + * + * @(#)pat_rep.h 8.1 (Berkeley) 5/31/93 + */ + +#include <regex.h> +/* + * data structure for storing user supplied replacement strings (-s) + */ +typedef struct replace { + char *nstr; /* the new string we will substitute with */ + regex_t rcmp; /* compiled regular expression used to match */ + int flgs; /* print conversions? global in operation? */ +#define PRNT 0x1 +#define GLOB 0x2 +#define RENM 0x4 +#define SYML 0x8 + struct replace *fow; /* pointer to next pattern */ +} REPLACE; diff --git a/bin/pax/pax.1 b/bin/pax/pax.1 new file mode 100644 index 0000000..8203a94 --- /dev/null +++ b/bin/pax/pax.1 @@ -0,0 +1,1304 @@ +.\" $NetBSD: pax.1,v 1.69 2017/07/03 21:33:23 wiz Exp $ +.\" +.\" Copyright (c) 1992 Keith Muller. +.\" Copyright (c) 1992, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Keith Muller of the University of California, San Diego. +.\" +.\" 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. +.\" +.\" @(#)pax.1 8.4 (Berkeley) 4/18/94 +.\" +.Dd August 12, 2016 +.Dt PAX 1 +.Os +.Sh NAME +.Nm pax +.Nd read and write file archives and copy directory hierarchies +.Sh SYNOPSIS +.Nm +.Op Fl 0cdjnOVvz +.Op Fl E Ar limit +.Op Fl f Ar archive +.Op Fl N Ar dbdir +.Op Fl s Ar replstr +.Ar ...\& +.Op Fl U Ar user +.Ar ...\& +.Op Fl G Ar group +.Ar ...\& +.Oo +.Fl T +.Sm off +.Op Ar from_date +.Oo , Ar to_date Oc +.Sm on +.Oc +.Ar ...\& +.Op Ar pattern ...\& +.Nm +.Fl r +.Op Fl AcDdijknOuVvYZz +.Op Fl E Ar limit +.Op Fl f Ar archive +.Op Fl N Ar dbdir +.Op Fl o Ar options +.Ar ...\& +.Op Fl p Ar string +.Ar ...\& +.Op Fl s Ar replstr +.Ar ...\& +.Op Fl U Ar user +.Ar ...\& +.Op Fl G Ar group +.Ar ...\& +.Oo +.Fl T +.Sm off +.Op Ar from_date +.Oo , Ar to_date Oc +.Sm on +.Oc +.Ar ...\& +.Op Ar pattern ...\& +.Nm +.Fl w +.Op Fl AdHijLMOPtuVvXz +.Op Fl b Ar blocksize +.Oo +.Op Fl a +.Op Fl f Ar archive +.Oc +.Op Fl x Ar format +.Op Fl B Ar bytes +.Op Fl N Ar dbdir +.Op Fl o Ar options +.Ar ...\& +.Op Fl s Ar replstr +.Ar ...\& +.Op Fl U Ar user +.Ar ...\& +.Op Fl G Ar group +.Ar ...\& +.Oo +.Fl T +.Sm off +.Op Ar from_date +.Oo , Ar to_date Oc +.Oo /[ Cm c ] [ Cm m ] Oc +.Sm on +.Oc +.Ar ...\& +.Op Ar file ...\& +.Nm +.Fl r +.Fl w +.Op Fl ADdHijkLlMnOPtuVvXYZz +.Op Fl N Ar dbdir +.Op Fl p Ar string +.Ar ...\& +.Op Fl s Ar replstr +.Ar ...\& +.Op Fl U Ar user +.Ar ...\& +.Op Fl G Ar group +.Ar ...\& +.Oo +.Fl T +.Sm off +.Op Ar from_date +.Oo , Ar to_date Oc +.Oo /[ Cm c ] [ Cm m ] Oc +.Sm on +.Oc +.Ar ...\& +.Op Ar file ...\& +.Ar directory +.Sh DESCRIPTION +.Nm +will read, write, and list the members of an archive file, +and will copy directory hierarchies. +If the archive file is of the form: +.Ar [[user@]host:]file +then the archive will be processed using +.Xr rmt 8 . +.Pp +.Nm +operation is independent of the specific archive format, +and supports a wide variety of different archive formats. +A list of supported archive formats can be found under the description of the +.Fl x +option. +.Pp +The presence of the +.Fl r +and the +.Fl w +options specifies which of the following functional modes +.Nm +will operate under: +.Em list , read , write , +and +.Em copy . +.Bl -tag -width 6n +.It Aq none +.Em List . +.Nm +will write to +.Dv standard output +a table of contents of the members of the archive file read from +.Dv standard input , +whose pathnames match the specified +.Ar patterns . +The table of contents contains one filename per line +and is written using single line buffering. +.It Fl r +.Em Read . +.Nm +extracts the members of the archive file read from the +.Dv standard input , +with pathnames matching the specified +.Ar patterns . +The archive format and blocking is automatically determined on input. +When an extracted file is a directory, the entire file hierarchy +rooted at that directory is extracted. +All extracted files are created relative to the current file hierarchy. +The setting of ownership, access and modification times, and file mode of +the extracted files are discussed in more detail under the +.Fl p +option. +.It Fl w +.Em Write . +.Nm +writes an archive containing the +.Ar file +operands to +.Dv standard output +using the specified archive format. +When no +.Ar file +operands are specified, a list of files to copy with one per line is read from +.Dv standard input . +When a +.Ar file +operand is also a directory, the entire file hierarchy rooted +at that directory will be included. +.It Fl r Fl w +.Em Copy . +.Nm +copies the +.Ar file +operands to the destination +.Ar directory . +When no +.Ar file +operands are specified, a list of files to copy with one per line is read from +the +.Dv standard input . +When a +.Ar file +operand is also a directory the entire file +hierarchy rooted at that directory will be included. +The effect of the +.Em copy +is as if the copied files were written to an archive file and then +subsequently extracted, except that there may be hard links between +the original and the copied files (see the +.Fl l +option below). +.Pp +.Em Warning : +The destination +.Ar directory +must not be one of the +.Ar file +operands or a member of a file hierarchy rooted at one of the +.Ar file +operands. +The result of a +.Em copy +under these conditions is unpredictable. +.El +.Pp +While processing a damaged archive during a +.Em read +or +.Em list +operation, +.Nm +will attempt to recover from media defects and will search through the archive +to locate and process the largest number of archive members possible (see the +.Fl E +option for more details on error handling). +.Sh OPERANDS +The +.Ar directory +operand specifies a destination directory pathname. +If the +.Ar directory +operand does not exist, or it is not writable by the user, +or it is not of type directory, +.Nm +will exit with a non-zero exit status. +.Pp +The +.Ar pattern +operand is used to select one or more pathnames of archive members. +Archive members are selected using the pattern matching notation described +by +.Xr fnmatch 3 . +When the +.Ar pattern +operand is not supplied, all members of the archive will be selected. +When a +.Ar pattern +matches a directory, the entire file hierarchy rooted at that directory will +be selected. +When a +.Ar pattern +operand does not select at least one archive member, +.Nm +will write these +.Ar pattern +operands in a diagnostic message to +.Dv standard error +and then exit with a non-zero exit status. +.Pp +The +.Ar file +operand specifies the pathname of a file to be copied or archived. +When a +.Ar file +operand does not select at least one archive member, +.Nm +will write these +.Ar file +operand pathnames in a diagnostic message to +.Dv standard error +and then exit with a non-zero exit status. +.Sh OPTIONS +The following options are supported: +.Bl -tag -width 4n +.It Fl r +Read an archive file from +.Dv standard input +and extract the specified +.Ar files . +If any intermediate directories are needed in order to extract an archive +member, these directories will be created as if +.Xr mkdir 2 +was called with the bitwise inclusive +.Dv OR +of +.Dv S_IRWXU , S_IRWXG , +and +.Dv S_IRWXO +as the mode argument. +When the selected archive format supports the specification of linked +files and these files cannot be linked while the archive is being extracted, +.Nm +will write a diagnostic message to +.Dv standard error +and exit with a non-zero exit status at the completion of operation. +.It Fl w +Write files to the +.Dv standard output +in the specified archive format. +When no +.Ar file +operands are specified, +.Dv standard input +is read for a list of pathnames with one per line without any leading or +trailing +.Aq blanks . +.It Fl a +Append +.Ar files +to the end of an archive that was previously written. +If an archive format is not specified with a +.Fl x +option, the format currently being used in the archive will be selected. +Any attempt to append to an archive in a format different from the +format already used in the archive will cause +.Nm +to exit immediately +with a non-zero exit status. +The blocking size used in the archive volume where writing starts +will continue to be used for the remainder of that archive volume. +.Pp +.Em Warning : +Many storage devices are not able to support the operations necessary +to perform an append operation. +Any attempt to append to an archive stored on such a device may damage the +archive or have other unpredictable results. +Tape drives in particular are more likely to not support an append operation. +An archive stored in a regular file system file or on a disk device will +usually support an append operation. +.It Fl b Ar blocksize +When +.Em writing +an archive, +block the output at a positive decimal integer number of +bytes per write to the archive file. +The +.Ar blocksize +must be a multiple of 512 bytes with a maximum of 32256 bytes. +A +.Ar blocksize +can end with +.Li k +or +.Li b +to specify multiplication by 1024 (1K) or 512, respectively. +A pair of +.Ar blocksizes +can be separated by +.Li x +to indicate a product. +A specific archive device may impose additional restrictions on the size +of blocking it will support. +When blocking is not specified, the default +.Ar blocksize +is dependent on the specific archive format being used (see the +.Fl x +option). +.It Fl c +Match all file or archive members +.Em except +those specified by the +.Ar pattern +and +.Ar file +operands. +.It Fl d +Cause files of type directory being copied or archived, or archive members of +type directory being extracted, to match only the directory file or archive +member and not the file hierarchy rooted at the directory. +.It Fl f Ar archive +Specify +.Ar archive +as the pathname of the input or output archive, overriding the default +.Dv standard input +(for +.Em list +and +.Em read ) +or +.Dv standard output +(for +.Em write ) . +A single archive may span multiple files and different archive devices. +When required, +.Nm +will prompt for the pathname of the file or device of the next volume in the +archive. +.It Fl i +Interactively rename files or archive members. +For each archive member matching a +.Ar pattern +operand or each file matching a +.Ar file +operand, +.Nm +will prompt to +.Pa /dev/tty +giving the name of the file, its file mode and its modification time. +.Nm +will then read a line from +.Pa /dev/tty . +If this line is blank, the file or archive member is skipped. +If this line consists of a single period, the +file or archive member is processed with no modification to its name. +Otherwise, its name is replaced with the contents of the line. +.Nm +will immediately exit with a non-zero exit status if +.Aq Dv EOF +is encountered when reading a response or if +.Pa /dev/tty +cannot be opened for reading and writing. +.It Fl j +Use +.Xr bzip2 1 +for compression when reading or writing archive files. +.It Fl k +Do not overwrite existing files. +.It Fl l +Link files. +(The letter ell). +In the +.Em copy +mode +.Fl ( r +.Fl w ) , +hard links are made between the source and destination file hierarchies +whenever possible. +.It Fl n +Select the first archive member that matches each +.Ar pattern +operand. +No more than one archive member is matched for each +.Ar pattern . +When members of type directory are matched, the file hierarchy rooted at that +directory is also matched (unless +.Fl d +is also specified). +.It Fl o Ar options +Information to modify the algorithm for extracting or writing archive files +which is specific to the archive format specified by +.Fl x . +In general, +.Ar options +take the form: +.Cm name=value +.It Fl p Ar string +Specify one or more file characteristic options (privileges). +The +.Ar string +option-argument is a string specifying file characteristics to be retained or +discarded on extraction. +The string consists of the specification characters +.Cm a , e , +.Cm m , o , +and +.Cm p . +Multiple characteristics can be concatenated within the same string +and multiple +.Fl p +options can be specified. +The meaning of the specification characters are as follows: +.Bl -tag -width 2n +.It Cm a +Do not preserve file access times. +By default, file access times are preserved whenever possible. +.It Cm e +.Sq Preserve everything , +the user ID, group ID, file mode bits, +file access time, and file modification time. +This is intended to be used by +.Em root , +someone with all the appropriate privileges, in order to preserve all +aspects of the files as they are recorded in the archive. +The +.Cm e +flag is the sum of the +.Cm o +and +.Cm p +flags. +.\" .It Cm f +.\" Do not preserve file flags. +.\" By default, file flags are preserved whenever possible. +.It Cm m +Do not preserve file modification times. +By default, file modification times are preserved whenever possible. +.It Cm o +Preserve the user ID and group ID. +.It Cm p +.Sq Preserve +the file mode bits. +This is intended to be used by a +.Em user +with regular privileges who wants to preserve all aspects of the file other +than the ownership. +The file times are preserved by default, but two other flags are offered to +disable this and use the time of extraction instead. +.El +.Pp +In the preceding list, +.Sq preserve +indicates that an attribute stored in the archive is given to the +extracted file, subject to the permissions of the invoking +process. +Otherwise the attribute of the extracted file is determined as +part of the normal file creation action. +If neither the +.Cm e +nor the +.Cm o +specification character is specified, or the user ID and group ID are not +preserved for any reason, +.Nm +will not set the +.Dv S_ISUID +.Em ( setuid ) +and +.Dv S_ISGID +.Em ( setgid ) +bits of the file mode. +If the preservation of any of these items fails for any reason, +.Nm +will write a diagnostic message to +.Dv standard error . +Failure to preserve these items will affect the final exit status, +but will not cause the extracted file to be deleted. +If the file characteristic letters in any of the string option-arguments are +duplicated or conflict with each other, the one(s) given last will take +precedence. +For example, if +.Dl Fl p Ar eme +is specified, file modification times are still preserved. +.It Fl s Ar replstr +Modify the file or archive member names specified by the +.Ar pattern +or +.Ar file +operands according to the substitution expression +.Ar replstr , +using the syntax of the +.Xr ed 1 +utility regular expressions. +The format of these regular expressions are: +.Dl /old/new/[gp] +As in +.Xr ed 1 , +.Cm old +is a basic regular expression and +.Cm new +can contain an ampersand (&), \en (where n is a digit) back-references, +or subexpression matching. +The +.Cm old +string may also contain +.Aq Dv newline +characters. +Any non-null character can be used as a delimiter (/ is shown here). +Multiple +.Fl s +expressions can be specified. +The expressions are applied in the order they are specified on the +command line, terminating with the first successful substitution. +The optional trailing +.Cm g +continues to apply the substitution expression to the pathname substring +which starts with the first character following the end of the last successful +substitution. +The first unsuccessful substitution stops the operation of the +.Cm g +option. +The optional trailing +.Cm p +will cause the final result of a successful substitution to be written to +.Dv standard error +in the following format: +.Dl Ao "original pathname" Ac >> Ao "new pathname" Ac +File or archive member names that substitute to the empty string +are not selected and will be skipped. +.It Fl t +Reset the access times of any file or directory read or accessed by +.Nm +to be the same as they were before being read or accessed by +.Nm , +if the user has the appropriate permissions required by +.Xr utime 3 . +.It Fl u +Ignore files that are older (having a less recent file modification time) +than a pre-existing file or archive member with the same name. +During +.Em read , +an archive member with the same name as a file in the file system will be +extracted if the archive member is newer than the file. +During +.Em write , +a file system member with the same name as an archive member will be +written to the archive if it is newer than the archive member. +During +.Em copy , +the file in the destination hierarchy is replaced by the file in the source +hierarchy or by a link to the file in the source hierarchy if the file in +the source hierarchy is newer. +.It Fl v +During a +.Em list +operation, produce a verbose table of contents using the format of the +.Xr ls 1 +utility with the +.Fl l +option. +For pathnames representing a hard link to a previous member of the archive, +the output has the format: +.Dl Ao "ls -l listing" Ac == Ao "link name" Ac +Where +.Aq "ls -l listing" +is the output format specified by the +.Xr ls 1 +utility when used with the +.Fl l +option. +.Pp +Otherwise for all the other operational modes +.Em ( read , write , +and +.Em copy ) , +pathnames are written and flushed to +.Dv standard error +without a trailing +.Aq Dv newline +as soon as processing begins on that file or +archive member. +The trailing +.Aq Dv newline , +is not buffered, and is written only after the file has been read or written. +.Pp +A final summary of archive operations is printed after they have been +completed. +.It Fl x Ar format +Specify the output archive format, with the default format being +.Ar ustar . +.Nm +currently supports the following formats: +.Bl -tag -width "sv4cpio" +.It Ar cpio +The extended cpio interchange format specified in the +.St -p1003.2 +standard. +The default blocksize for this format is 5120 bytes. +Inode and device information about a file (used for detecting file hard links +by this format) which may be truncated by this format is detected by +.Nm +and is repaired. +.It Ar bcpio +The old binary cpio format. +The default blocksize for this format is 5120 bytes. +This format is not very portable and should not be used when other formats +are available. +Inode and device information about a file (used for detecting file hard links +by this format) which may be truncated by this format is detected by +.Nm +and is repaired. +.It Ar sv4cpio +The +.At V.4 +cpio. +The default blocksize for this format is 5120 bytes. +Inode and device information about a file (used for detecting file hard links +by this format) which may be truncated by this format is detected by +.Nm +and is repaired. +.It Ar sv4crc +The +.At V.4 +cpio with file crc checksums. +The default blocksize for this format is 5120 bytes. +Inode and device information about a file (used for detecting file hard links +by this format) which may be truncated by this format is detected by +.Nm +and is repaired. +.It Ar tar +The old +.Bx +tar format as found in +.Bx 4.3 . +The default blocksize for this format is 10240 bytes. +Pathnames stored by this format must be 100 characters or less in length. +Only +.Em regular +files, +.Em hard links , soft links , +and +.Em directories +will be archived (other file types are not supported). +For backward compatibility with even older tar formats, a +.Fl o +option can be used when writing an archive to omit the storage of directories. +This option takes the form: +.Dl Fl o Cm write_opt=nodir +.It Ar ustar +The extended tar interchange format specified in the +.St -p1003.2 +standard. +The default blocksize for this format is 10240 bytes. +Pathnames stored by this format must be 250 characters or less in length. +.El +.Pp +.Nm +will detect and report any file that it is unable to store or extract +as the result of any specific archive format restrictions. +The individual archive formats may impose additional restrictions on use. +Typical archive format restrictions include (but are not limited to): +file pathname length, file size, link pathname length and the type of the file. +.It Fl Fl gnu +Recognize GNU tar extensions. +.It Fl Fl timestamp Ar timestamp +Store all modification times in the archive with the +.Ar timestamp +given instead of the actual modification time of the individual archive member +so that repeatable builds are possible. +The +.Ar timestamp +can be a +.Pa pathname , +where the timestamps are derived from that file, a parseable date for +.Xr parsedate 3 +(this option is not yet available in the tools build), or an integer value +interpreted as the number of seconds from the Epoch. +.It Fl Fl xz +Use +.Xr xz 1 +compression, when reading or writing archive files. +.It Fl z +Use +.Xr gzip 1 +compression, when reading or writing archive files. +.It Fl A +Do not strip leading `/'s from file names. +.It Fl B Ar bytes +Limit the number of bytes written to a single archive volume to +.Ar bytes . +The +.Ar bytes +limit can end with +.Li m , +.Li k , +or +.Li b +to specify multiplication by 1048576 (1M), 1024 (1K) or 512, respectively. +A pair of +.Ar bytes +limits can be separated by +.Li x +to indicate a product. +.Pp +.Em Warning : +Only use this option when writing an archive to a device which supports +an end of file read condition based on last (or largest) write offset +(such as a regular file or a tape drive). +The use of this option with a floppy or hard disk is not recommended. +.It Fl D +This option is the same as the +.Fl u +option, except that the file inode change time is checked instead of the +file modification time. +The file inode change time can be used to select files whose inode information +(e.g. uid, gid, etc.) is newer than a copy of the file in the destination +.Ar directory . +.It Fl E Ar limit +Limit the number of consecutive read faults while trying to read a flawed +archives to +.Ar limit . +With a positive +.Ar limit , +.Nm +will attempt to recover from an archive read error and will +continue processing starting with the next file stored in the archive. +A +.Ar limit +of 0 will cause +.Nm +to stop operation after the first read error is detected on an archive volume. +A +.Ar limit +of +.Li NONE +will cause +.Nm +to attempt to recover from read errors forever. +The default +.Ar limit +is a small positive number of retries. +.Pp +.Em Warning : +Using this option with +.Li NONE +should be used with extreme caution as +.Nm +may get stuck in an infinite loop on a very badly flawed archive. +.It Fl G Ar group +Select a file based on its +.Ar group +name, or when starting with a +.Cm # , +a numeric gid. +A '\e' can be used to escape the +.Cm # . +Multiple +.Fl G +options may be supplied and checking stops with the first match. +.It Fl H +Follow only command line symbolic links while performing a physical file +system traversal. +.It Fl L +Follow all symbolic links to perform a logical file system traversal. +.It Fl M +During a +.Em write +or +.Em copy +operation, treat the list of files on +.Dv standard input +as an +.Xr mtree 8 +.Sq specfile +specification, and write or copy only those items in the specfile. +.Pp +If the file exists in the underlying file system, its permissions and +modification time will be used unless specifically overridden by the specfile. +An error will be raised if the type of entry in the specfile conflicts +with that of an existing file. +A directory entry that is marked +.Sq Sy optional +will not be copied (even though its contents will be). +.Pp +Otherwise, the entry will be +.Sq faked-up , +and it is necessary to specify at least the following parameters +in the specfile: +.Sy type , +.Sy mode , +.Sy gname +or +.Sy gid , +and +.Sy uname +or +.Sy uid , +.Sy device +(in the case of block or character devices), and +.Sy link +(in the case of symbolic links). +If +.Sy time +isn't provided, the current time will be used. +A +.Sq faked-up +entry that is marked +.Sq Sy optional +will not be copied. +.It Fl N Ar dbdir +Except for lookups for the +.Fl G +and +.Fl U +options, +use the user database text file +.Pa master.passwd +and group database text file +.Pa group +from +.Ar dbdir , +rather than using the results from the system's +.Xr getpwnam 3 +and +.Xr getgrnam 3 +(and related) library calls. +.It Fl O +Force the archive to be one volume. +If a volume ends prematurely, +.Nm +will not prompt for a new volume. +This option can be useful for +automated tasks where error recovery cannot be performed by a human. +.It Fl P +Do not follow symbolic links, perform a physical file system traversal. +This is the default mode. +.It Fl T Ar [from_date][,to_date][/[c][m]] +Allow files to be selected based on a file modification or inode change +time falling within a specified time range of +.Ar from_date +to +.Ar to_date +(the dates are inclusive). +If only a +.Ar from_date +is supplied, all files with a modification or inode change time +equal to or younger are selected. +If only a +.Ar to_date +is supplied, all files with a modification or inode change time +equal to or older will be selected. +When the +.Ar from_date +is equal to the +.Ar to_date , +only files with a modification or inode change time of exactly that +time will be selected. +.Pp +When +.Nm +is in the +.Em write +or +.Em copy +mode, the optional trailing field +.Ar [c][m] +can be used to determine which file time (inode change, file modification or +both) are used in the comparison. +If neither is specified, the default is to use file modification time only. +The +.Ar m +specifies the comparison of file modification time (the time when +the file was last written). +The +.Ar c +specifies the comparison of inode change time (the time when the file +inode was last changed; e.g. a change of owner, group, mode, etc). +When +.Ar c +and +.Ar m +are both specified, then the modification and inode change times are +both compared. +The inode change time comparison is useful in selecting files whose +attributes were recently changed or selecting files which were recently +created and had their modification time reset to an older time (as what +happens when a file is extracted from an archive and the modification time +is preserved). +Time comparisons using both file times is useful when +.Nm +is used to create a time based incremental archive (only files that were +changed during a specified time range will be archived). +.Pp +A time range is made up of seven different fields and each field must contain +two digits. +The format is: +.Dl [[[[[cc]yy]mm]dd]hh]mm[\&.ss] +where +.Cm cc +is the first two digits of the year (the century), +.Cm yy +is the last two digits of the year, +the first +.Cm mm +is the month (from 01 to 12), +.Cm dd +is the day of the month (from 01 to 31), +.Cm hh +is the hour of the day (from 00 to 23), +the second +.Cm mm +is the minute (from 00 to 59), +and +.Cm ss +is the seconds (from 00 to 61). +Only the minute field +.Cm mm +is required; the others will default to the current system values. +The +.Cm ss +field may be added independently of the other fields. +If the century is not specified, it defaults to 1900 for +years between 69 and 99, or 2000 for years between 0 and 68. +Time ranges are relative to the current time, so +.Dl Fl T Ar 1234/cm +would select all files with a modification or inode change time +of 12:34 PM today or later. +Multiple +.Fl T +time range can be supplied and checking stops with the first match. +.It Fl U Ar user +Select a file based on its +.Ar user +name, or when starting with a +.Cm # , +a numeric uid. +A '\e' can be used to escape the +.Cm # . +Multiple +.Fl U +options may be supplied and checking stops with the first match. +.It Fl V +A final summary of archive operations is printed after they have been +completed. +Some potentially long-running tape operations are noted. +.It Fl X +When traversing the file hierarchy specified by a pathname, +do not descend into directories that have a different device ID. +See the +.Li st_dev +field as described in +.Xr stat 2 +for more information about device ID's. +.It Fl Y +This option is the same as the +.Fl D +option, except that the inode change time is checked using the +pathname created after all the file name modifications have completed. +.It Fl Z +This option is the same as the +.Fl u +option, except that the modification time is checked using the +pathname created after all the file name modifications have completed. +.It Fl 0 +Use the nul character instead of \en as the file separator when reading +files from standard input. +.It Fl Fl force-local +Do not interpret filenames that contain a `:' as remote files. +.It Fl Fl insecure +Normally +.Nm +ignores filenames that contain +.Dq .. +as a path component. +With this option, +files that contain +.Dq .. +can be processed. +.It Fl Fl use-compress-program +Use the named program as the program to decompress the input or compress +the output. +.El +.Pp +The options that operate on the names of files or archive members +.Fl ( c , +.Fl i , +.Fl n , +.Fl s , +.Fl u , +.Fl v , +.Fl D , +.Fl G , +.Fl T , +.Fl U , +.Fl Y , +and +.Fl Z ) +interact as follows. +.Pp +When extracting files during a +.Em read +operation, archive members are +.Sq selected , +based only on the user specified pattern operands as modified by the +.Fl c , +.Fl n , +.Fl u , +.Fl D , +.Fl G , +.Fl T , +.Fl U +options. +Then any +.Fl s +and +.Fl i +options will modify in that order, the names of these selected files. +Then the +.Fl Y +and +.Fl Z +options will be applied based on the final pathname. +Finally the +.Fl v +option will write the names resulting from these modifications. +.Pp +When archiving files during a +.Em write +operation, or copying files during a +.Em copy +operation, archive members are +.Sq selected , +based only on the user specified pathnames as modified by the +.Fl n , +.Fl u , +.Fl D , +.Fl G , +.Fl T , +and +.Fl U +options (the +.Fl D +option only applies during a copy operation). +Then any +.Fl s +and +.Fl i +options will modify in that order, the names of these selected files. +Then during a +.Em copy +operation the +.Fl Y +and the +.Fl Z +options will be applied based on the final pathname. +Finally the +.Fl v +option will write the names resulting from these modifications. +.Pp +When one or both of the +.Fl u +or +.Fl D +options are specified along with the +.Fl n +option, a file is not considered selected unless it is newer +than the file to which it is compared. +.Sh EXIT STATUS +.Nm +will exit with one of the following values: +.Bl -tag -width 2n +.It 0 +All files were processed successfully. +.It 1 +An error occurred. +.El +.Pp +Whenever +.Nm +cannot create a file or a link when reading an archive or cannot +find a file when writing an archive, or cannot preserve the user ID, +group ID, or file mode when the +.Fl p +option is specified, a diagnostic message is written to +.Dv standard error +and a non-zero exit status will be returned, but processing will continue. +In the case where pax cannot create a link to a file, +.Nm +will not create a second copy of the file. +.Pp +If the extraction of a file from an archive is prematurely terminated by +a signal or error, +.Nm +may have only partially extracted a file the user wanted. +Additionally, the file modes of extracted files and directories +may have incorrect file bits, and the modification and access times may be +wrong. +.Pp +If the creation of an archive is prematurely terminated by a signal or error, +.Nm +may have only partially created the archive which may violate the specific +archive format specification. +.Pp +If while doing a +.Em copy , +.Nm +detects a file is about to overwrite itself, the file is not copied, +a diagnostic message is written to +.Dv standard error +and when +.Nm +completes it will exit with a non-zero exit status. +.Sh EXAMPLES +The command: +.Dl pax -w -f /dev/rst0 \&. +copies the contents of the current directory to the device +.Pa /dev/rst0 . +.Pp +The command: +.Dl pax -v -f filename +gives the verbose table of contents for an archive stored in +.Pa filename . +.Pp +The following commands: +.Dl mkdir newdir +.Dl cd olddir +.Dl pax -rw -pp .\ ../newdir +will copy the entire +.Pa olddir +directory hierarchy to +.Pa newdir , +preserving permissions and access times. +.Pp +When running as root, one may also wish to preserve file +ownership when copying directory trees. +This can be done with the following commands: +.Dl cd olddir +.Dl pax -rw -pe .\ ../newdir +which will copy the contents of +.Pa olddir +into +.Pa ../newdir , +preserving ownership, permissions and access times. +.Pp +The command: +.Dl pax -r -s ',^//*usr//*,,' -f a.pax +reads the archive +.Pa a.pax , +with all files rooted in ``/usr'' into the archive extracted relative to the +current directory. +.Pp +The command: +.Dl pax -rw -i .\ dest_dir +can be used to interactively select the files to copy from the current +directory to +.Pa dest_dir . +.Pp +The command: +.Dl pax -r -pe -U root -G bin -f a.pax +will extract all files from the archive +.Pa a.pax +which are owned by +.Em root +with group +.Em bin +and will preserve all file permissions. +.Pp +The command: +.Dl pax -r -w -v -Y -Z home /backup +will update (and list) only those files in the destination directory +.Pa /backup +which are older (less recent inode change or file modification times) than +files with the same name found in the source file tree +.Pa home . +.Sh SEE ALSO +.Xr cpio 1 , +.Xr tar 1 , +.Xr symlink 7 , +.Xr mtree 8 +.Sh STANDARDS +The +.Nm +utility is a superset of the +.St -p1003.2 +standard. +The options +.Fl B , +.Fl D , +.Fl E , +.Fl G , +.Fl H , +.Fl L , +.Fl M , +.Fl O , +.Fl P , +.Fl T , +.Fl U , +.Fl Y , +.Fl Z , +.Fl z , +the archive formats +.Ar bcpio , +.Ar sv4cpio , +.Ar sv4crc , +.Ar tar , +and the flawed archive handling during +.Ar list +and +.Ar read +operations are extensions to the +.Tn POSIX +standard. +.Sh HISTORY +A +.Nm +utility appeared in +.Bx 4.4 . +.Sh AUTHORS +.An -nosplit +.An Keith Muller +at the University of California, San Diego. +.An Luke Mewburn +implemented +.Fl M . diff --git a/bin/pax/pax.c b/bin/pax/pax.c new file mode 100644 index 0000000..3906569 --- /dev/null +++ b/bin/pax/pax.c @@ -0,0 +1,492 @@ +/* $NetBSD: pax.c,v 1.48 2017/10/02 21:55:35 joerg Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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 HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +__COPYRIGHT("@(#) Copyright (c) 1992, 1993\ + The Regents of the University of California. All rights reserved."); +#if 0 +static char sccsid[] = "@(#)pax.c 8.2 (Berkeley) 4/18/94"; +#else +__RCSID("$NetBSD: pax.c,v 1.48 2017/10/02 21:55:35 joerg Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <util.h> +#include "pax.h" +#include "extern.h" +static int gen_init(void); + +/* + * PAX main routines, general globals and some simple start up routines + */ + +/* + * Variables that can be accessed by any routine within pax + */ +int act = ERROR; /* read/write/append/copy */ +FSUB *frmt = NULL; /* archive format type */ +int cflag; /* match all EXCEPT pattern/file */ +int cwdfd = -1; /* starting cwd */ +int dflag; /* directory member match only */ +int iflag; /* interactive file/archive rename */ +int kflag; /* do not overwrite existing files */ +int lflag; /* use hard links when possible */ +int nflag; /* select first archive member match */ +int tflag; /* restore access time after read */ +int uflag; /* ignore older modification time files */ +int vflag; /* produce verbose output */ +int Aflag; /* honor absolute path */ +int Dflag; /* same as uflag except inode change time */ +int Hflag; /* follow command line symlinks (write only) */ +int Lflag; /* follow symlinks when writing */ +int Mflag; /* treat stdin as an mtree(8) specfile */ +int Vflag; /* produce somewhat verbose output (no listing) */ +int Xflag; /* archive files with same device id only */ +int Yflag; /* same as Dflg except after name mode */ +int Zflag; /* same as uflg except after name mode */ +int vfpart; /* is partial verbose output in progress */ +int patime = 1; /* preserve file access time */ +int pmtime = 1; /* preserve file modification times */ +int nodirs; /* do not create directories as needed */ +int pfflags = 1; /* preserve file flags */ +int pmode; /* preserve file mode bits */ +int pids; /* preserve file uid/gid */ +int rmleadslash = 0; /* remove leading '/' from pathnames */ +int exit_val; /* exit value */ +int docrc; /* check/create file crc */ +int to_stdout; /* extract to stdout */ +char *dirptr; /* destination dir in a copy */ +char *ltmfrmt; /* -v locale time format (if any) */ +const char *argv0; /* root of argv[0] */ +sigset_t s_mask; /* signal mask for cleanup critical sect */ +FILE *listf; /* file pointer to print file list to */ +char *tempfile; /* tempfile to use for mkstemp(3) */ +char *tempbase; /* basename of tempfile to use for mkstemp(3) */ +int forcelocal; /* force local operation even if the name + * contains a : + */ +int secure = 1; /* don't extract names that contain .. */ + +/* + * PAX - Portable Archive Interchange + * + * A utility to read, write, and write lists of the members of archive + * files and copy directory hierarchies. A variety of archive formats + * are supported (some are described in POSIX 1003.1 10.1): + * + * ustar - 10.1.1 extended tar interchange format + * cpio - 10.1.2 extended cpio interchange format + * tar - old BSD 4.3 tar format + * binary cpio - old cpio with binary header format + * sysVR4 cpio - with and without CRC + * + * This version is a superset of IEEE Std 1003.2b-d3 + * + * Summary of Extensions to the IEEE Standard: + * + * 1 READ ENHANCEMENTS + * 1.1 Operations which read archives will continue to operate even when + * processing archives which may be damaged, truncated, or fail to meet + * format specs in several different ways. Damaged sections of archives + * are detected and avoided if possible. Attempts will be made to resync + * archive read operations even with badly damaged media. + * 1.2 Blocksize requirements are not strictly enforced on archive read. + * Tapes which have variable sized records can be read without errors. + * 1.3 The user can specify via the non-standard option flag -E if error + * resync operation should stop on a media error, try a specified number + * of times to correct, or try to correct forever. + * 1.4 Sparse files (lseek holes) stored on the archive (but stored with blocks + * of all zeros will be restored with holes appropriate for the target + * filesystem + * 1.5 The user is notified whenever something is found during archive + * read operations which violates spec (but the read will continue). + * 1.6 Multiple archive volumes can be read and may span over different + * archive devices + * 1.7 Rigidly restores all file attributes exactly as they are stored on the + * archive. + * 1.8 Modification change time ranges can be specified via multiple -T + * options. These allow a user to select files whose modification time + * lies within a specific time range. + * 1.9 Files can be selected based on owner (user name or uid) via one or more + * -U options. + * 1.10 Files can be selected based on group (group name or gid) via one o + * more -G options. + * 1.11 File modification time can be checked against existing file after + * name modification (-Z) + * + * 2 WRITE ENHANCEMENTS + * 2.1 Write operation will stop instead of allowing a user to create a flawed + * flawed archive (due to any problem). + * 2.2 Archives written by pax are forced to strictly conform to both the + * archive and pax the specific format specifications. + * 2.3 Blocking size and format is rigidly enforced on writes. + * 2.4 Formats which may exhibit header overflow problems (they have fields + * too small for large file systems, such as inode number storage), use + * routines designed to repair this problem. These techniques still + * conform to both pax and format specifications, but no longer truncate + * these fields. This removes any restrictions on using these archive + * formats on large file systems. + * 2.5 Multiple archive volumes can be written and may span over different + * archive devices + * 2.6 A archive volume record limit allows the user to specify the number + * of bytes stored on an archive volume. When reached the user is + * prompted for the next archive volume. This is specified with the + * non-standard -B flag. The limit is rounded up to the next blocksize. + * 2.7 All archive padding during write use zero filled sections. This makes + * it much easier to pull data out of flawed archive during read + * operations. + * 2.8 Access time reset with the -t applies to all file nodes (including + * directories). + * 2.9 Symbolic links can be followed with -L (optional in the spec). + * 2.10 Modification or inode change time ranges can be specified via + * multiple -T options. These allow a user to select files whose + * modification or inode change time lies within a specific time range. + * 2.11 Files can be selected based on owner (user name or uid) via one or more + * -U options. + * 2.12 Files can be selected based on group (group name or gid) via one o + * more -G options. + * 2.13 Symlinks which appear on the command line can be followed (without + * following other symlinks; -H flag) + * + * 3 COPY ENHANCEMENTS + * 3.1 Sparse files (lseek holes) can be copied without expanding the holes + * into zero filled blocks. The file copy is created with holes which are + * appropriate for the target filesystem + * 3.2 Access time as well as modification time on copied file trees can be + * preserved with the appropriate -p options. + * 3.3 Access time reset with the -t applies to all file nodes (including + * directories). + * 3.4 Symbolic links can be followed with -L (optional in the spec). + * 3.5 Modification or inode change time ranges can be specified via + * multiple -T options. These allow a user to select files whose + * modification or inode change time lies within a specific time range. + * 3.6 Files can be selected based on owner (user name or uid) via one or more + * -U options. + * 3.7 Files can be selected based on group (group name or gid) via one o + * more -G options. + * 3.8 Symlinks which appear on the command line can be followed (without + * following other symlinks; -H flag) + * 3.9 File inode change time can be checked against existing file before + * name modification (-D) + * 3.10 File inode change time can be checked against existing file after + * name modification (-Y) + * 3.11 File modification time can be checked against existing file after + * name modification (-Z) + * + * 4 GENERAL ENHANCEMENTS + * 4.1 Internal structure is designed to isolate format dependent and + * independent functions. Formats are selected via a format driver table. + * This encourages the addition of new archive formats by only having to + * write those routines which id, read and write the archive header. + */ + +/* + * main() + * parse options, set up and operate as specified by the user. + * any operational flaw will set exit_val to non-zero + * Return: 0 if ok, 1 otherwise + */ + +int +main(int argc, char **argv) +{ + const char *tmpdir; + size_t tdlen; + int rval; + + setprogname(argv[0]); + + listf = stderr; + + /* + * parse options, determine operational mode + */ + options(argc, argv); + + /* + * general init + */ + if ((gen_init() < 0) || (tty_init() < 0)) + return exit_val; + + /* + * Keep a reference to cwd, so we can always come back home. + */ + cwdfd = open(".", O_RDONLY); + if (cwdfd < 0) { + syswarn(1, errno, "Can't open current working directory."); + return exit_val; + } + if (updatepath() == -1) + return exit_val; + + /* + * Where should we put temporary files? + */ + if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0') + tmpdir = _PATH_TMP; + tdlen = strlen(tmpdir); + while(tdlen > 0 && tmpdir[tdlen - 1] == '/') + tdlen--; + tempfile = malloc(tdlen + 1 + sizeof(_TFILE_BASE)); + if (tempfile == NULL) { + tty_warn(1, "Cannot allocate memory for temp file name."); + return exit_val; + } + if (tdlen) + memcpy(tempfile, tmpdir, tdlen); + tempbase = tempfile + tdlen; + *tempbase++ = '/'; + + (void)time(&starttime); +#ifdef SIGINFO + (void)signal(SIGINFO, ar_summary); +#endif + /* + * select a primary operation mode + */ + switch (act) { + case EXTRACT: + rval = extract(); + break; + case ARCHIVE: + rval = archive(); + break; + case APPND: + if (gzip_program != NULL) + err(1, "cannot gzip while appending"); + rval = append(); + /* + * Check if we tried to append on an empty file and + * turned into ARCHIVE mode. + */ + if (act == -ARCHIVE) { + act = ARCHIVE; + rval = archive(); + } + break; + case COPY: + rval = copy(); + break; + default: + case LIST: + rval = list(); + break; + } + if (rval != 0) + exit_val = 1; + return exit_val; +} + +/* + * sig_cleanup() + * when interrupted we try to do whatever delayed processing we can. + * This is not critical, but we really ought to limit our damage when we + * are aborted by the user. + * Return: + * never.... + */ + +__dead static void +sig_cleanup(int which_sig) +{ + /* + * restore modes and times for any dirs we may have created + * or any dirs we may have read. Set vflag and vfpart so the user + * will clearly see the message on a line by itself. + */ + vflag = vfpart = 1; +#ifdef SIGXCPU + if (which_sig == SIGXCPU) + tty_warn(1, "CPU time limit reached, cleaning up."); + else +#endif + tty_warn(1, "Signal caught, cleaning up."); + + /* delete any open temporary file */ + if (xtmp_name) + (void)unlink(xtmp_name); + ar_close(); + proc_dir(); + if (tflag) + atdir_end(); + + (void)raise_default_signal(which_sig); + exit(1); +} + +/* + * gen_init() + * general setup routines. Not all are required, but they really help + * when dealing with a medium to large sized archives. + */ + +static int +gen_init(void) +{ + struct rlimit reslimit; + struct sigaction n_hand; + struct sigaction o_hand; + + /* + * Really needed to handle large archives. We can run out of memory for + * internal tables really fast when we have a whole lot of files... + */ + if (getrlimit(RLIMIT_DATA , &reslimit) == 0){ + reslimit.rlim_cur = reslimit.rlim_max; + (void)setrlimit(RLIMIT_DATA , &reslimit); + } + + /* + * should file size limits be waived? if the os limits us, this is + * needed if we want to write a large archive + */ + if (getrlimit(RLIMIT_FSIZE , &reslimit) == 0){ + reslimit.rlim_cur = reslimit.rlim_max; + (void)setrlimit(RLIMIT_FSIZE , &reslimit); + } + + /* + * increase the size the stack can grow to + */ + if (getrlimit(RLIMIT_STACK , &reslimit) == 0){ + reslimit.rlim_cur = reslimit.rlim_max; + (void)setrlimit(RLIMIT_STACK , &reslimit); + } + +#ifdef RLIMIT_RSS + /* + * not really needed, but doesn't hurt + */ + if (getrlimit(RLIMIT_RSS , &reslimit) == 0){ + reslimit.rlim_cur = reslimit.rlim_max; + (void)setrlimit(RLIMIT_RSS , &reslimit); + } +#endif + + /* + * Handle posix locale + * + * set user defines time printing format for -v option + */ + ltmfrmt = getenv("LC_TIME"); + + /* + * signal handling to reset stored directory times and modes. Since + * we deal with broken pipes via failed writes we ignore it. We also + * deal with any file size limit through failed writes. CPU time + * limits are caught and a cleanup is forced. + */ + if ((sigemptyset(&s_mask) < 0) || (sigaddset(&s_mask, SIGTERM) < 0) || + (sigaddset(&s_mask,SIGINT) < 0)||(sigaddset(&s_mask,SIGHUP) < 0) || + (sigaddset(&s_mask,SIGPIPE) < 0)||(sigaddset(&s_mask,SIGQUIT)<0)){ + tty_warn(1, "Unable to set up signal mask"); + return -1; + } +#ifdef SIGXCPU + if (sigaddset(&s_mask,SIGXCPU) < 0) { + tty_warn(1, "Unable to set up signal mask"); + return -1; + } +#endif +#ifdef SIGXFSZ + if (sigaddset(&s_mask,SIGXFSZ) < 0) { + tty_warn(1, "Unable to set up signal mask"); + return -1; + } +#endif + + memset(&n_hand, 0, sizeof n_hand); + n_hand.sa_mask = s_mask; + n_hand.sa_flags = 0; + n_hand.sa_handler = sig_cleanup; + + if ((sigaction(SIGHUP, &n_hand, &o_hand) < 0) && + (o_hand.sa_handler == SIG_IGN) && + (sigaction(SIGHUP, &o_hand, &o_hand) < 0)) + goto out; + + if ((sigaction(SIGTERM, &n_hand, &o_hand) < 0) && + (o_hand.sa_handler == SIG_IGN) && + (sigaction(SIGTERM, &o_hand, &o_hand) < 0)) + goto out; + + if ((sigaction(SIGINT, &n_hand, &o_hand) < 0) && + (o_hand.sa_handler == SIG_IGN) && + (sigaction(SIGINT, &o_hand, &o_hand) < 0)) + goto out; + + if ((sigaction(SIGQUIT, &n_hand, &o_hand) < 0) && + (o_hand.sa_handler == SIG_IGN) && + (sigaction(SIGQUIT, &o_hand, &o_hand) < 0)) + goto out; + +#ifdef SIGXCPU + if ((sigaction(SIGXCPU, &n_hand, &o_hand) < 0) && + (o_hand.sa_handler == SIG_IGN) && + (sigaction(SIGXCPU, &o_hand, &o_hand) < 0)) + goto out; +#endif + n_hand.sa_handler = SIG_IGN; + if (sigaction(SIGPIPE, &n_hand, &o_hand) < 0) + goto out; +#ifdef SIGXFSZ + if (sigaction(SIGXFSZ, &n_hand, &o_hand) < 0) + goto out; +#endif + return 0; + + out: + syswarn(1, errno, "Unable to set up signal handler"); + return -1; +} diff --git a/bin/pax/pax.h b/bin/pax/pax.h new file mode 100644 index 0000000..ccc0fb7 --- /dev/null +++ b/bin/pax/pax.h @@ -0,0 +1,283 @@ +/* $NetBSD: pax.h,v 1.31 2012/08/09 08:09:21 christos Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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. + * + * @(#)pax.h 8.2 (Berkeley) 4/18/94 + */ + +#if ! HAVE_NBTOOL_CONFIG_H +#define HAVE_LUTIMES 1 +#define HAVE_STRUCT_STAT_ST_FLAGS 1 +#endif + +/* + * BSD PAX global data structures and constants. + */ + +#define MAXBLK 32256 /* MAX blocksize supported (posix SPEC) */ + /* WARNING: increasing MAXBLK past 32256 */ + /* will violate posix spec. */ +#define BLKMULT 512 /* blocksize must be even mult of 512 bytes */ + /* Don't even think of changing this */ +#define DEVBLK 8192 /* default read blksize for devices */ +#define FILEBLK 10240 /* default read blksize for files */ +#define PAXPATHLEN 3072 /* maximum path length for pax. MUST be */ + /* longer than the system MAXPATHLEN */ + +/* + * Pax modes of operation + */ +#define ERROR -1 /* nothing selected */ +#define LIST 0 /* List the file in an archive */ +#define EXTRACT 1 /* extract the files in an archive */ +#define ARCHIVE 2 /* write a new archive */ +#define APPND 3 /* append to the end of an archive */ +#define COPY 4 /* copy files to destination dir */ + +/* + * Device type of the current archive volume + */ +#define ISREG 0 /* regular file */ +#define ISCHR 1 /* character device */ +#define ISBLK 2 /* block device */ +#define ISTAPE 3 /* tape drive */ +#define ISPIPE 4 /* pipe/socket */ +#ifdef SUPPORT_RMT +#define ISRMT 5 /* rmt */ +#endif + +/* + * Pattern matching structure + * + * Used to store command line patterns + */ +typedef struct pattern { + char *pstr; /* pattern to match, user supplied */ + char *pend; /* end of a prefix match */ + char *chdname; /* the dir to change to if not NULL. */ + int plen; /* length of pstr */ + int flgs; /* processing/state flags */ +#define MTCH 0x1 /* pattern has been matched */ +#define DIR_MTCH 0x2 /* pattern matched a directory */ +#define NOGLOB_MTCH 0x4 /* non-globbing match */ + struct pattern *fow; /* next pattern */ +} PATTERN; + +/* + * General Archive Structure (used internal to pax) + * + * This structure is used to pass information about archive members between + * the format independent routines and the format specific routines. When + * new archive formats are added, they must accept requests and supply info + * encoded in a structure of this type. The name fields are declared statically + * here, as there is only ONE of these floating around, size is not a major + * consideration. Eventually converting the name fields to a dynamic length + * may be required if and when the supporting operating system removes all + * restrictions on the length of pathnames it will resolve. + */ +typedef struct { + int nlen; /* file name length */ + char name[PAXPATHLEN+1]; /* file name */ + int ln_nlen; /* link name length */ + char ln_name[PAXPATHLEN+1]; /* name to link to (if any) */ + char *org_name; /* orig name in file system */ + char fts_name[PAXPATHLEN+1]; /* name from fts (for *org_name) */ + char *tmp_name; /* tmp name used to restore */ + PATTERN *pat; /* ptr to pattern match (if any) */ + struct stat sb; /* stat buffer see stat(2) */ + off_t pad; /* bytes of padding after file xfer */ + off_t skip; /* bytes of real data after header */ + /* IMPORTANT. The st_size field does */ + /* not always indicate the amount of */ + /* data following the header. */ + u_long crc; /* file crc */ + int type; /* type of file node */ +#define PAX_DIR 1 /* directory */ +#define PAX_CHR 2 /* character device */ +#define PAX_BLK 3 /* block device */ +#define PAX_REG 4 /* regular file */ +#define PAX_SLK 5 /* symbolic link */ +#define PAX_SCK 6 /* socket */ +#define PAX_FIF 7 /* fifo */ +#define PAX_HLK 8 /* hard link */ +#define PAX_HRG 9 /* hard link to a regular file */ +#define PAX_CTG 10 /* high performance file */ +#define PAX_GLL 11 /* GNU long symlink */ +#define PAX_GLF 12 /* GNU long file */ +} ARCHD; + +/* + * Format Specific Routine Table + * + * The format specific routine table allows new archive formats to be quickly + * added. Overall pax operation is independent of the actual format used to + * form the archive. Only those routines which deal directly with the archive + * are tailored to the oddities of the specific format. All other routines are + * independent of the archive format. Data flow in and out of the format + * dependent routines pass pointers to ARCHD structure (described below). + */ +typedef struct { + const char *name; /* name of format, this is the name the user */ + /* gives to -x option to select it. */ + int bsz; /* default block size. used when the user */ + /* does not specify a blocksize for writing */ + /* Appends continue to with the blocksize */ + /* the archive is currently using.*/ + int hsz; /* Header size in bytes. this is the size of */ + /* the smallest header this format supports. */ + /* Headers are assumed to fit in a BLKMULT. */ + /* If they are bigger, get_head() and */ + /* get_arc() must be adjusted */ + int udev; /* does append require unique dev/ino? some */ + /* formats use the device and inode fields */ + /* to specify hard links. when members in */ + /* the archive have the same inode/dev they */ + /* are assumed to be hard links. During */ + /* append we may have to generate unique ids */ + /* to avoid creating incorrect hard links */ + int hlk; /* does archive store hard links info? if */ + /* not, we do not bother to look for them */ + /* during archive write operations */ + int blkalgn; /* writes must be aligned to blkalgn boundary */ + int inhead; /* is the trailer encoded in a valid header? */ + /* if not, trailers are assumed to be found */ + /* in invalid headers (i.e like tar) */ + int (*id)(char *, int); /* checks if a buffer is a valid header */ + /* returns 1 if it is, o.w. returns a 0 */ + int (*st_rd)(void); /* initialize routine for read. so format */ + /* can set up tables etc before it starts */ + /* reading an archive */ + int (*rd) /* read header routine. passed a pointer to */ + (ARCHD *, char *); /* ARCHD. It must extract the info */ + /* from the format and store it in the ARCHD */ + /* struct. This routine is expected to fill */ + /* all the fields in the ARCHD (including */ + /* stat buf). 0 is returned when a valid */ + /* header is found. -1 when not valid. This */ + /* routine set the skip and pad fields so the */ + /* format independent routines know the */ + /* amount of padding and the number of bytes */ + /* of data which follow the header. This info */ + /* is used to skip to the next file header */ + off_t (*end_rd)(void); /* read cleanup. Allows format to clean up */ + /* and MUST RETURN THE LENGTH OF THE TRAILER */ + /* RECORD (so append knows how many bytes */ + /* to move back to rewrite the trailer) */ + int (*st_wr)(void); /* initialize routine for write operations */ + int (*wr)(ARCHD *); /* write archive header. Passed an ARCHD */ + /* filled with the specs on the next file to */ + /* archived. Returns a 1 if no file data is */ + /* is to be stored; 0 if file data is to be */ + /* added. A -1 is returned if a write */ + /* operation to the archive failed. this */ + /* function sets the skip and pad fields so */ + /* the proper padding can be added after */ + /* file data. This routine must NEVER write */ + /* a flawed archive header. */ + int (*end_wr)(void); /* end write. write the trailer and do any */ + /* other format specific functions needed */ + /* at the end of an archive write */ + int (*trail) /* returns 0 if a valid trailer, -1 if not */ + (char *, int, int *); /* For formats which encode the */ + /* trailer outside of a valid header, a */ + /* return value of 1 indicates that the block */ + /* passed to it can never contain a valid */ + /* header (skip this block, no point in */ + /* looking at it) */ + int (*subtrail) /* read/process file data from the archive */ + (ARCHD *); /* this function is called for trailers */ + /* inside headers. */ + int (*rd_data) /* read/process file data from the archive */ + (ARCHD *, int, off_t *); + int (*wr_data) /* write/process file data to the archive */ + (ARCHD *, int, off_t *); + int (*options)(void); /* process format specific options (-o) */ +} FSUB; + +/* + * Format Specific Options List + * + * Used to pass format options to the format options handler + */ +typedef struct oplist { + char *name; /* option variable name e.g. name= */ + char *value; /* value for option variable */ + struct oplist *fow; /* next option */ +} OPLIST; + +/* + * General Macros + */ +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +#ifdef HOSTPROG +# include "pack_dev.h" /* explicitly use NetBSD's macros */ +# define MAJOR(x) major_netbsd(x) +# define MINOR(x) minor_netbsd(x) +# define TODEV(x, y) makedev_netbsd((x), (y)) +#else +# define MAJOR(x) major(x) +# define MINOR(x) minor(x) +# define TODEV(x, y) makedev((x), (y)) +#endif + +/* + * General Defines + */ +#define HEX 16 +#define OCT 8 +#define _PAX_ 1 + +/* + * Pathname base component of the temporary file template, to be created in + * ${TMPDIR} or, as a fall-back, _PATH_TMP. + */ +#define _TFILE_BASE "paxXXXXXXXXXX" + +/* + * Macros to manipulate off_t as uintmax_t + */ +#define OFFT_F "%" PRIuMAX +#define OFFT_FP(x) "%" x PRIuMAX +#define OFFT_T uintmax_t +#define ASC_OFFT(x,y,z) asc_umax(x,y,z) +#define OFFT_ASC(w,x,y,z) umax_asc((uintmax_t)w,x,y,z) +#define OFFT_OCT(w,x,y,z) umax_oct((uintmax_t)w,x,y,z) +#define STRTOOFFT(x,y,z) strtoimax(x,y,z) +#define OFFT_MAX INTMAX_MAX + +#define TOP_HALF 0xffffffff00000000ULL +#define BOTTOM_HALF 0x00000000ffffffffULL + diff --git a/bin/pax/sel_subs.c b/bin/pax/sel_subs.c new file mode 100644 index 0000000..87f1e79 --- /dev/null +++ b/bin/pax/sel_subs.c @@ -0,0 +1,617 @@ +/* $NetBSD: sel_subs.c,v 1.24 2011/08/31 16:24:54 plunky Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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 HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +#if 0 +static char sccsid[] = "@(#)sel_subs.c 8.1 (Berkeley) 5/31/93"; +#else +__RCSID("$NetBSD: sel_subs.c,v 1.24 2011/08/31 16:24:54 plunky Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/param.h> + +#include <pwd.h> +#include <grp.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <strings.h> +#include <time.h> +#include <unistd.h> +#include <stdlib.h> +#include <tzfile.h> + +#include "pax.h" +#include "sel_subs.h" +#include "extern.h" + +static int str_sec(const char *, time_t *); +static int usr_match(ARCHD *); +static int grp_match(ARCHD *); +static int trng_match(ARCHD *); + +static TIME_RNG *trhead = NULL; /* time range list head */ +static TIME_RNG *trtail = NULL; /* time range list tail */ +static USRT **usrtb = NULL; /* user selection table */ +static GRPT **grptb = NULL; /* group selection table */ + +/* + * Routines for selection of archive members + */ + +/* + * sel_chk() + * check if this file matches a specified uid, gid or time range + * Return: + * 0 if this archive member should be processed, 1 if it should be skipped + */ + +int +sel_chk(ARCHD *arcn) +{ + if (((usrtb != NULL) && usr_match(arcn)) || + ((grptb != NULL) && grp_match(arcn)) || + ((trhead != NULL) && trng_match(arcn))) + return 1; + return 0; +} + +/* + * User/group selection routines + * + * Routines to handle user selection of files based on the file uid/gid. To + * add an entry, the user supplies either the name or the uid/gid starting with + * a # on the command line. A \# will escape the #. + */ + +/* + * usr_add() + * add a user match to the user match hash table + * Return: + * 0 if added ok, -1 otherwise; + */ + +int +usr_add(char *str) +{ + u_int indx; + USRT *pt; + struct passwd *pw; + uid_t uid; + + /* + * create the table if it doesn't exist + */ + if ((str == NULL) || (*str == '\0')) + return -1; + if ((usrtb == NULL) && + ((usrtb = (USRT **)calloc(USR_TB_SZ, sizeof(USRT *))) == NULL)) { + tty_warn(1, + "Unable to allocate memory for user selection table"); + return -1; + } + + /* + * figure out user spec + */ + if (str[0] != '#') { + /* + * it is a user name, \# escapes # as first char in user name + */ + if ((str[0] == '\\') && (str[1] == '#')) + ++str; + if ((pw = getpwnam(str)) == NULL) { + tty_warn(1, "Unable to find uid for user: %s", str); + return -1; + } + uid = (uid_t)pw->pw_uid; + } else + uid = (uid_t)strtoul(str+1, NULL, 10); + endpwent(); + + /* + * hash it and go down the hash chain (if any) looking for it + */ + indx = ((unsigned)uid) % USR_TB_SZ; + if ((pt = usrtb[indx]) != NULL) { + while (pt != NULL) { + if (pt->uid == uid) + return 0; + pt = pt->fow; + } + } + + /* + * uid is not yet in the table, add it to the front of the chain + */ + if ((pt = (USRT *)malloc(sizeof(USRT))) != NULL) { + pt->uid = uid; + pt->fow = usrtb[indx]; + usrtb[indx] = pt; + return 0; + } + tty_warn(1, "User selection table out of memory"); + return -1; +} + +/* + * usr_match() + * check if this files uid matches a selected uid. + * Return: + * 0 if this archive member should be processed, 1 if it should be skipped + */ + +static int +usr_match(ARCHD *arcn) +{ + USRT *pt; + + /* + * hash and look for it in the table + */ + pt = usrtb[((unsigned)arcn->sb.st_uid) % USR_TB_SZ]; + while (pt != NULL) { + if (pt->uid == arcn->sb.st_uid) + return 0; + pt = pt->fow; + } + + /* + * not found + */ + return 1; +} + +/* + * grp_add() + * add a group match to the group match hash table + * Return: + * 0 if added ok, -1 otherwise; + */ + +int +grp_add(char *str) +{ + u_int indx; + GRPT *pt; + struct group *gr; + gid_t gid; + + /* + * create the table if it doesn't exist + */ + if ((str == NULL) || (*str == '\0')) + return -1; + if ((grptb == NULL) && + ((grptb = (GRPT **)calloc(GRP_TB_SZ, sizeof(GRPT *))) == NULL)) { + tty_warn(1, + "Unable to allocate memory fo group selection table"); + return -1; + } + + /* + * figure out user spec + */ + if (str[0] != '#') { + /* + * it is a group name, \# escapes # as first char in group name + */ + if ((str[0] == '\\') && (str[1] == '#')) + ++str; + if ((gr = getgrnam(str)) == NULL) { + tty_warn(1, + "Cannot determine gid for group name: %s", str); + return -1; + } + gid = (gid_t)gr->gr_gid; + } else + gid = (gid_t)strtoul(str+1, NULL, 10); + endgrent(); + + /* + * hash it and go down the hash chain (if any) looking for it + */ + indx = ((unsigned)gid) % GRP_TB_SZ; + if ((pt = grptb[indx]) != NULL) { + while (pt != NULL) { + if (pt->gid == gid) + return 0; + pt = pt->fow; + } + } + + /* + * gid not in the table, add it to the front of the chain + */ + if ((pt = (GRPT *)malloc(sizeof(GRPT))) != NULL) { + pt->gid = gid; + pt->fow = grptb[indx]; + grptb[indx] = pt; + return 0; + } + tty_warn(1, "Group selection table out of memory"); + return -1; +} + +/* + * grp_match() + * check if this files gid matches a selected gid. + * Return: + * 0 if this archive member should be processed, 1 if it should be skipped + */ + +static int +grp_match(ARCHD *arcn) +{ + GRPT *pt; + + /* + * hash and look for it in the table + */ + pt = grptb[((unsigned)arcn->sb.st_gid) % GRP_TB_SZ]; + while (pt != NULL) { + if (pt->gid == arcn->sb.st_gid) + return 0; + pt = pt->fow; + } + + /* + * not found + */ + return 1; +} + +/* + * Time range selection routines + * + * Routines to handle user selection of files based on the modification and/or + * inode change time falling within a specified time range (the non-standard + * -T flag). The user may specify any number of different file time ranges. + * Time ranges are checked one at a time until a match is found (if at all). + * If the file has a mtime (and/or ctime) which lies within one of the time + * ranges, the file is selected. Time ranges may have a lower and/or a upper + * value. These ranges are inclusive. When no time ranges are supplied to pax + * with the -T option, all members in the archive will be selected by the time + * range routines. When only a lower range is supplied, only files with a + * mtime (and/or ctime) equal to or younger are selected. When only a upper + * range is supplied, only files with a mtime (and/or ctime) equal to or older + * are selected. When the lower time range is equal to the upper time range, + * only files with a mtime (or ctime) of exactly that time are selected. + */ + +/* + * trng_add() + * add a time range match to the time range list. + * This is a non-standard pax option. Lower and upper ranges are in the + * format: [yy[mm[dd[hh]]]]mm[.ss] and are comma separated. + * Time ranges are based on current time, so 1234 would specify a time of + * 12:34 today. + * Return: + * 0 if the time range was added to the list, -1 otherwise + */ + +int +trng_add(char *str) +{ + TIME_RNG *pt; + char *up_pt = NULL; + char *stpt; + char *flgpt; + int dot = 0; + + /* + * throw out the badly formed time ranges + */ + if ((str == NULL) || (*str == '\0')) { + tty_warn(1, "Empty time range string"); + return -1; + } + + /* + * locate optional flags suffix /{cm}. + */ + if ((flgpt = strrchr(str, '/')) != NULL) + *flgpt++ = '\0'; + + for (stpt = str; *stpt != '\0'; ++stpt) { + if ((*stpt >= '0') && (*stpt <= '9')) + continue; + if ((*stpt == ',') && (up_pt == NULL)) { + *stpt = '\0'; + up_pt = stpt + 1; + dot = 0; + continue; + } + + /* + * allow only one dot per range (secs) + */ + if ((*stpt == '.') && (!dot)) { + ++dot; + continue; + } + tty_warn(1, "Improperly specified time range: %s", str); + goto out; + } + + /* + * allocate space for the time range and store the limits + */ + if ((pt = malloc(sizeof(TIME_RNG))) == NULL) { + tty_warn(1, "Unable to allocate memory for time range"); + return -1; + } + + /* + * by default we only will check file mtime, but user can specify + * mtime, ctime (inode change time) or both. + */ + if ((flgpt == NULL) || (*flgpt == '\0')) + pt->flgs = CMPMTME; + else { + pt->flgs = 0; + while (*flgpt != '\0') { + switch(*flgpt) { + case 'M': + case 'm': + pt->flgs |= CMPMTME; + break; + case 'C': + case 'c': + pt->flgs |= CMPCTME; + break; + default: + tty_warn(1, "Bad option %c with time range %s", + *flgpt, str); + free(pt); + goto out; + } + ++flgpt; + } + } + + /* + * start off with the current time + */ + pt->low_time = pt->high_time = time(NULL); + if (*str != '\0') { + /* + * add lower limit + */ + if (str_sec(str, &(pt->low_time)) < 0) { + tty_warn(1, "Illegal lower time range %s", str); + free(pt); + goto out; + } + pt->flgs |= HASLOW; + } + + if ((up_pt != NULL) && (*up_pt != '\0')) { + /* + * add upper limit + */ + if (str_sec(up_pt, &(pt->high_time)) < 0) { + tty_warn(1, "Illegal upper time range %s", up_pt); + free(pt); + goto out; + } + pt->flgs |= HASHIGH; + + /* + * check that the upper and lower do not overlap + */ + if (pt->flgs & HASLOW) { + if (pt->low_time > pt->high_time) { + tty_warn(1, + "Upper %s and lower %s time overlap", + up_pt, str); + free(pt); + return -1; + } + } + } + + pt->fow = NULL; + if (trhead == NULL) { + trtail = trhead = pt; + return 0; + } + trtail->fow = pt; + trtail = pt; + return 0; + + out: + tty_warn(1, "Time range format is: [yy[mm[dd[hh]]]]mm[.ss][/[c][m]]"); + return -1; +} + +/* + * trng_match() + * check if this files mtime/ctime falls within any supplied time range. + * Return: + * 0 if this archive member should be processed, 1 if it should be skipped + */ + +static int +trng_match(ARCHD *arcn) +{ + TIME_RNG *pt; + + /* + * have to search down the list one at a time looking for a match. + * remember time range limits are inclusive. + */ + pt = trhead; + while (pt != NULL) { + switch(pt->flgs & CMPBOTH) { + case CMPBOTH: + /* + * user wants both mtime and ctime checked for this + * time range + */ + if (((pt->flgs & HASLOW) && + (arcn->sb.st_mtime < pt->low_time) && + (arcn->sb.st_ctime < pt->low_time)) || + ((pt->flgs & HASHIGH) && + (arcn->sb.st_mtime > pt->high_time) && + (arcn->sb.st_ctime > pt->high_time))) { + pt = pt->fow; + continue; + } + break; + case CMPCTME: + /* + * user wants only ctime checked for this time range + */ + if (((pt->flgs & HASLOW) && + (arcn->sb.st_ctime < pt->low_time)) || + ((pt->flgs & HASHIGH) && + (arcn->sb.st_ctime > pt->high_time))) { + pt = pt->fow; + continue; + } + break; + case CMPMTME: + default: + /* + * user wants only mtime checked for this time range + */ + if (((pt->flgs & HASLOW) && + (arcn->sb.st_mtime < pt->low_time)) || + ((pt->flgs & HASHIGH) && + (arcn->sb.st_mtime > pt->high_time))) { + pt = pt->fow; + continue; + } + break; + } + break; + } + + if (pt == NULL) + return 1; + return 0; +} + +/* + * str_sec() + * Convert a time string in the format of [yy[mm[dd[hh]]]]mm[.ss] to gmt + * seconds. Tval already has current time loaded into it at entry. + * Return: + * 0 if converted ok, -1 otherwise + */ + +#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) + +static int +str_sec(const char *p, time_t *tval) +{ + struct tm *lt; + const char *dot, *t; + int yearset, len; + + for (t = p, dot = NULL; *t; ++t) { + if (isdigit((unsigned char)*t)) + continue; + if (*t == '.' && dot == NULL) { + dot = t; + continue; + } + return -1; + } + + lt = localtime(tval); + + if (dot != NULL) { + len = strlen(dot); + if (len != 3) + return -1; + ++dot; + lt->tm_sec = ATOI2(dot); + } else { + len = 0; + lt->tm_sec = 0; + } + + yearset = 0; + switch (strlen(p) - len) { + case 12: + lt->tm_year = ATOI2(p) * 100 - TM_YEAR_BASE; + yearset = 1; + /* FALLTHROUGH */ + case 10: + if (yearset) { + lt->tm_year += ATOI2(p); + } else { + yearset = ATOI2(p); + if (yearset < 69) + lt->tm_year = yearset + 2000 - TM_YEAR_BASE; + else + lt->tm_year = yearset + 1900 - TM_YEAR_BASE; + } + /* FALLTHROUGH */ + case 8: + lt->tm_mon = ATOI2(p); + --lt->tm_mon; + /* FALLTHROUGH */ + case 6: + lt->tm_mday = ATOI2(p); + /* FALLTHROUGH */ + case 4: + lt->tm_hour = ATOI2(p); + /* FALLTHROUGH */ + case 2: + lt->tm_min = ATOI2(p); + break; + default: + return -1; + } + + /* + * convert broken-down time to GMT clock time seconds + */ + if ((*tval = mktime(lt)) == -1) + return -1; + return 0; +} diff --git a/bin/pax/sel_subs.h b/bin/pax/sel_subs.h new file mode 100644 index 0000000..c91f3ea --- /dev/null +++ b/bin/pax/sel_subs.h @@ -0,0 +1,69 @@ +/* $NetBSD: sel_subs.h,v 1.6 2003/10/13 07:41:22 agc Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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. + * + * @(#)sel_subs.h 8.1 (Berkeley) 5/31/93 + */ + +/* + * data structure for storing uid/grp selects (-U, -G non standard options) + */ + +#define USR_TB_SZ 317 /* user selection table size */ +#define GRP_TB_SZ 317 /* user selection table size */ + +typedef struct usrt { + uid_t uid; + struct usrt *fow; /* next uid */ +} USRT; + +typedef struct grpt { + gid_t gid; + struct grpt *fow; /* next gid */ +} GRPT; + +/* + * data structure for storing user supplied time ranges (-T option) + */ + +typedef struct time_rng { + time_t low_time; /* lower inclusive time limit */ + time_t high_time; /* higher inclusive time limit */ + int flgs; /* option flags */ +#define HASLOW 0x01 /* has lower time limit */ +#define HASHIGH 0x02 /* has higher time limit */ +#define CMPMTME 0x04 /* compare file modification time */ +#define CMPCTME 0x08 /* compare inode change time */ +#define CMPBOTH (CMPMTME|CMPCTME) /* compare inode and mod time */ + struct time_rng *fow; /* next pattern */ +} TIME_RNG; diff --git a/bin/pax/tables.c b/bin/pax/tables.c new file mode 100644 index 0000000..dd135fe --- /dev/null +++ b/bin/pax/tables.c @@ -0,0 +1,1379 @@ +/* $NetBSD: tables.c,v 1.31 2013/10/18 19:53:34 christos Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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 HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +#if 0 +static char sccsid[] = "@(#)tables.c 8.1 (Berkeley) 5/31/93"; +#else +__RCSID("$NetBSD: tables.c,v 1.31 2013/10/18 19:53:34 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <stdio.h> +#include <ctype.h> +#include <fcntl.h> +#include <paths.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include "pax.h" +#include "tables.h" +#include "extern.h" + +/* + * Routines for controlling the contents of all the different databases pax + * keeps. Tables are dynamically created only when they are needed. The + * goal was speed and the ability to work with HUGE archives. The databases + * were kept simple, but do have complex rules for when the contents change. + * As of this writing, the POSIX library functions were more complex than + * needed for this application (pax databases have very short lifetimes and + * do not survive after pax is finished). Pax is required to handle very + * large archives. These database routines carefully combine memory usage and + * temporary file storage in ways which will not significantly impact runtime + * performance while allowing the largest possible archives to be handled. + * Trying to force the fit to the POSIX database routines was not considered + * time well spent. + */ + +static HRDLNK **ltab = NULL; /* hard link table for detecting hard links */ +static FTM **ftab = NULL; /* file time table for updating arch */ +static NAMT **ntab = NULL; /* interactive rename storage table */ +static DEVT **dtab = NULL; /* device/inode mapping tables */ +static ATDIR **atab = NULL; /* file tree directory time reset table */ +#ifdef DIRS_USE_FILE +static int dirfd = -1; /* storage for setting created dir time/mode */ +static u_long dircnt; /* entries in dir time/mode storage */ +#endif +static int ffd = -1; /* tmp file for file time table name storage */ + +static DEVT *chk_dev(dev_t, int); + +/* + * hard link table routines + * + * The hard link table tries to detect hard links to files using the device and + * inode values. We do this when writing an archive, so we can tell the format + * write routine that this file is a hard link to another file. The format + * write routine then can store this file in whatever way it wants (as a hard + * link if the format supports that like tar, or ignore this info like cpio). + * (Actually a field in the format driver table tells us if the format wants + * hard link info. if not, we do not waste time looking for them). We also use + * the same table when reading an archive. In that situation, this table is + * used by the format read routine to detect hard links from stored dev and + * inode numbers (like cpio). This will allow pax to create a link when one + * can be detected by the archive format. + */ + +/* + * lnk_start + * Creates the hard link table. + * Return: + * 0 if created, -1 if failure + */ + +int +lnk_start(void) +{ + if (ltab != NULL) + return 0; + if ((ltab = (HRDLNK **)calloc(L_TAB_SZ, sizeof(HRDLNK *))) == NULL) { + tty_warn(1, "Cannot allocate memory for hard link table"); + return -1; + } + return 0; +} + +/* + * chk_lnk() + * Looks up entry in hard link hash table. If found, it copies the name + * of the file it is linked to (we already saw that file) into ln_name. + * lnkcnt is decremented and if goes to 1 the node is deleted from the + * database. (We have seen all the links to this file). If not found, + * we add the file to the database if it has the potential for having + * hard links to other files we may process (it has a link count > 1) + * Return: + * if found returns 1; if not found returns 0; -1 on error + */ + +int +chk_lnk(ARCHD *arcn) +{ + HRDLNK *pt; + HRDLNK **ppt; + u_int indx; + + if (ltab == NULL) + return -1; + /* + * ignore those nodes that cannot have hard links + */ + if ((arcn->type == PAX_DIR) || (arcn->sb.st_nlink <= 1)) + return 0; + + /* + * hash inode number and look for this file + */ + indx = ((unsigned)arcn->sb.st_ino) % L_TAB_SZ; + if ((pt = ltab[indx]) != NULL) { + /* + * its hash chain is not empty, walk down looking for it + */ + ppt = &(ltab[indx]); + while (pt != NULL) { + if ((pt->ino == arcn->sb.st_ino) && + (pt->dev == arcn->sb.st_dev)) + break; + ppt = &(pt->fow); + pt = pt->fow; + } + + if (pt != NULL) { + /* + * found a link. set the node type and copy in the + * name of the file it is to link to. we need to + * handle hardlinks to regular files differently than + * other links. + */ + arcn->ln_nlen = strlcpy(arcn->ln_name, pt->name, + sizeof(arcn->ln_name)); + if (arcn->type == PAX_REG) + arcn->type = PAX_HRG; + else + arcn->type = PAX_HLK; + + /* + * if we have found all the links to this file, remove + * it from the database + */ + if (--pt->nlink <= 1) { + *ppt = pt->fow; + (void)free((char *)pt->name); + (void)free((char *)pt); + } + return 1; + } + } + + /* + * we never saw this file before. It has links so we add it to the + * front of this hash chain + */ + if ((pt = (HRDLNK *)malloc(sizeof(HRDLNK))) != NULL) { + if ((pt->name = strdup(arcn->name)) != NULL) { + pt->dev = arcn->sb.st_dev; + pt->ino = arcn->sb.st_ino; + pt->nlink = arcn->sb.st_nlink; + pt->fow = ltab[indx]; + ltab[indx] = pt; + return 0; + } + (void)free((char *)pt); + } + + tty_warn(1, "Hard link table out of memory"); + return -1; +} + +/* + * purg_lnk + * remove reference for a file that we may have added to the data base as + * a potential source for hard links. We ended up not using the file, so + * we do not want to accidentally point another file at it later on. + */ + +void +purg_lnk(ARCHD *arcn) +{ + HRDLNK *pt; + HRDLNK **ppt; + u_int indx; + + if (ltab == NULL) + return; + /* + * do not bother to look if it could not be in the database + */ + if ((arcn->sb.st_nlink <= 1) || (arcn->type == PAX_DIR) || + (arcn->type == PAX_HLK) || (arcn->type == PAX_HRG)) + return; + + /* + * find the hash chain for this inode value, if empty return + */ + indx = ((unsigned)arcn->sb.st_ino) % L_TAB_SZ; + if ((pt = ltab[indx]) == NULL) + return; + + /* + * walk down the list looking for the inode/dev pair, unlink and + * free if found + */ + ppt = &(ltab[indx]); + while (pt != NULL) { + if ((pt->ino == arcn->sb.st_ino) && + (pt->dev == arcn->sb.st_dev)) + break; + ppt = &(pt->fow); + pt = pt->fow; + } + if (pt == NULL) + return; + + /* + * remove and free it + */ + *ppt = pt->fow; + (void)free((char *)pt->name); + (void)free((char *)pt); +} + +/* + * lnk_end() + * pull apart a existing link table so we can reuse it. We do this between + * read and write phases of append with update. (The format may have + * used the link table, and we need to start with a fresh table for the + * write phase + */ + +void +lnk_end(void) +{ + int i; + HRDLNK *pt; + HRDLNK *ppt; + + if (ltab == NULL) + return; + + for (i = 0; i < L_TAB_SZ; ++i) { + if (ltab[i] == NULL) + continue; + pt = ltab[i]; + ltab[i] = NULL; + + /* + * free up each entry on this chain + */ + while (pt != NULL) { + ppt = pt; + pt = ppt->fow; + (void)free((char *)ppt->name); + (void)free((char *)ppt); + } + } + return; +} + +/* + * modification time table routines + * + * The modification time table keeps track of last modification times for all + * files stored in an archive during a write phase when -u is set. We only + * add a file to the archive if it is newer than a file with the same name + * already stored on the archive (if there is no other file with the same + * name on the archive it is added). This applies to writes and appends. + * An append with an -u must read the archive and store the modification time + * for every file on that archive before starting the write phase. It is clear + * that this is one HUGE database. To save memory space, the actual file names + * are stored in a scratch file and indexed by an in-memory hash table. The + * hash table is indexed by hashing the file path. The nodes in the table store + * the length of the filename and the lseek offset within the scratch file + * where the actual name is stored. Since there are never any deletions from this + * table, fragmentation of the scratch file is never a issue. Lookups seem to + * not exhibit any locality at all (files in the database are rarely + * looked up more than once...), so caching is just a waste of memory. The + * only limitation is the amount of scratch file space available to store the + * path names. + */ + +/* + * ftime_start() + * create the file time hash table and open for read/write the scratch + * file. (after created it is unlinked, so when we exit we leave + * no witnesses). + * Return: + * 0 if the table and file was created ok, -1 otherwise + */ + +int +ftime_start(void) +{ + if (ftab != NULL) + return 0; + if ((ftab = (FTM **)calloc(F_TAB_SZ, sizeof(FTM *))) == NULL) { + tty_warn(1, "Cannot allocate memory for file time table"); + return -1; + } + + /* + * get random name and create temporary scratch file, unlink name + * so it will get removed on exit + */ + memcpy(tempbase, _TFILE_BASE, sizeof(_TFILE_BASE)); + if ((ffd = mkstemp(tempfile)) == -1) { + syswarn(1, errno, "Unable to create temporary file: %s", + tempfile); + return -1; + } + + (void)unlink(tempfile); + return 0; +} + +/* + * chk_ftime() + * looks up entry in file time hash table. If not found, the file is + * added to the hash table and the file named stored in the scratch file. + * If a file with the same name is found, the file times are compared and + * the most recent file time is retained. If the new file was younger (or + * was not in the database) the new file is selected for storage. + * Return: + * 0 if file should be added to the archive, 1 if it should be skipped, + * -1 on error + */ + +int +chk_ftime(ARCHD *arcn) +{ + FTM *pt; + int namelen; + u_int indx; + char ckname[PAXPATHLEN+1]; + + /* + * no info, go ahead and add to archive + */ + if (ftab == NULL) + return 0; + + /* + * hash the pathname and look up in table + */ + namelen = arcn->nlen; + indx = st_hash(arcn->name, namelen, F_TAB_SZ); + if ((pt = ftab[indx]) != NULL) { + /* + * the hash chain is not empty, walk down looking for match + * only read up the path names if the lengths match, speeds + * up the search a lot + */ + while (pt != NULL) { + if (pt->namelen == namelen) { + /* + * potential match, have to read the name + * from the scratch file. + */ + if (lseek(ffd,pt->seek,SEEK_SET) != pt->seek) { + syswarn(1, errno, + "Failed ftime table seek"); + return -1; + } + if (xread(ffd, ckname, namelen) != namelen) { + syswarn(1, errno, + "Failed ftime table read"); + return -1; + } + + /* + * if the names match, we are done + */ + if (!strncmp(ckname, arcn->name, namelen)) + break; + } + + /* + * try the next entry on the chain + */ + pt = pt->fow; + } + + if (pt != NULL) { + /* + * found the file, compare the times, save the newer + */ + if (arcn->sb.st_mtime > pt->mtime) { + /* + * file is newer + */ + pt->mtime = arcn->sb.st_mtime; + return 0; + } + /* + * file is older + */ + return 1; + } + } + + /* + * not in table, add it + */ + if ((pt = (FTM *)malloc(sizeof(FTM))) != NULL) { + /* + * add the name at the end of the scratch file, saving the + * offset. add the file to the head of the hash chain + */ + if ((pt->seek = lseek(ffd, (off_t)0, SEEK_END)) >= 0) { + if (xwrite(ffd, arcn->name, namelen) == namelen) { + pt->mtime = arcn->sb.st_mtime; + pt->namelen = namelen; + pt->fow = ftab[indx]; + ftab[indx] = pt; + return 0; + } + syswarn(1, errno, "Failed write to file time table"); + } else + syswarn(1, errno, "Failed seek on file time table"); + } else + tty_warn(1, "File time table ran out of memory"); + + if (pt != NULL) + (void)free((char *)pt); + return -1; +} + +/* + * Interactive rename table routines + * + * The interactive rename table keeps track of the new names that the user + * assigns to files from tty input. Since this map is unique for each file + * we must store it in case there is a reference to the file later in archive + * (a link). Otherwise we will be unable to find the file we know was + * extracted. The remapping of these files is stored in a memory based hash + * table (it is assumed since input must come from /dev/tty, it is unlikely to + * be a very large table). + */ + +/* + * name_start() + * create the interactive rename table + * Return: + * 0 if successful, -1 otherwise + */ + +int +name_start(void) +{ + if (ntab != NULL) + return 0; + if ((ntab = (NAMT **)calloc(N_TAB_SZ, sizeof(NAMT *))) == NULL) { + tty_warn(1, + "Cannot allocate memory for interactive rename table"); + return -1; + } + return 0; +} + +/* + * add_name() + * add the new name to old name mapping just created by the user. + * If an old name mapping is found (there may be duplicate names on an + * archive) only the most recent is kept. + * Return: + * 0 if added, -1 otherwise + */ + +int +add_name(char *oname, int onamelen, char *nname) +{ + NAMT *pt; + u_int indx; + + if (ntab == NULL) { + /* + * should never happen + */ + tty_warn(0, "No interactive rename table, links may fail\n"); + return 0; + } + + /* + * look to see if we have already mapped this file, if so we + * will update it + */ + indx = st_hash(oname, onamelen, N_TAB_SZ); + if ((pt = ntab[indx]) != NULL) { + /* + * look down the has chain for the file + */ + while ((pt != NULL) && (strcmp(oname, pt->oname) != 0)) + pt = pt->fow; + + if (pt != NULL) { + /* + * found an old mapping, replace it with the new one + * the user just input (if it is different) + */ + if (strcmp(nname, pt->nname) == 0) + return 0; + + (void)free((char *)pt->nname); + if ((pt->nname = strdup(nname)) == NULL) { + tty_warn(1, "Cannot update rename table"); + return -1; + } + return 0; + } + } + + /* + * this is a new mapping, add it to the table + */ + if ((pt = (NAMT *)malloc(sizeof(NAMT))) != NULL) { + if ((pt->oname = strdup(oname)) != NULL) { + if ((pt->nname = strdup(nname)) != NULL) { + pt->fow = ntab[indx]; + ntab[indx] = pt; + return 0; + } + (void)free((char *)pt->oname); + } + (void)free((char *)pt); + } + tty_warn(1, "Interactive rename table out of memory"); + return -1; +} + +/* + * sub_name() + * look up a link name to see if it points at a file that has been + * remapped by the user. If found, the link is adjusted to contain the + * new name (oname is the link to name) + */ + +void +sub_name(char *oname, int *onamelen, size_t onamesize) +{ + NAMT *pt; + u_int indx; + + if (ntab == NULL) + return; + /* + * look the name up in the hash table + */ + indx = st_hash(oname, *onamelen, N_TAB_SZ); + if ((pt = ntab[indx]) == NULL) + return; + + while (pt != NULL) { + /* + * walk down the hash chain looking for a match + */ + if (strcmp(oname, pt->oname) == 0) { + /* + * found it, replace it with the new name + * and return (we know that oname has enough space) + */ + *onamelen = strlcpy(oname, pt->nname, onamesize); + return; + } + pt = pt->fow; + } + + /* + * no match, just return + */ + return; +} + +/* + * device/inode mapping table routines + * (used with formats that store device and inodes fields) + * + * device/inode mapping tables remap the device field in an archive header. The + * device/inode fields are used to determine when files are hard links to each + * other. However these values have very little meaning outside of that. This + * database is used to solve one of two different problems. + * + * 1) when files are appended to an archive, while the new files may have hard + * links to each other, you cannot determine if they have hard links to any + * file already stored on the archive from a prior run of pax. We must assume + * that these inode/device pairs are unique only within a SINGLE run of pax + * (which adds a set of files to an archive). So we have to make sure the + * inode/dev pairs we add each time are always unique. We do this by observing + * while the inode field is very dense, the use of the dev field is fairly + * sparse. Within each run of pax, we remap any device number of a new archive + * member that has a device number used in a prior run and already stored in a + * file on the archive. During the read phase of the append, we store the + * device numbers used and mark them to not be used by any file during the + * write phase. If during write we go to use one of those old device numbers, + * we remap it to a new value. + * + * 2) Often the fields in the archive header used to store these values are + * too small to store the entire value. The result is an inode or device value + * which can be truncated. This really can foul up an archive. With truncation + * we end up creating links between files that are really not links (after + * truncation the inodes are the same value). We address that by detecting + * truncation and forcing a remap of the device field to split truncated + * inodes away from each other. Each truncation creates a pattern of bits that + * are removed. We use this pattern of truncated bits to partition the inodes + * on a single device to many different devices (each one represented by the + * truncated bit pattern). All inodes on the same device that have the same + * truncation pattern are mapped to the same new device. Two inodes that + * truncate to the same value clearly will always have different truncation + * bit patterns, so they will be split from away each other. When we spot + * device truncation we remap the device number to a non truncated value. + * (for more info see table.h for the data structures involved). + */ + +/* + * dev_start() + * create the device mapping table + * Return: + * 0 if successful, -1 otherwise + */ + +int +dev_start(void) +{ + if (dtab != NULL) + return 0; + if ((dtab = (DEVT **)calloc(D_TAB_SZ, sizeof(DEVT *))) == NULL) { + tty_warn(1, "Cannot allocate memory for device mapping table"); + return -1; + } + return 0; +} + +/* + * add_dev() + * add a device number to the table. this will force the device to be + * remapped to a new value if it be used during a write phase. This + * function is called during the read phase of an append to prohibit the + * use of any device number already in the archive. + * Return: + * 0 if added ok, -1 otherwise + */ + +int +add_dev(ARCHD *arcn) +{ + if (chk_dev(arcn->sb.st_dev, 1) == NULL) + return -1; + return 0; +} + +/* + * chk_dev() + * check for a device value in the device table. If not found and the add + * flag is set, it is added. This does NOT assign any mapping values, just + * adds the device number as one that need to be remapped. If this device + * is already mapped, just return with a pointer to that entry. + * Return: + * pointer to the entry for this device in the device map table. Null + * if the add flag is not set and the device is not in the table (it is + * not been seen yet). If add is set and the device cannot be added, null + * is returned (indicates an error). + */ + +static DEVT * +chk_dev(dev_t dev, int add) +{ + DEVT *pt; + u_int indx; + + if (dtab == NULL) + return NULL; + /* + * look to see if this device is already in the table + */ + indx = ((unsigned)dev) % D_TAB_SZ; + if ((pt = dtab[indx]) != NULL) { + while ((pt != NULL) && (pt->dev != dev)) + pt = pt->fow; + + /* + * found it, return a pointer to it + */ + if (pt != NULL) + return pt; + } + + /* + * not in table, we add it only if told to as this may just be a check + * to see if a device number is being used. + */ + if (add == 0) + return NULL; + + /* + * allocate a node for this device and add it to the front of the hash + * chain. Note we do not assign remaps values here, so the pt->list + * list must be NULL. + */ + if ((pt = (DEVT *)malloc(sizeof(DEVT))) == NULL) { + tty_warn(1, "Device map table out of memory"); + return NULL; + } + pt->dev = dev; + pt->list = NULL; + pt->fow = dtab[indx]; + dtab[indx] = pt; + return pt; +} +/* + * map_dev() + * given an inode and device storage mask (the mask has a 1 for each bit + * the archive format is able to store in a header), we check for inode + * and device truncation and remap the device as required. Device mapping + * can also occur when during the read phase of append a device number was + * seen (and was marked as do not use during the write phase). WE ASSUME + * that unsigned longs are the same size or bigger than the fields used + * for ino_t and dev_t. If not the types will have to be changed. + * Return: + * 0 if all ok, -1 otherwise. + */ + +int +map_dev(ARCHD *arcn, u_long dev_mask, u_long ino_mask) +{ + DEVT *pt; + DLIST *dpt; + static dev_t lastdev = 0; /* next device number to try */ + int trc_ino = 0; + int trc_dev = 0; + ino_t trunc_bits = 0; + ino_t nino; + + if (dtab == NULL) + return 0; + /* + * check for device and inode truncation, and extract the truncated + * bit pattern. + */ + if ((arcn->sb.st_dev & (dev_t)dev_mask) != arcn->sb.st_dev) + ++trc_dev; + if ((nino = arcn->sb.st_ino & (ino_t)ino_mask) != arcn->sb.st_ino) { + ++trc_ino; + trunc_bits = arcn->sb.st_ino & (ino_t)(~ino_mask); + } + + /* + * see if this device is already being mapped, look up the device + * then find the truncation bit pattern which applies + */ + if ((pt = chk_dev(arcn->sb.st_dev, 0)) != NULL) { + /* + * this device is already marked to be remapped + */ + for (dpt = pt->list; dpt != NULL; dpt = dpt->fow) + if (dpt->trunc_bits == trunc_bits) + break; + + if (dpt != NULL) { + /* + * we are being remapped for this device and pattern + * change the device number to be stored and return + */ + arcn->sb.st_dev = dpt->dev; + arcn->sb.st_ino = nino; + return 0; + } + } else { + /* + * this device is not being remapped YET. if we do not have any + * form of truncation, we do not need a remap + */ + if (!trc_ino && !trc_dev) + return 0; + + /* + * we have truncation, have to add this as a device to remap + */ + if ((pt = chk_dev(arcn->sb.st_dev, 1)) == NULL) + goto bad; + + /* + * if we just have a truncated inode, we have to make sure that + * all future inodes that do not truncate (they have the + * truncation pattern of all 0's) continue to map to the same + * device number. We probably have already written inodes with + * this device number to the archive with the truncation + * pattern of all 0's. So we add the mapping for all 0's to the + * same device number. + */ + if (!trc_dev && (trunc_bits != 0)) { + if ((dpt = (DLIST *)malloc(sizeof(DLIST))) == NULL) + goto bad; + dpt->trunc_bits = 0; + dpt->dev = arcn->sb.st_dev; + dpt->fow = pt->list; + pt->list = dpt; + } + } + + /* + * look for a device number not being used. We must watch for wrap + * around on lastdev (so we do not get stuck looking forever!) + */ + while (++lastdev > 0) { + if (chk_dev(lastdev, 0) != NULL) + continue; + /* + * found an unused value. If we have reached truncation point + * for this format we are hosed, so we give up. Otherwise we + * mark it as being used. + */ + if (((lastdev & ((dev_t)dev_mask)) != lastdev) || + (chk_dev(lastdev, 1) == NULL)) + goto bad; + break; + } + + if ((lastdev <= 0) || ((dpt = (DLIST *)malloc(sizeof(DLIST))) == NULL)) + goto bad; + + /* + * got a new device number, store it under this truncation pattern. + * change the device number this file is being stored with. + */ + dpt->trunc_bits = trunc_bits; + dpt->dev = lastdev; + dpt->fow = pt->list; + pt->list = dpt; + arcn->sb.st_dev = lastdev; + arcn->sb.st_ino = nino; + return 0; + + bad: + tty_warn(1, + "Unable to fix truncated inode/device field when storing %s", + arcn->name); + tty_warn(0, "Archive may create improper hard links when extracted"); + return 0; +} + +/* + * directory access/mod time reset table routines (for directories READ by pax) + * + * The pax -t flag requires that access times of archive files to be the same + * as before being read by pax. For regular files, access time is restored after + * the file has been copied. This database provides the same functionality for + * directories read during file tree traversal. Restoring directory access time + * is more complex than files since directories may be read several times until + * all the descendants in their subtree are visited by fts. Directory access + * and modification times are stored during the fts pre-order visit (done + * before any descendants in the subtree is visited) and restored after the + * fts post-order visit (after all the descendants have been visited). In the + * case of premature exit from a subtree (like from the effects of -n), any + * directory entries left in this database are reset during final cleanup + * operations of pax. Entries are hashed by inode number for fast lookup. + */ + +/* + * atdir_start() + * create the directory access time database for directories READ by pax. + * Return: + * 0 is created ok, -1 otherwise. + */ + +int +atdir_start(void) +{ + if (atab != NULL) + return 0; + if ((atab = (ATDIR **)calloc(A_TAB_SZ, sizeof(ATDIR *))) == NULL) { + tty_warn(1, + "Cannot allocate space for directory access time table"); + return -1; + } + return 0; +} + + +/* + * atdir_end() + * walk through the directory access time table and reset the access time + * of any directory who still has an entry left in the database. These + * entries are for directories READ by pax + */ + +void +atdir_end(void) +{ + ATDIR *pt; + int i; + + if (atab == NULL) + return; + /* + * for each non-empty hash table entry reset all the directories + * chained there. + */ + for (i = 0; i < A_TAB_SZ; ++i) { + if ((pt = atab[i]) == NULL) + continue; + /* + * remember to force the times, set_ftime() looks at pmtime + * and patime, which only applies to things CREATED by pax, + * not read by pax. Read time reset is controlled by -t. + */ + for (; pt != NULL; pt = pt->fow) + set_ftime(pt->name, pt->mtime, pt->atime, 1, 0); + } +} + +/* + * add_atdir() + * add a directory to the directory access time table. Table is hashed + * and chained by inode number. This is for directories READ by pax + */ + +void +add_atdir(char *fname, dev_t dev, ino_t ino, time_t mtime, time_t atime) +{ + ATDIR *pt; + u_int indx; + + if (atab == NULL) + return; + + /* + * make sure this directory is not already in the table, if so just + * return (the older entry always has the correct time). The only + * way this will happen is when the same subtree can be traversed by + * different args to pax and the -n option is aborting fts out of a + * subtree before all the post-order visits have been made. + */ + indx = ((unsigned)ino) % A_TAB_SZ; + if ((pt = atab[indx]) != NULL) { + while (pt != NULL) { + if ((pt->ino == ino) && (pt->dev == dev)) + break; + pt = pt->fow; + } + + /* + * oops, already there. Leave it alone. + */ + if (pt != NULL) + return; + } + + /* + * add it to the front of the hash chain + */ + if ((pt = (ATDIR *)malloc(sizeof(ATDIR))) != NULL) { + if ((pt->name = strdup(fname)) != NULL) { + pt->dev = dev; + pt->ino = ino; + pt->mtime = mtime; + pt->atime = atime; + pt->fow = atab[indx]; + atab[indx] = pt; + return; + } + (void)free((char *)pt); + } + + tty_warn(1, "Directory access time reset table ran out of memory"); + return; +} + +/* + * get_atdir() + * look up a directory by inode and device number to obtain the access + * and modification time you want to set to. If found, the modification + * and access time parameters are set and the entry is removed from the + * table (as it is no longer needed). These are for directories READ by + * pax + * Return: + * 0 if found, -1 if not found. + */ + +int +get_atdir(dev_t dev, ino_t ino, time_t *mtime, time_t *atime) +{ + ATDIR *pt; + ATDIR **ppt; + u_int indx; + + if (atab == NULL) + return -1; + /* + * hash by inode and search the chain for an inode and device match + */ + indx = ((unsigned)ino) % A_TAB_SZ; + if ((pt = atab[indx]) == NULL) + return -1; + + ppt = &(atab[indx]); + while (pt != NULL) { + if ((pt->ino == ino) && (pt->dev == dev)) + break; + /* + * no match, go to next one + */ + ppt = &(pt->fow); + pt = pt->fow; + } + + /* + * return if we did not find it. + */ + if (pt == NULL) + return -1; + + /* + * found it. return the times and remove the entry from the table. + */ + *ppt = pt->fow; + *mtime = pt->mtime; + *atime = pt->atime; + (void)free((char *)pt->name); + (void)free((char *)pt); + return 0; +} + +/* + * directory access mode and time storage routines (for directories CREATED + * by pax). + * + * Pax requires that extracted directories, by default, have their access/mod + * times and permissions set to the values specified in the archive. During the + * actions of extracting (and creating the destination subtree during -rw copy) + * directories extracted may be modified after being created. Even worse is + * that these directories may have been created with file permissions which + * prohibits any descendants of these directories from being extracted. When + * directories are created by pax, access rights may be added to permit the + * creation of files in their subtree. Every time pax creates a directory, the + * times and file permissions specified by the archive are stored. After all + * files have been extracted (or copied), these directories have their times + * and file modes reset to the stored values. The directory info is restored in + * reverse order as entries were added to the data file from root to leaf. To + * restore atime properly, we must go backwards. The data file consists of + * records with two parts, the file name followed by a DIRDATA trailer. The + * fixed sized trailer contains the size of the name plus the off_t location in + * the file. To restore we work backwards through the file reading the trailer + * then the file name. + */ + +#ifndef DIRS_USE_FILE +static DIRDATA *dirdata_head; +#endif + +/* + * dir_start() + * set up the directory time and file mode storage for directories CREATED + * by pax. + * Return: + * 0 if ok, -1 otherwise + */ + +int +dir_start(void) +{ +#ifdef DIRS_USE_FILE + if (dirfd != -1) + return 0; + + /* + * unlink the file so it goes away at termination by itself + */ + memcpy(tempbase, _TFILE_BASE, sizeof(_TFILE_BASE)); + if ((dirfd = mkstemp(tempfile)) >= 0) { + (void)unlink(tempfile); + return 0; + } + tty_warn(1, "Unable to create temporary file for directory times: %s", + tempfile); + return -1; +#else + return (0); +#endif /* DIRS_USE_FILE */ +} + +/* + * add_dir() + * add the mode and times for a newly CREATED directory + * name is name of the directory, psb the stat buffer with the data in it, + * frc_mode is a flag that says whether to force the setting of the mode + * (ignoring the user set values for preserving file mode). Frc_mode is + * for the case where we created a file and found that the resulting + * directory was not writable and the user asked for file modes to NOT + * be preserved. (we have to preserve what was created by default, so we + * have to force the setting at the end. this is stated explicitly in the + * pax spec) + */ + +void +add_dir(char *name, int nlen, struct stat *psb, int frc_mode) +{ +#ifdef DIRS_USE_FILE + DIRDATA dblk; +#else + DIRDATA *dblk; +#endif + char realname[MAXPATHLEN], *rp; + + if (havechd && *name != '/') { + if ((rp = realpath(name, realname)) == NULL) { + tty_warn(1, "Cannot canonicalize %s", name); + return; + } + name = rp; +#ifdef DIRS_USE_FILE + nlen = strlen(name); +#endif + } + +#ifdef DIRS_USE_FILE + if (dirfd < 0) + return; + + /* + * get current position (where file name will start) so we can store it + * in the trailer + */ + if ((dblk.npos = lseek(dirfd, 0L, SEEK_CUR)) < 0) { + tty_warn(1, + "Unable to store mode and times for directory: %s",name); + return; + } + + /* + * write the file name followed by the trailer + */ + dblk.nlen = nlen + 1; + dblk.mode = psb->st_mode & 0xffff; + dblk.mtime = psb->st_mtime; + dblk.atime = psb->st_atime; +#if HAVE_STRUCT_STAT_ST_FLAGS + dblk.fflags = psb->st_flags; +#else + dblk.fflags = 0; +#endif + dblk.frc_mode = frc_mode; + if ((xwrite(dirfd, name, dblk.nlen) == dblk.nlen) && + (xwrite(dirfd, (char *)&dblk, sizeof(dblk)) == sizeof(dblk))) { + ++dircnt; + return; + } + + tty_warn(1, + "Unable to store mode and times for created directory: %s",name); + return; +#else + + if ((dblk = malloc(sizeof(*dblk))) == NULL || + (dblk->name = strdup(name)) == NULL) { + tty_warn(1, + "Unable to store mode and times for directory: %s",name); + if (dblk != NULL) + free(dblk); + return; + } + + dblk->mode = psb->st_mode & 0xffff; + dblk->mtime = psb->st_mtime; + dblk->atime = psb->st_atime; +#if HAVE_STRUCT_STAT_ST_FLAGS + dblk->fflags = psb->st_flags; +#else + dblk->fflags = 0; +#endif + dblk->frc_mode = frc_mode; + + dblk->next = dirdata_head; + dirdata_head = dblk; + return; +#endif /* DIRS_USE_FILE */ +} + +/* + * proc_dir() + * process all file modes and times stored for directories CREATED + * by pax + */ + +void +proc_dir(void) +{ +#ifdef DIRS_USE_FILE + char name[PAXPATHLEN+1]; + DIRDATA dblk; + u_long cnt; + + if (dirfd < 0) + return; + /* + * read backwards through the file and process each directory + */ + for (cnt = 0; cnt < dircnt; ++cnt) { + /* + * read the trailer, then the file name, if this fails + * just give up. + */ + if (lseek(dirfd, -((off_t)sizeof(dblk)), SEEK_CUR) < 0) + break; + if (xread(dirfd,(char *)&dblk, sizeof(dblk)) != sizeof(dblk)) + break; + if (lseek(dirfd, dblk.npos, SEEK_SET) < 0) + break; + if (xread(dirfd, name, dblk.nlen) != dblk.nlen) + break; + if (lseek(dirfd, dblk.npos, SEEK_SET) < 0) + break; + + /* + * frc_mode set, make sure we set the file modes even if + * the user didn't ask for it (see file_subs.c for more info) + */ + if (pmode || dblk.frc_mode) + set_pmode(name, dblk.mode); + if (patime || pmtime) + set_ftime(name, dblk.mtime, dblk.atime, 0, 0); + if (pfflags) + set_chflags(name, dblk.fflags); + } + + (void)close(dirfd); + dirfd = -1; + if (cnt != dircnt) + tty_warn(1, + "Unable to set mode and times for created directories"); + return; +#else + DIRDATA *dblk; + + for (dblk = dirdata_head; dblk != NULL; dblk = dirdata_head) { + dirdata_head = dblk->next; + + /* + * frc_mode set, make sure we set the file modes even if + * the user didn't ask for it (see file_subs.c for more info) + */ + if (pmode || dblk->frc_mode) + set_pmode(dblk->name, dblk->mode); + if (patime || pmtime) + set_ftime(dblk->name, dblk->mtime, dblk->atime, 0, 0); + if (pfflags) + set_chflags(dblk->name, dblk->fflags); + + free(dblk->name); + free(dblk); + } +#endif /* DIRS_USE_FILE */ +} + +/* + * database independent routines + */ + +/* + * st_hash() + * hashes filenames to a u_int for hashing into a table. Looks at the tail + * end of file, as this provides far better distribution than any other + * part of the name. For performance reasons we only care about the last + * MAXKEYLEN chars (should be at LEAST large enough to pick off the file + * name). Was tested on 500,000 name file tree traversal from the root + * and gave almost a perfectly uniform distribution of keys when used with + * prime sized tables (MAXKEYLEN was 128 in test). Hashes (sizeof int) + * chars at a time and pads with 0 for last addition. + * Return: + * the hash value of the string MOD (%) the table size. + */ + +u_int +st_hash(char *name, int len, int tabsz) +{ + char *pt; + char *dest; + char *end; + int i; + u_int key = 0; + int steps; + int res; + u_int val; + + /* + * only look at the tail up to MAXKEYLEN, we do not need to waste + * time here (remember these are pathnames, the tail is what will + * spread out the keys) + */ + if (len > MAXKEYLEN) { + pt = &(name[len - MAXKEYLEN]); + len = MAXKEYLEN; + } else + pt = name; + + /* + * calculate the number of u_int size steps in the string and if + * there is a runt to deal with + */ + steps = len/sizeof(u_int); + res = len % sizeof(u_int); + + /* + * add up the value of the string in unsigned integer sized pieces + * too bad we cannot have unsigned int aligned strings, then we + * could avoid the expensive copy. + */ + for (i = 0; i < steps; ++i) { + end = pt + sizeof(u_int); + dest = (char *)&val; + while (pt < end) + *dest++ = *pt++; + key += val; + } + + /* + * add in the runt padded with zero to the right + */ + if (res) { + val = 0; + end = pt + res; + dest = (char *)&val; + while (pt < end) + *dest++ = *pt++; + key += val; + } + + /* + * return the result mod the table size + */ + return key % tabsz; +} diff --git a/bin/pax/tables.h b/bin/pax/tables.h new file mode 100644 index 0000000..1038589 --- /dev/null +++ b/bin/pax/tables.h @@ -0,0 +1,176 @@ +/* $NetBSD: tables.h,v 1.10 2007/04/29 20:23:34 msaitoh Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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. + * + * @(#)tables.h 8.1 (Berkeley) 5/31/93 + */ + +/* + * data structures and constants used by the different databases kept by pax + */ + +/* + * Hash Table Sizes MUST BE PRIME, if set too small performance suffers. + * Probably safe to expect 500000 inodes per tape. Assuming good key + * distribution (inodes) chains of under 50 long (worse case) is ok. + */ +#define L_TAB_SZ 2503 /* hard link hash table size */ +#define F_TAB_SZ 50503 /* file time hash table size */ +#define N_TAB_SZ 541 /* interactive rename hash table */ +#define D_TAB_SZ 317 /* unique device mapping table */ +#define A_TAB_SZ 317 /* ftree dir access time reset table */ +#define MAXKEYLEN 64 /* max number of chars for hash */ + +/* + * file hard link structure (hashed by dev/ino and chained) used to find the + * hard links in a file system or with some archive formats (cpio) + */ +typedef struct hrdlnk { + char *name; /* name of first file seen with this ino/dev */ + dev_t dev; /* files device number */ + ino_t ino; /* files inode number */ + u_long nlink; /* expected link count */ + struct hrdlnk *fow; +} HRDLNK; + +/* + * Archive write update file time table (the -u, -C flag), hashed by filename. + * Filenames are stored in a scratch file at seek offset into the file. The + * file time (mod time) and the file name length (for a quick check) are + * stored in a hash table node. We were forced to use a scratch file because + * with -u, the mtime for every node in the archive must always be available + * to compare against (and this data can get REALLY large with big archives). + * By being careful to read only when we have a good chance of a match, the + * performance loss is not measurable (and the size of the archive we can + * handle is greatly increased). + */ +typedef struct ftm { + int namelen; /* file name length */ + time_t mtime; /* files last modification time */ + off_t seek; /* location in scratch file */ + struct ftm *fow; +} FTM; + +/* + * Interactive rename table (-i flag), hashed by orig filename. + * We assume this will not be a large table as this mapping data can only be + * obtained through interactive input by the user. Nobody is going to type in + * changes for 500000 files? We use chaining to resolve collisions. + */ + +typedef struct namt { + char *oname; /* old name */ + char *nname; /* new name typed in by the user */ + struct namt *fow; +} NAMT; + +/* + * Unique device mapping tables. Some protocols (e.g. cpio) require that the + * <c_dev,c_ino> pair will uniquely identify a file in an archive unless they + * are links to the same file. Appending to archives can break this. For those + * protocols that have this requirement we map c_dev to a unique value not seen + * in the archive when we append. We also try to handle inode truncation with + * this table. (When the inode field in the archive header are too small, we + * remap the dev on writes to remove accidental collisions). + * + * The list is hashed by device number using chain collision resolution. Off of + * each DEVT are linked the various remaps for this device based on those bits + * in the inode which were truncated. For example if we are just remapping to + * avoid a device number during an update append, off the DEVT we would have + * only a single DLIST that has a truncation id of 0 (no inode bits were + * stripped for this device so far). When we spot inode truncation we create + * a new mapping based on the set of bits in the inode which were stripped off. + * so if the top four bits of the inode are stripped and they have a pattern of + * 0110...... (where . are those bits not truncated) we would have a mapping + * assigned for all inodes that has the same 0110.... pattern (with this dev + * number of course). This keeps the mapping sparse and should be able to store + * close to the limit of files which can be represented by the optimal + * combination of dev and inode bits, and without creating a fouled up archive. + * Note we also remap truncated devs in the same way (an exercise for the + * dedicated reader; always wanted to say that...:) + */ + +typedef struct devt { + dev_t dev; /* the orig device number we now have to map */ + struct devt *fow; /* new device map list */ + struct dlist *list; /* map list based on inode truncation bits */ +} DEVT; + +typedef struct dlist { + ino_t trunc_bits; /* truncation pattern for a specific map */ + dev_t dev; /* the new device id we use */ + struct dlist *fow; +} DLIST; + +/* + * ftree directory access time reset table. When we are done with a + * subtree we reset the access and mod time of the directory when the tflag is + * set. Not really explicitly specified in the pax spec, but easy and fast to + * do (and this may have even been intended in the spec, it is not clear). + * table is hashed by inode with chaining. + */ + +typedef struct atdir { + char *name; /* name of directory to reset */ + dev_t dev; /* dev and inode for fast lookup */ + ino_t ino; + time_t mtime; /* access and mod time to reset to */ + time_t atime; + struct atdir *fow; +} ATDIR; + +/* + * created directory time and mode storage entry. After pax is finished during + * extraction or copy, we must reset directory access modes and times that + * may have been modified after creation (they no longer have the specified + * times and/or modes). We must reset time in the reverse order of creation, + * because entries are added from the top of the file tree to the bottom. + * We MUST reset times from leaf to root (it will not work the other + * direction). Entries are recorded into a spool file to make reverse + * reading faster. + */ + +typedef struct dirdata { +#ifdef DIRS_USE_FILE + int nlen; /* length of the directory name (includes \0) */ + off_t npos; /* position in file where this dir name starts */ +#else + char *name; /* file name */ + struct dirdata *next; +#endif + mode_t mode; /* file mode to restore */ + time_t mtime; /* mtime to set */ + time_t atime; /* atime to set */ + long fflags; /* file flags to set */ + int frc_mode; /* do we force mode settings? */ +} DIRDATA; diff --git a/bin/pax/tar.1 b/bin/pax/tar.1 new file mode 100644 index 0000000..f98a138 --- /dev/null +++ b/bin/pax/tar.1 @@ -0,0 +1,372 @@ +.\" $NetBSD: tar.1,v 1.37 2017/07/03 21:33:23 wiz Exp $ +.\" +.\" Copyright (c) 1996 SigmaSoft, Th. Lockert +.\" 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. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" OpenBSD: tar.1,v 1.28 2000/11/09 23:58:56 aaron Exp +.\" +.Dd December 19, 2015 +.Dt TAR 1 +.Os +.Sh NAME +.Nm tar +.Nd tape archiver +.Sh SYNOPSIS +.Nm tar +.Sm off +.Oo \&- Oc {crtux} Op Fl 014578befHhJjklmOoPpqSvwXZz +.Sm on +.Op Ar archive +.Op Ar blocksize +.\" XXX how to do this right? +.Op Fl C Ar directory +.Op Fl s Ar replstr +.Op Fl T Ar file +.Op Ar file ... +.Sh DESCRIPTION +The +.Nm +command creates, adds files to, or extracts files from an +archive file in +.Dq tar +format. +A tar archive is often stored on a magnetic tape, but can be +stored equally well on a floppy, CD-ROM, or in a regular disk file. +.Pp +One of the following flags must be present: +.Bl -tag -width Ar +.It Fl c , Fl Fl create +Create new archive, or overwrite an existing archive, +adding the specified files to it. +.It Fl r , Fl Fl append +Append the named new files to existing archive. +Note that this will only work on media on which an end-of-file mark +can be overwritten. +.It Fl t , Fl Fl list +List contents of archive. +If any files are named on the +command line, only those files will be listed. +.It Fl u , Fl Fl update +Alias for +.Fl r . +.It Fl x , Fl Fl extract , Fl Fl get +Extract files from archive. +If any files are named on the +command line, only those files will be extracted from the +archive. +If more than one copy of a file exists in the +archive, later copies will overwrite earlier copies during +extraction. +The file mode and modification time are preserved +if possible. +The file mode is subject to modification by the +.Xr umask 2 . +.El +.Pp +In addition to the flags mentioned above, any of the following +flags may be used: +.Bl -tag -width Ar +.It Fl b Ar "blocking factor" , Fl Fl block-size Ar "blocking factor" +Set blocking factor to use for the archive. +.Nm +uses 512 byte blocks. +The default is 20, the maximum is 126. +Archives with a blocking factor larger 63 violate the +.Tn POSIX +standard and will not be portable to all systems. +.It Fl e +Stop after first error. +.It Fl f Ar archive , Fl Fl file Ar archive +Filename where the archive is stored. +Defaults to +.Pa /dev/rst0 . +If the archive is of the form: +.Ar [[user@]host:]file +then the archive will be processed using +.Xr rmt 8 . +.It Fl h , Fl Fl dereference +Follow symbolic links as if they were normal files +or directories. +.It Fl J, Fl Fl xz +Compress/decompress archive using +.Xr xz 1 . +.It Fl j, Fl Fl bzip2, Fl Fl bunzip2 +Use +.Xr bzip2 1 +for compression of the archive. +This option is a GNU extension. +.It Fl k , Fl Fl keep-old-files +Keep existing files; don't overwrite them from archive. +.It Fl l , Fl Fl one-file-system +Do not descend across mount points. +.\" should be '-X' +.It Fl m , Fl Fl modification-time +Do not preserve modification time. +.It Fl O +When creating and appending to an archive, write old-style (non-POSIX) archives. +When extracting from an archive, extract to standard output. +.It Fl o , Fl Fl portability , Fl Fl old-archive +Don't write directory information that the older (V7) style +.Nm +is unable to decode. +This implies the +.Fl O +flag. +.It Fl p , Fl Fl preserve-permissions , Fl Fl preserve +Preserve user and group ID as well as file mode regardless of +the current +.Xr umask 2 . +The setuid and setgid bits are only preserved if the user is +the superuser. +Only meaningful in conjunction with the +.Fl x +flag. +.It Fl q , Fl Fl fast-read +Select the first archive member that matches each +.Ar pattern +operand. +No more than one archive member is matched for each +.Ar pattern . +When members of type directory are matched, the file hierarchy rooted at that +directory is also matched. +.It Fl S , Fl Fl sparse +This flag has no effect as +.Nm +always generates sparse files. +.It Fl s Ar replstr +Modify the file or archive member names specified by the +.Ar pattern +or +.Ar file +operands according to the substitution expression +.Ar replstr , +using the syntax of the +.Xr ed 1 +utility regular expressions. +The format of these regular expressions are: +.Dl /old/new/[gps] +As in +.Xr ed 1 , +.Cm old +is a basic regular expression and +.Cm new +can contain an ampersand (&), \en (where n is a digit) back-references, +or subexpression matching. +The +.Cm old +string may also contain +.Aq Dv newline +characters. +Any non-null character can be used as a delimiter (/ is shown here). +Multiple +.Fl s +expressions can be specified. +The expressions are applied in the order they are specified on the +command line, terminating with the first successful substitution. +The optional trailing +.Cm g +continues to apply the substitution expression to the pathname substring +which starts with the first character following the end of the last successful +substitution. +The first unsuccessful substitution stops the operation of the +.Cm g +option. +The optional trailing +.Cm p +will cause the final result of a successful substitution to be written to +.Dv standard error +in the following format: +.Dl <original pathname> >> <new pathname> +File or archive member names that substitute to the empty string +are not selected and will be skipped. +The substitutions are applied by default to the destination hard and symbolic +links. +The optional trailing +.Cm s +prevents the substitutions from being performed on symbolic link destinations. +.It Fl v +Verbose operation mode. +.It Fl w , Fl Fl interactive , Fl Fl confirmation +Interactively rename files. +This option causes +.Nm +to prompt the user for the filename to use when storing or +extracting files in an archive. +.It Fl z , Fl Fl gzip , Fl Fl gunzip +Compress/decompress archive using +.Xr gzip 1 . +.It Fl B , Fl Fl read-full-blocks +Reassemble small reads into full blocks (For reading from 4.2BSD pipes). +.It Fl C Ar directory , Fl Fl directory Ar directory +This is a positional argument which sets the working directory for the +following files. +When extracting, files will be extracted into +the specified directory; when creating, the specified files will be matched +from the directory. +This argument and its parameter may also appear in a file list specified by +.Fl T . +.It Fl H +Only follow symlinks given on command line. +.Pp +Note SysVr3/i386 picked up ISC/SCO UNIX compatibility which implemented +.Dq Fl F Ar file +which was defined as obtaining a list of command line switches and files +on which to operate from the specified file, +but SunOS-5 uses +.Dq Fl I Ar file +because they use +.Sq Fl F +to mean something else. +We might someday provide SunOS-5 compatibility +but it makes little sense to confuse things with ISC/SCO compatibility. +.\".It Fl L +.\"Do not follow any symlinks (do the opposite of +.\".Fl h ). +.It Fl P , Fl Fl absolute-paths +Do not strip leading slashes +.Pq Sq / +from pathnames. +The default is to strip leading slashes. +.It Fl T Ar file , Fl Fl files-from Ar file +Read the names of files to archive or extract from the given file, one +per line. +A line may also specify the positional argument +.Dq Fl C Ar directory . +.It Fl X Ar file , Fl Fl exclude-from Ar file +Exclude files matching the shell glob patterns listed in the given file. +.\" exclude should be '-E' and '-X' should be one-file-system +.Pp +Note that it would be more standard to use this option to mean ``do not +cross filesystem mount points.'' +.It Fl Z , Fl Fl compress , Fl Fl uncompress +Compress archive using compress. +.It Fl Fl strict +Do not enable GNU tar extensions such as long filenames and long link names. +.It Fl Fl atime-preserve +Preserve file access times. +.It Fl Fl chroot +.Fn chroot +to the current directory before extracting files. +Use with +.Fl x +and +.Fl h +to make absolute symlinks relative to the current directory. +.It Fl Fl unlink +Ignored, only accepted for compatibility with other +.Nm +implementations. +.Nm +always unlinks files before creating them. +.It Fl Fl use-compress-program Ar program +Use the named program as the program to decompress the input. +.It Fl Fl force-local +Do not interpret filenames that contain a +.Sq \&: +as remote files. +.It Fl Fl insecure +Normally +.Nm +ignores filenames that contain +.Dq .. +as a path component. +With this option, files that contain +.Dq .. +can be processed. +.It Fl Fl no-recursion +Cause files of type directory being copied or archived, or archive members of +type directory being extracted, to match only the directory file or archive +member and not the file hierarchy rooted at the directory. +.It Fl Fl timestamp Ar timestamp +Store all modification times in the archive with the +.Ar timestamp +given instead of the actual modification time of the individual archive member +so that repeatable builds are possible. +The +.Ar timestamp +can be a +.Pa pathname , +where the timestamps are derived from that file, a parseable date for +.Xr parsedate 3 +(this option is not yet available in the tools build), or an integer value +interpreted as the number of seconds from the Epoch. +.El +.Pp +The options +.Op Fl 014578 +can be used to select one of the compiled-in backup devices, +.Pa /dev/rstN . +.Sh FILES +.Bl -tag -width "/dev/rst0" +.It Pa /dev/rst0 +default archive name +.El +.Sh DIAGNOSTICS +.Nm +will exit with one of the following values: +.Bl -tag -width 2n +.It 0 +All files were processed successfully. +.It 1 +An error occurred. +.El +.Pp +Whenever +.Nm +cannot create a file or a link when extracting an archive or cannot +find a file while writing an archive, or cannot preserve the user +ID, group ID, file mode, or access and modification times when the +.Fl p +option is specified, a diagnostic message is written to standard +error and a non-zero exit value will be returned, but processing +will continue. +In the case where +.Nm +cannot create a link to a file, +.Nm +will not create a second copy of the file. +.Pp +If the extraction of a file from an archive is prematurely terminated +by a signal or error, +.Nm +may have only partially extracted the file the user wanted. +Additionally, the file modes of extracted files and directories may +have incorrect file bits, and the modification and access times may +be wrong. +.Pp +If the creation of an archive is prematurely terminated by a signal +or error, +.Nm +may have only partially created the archive which may violate the +specific archive format specification. +.Sh SEE ALSO +.Xr cpio 1 , +.Xr pax 1 +.Sh HISTORY +A +.Nm +command first appeared in +.At v7 . +.Sh AUTHORS +.An Keith Muller +at the University of California, San Diego. diff --git a/bin/pax/tar.c b/bin/pax/tar.c new file mode 100644 index 0000000..ded2ad4 --- /dev/null +++ b/bin/pax/tar.c @@ -0,0 +1,1430 @@ +/* $NetBSD: tar.c,v 1.74 2018/11/30 00:53:11 christos Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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 HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +#if 0 +static char sccsid[] = "@(#)tar.c 8.2 (Berkeley) 4/18/94"; +#else +__RCSID("$NetBSD: tar.c,v 1.74 2018/11/30 00:53:11 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/param.h> + +#include <ctype.h> +#include <errno.h> +#include <grp.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "pax.h" +#include "extern.h" +#include "tar.h" + +extern struct stat tst; + +/* + * Routines for reading, writing and header identify of various versions of tar + */ + +static int expandname(char *, size_t, char **, size_t *, const char *, size_t); +static void longlink(ARCHD *, int); +static uint32_t tar_chksm(char *, int); +static char *name_split(char *, int); +static int u32_oct(uintmax_t, char *, int, int); +static int umax_oct(uintmax_t, char *, int, int); +static int tar_gnutar_exclude_one(const char *, size_t); +static int check_sum(char *, size_t, char *, size_t, int); + +/* + * Routines common to all versions of tar + */ + +static int tar_nodir; /* do not write dirs under old tar */ +int is_gnutar; /* behave like gnu tar; enable gnu + * extensions and skip end-of-volume + * checks + */ +static int seen_gnu_warning; /* Have we warned yet? */ +static char *gnu_hack_string; /* ././@LongLink hackery */ +static int gnu_hack_len; /* len of gnu_hack_string */ +char *gnu_name_string; /* ././@LongLink hackery name */ +char *gnu_link_string; /* ././@LongLink hackery link */ +size_t gnu_name_length; /* ././@LongLink hackery name */ +size_t gnu_link_length; /* ././@LongLink hackery link */ +static int gnu_short_trailer; /* gnu short trailer */ + +static const char LONG_LINK[] = "././@LongLink"; + +#ifdef _PAX_ +char DEV_0[] = "/dev/rst0"; +char DEV_1[] = "/dev/rst1"; +char DEV_4[] = "/dev/rst4"; +char DEV_5[] = "/dev/rst5"; +char DEV_7[] = "/dev/rst7"; +char DEV_8[] = "/dev/rst8"; +#endif + +static int +check_sum(char *hd, size_t hdlen, char *bl, size_t bllen, int quiet) +{ + uint32_t hdck, blck; + + hdck = asc_u32(hd, hdlen, OCT); + blck = tar_chksm(bl, bllen); + + if (hdck != blck) { + if (!quiet) + tty_warn(0, "Header checksum %o does not match %o", + hdck, blck); + return -1; + } + return 0; +} + + +/* + * tar_endwr() + * add the tar trailer of two null blocks + * Return: + * 0 if ok, -1 otherwise (what wr_skip returns) + */ + +int +tar_endwr(void) +{ + return wr_skip((off_t)(NULLCNT * BLKMULT)); +} + +/* + * tar_endrd() + * no cleanup needed here, just return size of trailer (for append) + * Return: + * size of trailer BLKMULT + */ + +off_t +tar_endrd(void) +{ + return (off_t)((gnu_short_trailer ? 1 : NULLCNT) * BLKMULT); +} + +/* + * tar_trail() + * Called to determine if a header block is a valid trailer. We are passed + * the block, the in_sync flag (which tells us we are in resync mode; + * looking for a valid header), and cnt (which starts at zero) which is + * used to count the number of empty blocks we have seen so far. + * Return: + * 0 if a valid trailer, -1 if not a valid trailer, or 1 if the block + * could never contain a header. + */ + +int +tar_trail(char *buf, int in_resync, int *cnt) +{ + int i; + + gnu_short_trailer = 0; + /* + * look for all zero, trailer is two consecutive blocks of zero + */ + for (i = 0; i < BLKMULT; ++i) { + if (buf[i] != '\0') + break; + } + + /* + * if not all zero it is not a trailer, but MIGHT be a header. + */ + if (i != BLKMULT) + return -1; + + /* + * When given a zero block, we must be careful! + * If we are not in resync mode, check for the trailer. Have to watch + * out that we do not mis-identify file data as the trailer, so we do + * NOT try to id a trailer during resync mode. During resync mode we + * might as well throw this block out since a valid header can NEVER be + * a block of all 0 (we must have a valid file name). + */ + if (!in_resync) { + ++*cnt; + /* + * old GNU tar (up through 1.13) only writes one block of + * trailers, so we pretend we got another + */ + if (is_gnutar) { + gnu_short_trailer = 1; + ++*cnt; + } + if (*cnt >= NULLCNT) + return 0; + } + return 1; +} + +/* + * u32_oct() + * convert an uintmax_t to an octal string. many oddball field + * termination characters are used by the various versions of tar in the + * different fields. term selects which kind to use. str is '0' padded + * at the front to len. we are unable to use only one format as many old + * tar readers are very cranky about this. + * Return: + * 0 if the number fit into the string, -1 otherwise + */ + +static int +u32_oct(uintmax_t val, char *str, int len, int term) +{ + char *pt; + uint64_t p; + + p = val & TOP_HALF; + if (p && p != TOP_HALF) + return -1; + + val &= BOTTOM_HALF; + + /* + * term selects the appropriate character(s) for the end of the string + */ + pt = str + len - 1; + switch(term) { + case 3: + *pt-- = '\0'; + break; + case 2: + *pt-- = ' '; + *pt-- = '\0'; + break; + case 1: + *pt-- = ' '; + break; + case 0: + default: + *pt-- = '\0'; + *pt-- = ' '; + break; + } + + /* + * convert and blank pad if there is space + */ + while (pt >= str) { + *pt-- = '0' + (char)(val & 0x7); + if ((val = val >> 3) == 0) + break; + } + + while (pt >= str) + *pt-- = '0'; + if (val != 0) + return -1; + return 0; +} + +/* + * umax_oct() + * convert an unsigned long long to an octal string. one of many oddball + * field termination characters are used by the various versions of tar + * in the different fields. term selects which kind to use. str is '0' + * padded at the front to len. we are unable to use only one format as + * many old tar readers are very cranky about this. + * Return: + * 0 if the number fit into the string, -1 otherwise + */ + +static int +umax_oct(uintmax_t val, char *str, int len, int term) +{ + char *pt; + + /* + * term selects the appropriate character(s) for the end of the string + */ + pt = str + len - 1; + switch(term) { + case 3: + *pt-- = '\0'; + break; + case 2: + *pt-- = ' '; + *pt-- = '\0'; + break; + case 1: + *pt-- = ' '; + break; + case 0: + default: + *pt-- = '\0'; + *pt-- = ' '; + break; + } + + /* + * convert and blank pad if there is space + */ + while (pt >= str) { + *pt-- = '0' + (char)(val & 0x7); + if ((val = val >> 3) == 0) + break; + } + + while (pt >= str) + *pt-- = '0'; + if (val != 0) + return -1; + return 0; +} + +/* + * tar_chksm() + * calculate the checksum for a tar block counting the checksum field as + * all blanks (BLNKSUM is that value pre-calculated, the sum of 8 blanks). + * NOTE: we use len to short circuit summing 0's on write since we ALWAYS + * pad headers with 0. + * Return: + * unsigned long checksum + */ + +static uint32_t +tar_chksm(char *blk, int len) +{ + char *stop; + char *pt; + uint32_t chksm = BLNKSUM; /* initial value is checksum field sum */ + + /* + * add the part of the block before the checksum field + */ + pt = blk; + stop = blk + CHK_OFFSET; + while (pt < stop) + chksm += (uint32_t)(*pt++ & 0xff); + /* + * move past the checksum field and keep going, spec counts the + * checksum field as the sum of 8 blanks (which is pre-computed as + * BLNKSUM). + * ASSUMED: len is greater than CHK_OFFSET. (len is where our 0 padding + * starts, no point in summing zero's) + */ + pt += CHK_LEN; + stop = blk + len; + while (pt < stop) + chksm += (uint32_t)(*pt++ & 0xff); + return chksm; +} + +/* + * Routines for old BSD style tar (also made portable to sysV tar) + */ + +/* + * tar_id() + * determine if a block given to us is a valid tar header (and not a USTAR + * header). We have to be on the lookout for those pesky blocks of all + * zero's. + * Return: + * 0 if a tar header, -1 otherwise + */ + +int +tar_id(char *blk, int size) +{ + HD_TAR *hd; + HD_USTAR *uhd; + static int is_ustar = -1; + + if (size < BLKMULT) + return -1; + hd = (HD_TAR *)blk; + uhd = (HD_USTAR *)blk; + + /* + * check for block of zero's first, a simple and fast test, then make + * sure this is not a ustar header by looking for the ustar magic + * cookie. We should use TMAGLEN, but some USTAR archive programs are + * wrong and create archives missing the \0. Last we check the + * checksum. If this is ok we have to assume it is a valid header. + */ + if (hd->name[0] == '\0') + return -1; + if (strncmp(uhd->magic, TMAGIC, TMAGLEN - 1) == 0) { + if (is_ustar == -1) { + is_ustar = 1; + return -1; + } else + tty_warn(0, + "Busted tar archive: has both ustar and old tar " + "records"); + } else + is_ustar = 0; + return check_sum(hd->chksum, sizeof(hd->chksum), blk, BLKMULT, 1); +} + +/* + * tar_opt() + * handle tar format specific -o options + * Return: + * 0 if ok -1 otherwise + */ + +int +tar_opt(void) +{ + OPLIST *opt; + + while ((opt = opt_next()) != NULL) { + if (strcmp(opt->name, TAR_OPTION) || + strcmp(opt->value, TAR_NODIR)) { + tty_warn(1, + "Unknown tar format -o option/value pair %s=%s", + opt->name, opt->value); + tty_warn(1, + "%s=%s is the only supported tar format option", + TAR_OPTION, TAR_NODIR); + return -1; + } + + /* + * we only support one option, and only when writing + */ + if ((act != APPND) && (act != ARCHIVE)) { + tty_warn(1, "%s=%s is only supported when writing.", + opt->name, opt->value); + return -1; + } + tar_nodir = 1; + } + return 0; +} + + +/* + * tar_rd() + * extract the values out of block already determined to be a tar header. + * store the values in the ARCHD parameter. + * Return: + * 0 + */ + +int +tar_rd(ARCHD *arcn, char *buf) +{ + HD_TAR *hd; + char *pt; + + /* + * we only get proper sized buffers passed to us + */ + if (tar_id(buf, BLKMULT) < 0) + return -1; + memset(arcn, 0, sizeof(*arcn)); + arcn->org_name = arcn->name; + arcn->pat = NULL; + arcn->sb.st_nlink = 1; + + /* + * copy out the name and values in the stat buffer + */ + hd = (HD_TAR *)buf; + if (hd->linkflag != LONGLINKTYPE && hd->linkflag != LONGNAMETYPE) { + arcn->nlen = expandname(arcn->name, sizeof(arcn->name), + &gnu_name_string, &gnu_name_length, hd->name, + sizeof(hd->name)); + arcn->ln_nlen = expandname(arcn->ln_name, sizeof(arcn->ln_name), + &gnu_link_string, &gnu_link_length, hd->linkname, + sizeof(hd->linkname)); + } + arcn->sb.st_mode = (mode_t)(asc_u32(hd->mode,sizeof(hd->mode),OCT) & + 0xfff); + arcn->sb.st_uid = (uid_t)asc_u32(hd->uid, sizeof(hd->uid), OCT); + arcn->sb.st_gid = (gid_t)asc_u32(hd->gid, sizeof(hd->gid), OCT); + arcn->sb.st_size = (off_t)ASC_OFFT(hd->size, sizeof(hd->size), OCT); + if (arcn->sb.st_size == -1) + return -1; + arcn->sb.st_mtime = (time_t)(int32_t)asc_u32(hd->mtime, sizeof(hd->mtime), OCT); + arcn->sb.st_ctime = arcn->sb.st_atime = arcn->sb.st_mtime; + + /* + * have to look at the last character, it may be a '/' and that is used + * to encode this as a directory + */ + pt = &(arcn->name[arcn->nlen - 1]); + arcn->pad = 0; + arcn->skip = 0; + switch(hd->linkflag) { + case SYMTYPE: + /* + * symbolic link, need to get the link name and set the type in + * the st_mode so -v printing will look correct. + */ + arcn->type = PAX_SLK; + arcn->sb.st_mode |= S_IFLNK; + break; + case LNKTYPE: + /* + * hard link, need to get the link name, set the type in the + * st_mode and st_nlink so -v printing will look better. + */ + arcn->type = PAX_HLK; + arcn->sb.st_nlink = 2; + + /* + * no idea of what type this thing really points at, but + * we set something for printing only. + */ + arcn->sb.st_mode |= S_IFREG; + break; + case LONGLINKTYPE: + case LONGNAMETYPE: + /* + * GNU long link/file; we tag these here and let the + * pax internals deal with it -- too ugly otherwise. + */ + if (hd->linkflag != LONGLINKTYPE) + arcn->type = PAX_GLF; + else + arcn->type = PAX_GLL; + arcn->pad = TAR_PAD(arcn->sb.st_size); + arcn->skip = arcn->sb.st_size; + break; + case AREGTYPE: + case REGTYPE: + case DIRTYPE: /* see below */ + default: + /* + * If we have a trailing / this is a directory and NOT a file. + * Note: V7 tar doesn't actually have DIRTYPE, but it was + * reported that V7 archives using USTAR directories do exist. + */ + if (*pt == '/' || hd->linkflag == DIRTYPE) { + /* + * it is a directory, set the mode for -v printing + */ + arcn->type = PAX_DIR; + arcn->sb.st_mode |= S_IFDIR; + arcn->sb.st_nlink = 2; + } else { + /* + * have a file that will be followed by data. Set the + * skip value to the size field and calculate the size + * of the padding. + */ + arcn->type = PAX_REG; + arcn->sb.st_mode |= S_IFREG; + arcn->pad = TAR_PAD(arcn->sb.st_size); + arcn->skip = arcn->sb.st_size; + } + break; + } + + /* + * strip off any trailing slash. + */ + if (*pt == '/') { + *pt = '\0'; + --arcn->nlen; + } + return 0; +} + +/* + * tar_wr() + * write a tar header for the file specified in the ARCHD to the archive. + * Have to check for file types that cannot be stored and file names that + * are too long. Be careful of the term (last arg) to u32_oct, each field + * of tar has it own spec for the termination character(s). + * ASSUMED: space after header in header block is zero filled + * Return: + * 0 if file has data to be written after the header, 1 if file has NO + * data to write after the header, -1 if archive write failed + */ + +int +tar_wr(ARCHD *arcn) +{ + HD_TAR *hd; + int len; + uintmax_t mtime; + char hdblk[sizeof(HD_TAR)]; + + /* + * check for those file system types which tar cannot store + */ + switch(arcn->type) { + case PAX_DIR: + /* + * user asked that dirs not be written to the archive + */ + if (tar_nodir) + return 1; + break; + case PAX_CHR: + tty_warn(1, "Tar cannot archive a character device %s", + arcn->org_name); + return 1; + case PAX_BLK: + tty_warn(1, + "Tar cannot archive a block device %s", arcn->org_name); + return 1; + case PAX_SCK: + tty_warn(1, "Tar cannot archive a socket %s", arcn->org_name); + return 1; + case PAX_FIF: + tty_warn(1, "Tar cannot archive a fifo %s", arcn->org_name); + return 1; + case PAX_SLK: + case PAX_HLK: + case PAX_HRG: + if (arcn->ln_nlen > (int)sizeof(hd->linkname)) { + tty_warn(1,"Link name too long for tar %s", + arcn->ln_name); + return 1; + } + break; + case PAX_REG: + case PAX_CTG: + default: + break; + } + + /* + * check file name len, remember extra char for dirs (the / at the end) + */ + len = arcn->nlen; + if (arcn->type == PAX_DIR) + ++len; + if (len >= (int)sizeof(hd->name)) { + tty_warn(1, "File name too long for tar %s", arcn->name); + return 1; + } + + /* + * copy the data out of the ARCHD into the tar header based on the type + * of the file. Remember many tar readers want the unused fields to be + * padded with zero. We set the linkflag field (type), the linkname + * (or zero if not used),the size, and set the padding (if any) to be + * added after the file data (0 for all other types, as they only have + * a header) + */ + memset(hdblk, 0, sizeof(hdblk)); + hd = (HD_TAR *)hdblk; + strlcpy(hd->name, arcn->name, sizeof(hd->name)); + arcn->pad = 0; + + if (arcn->type == PAX_DIR) { + /* + * directories are the same as files, except have a filename + * that ends with a /, we add the slash here. No data follows, + * dirs, so no pad. + */ + hd->linkflag = AREGTYPE; + hd->name[len-1] = '/'; + if (u32_oct((uintmax_t)0L, hd->size, sizeof(hd->size), 1)) + goto out; + } else if (arcn->type == PAX_SLK) { + /* + * no data follows this file, so no pad + */ + hd->linkflag = SYMTYPE; + strlcpy(hd->linkname, arcn->ln_name, sizeof(hd->linkname)); + if (u32_oct((uintmax_t)0L, hd->size, sizeof(hd->size), 1)) + goto out; + } else if ((arcn->type == PAX_HLK) || (arcn->type == PAX_HRG)) { + /* + * no data follows this file, so no pad + */ + hd->linkflag = LNKTYPE; + strlcpy(hd->linkname, arcn->ln_name, sizeof(hd->linkname)); + if (u32_oct((uintmax_t)0L, hd->size, sizeof(hd->size), 1)) + goto out; + } else { + /* + * data follows this file, so set the pad + */ + hd->linkflag = AREGTYPE; + if (OFFT_OCT(arcn->sb.st_size, hd->size, sizeof(hd->size), 1)) { + tty_warn(1,"File is too large for tar %s", + arcn->org_name); + return 1; + } + arcn->pad = TAR_PAD(arcn->sb.st_size); + } + + /* + * copy those fields that are independent of the type + */ + mtime = tst.st_ino ? tst.st_mtime : arcn->sb.st_mtime; + if (u32_oct((uintmax_t)arcn->sb.st_mode, hd->mode, sizeof(hd->mode), 0) || + u32_oct((uintmax_t)arcn->sb.st_uid, hd->uid, sizeof(hd->uid), 0) || + u32_oct((uintmax_t)arcn->sb.st_gid, hd->gid, sizeof(hd->gid), 0) || + u32_oct(mtime, hd->mtime, sizeof(hd->mtime), 1)) + goto out; + + /* + * calculate and add the checksum, then write the header. A return of + * 0 tells the caller to now write the file data, 1 says no data needs + * to be written + */ + if (u32_oct(tar_chksm(hdblk, sizeof(HD_TAR)), hd->chksum, + sizeof(hd->chksum), 3)) + goto out; /* XXX Something's wrong here + * because a zero-byte file can + * cause this to be done and + * yet the resulting warning + * seems incorrect */ + + if (wr_rdbuf(hdblk, sizeof(HD_TAR)) < 0) + return -1; + if (wr_skip((off_t)(BLKMULT - sizeof(HD_TAR))) < 0) + return -1; + if ((arcn->type == PAX_CTG) || (arcn->type == PAX_REG)) + return 0; + return 1; + + out: + /* + * header field is out of range + */ + tty_warn(1, "Tar header field is too small for %s", arcn->org_name); + return 1; +} + +/* + * Routines for POSIX ustar + */ + +/* + * ustar_strd() + * initialization for ustar read + * Return: + * 0 if ok, -1 otherwise + */ + +int +ustar_strd(void) +{ + return 0; +} + +/* + * ustar_stwr() + * initialization for ustar write + * Return: + * 0 if ok, -1 otherwise + */ + +int +ustar_stwr(void) +{ + return 0; +} + +/* + * ustar_id() + * determine if a block given to us is a valid ustar header. We have to + * be on the lookout for those pesky blocks of all zero's + * Return: + * 0 if a ustar header, -1 otherwise + */ + +int +ustar_id(char *blk, int size) +{ + HD_USTAR *hd; + + if (size < BLKMULT) + return -1; + hd = (HD_USTAR *)blk; + + /* + * check for block of zero's first, a simple and fast test then check + * ustar magic cookie. We should use TMAGLEN, but some USTAR archive + * programs are fouled up and create archives missing the \0. Last we + * check the checksum. If ok we have to assume it is a valid header. + */ + if (hd->name[0] == '\0') + return -1; + if (strncmp(hd->magic, TMAGIC, TMAGLEN - 1) != 0) + return -1; + /* This is GNU tar */ + if (strncmp(hd->magic, "ustar ", 8) == 0 && !is_gnutar && + !seen_gnu_warning) { + seen_gnu_warning = 1; + tty_warn(0, + "Trying to read GNU tar archive with GNU extensions and end-of-volume checks off"); + } + return check_sum(hd->chksum, sizeof(hd->chksum), blk, BLKMULT, 0); +} + +/* + * ustar_rd() + * extract the values out of block already determined to be a ustar header. + * store the values in the ARCHD parameter. + * Return: + * 0 + */ + +int +ustar_rd(ARCHD *arcn, char *buf) +{ + HD_USTAR *hd; + char *dest; + int cnt; + dev_t devmajor; + dev_t devminor; + + /* + * we only get proper sized buffers + */ + if (ustar_id(buf, BLKMULT) < 0) + return -1; + + memset(arcn, 0, sizeof(*arcn)); + arcn->org_name = arcn->name; + arcn->pat = NULL; + arcn->sb.st_nlink = 1; + hd = (HD_USTAR *)buf; + + /* + * see if the filename is split into two parts. if, so joint the parts. + * we copy the prefix first and add a / between the prefix and name. + */ + dest = arcn->name; + if (*(hd->prefix) != '\0') { + cnt = strlcpy(arcn->name, hd->prefix, sizeof(arcn->name)); + dest += cnt; + *dest++ = '/'; + cnt++; + } else { + cnt = 0; + } + + if (hd->typeflag != LONGLINKTYPE && hd->typeflag != LONGNAMETYPE) { + arcn->nlen = expandname(dest, sizeof(arcn->name) - cnt, + &gnu_name_string, &gnu_name_length, hd->name, + sizeof(hd->name)) + cnt; + arcn->ln_nlen = expandname(arcn->ln_name, + sizeof(arcn->ln_name), &gnu_link_string, &gnu_link_length, + hd->linkname, sizeof(hd->linkname)); + } + + /* + * follow the spec to the letter. we should only have mode bits, strip + * off all other crud we may be passed. + */ + arcn->sb.st_mode = (mode_t)(asc_u32(hd->mode, sizeof(hd->mode), OCT) & + 0xfff); + arcn->sb.st_size = (off_t)ASC_OFFT(hd->size, sizeof(hd->size), OCT); + if (arcn->sb.st_size == -1) + return -1; + arcn->sb.st_mtime = (time_t)(int32_t)asc_u32(hd->mtime, sizeof(hd->mtime), OCT); + arcn->sb.st_ctime = arcn->sb.st_atime = arcn->sb.st_mtime; + + /* + * If we can find the ascii names for gname and uname in the password + * and group files we will use the uid's and gid they bind. Otherwise + * we use the uid and gid values stored in the header. (This is what + * the posix spec wants). + */ + hd->gname[sizeof(hd->gname) - 1] = '\0'; + if (gid_from_group(hd->gname, &(arcn->sb.st_gid)) < 0) + arcn->sb.st_gid = (gid_t)asc_u32(hd->gid, sizeof(hd->gid), OCT); + hd->uname[sizeof(hd->uname) - 1] = '\0'; + if (uid_from_user(hd->uname, &(arcn->sb.st_uid)) < 0) + arcn->sb.st_uid = (uid_t)asc_u32(hd->uid, sizeof(hd->uid), OCT); + + /* + * set the defaults, these may be changed depending on the file type + */ + arcn->pad = 0; + arcn->skip = 0; + arcn->sb.st_rdev = (dev_t)0; + + /* + * set the mode and PAX type according to the typeflag in the header + */ + switch(hd->typeflag) { + case FIFOTYPE: + arcn->type = PAX_FIF; + arcn->sb.st_mode |= S_IFIFO; + break; + case DIRTYPE: + arcn->type = PAX_DIR; + arcn->sb.st_mode |= S_IFDIR; + arcn->sb.st_nlink = 2; + + /* + * Some programs that create ustar archives append a '/' + * to the pathname for directories. This clearly violates + * ustar specs, but we will silently strip it off anyway. + */ + if (arcn->name[arcn->nlen - 1] == '/') + arcn->name[--arcn->nlen] = '\0'; + break; + case BLKTYPE: + case CHRTYPE: + /* + * this type requires the rdev field to be set. + */ + if (hd->typeflag == BLKTYPE) { + arcn->type = PAX_BLK; + arcn->sb.st_mode |= S_IFBLK; + } else { + arcn->type = PAX_CHR; + arcn->sb.st_mode |= S_IFCHR; + } + devmajor = (dev_t)asc_u32(hd->devmajor,sizeof(hd->devmajor),OCT); + devminor = (dev_t)asc_u32(hd->devminor,sizeof(hd->devminor),OCT); + arcn->sb.st_rdev = TODEV(devmajor, devminor); + break; + case SYMTYPE: + case LNKTYPE: + if (hd->typeflag == SYMTYPE) { + arcn->type = PAX_SLK; + arcn->sb.st_mode |= S_IFLNK; + } else { + arcn->type = PAX_HLK; + /* + * so printing looks better + */ + arcn->sb.st_mode |= S_IFREG; + arcn->sb.st_nlink = 2; + } + break; + case LONGLINKTYPE: + case LONGNAMETYPE: + if (is_gnutar) { + /* + * GNU long link/file; we tag these here and let the + * pax internals deal with it -- too ugly otherwise. + */ + if (hd->typeflag != LONGLINKTYPE) + arcn->type = PAX_GLF; + else + arcn->type = PAX_GLL; + arcn->pad = TAR_PAD(arcn->sb.st_size); + arcn->skip = arcn->sb.st_size; + } else { + tty_warn(1, "GNU Long %s found in posix ustar archive.", + hd->typeflag == LONGLINKTYPE ? "Link" : "File"); + } + break; + case FILEXTYPE: + case GLOBXTYPE: + tty_warn(0, "%s extended headers posix ustar archive." + " Extracting as plain files. Following files might be" + " in the wrong directory or have wrong attributes.", + hd->typeflag == FILEXTYPE ? "File" : "Global"); + /*FALLTHROUGH*/ + case CONTTYPE: + case AREGTYPE: + case REGTYPE: + default: + /* + * these types have file data that follows. Set the skip and + * pad fields. + */ + arcn->type = PAX_REG; + arcn->pad = TAR_PAD(arcn->sb.st_size); + arcn->skip = arcn->sb.st_size; + arcn->sb.st_mode |= S_IFREG; + break; + } + return 0; +} + +static int +expandname(char *buf, size_t len, char **gnu_name, size_t *gnu_length, + const char *name, size_t nlen) +{ + if (*gnu_name) { + len = strlcpy(buf, *gnu_name, len); + free(*gnu_name); + *gnu_name = NULL; + *gnu_length = 0; + } else { + if (len > ++nlen) + len = nlen; + len = strlcpy(buf, name, len); + } + return len; +} + +static void +longlink(ARCHD *arcn, int type) +{ + ARCHD larc; + + (void)memset(&larc, 0, sizeof(larc)); + + larc.type = type; + larc.nlen = strlcpy(larc.name, LONG_LINK, sizeof(larc.name)); + + switch (type) { + case PAX_GLL: + gnu_hack_string = arcn->ln_name; + gnu_hack_len = arcn->ln_nlen + 1; + break; + case PAX_GLF: + gnu_hack_string = arcn->name; + gnu_hack_len = arcn->nlen + 1; + break; + default: + errx(1, "Invalid type in GNU longlink %d", type); + } + + /* + * We need a longlink now. + */ + ustar_wr(&larc); +} + +/* + * ustar_wr() + * write a ustar header for the file specified in the ARCHD to the archive + * Have to check for file types that cannot be stored and file names that + * are too long. Be careful of the term (last arg) to u32_oct, we only use + * '\0' for the termination character (this is different than picky tar) + * ASSUMED: space after header in header block is zero filled + * Return: + * 0 if file has data to be written after the header, 1 if file has NO + * data to write after the header, -1 if archive write failed + */ + +static int +size_err(const char *what, ARCHD *arcn) +{ + /* + * header field is out of range + */ + tty_warn(1, "Ustar %s header field is too small for %s", + what, arcn->org_name); + return 1; +} + +int +ustar_wr(ARCHD *arcn) +{ + HD_USTAR *hd; + char *pt; + uintmax_t mtime; + char hdblk[sizeof(HD_USTAR)]; + const char *user, *group; + + switch (arcn->type) { + case PAX_SCK: + /* + * check for those file system types ustar cannot store + */ + if (!is_gnutar) + tty_warn(1, "Ustar cannot archive a socket %s", + arcn->org_name); + return 1; + + case PAX_SLK: + case PAX_HLK: + case PAX_HRG: + /* + * check the length of the linkname + */ + if (arcn->ln_nlen >= (int)sizeof(hd->linkname)) { + if (is_gnutar) { + longlink(arcn, PAX_GLL); + } else { + tty_warn(1, "Link name too long for ustar %s", + arcn->ln_name); + return 1; + } + } + break; + default: + break; + } + + /* + * split the path name into prefix and name fields (if needed). if + * pt != arcn->name, the name has to be split + */ + if ((pt = name_split(arcn->name, arcn->nlen)) == NULL) { + if (is_gnutar) { + longlink(arcn, PAX_GLF); + pt = arcn->name; + } else { + tty_warn(1, "File name too long for ustar %s", + arcn->name); + return 1; + } + } + + /* + * zero out the header so we don't have to worry about zero fill below + */ + memset(hdblk, 0, sizeof(hdblk)); + hd = (HD_USTAR *)hdblk; + arcn->pad = 0L; + + /* + * split the name, or zero out the prefix + */ + if (pt != arcn->name) { + /* + * name was split, pt points at the / where the split is to + * occur, we remove the / and copy the first part to the prefix + */ + *pt = '\0'; + strlcpy(hd->prefix, arcn->name, sizeof(hd->prefix)); + *pt++ = '/'; + } + + /* + * copy the name part. this may be the whole path or the part after + * the prefix + */ + strlcpy(hd->name, pt, sizeof(hd->name)); + + /* + * set the fields in the header that are type dependent + */ + switch(arcn->type) { + case PAX_DIR: + hd->typeflag = DIRTYPE; + if (u32_oct((uintmax_t)0L, hd->size, sizeof(hd->size), 3)) + return size_err("DIRTYPE", arcn); + break; + case PAX_CHR: + case PAX_BLK: + if (arcn->type == PAX_CHR) + hd->typeflag = CHRTYPE; + else + hd->typeflag = BLKTYPE; + if (u32_oct((uintmax_t)MAJOR(arcn->sb.st_rdev), hd->devmajor, + sizeof(hd->devmajor), 3) || + u32_oct((uintmax_t)MINOR(arcn->sb.st_rdev), hd->devminor, + sizeof(hd->devminor), 3) || + u32_oct((uintmax_t)0L, hd->size, sizeof(hd->size), 3)) + return size_err("DEVTYPE", arcn); + break; + case PAX_FIF: + hd->typeflag = FIFOTYPE; + if (u32_oct((uintmax_t)0L, hd->size, sizeof(hd->size), 3)) + return size_err("FIFOTYPE", arcn); + break; + case PAX_GLL: + case PAX_SLK: + case PAX_HLK: + case PAX_HRG: + if (arcn->type == PAX_SLK) + hd->typeflag = SYMTYPE; + else if (arcn->type == PAX_GLL) + hd->typeflag = LONGLINKTYPE; + else + hd->typeflag = LNKTYPE; + strlcpy(hd->linkname, arcn->ln_name, sizeof(hd->linkname)); + if (u32_oct((uintmax_t)gnu_hack_len, hd->size, + sizeof(hd->size), 3)) + return size_err("LINKTYPE", arcn); + break; + case PAX_GLF: + case PAX_REG: + case PAX_CTG: + default: + /* + * file data with this type, set the padding + */ + if (arcn->type == PAX_GLF) { + hd->typeflag = LONGNAMETYPE; + arcn->pad = TAR_PAD(gnu_hack_len); + if (OFFT_OCT((uint32_t)gnu_hack_len, hd->size, + sizeof(hd->size), 3)) { + tty_warn(1,"File is too long for ustar %s", + arcn->org_name); + return 1; + } + } else { + if (arcn->type == PAX_CTG) + hd->typeflag = CONTTYPE; + else + hd->typeflag = REGTYPE; + arcn->pad = TAR_PAD(arcn->sb.st_size); + if (OFFT_OCT(arcn->sb.st_size, hd->size, + sizeof(hd->size), 3)) { + tty_warn(1,"File is too long for ustar %s", + arcn->org_name); + return 1; + } + } + break; + } + + strncpy(hd->magic, TMAGIC, TMAGLEN); + if (is_gnutar) + hd->magic[TMAGLEN - 1] = hd->version[0] = ' '; + else + strncpy(hd->version, TVERSION, TVERSLEN); + + /* + * set the remaining fields. Some versions want all 16 bits of mode + * we better humor them (they really do not meet spec though).... + */ + if (u32_oct((uintmax_t)arcn->sb.st_mode, hd->mode, sizeof(hd->mode), 3)) + return size_err("MODE", arcn); + if (u32_oct((uintmax_t)arcn->sb.st_uid, hd->uid, sizeof(hd->uid), 3)) + return size_err("UID", arcn); + if (u32_oct((uintmax_t)arcn->sb.st_gid, hd->gid, sizeof(hd->gid), 3)) + return size_err("GID", arcn); + mtime = tst.st_ino ? tst.st_mtime : arcn->sb.st_mtime; + if (u32_oct(mtime, hd->mtime, sizeof(hd->mtime), 3)) + return size_err("MTIME", arcn); + user = user_from_uid(arcn->sb.st_uid, 1); + group = group_from_gid(arcn->sb.st_gid, 1); + strncpy(hd->uname, user ? user : "", sizeof(hd->uname)); + strncpy(hd->gname, group ? group : "", sizeof(hd->gname)); + + /* + * calculate and store the checksum write the header to the archive + * return 0 tells the caller to now write the file data, 1 says no data + * needs to be written + */ + if (u32_oct(tar_chksm(hdblk, sizeof(HD_USTAR)), hd->chksum, + sizeof(hd->chksum), 3)) + return size_err("CHKSUM", arcn); + if (wr_rdbuf(hdblk, sizeof(HD_USTAR)) < 0) + return -1; + if (wr_skip((off_t)(BLKMULT - sizeof(HD_USTAR))) < 0) + return -1; + if (gnu_hack_string) { + int res = wr_rdbuf(gnu_hack_string, gnu_hack_len); + int pad = gnu_hack_len; + gnu_hack_string = NULL; + gnu_hack_len = 0; + if (res < 0) + return -1; + if (wr_skip((off_t)(BLKMULT - (pad % BLKMULT))) < 0) + return -1; + } + if ((arcn->type == PAX_CTG) || (arcn->type == PAX_REG)) + return 0; + return 1; +} + +/* + * name_split() + * see if the name has to be split for storage in a ustar header. We try + * to fit the entire name in the name field without splitting if we can. + * The split point is always at a / + * Return + * character pointer to split point (always the / that is to be removed + * if the split is not needed, the points is set to the start of the file + * name (it would violate the spec to split there). A NULL is returned if + * the file name is too long + */ + +static char * +name_split(char *name, int len) +{ + char *start; + + /* + * check to see if the file name is small enough to fit in the name + * field. if so just return a pointer to the name. + */ + if (len < TNMSZ) + return name; + /* + * GNU tar does not honor the prefix+name mode if the magic + * is not "ustar\0". So in GNU tar compatibility mode, we don't + * split the filename into prefix+name because we are setting + * the magic to "ustar " as GNU tar does. This of course will + * end up creating a LongLink record in cases where it does not + * really need do, but we are behaving like GNU tar after all. + */ + if (is_gnutar || len > (TPFSZ + TNMSZ)) + return NULL; + + /* + * we start looking at the biggest sized piece that fits in the name + * field. We walk forward looking for a slash to split at. The idea is + * to find the biggest piece to fit in the name field (or the smallest + * prefix we can find) (the -1 is correct the biggest piece would + * include the slash between the two parts that gets thrown away) + */ + start = name + len - TNMSZ; + while ((*start != '\0') && (*start != '/')) + ++start; + + /* + * if we hit the end of the string, this name cannot be split, so we + * cannot store this file. + */ + if (*start == '\0') + return NULL; + len = start - name; + + /* + * NOTE: /str where the length of str == TNMSZ cannot be stored under + * the p1003.1-1990 spec for ustar. We could force a prefix of / and + * the file would then expand on extract to //str. The len == 0 below + * makes this special case follow the spec to the letter. + */ + if ((len >= TPFSZ) || (len == 0)) + return NULL; + + /* + * ok have a split point, return it to the caller + */ + return start; +} + +/* + * convert a glob into a RE, and add it to the list. we convert to + * four different RE's (because we're using BRE's and can't use | + * alternation :-() with this padding: + * .*\/ and $ + * .*\/ and \/.* + * ^ and $ + * ^ and \/.* + */ +static int +tar_gnutar_exclude_one(const char *line, size_t len) +{ + /* 2 * buffer len + nul */ + char sbuf[MAXPATHLEN * 2 + 1]; + /* + / + // + .*""/\/ + \/.* */ + char rabuf[MAXPATHLEN * 2 + 1 + 1 + 2 + 4 + 4]; + size_t i; + int j = 0; + + if (line[len - 1] == '\n') + len--; + for (i = 0; i < len; i++) { + /* + * convert glob to regexp, escaping everything + */ + if (line[i] == '*') + sbuf[j++] = '.'; + else if (line[i] == '?') { + sbuf[j++] = '.'; + continue; + } else if (!isalnum((unsigned char)line[i]) && + !isblank((unsigned char)line[i])) + sbuf[j++] = '\\'; + sbuf[j++] = line[i]; + } + sbuf[j] = '\0'; + /* don't need the .*\/ ones if we start with /, i guess */ + if (line[0] != '/') { + (void)snprintf(rabuf, sizeof rabuf, "/.*\\/%s$//", sbuf); + if (rep_add(rabuf) < 0) + return (-1); + (void)snprintf(rabuf, sizeof rabuf, "/.*\\/%s\\/.*//", sbuf); + if (rep_add(rabuf) < 0) + return (-1); + } + + (void)snprintf(rabuf, sizeof rabuf, "/^%s$//", sbuf); + if (rep_add(rabuf) < 0) + return (-1); + (void)snprintf(rabuf, sizeof rabuf, "/^%s\\/.*//", sbuf); + if (rep_add(rabuf) < 0) + return (-1); + + return (0); +} + +/* + * deal with GNU tar -X/--exclude-from & --exclude switchs. basically, + * we go through each line of the file, building a string from the "glob" + * lines in the file into RE lines, of the form `/^RE$//', which we pass + * to rep_add(), which will add a empty replacement (exclusion), for the + * named files. + */ +int +tar_gnutar_minus_minus_exclude(const char *path) +{ + size_t len = strlen(path); + + if (len > MAXPATHLEN) + tty_warn(0, "pathname too long: %s", path); + + return (tar_gnutar_exclude_one(path, len)); +} + +int +tar_gnutar_X_compat(const char *path) +{ + char *line; + FILE *fp; + int lineno = 0; + size_t len; + + if (path[0] == '-' && path[1] == '\0') + fp = stdin; + else { + fp = fopen(path, "r"); + if (fp == NULL) { + tty_warn(1, "cannot open %s: %s", path, + strerror(errno)); + return -1; + } + } + + while ((line = fgetln(fp, &len))) { + lineno++; + if (len > MAXPATHLEN) { + tty_warn(0, "pathname too long, line %d of %s", + lineno, path); + } + if (tar_gnutar_exclude_one(line, len)) + return -1; + } + if (fp != stdin) + fclose(fp); + return 0; +} diff --git a/bin/pax/tar.h b/bin/pax/tar.h new file mode 100644 index 0000000..ae7f6ce --- /dev/null +++ b/bin/pax/tar.h @@ -0,0 +1,154 @@ +/* $NetBSD: tar.h,v 1.10 2013/01/24 17:43:44 christos Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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. + * + * @(#)tar.h 8.2 (Berkeley) 4/18/94 + */ + +/* + * defines and data structures common to all tar formats + */ +#define CHK_LEN 8 /* length of checksum field */ +#define TNMSZ 100 /* size of name field */ +#ifdef _PAX_ +#define NULLCNT 2 /* number of null blocks in trailer */ +#define CHK_OFFSET 148 /* start of chksum field */ +#define BLNKSUM 256L /* sum of checksum field using ' ' */ +#endif /* _PAX_ */ + +/* + * Values used in typeflag field in all tar formats + * (only REGTYPE, LNKTYPE and SYMTYPE are used in old bsd tar headers) + */ +#define REGTYPE '0' /* Regular File */ +#define AREGTYPE '\0' /* Regular File */ +#define LNKTYPE '1' /* Link */ +#define SYMTYPE '2' /* Symlink */ +#define CHRTYPE '3' /* Character Special File */ +#define BLKTYPE '4' /* Block Special File */ +#define DIRTYPE '5' /* Directory */ +#define FIFOTYPE '6' /* FIFO */ +#define CONTTYPE '7' /* high perf file */ +#define GLOBXTYPE 'g' /* global extended header */ +#define FILEXTYPE 'x' /* file extended header */ + +/* + * GNU tar compatibility; + */ +#define LONGLINKTYPE 'K' /* Long Symlink */ +#define LONGNAMETYPE 'L' /* Long File */ + +/* + * Mode field encoding of the different file types - values in octal + */ +#define TSUID 04000 /* Set UID on execution */ +#define TSGID 02000 /* Set GID on execution */ +#define TSVTX 01000 /* Reserved */ +#define TUREAD 00400 /* Read by owner */ +#define TUWRITE 00200 /* Write by owner */ +#define TUEXEC 00100 /* Execute/Search by owner */ +#define TGREAD 00040 /* Read by group */ +#define TGWRITE 00020 /* Write by group */ +#define TGEXEC 00010 /* Execute/Search by group */ +#define TOREAD 00004 /* Read by other */ +#define TOWRITE 00002 /* Write by other */ +#define TOEXEC 00001 /* Execute/Search by other */ + +#ifdef _PAX_ +/* + * Pad with a bit mask, much faster than doing a mod but only works on powers + * of 2. Macro below is for block of 512 bytes. + */ +#define TAR_PAD(x) ((512 - ((x) & 511)) & 511) +#endif /* _PAX_ */ + +/* + * structure of an old tar header as it appeared in BSD releases + */ +typedef struct { + char name[TNMSZ]; /* name of entry */ + char mode[8]; /* mode */ + char uid[8]; /* uid */ + char gid[8]; /* gid */ + char size[12]; /* size */ + char mtime[12]; /* modification time */ + char chksum[CHK_LEN]; /* checksum */ + char linkflag; /* norm, hard, or sym. */ + char linkname[TNMSZ]; /* linked to name */ +} HD_TAR; + +#ifdef _PAX_ +/* + * -o options for BSD tar to not write directories to the archive + */ +#define TAR_NODIR "nodir" +#define TAR_OPTION "write_opt" + +/* + * default device names + */ +extern char DEV_0[]; +extern char DEV_1[]; +extern char DEV_4[]; +extern char DEV_5[]; +extern char DEV_7[]; +extern char DEV_8[]; +#endif /* _PAX_ */ + +/* + * Data Interchange Format - Extended tar header format - POSIX 1003.1-1990 + */ +#define TPFSZ 155 +#define TMAGIC "ustar" /* ustar and a null */ +#define TMAGLEN 6 +#define TVERSION "00" /* 00 and no null */ +#define TVERSLEN 2 + +typedef struct { + char name[TNMSZ]; /* name of entry */ + char mode[8]; /* mode */ + char uid[8]; /* uid */ + char gid[8]; /* gid */ + char size[12]; /* size */ + char mtime[12]; /* modification time */ + char chksum[CHK_LEN]; /* checksum */ + char typeflag; /* type of file. */ + char linkname[TNMSZ]; /* linked to name */ + char magic[TMAGLEN]; /* magic cookie */ + char version[TVERSLEN]; /* version */ + char uname[32]; /* ascii owner name */ + char gname[32]; /* ascii group name */ + char devmajor[8]; /* major device number */ + char devminor[8]; /* minor device number */ + char prefix[TPFSZ]; /* linked to name */ +} HD_USTAR; diff --git a/bin/pax/tty_subs.c b/bin/pax/tty_subs.c new file mode 100644 index 0000000..5956877 --- /dev/null +++ b/bin/pax/tty_subs.c @@ -0,0 +1,200 @@ +/* $NetBSD: tty_subs.c,v 1.19 2007/04/23 18:40:22 christos Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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 HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include <sys/cdefs.h> +#if !defined(lint) +#if 0 +static char sccsid[] = "@(#)tty_subs.c 8.2 (Berkeley) 4/18/94"; +#else +__RCSID("$NetBSD: tty_subs.c,v 1.19 2007/04/23 18:40:22 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <fcntl.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include "pax.h" +#include "extern.h" +#include <stdarg.h> + +/* + * routines that deal with I/O to and from the user + */ + +#define DEVTTY "/dev/tty" /* device for interactive i/o */ +static FILE *ttyoutf = NULL; /* output pointing at control tty */ +static FILE *ttyinf = NULL; /* input pointing at control tty */ + +/* + * tty_init() + * Try to open the controlling terminal (if any) for this process. If the + * open fails, future ops that require user input will get an EOF. + */ + +int +tty_init(void) +{ + int ttyfd; + + if ((ttyfd = open(DEVTTY, O_RDWR)) >= 0) { + if ((ttyoutf = fdopen(ttyfd, "w")) != NULL) { + if ((ttyinf = fdopen(ttyfd, "r")) != NULL) + return 0; + (void)fclose(ttyoutf); + } + (void)close(ttyfd); + } + + if (iflag) { + tty_warn(1, "Fatal error, cannot open %s", DEVTTY); + return -1; + } + return 0; +} + +/* + * tty_prnt() + * print a message using the specified format to the controlling tty + * if there is no controlling terminal, just return. + */ + +void +tty_prnt(const char *fmt, ...) +{ + va_list ap; + if (ttyoutf == NULL) + return; + va_start(ap, fmt); + (void)vfprintf(ttyoutf, fmt, ap); + va_end(ap); + (void)fflush(ttyoutf); +} + +/* + * tty_read() + * read a string from the controlling terminal if it is open into the + * supplied buffer + * Return: + * 0 if data was read, -1 otherwise. + */ + +int +tty_read(char *str, int len) +{ + char *pt; + + if ((--len <= 0) || (ttyinf == NULL) || (fgets(str,len,ttyinf) == NULL)) + return -1; + *(str + len) = '\0'; + + /* + * strip off that trailing newline + */ + if ((pt = strchr(str, '\n')) != NULL) + *pt = '\0'; + return 0; +} + +/* + * tty_warn() + * write a warning message to stderr. if "set" the exit value of pax + * will be non-zero. + */ + +void +tty_warn(int set, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + if (set) + exit_val = 1; + /* + * when vflag we better ship out an extra \n to get this message on a + * line by itself + */ + if ((Vflag || vflag) && vfpart) { + (void)fputc('\n', stderr); + vfpart = 0; + } + (void)fprintf(stderr, "%s: ", argv0); + (void)vfprintf(stderr, fmt, ap); + va_end(ap); + (void)fputc('\n', stderr); +} + +/* + * syswarn() + * write a warning message to stderr. if "set" the exit value of pax + * will be non-zero. + */ + +void +syswarn(int set, int errnum, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + if (set) + exit_val = 1; + /* + * when vflag we better ship out an extra \n to get this message on a + * line by itself + */ + if ((Vflag || vflag) && vfpart) { + (void)fputc('\n', stdout); + vfpart = 0; + } + (void)fprintf(stderr, "%s: ", argv0); + (void)vfprintf(stderr, fmt, ap); + va_end(ap); + + /* + * format and print the errno + */ + if (errnum > 0) + (void)fprintf(stderr, " (%s)", strerror(errnum)); + (void)fputc('\n', stderr); +} diff --git a/bin/ps/extern.h b/bin/ps/extern.h new file mode 100644 index 0000000..6ec96ca --- /dev/null +++ b/bin/ps/extern.h @@ -0,0 +1,99 @@ +/* $NetBSD: extern.h,v 1.39 2017/12/09 14:56:54 kamil Exp $ */ + +/*- + * Copyright (c) 1991, 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. + * + * @(#)extern.h 8.3 (Berkeley) 4/2/94 + */ + +/* + * We expect to be included by ps.h, which will already have + * defined the types we use. + */ + +extern double log_ccpu; +extern int eval, fscale, mempages, nlistread, maxslp, uspace; +extern int sumrusage, termwidth, totwidth; +extern int needenv, needcomm, commandonly; +extern uid_t myuid; +extern kvm_t *kd; +extern VAR var[]; +extern VARLIST displaylist; +extern VARLIST sortlist; + +void command(struct pinfo *, VARENT *, enum mode); +void cpuid(struct pinfo *, VARENT *, enum mode); +void cputime(struct pinfo *, VARENT *, enum mode); +void donlist(void); +void donlist_sysctl(void); +void fmt_puts(char *, int *); +void fmt_putc(int, int *); +void elapsed(struct pinfo *, VARENT *, enum mode); +double getpcpu(const struct kinfo_proc2 *); +double getpmem(const struct kinfo_proc2 *); +void gname(struct pinfo *, VARENT *, enum mode); +void groups(struct pinfo *, VARENT *, enum mode); +void groupnames(struct pinfo *, VARENT *, enum mode); +void lcputime(struct pinfo *, VARENT *, enum mode); +void logname(struct pinfo *, VARENT *, enum mode); +void longtname(struct pinfo *, VARENT *, enum mode); +void lname(struct pinfo *, VARENT *, enum mode); +void lstarted(struct pinfo *, VARENT *, enum mode); +void lstate(struct pinfo *, VARENT *, enum mode); +void maxrss(struct pinfo *, VARENT *, enum mode); +void nlisterr(struct nlist *); +void p_rssize(struct pinfo *, VARENT *, enum mode); +void pagein(struct pinfo *, VARENT *, enum mode); +void parsefmt(const char *); +void parsefmt_insert(const char *, VARENT **); +void parsesort(const char *); +VARENT * varlist_find(VARLIST *, const char *); +void emul(struct pinfo *, VARENT *, enum mode); +void pcpu(struct pinfo *, VARENT *, enum mode); +void pmem(struct pinfo *, VARENT *, enum mode); +void pnice(struct pinfo *, VARENT *, enum mode); +void pri(struct pinfo *, VARENT *, enum mode); +void printheader(void); +void putimeval(struct pinfo *, VARENT *, enum mode); +void pvar(struct pinfo *, VARENT *, enum mode); +void rgname(struct pinfo *, VARENT *, enum mode); +void rssize(struct pinfo *, VARENT *, enum mode); +void runame(struct pinfo *, VARENT *, enum mode); +void showkey(void); +void started(struct pinfo *, VARENT *, enum mode); +void state(struct pinfo *, VARENT *, enum mode); +void svgname(struct pinfo *, VARENT *, enum mode); +void svuname(struct pinfo *, VARENT *, enum mode); +void tdev(struct pinfo *, VARENT *, enum mode); +void tname(struct pinfo *, VARENT *, enum mode); +void tsize(struct pinfo *, VARENT *, enum mode); +void ucomm(struct pinfo *, VARENT *, enum mode); +void usrname(struct pinfo *, VARENT *, enum mode); +void uvar(struct pinfo *, VARENT *, enum mode); +void vsize(struct pinfo *, VARENT *, enum mode); +void wchan(struct pinfo *, VARENT *, enum mode); diff --git a/bin/ps/fmt.c b/bin/ps/fmt.c new file mode 100644 index 0000000..489a0ec --- /dev/null +++ b/bin/ps/fmt.c @@ -0,0 +1,60 @@ +/* $NetBSD: fmt.c,v 1.21 2007/12/12 22:55:43 lukem Exp $ */ + +#include <sys/cdefs.h> +__RCSID("$NetBSD: fmt.c,v 1.21 2007/12/12 22:55:43 lukem Exp $"); + +#include <kvm.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <vis.h> +#include <sys/time.h> +#include <sys/resource.h> +#include "ps.h" + +void +fmt_puts(char *s, int *leftp) +{ + static char *v = 0; + static int maxlen = 0; + char *nv; + int len, nlen; + + if (*leftp == 0) + return; + len = strlen(s) * 4 + 1; + if (len > maxlen) { + if (maxlen == 0) + nlen = getpagesize(); + else + nlen = maxlen; + while (len > nlen) + nlen *= 2; + nv = realloc(v, nlen); + if (nv == 0) + return; + v = nv; + maxlen = nlen; + } + len = strvis(v, s, VIS_TAB | VIS_NL | VIS_CSTYLE); + if (*leftp != -1) { + if (len > *leftp) { + v[*leftp] = '\0'; + *leftp = 0; + } else + *leftp -= len; + } + (void)printf("%s", v); +} + +void +fmt_putc(int c, int *leftp) +{ + + if (*leftp == 0) + return; + if (*leftp != -1) + *leftp -= 1; + putchar(c); +} diff --git a/bin/ps/keyword.c b/bin/ps/keyword.c new file mode 100644 index 0000000..f7504ca --- /dev/null +++ b/bin/ps/keyword.c @@ -0,0 +1,415 @@ +/* $NetBSD: keyword.c,v 1.56 2018/04/11 18:52:05 christos Exp $ */ + +/*- + * Copyright (c) 1990, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)keyword.c 8.5 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: keyword.c,v 1.56 2018/04/11 18:52:05 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/time.h> +#include <sys/lwp.h> +#include <sys/proc.h> +#include <sys/resource.h> +#include <sys/sysctl.h> +#include <sys/ucred.h> + +#include <err.h> +#include <errno.h> +#include <kvm.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> + +#include "ps.h" + +static VAR *findvar(const char *); +static int vcmp(const void *, const void *); + +#if 0 /* kernel doesn't calculate these */ + PUVAR("idrss", "IDRSS", 0, p_uru_idrss, UINT64, PRIu64), + PUVAR("isrss", "ISRSS", 0, p_uru_isrss, UINT64, PRId64), + PUVAR("ixrss", "IXRSS", 0, p_uru_ixrss, UINT64, PRId64), + PUVAR("maxrss", "MAXRSS", 0, p_uru_maxrss, UINT64, PRIu64), +#endif + +/* Compute offset in common structures. */ +#define POFF(x) offsetof(struct kinfo_proc2, x) +#define LOFF(x) offsetof(struct kinfo_lwp, x) + +#define UIDFMT "u" +#define UID(n1, n2, of) \ + { .name = n1, .header = n2, .flag = 0, .oproc = pvar, \ + .off = POFF(of), .type = UINT32, .fmt = UIDFMT } +#define GID(n1, n2, off) UID(n1, n2, off) + +#define PIDFMT "d" +#define PID(n1, n2, of) \ + { .name = n1, .header = n2, .flag = 0, .oproc = pvar, \ + .off = POFF(of), .type = INT32, .fmt = PIDFMT } + +#define LVAR(n1, n2, fl, of, ty, fm) \ + { .name = n1, .header = n2, .flag = (fl) | LWP, .oproc = pvar, \ + .off = LOFF(of), .type = ty, .fmt = fm } +#define PVAR(n1, n2, fl, of, ty, fm) \ + { .name = n1, .header = n2, .flag = (fl) | 0, .oproc = pvar, \ + .off = POFF(of), .type = ty, .fmt = fm } +#define PUVAR(n1, n2, fl, of, ty, fm) \ + { .name = n1, .header = n2, .flag = (fl) | UAREA, .oproc = pvar, \ + .off = POFF(of), .type = ty, .fmt = fm } +#define VAR3(n1, n2, fl) \ + { .name = n1, .header = n2, .flag = fl } +#define VAR4(n1, n2, fl, op) \ + { .name = n1, .header = n2, .flag = fl, .oproc = op, } +#define VAR6(n1, n2, fl, op, of, ty) \ + { .name = n1, .header = n2, .flag = fl, .oproc = op, \ + .off = of, .type = ty } + +/* NB: table must be sorted, in vi use: + * :/^VAR/,/end_sort/! sort -t\" +1 + * breaking long lines just makes the sort harder + * + * We support all the fields required by P1003.1-2004 (SUSv3), with + * the correct default headers, except for the "tty" field, where the + * standard says the header should be "TT", but we have "TTY". + */ +VAR var[] = { + VAR6("%cpu", "%CPU", 0, pcpu, 0, PCPU), + VAR6("%mem", "%MEM", 0, pmem, POFF(p_vm_rssize), INT32), + PVAR("acflag", "ACFLG", 0, p_acflag, USHORT, "x"), + VAR3("acflg", "acflag", ALIAS), + VAR3("args", "command", ALIAS), + VAR3("blocked", "sigmask", ALIAS), + VAR3("caught", "sigcatch", ALIAS), + VAR4("comm", "COMMAND", COMM|ARGV0|LJUST, command), + VAR4("command", "COMMAND", COMM|LJUST, command), + PVAR("cpu", "CPU", 0, p_estcpu, UINT, "u"), + VAR4("cpuid", "CPUID", LWP, cpuid), + VAR3("cputime", "time", ALIAS), + VAR6("ctime", "CTIME", 0, putimeval, POFF(p_uctime_sec), TIMEVAL), + GID("egid", "EGID", p_gid), + VAR4("egroup", "EGROUP", LJUST, gname), + VAR4("emul", "EMUL", LJUST, emul), + VAR6("etime", "ELAPSED", 0, elapsed, POFF(p_ustart_sec), TIMEVAL), + UID("euid", "EUID", p_uid), + VAR4("euser", "EUSER", LJUST, usrname), + PVAR("f", "F", 0, p_flag, INT, "x"), + VAR3("flags", "f", ALIAS), + GID("gid", "GID", p_gid), + VAR4("group", "GROUP", LJUST, gname), + VAR4("groupnames", "GROUPNAMES", LJUST, groupnames), + VAR4("groups", "GROUPS", LJUST, groups), + /* holdcnt: unused, left for compat. */ + LVAR("holdcnt", "HOLDCNT", 0, l_holdcnt, INT, "d"), + VAR3("ignored", "sigignore", ALIAS), + PUVAR("inblk", "INBLK", 0, p_uru_inblock, UINT64, PRIu64), + VAR3("inblock", "inblk", ALIAS), + PVAR("jobc", "JOBC", 0, p_jobc, SHORT, "d"), + PVAR("ktrace", "KTRACE", 0, p_traceflag, INT, "x"), +/*XXX*/ PVAR("ktracep", "KTRACEP", 0, p_tracep, KPTR, PRIx64), + LVAR("laddr", "LADDR", 0, l_laddr, KPTR, PRIx64), + LVAR("lid", "LID", 0, l_lid, INT32, "d"), + VAR4("lim", "LIM", 0, maxrss), + VAR4("lname", "LNAME", LJUST|LWP, lname), + VAR4("login", "LOGIN", LJUST, logname), + VAR3("logname", "login", ALIAS), + VAR6("lstart", "STARTED", LJUST, lstarted, POFF(p_ustart_sec), UINT32), + VAR4("lstate", "STAT", LJUST|LWP, lstate), + VAR6("ltime", "LTIME", LWP, lcputime, 0, CPUTIME), + PUVAR("majflt", "MAJFLT", 0, p_uru_majflt, UINT64, PRIu64), + PUVAR("minflt", "MINFLT", 0, p_uru_minflt, UINT64, PRIu64), + PUVAR("msgrcv", "MSGRCV", 0, p_uru_msgrcv, UINT64, PRIu64), + PUVAR("msgsnd", "MSGSND", 0, p_uru_msgsnd, UINT64, PRIu64), + VAR3("ni", "nice", ALIAS), + VAR6("nice", "NI", 0, pnice, POFF(p_nice), UCHAR), + PUVAR("nivcsw", "NIVCSW", 0, p_uru_nivcsw, UINT64, PRIu64), + PVAR("nlwp", "NLWP", 0, p_nlwps, UINT64, PRId64), + VAR3("nsignals", "nsigs", ALIAS), + PUVAR("nsigs", "NSIGS", 0, p_uru_nsignals, UINT64, PRIu64), + /* nswap: unused, left for compat. */ + PUVAR("nswap", "NSWAP", 0, p_uru_nswap, UINT64, PRIu64), + PUVAR("nvcsw", "NVCSW", 0, p_uru_nvcsw, UINT64, PRIu64), +/*XXX*/ LVAR("nwchan", "WCHAN", 0, l_wchan, KPTR, PRIx64), + PUVAR("oublk", "OUBLK", 0, p_uru_oublock, UINT64, PRIu64), + VAR3("oublock", "oublk", ALIAS), +/*XXX*/ PVAR("p_ru", "P_RU", 0, p_ru, KPTR, PRIx64), +/*XXX*/ PVAR("paddr", "PADDR", 0, p_paddr, KPTR, PRIx64), + PUVAR("pagein", "PAGEIN", 0, p_uru_majflt, UINT64, PRIu64), + VAR3("pcpu", "%cpu", ALIAS), + VAR3("pending", "sig", ALIAS), + PID("pgid", "PGID", p__pgid), + PID("pid", "PID", p_pid), + VAR3("pmem", "%mem", ALIAS), + PID("ppid", "PPID", p_ppid), + VAR4("pri", "PRI", LWP, pri), + LVAR("re", "RE", INF127, l_swtime, UINT, "u"), + GID("rgid", "RGID", p_rgid), + VAR4("rgroup", "RGROUP", LJUST, rgname), +/*XXX*/ LVAR("rlink", "RLINK", 0, l_back, KPTR, PRIx64), + PVAR("rlwp", "RLWP", 0, p_nrlwps, UINT64, PRId64), + VAR6("rss", "RSS", 0, p_rssize, POFF(p_vm_rssize), INT32), + VAR3("rssize", "rsz", ALIAS), + VAR6("rsz", "RSZ", 0, rssize, POFF(p_vm_rssize), INT32), + UID("ruid", "RUID", p_ruid), + VAR4("ruser", "RUSER", LJUST, runame), + PVAR("sess", "SESS", 0, p_sess, KPTR24, PRIx64), + PID("sid", "SID", p_sid), + PVAR("sig", "PENDING", 0, p_siglist, SIGLIST, "s"), + PVAR("sigcatch", "CAUGHT", 0, p_sigcatch, SIGLIST, "s"), + PVAR("sigignore", "IGNORED", 0, p_sigignore, SIGLIST, "s"), + PVAR("sigmask", "BLOCKED", 0, p_sigmask, SIGLIST, "s"), + LVAR("sl", "SL", INF127, l_slptime, UINT, "u"), + VAR6("start", "STARTED", 0, started, POFF(p_ustart_sec), UINT32), + VAR3("stat", "state", ALIAS), + VAR4("state", "STAT", LJUST, state), + VAR6("stime", "STIME", 0, putimeval, POFF(p_ustime_sec), TIMEVAL), + GID("svgid", "SVGID", p_svgid), + VAR4("svgroup", "SVGROUP", LJUST, svgname), + UID("svuid", "SVUID", p_svuid), + VAR4("svuser", "SVUSER", LJUST, svuname), + /* "tdev" is UINT32, but we do this for sorting purposes */ + VAR6("tdev", "TDEV", 0, tdev, POFF(p_tdev), INT32), + VAR6("time", "TIME", 0, cputime, 0, CPUTIME), + PID("tpgid", "TPGID", p_tpgid), + PVAR("tsess", "TSESS", 0, p_tsess, KPTR, PRIx64), + VAR6("tsiz", "TSIZ", 0, tsize, POFF(p_vm_tsize), INT32), + VAR6("tt", "TTY", LJUST, tname, POFF(p_tdev), INT32), + VAR6("tty", "TTY", LJUST, longtname, POFF(p_tdev), INT32), + LVAR("uaddr", "UADDR", 0, l_addr, KPTR, PRIx64), + VAR4("ucomm", "UCOMM", LJUST, ucomm), + UID("uid", "UID", p_uid), + LVAR("upr", "UPR", 0, l_usrpri, UCHAR, "u"), + VAR4("user", "USER", LJUST, usrname), + VAR3("usrpri", "upr", ALIAS), + VAR6("utime", "UTIME", 0, putimeval, POFF(p_uutime_sec), TIMEVAL), + VAR3("vsize", "vsz", ALIAS), + VAR6("vsz", "VSZ", 0, vsize, 0, VSIZE), + VAR4("wchan", "WCHAN", LJUST|LWP, wchan), + PVAR("xstat", "XSTAT", 0, p_xstat, USHORT, "x"), +/* "zzzz" end_sort */ + { .name = "" }, +}; + +void +showkey(void) +{ + VAR *v; + int i; + const char *p; + const char *sep; + + i = 0; + sep = ""; + for (v = var; *(p = v->name); ++v) { + int len = strlen(p); + if (termwidth && (i += len + 1) > termwidth) { + i = len; + sep = "\n"; + } + (void)printf("%s%s", sep, p); + sep = " "; + } + (void)printf("\n"); +} + +/* + * Parse the string pp, and insert or append entries to the list + * referenced by listptr. If pos in non-null and *pos is non-null, then + * *pos specifies where to insert (instead of appending). If pos is + * non-null, then a new value is returned through *pos referring to the + * last item inserted. + */ +static void +parsevarlist(const char *pp, struct varlist *listptr, struct varent **pos) +{ + char *p, *sp, *equalsp; + + /* dup to avoid zapping arguments. We will free sp later. */ + p = sp = strdup(pp); + + /* + * Everything after the first '=' is part of a custom header. + * Temporarily replace it with '\0' to simplify other code. + */ + equalsp = strchr(p, '='); + if (equalsp) + *equalsp = '\0'; + +#define FMTSEP " \t,\n" + while (p && *p) { + char *cp; + VAR *v; + struct varent *vent; + + /* + * skip separators before the first keyword, and + * look for the separator after the keyword. + */ + for (cp = p; *cp != '\0'; cp++) { + p = strpbrk(cp, FMTSEP); + if (p != cp) + break; + } + if (*cp == '\0') + break; + /* + * Now cp points to the start of a keyword, + * and p is NULL or points past the end of the keyword. + * + * Terminate the keyword with '\0', or reinstate the + * '=' that was removed earlier, if appropriate. + */ + if (p) { + *p = '\0'; + p++; + } else if (equalsp) { + *equalsp = '='; + } + + /* + * If findvar() likes the keyword or keyword=header, + * add it to our list. If findvar() doesn't like it, + * it will print a warning, so we ignore it. + */ + if ((v = findvar(cp)) == NULL) + continue; + if ((vent = malloc(sizeof(struct varent))) == NULL) + err(EXIT_FAILURE, NULL); + vent->var = v; + if (pos && *pos) + SIMPLEQ_INSERT_AFTER(listptr, *pos, vent, next); + else { + SIMPLEQ_INSERT_TAIL(listptr, vent, next); + } + if (pos) + *pos = vent; + } + free(sp); + if (SIMPLEQ_EMPTY(listptr)) + errx(EXIT_FAILURE, "no valid keywords"); +} + +void +parsefmt(const char *p) +{ + + parsevarlist(p, &displaylist, NULL); +} + +void +parsefmt_insert(const char *p, struct varent **pos) +{ + + parsevarlist(p, &displaylist, pos); +} + +void +parsesort(const char *p) +{ + + parsevarlist(p, &sortlist, NULL); +} + +/* Search through a list for an entry with a specified name. */ +struct varent * +varlist_find(struct varlist *list, const char *name) +{ + struct varent *vent; + + SIMPLEQ_FOREACH(vent, list, next) { + if (strcmp(vent->var->name, name) == 0) + break; + } + return vent; +} + +static VAR * +findvar(const char *p) +{ + VAR *v; + char *hp; + + hp = strchr(p, '='); + if (hp) + *hp++ = '\0'; + + v = bsearch(p, var, sizeof(var)/sizeof(VAR) - 1, sizeof(VAR), vcmp); + if (v && v->flag & ALIAS) + v = findvar(v->header); + if (!v) { + warnx("%s: keyword not found", p); + eval = 1; + return NULL; + } + + if (v && hp) { + /* + * Override the header. + * + * We need to copy the entry first, and override the + * header in the copy, because the same field might be + * used multiple times with different headers. We also + * need to strdup the header. + */ + struct var *newvar; + char *newheader; + + if ((newvar = malloc(sizeof(struct var))) == NULL) + err(EXIT_FAILURE, NULL); + if ((newheader = strdup(hp)) == NULL) + err(EXIT_FAILURE, NULL); + memcpy(newvar, v, sizeof(struct var)); + newvar->header = newheader; + + /* + * According to P1003.1-2004, if the header text is null, + * such as -o user=, the field width will be at least as + * wide as the default header text. + */ + if (*hp == '\0') + newvar->width = strlen(v->header); + + v = newvar; + } + return v; +} + +static int +vcmp(const void *a, const void *b) +{ + return strcmp(a, ((const VAR *)b)->name); +} diff --git a/bin/ps/nlist.c b/bin/ps/nlist.c new file mode 100644 index 0000000..42471ea --- /dev/null +++ b/bin/ps/nlist.c @@ -0,0 +1,234 @@ +/* $NetBSD: nlist.c,v 1.27 2016/11/28 08:19:23 rin Exp $ */ + +/* + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Simon Burge. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + */ + +/* + * Copyright (c) 1990, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)nlist.c 8.4 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: nlist.c,v 1.27 2016/11/28 08:19:23 rin Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/time.h> +#include <sys/lwp.h> +#include <sys/proc.h> +#include <sys/resource.h> +#include <sys/sysctl.h> + +#include <err.h> +#include <errno.h> +#include <kvm.h> +#include <math.h> +#include <nlist.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "ps.h" + +struct nlist psnl[] = { + { .n_name = "_fscale" }, +#define X_FSCALE 0 + { .n_name = "_ccpu" }, +#define X_CCPU 1 + { .n_name = "_physmem" }, +#define X_PHYSMEM 2 + { .n_name = "_maxslp" }, +#define X_MAXSLP 3 + { .n_name = NULL } +}; + +double log_ccpu; /* log of kernel _ccpu variable */ +int nlistread; /* if nlist already read. */ +int mempages; /* number of pages of phys. memory */ +int fscale; /* kernel _fscale variable */ +int maxslp; /* kernel _maxslp variable */ +int uspace; /* kernel USPACE value */ + +/* XXX Hopefully reasonable default */ +#define MEMPAGES 0 +#ifndef FSCALE +#define FSCALE (1 << 8) +#endif +#define LOG_CCPU (-1.0 / 20.0) +#ifndef MAXSLP +#define MAXSLP 20 +#endif +#ifndef USPACE +#define USPACE (getpagesize()) +#endif + +#define kread(x, v) \ + kvm_read(kd, psnl[x].n_value, (char *)&v, sizeof v) != sizeof(v) + +void +donlist(void) +{ + fixpt_t xccpu; + + nlistread = 1; + + if (kvm_nlist(kd, psnl)) { + nlisterr(psnl); + eval = 1; + fscale = FSCALE; + mempages = MEMPAGES; + log_ccpu = LOG_CCPU; + maxslp = MAXSLP; + return; + } + + if (kread(X_FSCALE, fscale)) { + warnx("fscale: %s", kvm_geterr(kd)); + eval = 1; + fscale = FSCALE; + } + + if (kread(X_PHYSMEM, mempages)) { + warnx("avail_start: %s", kvm_geterr(kd)); + eval = 1; + mempages = MEMPAGES; + } + + if (kread(X_CCPU, xccpu)) { + warnx("ccpu: %s", kvm_geterr(kd)); + eval = 1; + log_ccpu = LOG_CCPU; + } else + log_ccpu = log((double)xccpu / fscale); + + if (kread(X_MAXSLP, maxslp)) { + warnx("maxslp: %s", kvm_geterr(kd)); + eval = 1; + maxslp = MAXSLP; + } +} + +void +donlist_sysctl(void) +{ + int mib[2]; + size_t size; + fixpt_t xccpu; + uint64_t memsize; + + nlistread = 1; + + mib[0] = CTL_KERN; + mib[1] = KERN_FSCALE; + size = sizeof(fscale); + if (sysctl(mib, 2, &fscale, &size, NULL, 0)) { + warn("fscale"); + eval = 1; + fscale = FSCALE; + } + + mib[0] = CTL_HW; + mib[1] = HW_PHYSMEM64; + size = sizeof(memsize); + if (sysctl(mib, 2, &memsize, &size, NULL, 0)) { + warn("avail_start"); + eval = 1; + mempages = MEMPAGES; + } else + mempages = memsize / getpagesize(); + + mib[0] = CTL_KERN; + mib[1] = KERN_CCPU; + size = sizeof(xccpu); + if (sysctl(mib, 2, &xccpu, &size, NULL, 0)) { + warn("ccpu"); + eval = 1; + log_ccpu = LOG_CCPU; + } else + log_ccpu = log((double)xccpu / fscale); + + mib[0] = CTL_VM; + mib[1] = VM_MAXSLP; + size = sizeof(maxslp); + if (sysctl(mib, 2, &maxslp, &size, NULL, 0)) { + warn("maxslp"); + eval = 1; + maxslp = MAXSLP; + } + + mib[0] = CTL_VM; + mib[1] = VM_USPACE; + size = sizeof(uspace); + if (sysctl(mib, 2, &uspace, &size, NULL, 0)) { + warn("uspace"); + eval = 1; + uspace = USPACE; + } +} + +void +nlisterr(struct nlist nl[]) +{ + int i; + + (void)fprintf(stderr, "ps: nlist: can't find following symbols:"); + for (i = 0; nl[i].n_name != NULL; i++) + if (nl[i].n_value == 0) + (void)fprintf(stderr, " %s", nl[i].n_name); + (void)fprintf(stderr, "\n"); +} diff --git a/bin/ps/print.c b/bin/ps/print.c new file mode 100644 index 0000000..e725ef0 --- /dev/null +++ b/bin/ps/print.c @@ -0,0 +1,1413 @@ +/* $NetBSD: print.c,v 1.130 2018/09/19 15:20:39 maxv Exp $ */ + +/* + * Copyright (c) 2000, 2007 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Simon Burge. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + */ + +/* + * Copyright (c) 1990, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)print.c 8.6 (Berkeley) 4/16/94"; +#else +__RCSID("$NetBSD: print.c,v 1.130 2018/09/19 15:20:39 maxv Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/lwp.h> +#include <sys/proc.h> +#include <sys/stat.h> +#include <sys/ucred.h> +#include <sys/sysctl.h> + +#include <err.h> +#include <grp.h> +#include <kvm.h> +#include <math.h> +#include <nlist.h> +#include <pwd.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <tzfile.h> +#include <unistd.h> + +#include "ps.h" + +static char *cmdpart(char *); +static void printval(void *, VAR *, enum mode); +static int titlecmp(char *, char **); + +static void doubleprintorsetwidth(VAR *, double, int, enum mode); +static void intprintorsetwidth(VAR *, int, enum mode); +static void strprintorsetwidth(VAR *, const char *, enum mode); + +static time_t now; + +#define min(a,b) ((a) <= (b) ? (a) : (b)) + +static int +iwidth(u_int64_t v) +{ + u_int64_t nlim, lim; + int w = 1; + + for (lim = 10; v >= lim; lim = nlim) { + nlim = lim * 10; + w++; + if (nlim < lim) + break; + } + return w; +} + +static char * +cmdpart(char *arg0) +{ + char *cp; + + return ((cp = strrchr(arg0, '/')) != NULL ? cp + 1 : arg0); +} + +void +printheader(void) +{ + int len; + VAR *v; + struct varent *vent; + static int firsttime = 1; + static int noheader = 0; + + /* + * If all the columns have user-specified null headers, + * don't print the blank header line at all. + */ + if (firsttime) { + SIMPLEQ_FOREACH(vent, &displaylist, next) { + if (vent->var->header[0]) + break; + } + if (vent == NULL) { + noheader = 1; + firsttime = 0; + } + + } + if (noheader) + return; + + SIMPLEQ_FOREACH(vent, &displaylist, next) { + v = vent->var; + if (firsttime) { + len = strlen(v->header); + if (len > v->width) + v->width = len; + totwidth += v->width + 1; /* +1 for space */ + } + if (v->flag & LJUST) { + if (SIMPLEQ_NEXT(vent, next) == NULL) /* last one */ + (void)printf("%s", v->header); + else + (void)printf("%-*s", v->width, + v->header); + } else + (void)printf("%*s", v->width, v->header); + if (SIMPLEQ_NEXT(vent, next) != NULL) + (void)putchar(' '); + } + (void)putchar('\n'); + if (firsttime) { + firsttime = 0; + totwidth--; /* take off last space */ + } +} + +/* + * Return 1 if the command name in the argument vector (u-area) does + * not match the command name (p_comm) + */ +static int +titlecmp(char *name, char **argv) +{ + char *title; + int namelen; + + + /* no argument vector == no match; system processes/threads do that */ + if (argv == 0 || argv[0] == 0) + return (1); + + title = cmdpart(argv[0]); + + /* the basename matches */ + if (!strcmp(name, title)) + return (0); + + /* handle login shells, by skipping the leading - */ + if (title[0] == '-' && !strcmp(name, title + 1)) + return (0); + + namelen = strlen(name); + + /* handle daemons that report activity as daemonname: activity */ + if (argv[1] == 0 && + !strncmp(name, title, namelen) && + title[namelen + 0] == ':' && + title[namelen + 1] == ' ') + return (0); + + return (1); +} + +static void +doubleprintorsetwidth(VAR *v, double val, int prec, enum mode mode) +{ + int fmtlen; + + if (mode == WIDTHMODE) { + if (val < 0.0 && val < v->longestnd) { + fmtlen = (int)log10(-val) + prec + 2; + v->longestnd = val; + if (fmtlen > v->width) + v->width = fmtlen; + } else if (val > 0.0 && val > v->longestpd) { + fmtlen = (int)log10(val) + prec + 1; + v->longestpd = val; + if (fmtlen > v->width) + v->width = fmtlen; + } + } else { + (void)printf("%*.*f", v->width, prec, val); + } +} + +static void +intprintorsetwidth(VAR *v, int val, enum mode mode) +{ + int fmtlen; + + if (mode == WIDTHMODE) { + if (val < 0 && val < v->longestn) { + v->longestn = val; + fmtlen = iwidth(-val) + 1; + if (fmtlen > v->width) + v->width = fmtlen; + } else if (val > 0 && val > v->longestp) { + v->longestp = val; + fmtlen = iwidth(val); + if (fmtlen > v->width) + v->width = fmtlen; + } + } else + (void)printf("%*d", v->width, val); +} + +static void +strprintorsetwidth(VAR *v, const char *str, enum mode mode) +{ + int len; + + if (mode == WIDTHMODE) { + len = strlen(str); + if (len > v->width) + v->width = len; + } else { + if (v->flag & LJUST) + (void)printf("%-*.*s", v->width, v->width, str); + else + (void)printf("%*.*s", v->width, v->width, str); + } +} + +void +command(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *ki = pi->ki; + VAR *v; + int left; + char **argv, **p, *name; + + if (mode == WIDTHMODE) + return; + + v = ve->var; + if (SIMPLEQ_NEXT(ve, next) != NULL || termwidth != UNLIMITED) { + if (SIMPLEQ_NEXT(ve, next) == NULL) { + left = termwidth - (totwidth - v->width); + if (left < 1) /* already wrapped, just use std width */ + left = v->width; + } else + left = v->width; + } else + left = -1; + if (needenv && kd) { + argv = kvm_getenvv2(kd, ki, termwidth); + if ((p = argv) != NULL) { + while (*p) { + fmt_puts(*p, &left); + p++; + fmt_putc(' ', &left); + } + } + } + if (needcomm) { + if (pi->prefix) + (void)fmt_puts(pi->prefix, &left); + name = ki->p_comm; + if (!commandonly) { + argv = kvm_getargv2(kd, ki, termwidth); + if ((p = argv) != NULL) { + while (*p) { + fmt_puts(*p, &left); + p++; + fmt_putc(' ', &left); + if (v->flag & ARGV0) + break; + } + if (!(v->flag & ARGV0) && + titlecmp(name, argv)) { + /* + * append the real command name within + * parentheses, if the command name + * does not match the one in the + * argument vector + */ + fmt_putc('(', &left); + fmt_puts(name, &left); + fmt_putc(')', &left); + } + } else { + /* + * Commands that don't set an argv vector + * are printed with square brackets if they + * are system commands. Otherwise they are + * printed within parentheses. + */ + if (ki->p_flag & P_SYSTEM) { + fmt_putc('[', &left); + fmt_puts(name, &left); + fmt_putc(']', &left); + } else { + fmt_putc('(', &left); + fmt_puts(name, &left); + fmt_putc(')', &left); + } + } + } else { + fmt_puts(name, &left); + } + } + if (SIMPLEQ_NEXT(ve, next) != NULL && left > 0) + (void)printf("%*s", left, ""); +} + +void +groups(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *ki = pi->ki; + VAR *v; + int left, i; + char buf[16], *p; + + if (mode == WIDTHMODE) + return; + + v = ve->var; + if (SIMPLEQ_NEXT(ve, next) != NULL || termwidth != UNLIMITED) { + if (SIMPLEQ_NEXT(ve, next) == NULL) { + left = termwidth - (totwidth - v->width); + if (left < 1) /* already wrapped, just use std width */ + left = v->width; + } else + left = v->width; + } else + left = -1; + + if (ki->p_ngroups == 0) + fmt_putc('-', &left); + + for (i = 0; i < ki->p_ngroups; i++) { + (void)snprintf(buf, sizeof(buf), "%d", ki->p_groups[i]); + if (i) + fmt_putc(' ', &left); + for (p = &buf[0]; *p; p++) + fmt_putc(*p, &left); + } + + if (SIMPLEQ_NEXT(ve, next) != NULL && left > 0) + (void)printf("%*s", left, ""); +} + +void +groupnames(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *ki = pi->ki; + VAR *v; + int left, i; + const char *p; + + if (mode == WIDTHMODE) + return; + + v = ve->var; + if (SIMPLEQ_NEXT(ve, next) != NULL || termwidth != UNLIMITED) { + if (SIMPLEQ_NEXT(ve, next) == NULL) { + left = termwidth - (totwidth - v->width); + if (left < 1) /* already wrapped, just use std width */ + left = v->width; + } else + left = v->width; + } else + left = -1; + + if (ki->p_ngroups == 0) + fmt_putc('-', &left); + + for (i = 0; i < ki->p_ngroups; i++) { + if (i) + fmt_putc(' ', &left); + for (p = group_from_gid(ki->p_groups[i], 0); *p; p++) + fmt_putc(*p, &left); + } + + if (SIMPLEQ_NEXT(ve, next) != NULL && left > 0) + (void)printf("%*s", left, ""); +} + +void +ucomm(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + char buf[MAXPATHLEN], *p; + VAR *v; + + v = ve->var; + if (pi->prefix) + snprintf(p = buf, sizeof(buf), "%s%s", pi->prefix, k->p_comm); + else + p = k->p_comm; + strprintorsetwidth(v, p, mode); +} + +void +emul(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + + v = ve->var; + strprintorsetwidth(v, k->p_ename, mode); +} + +void +logname(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + + v = ve->var; + strprintorsetwidth(v, k->p_login, mode); +} + +void +state(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + int flag, is_zombie; + char *cp; + VAR *v; + char buf[16]; + + is_zombie = 0; + v = ve->var; + flag = k->p_flag; + cp = buf; + + /* + * NOTE: There are historical letters, which are no longer used: + * + * - W: indicated that process is swapped out. + * - L: indicated non-zero l_holdcnt (i.e. that process was + * prevented from swapping-out. + * + * These letters should not be used for new states to avoid + * conflicts with old applications which might depend on them. + */ + switch (k->p_stat) { + + case LSSTOP: + *cp = 'T'; + break; + + case LSSLEEP: + if (flag & L_SINTR) /* interruptable (long) */ + *cp = (int)k->p_slptime >= maxslp ? 'I' : 'S'; + else + *cp = 'D'; + break; + + case LSRUN: + case LSIDL: + *cp = 'R'; + break; + + case LSONPROC: + *cp = 'O'; + break; + + case LSZOMB: + *cp = 'Z'; + is_zombie = 1; + break; + + case LSSUSPENDED: + *cp = 'U'; + break; + + default: + *cp = '?'; + } + cp++; + if (k->p_nice < NZERO) + *cp++ = '<'; + else if (k->p_nice > NZERO) + *cp++ = 'N'; + if (flag & P_TRACED) + *cp++ = 'X'; + if (flag & P_WEXIT && !is_zombie) + *cp++ = 'E'; + if (flag & P_PPWAIT) + *cp++ = 'V'; + if (flag & P_SYSTEM) + *cp++ = 'K'; + if (k->p_eflag & EPROC_SLEADER) + *cp++ = 's'; + if (flag & P_SA) + *cp++ = 'a'; + else if (k->p_nlwps > 1) + *cp++ = 'l'; + if ((flag & P_CONTROLT) && k->p__pgid == k->p_tpgid) + *cp++ = '+'; + *cp = '\0'; + strprintorsetwidth(v, buf, mode); +} + +void +lstate(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_lwp *k = pi->li; + int flag; + char *cp; + VAR *v; + char buf[16]; + + v = ve->var; + flag = k->l_flag; + cp = buf; + + switch (k->l_stat) { + + case LSSTOP: + *cp = 'T'; + break; + + case LSSLEEP: + if (flag & L_SINTR) /* interruptible (long) */ + *cp = (int)k->l_slptime >= maxslp ? 'I' : 'S'; + else + *cp = 'D'; + break; + + case LSRUN: + case LSIDL: + *cp = 'R'; + break; + + case LSONPROC: + *cp = 'O'; + break; + + case LSZOMB: + case LSDEAD: + *cp = 'Z'; + break; + + case LSSUSPENDED: + *cp = 'U'; + break; + + default: + *cp = '?'; + } + cp++; + if (flag & L_SYSTEM) + *cp++ = 'K'; + if (flag & L_SA) + *cp++ = 'a'; + if (flag & L_DETACHED) + *cp++ = '-'; + *cp = '\0'; + strprintorsetwidth(v, buf, mode); +} + +void +pnice(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + + v = ve->var; + intprintorsetwidth(v, k->p_nice - NZERO, mode); +} + +void +pri(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_lwp *l = pi->li; + VAR *v; + + v = ve->var; + intprintorsetwidth(v, l->l_priority, mode); +} + +void +usrname(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + + v = ve->var; + strprintorsetwidth(v, user_from_uid(k->p_uid, 0), mode); +} + +void +runame(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + + v = ve->var; + strprintorsetwidth(v, user_from_uid(k->p_ruid, 0), mode); +} + +void +svuname(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + + v = ve->var; + strprintorsetwidth(v, user_from_uid(k->p_svuid, 0), mode); +} + +void +gname(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + + v = ve->var; + strprintorsetwidth(v, group_from_gid(k->p_gid, 0), mode); +} + +void +rgname(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + + v = ve->var; + strprintorsetwidth(v, group_from_gid(k->p_rgid, 0), mode); +} + +void +svgname(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + + v = ve->var; + strprintorsetwidth(v, group_from_gid(k->p_svgid, 0), mode); +} + +void +tdev(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + dev_t dev; + char buff[16]; + + v = ve->var; + dev = k->p_tdev; + if (dev == NODEV) { + if (mode == PRINTMODE) + (void)printf("%*s", v->width, "?"); + else + if (v->width < 2) + v->width = 2; + } else { + (void)snprintf(buff, sizeof(buff), + "%lld/%lld", (long long)major(dev), (long long)minor(dev)); + strprintorsetwidth(v, buff, mode); + } +} + +void +tname(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + dev_t dev; + const char *ttname; + int noctty; + + v = ve->var; + dev = k->p_tdev; + if (dev == NODEV || (ttname = devname(dev, S_IFCHR)) == NULL) { + if (mode == PRINTMODE) + (void)printf("%-*s", v->width, "?"); + else + if (v->width < 2) + v->width = 2; + } else { + noctty = !(k->p_eflag & EPROC_CTTY) ? 1 : 0; + if (mode == WIDTHMODE) { + int fmtlen; + + fmtlen = strlen(ttname) + noctty; + if (v->width < fmtlen) + v->width = fmtlen; + } else { + if (noctty) + (void)printf("%-*s-", v->width - 1, ttname); + else + (void)printf("%-*s", v->width, ttname); + } + } +} + +void +longtname(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + dev_t dev; + const char *ttname; + + v = ve->var; + dev = k->p_tdev; + if (dev == NODEV || (ttname = devname(dev, S_IFCHR)) == NULL) { + if (mode == PRINTMODE) + (void)printf("%-*s", v->width, "?"); + else + if (v->width < 2) + v->width = 2; + } else { + strprintorsetwidth(v, ttname, mode); + } +} + +void +started(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + time_t startt; + struct tm *tp; + char buf[100], *cp; + + v = ve->var; + if (!k->p_uvalid) { + if (mode == PRINTMODE) + (void)printf("%*s", v->width, "-"); + return; + } + + startt = k->p_ustart_sec; + tp = localtime(&startt); + if (now == 0) + (void)time(&now); + if (now - k->p_ustart_sec < SECSPERDAY) + /* I *hate* SCCS... */ + (void)strftime(buf, sizeof(buf) - 1, "%l:%" "M%p", tp); + else if (now - k->p_ustart_sec < DAYSPERWEEK * SECSPERDAY) + /* I *hate* SCCS... */ + (void)strftime(buf, sizeof(buf) - 1, "%a%" "I%p", tp); + else + (void)strftime(buf, sizeof(buf) - 1, "%e%b%y", tp); + /* %e and %l can start with a space. */ + cp = buf; + if (*cp == ' ') + cp++; + strprintorsetwidth(v, cp, mode); +} + +void +lstarted(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + time_t startt; + char buf[100]; + + v = ve->var; + if (!k->p_uvalid) { + /* + * Minimum width is less than header - we don't + * need to check it every time. + */ + if (mode == PRINTMODE) + (void)printf("%*s", v->width, "-"); + return; + } + startt = k->p_ustart_sec; + + /* assume all times are the same length */ + if (mode != WIDTHMODE || v->width == 0) { + (void)strftime(buf, sizeof(buf) -1, "%c", + localtime(&startt)); + strprintorsetwidth(v, buf, mode); + } +} + +void +elapsed(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + int32_t origseconds, secs, mins, hours, days; + int fmtlen, printed_something; + + v = ve->var; + if (k->p_uvalid == 0) { + origseconds = 0; + } else { + if (now == 0) + (void)time(&now); + origseconds = now - k->p_ustart_sec; + if (origseconds < 0) { + /* + * Don't try to be fancy if the machine's + * clock has been rewound to before the + * process "started". + */ + origseconds = 0; + } + } + + secs = origseconds; + mins = secs / SECSPERMIN; + secs %= SECSPERMIN; + hours = mins / MINSPERHOUR; + mins %= MINSPERHOUR; + days = hours / HOURSPERDAY; + hours %= HOURSPERDAY; + + if (mode == WIDTHMODE) { + if (origseconds == 0) + /* non-zero so fmtlen is calculated at least once */ + origseconds = 1; + + if (origseconds > v->longestp) { + v->longestp = origseconds; + + if (days > 0) { + /* +9 for "-hh:mm:ss" */ + fmtlen = iwidth(days) + 9; + } else if (hours > 0) { + /* +6 for "mm:ss" */ + fmtlen = iwidth(hours) + 6; + } else { + /* +3 for ":ss" */ + fmtlen = iwidth(mins) + 3; + } + + if (fmtlen > v->width) + v->width = fmtlen; + } + } else { + printed_something = 0; + fmtlen = v->width; + + if (days > 0) { + (void)printf("%*d", fmtlen - 9, days); + printed_something = 1; + } else if (fmtlen > 9) { + (void)printf("%*s", fmtlen - 9, ""); + } + if (fmtlen > 9) + fmtlen = 9; + + if (printed_something) { + (void)printf("-%.*d", fmtlen - 7, hours); + printed_something = 1; + } else if (hours > 0) { + (void)printf("%*d", fmtlen - 6, hours); + printed_something = 1; + } else if (fmtlen > 6) { + (void)printf("%*s", fmtlen - 6, ""); + } + if (fmtlen > 6) + fmtlen = 6; + + /* Don't need to set fmtlen or printed_something any more... */ + if (printed_something) { + (void)printf(":%.*d", fmtlen - 4, mins); + } else if (mins > 0) { + (void)printf("%*d", fmtlen - 3, mins); + } else if (fmtlen > 3) { + (void)printf("%*s", fmtlen - 3, "0"); + } + + (void)printf(":%.2d", secs); + } +} + +void +wchan(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_lwp *l = pi->li; + VAR *v; + + v = ve->var; + if (l->l_wmesg[0]) { + strprintorsetwidth(v, l->l_wmesg, mode); + v->width = min(v->width, KI_WMESGLEN); + } else { + if (mode == PRINTMODE) + (void)printf("%-*s", v->width, "-"); + } +} + +#define pgtok(a) (((a)*(size_t)getpagesize())/1024) + +void +vsize(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + + v = ve->var; + intprintorsetwidth(v, pgtok(k->p_vm_msize), mode); +} + +void +rssize(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + + v = ve->var; + /* XXX don't have info about shared */ + intprintorsetwidth(v, pgtok(k->p_vm_rssize), mode); +} + +void +p_rssize(struct pinfo *pi, VARENT *ve, enum mode mode) /* doesn't account for text */ +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + + v = ve->var; + intprintorsetwidth(v, pgtok(k->p_vm_rssize), mode); +} + +void +cpuid(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_lwp *l = pi->li; + VAR *v; + + v = ve->var; + intprintorsetwidth(v, l->l_cpuid, mode); +} + +static void +cputime1(int32_t secs, int32_t psecs, VAR *v, enum mode mode) +{ + int fmtlen; + + /* + * round and scale to 100's + */ + psecs = (psecs + 5000) / 10000; + secs += psecs / 100; + psecs = psecs % 100; + + if (mode == WIDTHMODE) { + /* + * Ugg, this is the only field where a value of 0 is longer + * than the column title. + * Use SECSPERMIN, because secs is divided by that when + * passed to iwidth(). + */ + if (secs == 0) + secs = SECSPERMIN; + + if (secs > v->longestp) { + v->longestp = secs; + /* "+6" for the ":%02ld.%02ld" in the printf() below */ + fmtlen = iwidth(secs / SECSPERMIN) + 6; + if (fmtlen > v->width) + v->width = fmtlen; + } + } else { + (void)printf("%*ld:%02ld.%02ld", v->width - 6, + (long)(secs / SECSPERMIN), (long)(secs % SECSPERMIN), + (long)psecs); + } +} + +void +cputime(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + int32_t secs; + int32_t psecs; /* "parts" of a second. first micro, then centi */ + + v = ve->var; + + /* + * This counts time spent handling interrupts. We could + * fix this, but it is not 100% trivial (and interrupt + * time fractions only work on the sparc anyway). XXX + */ + secs = k->p_rtime_sec; + psecs = k->p_rtime_usec; + if (sumrusage) { + secs += k->p_uctime_sec; + psecs += k->p_uctime_usec; + } + + cputime1(secs, psecs, v, mode); +} + +void +lcputime(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_lwp *l = pi->li; + VAR *v; + int32_t secs; + int32_t psecs; /* "parts" of a second. first micro, then centi */ + + v = ve->var; + + secs = l->l_rtime_sec; + psecs = l->l_rtime_usec; + + cputime1(secs, psecs, v, mode); +} + +void +pcpu(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + VAR *v; + double dbl; + + v = ve->var; + dbl = pi->pcpu; + doubleprintorsetwidth(v, dbl, (dbl >= 99.95) ? 0 : 1, mode); +} + +double +getpmem(const struct kinfo_proc2 *k) +{ + double fracmem; + int szptudot; + + if (!nlistread) + donlist(); + + /* XXX want pmap ptpages, segtab, etc. (per architecture) */ + szptudot = uspace/getpagesize(); + /* XXX don't have info about shared */ + fracmem = ((float)k->p_vm_rssize + szptudot)/mempages; + return (100.0 * fracmem); +} + +void +pmem(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + + v = ve->var; + doubleprintorsetwidth(v, getpmem(k), 1, mode); +} + +void +pagein(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + + v = ve->var; + intprintorsetwidth(v, k->p_uvalid ? k->p_uru_majflt : 0, mode); +} + +void +maxrss(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + VAR *v; + + v = ve->var; + /* No need to check width! */ + if (mode == PRINTMODE) + (void)printf("%*s", v->width, "-"); +} + +void +tsize(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_proc2 *k = pi->ki; + VAR *v; + + v = ve->var; + intprintorsetwidth(v, pgtok(k->p_vm_tsize), mode); +} + +/* + * Generic output routines. Print fields from various prototype + * structures. + */ +static void +printval(void *bp, VAR *v, enum mode mode) +{ + static char ofmt[32] = "%"; + int width, vok, fmtlen; + const char *fcp; + char *cp; + int64_t val; + u_int64_t uval; + + val = 0; /* XXXGCC -Wuninitialized [hpcarm] */ + uval = 0; /* XXXGCC -Wuninitialized [hpcarm] */ + + /* + * Note that the "INF127" check is nonsensical for types + * that are or can be signed. + */ +#define GET(type) (*(type *)bp) +#define CHK_INF127(n) (((n) > 127) && (v->flag & INF127) ? 127 : (n)) + +#define VSIGN 1 +#define VUNSIGN 2 +#define VPTR 3 + + if (mode == WIDTHMODE) { + vok = 0; + switch (v->type) { + case CHAR: + val = GET(char); + vok = VSIGN; + break; + case UCHAR: + uval = CHK_INF127(GET(u_char)); + vok = VUNSIGN; + break; + case SHORT: + val = GET(short); + vok = VSIGN; + break; + case USHORT: + uval = CHK_INF127(GET(u_short)); + vok = VUNSIGN; + break; + case INT32: + val = GET(int32_t); + vok = VSIGN; + break; + case INT: + val = GET(int); + vok = VSIGN; + break; + case UINT: + case UINT32: + uval = CHK_INF127(GET(u_int)); + vok = VUNSIGN; + break; + case LONG: + val = GET(long); + vok = VSIGN; + break; + case ULONG: + uval = CHK_INF127(GET(u_long)); + vok = VUNSIGN; + break; + case KPTR: + uval = GET(u_int64_t); + vok = VPTR; + break; + case KPTR24: + uval = GET(u_int64_t); + uval &= 0xffffff; + vok = VPTR; + break; + case INT64: + val = GET(int64_t); + vok = VSIGN; + break; + case UINT64: + uval = CHK_INF127(GET(u_int64_t)); + vok = VUNSIGN; + break; + + case SIGLIST: + default: + /* nothing... */; + } + switch (vok) { + case VSIGN: + if (val < 0 && val < v->longestn) { + v->longestn = val; + fmtlen = iwidth(-val) + 1; + if (fmtlen > v->width) + v->width = fmtlen; + } else if (val > 0 && val > v->longestp) { + v->longestp = val; + fmtlen = iwidth(val); + if (fmtlen > v->width) + v->width = fmtlen; + } + return; + case VUNSIGN: + if (uval > v->longestu) { + v->longestu = uval; + v->width = iwidth(uval); + } + return; + case VPTR: + fmtlen = 0; + while (uval > 0) { + uval >>= 4; + fmtlen++; + } + if (fmtlen > v->width) + v->width = fmtlen; + return; + } + } + + width = v->width; + cp = ofmt + 1; + fcp = v->fmt; + if (v->flag & LJUST) + *cp++ = '-'; + *cp++ = '*'; + while ((*cp++ = *fcp++) != '\0') + continue; + + switch (v->type) { + case CHAR: + (void)printf(ofmt, width, GET(char)); + return; + case UCHAR: + (void)printf(ofmt, width, CHK_INF127(GET(u_char))); + return; + case SHORT: + (void)printf(ofmt, width, GET(short)); + return; + case USHORT: + (void)printf(ofmt, width, CHK_INF127(GET(u_short))); + return; + case INT: + (void)printf(ofmt, width, GET(int)); + return; + case UINT: + (void)printf(ofmt, width, CHK_INF127(GET(u_int))); + return; + case LONG: + (void)printf(ofmt, width, GET(long)); + return; + case ULONG: + (void)printf(ofmt, width, CHK_INF127(GET(u_long))); + return; + case KPTR: + (void)printf(ofmt, width, GET(u_int64_t)); + return; + case KPTR24: + (void)printf(ofmt, width, GET(u_int64_t) & 0xffffff); + return; + case INT32: + (void)printf(ofmt, width, GET(int32_t)); + return; + case UINT32: + (void)printf(ofmt, width, CHK_INF127(GET(u_int32_t))); + return; + case SIGLIST: + { + sigset_t *s = (sigset_t *)(void *)bp; + size_t i; +#define SIGSETSIZE (sizeof(s->__bits) / sizeof(s->__bits[0])) + char buf[SIGSETSIZE * 8 + 1]; + + for (i = 0; i < SIGSETSIZE; i++) + (void)snprintf(&buf[i * 8], 9, "%.8x", + s->__bits[(SIGSETSIZE - 1) - i]); + + /* Skip leading zeroes */ + for (i = 0; buf[i] == '0'; i++) + continue; + + if (buf[i] == '\0') + i--; + strprintorsetwidth(v, buf + i, mode); +#undef SIGSETSIZE + } + return; + case INT64: + (void)printf(ofmt, width, GET(int64_t)); + return; + case UINT64: + (void)printf(ofmt, width, CHK_INF127(GET(u_int64_t))); + return; + default: + errx(EXIT_FAILURE, "unknown type %d", v->type); + } +#undef GET +#undef CHK_INF127 +} + +void +pvar(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + VAR *v = ve->var; + char *b = (v->flag & LWP) ? (char *)pi->li : (char *)pi->ki; + + if ((v->flag & UAREA) && !pi->ki->p_uvalid) { + if (mode == PRINTMODE) + (void)printf("%*s", v->width, "-"); + return; + } + + (void)printval(b + v->off, v, mode); +} + +void +putimeval(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + VAR *v = ve->var; + struct kinfo_proc2 *k = pi->ki; + char *b = (v->flag & LWP) ? (char *)pi->li : (char *)pi->ki; + ulong secs = *(uint32_t *)(b + v->off); + ulong usec = *(uint32_t *)(b + v->off + sizeof (uint32_t)); + int fmtlen; + + if (!k->p_uvalid) { + if (mode == PRINTMODE) + (void)printf("%*s", v->width, "-"); + return; + } + + if (mode == WIDTHMODE) { + if (secs == 0) + /* non-zero so fmtlen is calculated at least once */ + secs = 1; + if (secs > v->longestu) { + v->longestu = secs; + if (secs <= 999) + /* sss.ssssss */ + fmtlen = iwidth(secs) + 6 + 1; + else + /* hh:mm:ss.ss */ + fmtlen = iwidth((secs + 1) / SECSPERHOUR) + + 2 + 1 + 2 + 1 + 2 + 1; + if (fmtlen > v->width) + v->width = fmtlen; + } + return; + } + + if (secs < 999) + (void)printf( "%*lu.%.6lu", v->width - 6 - 1, secs, usec); + else { + uint h, m; + usec += 5000; + if (usec >= 1000000) { + usec -= 1000000; + secs++; + } + m = secs / SECSPERMIN; + secs -= m * SECSPERMIN; + h = m / MINSPERHOUR; + m -= h * MINSPERHOUR; + (void)printf( "%*u:%.2u:%.2lu.%.2lu", v->width - 9, h, m, secs, + usec / 10000u ); + } +} + +void +lname(struct pinfo *pi, VARENT *ve, enum mode mode) +{ + struct kinfo_lwp *l = pi->li; + VAR *v; + + v = ve->var; + if (l->l_name[0] != '\0') { + strprintorsetwidth(v, l->l_name, mode); + v->width = min(v->width, KI_LNAMELEN); + } else { + if (mode == PRINTMODE) + (void)printf("%-*s", v->width, "-"); + } +} diff --git a/bin/ps/ps.1 b/bin/ps/ps.1 new file mode 100644 index 0000000..635ec3b --- /dev/null +++ b/bin/ps/ps.1 @@ -0,0 +1,706 @@ +.\" $NetBSD: ps.1,v 1.109 2017/08/28 05:57:37 wiz Exp $ +.\" +.\" Copyright (c) 1980, 1990, 1991, 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. +.\" +.\" @(#)ps.1 8.3 (Berkeley) 4/18/94 +.\" +.Dd August 28, 2017 +.Dt PS 1 +.Os +.Sh NAME +.Nm ps +.Nd process status +.Sh SYNOPSIS +.Nm +.Op Fl AaCcdehjlmrSsTuvwx +.Op Fl k Ar key +.Op Fl M Ar core +.Op Fl N Ar system +.Op Fl O Ar fmt +.Op Fl o Ar fmt +.Op Fl p Ar pid +.Op Fl t Ar tty +.Op Fl U Ar user +.Op Fl W Ar swap +.Nm +.Fl L +.Sh DESCRIPTION +.Nm +displays a header line followed by lines containing information about +running processes. +By default, the display includes only processes that have +controlling terminals and are owned by your uid. +The default sort order of controlling terminal and +(among processes with the same controlling terminal) process ID +may be changed using the +.Fl k , Fl m , +or +.Fl r +options. +.Pp +The information displayed for each process +is selected based on a set of keywords (see the +.Fl L , +.Fl O , +and +.Fl o +options). +The default output format includes, for each process, the process' ID, +controlling terminal, CPU time (including both user and system time), +state, and associated command. +.Pp +The options are as follows: +.Bl -tag -width XNXsystemXX +.It Fl A +Display information about all processes. +This is equivalent to +.Fl a Fl x . +.It Fl a +Display information about other users' processes as well as your own. +Note that this does not display information about processes +without controlling terminals. +.It Fl C +Change the way the CPU percentage is calculated by using a +.Dq raw +CPU calculation that ignores +.Dq resident +time (this normally has no effect). +.It Fl c +Do not display full command with arguments, but only the +executable name. +This may be somewhat confusing; for example, all +.Xr sh 1 +scripts will show as +.Dq sh . +.It Fl d +Arrange processes into descendancy order and prefix each command with +indentation text showing sibling and parent/child relationships. +If either of the +.Fl m +and +.Fl r +options are also used, they control how sibling processes are sorted +relative to each other. +.It Fl e +Display the environment as well. +The environment for other +users' processes can only be displayed by the super-user. +.It Fl h +Repeat the information header as often as necessary to guarantee one +header per page of information. +.It Fl j +Print information associated with the following keywords: +.Ar user , pid , ppid , pgid , sess , jobc , state , tt , time , +and +.Ar command . +.It Fl k Ar key +Sort the output using the space or comma separated list of keywords. +Multiple sort keys may be specified, using any of the +.Fl k , Fl m , +or +.Fl r +options. +The default sort order is equivalent to +.Fl k Ar tdev,pid . +.It Fl L +List the set of available keywords. +.It Fl l +Display information associated with the following keywords: +.Ar uid , pid , ppid , cpu , pri , nice , vsz , rss , wchan , state , +.Ar tt , time , +and +.Ar command . +.It Fl M Ar core +Extract values from the specified core file instead of the running system. +.It Fl m +Sort by memory usage. +This is equivalent to +.Fl k Ar vsz . +.It Fl N Ar system +Extract the name list from the specified system instead of the default, +.Dq Pa /netbsd . +Ignored unless +.Fl M +is specified. +.It Fl O Ar fmt +Display information associated with the space or comma separated list +of keywords specified. +The +.Fl O +option does not suppress the default display; +it inserts additional keywords just after the +.Ar pid +keyword in the default display, or after the +.Ar pid +keyword (if any) in a non-default display specified before the +first use of the +.Fl O +flag. +Keywords inserted by multiple +.Fl O +options will be adjacent. +.Pp +An equals sign +.Pq Dq \&= +followed by a customised header string may be appended to a keyword, +as described in more detail under the +.Fl o +option. +.It Fl o Ar fmt +Display information associated with the space or comma separated list +of keywords specified. +Use of the +.Fl o +option suppresses the set of keywords that would be displayed by default, +or appends to the set of keywords specified by other options. +.Pp +An equals sign +.Pq Dq \&= +followed by a customised header string may be appended to a keyword. +This causes the printed header to use the specified string instead of +the default header associated with the keyword. +.Pp +Everything after the first equals sign is part of the customised +header text, and this may include embedded spaces +.Pq Dq " " , +commas +.Pq Dq \&, , +or equals signs +.Pq Dq \&= . +To specify multiple keywords with customised headers, use multiple +.Fl o +or +.Fl O +options. +.Pp +If all the keywords to be displayed have customised headers, +and all the customised headers are entirely empty, +then the header line is not printed at all. +.It Fl p Ar pid +Display information associated with the specified process ID. +.It Fl r +Sort by current CPU usage. +This is equivalent to +.Fl k Ar %cpu . +.It Fl S +Change the way the process time is calculated by summing all exited +children to their parent process. +.It Fl s +Display one line for each LWP, rather than one line for each process, +and display information associated with the following keywords: +.Ar uid , pid , ppid , cpu , lid , nlwp , pri , nice , vsz , rss , +.Ar wchan , lstate , tt , time , +and +.Ar command . +.It Fl T +Display information about processes attached to the device associated +with the standard input. +.It Fl t Ar tty +Display information about processes attached to the specified terminal +device. +Use a question mark +.Pq Dq \&? +for processes not attached to a +terminal device and a minus sign +.Pq Dq - +for processes that have +been revoked from their terminal device. +.It Fl U Ar user +Display processes belonging to the specified user, +given either as a user name or a uid. +.It Fl u +Display information associated with the following keywords: +.Ar user , pid , %cpu , %mem , vsz , rss , tt , state , start , time , +and +.Ar command . +The +.Fl u +option implies the +.Fl r +option. +.It Fl v +Display information associated with the following keywords: +.Ar pid , state , time , sl , re , pagein , vsz , rss , lim , tsiz , +.Ar %cpu , %mem , +and +.Ar command . +The +.Fl v +option implies the +.Fl m +option. +.It Fl W Ar swap +Extract swap information from the specified file instead of the default, +.Dq Pa /dev/drum . +Ignored unless +.Fl M +is specified. +.It Fl w +Use 132 columns to display information instead of the default, which +is your window size. +If the +.Fl w +option is specified more than once, +.Nm +will use as many columns as necessary without regard to your window size. +.It Fl x +Also display information about processes without controlling terminals. +.El +.Pp +A complete list of the available keywords are listed below. +Some of these keywords are further specified as follows: +.Bl -tag -width indent +.It Ar %cpu +The CPU utilization of the process; this is a decaying average over up to +a minute of previous (real) time. +Since the time base over which this is computed varies (since processes may +be very young) it is possible for the sum of all %CPU fields to exceed 100%. +.It Ar %mem +The percentage of real memory used by this process. +.It Ar flags +The flags (in hexadecimal) associated with the process as in +the include file +.In sys/proc.h : +.Bl -column P_NOCLDSTOP P_NOCLDSTOP compact +.It Dv "P_ADVLOCK" Ta No "0x00000001 process may hold a POSIX advisory lock" +.It Dv "P_CONTROLT" Ta No "0x00000002 process has a controlling terminal" +.It Dv "P_NOCLDSTOP" Ta No "0x00000008 no" Dv SIGCHLD No when children stop +.It Dv "P_PPWAIT" Ta No "0x00000010 parent is waiting for child to exec/exit" +.It Dv "P_PROFIL" Ta No "0x00000020 process has started profiling" +.It Dv "P_SELECT" Ta No "0x00000040 selecting; wakeup/waiting danger" +.It Dv "P_SINTR" Ta No "0x00000080 sleep is interruptible" +.It Dv "P_SUGID" Ta No "0x00000100 process had set id privileges since last exec" +.It Dv "P_SYSTEM" Ta No "0x00000200 system process: no sigs or stats" +.It Dv "P_TIMEOUT" Ta No "0x00000400 timing out during sleep" +.It Dv "P_TRACED" Ta No "0x00000800 process is being traced" +.It Dv "P_WAITED" Ta No "0x00001000 debugging process has waited for child" +.It Dv "P_WEXIT" Ta No "0x00002000 working on exiting" +.It Dv "P_EXEC" Ta No "0x00004000 process called" Xr execve 2 +.It Dv "P_OWEUPC" Ta No "0x00008000 owe process an addupc() call at next ast" +.\" the routine addupc is not documented in the man pages +.It Dv "P_NOCLDWAIT" Ta No "0x00020000 no zombies when children die" +.It Dv "P_32" Ta No "0x00040000 32-bit process (used on 64-bit kernels)" +.It Dv "P_BIGLOCK" Ta No "0x00080000 process needs kernel ``big lock'' to run" +.It Dv "P_INEXEC" Ta No "0x00100000 process is exec'ing and cannot be traced" +.El +.It Ar lim +The soft limit on memory used, specified via a call to +.Xr setrlimit 2 . +.It Ar lstart +The exact time the command started, using the +.Dq \&%c +format described in +.Xr strftime 3 . +.It Ar nice +The process scheduling increment (see +.Xr setpriority 2 ) . +.It Ar rss +the real memory (resident set) size of the process (in 1024 byte units). +.It Ar start +The time the command started. +If the command started less than 24 hours ago, the start time is +displayed using the +.Dq %l:%M%p +format described in +.Xr strftime 3 . +If the command started less than 7 days ago, the start time is +displayed using the +.Dq %a%p +format. +Otherwise, the start time is displayed using the +.Dq %e%b%y +format. +.It Ar state +The state is given by a sequence of letters, for example, +.Dq RNs . +The first letter indicates the run state of the process: +.Pp +.Bl -tag -width indent -compact +.It D +Marks a process in device or other short term, uninterruptible wait. +.It I +Marks a process that is idle (sleeping interruptibly for longer than about +.Dv MAXSLP +(default 20) seconds). +.It O +Marks a process running on a processor. +.It R +Marks a runnable process, or one that is in the process of creation. +.It S +Marks a process that is sleeping interruptibly for less than about +.Dv MAXSLP +(default 20) seconds. +.It T +Marks a stopped process. +.It U +Marks a suspended process. +.It Z +Marks a dead process that has exited, but not been waited for (a +.Dq zombie ) . +.El +.Pp +Additional characters after these, if any, indicate additional state +information: +.Pp +.Bl -tag -width indent -compact +.It + +The process is in the foreground process group of its control terminal. +.It - +The LWP is detached (can't be waited for). +.It < +The process has raised CPU scheduling priority. +.It a +The process is using scheduler activations (deprecated). +.It E +The process is in the process of exiting. +.It K +The process is a kernel thread or system process. +.It l +The process has multiple LWPs. +.It N +The process is niced (has reduced CPU scheduling priority) (see +.Xr setpriority 2 ) . +.It s +The process is a session leader. +.It V +The process is suspended during a +.Xr vfork 2 . +.It X +The process is being traced or debugged. +.El +.It Ar tt +An abbreviation for the pathname of the controlling terminal, if any. +The abbreviation consists of the two letters following +.Dq Pa /dev/tty +or, for the console, +.Dq co . +This is followed by a +.Dq \&- +if the process can no longer reach that +controlling terminal (i.e., it has been revoked). +.It Ar wchan +The event (an address in the system) on which a process waits. +When printed numerically, the initial part of the address is +trimmed off and the result is printed in hex, for example, 0x80324000 prints +as 324000. +.El +.Pp +When printing using the +.Ar command +keyword, a process that has exited and has a parent that has not yet +waited for the process (in other words, a zombie) is listed as +.Dq Aq defunct , +and a process which is blocked while trying to exit is listed as +.Dq Aq exiting . +.Pp +.Nm +will try to locate the processes' argument vector from the user +area in order to print the command name and arguments. +This method is not reliable because a process is allowed to destroy this +information. +The +.Ar ucomm +(accounting) keyword will always contain the real command name as +contained in the process structure's +.Va p_comm +field. +.Pp +If the command vector cannot be located (usually because it has not +been set, as is the case of system processes and/or kernel threads) +the command name is printed within square brackets. +.Pp +To indicate that the argument vector has been tampered with, +.Nm +will append the real command name to the output within parentheses +if the basename of the first argument in the argument vector +does not match the contents of the real command name. +.Pp +In addition, +.Nm +checks for the following two situations and does not append the +real command name parenthesized: +.Bl -tag -width indent +.It -shellname +The login process traditionally adds a +.Sq - +in front of the shell name to indicate a login shell. +.Nm +will not append parenthesized the command name if it matches with +the name in the first argument of the argument vector, skipping +the leading +.Sq - . +.It daemonname: current-activity +Daemon processes frequently report their current activity by setting +their name to be like +.Dq daemonname: current-activity . +.Nm +will not append parenthesized the command name, if the string preceding the +.Sq \&: +in the first argument of the argument vector matches the command name. +.El +.Sh KEYWORDS +The following is a complete list of the available keywords and their +meanings. +Several of them have aliases (keywords which are synonyms). +.Pp +.Bl -tag -width groupnames -compact +.It Ar %cpu +percentage CPU usage (alias +.Ar pcpu ) +.It Ar %mem +percentage memory usage (alias +.Ar pmem ) +.It Ar acflag +accounting flag (alias +.Ar acflg ) +.It Ar comm +command (the argv[0] value) +.It Ar command +command and arguments (alias +.Ar args ) +.It Ar cpu +short-term CPU usage factor (for scheduling) +.It Ar cpuid +CPU number the current process or lwp is running on. +.It Ar ctime +accumulated CPU time of all children that have exited +.It Ar egid +effective group id +.It Ar egroup +group name (from egid) +.It Ar emul +emulation name +.It Ar etime +elapsed time since the process was started, in the form +.Li [[dd-]hh:]mm:ss +.It Ar euid +effective user id +.It Ar euser +user name (from euid) +.It Ar flags +the process flags, in hexadecimal (alias +.Ar f ) +.It Ar gid +effective group id +.It Ar group +group name (from gid) +.It Ar groupnames +group names (from group access list) +.It Ar groups +group access list +.It Ar inblk +total blocks read (alias +.Ar inblock ) +.It Ar jobc +job control count +.It Ar ktrace +tracing flags +.It Ar ktracep +tracing vnode +.It Ar laddr +kernel virtual address of the +.Ft "struct lwp" +belonging to the LWP. +.It Ar lid +ID of the LWP +.It Ar lim +memory use limit +.It Ar lname +descriptive name of the LWP +.It Ar logname +login name of user who started the process (alias +.Ar login ) +.It Ar lstart +time started +.It Ar lstate +symbolic LWP state +.It Ar ltime +CPU time of the LWP +.It Ar majflt +total page faults +.It Ar minflt +total page reclaims +.It Ar msgrcv +total messages received (reads from pipes/sockets) +.It Ar msgsnd +total messages sent (writes on pipes/sockets) +.It Ar nice +nice value (alias +.Ar ni ) +.It Ar nivcsw +total involuntary context switches +.It Ar nlwp +number of LWPs in the process +.It Ar nsigs +total signals taken (alias +.Ar nsignals ) +.It Ar nvcsw +total voluntary context switches +.It Ar nwchan +wait channel (as an address) +.It Ar oublk +total blocks written (alias +.Ar oublock ) +.It Ar p_ru +resource usage pointer (valid only for zombie) +.It Ar paddr +kernel virtual address of the +.Ft "struct proc" +belonging to the process. +.It Ar pagein +pageins (same as majflt) +.It Ar pgid +process group number +.It Ar pid +process ID +.It Ar ppid +parent process ID +.It Ar pri +scheduling priority +.It Ar re +core residency time (in seconds; 127 = infinity) +.It Ar rgid +real group ID +.It Ar rlink +reverse link on run queue, or 0 +.It Ar rlwp +number of LWPs on a processor or run queue +.It Ar rss +resident set size +.It Ar rsz +resident set size + (text size / text use count) (alias +.Ar rssize ) +.It Ar ruid +real user ID +.It Ar ruser +user name (from ruid) +.It Ar sess +session pointer +.It Ar sid +session ID +.It Ar sig +pending signals (alias +.Ar pending ) +.It Ar sigcatch +caught signals (alias +.Ar caught ) +.It Ar sigignore +ignored signals (alias +.Ar ignored ) +.It Ar sigmask +blocked signals (alias +.Ar blocked ) +.It Ar sl +sleep time (in seconds; 127 = infinity) +.It Ar start +time started +.It Ar state +symbolic process state (alias +.Ar stat ) +.It Ar stime +accumulated system CPU time +.It Ar svgid +saved gid from a setgid executable +.It Ar svgroup +group name (from svgid) +.It Ar svuid +saved uid from a setuid executable +.It Ar svuser +user name (from svuid) +.It Ar tdev +control terminal device number +.It Ar time +accumulated CPU time, user + system (alias +.Ar cputime ) +.It Ar tpgid +control terminal process group ID +.It Ar tsess +control terminal session pointer +.It Ar tsiz +text size (in Kbytes) +.It Ar tt +control terminal name (two letter abbreviation) +.It Ar tty +full name of control terminal +.It Ar uaddr +kernel virtual address of the +.Ft "struct user" +belonging to the LWP. +.It Ar ucomm +name to be used for accounting +.It Ar uid +effective user ID +.It Ar upr +scheduling priority on return from system call (alias +.Ar usrpri ) +.It Ar user +user name (from uid) +.It Ar utime +accumulated user CPU time +.It Ar vsz +virtual size in Kbytes (alias +.Ar vsize ) +.It Ar wchan +wait channel (as a symbolic name) +.It Ar xstat +exit or stop status (valid only for stopped or zombie process) +.El +.Sh FILES +.Bl -tag -width /var/run/kvm.db -compact +.It Pa /dev +special files and device names +.It Pa /dev/drum +default swap device +.It Pa /var/run/dev.cdb +/dev name database +.It Pa /var/db/kvm.db +system name list database +.It Pa /netbsd +default system name list +.El +.Sh SEE ALSO +.Xr kill 1 , +.Xr pgrep 1 , +.Xr pkill 1 , +.Xr sh 1 , +.Xr w 1 , +.Xr kvm 3 , +.Xr strftime 3 , +.Xr dev_mkdb 8 , +.Xr pstat 8 +.Sh HISTORY +A +.Nm +utility appeared in +.At v3 +in section 8 of the manual. +.Sh BUGS +Since +.Nm +cannot run faster than the system and is run as any other scheduled +process, the information it displays can never be exact. diff --git a/bin/ps/ps.c b/bin/ps/ps.c new file mode 100644 index 0000000..da26fc1 --- /dev/null +++ b/bin/ps/ps.c @@ -0,0 +1,947 @@ +/* $NetBSD: ps.c,v 1.91 2018/04/11 18:52:29 christos Exp $ */ + +/* + * Copyright (c) 2000-2008 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Simon Burge. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + */ + +/* + * Copyright (c) 1990, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1990, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)ps.c 8.4 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: ps.c,v 1.91 2018/04/11 18:52:29 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/lwp.h> +#include <sys/proc.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/sysctl.h> + +#include <stddef.h> +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <kvm.h> +#include <limits.h> +#include <locale.h> +#include <math.h> +#include <nlist.h> +#include <paths.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "ps.h" + +/* + * ARGOPTS must contain all option characters that take arguments + * (except for 't'!) - it is used in kludge_oldps_options() + */ +#define GETOPTSTR "aAcCdeghjk:LlM:mN:O:o:p:rSsTt:U:uvW:wx" +#define ARGOPTS "kMNOopUW" + +struct varlist displaylist = SIMPLEQ_HEAD_INITIALIZER(displaylist); +struct varlist sortlist = SIMPLEQ_HEAD_INITIALIZER(sortlist); + +int eval; /* exit value */ +int sumrusage; /* -S */ +int termwidth; /* width of screen (0 == infinity) */ +int totwidth; /* calculated width of requested variables */ + +int needcomm, needenv, commandonly; +uid_t myuid; + +static struct kinfo_lwp + *pick_representative_lwp(struct kinfo_proc2 *, + struct kinfo_lwp *, int); +static struct kinfo_proc2 + *getkinfo_kvm(kvm_t *, int, int, int *); +static struct pinfo + *setpinfo(struct kinfo_proc2 *, int, int, int); +static char *kludge_oldps_options(char *); +static int pscomp(const void *, const void *); +static void scanvars(void); +__dead static void usage(void); +static int parsenum(const char *, const char *); +static void descendant_sort(struct pinfo *, int); + +char dfmt[] = "pid tt state time command"; +char jfmt[] = "user pid ppid pgid sess jobc state tt time command"; +char lfmt[] = "uid pid ppid cpu pri nice vsz rss wchan state tt time command"; +char sfmt[] = "uid pid ppid cpu lid nlwp pri nice vsz rss wchan lstate tt " + "ltime command"; +char ufmt[] = "user pid %cpu %mem vsz rss tt state start time command"; +char vfmt[] = "pid state time sl re pagein vsz rss lim tsiz %cpu %mem command"; + +const char *default_fmt = dfmt; + +struct varent *Opos = NULL; /* -O flag inserts after this point */ + +kvm_t *kd; + +static long long +ttyname2dev(const char *ttname, int *xflg, int *what) +{ + struct stat sb; + const char *ttypath; + char pathbuf[MAXPATHLEN]; + + ttypath = NULL; + if (strcmp(ttname, "?") == 0) { + *xflg = 1; + return KERN_PROC_TTY_NODEV; + } + if (strcmp(ttname, "-") == 0) + return KERN_PROC_TTY_REVOKE; + + if (strcmp(ttname, "co") == 0) + ttypath = _PATH_CONSOLE; + else if (strncmp(ttname, "pts/", 4) == 0 || + strncmp(ttname, "tty", 3) == 0) { + (void)snprintf(pathbuf, + sizeof(pathbuf), "%s%s", _PATH_DEV, ttname); + ttypath = pathbuf; + } else if (*ttname != '/') { + (void)snprintf(pathbuf, + sizeof(pathbuf), "%s%s", _PATH_TTY, ttname); + ttypath = pathbuf; + } else + ttypath = ttname; + *what = KERN_PROC_TTY; + if (stat(ttypath, &sb) == -1) { + devmajor_t pts; + int serrno; + + serrno = errno; + pts = getdevmajor("pts", S_IFCHR); + if (pts != NODEVMAJOR && strncmp(ttname, "pts/", 4) == 0) { + int ptsminor = atoi(ttname + 4); + + snprintf(pathbuf, sizeof(pathbuf), "pts/%d", ptsminor); + if (strcmp(pathbuf, ttname) == 0 && ptsminor >= 0) + return makedev(pts, ptsminor); + } + errno = serrno; + err(EXIT_FAILURE, "%s", ttypath); + } + if (!S_ISCHR(sb.st_mode)) + errx(EXIT_FAILURE, "%s: not a terminal", ttypath); + return sb.st_rdev; +} + +int +main(int argc, char *argv[]) +{ + struct kinfo_proc2 *kinfo; + struct pinfo *pinfo; + struct varent *vent; + struct winsize ws; + struct kinfo_lwp *kl, *l; + int ch, i, j, fmt, lineno, descendancy, nentries, nlwps; + long long flag; + int calc_pcpu, prtheader, wflag, what, xflg, rawcpu, showlwps; + char *nlistf, *memf, *swapf, errbuf[_POSIX2_LINE_MAX]; + char *ttname; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + if ((ioctl(STDOUT_FILENO, TIOCGWINSZ, (char *)&ws) == -1 && + ioctl(STDERR_FILENO, TIOCGWINSZ, (char *)&ws) == -1 && + ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ws) == -1) || + ws.ws_col == 0) + termwidth = 79; + else + termwidth = ws.ws_col - 1; + + if (argc > 1) + argv[1] = kludge_oldps_options(argv[1]); + + descendancy = fmt = prtheader = wflag = xflg = rawcpu = showlwps = 0; + what = KERN_PROC_UID; + flag = myuid = getuid(); + memf = nlistf = swapf = NULL; + + while ((ch = getopt(argc, argv, GETOPTSTR)) != -1) + switch((char)ch) { + case 'A': + /* "-A" shows all processes, like "-ax" */ + xflg = 1; + /*FALLTHROUGH*/ + case 'a': + what = KERN_PROC_ALL; + flag = 0; + break; + case 'c': + commandonly = 1; + break; + case 'd': + descendancy = 1; + break; + case 'e': /* XXX set ufmt */ + needenv = 1; + break; + case 'C': + rawcpu = 1; + break; + case 'g': + break; /* no-op */ + case 'h': + prtheader = ws.ws_row > 5 ? ws.ws_row : 22; + break; + case 'j': + parsefmt(jfmt); + fmt = 1; + jfmt[0] = '\0'; + break; + case 'k': + parsesort(optarg); + break; + case 'K': + break; /* no-op - was dontuseprocfs */ + case 'L': + showkey(); + return 0; + case 'l': + parsefmt(lfmt); + fmt = 1; + lfmt[0] = '\0'; + break; + case 'M': + memf = optarg; + break; + case 'm': + parsesort("vsz"); + break; + case 'N': + nlistf = optarg; + break; + case 'O': + /* + * If this is not the first -O option, insert + * just after the previous one. + * + * If there is no format yet, start with the default + * format, and insert after the pid column. + * + * If there is already a format, insert after + * the pid column, or at the end if there's no + * pid column. + */ + if (!Opos) { + if (!fmt) + parsefmt(default_fmt); + Opos = varlist_find(&displaylist, "pid"); + } + parsefmt_insert(optarg, &Opos); + fmt = 1; + break; + case 'o': + parsefmt(optarg); + fmt = 1; + break; + case 'p': + what = KERN_PROC_PID; + flag = parsenum(optarg, "process id"); + xflg = 1; + break; + case 'r': + parsesort("%cpu"); + break; + case 'S': + sumrusage = 1; + break; + case 's': + /* -L was already taken... */ + showlwps = 1; + default_fmt = sfmt; + break; + case 'T': + if ((ttname = ttyname(STDIN_FILENO)) == NULL) + errx(EXIT_FAILURE, "stdin: not a terminal"); + flag = ttyname2dev(ttname, &xflg, &what); + break; + case 't': + flag = ttyname2dev(optarg, &xflg, &what); + break; + case 'U': + if (*optarg != '\0') { + struct passwd *pw; + + what = KERN_PROC_UID; + pw = getpwnam(optarg); + if (pw == NULL) { + flag = parsenum(optarg, "user name"); + } else + flag = pw->pw_uid; + } + break; + case 'u': + parsefmt(ufmt); + parsesort("%cpu"); + fmt = 1; + ufmt[0] = '\0'; + break; + case 'v': + parsefmt(vfmt); + parsesort("vsz"); + fmt = 1; + vfmt[0] = '\0'; + break; + case 'W': + swapf = optarg; + break; + case 'w': + if (wflag) + termwidth = UNLIMITED; + else if (termwidth < 131) + termwidth = 131; + wflag++; + break; + case 'x': + xflg = 1; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + +#define BACKWARD_COMPATIBILITY +#ifdef BACKWARD_COMPATIBILITY + if (*argv) { + nlistf = *argv; + if (*++argv) { + memf = *argv; + if (*++argv) + swapf = *argv; + } + } +#endif + + if (memf == NULL) { + kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf); + donlist_sysctl(); + } else + kd = kvm_openfiles(nlistf, memf, swapf, O_RDONLY, errbuf); + + if (kd == NULL) + errx(EXIT_FAILURE, "%s", errbuf); + + if (!fmt) + parsefmt(default_fmt); + + /* Add default sort criteria */ + parsesort("tdev,pid"); + calc_pcpu = 0; + SIMPLEQ_FOREACH(vent, &sortlist, next) { + if (vent->var->flag & LWP || vent->var->type == UNSPECIFIED) + warnx("Cannot sort on %s, sort key ignored", + vent->var->name); + if (vent->var->type == PCPU) + calc_pcpu = 1; + } + if (!calc_pcpu) + SIMPLEQ_FOREACH(vent, &displaylist, next) + if (vent->var->type == PCPU) { + calc_pcpu = 1; + break; + } + + /* + * scan requested variables, noting what structures are needed. + */ + scanvars(); + + /* + * select procs + */ + if (!(kinfo = getkinfo_kvm(kd, what, flag, &nentries))) + errx(EXIT_FAILURE, "%s", kvm_geterr(kd)); + if (nentries == 0) { + printheader(); + return 1; + } + pinfo = setpinfo(kinfo, nentries, calc_pcpu, rawcpu); + + /* + * sort proc list + */ + qsort(pinfo, nentries, sizeof(struct pinfo), pscomp); + + /* + * We want things in descendant order + */ + if (descendancy) + descendant_sort(pinfo, nentries); + + /* + * For each proc, call each variable output function in + * "setwidth" mode to determine the widest element of + * the column. + */ + + for (i = 0; i < nentries; i++) { + struct pinfo *pi = &pinfo[i]; + struct kinfo_proc2 *ki = pi->ki; + + if (xflg == 0 && (ki->p_tdev == (uint32_t)NODEV || + (ki->p_flag & P_CONTROLT) == 0)) + continue; + + kl = kvm_getlwps(kd, ki->p_pid, ki->p_paddr, + sizeof(struct kinfo_lwp), &nlwps); + if (kl == 0) + nlwps = 0; + if (showlwps == 0) { + l = pick_representative_lwp(ki, kl, nlwps); + SIMPLEQ_FOREACH(vent, &displaylist, next) + OUTPUT(vent, l, pi, ki, WIDTHMODE); + } else { + /* The printing is done with the loops + * reversed, but here we don't need that, + * and this improves the code locality a bit. + */ + SIMPLEQ_FOREACH(vent, &displaylist, next) + for (j = 0; j < nlwps; j++) + OUTPUT(vent, &kl[j], pi, ki, WIDTHMODE); + } + } + /* + * Print header - AFTER determining process field widths. + * printheader() also adds up the total width of all + * fields the first time it's called. + */ + printheader(); + /* + * For each proc, call each variable output function in + * print mode. + */ + for (i = lineno = 0; i < nentries; i++) { + struct pinfo *pi = &pinfo[i]; + struct kinfo_proc2 *ki = pi->ki; + + if (xflg == 0 && (ki->p_tdev == (uint32_t)NODEV || + (ki->p_flag & P_CONTROLT ) == 0)) + continue; + kl = kvm_getlwps(kd, ki->p_pid, (u_long)ki->p_paddr, + sizeof(struct kinfo_lwp), &nlwps); + if (kl == 0) + nlwps = 0; + if (showlwps == 0) { + l = pick_representative_lwp(ki, kl, nlwps); + SIMPLEQ_FOREACH(vent, &displaylist, next) { + OUTPUT(vent, l, pi, ki, PRINTMODE); + if (SIMPLEQ_NEXT(vent, next) != NULL) + (void)putchar(' '); + } + (void)putchar('\n'); + if (prtheader && lineno++ == prtheader - 4) { + (void)putchar('\n'); + printheader(); + lineno = 0; + } + } else { + for (j = 0; j < nlwps; j++) { + SIMPLEQ_FOREACH(vent, &displaylist, next) { + OUTPUT(vent, &kl[j], pi, ki, PRINTMODE); + if (SIMPLEQ_NEXT(vent, next) != NULL) + (void)putchar(' '); + } + (void)putchar('\n'); + if (prtheader && lineno++ == prtheader - 4) { + (void)putchar('\n'); + printheader(); + lineno = 0; + } + } + } + } + return eval; +} + +static struct kinfo_lwp * +pick_representative_lwp(struct kinfo_proc2 *ki, struct kinfo_lwp *kl, int nlwps) +{ + int i, onproc, running, sleeping, stopped, suspended; + static struct kinfo_lwp zero_lwp; + + if (kl == 0) + return &zero_lwp; + + /* Trivial case: only one LWP */ + if (nlwps == 1) + return kl; + + switch (ki->p_realstat) { + case SSTOP: + case SACTIVE: + /* Pick the most live LWP */ + onproc = running = sleeping = stopped = suspended = -1; + for (i = 0; i < nlwps; i++) { + switch (kl[i].l_stat) { + case LSONPROC: + onproc = i; + break; + case LSRUN: + running = i; + break; + case LSSLEEP: + sleeping = i; + break; + case LSSTOP: + stopped = i; + break; + case LSSUSPENDED: + suspended = i; + break; + } + } + if (onproc != -1) + return &kl[onproc]; + if (running != -1) + return &kl[running]; + if (sleeping != -1) + return &kl[sleeping]; + if (stopped != -1) + return &kl[stopped]; + if (suspended != -1) + return &kl[suspended]; + break; + case SZOMB: + /* First will do */ + return kl; + break; + } + /* Error condition! */ + warnx("Inconsistent LWP state for process %d", ki->p_pid); + return kl; +} + +static struct kinfo_proc2 * +getkinfo_kvm(kvm_t *kdp, int what, int flag, int *nentriesp) +{ + + return (kvm_getproc2(kdp, what, flag, sizeof(struct kinfo_proc2), + nentriesp)); +} + +static struct pinfo * +setpinfo(struct kinfo_proc2 *ki, int nentries, int calc_pcpu, int rawcpu) +{ + struct pinfo *pi; + int i; + + pi = calloc(nentries, sizeof(*pi)); + if (pi == NULL) + err(EXIT_FAILURE, "calloc"); + + if (calc_pcpu && !nlistread) + donlist(); + + for (i = 0; i < nentries; i++) { + pi[i].ki = &ki[i]; + if (!calc_pcpu) + continue; + if (ki[i].p_swtime == 0 || ki[i].p_realstat == SZOMB) { + pi[i].pcpu = 0.0; + continue; + } + pi[i].pcpu = 100.0 * (double)ki[i].p_pctcpu / fscale; + if (!rawcpu) + pi[i].pcpu /= 1.0 - exp(ki[i].p_swtime * log_ccpu); + } + + return pi; +} + +static void +scanvars(void) +{ + struct varent *vent; + VAR *v; + + SIMPLEQ_FOREACH(vent, &displaylist, next) { + v = vent->var; + if (v->flag & COMM) { + needcomm = 1; + break; + } + } +} + +static int +pscomp(const void *a, const void *b) +{ + const struct pinfo *pa = (const struct pinfo *)a; + const struct pinfo *pb = (const struct pinfo *)b; + const struct kinfo_proc2 *ka = pa->ki; + const struct kinfo_proc2 *kb = pb->ki; + + int i; + int64_t i64; + VAR *v; + struct varent *ve; + const sigset_t *sa, *sb; + +#define V_SIZE(k) ((k)->p_vm_msize) +#define RDIFF_N(t, n) \ + if (((const t *)((const char *)ka + v->off))[n] > ((const t *)((const char *)kb + v->off))[n]) \ + return 1; \ + if (((const t *)((const char *)ka + v->off))[n] < ((const t *)((const char *)kb + v->off))[n]) \ + return -1; + +#define RDIFF(type) RDIFF_N(type, 0); continue + + SIMPLEQ_FOREACH(ve, &sortlist, next) { + v = ve->var; + if (v->flag & LWP) + /* LWP structure not available (yet) */ + continue; + /* Sort on pvar() fields, + a few others */ + switch (v->type) { + case CHAR: + RDIFF(char); + case UCHAR: + RDIFF(u_char); + case SHORT: + RDIFF(short); + case USHORT: + RDIFF(ushort); + case INT: + RDIFF(int); + case UINT: + RDIFF(uint); + case LONG: + RDIFF(long); + case ULONG: + RDIFF(ulong); + case INT32: + RDIFF(int32_t); + case UINT32: + RDIFF(uint32_t); + case SIGLIST: + sa = (const void *)((const char *)ka + v->off); + sb = (const void *)((const char *)kb + v->off); + i = 0; + do { + if (sa->__bits[i] > sb->__bits[i]) + return 1; + if (sa->__bits[i] < sb->__bits[i]) + return -1; + i++; + } while (i < (int)__arraycount(sa->__bits)); + continue; + case INT64: + RDIFF(int64_t); + case KPTR: + case KPTR24: + case UINT64: + RDIFF(uint64_t); + case TIMEVAL: + /* compare xxx_sec then xxx_usec */ + RDIFF_N(uint32_t, 0); + RDIFF_N(uint32_t, 1); + continue; + case CPUTIME: + i64 = ka->p_rtime_sec * 1000000 + ka->p_rtime_usec; + i64 -= kb->p_rtime_sec * 1000000 + kb->p_rtime_usec; + if (sumrusage) { + i64 += ka->p_uctime_sec * 1000000 + + ka->p_uctime_usec; + i64 -= kb->p_uctime_sec * 1000000 + + kb->p_uctime_usec; + } + if (i64 != 0) + return i64 > 0 ? 1 : -1; + continue; + case PCPU: + i = pb->pcpu - pa->pcpu; + if (i != 0) + return i; + continue; + case VSIZE: + i = V_SIZE(kb) - V_SIZE(ka); + if (i != 0) + return i; + continue; + + default: + /* Ignore everything else */ + break; + } + } + return 0; + +#undef VSIZE +} + +/* + * ICK (all for getopt), would rather hide the ugliness + * here than taint the main code. + * + * ps foo -> ps -foo + * ps 34 -> ps -p34 + * + * The old convention that 't' with no trailing tty arg means the user's + * tty, is only supported if argv[1] doesn't begin with a '-'. This same + * feature is available with the option 'T', which takes no argument. + */ +static char * +kludge_oldps_options(char *s) +{ + size_t len; + char *newopts, *ns, *cp; + + len = strlen(s); + if ((newopts = ns = malloc(len + 3)) == NULL) + err(EXIT_FAILURE, NULL); + /* + * options begin with '-' + */ + if (*s != '-') + *ns++ = '-'; /* add option flag */ + /* + * gaze to end of argv[1] + */ + cp = s + len - 1; + /* + * if the last letter is a 't' flag and there are no other option + * characters that take arguments (eg U, p, o) in the option + * string and the option string doesn't start with a '-' then + * convert to 'T' (meaning *this* terminal, i.e. ttyname(0)). + */ + if (*cp == 't' && *s != '-' && strpbrk(s, ARGOPTS) == NULL) + *cp = 'T'; + else { + /* + * otherwise check for trailing number, which *may* be a + * pid. + */ + while (cp >= s && isdigit((unsigned char)*cp)) + --cp; + } + cp++; + memmove(ns, s, (size_t)(cp - s)); /* copy up to trailing number */ + ns += cp - s; + /* + * if there's a trailing number, and not a preceding 'p' (pid) or + * 't' (tty) flag, then assume it's a pid and insert a 'p' flag. + */ + if (isdigit((unsigned char)*cp) && + (cp == s || (cp[-1] != 'U' && cp[-1] != 't' && cp[-1] != 'p' && + cp[-1] != '/' && (cp - 1 == s || cp[-2] != 't')))) + *ns++ = 'p'; + /* and append the number */ + (void)strcpy(ns, cp); /* XXX strcpy is safe here */ + + return (newopts); +} + +static int +parsenum(const char *str, const char *msg) +{ + char *ep; + unsigned long ul; + + ul = strtoul(str, &ep, 0); + + if (*str == '\0' || *ep != '\0') + errx(EXIT_FAILURE, "Invalid %s: `%s'", msg, str); + + if (ul > INT_MAX) + errx(EXIT_FAILURE, "Out of range %s: `%s'", msg, str); + + return (int)ul; +} + +static void +descendant_sort(struct pinfo *ki, int items) +{ + int dst, lvl, maxlvl, n, ndst, nsrc, siblings, src; + unsigned char *path; + struct pinfo kn; + + /* + * First, sort the entries by descendancy, tracking the descendancy + * depth in the level field. + */ + src = 0; + maxlvl = 0; + while (src < items) { + if (ki[src].level) { + src++; + continue; + } + for (nsrc = 1; src + nsrc < items; nsrc++) + if (!ki[src + nsrc].level) + break; + + for (dst = 0; dst < items; dst++) { + if (ki[dst].ki->p_pid == ki[src].ki->p_pid) + continue; + if (ki[dst].ki->p_pid == ki[src].ki->p_ppid) + break; + } + + if (dst == items) { + src += nsrc; + continue; + } + + for (ndst = 1; dst + ndst < items; ndst++) + if (ki[dst + ndst].level <= ki[dst].level) + break; + + for (n = src; n < src + nsrc; n++) { + ki[n].level += ki[dst].level + 1; + if (maxlvl < ki[n].level) + maxlvl = ki[n].level; + } + + while (nsrc) { + if (src < dst) { + kn = ki[src]; + memmove(ki + src, ki + src + 1, + (dst - src + ndst - 1) * sizeof *ki); + ki[dst + ndst - 1] = kn; + nsrc--; + dst--; + ndst++; + } else if (src != dst + ndst) { + kn = ki[src]; + memmove(ki + dst + ndst + 1, ki + dst + ndst, + (src - dst - ndst) * sizeof *ki); + ki[dst + ndst] = kn; + ndst++; + nsrc--; + src++; + } else { + ndst += nsrc; + src += nsrc; + nsrc = 0; + } + } + } + + /* + * Now populate prefix (instead of level) with the command + * prefix used to show descendancies. + */ + path = malloc((maxlvl + 7) / 8); + memset(path, '\0', (maxlvl + 7) / 8); + for (src = 0; src < items; src++) { + if ((lvl = ki[src].level) == 0) { + ki[src].prefix = NULL; + continue; + } + if ((ki[src].prefix = malloc(lvl * 2 + 1)) == NULL) + errx(EXIT_FAILURE, "malloc failed"); + for (n = 0; n < lvl - 2; n++) { + ki[src].prefix[n * 2] = + path[n / 8] & 1 << (n % 8) ? '|' : ' '; + ki[src].prefix[n * 2 + 1] = ' '; + + } + if (n == lvl - 2) { + /* Have I any more siblings? */ + for (siblings = 0, dst = src + 1; dst < items; dst++) { + if (ki[dst].level > lvl) + continue; + if (ki[dst].level == lvl) + siblings = 1; + break; + } + if (siblings) + path[n / 8] |= 1 << (n % 8); + else + path[n / 8] &= ~(1 << (n % 8)); + ki[src].prefix[n * 2] = siblings ? '|' : '`'; + ki[src].prefix[n * 2 + 1] = '-'; + n++; + } + strcpy(ki[src].prefix + n * 2, "- "); + } + free(path); +} + +static void +usage(void) +{ + + (void)fprintf(stderr, + "usage:\t%s\n\t %s\n\t%s\n", + "ps [-AaCcdehjlmrSsTuvwx] [-k key] [-M core] [-N system] [-O fmt]", + "[-o fmt] [-p pid] [-t tty] [-U user] [-W swap]", + "ps -L"); + exit(1); + /* NOTREACHED */ +} diff --git a/bin/ps/ps.h b/bin/ps/ps.h new file mode 100644 index 0000000..7d8ddb8 --- /dev/null +++ b/bin/ps/ps.h @@ -0,0 +1,104 @@ +/* $NetBSD: ps.h,v 1.29 2016/12/02 21:59:03 christos Exp $ */ + +/*- + * Copyright (c) 1990, 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. + * + * @(#)ps.h 8.1 (Berkeley) 5/31/93 + */ + +#include <sys/queue.h> + +#define UNLIMITED 0 /* unlimited terminal width */ + +enum mode { + PRINTMODE = 0, /* print values */ + WIDTHMODE = 1 /* determine width of column */ +}; + +enum type { + UNSPECIFIED, + CHAR, UCHAR, SHORT, USHORT, INT, UINT, LONG, ULONG, + KPTR, KPTR24, INT32, UINT32, SIGLIST, INT64, UINT64, + TIMEVAL, CPUTIME, PCPU, VSIZE +}; + +/* Variables. */ +typedef SIMPLEQ_HEAD(varlist, varent) VARLIST; + +typedef struct varent { + SIMPLEQ_ENTRY(varent) next; + struct var *var; +} VARENT; + +struct pinfo { + struct kinfo_proc2 *ki; + struct kinfo_lwp *li; + char *prefix; + int level; + double pcpu; +}; + +typedef struct var { + const char *name; /* name(s) of variable */ + const char *header; /* header, possibly changed from default */ +#define COMM 0x01 /* needs exec arguments and environment (XXX) */ +#define ARGV0 0x02 /* only print argv[0] */ +#define LJUST 0x04 /* left adjust on output (trailing blanks) */ +#define INF127 0x08 /* 127 = infinity: if > 127, print 127. */ +#define LWP 0x10 /* dispatch to kinfo_lwp routine */ +#define UAREA 0x20 /* need to check p_uvalid */ +#define ALIAS 0x40 /* entry is alias for 'header' */ + u_int flag; + /* output routine */ + void (*oproc)(struct pinfo *pi, struct varent *, enum mode); + /* + * The following (optional) elements are hooks for passing information + * to the generic output routine: pvar (that which prints simple + * elements from struct kinfo_proc2). + */ + int off; /* offset in structure */ + enum type type; /* type of element */ + const char *fmt; /* printf format */ + + /* current longest element */ + int width; /* printing width */ + int64_t longestp; /* longest positive signed value */ + int64_t longestn; /* longest negative signed value */ + u_int64_t longestu; /* longest unsigned value */ + double longestpd; /* longest positive double */ + double longestnd; /* longest negative double */ +} VAR; + + +#define OUTPUT(vent, kl, pi, ki, mode) do { \ + if ((vent)->var->flag & LWP) \ + pi->li = kl; \ + ((vent)->var->oproc)(pi, (vent), (mode)); \ + } while (/*CONSTCOND*/ 0) + +#include "extern.h" diff --git a/bin/pwd/pwd.1 b/bin/pwd/pwd.1 new file mode 100644 index 0000000..bbdaf32 --- /dev/null +++ b/bin/pwd/pwd.1 @@ -0,0 +1,110 @@ +.\" $NetBSD: pwd.1,v 1.27 2017/07/04 06:50:37 wiz Exp $ +.\" +.\" Copyright (c) 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. +.\" +.\" @(#)pwd.1 8.2 (Berkeley) 4/28/95 +.\" +.Dd August 12, 2016 +.Dt PWD 1 +.Os +.Sh NAME +.Nm pwd +.Nd return working directory name +.Sh SYNOPSIS +.Nm +.Op Fl LP +.Sh DESCRIPTION +.Nm +writes the absolute pathname of the current working directory to +the standard output. +.Pp +The following options are available: +.Bl -tag -width indent +.It Fl L +If the +.Ev PWD +environment variable is an absolute pathname that contains +neither "/./" nor "/../" and references the current directory, then +.Ev PWD +is assumed to be the name of the current directory. +.It Fl P +Print the physical path to the current working directory, with symbolic +links in the path resolved. +.El +.Pp +The default for the +.Nm +command is +.Fl P . +.Pp +.Nm +is usually provided as a shell builtin (which may have a different +default). +.Sh EXIT STATUS +.Ex -std pwd +.Sh SEE ALSO +.Xr cd 1 , +.Xr csh 1 , +.Xr ksh 1 , +.Xr sh 1 , +.Xr getcwd 3 +.Sh STANDARDS +The +.Nm +utility is expected to be conforming to +.St -p1003.1 , +except that the default is +.Fl P +not +.Fl L . +.Sh HISTORY +A +.Nm +utility appeared in +.At v5 . +.Sh BUGS +In +.Xr csh 1 +the command +.Ic dirs +is always faster (although it can give a different answer in the rare case +that the current directory or a containing directory was moved after +the shell descended into it). +.Pp +.Nm +.Fl L +relies on the file system having unique inode numbers. +If this is not true (e.g., on FAT file systems) then +.Nm +.Fl L +may fail to detect that +.Ev PWD +is incorrect. diff --git a/bin/pwd/pwd.c b/bin/pwd/pwd.c new file mode 100644 index 0000000..27888eb --- /dev/null +++ b/bin/pwd/pwd.c @@ -0,0 +1,143 @@ +/* $NetBSD: pwd.c,v 1.22 2011/08/29 14:51:19 joerg Exp $ */ + +/* + * Copyright (c) 1991, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1991, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)pwd.c 8.3 (Berkeley) 4/1/94"; +#else +__RCSID("$NetBSD: pwd.c,v 1.22 2011/08/29 14:51:19 joerg Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <err.h> +#include <errno.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static char *getcwd_logical(void); +__dead static void usage(void); + +/* + * Note that EEE Std 1003.1, 2003 requires that the default be -L. + * This is inconsistent with the historic behaviour of everything + * except the ksh builtin. + * To avoid breaking scripts the default has been kept as -P. + * (Some scripts run /bin/pwd in order to get 'pwd -P'.) + */ + +int +main(int argc, char *argv[]) +{ + int ch, lFlag; + const char *p; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + lFlag = 0; + while ((ch = getopt(argc, argv, "LP")) != -1) { + switch (ch) { + case 'L': + lFlag = 1; + break; + case 'P': + lFlag = 0; + break; + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc != 0) + usage(); + + if (lFlag) + p = getcwd_logical(); + else + p = NULL; + if (p == NULL) + p = getcwd(NULL, 0); + + if (p == NULL) + err(EXIT_FAILURE, NULL); + + (void)printf("%s\n", p); + + exit(EXIT_SUCCESS); + /* NOTREACHED */ +} + +static char * +getcwd_logical(void) +{ + char *pwd; + struct stat s_pwd, s_dot; + + /* Check $PWD -- if it's right, it's fast. */ + pwd = getenv("PWD"); + if (pwd == NULL) + return NULL; + if (pwd[0] != '/') + return NULL; + if (strstr(pwd, "/./") != NULL) + return NULL; + if (strstr(pwd, "/../") != NULL) + return NULL; + if (stat(pwd, &s_pwd) == -1 || stat(".", &s_dot) == -1) + return NULL; + if (s_pwd.st_dev != s_dot.st_dev || s_pwd.st_ino != s_dot.st_ino) + return NULL; + return pwd; +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: %s [-LP]\n", getprogname()); + exit(EXIT_FAILURE); + /* NOTREACHED */ +} diff --git a/bin/rm/rm.1 b/bin/rm/rm.1 new file mode 100644 index 0000000..d7c5720 --- /dev/null +++ b/bin/rm/rm.1 @@ -0,0 +1,215 @@ +.\" $NetBSD: rm.1,v 1.29 2017/07/03 21:33:23 wiz Exp $ +.\" +.\" Copyright (c) 1990, 1993, 1994, 2003 +.\" 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. +.\" +.\" @(#)rm.1 8.5 (Berkeley) 12/5/94 +.\" +.Dd August 12, 2016 +.Dt RM 1 +.Os +.Sh NAME +.Nm rm +.Nd remove directory entries +.Sh SYNOPSIS +.Nm +.Op Fl f | Fl i +.Op Fl dPRrvWx +.Ar +.Sh DESCRIPTION +The +.Nm +utility attempts to remove the non-directory type files specified on the +command line. +If the permissions of the file do not permit writing, and the standard +input device is a terminal, the user is prompted (on the standard error +output) for confirmation. +.Pp +The options are as follows: +.Bl -tag -width flag +.It Fl d +Attempt to remove directories as well as other types of files. +.It Fl f +Attempt to remove the files without prompting for confirmation, +regardless of the file's permissions. +If the file does not exist, do not display a diagnostic message or modify +the exit status to reflect an error. +The +.Fl f +option overrides any previous +.Fl i +options. +.It Fl i +Request confirmation before attempting to remove each file, regardless of +the file's permissions, or whether or not the standard input device is a +terminal. +The +.Fl i +option overrides any previous +.Fl f +options. +.It Fl P +Overwrite regular files before deleting them. +Files are overwritten three times, first with the byte pattern 0xff, +then 0x00, and then with random data, before they are deleted. +Some care is taken to ensure that the data are actually written to +disk, but this cannot be guaranteed, even on traditional filesystems; +on log-structured filesystems or if any block-journaling scheme is +in use, this option is completely useless. +If the file cannot be +overwritten, it will not be removed. +.It Fl R +Attempt to remove the file hierarchy rooted in each file argument. +The +.Fl R +option implies the +.Fl d +option. +If the +.Fl i +option is specified, the user is prompted for confirmation before +each directory's contents are processed (as well as before the attempt +is made to remove the directory). +If the user does not respond affirmatively, the file hierarchy rooted in +that directory is skipped. +.It Fl r +Equivalent to +.Fl R . +.It Fl v +Cause +.Nm +to be verbose, showing files as they are processed. +.It Fl W +Attempts to undelete the named files. +Currently, this option can only be used to recover +files covered by whiteouts. +.It Fl x +When removing a hierarchy, do not cross mount points. +.El +.Pp +The +.Nm +utility removes symbolic links, not the files referenced by the links. +.Pp +It is an error to attempt to remove the files ``.'' and ``..''. +.Sh EXIT STATUS +The +.Nm +utility exits 0 if all of the named files or file hierarchies were removed, +or if the +.Fl f +option was specified and all of the existing files or file hierarchies were +removed. +If an error occurs, +.Nm +exits with a value >0. +.Sh EXAMPLES +.Nm +uses +.Xr getopt 3 +standard argument processing. +Removing filenames that begin with a dash +.Pq e.g., Ar -file +in the current directory which might otherwise be taken as option flags to +.Nm +can be accomplished as follows: +.Pp +.Ic "rm -- -file" +.Pp +or +.Pp +.Ic "rm ./-file" +.Sh COMPATIBILITY +The +.Nm +utility differs from historical implementations in that the +.Fl f +option only masks attempts to remove non-existent files instead of +masking a large variety of errors. +.Pp +Also, historical +.Bx +implementations prompted on the standard output, +not the standard error output. +.Sh SEE ALSO +.Xr rmdir 1 , +.Xr undelete 2 , +.Xr unlink 2 , +.Xr fts 3 , +.Xr getopt 3 , +.Xr symlink 7 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. +The +.Fl v +and +.Fl x +options are extensions. +.Pp +The +.Fl P +option attempts to conform to U.S. DoD 5220-22.M, "National Industrial +Security Program Operating Manual" ("NISPOM") as updated by Change +2 and the July 23, 2003 "Clearing & Sanitization Matrix". +However, unlike earlier revisions of NISPOM, the 2003 matrix imposes +requirements which make it clear that the standard does not and +can not apply to the erasure of individual files, in particular +requirements relating to spare sector management for an entire +magnetic disk. +.Em Because these requirements are not met, the +.Fl P +.Em option does not conform to the standard . +.Sh HISTORY +An +.Nm +utility appeared in +.At v1 . +.Sh BUGS +The +.Fl P +option assumes that the underlying file system is a fixed-block file +system. +FFS is a fixed-block file system, LFS is not. +In addition, only regular files are overwritten, other types of files +are not. +Recent research indicates that as many as 35 overwrite passes with +carefully chosen data patterns may be necessary to actually prevent +recovery of data from a magnetic disk. +Thus the +.Fl P +option is likely both insufficient for its design purpose and far +too costly for default operation. +However, it will at least prevent the recovery of data from FFS +volumes with +.Xr fsdb 8 . diff --git a/bin/rm/rm.c b/bin/rm/rm.c new file mode 100644 index 0000000..c1d2e8d --- /dev/null +++ b/bin/rm/rm.c @@ -0,0 +1,611 @@ +/* $NetBSD: rm.c,v 1.53 2013/04/26 18:43:22 christos Exp $ */ + +/*- + * Copyright (c) 1990, 1993, 1994, 2003 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1990, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)rm.c 8.8 (Berkeley) 4/27/95"; +#else +__RCSID("$NetBSD: rm.c,v 1.53 2013/04/26 18:43:22 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <fts.h> +#include <grp.h> +#include <locale.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static int dflag, eval, fflag, iflag, Pflag, stdin_ok, vflag, Wflag; +static int xflag; +static sig_atomic_t pinfo; + +static int check(char *, char *, struct stat *); +static void checkdot(char **); +static void progress(int); +static void rm_file(char **); +static int rm_overwrite(char *, struct stat *); +static void rm_tree(char **); +__dead static void usage(void); + +/* + * For the sake of the `-f' flag, check whether an error number indicates the + * failure of an operation due to an non-existent file, either per se (ENOENT) + * or because its filename argument was illegal (ENAMETOOLONG, ENOTDIR). + */ +#define NONEXISTENT(x) \ + ((x) == ENOENT || (x) == ENAMETOOLONG || (x) == ENOTDIR) + +/* + * rm -- + * This rm is different from historic rm's, but is expected to match + * POSIX 1003.2 behavior. The most visible difference is that -f + * has two specific effects now, ignore non-existent files and force + * file removal. + */ +int +main(int argc, char *argv[]) +{ + int ch, rflag; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + Pflag = rflag = xflag = 0; + while ((ch = getopt(argc, argv, "dfiPRrvWx")) != -1) + switch (ch) { + case 'd': + dflag = 1; + break; + case 'f': + fflag = 1; + iflag = 0; + break; + case 'i': + fflag = 0; + iflag = 1; + break; + case 'P': + Pflag = 1; + break; + case 'R': + case 'r': /* Compatibility. */ + rflag = 1; + break; + case 'v': + vflag = 1; + break; + case 'x': + xflag = 1; + break; + case 'W': + Wflag = 1; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + if (argc < 1) { + if (fflag) + return 0; + usage(); + } + + (void)signal(SIGINFO, progress); + + checkdot(argv); + + if (*argv) { + stdin_ok = isatty(STDIN_FILENO); + + if (rflag) + rm_tree(argv); + else + rm_file(argv); + } + + exit(eval); + /* NOTREACHED */ +} + +static void +rm_tree(char **argv) +{ + FTS *fts; + FTSENT *p; + int flags, needstat, rval; + + /* + * Remove a file hierarchy. If forcing removal (-f), or interactive + * (-i) or can't ask anyway (stdin_ok), don't stat the file. + */ + needstat = !fflag && !iflag && stdin_ok; + + /* + * If the -i option is specified, the user can skip on the pre-order + * visit. The fts_number field flags skipped directories. + */ +#define SKIPPED 1 + + flags = FTS_PHYSICAL; + if (!needstat) + flags |= FTS_NOSTAT; + if (Wflag) + flags |= FTS_WHITEOUT; + if (xflag) + flags |= FTS_XDEV; + if ((fts = fts_open(argv, flags, NULL)) == NULL) + err(1, "fts_open failed"); + while ((p = fts_read(fts)) != NULL) { + + switch (p->fts_info) { + case FTS_DNR: + if (!fflag || p->fts_errno != ENOENT) { + warnx("%s: %s", p->fts_path, + strerror(p->fts_errno)); + eval = 1; + } + continue; + case FTS_ERR: + errx(EXIT_FAILURE, "%s: %s", p->fts_path, + strerror(p->fts_errno)); + /* NOTREACHED */ + case FTS_NS: + /* + * FTS_NS: assume that if can't stat the file, it + * can't be unlinked. + */ + if (fflag && NONEXISTENT(p->fts_errno)) + continue; + if (needstat) { + warnx("%s: %s", p->fts_path, + strerror(p->fts_errno)); + eval = 1; + continue; + } + break; + case FTS_D: + /* Pre-order: give user chance to skip. */ + if (!fflag && !check(p->fts_path, p->fts_accpath, + p->fts_statp)) { + (void)fts_set(fts, p, FTS_SKIP); + p->fts_number = SKIPPED; + } + continue; + case FTS_DP: + /* Post-order: see if user skipped. */ + if (p->fts_number == SKIPPED) + continue; + break; + default: + if (!fflag && + !check(p->fts_path, p->fts_accpath, p->fts_statp)) + continue; + } + + rval = 0; + /* + * If we can't read or search the directory, may still be + * able to remove it. Don't print out the un{read,search}able + * message unless the remove fails. + */ + switch (p->fts_info) { + case FTS_DP: + case FTS_DNR: + rval = rmdir(p->fts_accpath); + if (rval != 0 && fflag && errno == ENOENT) + continue; + break; + + case FTS_W: + rval = undelete(p->fts_accpath); + if (rval != 0 && fflag && errno == ENOENT) + continue; + break; + + default: + if (Pflag) { + if (rm_overwrite(p->fts_accpath, NULL)) + continue; + } + rval = unlink(p->fts_accpath); + if (rval != 0 && fflag && NONEXISTENT(errno)) + continue; + break; + } + if (rval != 0) { + warn("%s", p->fts_path); + eval = 1; + } else if (vflag || pinfo) { + pinfo = 0; + (void)printf("%s\n", p->fts_path); + } + } + if (errno) + err(1, "fts_read"); + fts_close(fts); +} + +static void +rm_file(char **argv) +{ + struct stat sb; + int rval; + char *f; + + /* + * Remove a file. POSIX 1003.2 states that, by default, attempting + * to remove a directory is an error, so must always stat the file. + */ + while ((f = *argv++) != NULL) { + /* Assume if can't stat the file, can't unlink it. */ + if (lstat(f, &sb)) { + if (Wflag) { + sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR; + } else { + if (!fflag || !NONEXISTENT(errno)) { + warn("%s", f); + eval = 1; + } + continue; + } + } else if (Wflag) { + warnx("%s: %s", f, strerror(EEXIST)); + eval = 1; + continue; + } + + if (S_ISDIR(sb.st_mode) && !dflag) { + warnx("%s: is a directory", f); + eval = 1; + continue; + } + if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb)) + continue; + if (S_ISWHT(sb.st_mode)) + rval = undelete(f); + else if (S_ISDIR(sb.st_mode)) + rval = rmdir(f); + else { + if (Pflag) { + if (rm_overwrite(f, &sb)) + continue; + } + rval = unlink(f); + } + if (rval && (!fflag || !NONEXISTENT(errno))) { + warn("%s", f); + eval = 1; + } + if (vflag && rval == 0) + (void)printf("%s\n", f); + } +} + +/* + * rm_overwrite -- + * Overwrite the file 3 times with varying bit patterns. + * + * This is an expensive way to keep people from recovering files from your + * non-snapshotted FFS filesystems using fsdb(8). Really. No more. Only + * regular files are deleted, directories (and therefore names) will remain. + * Also, this assumes a fixed-block file system (like FFS, or a V7 or a + * System V file system). In a logging file system, you'll have to have + * kernel support. + * + * A note on standards: U.S. DoD 5220.22-M "National Industrial Security + * Program Operating Manual" ("NISPOM") is often cited as a reference + * for clearing and sanitizing magnetic media. In fact, a matrix of + * "clearing" and "sanitization" methods for various media was given in + * Chapter 8 of the original 1995 version of NISPOM. However, that + * matrix was *removed from the document* when Chapter 8 was rewritten + * in Change 2 to the document in 2001. Recently, the Defense Security + * Service has made a revised clearing and sanitization matrix available + * in Microsoft Word format on the DSS web site. The standardization + * status of this matrix is unclear. Furthermore, one must be very + * careful when referring to this matrix: it is intended for the "clearing" + * prior to reuse or "sanitization" prior to disposal of *entire media*, + * not individual files and the only non-physically-destructive method of + * "sanitization" that is permitted for magnetic disks of any kind is + * specifically noted to be prohibited for media that have contained + * Top Secret data. + * + * It is impossible to actually conform to the exact procedure given in + * the matrix if one is overwriting a file, not an entire disk, because + * the procedure requires examination and comparison of the disk's defect + * lists. Any program that claims to securely erase *files* while + * conforming to the standard, then, is not correct. We do as much of + * what the standard requires as can actually be done when erasing a + * file, rather than an entire disk; but that does not make us conformant. + * + * Furthermore, the presence of track caches, disk and controller write + * caches, and so forth make it extremely difficult to ensure that data + * have actually been written to the disk, particularly when one tries + * to repeatedly overwrite the same sectors in quick succession. We call + * fsync(), but controllers with nonvolatile cache, as well as IDE disks + * that just plain lie about the stable storage of data, will defeat this. + * + * Finally, widely respected research suggests that the given procedure + * is nowhere near sufficient to prevent the recovery of data using special + * forensic equipment and techniques that are well-known. This is + * presumably one reason that the matrix requires physical media destruction, + * rather than any technique of the sort attempted here, for secret data. + * + * Caveat Emptor. + * + * rm_overwrite will return 0 on success. + */ + +static int +rm_overwrite(char *file, struct stat *sbp) +{ + struct stat sb, sb2; + int fd, randint; + char randchar; + + fd = -1; + if (sbp == NULL) { + if (lstat(file, &sb)) + goto err; + sbp = &sb; + } + if (!S_ISREG(sbp->st_mode)) + return 0; + + /* flags to try to defeat hidden caching by forcing seeks */ + if ((fd = open(file, O_RDWR|O_SYNC|O_RSYNC|O_NOFOLLOW, 0)) == -1) + goto err; + + if (fstat(fd, &sb2)) { + goto err; + } + + if (sb2.st_dev != sbp->st_dev || sb2.st_ino != sbp->st_ino || + !S_ISREG(sb2.st_mode)) { + errno = EPERM; + goto err; + } + +#define RAND_BYTES 1 +#define THIS_BYTE 0 + +#define WRITE_PASS(mode, byte) do { \ + off_t len; \ + size_t wlen, i; \ + char buf[8 * 1024]; \ + \ + if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET)) \ + goto err; \ + \ + if (mode == THIS_BYTE) \ + memset(buf, byte, sizeof(buf)); \ + for (len = sbp->st_size; len > 0; len -= wlen) { \ + if (mode == RAND_BYTES) { \ + for (i = 0; i < sizeof(buf); \ + i+= sizeof(u_int32_t)) \ + *(int *)(buf + i) = arc4random(); \ + } \ + wlen = len < (off_t)sizeof(buf) ? (size_t)len : sizeof(buf); \ + if ((size_t)write(fd, buf, wlen) != wlen) \ + goto err; \ + } \ + sync(); /* another poke at hidden caches */ \ +} while (/* CONSTCOND */ 0) + +#define READ_PASS(byte) do { \ + off_t len; \ + size_t rlen; \ + char pattern[8 * 1024]; \ + char buf[8 * 1024]; \ + \ + if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET)) \ + goto err; \ + \ + memset(pattern, byte, sizeof(pattern)); \ + for(len = sbp->st_size; len > 0; len -= rlen) { \ + rlen = len < (off_t)sizeof(buf) ? (size_t)len : sizeof(buf); \ + if((size_t)read(fd, buf, rlen) != rlen) \ + goto err; \ + if(memcmp(buf, pattern, rlen)) \ + goto err; \ + } \ + sync(); /* another poke at hidden caches */ \ +} while (/* CONSTCOND */ 0) + + /* + * DSS sanitization matrix "clear" for magnetic disks: + * option 'c' "Overwrite all addressable locations with a single + * character." + */ + randint = arc4random(); + randchar = *(char *)&randint; + WRITE_PASS(THIS_BYTE, randchar); + + /* + * DSS sanitization matrix "sanitize" for magnetic disks: + * option 'd', sub 2 "Overwrite all addressable locations with a + * character, then its complement. Verify "complement" character + * was written successfully to all addressable locations, then + * overwrite all addressable locations with random characters; or + * verify third overwrite of random characters." The rest of the + * text in d-sub-2 specifies requirements for overwriting spared + * sectors; we cannot conform to it when erasing only a file, thus + * we do not conform to the standard. + */ + + /* 1. "a character" */ + WRITE_PASS(THIS_BYTE, 0xff); + + /* 2. "its complement" */ + WRITE_PASS(THIS_BYTE, 0x00); + + /* 3. "Verify 'complement' character" */ + READ_PASS(0x00); + + /* 4. "overwrite all addressable locations with random characters" */ + + WRITE_PASS(RAND_BYTES, 0x00); + + /* + * As the file might be huge, and we note that this revision of + * the matrix says "random characters", not "a random character" + * as the original did, we do not verify the random-character + * write; the "or" in the standard allows this. + */ + + if (close(fd) == -1) { + fd = -1; + goto err; + } + + return 0; + +err: eval = 1; + warn("%s", file); + if (fd != -1) + close(fd); + return 1; +} + +static int +check(char *path, char *name, struct stat *sp) +{ + int ch, first; + char modep[15]; + + /* Check -i first. */ + if (iflag) + (void)fprintf(stderr, "remove '%s'? ", path); + else { + /* + * If it's not a symbolic link and it's unwritable and we're + * talking to a terminal, ask. Symbolic links are excluded + * because their permissions are meaningless. Check stdin_ok + * first because we may not have stat'ed the file. + */ + if (!stdin_ok || S_ISLNK(sp->st_mode) || + !(access(name, W_OK) && (errno != ETXTBSY))) + return (1); + strmode(sp->st_mode, modep); + if (Pflag) { + warnx( + "%s: -P was specified but file could not" + " be overwritten", path); + return 0; + } + (void)fprintf(stderr, "override %s%s%s:%s for '%s'? ", + modep + 1, modep[9] == ' ' ? "" : " ", + user_from_uid(sp->st_uid, 0), + group_from_gid(sp->st_gid, 0), path); + } + (void)fflush(stderr); + + first = ch = getchar(); + while (ch != '\n' && ch != EOF) + ch = getchar(); + return (first == 'y' || first == 'Y'); +} + +/* + * POSIX.2 requires that if "." or ".." are specified as the basename + * portion of an operand, a diagnostic message be written to standard + * error and nothing more be done with such operands. + * + * Since POSIX.2 defines basename as the final portion of a path after + * trailing slashes have been removed, we'll remove them here. + */ +#define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2]))) +static void +checkdot(char **argv) +{ + char *p, **save, **t; + int complained; + + complained = 0; + for (t = argv; *t;) { + /* strip trailing slashes */ + p = strrchr(*t, '\0'); + while (--p > *t && *p == '/') + *p = '\0'; + + /* extract basename */ + if ((p = strrchr(*t, '/')) != NULL) + ++p; + else + p = *t; + + if (ISDOT(p)) { + if (!complained++) + warnx("\".\" and \"..\" may not be removed"); + eval = 1; + for (save = t; (t[0] = t[1]) != NULL; ++t) + continue; + t = save; + } else + ++t; + } +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "usage: %s [-f|-i] [-dPRrvWx] file ...\n", + getprogname()); + exit(1); + /* NOTREACHED */ +} + +static void +progress(int sig __unused) +{ + + pinfo++; +} diff --git a/bin/rmdir/rmdir.1 b/bin/rmdir/rmdir.1 new file mode 100644 index 0000000..8fe42f9 --- /dev/null +++ b/bin/rmdir/rmdir.1 @@ -0,0 +1,95 @@ +.\" $NetBSD: rmdir.1,v 1.17 2017/07/03 21:33:23 wiz Exp $ +.\" +.\" Copyright (c) 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. +.\" +.\" @(#)rmdir.1 8.1 (Berkeley) 5/31/93 +.\" +.Dd August 12, 2016 +.Dt RMDIR 1 +.Os +.Sh NAME +.Nm rmdir +.Nd remove directories +.Sh SYNOPSIS +.Nm +.Op Fl p +.Ar directory ... +.Sh DESCRIPTION +The rmdir utility removes the directory entry specified by +each +.Ar directory +argument, provided it is empty. +.Pp +Arguments are processed in the order given. +In order to remove both a parent directory and a subdirectory +of that parent, the subdirectory +must be specified first so the parent directory +is empty when +.Nm +tries to remove it. +.Pp +The following option is available: +.Bl -tag -width Ds +.It Fl p +Each +.Ar directory +argument is treated as a pathname of which all +components will be removed, if they are empty, +starting with the last most component. +(See +.Xr rm 1 +for fully non-discriminant recursive removal.) +.El +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Bl -tag -width Ds +.It Li \&0 +Each directory entry specified by a dir operand +referred to an empty directory and was removed +successfully. +.It Li \&>\&0 +An error occurred. +.El +.Sh SEE ALSO +.Xr rm 1 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. +.Sh HISTORY +An +.Nm +utility appeared in +.At v1 . diff --git a/bin/rmdir/rmdir.c b/bin/rmdir/rmdir.c new file mode 100644 index 0000000..7a87b30 --- /dev/null +++ b/bin/rmdir/rmdir.c @@ -0,0 +1,125 @@ +/* $NetBSD: rmdir.c,v 1.27 2017/08/10 22:52:13 ginsbach Exp $ */ + +/*- + * Copyright (c) 1992, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1992, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)rmdir.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: rmdir.c,v 1.27 2017/08/10 22:52:13 ginsbach Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> + +#include <err.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static int rm_path(char *); +__dead static void usage(void); + +int +main(int argc, char *argv[]) +{ + int ch, errors, pflag; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + pflag = 0; + while ((ch = getopt(argc, argv, "p")) != -1) + switch(ch) { + case 'p': + pflag = 1; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + if (argc == 0) + usage(); + + for (errors = 0; *argv; argv++) { + /* We rely on the kernel to ignore trailing '/' characters. */ + if (rmdir(*argv) < 0) { + warn("%s", *argv); + errors = 1; + } else if (pflag) + errors |= rm_path(*argv); + } + + exit(errors); + /* NOTREACHED */ +} + +static int +rm_path(char *path) +{ + char *p; + + while ((p = strrchr(path, '/')) != NULL) { + *p = 0; + if (p[1] == 0) + /* Ignore trailing '/' on deleted name */ + continue; + + if (*path == 0) + /* At top level (root) directory */ + break; + + if (rmdir(path) < 0) { + warn("%s", path); + return (1); + } + } + + return (0); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: %s [-p] directory ...\n", getprogname()); + exit(1); + /* NOTREACHED */ +} diff --git a/bin/sh/TOUR b/bin/sh/TOUR new file mode 100644 index 0000000..30cee04 --- /dev/null +++ b/bin/sh/TOUR @@ -0,0 +1,357 @@ +# $NetBSD: TOUR,v 1.11 2016/10/25 13:01:59 abhinav Exp $ +# @(#)TOUR 8.1 (Berkeley) 5/31/93 + +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 builtins.h builtins.c + mkinit *.c init.c + mknodes nodetypes nodes.h nodes.c + mksignames - signames.h signames.c + mksyntax - syntax.h syntax.c + mktokens - token.h + bltin/mkexpr unary_op binary_op operators.h operators.c + +There are undoubtedly too many of these. Mkinit searches all the +C source files for entries looking like: + + INIT { + x = 1; /* executed during initialization */ + } + + RESET { + x = 2; /* executed when the shell does a longjmp + back to the main command loop */ + } + + SHELLPROC { + x = 3; /* executed when the shell runs a shell procedure */ + } + +It pulls this code out into routines which are when particular +events occur. The intent is to improve modularity by isolating +the information about which modules need to be explicitly +initialized/reset within the modules themselves. + +Mkinit recognizes several constructs for placing declarations in +the init.c file. + INCLUDE "file.h" +includes a file. The storage class MKINIT makes a declaration +available in the init.c file, for example: + MKINIT int funcnest; /* depth of function calls */ +MKINIT alone on a line introduces a structure or union declara- +tion: + MKINIT + struct redirtab { + short renamed[10]; + }; +Preprocessor #define statements are copied to init.c without any +special action to request this. + +INDENTATION: The ash source is indented in multiples of six +spaces. The only study that I have heard of on the subject con- +cluded that the optimal amount to indent is in the range of four +to six spaces. I use six spaces since it is not too big a jump +from the widely used eight spaces. If you really hate six space +indentation, use the adjind (source included) program to change +it to something else. + +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. EXSHELLPROC is an excep- +tion which is raised when a shell procedure is invoked. The pur- +pose of EXSHELLPROC is to perform the cleanup actions associated +with other exceptions. After these cleanup actions, the shell +can interpret a shell procedure itself without exec'ing a new +copy of the shell. + +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 -j 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 +three tables: one for normal use, one for use when inside single +quotes, and one for use when inside double quotes. The tables +are machine dependent because they are indexed by character vari- +ables 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 Variable substitution + CTLENDVAR End of variable substitution + CTLBACKQ Command substitution + CTLBACKQ|CTLQUOTE Command substitution inside double quotes + 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} + +In addition, the type field will have the VSQUOTE flag set if the +variable is enclosed in double quotes. The name of the variable +comes next, terminated by an equals sign. If the type is not +VSNORMAL, then the text field in the substitution follows, ter- +minated by a CTLENDVAR byte. + +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. + +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 splitting and file name +generation, have the CTLESC characters removed during the vari- +able and command substitution phase. Words which are subject +splitting and file name generation have the CTLESC characters re- +moved 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: Arguments are processed in three passes. The first +(performed by the routine argstr) performs variable and command +substitution. The second (ifsbreakup) performs word splitting +and the third (expandmeta) performs file name generation. If the +"/u" directory is simulated, then when "/u/username" is replaced +by the user's home directory, the flag "didudir" is set. This +tells the cd command that it should print out the directory name, +just as it would if the "/u" directory were implemented using +symbolic links. + +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. +There are two consequences of this. First, if an assignment to +PATH precedes the command, the value of PATH before the assign- +ment must be remembered and passed to shellexec. Second, if the +program turns out to be a shell procedure, the strings from the +environment variables which preceded the command must be pulled +out of the table and replaced with strings obtained from malloc, +since the former will automatically be freed when the stack (see +the entry on memalloc.c) is emptied. + +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, which is processed by the mkbuil- +tins 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 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 makefile in this directory compiles these programs +in the normal fashion (so that they can be run regardless of +whether the invoker is ash), but also creates a library named +bltinlib.a which can be linked with ash. 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 pro- +gram is to be compiled stand-alone. + +CD.C: This file defines the cd and pwd builtins. The pwd com- +mand runs /bin/pwd the first time it is invoked (unless the user +has already done a cd to an absolute pathname), but then +remembers the current directory and updates it when the cd com- +mand is run, so subsequent pwd commands run very fast. The main +complication in the cd command is in the docd command, which +resolves symbolic links into actual names and informs the user +where the user ended up if he crossed a symbolic link. + +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/sh/USD.doc/Makefile b/bin/sh/USD.doc/Makefile new file mode 100644 index 0000000..55b7203 --- /dev/null +++ b/bin/sh/USD.doc/Makefile @@ -0,0 +1,12 @@ +# $NetBSD: Makefile,v 1.4 2014/07/05 19:23:00 dholland Exp $ +# @(#)Makefile 8.1 (Berkeley) 8/14/93 + +SECTION=reference/ref1 +ARTICLE=sh +SRCS= referargs t.mac t1 t2 t3 t4 +MACROS=-ms +ROFF_REFER=yes +#REFER_ARGS=-e -p Rv7man +EXTRAHTMLFILES=sh1.png sh2.png sh3.png sh4.png sh5.png + +.include <bsd.doc.mk> diff --git a/bin/sh/USD.doc/Rv7man b/bin/sh/USD.doc/Rv7man new file mode 100644 index 0000000..628c67f --- /dev/null +++ b/bin/sh/USD.doc/Rv7man @@ -0,0 +1,405 @@ +%A L. P. Deutsch +%A B. W. Lampson +%T An online editor +%J Comm. Assoc. Comp. Mach. +%V 10 +%N 12 +%D December 1967 +%P 793-799, 803 +%K qed + +.[ +%r 17 +%K cstr +%R Comp. Sci. Tech. Rep. No. 17 +%I Bell Laboratories +%C Murray Hill, New Jersey +%A B. W. Kernighan +%A L. L. Cherry +%T A System for Typesetting Mathematics +%d May 1974, revised April 1977 +%J Comm. Assoc. Comp. Mach. +%K acm cacm +%V 18 +%P 151-157 +%D March 1975 +.] + +%T U\s-2NIX\s0 Time-Sharing System: Document Preparation +%K unix bstj +%A B. W. Kernighan +%A M. E. Lesk +%A J. F. Ossanna +%J Bell Sys. Tech. J. +%V 57 +%N 6 +%P 2115-2135 +%D 1978 + +%A T. A. Dolotta +%A J. R. Mashey +%T An Introduction to the Programmer's Workbench +%J Proc. 2nd Int. Conf. on Software Engineering +%D October 13-15, 1976 +%P 164-168 + +%T U\s-2NIX\s0 Time-Sharing System: The Programmer's Workbench +%A T. A. Dolotta +%A R. C. Haight +%A J. R. Mashey +%J Bell Sys. Tech. J. +%V 57 +%N 6 +%P 2177-2200 +%D 1978 +%K unix bstj + +%T U\s-2NIX\s0 Time-Sharing System: U\s-2NIX\s0 on a Microprocessor +%K unix bstj +%A H. Lycklama +%J Bell Sys. Tech. J. +%V 57 +%N 6 +%P 2087-2101 +%D 1978 + +%T The C Programming Language +%A B. W. Kernighan +%A D. M. Ritchie +%I Prentice-Hall +%C Englewood Cliffs, New Jersey +%D 1978 + +%T Computer Recreations +%A Aleph-null +%J Software Practice and Experience +%V 1 +%N 2 +%D April-June 1971 +%P 201-204 + +%T U\s-2NIX\s0 Time-Sharing System: The U\s-2NIX\s0 Shell +%A S. R. Bourne +%K unix bstj +%J Bell Sys. Tech. J. +%V 57 +%N 6 +%P 1971-1990 +%D 1978 + +%A L. P. Deutsch +%A B. W. Lampson +%T \*sSDS\*n 930 time-sharing system preliminary reference manual +%R Doc. 30.10.10, Project \*sGENIE\*n +%C Univ. Cal. at Berkeley +%D April 1965 + +%A R. J. Feiertag +%A E. I. Organick +%T The Multics input-output system +%J Proc. Third Symposium on Operating Systems Principles +%D October 18-20, 1971 +%P 35-41 + +%A D. G. Bobrow +%A J. D. Burchfiel +%A D. L. Murphy +%A R. S. Tomlinson +%T \*sTENEX\*n, a Paged Time Sharing System for the \*sPDP\*n-10 +%J Comm. Assoc. Comp. Mach. +%V 15 +%N 3 +%D March 1972 +%K tenex +%P 135-143 + +%A R. E. Griswold +%A D. R. Hanson +%T An Overview of SL5 +%J SIGPLAN Notices +%V 12 +%N 4 +%D April 1977 +%P 40-50 + +%A E. W. Dijkstra +%T Cooperating Sequential Processes +%B Programming Languages +%E F. Genuys +%I Academic Press +%C New York +%D 1968 +%P 43-112 + +%A J. A. Hawley +%A W. B. Meyer +%T M\s-2UNIX\s0, A Multiprocessing Version of U\s-2NIX\s0 +%K munix unix +%R M.S. Thesis +%I Naval Postgraduate School +%C Monterey, Cal. +%D 1975 + +%T The U\s-2NIX\s0 Time-Sharing System +%K unix bstj +%A D. M. Ritchie +%A K. Thompson +%J Bell Sys. Tech. J. +%V 57 +%N 6 +%P 1905-1929 +%D 1978 + +%A E. I. Organick +%T The M\s-2ULTICS\s0 System +%K multics +%I M.I.T. Press +%C Cambridge, Mass. +%D 1972 + +%T UNIX for Beginners +%A B. W. Kernighan +%D 1978 + +%T U\s-2NIX\s0 Programmer's Man\&ual +%A K. Thompson +%A D. M. Ritchie +%K unix +%I Bell Laboratories +%O Seventh Edition. +%D 1978 + +%A K. Thompson +%T The U\s-2NIX\s0 Command Language +%B Structured Programming\(emInfotech State of the Art Report +%I Infotech International Ltd. +%C Nicholson House, Maidenhead, Berkshire, England +%D March 1975 +%P 375-384 +%K unix +%X pwb +Brief description of shell syntax and semantics, without much +detail on implementation. +Much on pipes and convenience of hooking programs together. +Includes SERMONETTE: +"Many familiar computing `concepts' are missing from UNIX. +Files have no records. There are no access methods. +There are no file types. These concepts fill a much-needed gap. +I sincerely hope that when future systems are designed by +manufacturers the value of some of these ingrained notions is re-examined. +Like the politician and his `common man', manufacturers have +their `average user'. + +%A J. R. Mashey +%T PWB/UNIX Shell Tutorial +%D September 30, 1977 + +%A D. F. Hartley (Ed.) +%T The Cambridge Multiple Access System \- Users Reference Manual +%I University Mathematical Laboratory +%C Cambridge, England +%D 1968 + +%A P. A. Crisman (Ed.) +%T The Compatible Time-Sharing System +%I M.I.T. Press +%K whole ctss book +%C Cambridge, Mass. +%D 1965 + +%T LR Parsing +%A A. V. Aho +%A S. C. Johnson +%J Comp. Surveys +%V 6 +%N 2 +%P 99-124 +%D June 1974 + +%T Deterministic Parsing of Ambiguous Grammars +%A A. V. Aho +%A S. C. Johnson +%A J. D. Ullman +%J Comm. Assoc. Comp. Mach. +%K acm cacm +%V 18 +%N 8 +%P 441-452 +%D August 1975 + +%A A. V. Aho +%A J. D. Ullman +%T Principles of Compiler Design +%I Addison-Wesley +%C Reading, Mass. +%D 1977 + +.[ +%r 65 +%R Comp. Sci. Tech. Rep. No. 65 +%K CSTR +%A S. C. Johnson +%T Lint, a C Program Checker +%D December 1977 +%O updated version TM 78-1273-3 +%D 1978 +.] + +%T A Portable Compiler: Theory and Practice +%A S. C. Johnson +%J Proc. 5th ACM Symp. on Principles of Programming Languages +%P 97-104 +%D January 1978 + +.[ +%r 39 +%K CSTR +%R Comp. Sci. Tech. Rep. No. 39 +%I Bell Laboratories +%C Murray Hill, New Jersey +%A M. E. Lesk +%T Lex \(em A Lexical Analyzer Generator +%D October 1975 +.] + +.[ +%r 32 +%K CSTR +%R Comp. Sci. Tech. Rep. No. 32 +%I Bell Laboratories +%C Murray Hill, New Jersey +%A S. C. Johnson +%T Yacc \(em Yet Another Compiler-Compiler +%D July 1975 +.] + +%T U\s-2NIX\s0 Time-Sharing System: Portability of C Programs and the U\s-2NIX\s0 System +%K unix bstj +%A S. C. Johnson +%A D. M. Ritchie +%J Bell Sys. Tech. J. +%V 57 +%N 6 +%P 2021-2048 +%D 1978 + +%T Typing Documents on UNIX and GCOS: The -ms Macros for Troff +%A M. E. Lesk +%D 1977 + +%A K. Thompson +%A D. M. Ritchie +%T U\s-2NIX\s0 Programmer's Manual +%K unix +%I Bell Laboratories +%O Sixth Edition +%D May 1975 + +%T The Network U\s-2NIX\s0 System +%K unix +%A G. L. Chesson +%J Operating Systems Review +%V 9 +%N 5 +%P 60-66 +%D 1975 +%O Also in \f2Proc. 5th Symp. on Operating Systems Principles.\f1 + +%T Spider \(em An Experimental Data Communications System +%Z ctr127 +%A A. G. Fraser +%J Proc. IEEE Conf. on Communications +%P 21F +%O IEEE Cat. No. 74CH0859-9-CSCB. +%D June 1974 + +%T A Virtual Channel Network +%A A. G. Fraser +%J Datamation +%P 51-56 +%D February 1975 + +.[ +%r 41 +%K CSTR +%R Comp. Sci. Tech. Rep. No. 41 +%I Bell Laboratories +%C Murray Hill, New Jersey +%A J. W. Hunt +%A M. D. McIlroy +%T An Algorithm for Differential File Comparison +%D June 1976 +.] + +%A F. P. Brooks, Jr. +%T The Mythical Man-Month +%I Addison-Wesley +%C Reading, Mass. +%D 1975 +%X pwb +Readable, classic reference on software engineering and +problems of large projects, from someone with experience in them. +Required reading for any software engineer, even if conclusions may not +always be agreed with. +%br +"The second is the most dangerous system a man ever designs." p.55. +%br +"Hence plan to throw one away; you will, anyhow." p.116. +%br +"Cosgrove has perceptively pointed out that the programmer delivers +satisfaction of a user need rather than any tangible product. +And both the actual need and the user's perception of that need +will change as programs are built, tested, and used." p.117. +%br +"The total cost of maintaining a widely used program is typically 40 percent +or more of the cost of developing it." p.121. +%br +"As shown above, amalgamating prose and program reduces the total +number of characters to be stored." p.175. + +%T A Portable Compiler for the Language C +%A A. Snyder +%I Master's Thesis, M.I.T. +%C Cambridge, Mass. +%D 1974 + +%T The C Language Calling Sequence +%A M. E. Lesk +%A S. C. Johnson +%A D. M. Ritchie +%D 1977 + +%T Optimal Code Generation for Expression Trees +%A A. V. Aho +%A S. C. Johnson +%D 1975 +%J J. Assoc. Comp. Mach. +%K acm jacm +%V 23 +%N 3 +%P 488-501 +%O Also in \f2Proc. ACM Symp. on Theory of Computing,\f1 pp. 207-217, 1975. + +%A R. Sethi +%A J. D. Ullman +%T The Generation of Optimal Code for Arithmetic Expressions +%J J. Assoc. Comp. Mach. +%K acm jacm +%V 17 +%N 4 +%D October 1970 +%P 715-728 +%O Reprinted as pp. 229-247 in \fICompiler Techniques\fR, ed. B. W. Pollack, Auerbach, Princeton NJ (1972). +%X pwb +Optimal approach for straight-line, fixed +number of regs. + +%T Code Generation for Machines with Multiregister +Operations +%A A. V. Aho +%A S. C. Johnson +%A J. D. Ullman +%J Proc. 4th ACM Symp. on Principles of Programming Languages +%P 21-28 +%D January 1977 + diff --git a/bin/sh/USD.doc/referargs b/bin/sh/USD.doc/referargs new file mode 100644 index 0000000..3bb6284 --- /dev/null +++ b/bin/sh/USD.doc/referargs @@ -0,0 +1,8 @@ +.\" $NetBSD: referargs,v 1.1 2014/07/05 19:22:02 dholland Exp $ +.\" +.\" Arguments for refer; these were previously passed on the refer(1) +.\" command line: -e -p Rv7man +.R1 +accumulate +database Rv7man +.R2 diff --git a/bin/sh/USD.doc/t.mac b/bin/sh/USD.doc/t.mac new file mode 100644 index 0000000..9bf65c8 --- /dev/null +++ b/bin/sh/USD.doc/t.mac @@ -0,0 +1,69 @@ +.\" $NetBSD: t.mac,v 1.2 2010/08/22 02:19:07 perry Exp $ +.\" +.\" Copyright (C) Caldera International Inc. 2001-2002. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" Redistributions of source code and documentation must retain the above +.\" copyright notice, this list of conditions and the following +.\" disclaimer. +.\" +.\" 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. +.\" +.\" All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" +.\" This product includes software developed or owned by Caldera +.\" International, Inc. Neither the name of Caldera International, Inc. +.\" nor the names of other contributors may be used to endorse or promote +.\" products derived from this software without specific prior written +.\" permission. +.\" +.\" USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA +.\" INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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) RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" @(#)t.mac 8.1 (Berkeley) 8/14/93 +.\" +.ds ZZ \fB.\|.\|.\fP +.ds ST \v'.3m'\s+2*\s0\v'-.3m' +.ds DO \h'\w'do 'u' +.ds Ca \h'\w'case 'u' +.ds WH \h'\w'while 'u' +.ds VT \|\fB\(or\fP\| +.ds TH \h'\w'then 'u' +.ds DC \*(DO\*(Ca +.ds AP >\h'-.2m'> +.ds HE <\h'-.2m'< +. \" macros for algol 68c reference manual +.ds DA 1977 November 1 +.ds md \v'.25m' +.ds mu \v'-.25m' +.ds U \*(mu\s-3 +.ds V \s0\*(md +.ds L \*(md\s-3 +.ds M \s0\*(mu +.ds S \s-1 +.ds T \s0 +. \" small 1 +.ds O \*S1\*T +.ds h \| +.ds s \|\| +. \" ellipsis +.ds e .\|.\|. +. \" subscripts +.ds 1 \*(md\s-41\s0\*(mu +.ds 2 \*(md\s-42\s0\*(mu diff --git a/bin/sh/USD.doc/t1 b/bin/sh/USD.doc/t1 new file mode 100644 index 0000000..075511f --- /dev/null +++ b/bin/sh/USD.doc/t1 @@ -0,0 +1,553 @@ +.\" $NetBSD: t1,v 1.3 2010/08/22 02:19:07 perry Exp $ +.\" +.\" Copyright (C) Caldera International Inc. 2001-2002. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" Redistributions of source code and documentation must retain the above +.\" copyright notice, this list of conditions and the following +.\" disclaimer. +.\" +.\" 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. +.\" +.\" All advertising materials mentioning features or use of this software +.\" must display the following acknowledgment: +.\" +.\" This product includes software developed or owned by Caldera +.\" International, Inc. Neither the name of Caldera International, Inc. +.\" nor the names of other contributors may be used to endorse or promote +.\" products derived from this software without specific prior written +.\" permission. +.\" +.\" USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA +.\" INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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) RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" @(#)t1 8.1 (Berkeley) 8/14/93 +.\" +.EH 'USD:3-%''An Introduction to the UNIX Shell' +.OH 'An Introduction to the UNIX Shell''USD:3-%' +.\".RP +.TL +An Introduction to the UNIX Shell +.AU +S. R. Bourne +.AI +Murray Hill, NJ +.AU +(Updated for 4.3BSD by Mark Seiden) +.AU +(Further updated by Perry E. Metzger)\(dg +.AB +.FS +\(dg This paper was updated in 2010 to reflect most features of modern +POSIX shells, which all follow the design of S.R. Bourne's original v7 +Unix shell. +Among these are ash, bash, ksh and others. +Typically one of these will be installed as /bin/sh on a modern system. +It does not describe the behavior of the c shell (csh). +If it's the c shell (csh) you're interested in, a good place to +begin is William Joy's paper "An Introduction to the C shell" (USD:4). +.FE +.LP +The +.ul +shell +is a command programming language that provides an interface +to the +.UX +operating system. +Its features include +control-flow primitives, parameter passing, variables and +string substitution. +Constructs such as +.ul +while, if then else, case +and +.ul +for +are available. +Two-way communication is possible between the +.ul +shell +and commands. +String-valued parameters, typically file names or flags, may be +passed to a command. +A return code is set by commands that may be used to determine control-flow, +and the standard output from a command may be used +as shell input. +.LP +The +.ul +shell +can modify the environment +in which commands run. +Input and output can be redirected +to files, and processes that communicate through `pipes' +can be invoked. +Commands are found by +searching directories +in the file system in a +sequence that can be defined by the user. +Commands can be read either from the terminal or from a file, +which allows command procedures to be +stored for later use. +.AE +.ds ST \v'.3m'\s+2*\s0\v'-.3m' +.SH +1.0\ Introduction +.LP +The shell is both a command language +and a programming language +that provides an interface to the UNIX +operating system. +This memorandum describes, with +examples, the UNIX shell. +The first section covers most of the +everyday requirements +of terminal users. +Some familiarity with UNIX +is an advantage when reading this section; +see, for example, +"UNIX for beginners". +.[ +unix beginn kernigh 1978 +.] +Section 2 describes those features +of the shell primarily intended +for use within shell procedures. +These include the control-flow +primitives and string-valued variables +provided by the shell. +A knowledge of a programming language +would be a help when reading this section. +The last section describes the more +advanced features of the shell. +References of the form "see \fIpipe\fP (2)" +are to a section of the UNIX manual. +.[ +seventh 1978 ritchie thompson +.] +.SH +1.1\ Simple\ commands +.LP +Simple commands consist of one or more words +separated by blanks. +The first word is the name of the command +to be executed; any remaining words +are passed as arguments to the command. +For example, +.DS + who +.DE +is a command that prints the names +of users logged in. +The command +.DS + ls \(mil +.DE +prints a list of files in the current +directory. +The argument \fI\(mil\fP tells \fIls\fP +to print status information, size and +the creation date for each file. +.SH +1.2\ Input\ output\ redirection +.LP +Most commands produce output on the standard output +that is initially connected to the terminal. +This output may be sent to a file +by writing, for example, +.DS + ls \(mil >file +.DE +The notation \fI>file\fP +is interpreted by the shell and is not passed +as an argument to \fIls.\fP +If \fIfile\fP does not exist then the +shell creates it; +otherwise the original contents of +\fIfile\fP are replaced with the output +from \fIls.\fP +Output may be appended to a file +using the notation +.DS + ls \(mil \*(APfile +.DE +In this case \fIfile\fP is also created if it does not already +exist. +.LP +The standard input of a command may be taken +from a file instead of the terminal by +writing, for example, +.DS + wc <file +.DE +The command \fIwc\fP reads its standard input +(in this case redirected from \fIfile\fP) +and prints the number of characters, words and +lines found. +If only the number of lines is required +then +.DS + wc \(mil <file +.DE +could be used. +.\" I considered adding the following, but have thought better of it +.\" for now. +.\" -- Perry Metzger +.\" +.\" .LP +.\" Error messages are typically printed by commands on a different +.\" channel, called standard error, which may also be redirected using the +.\" notation 2>\|. +.\" For example +.\" .DS +.\" command some args >out 2>errors +.\" .DE +.\" will redirect standard output to the file `out' but standard error +.\" (and thus all error messages) to `errors'. +.\" The notation 2>&1 sets standard error pointing to the same +.\" place as standard out. +.\" Thus: +.\" .DS +.\" command some args 2>&1 >everything +.\" .DE +.\" will put both standard out and standard error into the file `everything'. +.\" See section 3.7 below for more details. +.SH +1.3\ Pipelines\ and\ filters +.LP +The standard output of one command may be +connected to the standard input of another +by writing +the `pipe' operator, +indicated by \*(VT, +as in, +.DS + ls \(mil \*(VT wc +.DE +Two commands connected in this way constitute +a \fIpipeline\fP and +the overall effect is the same as +.DS + ls \(mil >file; wc <file +.DE +except that no \fIfile\fP is used. +Instead the two \fIprocesses\fP are connected +by a pipe (see \fIpipe\fP(2)) and are +run in parallel. +Pipes are unidirectional and +synchronization is achieved by +halting \fIwc\fP when there is +nothing to read and halting \fIls\fP +when the pipe is full. +.LP +A \fIfilter\fP is a command +that reads its standard input, +transforms it in some way, +and prints the result as output. +One such filter, \fIgrep,\fP +selects from its input those lines +that contain some specified string. +For example, +.DS + ls \*(VT grep old +.DE +prints those lines, if any, of the output +from \fIls\fP that contain +the string \fIold.\fP +Another useful filter is \fIsort\fP. +For example, +.DS + who \*(VT sort +.DE +will print an alphabetically sorted list +of logged in users. +.LP +A pipeline may consist of more than two commands, +for example, +.DS + ls \*(VT grep old \*(VT wc \(mil +.DE +prints the number of file names +in the current directory containing +the string \fIold.\fP +.SH +1.4\ Background\ commands +.LP +To execute a command (or pipeline) the shell normally +creates the new \fIprocesses\fP +and waits for them to finish. +A command may be run without waiting +for it to finish. +For example, +.DS + cc pgm.c & +.DE +calls the C compiler to compile +the file \fIpgm.c\|.\fP +The trailing \fB&\fP is an operator that instructs the shell +not to wait for the command to finish. +To help keep track of such a process +the shell reports its job number (see below) and process +id following its creation. +Such a command is said to be running in the \fIbackground\fP. +By contrast, a command executed without the \fB&\fP is said to be +running in the \fIforeground\fP.\(dg +.FS +\(dg Even after execution, one may move commands from the foreground +to the background, or temporarily suspend their execution (which is +known as \fIstopping\fP a command. +This is described in detail in section 3.10 on \fIJob Control\fB. +.FE +.LP +A list of currently active processes, including ones not associated +with the current shell, may be obtained using the \fIps\fP(1) command. +.SH +1.5\ File\ name\ generation +.LP +Many commands accept arguments +which are file names. +For example, +.DS + ls \(mil main.c +.DE +prints information relating to the file \fImain.c\fP\|. +.LP +The shell provides a mechanism +for generating a list of file names +that match a pattern. +For example, +.DS + ls \(mil \*(ST.c +.DE +generates, as arguments to \fIls,\fP +all file names in the current directory that end in \fI.c\|.\fP +The character \*(ST is a pattern that will match any string +including the null string. +In general \fIpatterns\fP are specified +as follows. +.RS +.IP \fB\*(ST\fR 8 +Matches any string of characters +including the null string. +.IP \fB?\fR 8 +Matches any single character. +.IP \fB[\*(ZZ]\fR 8 +Matches any one of the characters +enclosed. +A pair of characters separated by a minus will +match any character lexically between +the pair. +.RE +.LP +For example, +.DS + [a\(miz]\*(ST +.DE +matches all names in the current directory +beginning with +one of the letters \fIa\fP through \fIz.\fP +.DS + /usr/fred/test/? +.DE +matches all names in the directory +\fB/usr/fred/test\fP that consist of a single character. +If no file name is found that matches +the pattern then the pattern is passed, +unchanged, as an argument. +.LP +This mechanism is useful both to save typing +and to select names according to some pattern. +It may also be used to find files. +For example, +.DS + echo /usr/fred/\*(ST/core +.DE +finds and prints the names of all \fIcore\fP files in sub-directories +of \fB/usr/fred\|.\fP +(\fIecho\fP is a standard UNIX command that prints +its arguments, separated by blanks.) +This last feature can be expensive, +requiring a scan of all +sub-directories of \fB/usr/fred\|.\fP +.LP +There is one exception to the general +rules given for patterns. +The character `\fB.\fP' +at the start of a file name must be explicitly +matched. +.DS + echo \*(ST +.DE +will therefore echo all file names in the current +directory not beginning +with `\fB.\fP'\|. +.DS + echo \fB.\fP\*(ST +.DE +will echo all those file names that begin with `\fB.\fP'\|. +This avoids inadvertent matching +of the names `\fB.\fP' and `\fB..\fP' +which mean `the current directory' +and `the parent directory' +respectively. +(Notice that \fIls\fP suppresses +information for the files `\fB.\fP' and `\fB..\fP'\|.) +.LP +Finally, the tilde character, `\fB\(ap\fP', may be used to indicate the +home directory of a user. +The `\fB\(ap\fP' at the beginning of a path name followed by a +non-alphabetic character expands to the current user's home +directory. +If the `\fB\(ap\fP' is followed by a login name, it expands to the named +user's home directory. +For example: +.DS + ls \(ap + cd \(apegbert/ +.DE +will list the contents of the user's home directory and then change +to the home directory of the user ``egbert''. +.SH +1.6\ Quoting +.LP +Characters that have a special meaning +to the shell, such as \fB< > \*(ST ? \*(VT &\|,\fR +are called metacharacters. +A complete list of metacharacters is given +in appendix B. +Any character preceded by a \fB\\\fR is \fIquoted\fP +and loses its special meaning, if any. +The \fB\\\fP is elided so that +.DS + echo \\? +.DE +will echo a single \fB?\|,\fP +and +.DS + echo \\\\ +.DE +will echo a single \fB\\\|.\fR +To allow long strings to be continued over +more than one line +the sequence \fB\\newline\fP +is ignored. +.LP +\fB\\\fP is convenient for quoting +single characters. +When more than one character needs +quoting the above mechanism is clumsy and +error prone. +A string of characters may be quoted +by enclosing the string between single quotes. +For example, +.DS + echo xx\'\*(ST\*(ST\*(ST\*(ST\'xx +.DE +will echo +.DS + xx\*(ST\*(ST\*(ST\*(STxx +.DE +The quoted string may not contain +a single quote +but may contain newlines, which are preserved. +This quoting mechanism is the most +simple and is recommended +for casual use. +.LP +A third quoting mechanism using double quotes +is also available +that prevents interpretation of some but not all +metacharacters. +Discussion of the +details is deferred +to section 3.5\|. +.SH +1.7\ Prompting +.LP +When the shell is used from a terminal it will +issue a prompt before reading a command. +By default this prompt is `\fB$\ \fR'\|. +It may be changed by saying, +for example, +.DS + \s-1PS1\s0="yesdear$ " +.DE +that sets the prompt to be the string \fIyesdear$\|.\fP +If a newline is typed and further input is needed +then the shell will issue the prompt `\fB>\ \fR'\|. +Sometimes this can be caused by mistyping +a quote mark. +If it is unexpected then entering the interrupt character +(typically \s-1CONTROL-C\s0) +will return the shell to read another command. +This prompt may be changed by saying, for example, +.DS + \s-1PS2\s0=more +.DE +Entering the interrupt character may also be used to terminate most +programs running as the current foreground job. +.LP +(\s-1PS1\s0 and \s-1PS2\s0 are \fIshell variables\fP, which will be +described in section 2.4 below.) +.SH +1.8\ The\ shell\ and\ login +.LP +Following \fIlogin\fP(1) +the shell is called to read and execute +commands typed at the terminal. +If the user's login directory +contains the file \fB.profile\fP +then it is assumed to contain commands +and is read by the shell before reading +any commands from the terminal. +.LP +(Most versions of the shell also specify a file that is read and +executed on start-up whether or not the shell is invoked by login. +The \s-1ENV\s0 shell variable, described in section 2.4 below, can be +used to override the name of this file. +See the shell manual page for further information.) +.SH +1.9\ Summary +.sp +.RS +.IP \(bu +\fBls\fP +.br +Print the names of files in the current directory. +.IP \(bu +\fBls >file\fP +.br +Put the output from \fIls\fP into \fIfile.\fP +.IP \(bu +\fBls \*(VT wc \(mil\fR +.br +Print the number of files in the current directory. +.IP \(bu +\fBls \*(VT grep old\fR +.br +Print those file names containing the string \fIold.\fP +.IP \(bu +\fBls \*(VT grep old \*(VT wc \(mil\fR +.br +Print the number of files whose name contains the string \fIold.\fP +.IP \(bu +\fBcc pgm.c &\fR +.br +Run \fIcc\fP in the background. +.RE diff --git a/bin/sh/USD.doc/t2 b/bin/sh/USD.doc/t2 new file mode 100644 index 0000000..d49747e --- /dev/null +++ b/bin/sh/USD.doc/t2 @@ -0,0 +1,971 @@ +.\" $NetBSD: t2,v 1.3 2010/08/22 02:19:07 perry Exp $ +.\" +.\" Copyright (C) Caldera International Inc. 2001-2002. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" Redistributions of source code and documentation must retain the above +.\" copyright notice, this list of conditions and the following +.\" disclaimer. +.\" +.\" 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. +.\" +.\" All advertising materials mentioning features or use of this software +.\" must display the following acknowledgment: +.\" +.\" This product includes software developed or owned by Caldera +.\" International, Inc. Neither the name of Caldera International, Inc. +.\" nor the names of other contributors may be used to endorse or promote +.\" products derived from this software without specific prior written +.\" permission. +.\" +.\" USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA +.\" INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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) RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" @(#)t2 8.1 (Berkeley) 6/8/93 +.\" +.SH +2.0\ Shell\ scripts +.LP +The shell may be used to read and execute commands +contained in a file. +For example, +.DS + sh file [ args \*(ZZ ] +.DE +calls the shell to read commands from \fIfile.\fP +Such a file is called a \fIshell script.\fP +Arguments may be supplied with the call +and are referred to in \fIfile\fP +using the positional parameters +\fB$1, $2, \*(ZZ\|.\fR +.LP +For example, if the file \fIwg\fP contains +.DS + who \*(VT grep $1 +.DE +then +.DS + sh wg fred +.DE +is equivalent to +.DS + who \*(VT grep fred +.DE +.LP +UNIX files have three independent attributes, +\fIread,\fP \fIwrite\fP and \fIexecute.\fP +The UNIX command \fIchmod\fP(1) may be used +to make a file executable. +For example, +.DS + chmod +x wg +.DE +will ensure that the file \fIwg\fP has execute status. +Following this, the command +.DS + wg fred +.DE +is equivalent to +.DS + sh wg fred +.DE +This allows shell scripts and other programs +to be used interchangeably. +In either case a new process is created to +run the command. +.LP +The `\fB#\fP' character is used as a comment character by the shell. +All characters following the `#' on a line are ignored. +.LP +A typical modern system has several different shells, some with differing +command syntax, and it is desirable to specify which one should be +invoked when an executable script is invoked. +If the special comment +.DS + #!/\fIpath\fP/\fIto\fP/\fIinterpreter\fP +.DE +appears as the first line in a script, it is used to specify the +absolute pathname of the shell (or other interpreter) that should be +used to execute the file. +(Without such a line, \fB/bin/sh\fP is assumed.) +It is best if a script explicitly states +what shell it is intended for in this manner. +.LP +As well as providing names for the positional +parameters, +the number of positional parameters to a script +is available as \fB$#\|.\fP +The name of the file being executed +is available as \fB$0\|.\fP +.LP +A special shell parameter \fB$\*(ST\fP +is used to substitute for all positional parameters +except \fB$0\|.\fP +A typical use of this is to provide +some default arguments, +as in, +.DS + nroff \(miT450 \(mims $\*(ST +.DE +which simply prepends some arguments +to those already given. +(The variable \fB$@\fP also expands to ``all positional +parameters'', but is subtly different when expanded inside quotes. +See section 3.5, below.) +.SH +2.1\ Control\ flow\ -\ for +.LP +A frequent use of shell scripts is to loop +through the arguments (\fB$1, $2, \*(ZZ\fR) +executing commands once for each argument. +An example of such a script is +\fItel\fP that searches the file +\fB/usr/share/telnos\fR +that contains lines of the form +.DS + \*(ZZ + fred mh0123 + bert mh0789 + \*(ZZ +.DE +The text of \fItel\fP is +.DS + #!/bin/sh + + for i + do + grep $i /usr/share/telnos + done +.DE +The command +.DS + tel fred +.DE +prints those lines in \fB/usr/share/telnos\fR +that contain the string \fIfred\|.\fP +.DS + tel fred bert +.DE +prints those lines containing \fIfred\fP +followed by those for \fIbert.\fP +.LP +The \fBfor\fP loop notation is recognized by the shell +and has the general form +.DS + \fBfor\fR \fIname\fR \fBin\fR \fIw1 w2 \*(ZZ\fR + \fBdo\fR \fIcommand-list\fR + \fBdone\fR +.DE +A \fIcommand-list\fP is a sequence of one or more +simple commands separated or terminated by a newline or semicolon. +Furthermore, reserved words +like \fBdo\fP and \fBdone\fP are only +recognized following a newline or +semicolon. +\fIname\fP is a shell variable that is set +to the words \fIw1 w2 \*(ZZ\fR in turn each time the \fIcommand-list\fP +following \fBdo\fP +is executed. +If \fBin\fR \fIw1 w2 \*(ZZ\fR +is omitted then the loop +is executed once for each positional parameter; +that is, \fBin\fR \fI$\*(ST\fR is assumed. +.LP +Another example of the use of the \fBfor\fP +loop is the \fIcreate\fP command +whose text is +.DS + for i do >$i; done +.DE +The command +.DS + create alpha beta +.DE +ensures that two empty files +\fIalpha\fP and \fIbeta\fP exist +and are empty. +The notation \fI>file\fP may be used on its +own to create or clear the contents of a file. +Notice also that a semicolon (or newline) is required before \fBdone.\fP +.SH +2.2\ Control\ flow\ -\ case +.LP +A multiple way branch is provided for by the +\fBcase\fP notation. +For example, +.DS + case $# in + \*(Ca1) cat \*(AP$1 ;; + \*(Ca2) cat \*(AP$2 <$1 ;; + \*(Ca\*(ST) echo \'usage: append [ from ] to\' ;; + esac +.DE +is an \fIappend\fP command. +When called +with one argument as +.DS + append file +.DE +\fB$#\fP is the string \fI1\fP and +the standard input is copied onto the +end of \fIfile\fP +using the \fIcat\fP command. +.DS + append file1 file2 +.DE +appends the contents of \fIfile1\fP +onto \fIfile2.\fP +If the number of arguments supplied to +\fIappend\fP is other than 1 or 2 +then a message is printed indicating +proper usage. +.LP +The general form of the \fBcase\fP command +is +.DS + \fBcase \fIword \fBin + \*(Ca\fIpattern\|\fB)\ \fIcommand-list\fB\|;; + \*(Ca\*(ZZ + \fBesac\fR +.DE +The shell attempts to match +\fIword\fR with each \fIpattern,\fR +in the order in which the patterns +appear. +If a match is found the +associated \fIcommand-list\fP is +executed and execution +of the \fBcase\fP is complete. +Since \*(ST is the pattern that matches any +string it can be used for the default case. +.LP +A word of caution: +no check is made to ensure that only +one pattern matches +the case argument. +The first match found defines the set of commands +to be executed. +In the example below the commands following +the second \*(ST will never be executed. +.DS + case $# in + \*(Ca\*(ST) \*(ZZ ;; + \*(Ca\*(ST) \*(ZZ ;; + esac +.DE +.LP +Another example of the use of the \fBcase\fP +construction is to distinguish +between different forms +of an argument. +The following example is a fragment of a \fIcc\fP command. +.DS + for i + do case $i in + \*(DC\(mi[ocs]) \*(ZZ ;; + \*(DC\(mi\*(ST) echo "unknown flag $i" ;; + \*(DC\*(ST.c) /lib/c0 $i \*(ZZ ;; + \*(DC\*(ST) echo "unexpected argument $i" ;; + \*(DOesac + done +.DE +.LP +To allow the same commands to be associated +with more than one pattern +the \fBcase\fP command provides +for alternative patterns +separated by a \*(VT\|. +For example, +.DS + case $i in + \*(Ca\(mix\*(VT\(miy) \*(ZZ + esac +.DE +is equivalent to +.DS + case $i in + \*(Ca\(mi[xy]) \*(ZZ + esac +.DE +.LP +The usual quoting conventions apply +so that +.DS + case $i in + \*(Ca\\?) \*(ZZ +.DE +will match the character \fB?\|.\fP +.SH +2.3\ Here\ documents +.LP +The shell script \fItel\fP +in section 2.1 uses the file \fB/usr/share/telnos\fR +to supply the data +for \fIgrep.\fP +An alternative is to include this +data +within the shell script as a \fIhere\fP document, as in, +.DS + for i + do grep $i \*(HE! + \*(DO\*(ZZ + \*(DOfred mh0123 + \*(DObert mh0789 + \*(DO\*(ZZ + ! + done +.DE +In this example +the shell takes the lines between \fB\*(HE!\fR and \fB!\fR +as the standard input for \fIgrep.\fP +The string \fB!\fR is arbitrary, the document +being terminated by a line that consists +of the string following \*(HE\|. +.LP +Parameters are substituted in the document +before it is made available to \fIgrep\fP +as illustrated by the following script +called \fIedg\|.\fP +.DS + ed $3 \*(HE% + g/$1/s//$2/g + w + % +.DE +The call +.DS + edg string1 string2 file +.DE +is then equivalent to the command +.DS + ed file \*(HE% + g/string1/s//string2/g + w + % +.DE +and changes all occurrences of \fIstring1\fP +in \fIfile\fP to \fIstring2\|.\fP +Substitution can be prevented using \\ +to quote the special character \fB$\fP +as in +.DS + ed $3 \*(HE+ + 1,\\$s/$1/$2/g + w + + +.DE +(This version of \fIedg\fP is equivalent to +the first except that \fIed\fP will print +a \fB?\fR if there are no occurrences of +the string \fB$1\|.\fP) +Substitution within a \fIhere\fP document +may be prevented entirely by quoting +the terminating string, +for example, +.DS + grep $i \*(HE'end' + \*(ZZ + end +.DE +The document is presented +without modification to \fIgrep.\fP +If parameter substitution is not required +in a \fIhere\fP document this latter form +is more efficient. +.SH +2.4\ Shell\ variables\(dg +.LP +.FS +Also known as \fIenvironment variables\fB, see \fIenvironment\fB(7). +.FE +The shell +provides string-valued variables. +Variable names begin with a letter +and consist of letters, digits and +underscores. +Variables may be given values by writing, for example, +.DS + user=fred\ box=m000\ acct=mh0000 +.DE +which assigns values to the variables +\fBuser, box\fP and \fBacct.\fP +A variable may be set to the null string +by saying, for example, +.DS + null= +.DE +The value of a variable is substituted +by preceding its name with \fB$\|\fP; +for example, +.DS + echo $user +.DE +will echo \fIfred.\fP +.LP +Variables may be used interactively +to provide abbreviations for frequently +used strings. +For example, +.DS + b=/usr/fred/bin + mv pgm $b +.DE +will move the file \fIpgm\fP +from the current directory to the directory \fB/usr/fred/bin\|.\fR +A more general notation is available for parameter +(or variable) +substitution, as in, +.DS + echo ${user} +.DE +which is equivalent to +.DS + echo $user +.DE +and is used when the parameter name is +followed by a letter or digit. +For example, +.DS + tmp=/tmp/ps + ps a >${tmp}a +.DE +will direct the output of \fIps\fR +to the file \fB/tmp/psa,\fR +whereas, +.DS + ps a >$tmpa +.DE +would cause the value of the variable \fBtmpa\fP +to be substituted. +.LP +Except for \fB$?\fP the following +are set initially by the shell. +\fB$?\fP is set after executing each command. +.RS +.IP \fB$?\fP 8 +The exit status (return code) +of the last command executed +as a decimal string. +Most commands return a zero exit status +if they complete successfully, +otherwise a non-zero exit status is returned. +Testing the value of return codes is dealt with +later under \fBif\fP and \fBwhile\fP commands. +.IP \fB$#\fP 8 +The number of positional parameters +(in decimal). +Used, for example, in the \fIappend\fP command +to check the number of parameters. +.IP \fB$$\fP 8 +The process number of this shell (in decimal). +Since process numbers are unique among +all existing processes, this string is +frequently used to generate +unique +temporary file names. +For example, +.DS + ps a >/tmp/ps$$ + \*(ZZ + rm /tmp/ps$$ +.DE +.IP \fB$\|!\fP 8 +The process number of the last process +run in the background (in decimal). +.IP \fB$\(mi\fP 8 +The current shell flags, such as +\fB\(mix\fR and \fB\(miv\|.\fR +.RE +.LP +Some variables have a special meaning to the +shell and should be avoided for general +use. +.RS +.IP \fB$\s-1MAIL\s0\fP 8 +When used interactively +the shell looks at the file +specified by this variable +before it issues a prompt. +If the specified file has been modified +since it +was last looked at the shell +prints the message +\fIyou have mail\fP before prompting +for the next command. +This variable is typically set +in the file \fB.profile,\fP +in the user's login directory. +For example, +.DS + \s-1MAIL\s0=/usr/spool/mail/fred +.DE +.IP \fB$\s-1HOME\s0\fP 8 +The default argument +for the \fIcd\fP command. +The current directory is used to resolve +file name references that do not begin with +a \fB/\|,\fR +and is changed using the \fIcd\fP command. +For example, +.DS + cd /usr/fred/bin +.DE +makes the current directory \fB/usr/fred/bin\|.\fR +.DS + cat wn +.DE +will print on the terminal the file \fIwn\fP +in this directory. +The command +\fIcd\fP with no argument +is equivalent to +.DS + cd $\s-1HOME\s0 +.DE +This variable is also typically set in the +the user's login profile. +.IP \fB$\s-1PWD\s0\fP 8 +The current working directory. Set by the \fIcd\fB command. +.IP \fB$\s-1PATH\s0\fP 8 +A list of directories that contain commands (the \fIsearch path\fR\|). +Each time a command is executed by the shell +a list of directories is searched +for an executable file. +.ne 5 +If \fB$\s-1PATH\s0\fP is not set +then the current directory, +\fB/bin\fP, and \fB/usr/bin\fP are searched by default. +.ne 5 +Otherwise \fB$\s-1PATH\s0\fP consists of directory +names separated by \fB:\|.\fP +For example, +.DS + \s-1PATH\s0=\fB:\fP/usr/fred/bin\fB:\fP/bin\fB:\fP/usr/bin +.DE +specifies that the current directory +(the null string before the first \fB:\fP\|), +\fB/usr/fred/bin, /bin \fRand\fP /usr/bin\fR +are to be searched in that order. +In this way individual users +can have their own `private' commands +that are accessible independently +of the current directory. +If the command name contains a \fB/\fR then this directory search +is not used; a single attempt +is made to execute the command. +.IP \fB$\s-1PS1\s0\fP 8 +The primary shell prompt string, by default, `\fB$\ \fR'. +.IP \fB$\s-1PS2\s0\fP 8 +The shell prompt when further input is needed, +by default, `\fB>\ \fR'. +.IP \fB$\s-1IFS\s0\fP 8 +The set of characters used by \fIblank +interpretation\fR (see section 3.5). +.IP \fB$\s-1ENV\s0\fP 8 +The shell reads and executes the commands in the file +specified by this variable when it is initially started. +Unlike the \fB.profile\fP file, these commands are executed by all +shells, not just the one started at login. +(Most versions of the shell specify a filename that is used if +\s-1ENV\s0 is not explicitly set. See the manual page for your shell.) +.RE +.SH +2.5\ The\ test\ command +.LP +The \fItest\fP command, although not part of the shell, +is intended for use by shell programs. +For example, +.DS + test \(mif file +.DE +returns zero exit status if \fIfile\fP +exists and non-zero exit status otherwise. +In general \fItest\fP evaluates a predicate +and returns the result as its exit status. +Some of the more frequently used \fItest\fP +arguments are given here, see \fItest\fP(1) +for a complete specification. +.DS + test s true if the argument \fIs\fP is not the null string + test \(mif file true if \fIfile\fP exists + test \(mir file true if \fIfile\fP is readable + test \(miw file true if \fIfile\fP is writable + test \(mid file true if \fIfile\fP is a directory +.DE +The \fItest\fP command is known as `\fB[\fP' and may be invoked as +such. +For aesthetic reasons, the command ignores a close bracket `\fB]\fP' given +at the end of a command so +.DS + [ -f filename ] +.DE +and +.DS + test -f filename +.DE +are completely equivalent. +Typically, the bracket notation is used when \fItest\fP is invoked inside +shell control constructs. +.SH +2.6\ Control\ flow\ -\ while +.LP +The actions of +the \fBfor\fP loop and the \fBcase\fP +branch are determined by data available to the shell. +A \fBwhile\fP or \fBuntil\fP loop +and an \fBif then else\fP branch +are also provided whose +actions are determined by the exit status +returned by commands. +A \fBwhile\fP loop has the general form +.DS + \fBwhile\fP \fIcommand-list\*1\fP + \fBdo\fP \fIcommand-list\*2\fP + \fBdone\fP +.DE +.LP +The value tested by the \fBwhile\fP command +is the exit status of the last simple command +following \fBwhile.\fP +Each time round the loop +\fIcommand-list\*1\fP is executed; +if a zero exit status is returned then +\fIcommand-list\*2\fP +is executed; +otherwise, the loop terminates. +For example, +.DS + while [ $1 ] + do \*(ZZ + \*(DOshift + done +.DE +is equivalent to +.DS + for i + do \*(ZZ + done +.DE +\fIshift\fP is a shell command that +renames the positional parameters +\fB$2, $3, \*(ZZ\fR as \fB$1, $2, \*(ZZ\fR +and loses \fB$1\|.\fP +.LP +Another kind of use for the \fBwhile/until\fP +loop is to wait until some +external event occurs and then run +some commands. +In an \fBuntil\fP loop +the termination condition is reversed. +For example, +.DS + until [ \(mif file ] + do sleep 300; done + \fIcommands\fP +.DE +will loop until \fIfile\fP exists. +Each time round the loop it waits for +5 minutes before trying again. +(Presumably another process +will eventually create the file.) +.LP +The most recent enclosing loop may be exited with the \fBbreak\fP +command, or the rest of the body skipped and the next iteration begun +with the \fBcontinue\fP command. +.LP +The commands \fItrue\fP(1) and \fIfalse\fP(1) return 0 and non-zero +exit statuses respectively. They are sometimes of use in control flow, +e.g.: +.DS + while true + do date; sleep 5 + done +.DE +is an infinite loop that prints the date and time every five seconds. +.SH +2.7\ Control\ flow\ -\ if +.LP +Also available is a +general conditional branch +of the form, +.DS + \fBif\fP \fIcommand-list + \fBthen \fIcommand-list + \fBelse \fIcommand-list + \fBfi\fR +.DE +that tests the value returned by the last simple command +following \fBif.\fP +.LP +The \fBif\fP command may be used +in conjunction with the \fItest\fP command +to test for the existence of a file as in +.DS + if [ \(mif file ] + then \fIprocess file\fP + else \fIdo something else\fP + fi +.DE +.LP +An example of the use of \fBif, case\fP +and \fBfor\fP constructions is given in +section 2.10\|. +.LP +A multiple test \fBif\fP command +of the form +.DS + if \*(ZZ + then \*(ZZ + else if \*(ZZ + then \*(ZZ + else if \*(ZZ + \*(ZZ + fi + fi + fi +.DE +may be written using an extension of the \fBif\fP +notation as, +.DS + if \*(ZZ + then \*(ZZ + elif \*(ZZ + then \*(ZZ + elif \*(ZZ + \*(ZZ + fi +.DE +.LP +The following example is an implementation of the \fItouch\fP command +which changes the `last modified' time for a list +of files. +The command may be used in conjunction +with \fImake\fP(1) to force recompilation of a list +of files. +.DS + #!/bin/sh + + flag= + for i + do case $i in + \*(DC\(mic) flag=N ;; + \*(DC\*(ST) if [ \(mif $i ] + \*(DC then cp $i junk$$; mv junk$$ $i + \*(DC elif [ $flag ] + \*(DC then echo file \\'$i\\' does not exist + \*(DC else >$i + \*(DC fi + \*(DO esac + done +.DE +The \fB\(mic\fP flag is used in this command to +force subsequent files to be created if they do not already exist. +Otherwise, if the file does not exist, an error message is printed. +The shell variable \fIflag\fP +is set to some non-null string if the \fB\(mic\fP +argument is encountered. +The commands +.DS + cp \*(ZZ; mv \*(ZZ +.DE +copy the file and then overwrite it with the copy, +thus causing the last modified date to be updated. +.LP +The sequence +.DS + if command1 + then command2 + fi +.DE +may be written +.DS + command1 && command2 +.DE +Conversely, +.DS + command1 \*(VT\*(VT command2 +.DE +executes \fIcommand2\fP only if \fIcommand1\fP +fails. +In each case the value returned +is that of the last simple command executed. +.LP +Placing a `\fB!\fP' in front of a pipeline inverts its exit +status, almost in the manner of the C operator of the same name. +Thus: +.DS + if ! [ -d $1 ] + then + echo $1 is not a directory + fi +.DE +will print a message only if $1 is not a directory. +.SH +2.8\ Command\ grouping +.LP +Commands may be grouped in two ways, +.DS + \fB{\fI command-list\fB ; }\fR +.DE +and +.DS + \fB(\fI command-list\fB )\fR +.DE +.LP +In the first \fIcommand-list\fP is simply executed. +The second form executes \fIcommand-list\fP +as a separate process. +For example, +.DS + (cd x; rm junk ) +.DE +executes \fIrm junk\fP in the directory +\fBx\fP without changing the current +directory of the invoking shell. +.LP +The commands +.DS + cd x; rm junk +.DE +have the same effect but leave the invoking +shell in the directory \fBx.\fP +.SH +2.9\ Shell\ Functions +.LP +A function may be defined by the syntax +.DS + \fIfuncname\fP() \fB{\fI command-list\fB ; }\fR +.DE +Functions are invoked within a script as though they were separate +commands of the same name. +While they are executed, the +positional parameters \fB$1, $2, \*(ZZ\fR are temporarily set to the +arguments passed to the function. For example: +.DS + count() { + echo $2 : $# + } + + count a b c +.DE +would print `b : 3'. +.SH +2.10\ Debugging\ shell\ scripts +.LP +The shell provides two tracing mechanisms +to help when debugging shell scripts. +The first is invoked within the script +as +.DS + set \(miv +.DE +(\fBv\fP for verbose) and causes lines of the +script to be printed as they are read. +It is useful to help isolate syntax errors. +It may be invoked without modifying the script +by saying +.DS + sh \(miv \fIscript\fP \*(ZZ +.DE +where \fIscript\fP is the name of the shell script. +This flag may be used in conjunction +with the \fB\(min\fP flag which prevents +execution of subsequent commands. +(Note that saying \fIset \(min\fP at a terminal +will render the terminal useless +until an end-of-file is typed.) +.LP +The command +.DS + set \(mix +.DE +will produce an execution +trace. +Following parameter substitution +each command is printed as it is executed. +(Try these at the terminal to see +what effect they have.) +Both flags may be turned off by saying +.DS + set \(mi +.DE +and the current setting of the shell flags is available as \fB$\(mi\|\fR. +.SH +2.11\ The\ man\ command +.LP +The following is a simple implementation of the \fIman\fP command, +which is used to display sections of the UNIX manual on your terminal. +It is called, for example, as +.DS + man sh + man \(mit ed + man 2 fork +.DE +In the first the manual section for \fIsh\fP +is displayed.. +Since no section is specified, section 1 is used. +The second example will typeset (\fB\(mit\fP option) +the manual section for \fIed.\fP +The last prints the \fIfork\fP manual page +from section 2, which covers system calls. +.sp 2 +.DS + #!/bin/sh + + cd /usr/share/man + + # "#" is the comment character + # default is nroff ($N), section 1 ($s) + N=n\ s=1 + + for i + do case $i in +.sp .5 + \*(DC[1\(mi9]\*(ST) s=$i ;; +.sp .5 + \*(DC\(mit) N=t ;; +.sp .5 + \*(DC\(min) N=n ;; +.sp .5 + \*(DC\(mi\*(ST) echo unknown flag \\'$i\\' ;; +.sp .5 + \*(DC\*(ST) if [ \(mif man$s/$i.$s ] + \*(DC then + \*(DC ${N}roff \(miman man$s/$i.$s + \*(DC else # look through all manual sections + \*(DC found=no + \*(DC for j in 1 2 3 4 5 6 7 8 9 + \*(DC do + \*(DC \*(DOif [ \(mif man$j/$i.$j ] + \*(DC \*(DOthen + \*(DC \*(DO\*(THman $j $i + \*(DC \*(DO\*(THfound=yes + \*(DC \*(DO\*(THbreak + \*(DC \*(DOfi + \*(DC done + \*(DC case $found in + \*(DC \*(Cano) echo \\'$i: manual page not found\\' + \*(DC esac + \*(DC fi + \*(DOesac + done +.DE +.ce +.ft B +Figure 1. A version of the man command +.ft R diff --git a/bin/sh/USD.doc/t3 b/bin/sh/USD.doc/t3 new file mode 100644 index 0000000..aab53ee --- /dev/null +++ b/bin/sh/USD.doc/t3 @@ -0,0 +1,976 @@ +.\" $NetBSD: t3,v 1.3 2010/08/22 02:19:07 perry Exp $ +.\" +.\" Copyright (C) Caldera International Inc. 2001-2002. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" Redistributions of source code and documentation must retain the above +.\" copyright notice, this list of conditions and the following +.\" disclaimer. +.\" +.\" 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. +.\" +.\" All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" +.\" This product includes software developed or owned by Caldera +.\" International, Inc. Neither the name of Caldera International, Inc. +.\" nor the names of other contributors may be used to endorse or promote +.\" products derived from this software without specific prior written +.\" permission. +.\" +.\" USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA +.\" INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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) RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" @(#)t3 8.1 (Berkeley) 6/8/93 +.\" +.SH +3.0\ Keyword\ parameters +.LP +Shell variables may be given values +by assignment +or when a shell script is invoked. +An argument to a command of the form +\fIname=value\fP +that precedes the command name +causes \fIvalue\fP +to be assigned to \fIname\fP +before execution of the command begins. +The value of \fIname\fP in the invoking +shell is not affected. +For example, +.DS + user=fred\ command +.DE +will execute \fIcommand\fP with +\fBuser\fP set to \fIfred\fP. +.\" Removed by Perry Metzger because -k is not in POSIX +.\" +.\" The \fB\(mik\fR flag causes arguments of the form +.\" \fIname=value\fP to be interpreted in this way +.\" anywhere in the argument list. +.\" Such \fInames\fP are sometimes +.\" called keyword parameters. +.\" If any arguments remain they +.\" are available as positional +.\" parameters \fB$1, $2, \*(ZZ\|.\fP +.LP +The \fIset\fP command +may also be used to set positional parameters +from within a script. +For example, +.DS + set\ \(mi\(mi\ \*(ST +.DE +will set \fB$1\fP to the first file name +in the current directory, \fB$2\fP to the next, +and so on. +Note that the first argument, \(mi\(mi, ensures correct treatment +when the first file name begins with a \(mi\|. +.LP +.SH +3.1\ Parameter\ transmission +.LP +When a command is executed both positional parameters +and shell variables may be set on invocation. +Variables are also made available implicitly +to a command +by specifying in advance that such parameters +are to be exported from the invoking shell. +For example, +.DS + export\ user\ box=red +.DE +marks the variables \fBuser\fP and \fBbox\fP +for export (setting \fBbox\fP to ``red'' in the process). +When a command is invoked +copies are made of all exportable variables +(also known as \fIenvironment variables\fP) +for use within the invoked program. +Modification of such variables +within an invoked command does not +affect the values in the invoking shell. +It is generally true of +a shell script or other program +that it +cannot modify the state +of its caller without explicit +actions on the part of the caller. +.\" Removed by Perry Metzger because this is confusing to beginners. +.\" +.\" (Shared file descriptors are an +.\" exception to this rule.) +.LP +Names whose value is intended to remain +constant may be declared \fIreadonly\|.\fP +The form of this command is the same as that of the \fIexport\fP +command, +.DS + readonly name[=value] \*(ZZ +.DE +Subsequent attempts to set readonly variables +are illegal. +.SH +3.2\ Parameter\ substitution +.LP +If a shell parameter is not set +then the null string is substituted for it. +For example, if the variable \fBd\fP +is not set +.DS + echo $d +.DE +or +.DS + echo ${d} +.DE +will echo nothing. +A default string may be given +as in +.DS + echo ${d:\(mi\fB.\fR} +.DE +which will echo +the value of the variable \fBd\fP +if it is set and not null and `\fB.\fP' otherwise. +The default string is evaluated using the usual +quoting conventions so that +.DS + echo ${d:\(mi\'\*(ST\'} +.DE +will echo \fB\*(ST\fP if the variable \fBd\fP +is not set or null. +Similarly +.DS + echo ${d:\(mi$1} +.DE +will echo the value of \fBd\fP if it is set and not null +and the value (if any) of \fB$1\fP otherwise. +.LP +The notation ${d:+\fB.\fR} performs the inverse operation. It +substitutes `\fB.\fP' if \fBd\fP is set or not null, and otherwise +substitutes null. +.LP +A variable may be assigned a default value +using +the notation +.DS + echo ${d:=\fB.\fR} +.DE +which substitutes the same string as +.DS + echo ${d:\(mi\fB.\fR} +.DE +and if \fBd\fP were not previously set or null +then it will be set to the string `\fB.\fP'\|. +.LP +If there is no sensible default then +the notation +.DS + echo ${d:?\fImessage\fP} +.DE +will echo the value of the variable \fBd\fP if it is set and not null, +otherwise \fImessage\fP is printed by the shell and +execution of the shell script is abandoned. +If \fImessage\fP is absent then a standard message +is printed. +A shell script that requires some variables +to be set might start as follows: +.DS + :\ ${user:?}\ ${acct:?}\ ${bin:?} + \*(ZZ +.DE +Colon (\fB:\fP) is a command +that is +built in to the shell and does nothing +once its arguments have been evaluated. +If any of the variables \fBuser, acct\fP +or \fBbin\fP are not set then the shell +will abandon execution of the script. +.SH +3.3\ Command\ substitution +.LP +The standard output from a command can be +substituted in a similar way to parameters. +The command \fIpwd\fP prints on its standard +output the name of the current directory. +For example, if the current directory is +\fB/usr/fred/bin\fR +then the commands +.DS + d=$(pwd) +.DE +(or the older notation d=\`pwd\`) +is equivalent to +.DS + d=/usr/fred/bin +.DE +.LP +The entire string inside $(\*(ZZ)\| (or between grave accents \`\*(ZZ\`) +is taken as the command +to be executed +and is replaced with the output from +the command. +(The difference between the $(\*(ZZ) and \`\*(ZZ\` notations is that +the former may be nested, while the latter cannot be.) +.LP +The command is written using the usual quoting conventions, +except that inside \`\*(ZZ\` +a \fB\`\fR must be escaped using +a \fB\\\|\fR. +For example, +.DS + ls $(echo "$HOME") +.DE +is equivalent to +.DS + ls $HOME +.DE +Command substitution occurs in all contexts +where parameter substitution occurs (including \fIhere\fP documents) and the +treatment of the resulting text is the same +in both cases. +This mechanism allows string +processing commands to be used within +shell scripts. +An example of such a command is \fIbasename\fP +which removes a specified suffix from a string. +For example, +.DS + basename main\fB.\fPc \fB.\fPc +.DE +will print the string \fImain\|.\fP +Its use is illustrated by the following +fragment from a \fIcc\fP command. +.DS + case $A in + \*(Ca\*(ZZ + \*(Ca\*(ST\fB.\fPc) B=$(basename $A \fB.\fPc) + \*(Ca\*(ZZ + esac +.DE +that sets \fBB\fP to the part of \fB$A\fP +with the suffix \fB.c\fP stripped. +.LP +Here are some composite examples. +.RS +.IP \(bu +.ft B +for i in \`ls \(mit\`; do \*(ZZ +.ft R +.br +The variable \fBi\fP is set +to the names of files in time order, +most recent first. +.IP \(bu +.ft B +set \(mi\(mi\| \`date\`; echo $6 $2 $3, $4 +.ft R +.br +will print, e.g., +.ft I +1977 Nov 1, 23:59:59 +.ft R +.RE +.SH +3.4\ Arithmetic\ Expansion +.LP +Within a $((\*(ZZ)) construct, integer arithmetic operations are +evaluated. +(The $ in front of variable names is optional within $((\*(ZZ)). +For example: +.DS + x=5; y=1 + echo $(($x+3*2)) + echo $((y+=x)) + echo $y +.DE +will print `11', then `6', then `6' again. +Most of the constructs permitted in C arithmetic operations are +permitted though some (like `++') are not universally supported \(em +see the shell manual page for details. +.SH +3.5\ Evaluation\ and\ quoting +.LP +The shell is a macro processor that +provides parameter substitution, command substitution and file +name generation for the arguments to commands. +This section discusses the order in which +these evaluations occur and the +effects of the various quoting mechanisms. +.LP +Commands are parsed initially according to the grammar +given in appendix A. +Before a command is executed +the following +substitutions occur. +.RS +.IP \(bu +parameter substitution, e.g. \fB$user\fP +.IP \(bu +command substitution, e.g. \fB$(pwd)\fP or \fB\`pwd\`\fP +.IP \(bu +arithmetic expansion, e.g. \fB$(($count+1))\fP +.RS +.LP +Only one evaluation occurs so that if, for example, the value of the variable +\fBX\fP +is the string \fI$y\fP +then +.DS + echo $X +.DE +will echo \fI$y\|.\fP +.RE +.IP \(bu +blank interpretation +.RS +.LP +Following the above substitutions +the resulting characters +are broken into non-blank words (\fIblank interpretation\fP). +For this purpose `blanks' are the characters of the string +\fB$\s-1IFS\s0\fP. +By default, this string consists of blank, tab and newline. +The null string +is not regarded as a word unless it is quoted. +For example, +.DS + echo \'\' +.DE +will pass on the null string as the first argument to \fIecho\fP, +whereas +.DS + echo $null +.DE +will call \fIecho\fR with no arguments +if the variable \fBnull\fP is not set +or set to the null string. +.RE +.IP \(bu +file name generation +.RS +.LP +Each word +is then scanned for the file pattern characters +\fB\*(ST, ?\fR and \fB[\*(ZZ]\fR +and an alphabetical list of file names +is generated to replace the word. +Each such file name is a separate argument. +.RE +.RE +.LP +The evaluations just described also occur +in the list of words associated with a \fBfor\fP +loop. +Only substitution occurs +in the \fIword\fP used +for a \fBcase\fP branch. +.LP +As well as the quoting mechanisms described +earlier using \fB\\\fR and \fB\'\*(ZZ\'\fR +a third quoting mechanism is provided using double quotes. +Within double quotes parameter and command substitution +occurs but file name generation and the interpretation +of blanks does not. +The following characters +have a special meaning within double quotes +and may be quoted using \fB\\\|.\fP +.DS + \fB$ \fPparameter substitution + \fB$()\fP command substitution + \fB\`\fP command substitution + \fB"\fP ends the quoted string + \fB\e\fP quotes the special characters \fB$ \` " \e\fP +.DE +For example, +.DS + echo "$x" +.DE +will pass the value of the variable \fBx\fP as a +single argument to \fIecho.\fP +Similarly, +.DS + echo "$\*(ST" +.DE +will pass the positional parameters as a single +argument and is equivalent to +.DS + echo "$1 $2 \*(ZZ" +.DE +The notation \fB$@\fP +is the same as \fB$\*(ST\fR +except when it is quoted. +.DS + echo "$@" +.DE +will pass the positional parameters, unevaluated, to \fIecho\fR +and is equivalent to +.DS + echo "$1" "$2" \*(ZZ +.DE +.LP +The following table gives, for each quoting mechanism, +the shell metacharacters that are evaluated. +.DS +.ce +.ft I +metacharacter +.ft +.in 1.5i + \e $ * \` " \' +\' n n n n n t +\` y n n t n n +" y y n y t n + + t terminator + y interpreted + n not interpreted + +.in +.ft B +.ce +Figure 2. Quoting mechanisms +.ft +.DE +.LP +In cases where more than one evaluation of a string +is required the built-in command \fIeval\fP +may be used. +For example, +if the variable \fBX\fP has the value +\fI$y\fP, and if \fBy\fP has the value \fIpqr\fP +then +.DS + eval echo $X +.DE +will echo the string \fIpqr\|.\fP +.LP +In general the \fIeval\fP command +evaluates its arguments (as do all commands) +and treats the result as input to the shell. +The input is read and the resulting command(s) +executed. +For example, +.DS + wg=\'eval who\*(VTgrep\' + $wg fred +.DE +is equivalent to +.DS + who\*(VTgrep fred +.DE +In this example, +\fIeval\fP is required +since there is no interpretation +of metacharacters, such as \fB\*(VT\|\fR, following +substitution. +.SH +3.6\ Error\ handling +.LP +The treatment of errors detected by +the shell depends on the type of error +and on whether the shell is being +used interactively. +An interactive shell is one whose +input and output are connected +to a terminal. +.\" Removed by Perry Metzger, obsolete and excess detail +.\" +.\" (as determined by +.\" \fIgtty\fP (2)). +A shell invoked with the \fB\(mii\fP +flag is also interactive. +.LP +Execution of a command (see also 3.7) may fail +for any of the following reasons. +.IP \(bu +Input output redirection may fail. +For example, if a file does not exist +or cannot be created. +.IP \(bu +The command itself does not exist +or cannot be executed. +.IP \(bu +The command terminates abnormally, +for example, with a "bus error" +or "memory fault". +See Figure 2 below for a complete list +of UNIX signals. +.IP \(bu +The command terminates normally +but returns a non-zero exit status. +.LP +In all of these cases the shell +will go on to execute the next command. +Except for the last case an error +message will be printed by the shell. +All remaining errors cause the shell +to exit from a script. +An interactive shell will return +to read another command from the terminal. +Such errors include the following. +.IP \(bu +Syntax errors. +e.g., if \*(ZZ then \*(ZZ done +.IP \(bu +A signal such as interrupt. +The shell waits for the current +command, if any, to finish execution and +then either exits or returns to the terminal. +.IP \(bu +Failure of any of the built-in commands +such as \fIcd.\fP +.LP +The shell flag \fB\(mie\fP +causes the shell to terminate +if any error is detected. +.DS +1 hangup +2 interrupt +3* quit +4* illegal instruction +5* trace trap +6* IOT instruction +7* EMT instruction +8* floating point exception +9 kill (cannot be caught or ignored) +10* bus error +11* segmentation violation +12* bad argument to system call +13 write on a pipe with no one to read it +14 alarm clock +15 software termination (from \fIkill\fP (1)) + +.DE +.ft B +.ce +Figure 3. UNIX signals\(dg +.ft +.FS +\(dg Additional signals have been added in modern Unix. +See \fIsigvec\fP(2) or \fIsignal\fP(3) for an up-to-date list. +.FE +Those signals marked with an asterisk +produce a core dump +if not caught. +However, +the shell itself ignores quit which is the only +external signal that can cause a dump. +The signals in this list of potential interest +to shell programs are 1, 2, 3, 14 and 15. +.SH +3.7\ Fault\ handling +.LP +shell scripts normally terminate +when an interrupt is received from the +terminal. +The \fItrap\fP command is used +if some cleaning up is required, such +as removing temporary files. +For example, +.DS + trap\ \'rm\ /tmp/ps$$; exit\'\ 2 +.DE +sets a trap for signal 2 (terminal +interrupt), and if this signal is received +will execute the commands +.DS + rm /tmp/ps$$; exit +.DE +\fIexit\fP is +another built-in command +that terminates execution of a shell script. +The \fIexit\fP is required; otherwise, +after the trap has been taken, +the shell will resume executing +the script +at the place where it was interrupted. +.LP +UNIX signals can be handled in one of three ways. +They can be ignored, in which case +the signal is never sent to the process. +They can be caught, in which case the process +must decide what action to take when the +signal is received. +Lastly, they can be left to cause +termination of the process without +it having to take any further action. +If a signal is being ignored +on entry to the shell script, for example, +by invoking it in the background (see 3.7) then \fItrap\fP +commands (and the signal) are ignored. +.LP +The use of \fItrap\fP is illustrated +by this modified version of the \fItouch\fP +command (Figure 4). +The cleanup action is to remove the file \fBjunk$$\fR\|. +.DS + #!/bin/sh + + flag= + trap\ \'rm\ \(mif\ junk$$;\ exit\'\ 1 2 3 15 + for i + do\ case\ $i\ in + \*(DC\(mic) flag=N ;; + \*(DC\*(ST) if\ test\ \(mif\ $i + \*(DC then cp\ $i\ junk$$;\ mv\ junk$$ $i + \*(DC elif\ test\ $flag + \*(DC then echo\ file\ \\'$i\\'\ does\ not\ exist + \*(DC else >$i + \*(DC fi + \*(DOesac + done +.DE +.sp +.ft B +.ce +Figure 4. The touch command +.ft +.sp +The \fItrap\fP command +appears before the creation +of the temporary file; +otherwise it would be +possible for the process +to die without removing +the file. +.LP +Since there is no signal 0 in UNIX +it is used by the shell to indicate the +commands to be executed on exit from the +shell script. +.LP +A script may, itself, elect to +ignore signals by specifying the null +string as the argument to trap. +The following fragment is taken from the +\fInohup\fP command. +.DS + trap \'\' 1 2 3 15 +.DE +which causes \fIhangup, interrupt, quit \fRand\fI kill\fR +to be ignored both by the +script and by invoked commands. +.LP +Traps may be reset by saying +.DS + trap 2 3 +.DE +which resets the traps for signals 2 and 3 to their default values. +A list of the current values of traps may be obtained +by writing +.DS + trap +.DE +.LP +The script \fIscan\fP (Figure 5) is an example +of the use of \fItrap\fP where there is no exit +in the trap command. +\fIscan\fP takes each directory +in the current directory, prompts +with its name, and then executes +commands typed at the terminal +until an end of file or an interrupt is received. +Interrupts are ignored while executing +the requested commands but cause +termination when \fIscan\fP is +waiting for input. +.DS + d=\`pwd\` + for\ i\ in\ \*(ST + do\ if\ test\ \(mid\ $d/$i + \*(DOthen\ cd\ $d/$i + \*(DO\*(THwhile\ echo\ "$i:" + \*(DO\*(TH\*(WHtrap\ exit\ 2 + \*(DO\*(TH\*(WHread\ x + \*(DO\*(THdo\ trap\ :\ 2;\ eval\ $x;\ done + \*(DOfi + done +.DE +.sp +.ft B +.ce +Figure 5. The scan command +.ft +.sp +\fIread x\fR is a built-in command that reads one line from the +standard input +and places the result in the variable \fBx\|.\fP +It returns a non-zero exit status if either +an end-of-file is read or an interrupt +is received. +.SH +3.8\ Command\ execution +.LP +To run a command (other than a built-in) the shell first creates +a new process using the system call \fIfork.\fP +The execution environment for the command +includes input, output and the states of signals, and +is established in the child process +before the command is executed. +The built-in command \fIexec\fP +is used in the rare cases when no fork +is required +and simply replaces the shell with a new command. +For example, a simple version of the \fInohup\fP +command looks like +.DS + trap \\'\\' 1 2 3 15 + exec $\*(ST +.DE +The \fItrap\fP turns off the signals specified +so that they are ignored by subsequently created commands +and \fIexec\fP replaces the shell by the command +specified. +.LP +Most forms of input output redirection have already been +described. +In the following \fIword\fP is only subject +to parameter and command substitution. +No file name generation or blank interpretation +takes place so that, for example, +.DS + echo \*(ZZ >\*(ST.c +.DE +will write its output into a file whose name is \fB\*(ST.c\|.\fP +Input output specifications are evaluated left to right +as they appear in the command. +.IP >\ \fIword\fP 12 +The standard output (file descriptor 1) +is sent to the file \fIword\fP which is +created if it does not already exist. +.IP \*(AP\ \fIword\fP 12 +The standard output is sent to file \fIword.\fP +If the file exists then output is appended +(by seeking to the end); +otherwise the file is created. +.IP <\ \fIword\fP 12 +The standard input (file descriptor 0) +is taken from the file \fIword.\fP +.IP \*(HE\ \fIword\fP 12 +The standard input is taken from the lines +of shell input that follow up to but not +including a line consisting only of \fIword.\fP +If \fIword\fP is quoted then no interpretation +of the document occurs. +If \fIword\fP is not quoted +then parameter and command substitution +occur and \fB\\\fP is used to quote +the characters \fB\\\fP \fB$\fP \fB\`\fP and the first character +of \fIword.\fP +In the latter case \fB\\newline\fP is ignored (c.f. quoted strings). +.IP >&\ \fIdigit\fP 12 +The file descriptor \fIdigit\fP is duplicated using the system +call \fIdup\fP (2) +and the result is used as the standard output. +.IP <&\ \fIdigit\fP 12 +The standard input is duplicated from file descriptor \fIdigit.\fP +.IP <&\(mi 12 +The standard input is closed. +.IP >&\(mi 12 +The standard output is closed. +.LP +Any of the above may be preceded by a digit in which +case the file descriptor created is that specified by the digit +instead of the default 0 or 1. +For example, +.DS + \*(ZZ 2>file +.DE +runs a command with message output (file descriptor 2) +directed to \fIfile.\fP +.DS + \*(ZZ 2>&1 +.DE +runs a command with its standard output and message output +merged. +(Strictly speaking file descriptor 2 is created +by duplicating file descriptor 1 but the effect is usually to +merge the two streams.) +.\" Removed by Perry Metzger, most of this is now obsolete +.\" +.\" .LP +.\" The environment for a command run in the background such as +.\" .DS +.\" list \*(ST.c \*(VT lpr & +.\" .DE +.\" is modified in two ways. +.\" Firstly, the default standard input +.\" for such a command is the empty file \fB/dev/null\|.\fR +.\" This prevents two processes (the shell and the command), +.\" which are running in parallel, from trying to +.\" read the same input. +.\" Chaos would ensue +.\" if this were not the case. +.\" For example, +.\" .DS +.\" ed file & +.\" .DE +.\" would allow both the editor and the shell +.\" to read from the same input at the same time. +.\" .LP +.\" The other modification to the environment of a background +.\" command is to turn off the QUIT and INTERRUPT signals +.\" so that they are ignored by the command. +.\" This allows these signals to be used +.\" at the terminal without causing background +.\" commands to terminate. +.\" For this reason the UNIX convention +.\" for a signal is that if it is set to 1 +.\" (ignored) then it is never changed +.\" even for a short time. +.\" Note that the shell command \fItrap\fP +.\" has no effect for an ignored signal. +.SH +3.9\ Invoking\ the\ shell +.LP +The following flags are interpreted by the shell +when it is invoked. +If the first character of argument zero is a minus, +then commands are read from the file \fB.profile\|.\fP +.IP \fB\(mic\fP\ \fIstring\fP +.br +If the \fB\(mic\fP flag is present then +commands are read from \fIstring\|.\fP +.IP \fB\(mis\fP +If the \fB\(mis\fP flag is present or if no +arguments remain +then commands are read from the standard input. +Shell output is written to +file descriptor 2. +.IP \fB\(mii\fP +If the \fB\(mii\fP flag is present or +if the shell input and output are attached to a terminal (as told by \fIgtty\fP) +then this shell is \fIinteractive.\fP +In this case TERMINATE is ignored (so that \fBkill 0\fP +does not kill an interactive shell) and INTERRUPT is caught and ignored +(so that \fBwait\fP is interruptable). +In all cases QUIT is ignored by the shell. +.SH +3.10\ Job\ Control +.LP +When a command or pipeline (also known as a \fIjob\fP) is running in +the foreground, entering the stop character (typically +\s-1CONTROL-Z\s0 but user settable with the \fIstty\fP(1) command) +will usually cause the job to stop. +.LP +The jobs associated with the current shell may be listed by entering +the \fIjobs\fP command. +Each job has an associated \fIjob number\fP. +Jobs that are stopped may be continued by entering +.DS + bg %\fIjobnumber\fP +.DE +and jobs may be moved to the foreground by entering +.DS + fg %\fIjobnumber\fP +.DE +If there is a sole job with a particular name (say only one instance +of \fIcc\fP running), \fIfg\fP and \fIbg\fP may also use name of the +command in place of the number, as in: +.DS + bg %cc +.DE +If no `\fB%\fP' clause is entered, most recently stopped job +(indicated with a `+' by the \fIjobs\fP command) will be assumed. +See the manual page for the shell for more details. +.SH +3.11\ Aliases +.LP +The \fIalias\fP command creates a so-called shell alias, which is an +abbreviation that macro-expands at run time into some other command. +For example: +.DS + alias ls="ls -F" +.DE +would cause the command sequence \fBls -F\fP to be executed whenever +the user types \fBls\fP into the shell. +Note that if the user types \fBls -a\fP, the shell will in fact +execute \fBls -F -a\fP. +The command \fBalias\fP on its own prints out all current aliases. +The \fIunalias\fP command, as in: +.DS + unalias ls +.DE +will remove an existing alias. +Aliases can shadow pre-existing commands, as in the example above. +They are typically used to override the interactive behavior of +commands in small ways, for example to always invoke some program with +a favorite option, and are almost never found in scripts. +.SH +3.12\ Command\ Line\ Editing\ and\ Recall +.LP +When working interactively with the shell, it is often tedious to +retype previously entered commands, especially if they are complicated. +The shell therefore maintains a so-called \fIhistory\fP, which is +stored in the file specified by the \fB\s-1HISTFILE\s0\fP environment +variable if it is set. +Users may view, edit, and re-enter previous lines of input using +a small subset of the commands of the \fIvi\fP(1) or +\fIemacs\fP(1)\(dg editors. +.FS +Technically, vi command editing is standardized by POSIX while emacs +is not. +However, all modern shells support both styles. +.FE +Emacs style editing may be selected by entering +.DS + set -o emacs +.DE +and vi style editing may be selected with +.DS + set -o vi +.DE +The details of how command line editing works are beyond the scope of +this document. +See the shell manual page for details. +.SH +Acknowledgements +.LP +The design of the shell is +based in part on the original UNIX shell +.[ +unix command language thompson +.] +and the PWB/UNIX shell, +.[ +pwb shell mashey unix +.] +some +features having been taken from both. +Similarities also exist with the +command interpreters +of the Cambridge Multiple Access System +.[ +cambridge multiple access system hartley +.] +and of CTSS. +.[ +ctss +.] +.LP +I would like to thank Dennis Ritchie +and John Mashey for many +discussions during the design of the shell. +I am also grateful to the members of the Computing Science Research Center +and to Joe Maranzano for their +comments on drafts of this document. +.SH +.[ +$LIST$ +.] diff --git a/bin/sh/USD.doc/t4 b/bin/sh/USD.doc/t4 new file mode 100644 index 0000000..7719d6c --- /dev/null +++ b/bin/sh/USD.doc/t4 @@ -0,0 +1,180 @@ +.\" $NetBSD: t4,v 1.3 2010/08/22 02:19:07 perry Exp $ +.\" +.\" Copyright (C) Caldera International Inc. 2001-2002. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" Redistributions of source code and documentation must retain the above +.\" copyright notice, this list of conditions and the following +.\" disclaimer. +.\" +.\" 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. +.\" +.\" All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" +.\" This product includes software developed or owned by Caldera +.\" International, Inc. Neither the name of Caldera International, Inc. +.\" nor the names of other contributors may be used to endorse or promote +.\" products derived from this software without specific prior written +.\" permission. +.\" +.\" USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA +.\" INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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) RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" @(#)t4 8.1 (Berkeley) 8/14/93 +.\" +.bp +.SH +Appendix\ A\ -\ Grammar +.LP +Note: This grammar needs updating, it is obsolete. +.LP +.LD +\fIitem: word + input-output + name = value +.sp 0.7 +simple-command: item + simple-command item +.sp 0.7 +command: simple-command + \fB( \fIcommand-list \fB) + \fB{ \fIcommand-list \fB} + \fBfor \fIname \fBdo \fIcommand-list \fBdone + \fBfor \fIname \fBin \fIword \*(ZZ \fBdo \fIcommand-list \fBdone + \fBwhile \fIcommand-list \fBdo \fIcommand-list \fBdone + \fBuntil \fIcommand-list \fBdo \fIcommand-list \fBdone + \fBcase \fIword \fBin \fIcase-part \*(ZZ \fBesac + \fBif \fIcommand-list \fBthen \fIcommand-list \fIelse-part \fBfi +.sp 0.7 +\fIpipeline: command + pipeline \fB\*(VT\fI command +.sp 0.7 +andor: pipeline + andor \fB&&\fI pipeline + andor \fB\*(VT\*(VT\fI pipeline +.sp 0.7 +command-list: andor + command-list \fB;\fI + command-list \fB&\fI + command-list \fB;\fI andor + command-list \fB&\fI andor +.sp 0.7 +input-output: \fB> \fIfile + \fB< \fIfile + \fB\*(AP \fIword + \fB\*(HE \fIword +.sp 0.7 +file: word + \fB&\fI digit + \fB&\fI \(mi +.sp 0.7 +case-part: pattern\fB ) \fIcommand-list\fB ;; +.sp 0.7 +\fIpattern: word + pattern \fB\*(VT\fI word +.sp 0.7 +\fIelse-part: \fBelif \fIcommand-list\fB then\fI command-list else-part\fP + \fBelse \fIcommand-list\fI + empty +.sp 0.7 +empty: +.sp 0.7 +word: \fRa sequence of non-blank characters\fI +.sp 0.7 +name: \fRa sequence of letters, digits or underscores starting with a letter\fI +.sp 0.7 +digit: \fB0 1 2 3 4 5 6 7 8 9\fP +.DE +.LP +.bp +.SH +Appendix\ B\ -\ Meta-characters\ and\ Reserved\ Words +.LP +a) syntactic +.RS +.IP \fB\*(VT\fR 6 +pipe symbol +.IP \fB&&\fR 6 +`andf' symbol +.IP \fB\*(VT\*(VT\fR 6 +`orf' symbol +.IP \fB;\fP 8 +command separator +.IP \fB;;\fP 8 +case delimiter +.IP \fB&\fP 8 +background commands +.IP \fB(\ )\fP 8 +command grouping +.IP \fB<\fP 8 +input redirection +.IP \fB\*(HE\fP 8 +input from a here document +.IP \fB>\fP 8 +output creation +.IP \fB\*(AP\fP 8 +output append +.sp 2 +.RE +.LP +b) patterns +.RS +.IP \fB\*(ST\fP 8 +match any character(s) including none +.IP \fB?\fP 8 +match any single character +.IP \fB[...]\fP 8 +match any of the enclosed characters +.sp 2 +.RE +.LP +c) substitution +.RS +.IP \fB${...}\fP 8 +substitute shell variable +.IP \fB$(...)\fP 8 +substitute command output +.IP \fB\`...\`\fP 8 +substitute command output +.IP \fB$((...))\fP 8 +substitute arithmetic expression +.sp 2 +.RE +.LP +d) quoting +.RS +.IP \fB\e\fP 8 +quote the next character +.IP \fB\'...\'\fP 8 +quote the enclosed characters except for \' +.IP \fB"\&..."\fP 8 +quote the enclosed characters except +for \fB$ \` \e "\fP +.sp 2 +.RE +.LP +e) reserved words +.DS +.ft B +if then else elif fi +case in esac +for while until do done +! { } +.ft +.DE diff --git a/bin/sh/alias.c b/bin/sh/alias.c new file mode 100644 index 0000000..2848b52 --- /dev/null +++ b/bin/sh/alias.c @@ -0,0 +1,314 @@ +/* $NetBSD: alias.c,v 1.20 2018/12/03 06:40:26 kre Exp $ */ + +/*- + * 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> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)alias.c 8.3 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: alias.c,v 1.20 2018/12/03 06:40:26 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdlib.h> +#include "shell.h" +#include "input.h" +#include "output.h" +#include "error.h" +#include "memalloc.h" +#include "mystring.h" +#include "alias.h" +#include "options.h" /* XXX for argptr (should remove?) */ +#include "builtins.h" +#include "var.h" + +#define ATABSIZE 39 + +struct alias *atab[ATABSIZE]; + +STATIC void setalias(char *, char *); +STATIC int by_name(const void *, const void *); +STATIC void list_aliases(void); +STATIC int unalias(char *); +STATIC struct alias **freealias(struct alias **, int); +STATIC struct alias **hashalias(const char *); +STATIC size_t countaliases(void); + +STATIC +void +setalias(char *name, char *val) +{ + struct alias *ap, **app; + + (void) unalias(name); /* old one (if any) is now gone */ + app = hashalias(name); + + INTOFF; + ap = ckmalloc(sizeof (struct alias)); + ap->name = savestr(name); + ap->flag = 0; + ap->val = savestr(val); + ap->next = *app; + *app = ap; + INTON; +} + +STATIC struct alias ** +freealias(struct alias **app, int force) +{ + struct alias *ap = *app; + + if (ap == NULL) + return app; + + /* + * 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 discarding it. + * If we encounter it later, when it is idle, + * we will finish freeing it then. + * + * Unless we want to simply free everything (INIT) + */ + if (ap->flag & ALIASINUSE && !force) { + *ap->name = '\0'; + return &ap->next; + } + + INTOFF; + *app = ap->next; + ckfree(ap->name); + ckfree(ap->val); + ckfree(ap); + INTON; + + return app; +} + +STATIC int +unalias(char *name) +{ + struct alias *ap, **app; + + app = hashalias(name); + while ((ap = *app) != NULL) { + if (equal(name, ap->name)) { + (void) freealias(app, 0); + return 0; + } + app = &ap->next; + } + + return 1; +} + +#ifdef mkinit +MKINIT void rmaliases(int); + +SHELLPROC { + rmaliases(1); +} +#endif + +void +rmaliases(int force) +{ + struct alias **app; + int i; + + INTOFF; + for (i = 0; i < ATABSIZE; i++) { + app = &atab[i]; + while (*app) + app = freealias(app, force); + } + INTON; +} + +struct alias * +lookupalias(const char *name, int check) +{ + struct alias *ap = *hashalias(name); + + while (ap != NULL) { + if (equal(name, ap->name)) { + if (check && (ap->flag & ALIASINUSE)) + return NULL; + return ap; + } + ap = ap->next; + } + + return NULL; +} + +const char * +alias_text(void *dummy __unused, const char *name) +{ + struct alias *ap; + + ap = lookupalias(name, 0); + if (ap == NULL) + return NULL; + return ap->val; +} + +STATIC int +by_name(const void *a, const void *b) +{ + + return strcmp( + (*(const struct alias * const *)a)->name, + (*(const struct alias * const *)b)->name); +} + +STATIC void +list_aliases(void) +{ + size_t i, j, n; + const struct alias **aliases; + const struct alias *ap; + + INTOFF; + n = countaliases(); + aliases = ckmalloc(n * sizeof aliases[0]); + + j = 0; + for (i = 0; i < ATABSIZE; i++) + for (ap = atab[i]; ap != NULL; ap = ap->next) + if (ap->name[0] != '\0') + aliases[j++] = ap; + if (j != n) + error("Alias count botch"); + INTON; + + qsort(aliases, n, sizeof aliases[0], by_name); + + for (i = 0; i < n; i++) { + out1fmt("alias %s=", aliases[i]->name); + print_quoted(aliases[i]->val); + out1c('\n'); + } + + ckfree(aliases); +} + +/* + * Count how many aliases are defined (skipping any + * that have been deleted, but don't know it yet). + * Use this opportunity to clean up any of those + * zombies that are no longer needed. + */ +STATIC size_t +countaliases(void) +{ + struct alias *ap, **app; + size_t n; + int i; + + n = 0; + for (i = 0; i < ATABSIZE; i++) + for (app = &atab[i]; (ap = *app) != NULL;) { + if (ap->name[0] != '\0') + n++; + else { + app = freealias(app, 0); + continue; + } + app = &ap->next; + } + + return n; +} + +int +aliascmd(int argc, char **argv) +{ + char *n, *v; + int ret = 0; + struct alias *ap; + + if (argc == 1) { + list_aliases(); + return 0; + } + + while ((n = *++argv) != NULL) { + if ((v = strchr(n+1, '=')) == NULL) { /* n+1: funny ksh stuff */ + if ((ap = lookupalias(n, 0)) == NULL) { + outfmt(out2, "alias: %s not found\n", n); + ret = 1; + } else { + out1fmt("alias %s=", n); + print_quoted(ap->val); + out1c('\n'); + } + } else { + *v++ = '\0'; + setalias(n, v); + } + } + + return ret; +} + +int +unaliascmd(int argc, char **argv) +{ + int i; + + while ((i = nextopt("a")) != '\0') { + if (i == 'a') { + rmaliases(0); + return 0; + } + } + + (void)countaliases(); /* delete any dead ones */ + for (i = 0; *argptr; argptr++) + i |= unalias(*argptr); + + return i; +} + +STATIC struct alias ** +hashalias(const char *p) +{ + unsigned int hashval; + + hashval = *(const unsigned char *)p << 4; + while (*p) + hashval += *p++; + return &atab[hashval % ATABSIZE]; +} diff --git a/bin/sh/alias.h b/bin/sh/alias.h new file mode 100644 index 0000000..390df6d --- /dev/null +++ b/bin/sh/alias.h @@ -0,0 +1,48 @@ +/* $NetBSD: alias.h,v 1.9 2018/12/03 06:40:26 kre Exp $ */ + +/*- + * 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 + */ + +#define ALIASINUSE 1 + +struct alias { + struct alias *next; + char *name; + char *val; + int flag; +}; + +struct alias *lookupalias(const char *, int); +const char *alias_text(void *, const char *); +void rmaliases(int); diff --git a/bin/sh/arith_token.c b/bin/sh/arith_token.c new file mode 100644 index 0000000..cd91857 --- /dev/null +++ b/bin/sh/arith_token.c @@ -0,0 +1,262 @@ +/* $NetBSD: arith_token.c,v 1.7 2017/12/17 04:06:03 kre Exp $ */ + +/*- + * Copyright (c) 2002 + * Herbert Xu. + * 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 FreeBSD, from dash + */ + +#include <sys/cdefs.h> + +#ifndef lint +__RCSID("$NetBSD: arith_token.c,v 1.7 2017/12/17 04:06:03 kre Exp $"); +#endif /* not lint */ + +#include <inttypes.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +#include "shell.h" +#include "arith_tokens.h" +#include "expand.h" +#include "error.h" +#include "memalloc.h" +#include "parser.h" +#include "syntax.h" +#include "show.h" + +#if ARITH_BOR + ARITH_ASS_GAP != ARITH_BORASS || \ + ARITH_ASS + ARITH_ASS_GAP != ARITH_EQ +#error Arithmetic tokens are out of order. +#endif + +/* + * Scan next arithmetic token, return its type, + * leave its value (when applicable) in (global) a_t_val. + * + * Input text is in (global) arith_buf which is updated to + * refer to the next char after the token returned, except + * on error (ARITH_BAD returned) where arith_buf is not altered. + */ +int +arith_token(void) +{ + int token; + const char *buf = arith_buf; + char *end; + const char *p; + + for (;;) { + token = *buf; + + if (isdigit(token)) { + /* + * Numbers all start with a digit, and nothing + * else does, the number ends wherever + * strtoimax() stops... + */ + a_t_val.val = strtoimax(buf, &end, 0); + if (is_in_name(*end)) { + token = *end; + while (is_in_name(*++end)) + continue; + error("arithmetic: unexpected '%c' " + "(out of range) in numeric constant: " + "%.*s", token, (int)(end - buf), buf); + } + arith_buf = end; + VTRACE(DBG_ARITH, ("Arith token ARITH_NUM=%jd\n", + a_t_val.val)); + return ARITH_NUM; + + } else if (is_name(token)) { + /* + * Variable names all start with an alpha (or '_') + * and nothing else does. They continue for the + * longest unbroken sequence of alphanumerics ( + _ ) + */ + arith_var_lno = arith_lno; + p = buf; + while (buf++, is_in_name(*buf)) + ; + a_t_val.name = stalloc(buf - p + 1); + memcpy(a_t_val.name, p, buf - p); + a_t_val.name[buf - p] = '\0'; + arith_buf = buf; + VTRACE(DBG_ARITH, ("Arith token ARITH_VAR=\"%s\"\n", + a_t_val.name)); + return ARITH_VAR; + + } else switch (token) { + /* + * everything else must be some kind of + * operator, white space, or an error. + */ + case '\n': + arith_lno++; + VTRACE(DBG_ARITH, ("Arith: newline\n")); + /* FALLTHROUGH */ + case ' ': + case '\t': + buf++; + continue; + + default: + error("arithmetic: unexpected '%c' (%#x) in expression", + token, token); + /* NOTREACHED */ + + case '=': + token = ARITH_ASS; + checkeq: + buf++; + checkeqcur: + if (*buf != '=') + goto out; + token += ARITH_ASS_GAP; + break; + + case '>': + switch (*++buf) { + case '=': + token = ARITH_GE; + break; + case '>': + token = ARITH_RSHIFT; + goto checkeq; + default: + token = ARITH_GT; + goto out; + } + break; + + case '<': + switch (*++buf) { + case '=': + token = ARITH_LE; + break; + case '<': + token = ARITH_LSHIFT; + goto checkeq; + default: + token = ARITH_LT; + goto out; + } + break; + + case '|': + if (*++buf != '|') { + token = ARITH_BOR; + goto checkeqcur; + } + token = ARITH_OR; + break; + + case '&': + if (*++buf != '&') { + token = ARITH_BAND; + goto checkeqcur; + } + token = ARITH_AND; + break; + + case '!': + if (*++buf != '=') { + token = ARITH_NOT; + goto out; + } + token = ARITH_NE; + break; + + case 0: + goto out; + + case '(': + token = ARITH_LPAREN; + break; + case ')': + token = ARITH_RPAREN; + break; + + case '*': + token = ARITH_MUL; + goto checkeq; + case '/': + token = ARITH_DIV; + goto checkeq; + case '%': + token = ARITH_REM; + goto checkeq; + + case '+': + if (buf[1] == '+') { + buf++; + token = ARITH_INCR; + break; + } + token = ARITH_ADD; + goto checkeq; + case '-': + if (buf[1] == '-') { + buf++; + token = ARITH_DECR; + break; + } + token = ARITH_SUB; + goto checkeq; + case '~': + token = ARITH_BNOT; + break; + case '^': + token = ARITH_BXOR; + goto checkeq; + + case '?': + token = ARITH_QMARK; + break; + case ':': + token = ARITH_COLON; + break; + case ',': + token = ARITH_COMMA; + break; + } + break; + } + buf++; + out: + arith_buf = buf; + VTRACE(DBG_ARITH, ("Arith token: %d\n", token)); + return token; +} diff --git a/bin/sh/arith_tokens.h b/bin/sh/arith_tokens.h new file mode 100644 index 0000000..f655aa9 --- /dev/null +++ b/bin/sh/arith_tokens.h @@ -0,0 +1,120 @@ +/* $NetBSD: arith_tokens.h,v 1.3 2017/07/24 13:21:14 kre Exp $ */ + +/*- + * 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. + * + * From FreeBSD who obtained it from dash (modified both times.) + */ + +/* + * Tokens returned from arith_token() + * + * Caution, values are not arbitrary. + */ + +#define ARITH_BAD 0 + +#define ARITH_ASS 1 + +#define ARITH_OR 2 +#define ARITH_AND 3 +#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 /* Must be ARITH_ASS + ARITH_ASS_GAP */ + +#define ARITH_ASS_BASE 13 + +#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 ARITH_BINOP_MAX +#define ARITH_ASS_GAP (ARITH_ASS_MIN - ARITH_ASS_BASE) + +#define ARITH_REMASS (ARITH_ASS_GAP + ARITH_REM) +#define ARITH_BANDASS (ARITH_ASS_GAP + ARITH_BAND) +#define ARITH_LSHIFTASS (ARITH_ASS_GAP + ARITH_LSHIFT) +#define ARITH_RSHIFTASS (ARITH_ASS_GAP + ARITH_RSHIFT) +#define ARITH_MULASS (ARITH_ASS_GAP + ARITH_MUL) +#define ARITH_ADDASS (ARITH_ASS_GAP + ARITH_ADD) +#define ARITH_BORASS (ARITH_ASS_GAP + ARITH_BOR) +#define ARITH_SUBASS (ARITH_ASS_GAP + ARITH_SUB) +#define ARITH_BXORASS (ARITH_ASS_GAP + ARITH_BXOR) +#define ARITH_DIVASS (ARITH_ASS_GAP + ARITH_BXOR) + +#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 +#define ARITH_INCR 39 +#define ARITH_DECR 40 +#define ARITH_COMMA 41 + +/* + * Globals shared between arith parser, and lexer + */ + +extern const char *arith_buf; + +union a_token_val { + intmax_t val; + char *name; +}; + +extern union a_token_val a_t_val; + +extern int arith_lno, arith_var_lno; + +int arith_token(void); diff --git a/bin/sh/arithmetic.c b/bin/sh/arithmetic.c new file mode 100644 index 0000000..f9c91a4 --- /dev/null +++ b/bin/sh/arithmetic.c @@ -0,0 +1,502 @@ +/* $NetBSD: arithmetic.c,v 1.5 2018/04/21 23:01:29 kre Exp $ */ + +/*- + * 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. + * + * From FreeBSD, who obtained it from dash, modified both times... + */ + +#include <sys/cdefs.h> + +#ifndef lint +__RCSID("$NetBSD: arithmetic.c,v 1.5 2018/04/21 23:01:29 kre Exp $"); +#endif /* not lint */ + +#include <limits.h> +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "shell.h" +#include "arithmetic.h" +#include "arith_tokens.h" +#include "expand.h" +#include "error.h" +#include "memalloc.h" +#include "output.h" +#include "options.h" +#include "var.h" +#include "show.h" +#include "syntax.h" + +#if ARITH_BOR + ARITH_ASS_GAP != ARITH_BORASS || \ + ARITH_ASS + ARITH_ASS_GAP != ARITH_EQ +#error Arithmetic tokens are out of order. +#endif + +static const char *arith_startbuf; + +const char *arith_buf; +union a_token_val a_t_val; + +static int last_token; + +int arith_lno, arith_var_lno; + +#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 expcmd(int, char **); + +static void __dead +arith_err(const char *s) +{ + error("arithmetic expression: %s: \"%s\"", s, arith_startbuf); + /* NOTREACHED */ +} + +static intmax_t +arith_lookupvarint(char *varname) +{ + const char *str; + char *p; + intmax_t result; + const int oln = line_number; + + VTRACE(DBG_ARITH, ("Arith var lookup(\"%s\") with lno=%d\n", varname, + arith_var_lno)); + + line_number = arith_var_lno; + str = lookupvar(varname); + line_number = oln; + + if (uflag && str == NULL) + arith_err("variable not set"); + if (str == NULL || *str == '\0') + str = "0"; + errno = 0; + result = strtoimax(str, &p, 0); + if (errno != 0 || *p != '\0') { + if (errno == 0) { + while (*p != '\0' && is_space(*p)) + p++; + if (*p == '\0') + return result; + } + arith_err("variable contains non-numeric value"); + } + 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 intmax_t +do_binop(int op, intmax_t a, intmax_t b) +{ + + VTRACE(DBG_ARITH, ("Arith do binop %d (%jd, %jd)\n", op, a, b)); + switch (op) { + default: + arith_err("token error"); + case ARITH_REM: + case ARITH_DIV: + if (b == 0) + arith_err("division by zero"); + if (a == INTMAX_MIN && b == -1) + arith_err("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 intmax_t assignment(int, int); +static intmax_t comma_list(int, int); + +static intmax_t +primary(int token, union a_token_val *val, int op, int noeval) +{ + intmax_t result; + char sresult[DIGITS(result) + 1]; + + VTRACE(DBG_ARITH, ("Arith primary: token %d op %d%s\n", + token, op, noeval ? " noeval" : "")); + + switch (token) { + case ARITH_LPAREN: + result = comma_list(op, noeval); + if (last_token != ARITH_RPAREN) + arith_err("expecting ')'"); + last_token = arith_token(); + return result; + case ARITH_NUM: + last_token = op; + return val->val; + case ARITH_VAR: + result = noeval ? val->val : arith_lookupvarint(val->name); + if (op == ARITH_INCR || op == ARITH_DECR) { + last_token = arith_token(); + if (noeval) + return val->val; + + snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR, + result + (op == ARITH_INCR ? 1 : -1)); + setvar(val->name, sresult, 0); + } else + last_token = op; + return result; + case ARITH_ADD: + *val = a_t_val; + return primary(op, val, arith_token(), noeval); + case ARITH_SUB: + *val = a_t_val; + return -primary(op, val, arith_token(), noeval); + case ARITH_NOT: + *val = a_t_val; + return !primary(op, val, arith_token(), noeval); + case ARITH_BNOT: + *val = a_t_val; + return ~primary(op, val, arith_token(), noeval); + case ARITH_INCR: + case ARITH_DECR: + if (op != ARITH_VAR) + arith_err("incr/decr require var name"); + last_token = arith_token(); + if (noeval) + return val->val; + result = arith_lookupvarint(a_t_val.name); + snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR, + result += (token == ARITH_INCR ? 1 : -1)); + setvar(a_t_val.name, sresult, 0); + return result; + default: + arith_err("expecting primary"); + } + return 0; /* never reached */ +} + +static intmax_t +binop2(intmax_t a, int op, int precedence, int noeval) +{ + union a_token_val val; + intmax_t b; + int op2; + int token; + + VTRACE(DBG_ARITH, ("Arith: binop2 %jd op %d (P:%d)%s\n", + a, op, precedence, noeval ? " noeval" : "")); + + for (;;) { + token = arith_token(); + val = a_t_val; + + b = primary(token, &val, arith_token(), 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 intmax_t +binop(int token, union a_token_val *val, int op, int noeval) +{ + intmax_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 intmax_t +and(int token, union a_token_val *val, int op, int noeval) +{ + intmax_t a = binop(token, val, op, noeval); + intmax_t b; + + op = last_token; + if (op != ARITH_AND) + return a; + + VTRACE(DBG_ARITH, ("Arith: AND %jd%s\n", a, noeval ? " noeval" : "")); + + token = arith_token(); + *val = a_t_val; + + b = and(token, val, arith_token(), noeval | !a); + + return a && b; +} + +static intmax_t +or(int token, union a_token_val *val, int op, int noeval) +{ + intmax_t a = and(token, val, op, noeval); + intmax_t b; + + op = last_token; + if (op != ARITH_OR) + return a; + + VTRACE(DBG_ARITH, ("Arith: OR %jd%s\n", a, noeval ? " noeval" : "")); + + token = arith_token(); + *val = a_t_val; + + b = or(token, val, arith_token(), noeval | !!a); + + return a || b; +} + +static intmax_t +cond(int token, union a_token_val *val, int op, int noeval) +{ + intmax_t a = or(token, val, op, noeval); + intmax_t b; + intmax_t c; + + if (last_token != ARITH_QMARK) + return a; + + VTRACE(DBG_ARITH, ("Arith: ?: %jd%s\n", a, noeval ? " noeval" : "")); + + b = assignment(arith_token(), noeval | !a); + + if (last_token != ARITH_COLON) + arith_err("expecting ':'"); + + token = arith_token(); + *val = a_t_val; + + c = cond(token, val, arith_token(), noeval | !!a); + + return a ? b : c; +} + +static intmax_t +assignment(int var, int noeval) +{ + union a_token_val val = a_t_val; + int op = arith_token(); + intmax_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); + + VTRACE(DBG_ARITH, ("Arith: %s ASSIGN %d%s\n", val.name, op, + noeval ? " noeval" : "")); + + result = assignment(arith_token(), noeval); + if (noeval) + return result; + + if (op != ARITH_ASS) + result = do_binop(op - ARITH_ASS_GAP, + arith_lookupvarint(val.name), result); + snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR, result); + setvar(val.name, sresult, 0); + return result; +} + +static intmax_t +comma_list(int token, int noeval) +{ + intmax_t result = assignment(token, noeval); + + while (last_token == ARITH_COMMA) { + VTRACE(DBG_ARITH, ("Arith: comma discarding %jd%s\n", result, + noeval ? " noeval" : "")); + result = assignment(arith_token(), noeval); + } + + return result; +} + +intmax_t +arith(const char *s, int lno) +{ + struct stackmark smark; + intmax_t result; + const char *p; + int nls = 0; + + setstackmark(&smark); + + arith_lno = lno; + + CTRACE(DBG_ARITH, ("Arith(\"%s\", %d) @%d\n", s, lno, arith_lno)); + + /* check if it is possible we might reference LINENO */ + p = s; + while ((p = strchr(p, 'L')) != NULL) { + if (p[1] == 'I' && p[2] == 'N') { + /* if it is possible, we need to correct airth_lno */ + p = s; + while ((p = strchr(p, '\n')) != NULL) + nls++, p++; + VTRACE(DBG_ARITH, ("Arith found %d newlines\n", nls)); + arith_lno -= nls; + break; + } + p++; + } + + arith_buf = arith_startbuf = s; + + result = comma_list(arith_token(), 0); + + if (last_token) + arith_err("expecting end of expression"); + + popstackmark(&smark); + + CTRACE(DBG_ARITH, ("Arith result=%jd\n", result)); + + return result; +} + +/* + * The let(1)/exp(1) builtin. + */ +int +expcmd(int argc, char **argv) +{ + const char *p; + char *concat; + char **ap; + intmax_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, line_number); + + out1fmt(ARITH_FORMAT_STR "\n", i); + return !i; +} diff --git a/bin/sh/arithmetic.h b/bin/sh/arithmetic.h new file mode 100644 index 0000000..51808d3 --- /dev/null +++ b/bin/sh/arithmetic.h @@ -0,0 +1,42 @@ +/* $NetBSD: arithmetic.h,v 1.2 2017/06/07 05:08:32 kre Exp $ */ + +/*- + * 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. + * 4. 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 + * + * From FreeBSD, from dash + */ + +#include "shell.h" + +#define DIGITS(var) (3 + (2 + CHAR_BIT * sizeof((var))) / 3) + +#define ARITH_FORMAT_STR "%" PRIdMAX + +intmax_t arith(const char *, int); diff --git a/bin/sh/bltin/bltin.h b/bin/sh/bltin/bltin.h new file mode 100644 index 0000000..b5669f1 --- /dev/null +++ b/bin/sh/bltin/bltin.h @@ -0,0 +1,105 @@ +/* $NetBSD: bltin.h,v 1.15 2017/06/26 22:09:16 kre Exp $ */ + +/*- + * 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.1 (Berkeley) 5/31/93 + */ + +/* + * This file is included by programs which are optionally built into the + * shell. + * + * We always define SHELL_BUILTIN, to allow other included headers to + * hide some of their symbols if appropriate. + * + * If SHELL is defined, we try to map the standard UNIX library routines + * to ash routines using defines. + */ + +#define SHELL_BUILTIN +#include "../shell.h" +#include "../mystring.h" +#ifdef SHELL +#include "../output.h" +#include "../error.h" +#include "../var.h" +#undef stdout +#undef stderr +#undef putc +#undef putchar +#undef fileno +#undef ferror +#define FILE struct output +#define stdout out1 +#define stderr out2 +#ifdef __GNUC__ +#define _RETURN_INT(x) ({(x); 0;}) /* map from void foo() to int bar() */ +#else +#define _RETURN_INT(x) ((x), 0) /* map from void foo() to int bar() */ +#endif +#define fprintf(...) _RETURN_INT(outfmt(__VA_ARGS__)) +#define printf(...) _RETURN_INT(out1fmt(__VA_ARGS__)) +#define putc(c, file) _RETURN_INT(outc(c, file)) +#define putchar(c) _RETURN_INT(out1c(c)) +#define fputs(...) _RETURN_INT(outstr(__VA_ARGS__)) +#define fflush(f) _RETURN_INT(flushout(f)) +#define fileno(f) ((f)->fd) +#define ferror(f) ((f)->flags & OUTPUT_ERR) +#define INITARGS(argv) +#define err sh_err +#define verr sh_verr +#define errx sh_errx +#define verrx sh_verrx +#define warn sh_warn +#define vwarn sh_vwarn +#define warnx sh_warnx +#define vwarnx sh_vwarnx +#define exit sh_exit +#define setprogname(s) +#define getprogname() commandname +#define setlocate(l,s) 0 + +#define getenv(p) bltinlookup((p),0) + +#else /* ! SHELL */ +#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 /* ! SHELL */ + +pointer stalloc(int); + +int echocmd(int, char **); + + +extern const char *commandname; diff --git a/bin/sh/bltin/echo.1 b/bin/sh/bltin/echo.1 new file mode 100644 index 0000000..13b3c6a --- /dev/null +++ b/bin/sh/bltin/echo.1 @@ -0,0 +1,109 @@ +.\" $NetBSD: echo.1,v 1.14 2017/07/03 21:33:24 wiz Exp $ +.\" +.\" 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. +.\" Copyright 1989 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.1 8.1 (Berkeley) 5/31/93 +.\" +.Dd May 31, 1993 +.Dt ECHO 1 +.Os +.Sh NAME +.Nm echo +.Nd produce message in a shell script +.Sh SYNOPSIS +.Nm +.Op Fl n | Fl e +.Ar args ... +.Sh DESCRIPTION +.Nm +prints its arguments on the standard output, separated by spaces. +Unless the +.Fl n +option is present, a newline is output following the arguments. +The +.Fl e +option causes +.Nm +to treat the escape sequences specially, as described in the following +paragraph. +The +.Fl e +option is the default, and is provided solely for compatibility with +other systems. +Only one of the options +.Fl n +and +.Fl e +may be given. +.Pp +If any of the following sequences of characters is encountered during +output, the sequence is not output. Instead, the specified action is +performed: +.Bl -tag -width indent +.It Li \eb +A backspace character is output. +.It Li \ec +Subsequent output is suppressed. This is normally used at the end of the +last argument to suppress the trailing newline that +.Nm +would otherwise output. +.It Li \ef +Output a form feed. +.It Li \en +Output a newline character. +.It Li \er +Output a carriage return. +.It Li \et +Output a (horizontal) tab character. +.It Li \ev +Output a vertical tab. +.It Li \e0 Ns Ar digits +Output the character whose value is given by zero to three digits. +If there are zero digits, a nul character is output. +.It Li \e\e +Output a backslash. +.El +.Sh HINTS +Remember that backslash is special to the shell and needs to be escaped. +To output a message to standard error, say +.Pp +.D1 echo message >&2 +.Sh BUGS +The octal character escape mechanism +.Pq Li \e0 Ns Ar digits +differs from the +C language mechanism. +.Pp +There is no way to force +.Nm +to treat its arguments literally, rather than interpreting them as +options and escape sequences. diff --git a/bin/sh/bltin/echo.c b/bin/sh/bltin/echo.c new file mode 100644 index 0000000..440e01b --- /dev/null +++ b/bin/sh/bltin/echo.c @@ -0,0 +1,122 @@ +/* $NetBSD: echo.c,v 1.14 2008/10/12 01:40:37 dholland Exp $ */ + +/*- + * 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.1 (Berkeley) 5/31/93 + */ + +/* + * Echo command. + * + * echo is steeped in tradition - several of them! + * netbsd has supported 'echo [-n | -e] args' in spite of -e not being + * documented anywhere. + * Posix requires that -n be supported, output from strings containing + * \ is implementation defined + * The Single Unix Spec requires that \ escapes be treated as if -e + * were set, but that -n not be treated as an option. + * (ksh supports 'echo [-eEn] args', but not -- so that it is actually + * impossible to actually output '-n') + * + * It is suggested that 'printf "%b" "string"' be used to get \ sequences + * expanded. printf is now a builtin of netbsd's sh and csh. + */ + +#include <sys/cdefs.h> +__RCSID("$NetBSD: echo.c,v 1.14 2008/10/12 01:40:37 dholland Exp $"); + +#define main echocmd + +#include "bltin.h" + +int +main(int argc, char **argv) +{ + char **ap; + char *p; + char c; + int count; + int nflag = 0; + int eflag = 0; + + ap = argv; + if (argc) + ap++; + + if ((p = *ap) != NULL) { + if (equal(p, "-n")) { + nflag = 1; + ap++; + } else if (equal(p, "-e")) { + eflag = 1; + ap++; + } + } + + while ((p = *ap++) != NULL) { + while ((c = *p++) != '\0') { + if (c == '\\' && eflag) { + switch (*p++) { + case 'a': c = '\a'; break; /* bell */ + case 'b': c = '\b'; break; + case 'c': return 0; /* exit */ + case 'e': c = 033; break; /* escape */ + 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: + /* Output the '/' and char following */ + p--; + break; + } + } + putchar(c); + } + if (*ap) + putchar(' '); + } + if (! nflag) + putchar('\n'); + fflush(stdout); + if (ferror(stdout)) + return 1; + return 0; +} diff --git a/bin/sh/builtins.def b/bin/sh/builtins.def new file mode 100644 index 0000000..d702707 --- /dev/null +++ b/bin/sh/builtins.def @@ -0,0 +1,98 @@ +#!/bin/sh - +# $NetBSD: builtins.def,v 1.25 2017/05/15 20:00:36 kre Exp $ +# +# 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 + +# +# 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 SMALL compile-time symbol. +# The -s flag specifies that this is a posix 'special builtin' command. +# The -u flag specifies that this is a posix 'standard utility'. +# The rest of the line specifies the command name or names used to run +# the command. + +bltincmd -u command +bgcmd -j -u bg +breakcmd -s break -s continue +cdcmd -u cd chdir +dotcmd -s . +echocmd echo +evalcmd -s eval +execcmd -s exec +exitcmd -s exit +expcmd exp let +exportcmd -s export -s readonly +falsecmd -u false +histcmd -h -u fc +inputrc inputrc +fgcmd -j -u fg +fgcmd_percent -j -u % +getoptscmd -u getopts +hashcmd hash +jobidcmd jobid +jobscmd -u jobs +localcmd local +#ifndef TINY +printfcmd printf +#endif +pwdcmd -u pwd +readcmd -u read +returncmd -s return +setcmd -s set +fdflagscmd fdflags +setvarcmd setvar +shiftcmd -s shift +timescmd -s times +trapcmd -s trap +truecmd -s : -u true +typecmd type +umaskcmd -u umask +unaliascmd -u unalias +unsetcmd -s unset +waitcmd -u wait +aliascmd -u alias +ulimitcmd ulimit +testcmd test [ +killcmd -u kill # mandated by posix for 'kill %job' +wordexpcmd wordexp +#newgrp -u newgrp # optional command in posix + +#ifdef DEBUG +debugcmd debug +#endif + +#exprcmd expr diff --git a/bin/sh/cd.c b/bin/sh/cd.c new file mode 100644 index 0000000..2b1046d --- /dev/null +++ b/bin/sh/cd.c @@ -0,0 +1,463 @@ +/* $NetBSD: cd.c,v 1.50 2017/07/05 20:00:27 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)cd.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: cd.c,v 1.50 2017/07/05 20:00:27 kre Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.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 "builtins.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" + +STATIC int docd(const char *, int); +STATIC char *getcomponent(void); +STATIC void updatepwd(const char *); +STATIC void find_curdir(int noerror); + +char *curdir = NULL; /* current working directory */ +char *prevdir; /* previous working directory */ +STATIC char *cdcomppath; + +int +cdcmd(int argc, char **argv) +{ + const char *dest; + const char *path, *cp; + char *p; + char *d; + struct stat statb; + int print = cdprint; /* set -o cdprint to enable */ + + while (nextopt("P") != '\0') + ; + + /* + * Try (quite hard) to have 'curdir' defined, nothing has set + * it on entry to the shell, but we want 'cd fred; cd -' to work. + */ + getpwd(1); + dest = *argptr; + if (dest == NULL) { + dest = bltinlookup("HOME", 1); + if (dest == NULL) + error("HOME not set"); + } else if (argptr[1]) { + /* Do 'ksh' style substitution */ + if (!curdir) + error("PWD not set"); + p = strstr(curdir, dest); + if (!p) + error("bad substitution"); + d = stalloc(strlen(curdir) + strlen(argptr[1]) + 1); + memcpy(d, curdir, p - curdir); + strcpy(d + (p - curdir), argptr[1]); + strcat(d, p + strlen(dest)); + dest = d; + print = 1; + } else if (dest[0] == '-' && dest[1] == '\0') { + dest = prevdir ? prevdir : curdir; + print = 1; + } + if (*dest == '\0') + dest = "."; + + cp = dest; + if (*cp == '.' && *++cp == '.') + cp++; + if (*cp == 0 || *cp == '/' || (path = bltinlookup("CDPATH", 1)) == NULL) + path = nullstr; + while ((p = padvance(&path, dest, 0)) != NULL) { + stunalloc(p); + if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { + int dopr = print; + + if (!print) { + /* + * XXX - rethink + */ + if (p[0] == '.' && p[1] == '/' && p[2] != '\0') + dopr = strcmp(p + 2, dest); + else + dopr = strcmp(p, dest); + } + if (docd(p, dopr) >= 0) + return 0; + + } + } + error("can't cd to %s", dest); + /* NOTREACHED */ +} + + +/* + * Actually do the chdir. In an interactive shell, print the + * directory name if "print" is nonzero. + */ + +STATIC int +docd(const char *dest, int print) +{ +#if 0 /* no "cd -L" (ever) so all this is just a waste of time ... */ + char *p; + char *q; + char *component; + struct stat statb; + int first; + int badstat; + + CTRACE(DBG_CMDS, ("docd(\"%s\", %d) called\n", dest, print)); + + /* + * 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; + cdcomppath = stalloc(strlen(dest) + 1); + scopy(dest, cdcomppath); + STARTSTACKSTR(p); + if (*dest == '/') { + STPUTC('/', p); + cdcomppath++; + } + first = 1; + while ((q = getcomponent()) != NULL) { + if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0')) + continue; + if (! first) + STPUTC('/', p); + first = 0; + component = q; + while (*q) + STPUTC(*q++, p); + if (equal(component, "..")) + continue; + STACKSTRNUL(p); + if (lstat(stackblock(), &statb) < 0) { + badstat = 1; + break; + } + } +#endif + + INTOFF; + if (chdir(dest) < 0) { + INTON; + return -1; + } + updatepwd(NULL); /* only do cd -P, no "pretend" -L mode */ + INTON; + if (print && iflag == 1 && curdir) + out1fmt("%s\n", curdir); + return 0; +} + + +/* + * Get the next component of the path name pointed to by cdcomppath. + * This routine overwrites the string pointed to by cdcomppath. + */ + +STATIC char * +getcomponent(void) +{ + char *p; + char *start; + + if ((p = cdcomppath) == NULL) + return NULL; + start = cdcomppath; + while (*p != '/' && *p != '\0') + p++; + if (*p == '\0') { + cdcomppath = NULL; + } else { + *p++ = '\0'; + cdcomppath = p; + } + return start; +} + + + +/* + * 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(const char *dir) +{ + char *new; + char *p; + + hashcd(); /* update command hash table */ + + /* + * 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) { + if (prevdir) + ckfree(prevdir); + INTOFF; + prevdir = curdir; + curdir = NULL; + getpwd(1); + INTON; + if (curdir) { + setvar("OLDPWD", prevdir, VEXPORT); + setvar("PWD", curdir, VEXPORT); + } else + unsetvar("PWD", 0); + return; + } + cdcomppath = stalloc(strlen(dir) + 1); + scopy(dir, cdcomppath); + STARTSTACKSTR(new); + if (*dir != '/') { + p = curdir; + while (*p) + STPUTC(*p++, new); + if (p[-1] == '/') + STUNPUTC(new); + } + while ((p = getcomponent()) != NULL) { + if (equal(p, "..")) { + while (new > stackblock() && (STUNPUTC(new), *new) != '/'); + } else if (*p != '\0' && ! equal(p, ".")) { + STPUTC('/', new); + while (*p) + STPUTC(*p++, new); + } + } + if (new == stackblock()) + STPUTC('/', new); + STACKSTRNUL(new); + INTOFF; + if (prevdir) + ckfree(prevdir); + prevdir = curdir; + curdir = savestr(stackblock()); + setvar("OLDPWD", prevdir, VEXPORT); + setvar("PWD", curdir, VEXPORT); + INTON; +} + +/* + * Posix says the default should be 'pwd -L' (as below), however + * the 'cd' command (above) does something much nearer to the + * posix 'cd -P' (not the posix default of 'cd -L'). + * If 'cd' is changed to support -P/L then the default here + * needs to be revisited if the historic behaviour is to be kept. + */ + +int +pwdcmd(int argc, char **argv) +{ + int i; + char opt = 'L'; + + while ((i = nextopt("LP")) != '\0') + opt = i; + if (*argptr) + error("unexpected argument"); + + if (opt == 'L') + getpwd(0); + else + find_curdir(0); + + setvar("OLDPWD", prevdir, VEXPORT); + setvar("PWD", curdir, VEXPORT); + out1str(curdir); + out1c('\n'); + return 0; +} + + + +void +initpwd(void) +{ + getpwd(1); + if (curdir) + setvar("PWD", curdir, VEXPORT); + else + sh_warnx("Cannot determine current working directory"); +} + +#define MAXPWD 256 + +/* + * Find out what the current directory is. If we already know the current + * directory, this routine returns immediately. + */ +void +getpwd(int noerror) +{ + char *pwd; + struct stat stdot, stpwd; + static int first = 1; + + if (curdir) + return; + + if (first) { + first = 0; + pwd = getenv("PWD"); + if (pwd && *pwd == '/' && stat(".", &stdot) != -1 && + stat(pwd, &stpwd) != -1 && + stdot.st_dev == stpwd.st_dev && + stdot.st_ino == stpwd.st_ino) { + curdir = savestr(pwd); + return; + } + } + + find_curdir(noerror); + + return; +} + +STATIC void +find_curdir(int noerror) +{ + int i; + char *pwd; + + /* + * Things are a bit complicated here; we could have just used + * getcwd, but traditionally getcwd is implemented using popen + * to /bin/pwd. This creates a problem for us, since we cannot + * keep track of the job if it is being ran behind our backs. + * So we re-implement getcwd(), and we suppress interrupts + * throughout the process. This is not completely safe, since + * the user can still break out of it by killing the pwd program. + * We still try to use getcwd for systems that we know have a + * c implementation of getcwd, that does not open a pipe to + * /bin/pwd. + */ +#if defined(__NetBSD__) || defined(__SVR4) + + for (i = MAXPWD;; i *= 2) { + pwd = stalloc(i); + if (getcwd(pwd, i) != NULL) { + curdir = savestr(pwd); + stunalloc(pwd); + return; + } + stunalloc(pwd); + if (errno == ERANGE) + continue; + if (!noerror) + error("getcwd() failed: %s", strerror(errno)); + return; + } +#else + { + char *p; + int status; + struct job *jp; + int pip[2]; + + pwd = stalloc(MAXPWD); + INTOFF; + if (pipe(pip) < 0) + error("Pipe call failed"); + jp = makejob(NULL, 1); + if (forkshell(jp, NULL, FORK_NOJOB) == 0) { + (void) close(pip[0]); + movefd(pip[1], 1); + (void) execl("/bin/pwd", "pwd", (char *)0); + error("Cannot exec /bin/pwd"); + } + (void) close(pip[1]); + pip[1] = -1; + p = pwd; + while ((i = read(pip[0], p, pwd + MAXPWD - p)) > 0 + || (i == -1 && errno == EINTR)) { + if (i > 0) + p += i; + } + (void) close(pip[0]); + pip[0] = -1; + status = waitforjob(jp); + if (status != 0) + error((char *)0); + if (i < 0 || p == pwd || p[-1] != '\n') { + if (noerror) { + INTON; + return; + } + error("pwd command failed"); + } + p[-1] = '\0'; + INTON; + curdir = savestr(pwd); + stunalloc(pwd); + return; + } +#endif +} diff --git a/bin/sh/cd.h b/bin/sh/cd.h new file mode 100644 index 0000000..7600955 --- /dev/null +++ b/bin/sh/cd.h @@ -0,0 +1,34 @@ +/* $NetBSD: cd.h,v 1.6 2011/06/18 21:18:46 christos Exp $ */ + +/*- + * 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. + * + */ + +void initpwd(void); +void getpwd(int); diff --git a/bin/sh/error.c b/bin/sh/error.c new file mode 100644 index 0000000..db42926 --- /dev/null +++ b/bin/sh/error.c @@ -0,0 +1,378 @@ +/* $NetBSD: error.c,v 1.42 2019/01/21 14:29:12 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)error.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: error.c,v 1.42 2019/01/21 14:29:12 kre Exp $"); +#endif +#endif /* not lint */ + +/* + * Errors and exceptions. + */ + +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include "shell.h" +#include "eval.h" /* for commandname */ +#include "main.h" +#include "options.h" +#include "output.h" +#include "error.h" +#include "show.h" + + +/* + * Code to handle exceptions in C. + */ + +struct jmploc *handler; +int exception; +volatile int suppressint; +volatile int intpending; + + +static void exverror(int, const char *, va_list) __dead; + +/* + * 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". + */ + +void +exraise(int e) +{ + CTRACE(DBG_ERRS, ("exraise(%d)\n", e)); + if (handler == NULL) + abort(); + exception = e; + longjmp(handler->loc, 1); +} + + +/* + * Called from trap.c when a SIGINT is received. (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. The call to _exit is necessary because + * there is a short period after a fork before the signal handlers are + * set to the appropriate value for the child. (The test for iflag is + * just defensive programming.) + */ + +void +onint(void) +{ + sigset_t nsigset; + + if (suppressint) { + intpending = 1; + return; + } + intpending = 0; + sigemptyset(&nsigset); + sigprocmask(SIG_SETMASK, &nsigset, NULL); + if (rootshell && iflag) + exraise(EXINT); + else { + signal(SIGINT, SIG_DFL); + raise(SIGINT); + } + /* NOTREACHED */ +} + +static __printflike(2, 0) void +exvwarning(int sv_errno, const char *msg, va_list ap) +{ + /* Partially emulate line buffered output so that: + * printf '%d\n' 1 a 2 + * and + * printf '%d %d %d\n' 1 a 2 + * both generate sensible text when stdout and stderr are merged. + */ + if (output.buf != NULL && output.nextc != output.buf && + output.nextc[-1] == '\n') + flushout(&output); + if (commandname) + outfmt(&errout, "%s: ", commandname); + else + outfmt(&errout, "%s: ", getprogname()); + if (msg != NULL) { + doformat(&errout, msg, ap); + if (sv_errno >= 0) + outfmt(&errout, ": "); + } + if (sv_errno >= 0) + outfmt(&errout, "%s", strerror(sv_errno)); + out2c('\n'); + flushout(&errout); +} + +/* + * Exverror is called to raise the error exception. If the second argument + * is not NULL then error prints an error message using printf style + * formatting. It then raises the error exception. + */ +static __printflike(2, 0) void +exverror(int cond, const char *msg, va_list ap) +{ + CLEAR_PENDING_INT; + INTOFF; + +#ifdef DEBUG + if (msg) { + CTRACE(DBG_ERRS, ("exverror(%d, \"", cond)); + CTRACEV(DBG_ERRS, (msg, ap)); + CTRACE(DBG_ERRS, ("\") pid=%d\n", getpid())); + } else + CTRACE(DBG_ERRS, ("exverror(%d, NULL) pid=%d\n", cond, + getpid())); +#endif + if (msg) + exvwarning(-1, msg, ap); + + flushall(); + exraise(cond); + /* NOTREACHED */ +} + + +void +error(const char *msg, ...) +{ + va_list ap; + + /* + * On error, we certainly never want exit(0)... + */ + if (exerrno == 0) + exerrno = 1; + va_start(ap, msg); + exverror(EXERROR, msg, ap); + /* NOTREACHED */ + va_end(ap); +} + + +void +exerror(int cond, const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + exverror(cond, msg, ap); + /* NOTREACHED */ + va_end(ap); +} + +/* + * error/warning routines for external builtins + */ + +void +sh_exit(int rval) +{ + exerrno = rval & 255; + exraise(EXEXEC); +} + +void +sh_err(int status, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + exvwarning(errno, fmt, ap); + va_end(ap); + sh_exit(status); +} + +void +sh_verr(int status, const char *fmt, va_list ap) +{ + exvwarning(errno, fmt, ap); + sh_exit(status); +} + +void +sh_errx(int status, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + exvwarning(-1, fmt, ap); + va_end(ap); + sh_exit(status); +} + +void +sh_verrx(int status, const char *fmt, va_list ap) +{ + exvwarning(-1, fmt, ap); + sh_exit(status); +} + +void +sh_warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + exvwarning(errno, fmt, ap); + va_end(ap); +} + +void +sh_vwarn(const char *fmt, va_list ap) +{ + exvwarning(errno, fmt, ap); +} + +void +sh_warnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + exvwarning(-1, fmt, ap); + va_end(ap); +} + +void +sh_vwarnx(const char *fmt, va_list ap) +{ + exvwarning(-1, fmt, ap); +} + + +/* + * Table of error messages. + */ + +struct errname { + short errcode; /* error number */ + short action; /* operation which encountered the error */ + const char *msg; /* text describing the error */ +}; + + +#define ALL (E_OPEN|E_CREAT|E_EXEC) + +STATIC const struct errname errormsg[] = { + { EINTR, ALL, "interrupted" }, + { EACCES, ALL, "permission denied" }, + { EIO, ALL, "I/O error" }, + { EEXIST, ALL, "file exists" }, + { ENOENT, E_OPEN, "no such file" }, + { ENOENT, E_CREAT,"directory nonexistent" }, + { ENOENT, E_EXEC, "not found" }, + { ENOTDIR, E_OPEN, "no such file" }, + { ENOTDIR, E_CREAT,"directory nonexistent" }, + { ENOTDIR, E_EXEC, "not found" }, + { EISDIR, ALL, "is a directory" }, +#ifdef EMFILE + { EMFILE, ALL, "too many open files" }, +#endif + { ENFILE, ALL, "file table overflow" }, + { ENOSPC, ALL, "file system full" }, +#ifdef EDQUOT + { EDQUOT, ALL, "disk quota exceeded" }, +#endif +#ifdef ENOSR + { ENOSR, ALL, "no streams resources" }, +#endif + { ENXIO, ALL, "no such device or address" }, + { EROFS, ALL, "read-only file system" }, + { ETXTBSY, ALL, "text busy" }, +#ifdef EAGAIN + { EAGAIN, E_EXEC, "not enough memory" }, +#endif + { ENOMEM, ALL, "not enough memory" }, +#ifdef ENOLINK + { ENOLINK, ALL, "remote access failed" }, +#endif +#ifdef EMULTIHOP + { EMULTIHOP, ALL, "remote access failed" }, +#endif +#ifdef ECOMM + { ECOMM, ALL, "remote access failed" }, +#endif +#ifdef ESTALE + { ESTALE, ALL, "remote access failed" }, +#endif +#ifdef ETIMEDOUT + { ETIMEDOUT, ALL, "remote access failed" }, +#endif +#ifdef ELOOP + { ELOOP, ALL, "symbolic link loop" }, +#endif +#ifdef ENAMETOOLONG + { ENAMETOOLONG, ALL, "file name too long" }, +#endif + { E2BIG, E_EXEC, "argument list too long" }, +#ifdef ELIBACC + { ELIBACC, E_EXEC, "shared library missing" }, +#endif + { 0, 0, NULL }, +}; + + +/* + * Return a string describing an error. The returned string may be a + * pointer to a static buffer that will be overwritten on the next call. + * Action describes the operation that got the error. + */ + +const char * +errmsg(int e, int action) +{ + struct errname const *ep; + static char buf[12]; + + for (ep = errormsg ; ep->errcode ; ep++) { + if (ep->errcode == e && (ep->action & action) != 0) + return ep->msg; + } + fmtstr(buf, sizeof buf, "error %d", e); + return buf; +} diff --git a/bin/sh/error.h b/bin/sh/error.h new file mode 100644 index 0000000..8dcf737 --- /dev/null +++ b/bin/sh/error.h @@ -0,0 +1,120 @@ +/* $NetBSD: error.h,v 1.21 2018/08/19 23:50:27 kre Exp $ */ + +/*- + * 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 + */ + +#include <stdarg.h> + +/* + * Types of operations (passed to the errmsg routine). + */ + +#define E_OPEN 01 /* opening a file */ +#define E_CREAT 02 /* creating a file */ +#define E_EXEC 04 /* executing a program */ + + +/* + * 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 exeception. 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> + +struct jmploc { + jmp_buf loc; +}; + +extern struct jmploc *handler; +extern int exception; +extern int exerrno; /* error for EXEXEC */ + +/* exceptions */ +#define EXINT 0 /* SIGINT received */ +#define EXERROR 1 /* a generic error */ +#define EXSHELLPROC 2 /* execute a shell procedure */ +#define EXEXEC 3 /* command execution failed */ +#define EXEXIT 4 /* shell wants to exit(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 int suppressint; +extern volatile int intpending; + +#define INTOFF suppressint++ +#define INTON do { if (--suppressint == 0 && intpending) onint(); } while (0) +#define FORCEINTON do { suppressint = 0; if (intpending) onint(); } while (0) +#define CLEAR_PENDING_INT (intpending = 0) +#define int_pending() intpending + +#if ! defined(SHELL_BUILTIN) +void exraise(int) __dead; +void onint(void); +void error(const char *, ...) __dead __printflike(1, 2); +void exerror(int, const char *, ...) __dead __printflike(2, 3); +const char *errmsg(int, int); +#endif /* ! SHELL_BUILTIN */ + +void sh_err(int, const char *, ...) __dead __printflike(2, 3); +void sh_verr(int, const char *, va_list) __dead __printflike(2, 0); +void sh_errx(int, const char *, ...) __dead __printflike(2, 3); +void sh_verrx(int, const char *, va_list) __dead __printflike(2, 0); +void sh_warn(const char *, ...) __printflike(1, 2); +void sh_vwarn(const char *, va_list) __printflike(1, 0); +void sh_warnx(const char *, ...) __printflike(1, 2); +void sh_vwarnx(const char *, va_list) __printflike(1, 0); + +void sh_exit(int) __dead; + + +/* + * BSD setjmp saves the signal mask, which violates ANSI C and takes time, + * so we use _setjmp instead. + */ + +#if defined(BSD) && !defined(__SVR4) +#define setjmp(jmploc) _setjmp(jmploc) +#define longjmp(jmploc, val) _longjmp(jmploc, val) +#endif diff --git a/bin/sh/eval.c b/bin/sh/eval.c new file mode 100644 index 0000000..2bda702 --- /dev/null +++ b/bin/sh/eval.c @@ -0,0 +1,1680 @@ +/* $NetBSD: eval.c,v 1.170 2019/01/21 14:18:59 kre Exp $ */ + +/*- + * 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> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)eval.c 8.9 (Berkeley) 6/8/95"; +#else +__RCSID("$NetBSD: eval.c,v 1.170 2019/01/21 14:18:59 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdbool.h> +#include <stdlib.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <limits.h> +#include <unistd.h> +#include <sys/fcntl.h> +#include <sys/stat.h> +#include <sys/times.h> +#include <sys/param.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/sysctl.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" +#include "main.h" +#ifndef SMALL +#include "nodenames.h" +#include "myhistedit.h" +#endif + + +STATIC struct skipsave s_k_i_p; +#define evalskip (s_k_i_p.state) +#define skipcount (s_k_i_p.count) + +STATIC int loopnest; /* current loop nesting level */ +STATIC int funcnest; /* depth of function calls */ +STATIC int builtin_flags; /* evalcommand flags for builtins */ +/* + * Base function nesting level inside a dot command. Set to 0 initially + * and to (funcnest + 1) before every dot command to enable + * 1) detection of being in a file sourced by a dot command and + * 2) counting of function nesting in that file for the implementation + * of the return command. + * The value is reset to its previous value after the dot command. + */ +STATIC int dot_funcnest; + + +const char *commandname; +struct strlist *cmdenviron; +int exitstatus; /* exit status of last command */ +int back_exitstatus; /* exit status of backquoted command */ + + +STATIC void evalloop(union node *, int); +STATIC void evalfor(union node *, int); +STATIC void evalcase(union node *, int); +STATIC void evalsubshell(union node *, int); +STATIC void expredir(union node *); +STATIC void evalredir(union node *, int); +STATIC void evalpipe(union node *); +STATIC void evalcommand(union node *, int, struct backcmd *); +STATIC void prehash(union node *); + +STATIC char *find_dot_file(char *); + +/* + * Called to reset things after an exception. + */ + +#ifdef mkinit +INCLUDE "eval.h" + +RESET { + reset_eval(); +} + +SHELLPROC { + exitstatus = 0; +} +#endif + +void +reset_eval(void) +{ + evalskip = SKIPNONE; + dot_funcnest = 0; + loopnest = 0; + funcnest = 0; +} + +static int +sh_pipe(int fds[2]) +{ + int nfd; + + if (pipe(fds)) + return -1; + + if (fds[0] < 3) { + nfd = fcntl(fds[0], F_DUPFD, 3); + if (nfd != -1) { + close(fds[0]); + fds[0] = nfd; + } + } + + if (fds[1] < 3) { + nfd = fcntl(fds[1], F_DUPFD, 3); + if (nfd != -1) { + close(fds[1]); + fds[1] = nfd; + } + } + return 0; +} + + +/* + * The eval commmand. + */ + +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 (;;) { + while (*p) + STPUTC(*p++, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + evalstring(p, builtin_flags & EV_TESTED); + } else + exitstatus = 0; + return exitstatus; +} + + +/* + * Execute a command or commands contained in a string. + */ + +void +evalstring(char *s, int flag) +{ + union node *n; + struct stackmark smark; + int last; + int any; + + last = flag & EV_EXIT; + flag &= ~EV_EXIT; + + setstackmark(&smark); + setinputstring(s, 1, line_number); + + any = 0; /* to determine if exitstatus will have been set */ + while ((n = parsecmd(0)) != NEOF) { + XTRACE(DBG_EVAL, ("evalstring: "), showtree(n)); + if (n && nflag == 0) { + if (last && at_eof()) + evaltree(n, flag | EV_EXIT); + else + evaltree(n, flag); + any = 1; + if (evalskip) + break; + } + rststackmark(&smark); + } + popfile(); + popstackmark(&smark); + if (!any) + exitstatus = 0; + if (last) + exraise(EXEXIT); +} + + + +/* + * Evaluate a parse tree. The value is left in the global variable + * exitstatus. + */ + +void +evaltree(union node *n, int flags) +{ + bool do_etest; + int sflags = flags & ~EV_EXIT; + union node *next; + struct stackmark smark; + + do_etest = false; + if (n == NULL || nflag) { + VTRACE(DBG_EVAL, ("evaltree(%s) called\n", + n == NULL ? "NULL" : "-n")); + if (nflag == 0) + exitstatus = 0; + goto out2; + } + + setstackmark(&smark); + do { +#ifndef SMALL + displayhist = 1; /* show history substitutions done with fc */ +#endif + next = NULL; + CTRACE(DBG_EVAL, ("pid %d, evaltree(%p: %s(%d), %#x) called\n", + getpid(), n, NODETYPENAME(n->type), n->type, flags)); + if (n->type != NCMD && traps_invalid) + free_traps(); + switch (n->type) { + case NSEMI: + evaltree(n->nbinary.ch1, sflags); + if (nflag || evalskip) + goto out1; + next = n->nbinary.ch2; + break; + case NAND: + evaltree(n->nbinary.ch1, EV_TESTED); + if (nflag || evalskip || exitstatus != 0) + goto out1; + next = n->nbinary.ch2; + break; + case NOR: + evaltree(n->nbinary.ch1, EV_TESTED); + if (nflag || evalskip || exitstatus == 0) + goto out1; + 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 (nflag || evalskip) + goto out1; + 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, sflags); + break; + case NFOR: + evalfor(n, sflags); + break; + case NCASE: + evalcase(n, sflags); + break; + case NDEFUN: + CTRACE(DBG_EVAL, ("Defining fn %s @%d%s\n", + n->narg.text, n->narg.lineno, + fnline1 ? " LINENO=1" : "")); + defun(n->narg.text, n->narg.next, n->narg.lineno); + exitstatus = 0; + break; + case NNOT: + evaltree(n->nnot.com, EV_TESTED); + exitstatus = !exitstatus; + break; + case NDNOT: + evaltree(n->nnot.com, EV_TESTED); + if (exitstatus != 0) + exitstatus = 1; + break; + case NPIPE: + evalpipe(n); + do_etest = !(flags & EV_TESTED); + break; + case NCMD: + evalcommand(n, flags, NULL); + do_etest = !(flags & EV_TESTED); + break; + default: +#ifdef NODETYPENAME + out1fmt("Node type = %d(%s)\n", + n->type, NODETYPENAME(n->type)); +#else + out1fmt("Node type = %d\n", n->type); +#endif + flushout(&output); + break; + } + n = next; + rststackmark(&smark); + } while(n != NULL); + out1: + popstackmark(&smark); + out2: + if (pendingsigs) + 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; + + CTRACE(DBG_EVAL, ("evalloop %s:", NODETYPENAME(n->type))); + VXTRACE(DBG_EVAL, (" "), showtree(n->nbinary.ch1)); + VXTRACE(DBG_EVAL, ("evalloop do: "), showtree(n->nbinary.ch2)); + VTRACE(DBG_EVAL, ("evalloop done\n")); + CTRACE(DBG_EVAL, ("\n")); + + for (;;) { + evaltree(n->nbinary.ch1, EV_TESTED); + if (nflag) + break; + if (evalskip) { + skipping: if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = SKIPNONE; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = SKIPNONE; + break; + } + if (n->type == NWHILE) { + if (exitstatus != 0) + break; + } else { + if (exitstatus == 0) + break; + } + evaltree(n->nbinary.ch2, flags & EV_TESTED); + status = exitstatus; + if (evalskip) + goto skipping; + } + loopnest--; + exitstatus = status; +} + + + +STATIC void +evalfor(union node *n, int flags) +{ + struct arglist arglist; + union node *argp; + struct strlist *sp; + struct stackmark smark; + int status; + + status = nflag ? exitstatus : 0; + + setstackmark(&smark); + arglist.lastp = &arglist.list; + for (argp = n->nfor.args ; argp ; argp = argp->narg.next) { + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + if (evalskip) + goto out; + } + *arglist.lastp = NULL; + + loopnest++; + for (sp = arglist.list ; sp ; sp = sp->next) { + if (xflag) { + outxstr(expandstr(ps4val(), line_number)); + outxstr("for "); + outxstr(n->nfor.var); + outxc('='); + outxshstr(sp->text); + outxc('\n'); + flushout(outx); + } + + setvar(n->nfor.var, sp->text, 0); + evaltree(n->nfor.body, flags & EV_TESTED); + status = exitstatus; + if (nflag) + break; + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = SKIPNONE; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = SKIPNONE; + break; + } + } + loopnest--; + exitstatus = status; + out: + popstackmark(&smark); +} + + + +STATIC void +evalcase(union node *n, int flags) +{ + union node *cp, *ncp; + union node *patp; + struct arglist arglist; + struct stackmark smark; + int status = 0; + + setstackmark(&smark); + arglist.lastp = &arglist.list; + line_number = n->ncase.lineno; + expandarg(n->ncase.expr, &arglist, EXP_TILDE); + for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) { + for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) { + line_number = patp->narg.lineno; + if (casematch(patp, arglist.list->text)) { + while (cp != NULL && evalskip == 0 && + nflag == 0) { + if (cp->type == NCLISTCONT) + ncp = cp->nclist.next; + else + ncp = NULL; + line_number = cp->nclist.lineno; + evaltree(cp->nclist.body, flags); + status = exitstatus; + cp = ncp; + } + goto out; + } + } + } + out: + exitstatus = status; + popstackmark(&smark); +} + + + +/* + * Kick off a subshell to evaluate a tree. + */ + +STATIC void +evalsubshell(union node *n, int flags) +{ + struct job *jp= NULL; + int backgnd = (n->type == NBACKGND); + + expredir(n->nredir.redirect); + if (xflag && n->nredir.redirect) { + union node *rn; + + outxstr(expandstr(ps4val(), line_number)); + outxstr("using redirections:"); + for (rn = n->nredir.redirect; rn; rn = rn->nfile.next) + (void) outredir(outx, rn, ' '); + outxstr(" do subshell ("/*)*/); + if (backgnd) + outxstr(/*(*/") &"); + outxc('\n'); + flushout(outx); + } + if ((!backgnd && flags & EV_EXIT && !have_traps()) || + forkshell(jp = makejob(n, 1), n, backgnd?FORK_BG:FORK_FG) == 0) { + INTON; + if (backgnd) + flags &=~ EV_TESTED; + redirect(n->nredir.redirect, REDIR_KEEP); + evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */ + } else if (!backgnd) { + INTOFF; + exitstatus = waitforjob(jp); + INTON; + } else + exitstatus = 0; + + if (!backgnd && xflag && n->nredir.redirect) { + outxstr(expandstr(ps4val(), line_number)); + outxstr(/*(*/") done subshell\n"); + flushout(outx); + } +} + + + +/* + * 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; + + fn.lastp = &fn.list; + switch (redir->type) { + case NFROMTO: + case NFROM: + case NTO: + case NCLOBBER: + case NAPPEND: + expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR); + redir->nfile.expfname = fn.list->text; + break; + case NFROMFD: + case NTOFD: + if (redir->ndup.vname) { + expandarg(redir->ndup.vname, &fn, EXP_TILDE | EXP_REDIR); + fixredir(redir, fn.list->text, 1); + } + break; + } + } +} + +/* + * Perform redirections for a compound command, and then do it (and restore) + */ +STATIC void +evalredir(union node *n, int flags) +{ + struct jmploc jmploc; + struct jmploc * const savehandler = handler; + volatile int in_redirect = 1; + const char * volatile PS4 = NULL; + + expredir(n->nredir.redirect); + + if (xflag && n->nredir.redirect) { + union node *rn; + + outxstr(PS4 = expandstr(ps4val(), line_number)); + outxstr("using redirections:"); + for (rn = n->nredir.redirect; rn != NULL; rn = rn->nfile.next) + (void) outredir(outx, rn, ' '); + outxstr(" do {\n"); /* } */ + flushout(outx); + } + + if (setjmp(jmploc.loc)) { + int e; + + handler = savehandler; + e = exception; + popredir(); + if (PS4 != NULL) { + outxstr(PS4); + /* { */ outxstr("} failed\n"); + flushout(outx); + } + if (e == EXERROR || e == EXEXEC) { + if (in_redirect) { + exitstatus = 2; + return; + } + } + longjmp(handler->loc, 1); + } else { + INTOFF; + handler = &jmploc; + redirect(n->nredir.redirect, REDIR_PUSH | REDIR_KEEP); + in_redirect = 0; + INTON; + evaltree(n->nredir.n, flags); + } + INTOFF; + handler = savehandler; + popredir(); + INTON; + + if (PS4 != NULL) { + outxstr(PS4); + /* { */ outxstr("} done\n"); + flushout(outx); + } +} + + +/* + * 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]; + + CTRACE(DBG_EVAL, ("evalpipe(%p) called\n", 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 (sh_pipe(pip) < 0) { + if (prevfd >= 0) + close(prevfd); + error("Pipe call failed: %s", strerror(errno)); + } + } + if (forkshell(jp, lp->n, + n->npipe.backgnd ? FORK_BG : FORK_FG) == 0) { + INTON; + if (prevfd > 0) + movefd(prevfd, 0); + if (pip[1] >= 0) { + close(pip[0]); + movefd(pip[1], 1); + } + evaltree(lp->n, EV_EXIT); + } + if (prevfd >= 0) + close(prevfd); + prevfd = pip[0]; + close(pip[1]); + } + if (n->npipe.backgnd == 0) { + INTOFF; + exitstatus = waitforjob(jp); + CTRACE(DBG_EVAL, ("evalpipe: job done exit status %d\n", + exitstatus)); + INTON; + } else + exitstatus = 0; + INTON; +} + + + +/* + * 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; /* unnecessary (because we fork) */ + + result->fd = -1; + result->buf = NULL; + result->nleft = 0; + result->jp = NULL; + + if (nflag || n == NULL) + goto out; + + setstackmark(&smark); + +#ifdef notyet + /* + * For now we disable executing builtins in the same + * context as the shell, because we are not keeping + * enough state to recover from changes that are + * supposed only to affect subshells. eg. echo "`cd /`" + */ + if (n->type == NCMD) { + exitstatus = oexitstatus; /* XXX o... no longer exists */ + evalcommand(n, EV_BACKCMD, result); + } else +#endif + { + INTOFF; + if (sh_pipe(pip) < 0) + error("Pipe call failed"); + jp = makejob(n, 1); + if (forkshell(jp, n, FORK_NOJOB) == 0) { + FORCEINTON; + close(pip[0]); + movefd(pip[1], 1); + eflag = 0; + evaltree(n, EV_EXIT); + /* NOTREACHED */ + } + close(pip[1]); + result->fd = pip[0]; + result->jp = jp; + INTON; + } + popstackmark(&smark); + out: + CTRACE(DBG_EVAL, ("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n", + result->fd, result->buf, result->nleft, result->jp)); +} + +const char * +syspath(void) +{ + static char *sys_path = NULL; + static int mib[] = {CTL_USER, USER_CS_PATH}; + static char def_path[] = "PATH=/usr/bin:/bin:/usr/sbin:/sbin"; + size_t len; + + if (sys_path == NULL) { + if (sysctl(mib, 2, 0, &len, 0, 0) != -1 && + (sys_path = ckmalloc(len + 5)) != NULL && + sysctl(mib, 2, sys_path + 5, &len, 0, 0) != -1) { + memcpy(sys_path, "PATH=", 5); + } else { + ckfree(sys_path); + /* something to keep things happy */ + sys_path = def_path; + } + } + return sys_path; +} + +static int +parse_command_args(int argc, char **argv, int *use_syspath) +{ + int sv_argc = argc; + char *cp, c; + + *use_syspath = 0; + + for (;;) { + argv++; + if (--argc == 0) + break; + cp = *argv; + if (*cp++ != '-') + break; + if (*cp == '-' && cp[1] == 0) { + argv++; + argc--; + break; + } + while ((c = *cp++)) { + switch (c) { + case 'p': + *use_syspath = 1; + break; + default: + /* run 'typecmd' for other options */ + return 0; + } + } + } + return sv_argc - argc; +} + +int vforked = 0; +extern char *trap[]; + +/* + * Execute a simple command. + */ + +STATIC void +evalcommand(union node *cmd, int flgs, struct backcmd *backcmd) +{ + struct stackmark smark; + union node *argp; + struct arglist arglist; + struct arglist varlist; + volatile int flags = flgs; + char ** volatile argv; + volatile int argc; + char **envp; + int varflag; + struct strlist *sp; + volatile int mode; + int pip[2]; + struct cmdentry cmdentry; + struct job * volatile jp; + struct jmploc jmploc; + struct jmploc *volatile savehandler = NULL; + const char *volatile savecmdname; + volatile struct shparam saveparam; + struct localvar *volatile savelocalvars; + struct parsefile *volatile savetopfile; + volatile int e; + char * volatile lastarg; + const char * volatile path = pathval(); + volatile int temp_path; + const int savefuncline = funclinebase; + const int savefuncabs = funclineabs; + volatile int cmd_flags = 0; + + vforked = 0; + /* First expand the arguments. */ + CTRACE(DBG_EVAL, ("evalcommand(%p, %d) called [%s]\n", cmd, flags, + cmd->ncmd.args ? cmd->ncmd.args->narg.text : "")); + setstackmark(&smark); + back_exitstatus = 0; + + line_number = cmd->ncmd.lineno; + + arglist.lastp = &arglist.list; + varflag = 1; + /* Expand arguments, ignoring the initial 'name=value' ones */ + for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) { + if (varflag && isassignment(argp->narg.text)) + continue; + varflag = 0; + line_number = argp->narg.lineno; + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + } + *arglist.lastp = NULL; + + expredir(cmd->ncmd.redirect); + + /* Now do the initial 'name=value' ones we skipped above */ + varlist.lastp = &varlist.list; + for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) { + line_number = argp->narg.lineno; + if (!isassignment(argp->narg.text)) + break; + expandarg(argp, &varlist, EXP_VARTILDE); + } + *varlist.lastp = NULL; + + argc = 0; + for (sp = arglist.list ; sp ; sp = sp->next) + argc++; + argv = stalloc(sizeof (char *) * (argc + 1)); + + for (sp = arglist.list ; sp ; sp = sp->next) { + VTRACE(DBG_EVAL, ("evalcommand arg: %s\n", sp->text)); + *argv++ = sp->text; + } + *argv = NULL; + lastarg = NULL; + if (iflag && funcnest == 0 && argc > 0) + lastarg = argv[-1]; + argv -= argc; + + /* Print the command if xflag is set. */ + if (xflag) { + char sep = 0; + union node *rn; + + outxstr(expandstr(ps4val(), line_number)); + for (sp = varlist.list ; sp ; sp = sp->next) { + char *p; + + if (sep != 0) + outxc(sep); + + /* + * The "var=" part should not be quoted, regardless + * of the value, or it would not represent an + * assignment, but rather a command + */ + p = strchr(sp->text, '='); + if (p != NULL) { + *p = '\0'; /*XXX*/ + outxshstr(sp->text); + outxc('='); + *p++ = '='; /*XXX*/ + } else + p = sp->text; + outxshstr(p); + sep = ' '; + } + for (sp = arglist.list ; sp ; sp = sp->next) { + if (sep != 0) + outxc(sep); + outxshstr(sp->text); + sep = ' '; + } + for (rn = cmd->ncmd.redirect; rn; rn = rn->nfile.next) + if (outredir(outx, rn, sep)) + sep = ' '; + outxc('\n'); + flushout(outx); + } + + /* Now locate the command. */ + if (argc == 0) { + /* + * the empty command begins as a normal builtin, and + * remains that way while redirects are processed, then + * will become special before we get to doing the + * var assigns. + */ + cmdentry.cmdtype = CMDBUILTIN; + cmdentry.u.bltin = bltincmd; + } else { + static const char PATH[] = "PATH="; + + /* + * Modify the command lookup path, if a PATH= assignment + * is present + */ + for (sp = varlist.list; sp; sp = sp->next) + if (strncmp(sp->text, PATH, sizeof(PATH) - 1) == 0) + path = sp->text + sizeof(PATH) - 1; + + do { + int argsused, use_syspath; + + find_command(argv[0], &cmdentry, cmd_flags, path); +#if 0 + /* + * This short circuits all of the processing that + * should be done (including processing the + * redirects), so just don't ... + * + * (eventually this whole #if'd block will vanish) + */ + if (cmdentry.cmdtype == CMDUNKNOWN) { + exitstatus = 127; + flushout(&errout); + goto out; + } +#endif + + /* implement the 'command' builtin here */ + if (cmdentry.cmdtype != CMDBUILTIN || + cmdentry.u.bltin != bltincmd) + break; + cmd_flags |= DO_NOFUNC; + argsused = parse_command_args(argc, argv, &use_syspath); + if (argsused == 0) { + /* use 'type' builtin to display info */ + cmdentry.u.bltin = typecmd; + break; + } + argc -= argsused; + argv += argsused; + if (use_syspath) + path = syspath() + 5; + } while (argc != 0); + if (cmdentry.cmdtype == CMDSPLBLTIN && cmd_flags & DO_NOFUNC) + /* posix mandates that 'command <splbltin>' act as if + <splbltin> was a normal builtin */ + cmdentry.cmdtype = CMDBUILTIN; + } + + /* + * When traps are invalid, we permit the following: + * trap + * command trap + * eval trap + * command eval trap + * eval command trap + * without zapping the traps completely, in all other cases we do. + * + * The test here permits eval "anything" but when evalstring() comes + * back here again, the "anything" will be validated. + * This means we can actually do: + * eval eval eval command eval eval command trap + * as long as we end up with just "trap" + * + * We permit "command" by allowing CMDBUILTIN as well as CMDSPLBLTIN + * + * trapcmd() takes care of doing free_traps() if it is needed there. + */ + if (traps_invalid && + ((cmdentry.cmdtype!=CMDSPLBLTIN && cmdentry.cmdtype!=CMDBUILTIN) || + (cmdentry.u.bltin != trapcmd && cmdentry.u.bltin != evalcmd))) + free_traps(); + + /* Fork off a child process if necessary. */ + if (cmd->ncmd.backgnd || (have_traps() && (flags & EV_EXIT) != 0) + || ((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN) + && (flags & EV_EXIT) == 0) + || ((flags & EV_BACKCMD) != 0 && + ((cmdentry.cmdtype != CMDBUILTIN && cmdentry.cmdtype != CMDSPLBLTIN) + || cmdentry.u.bltin == dotcmd + || cmdentry.u.bltin == evalcmd))) { + INTOFF; + jp = makejob(cmd, 1); + mode = cmd->ncmd.backgnd; + if (flags & EV_BACKCMD) { + mode = FORK_NOJOB; + if (sh_pipe(pip) < 0) + error("Pipe call failed"); + } +#ifdef DO_SHAREDVFORK + /* It is essential that if DO_SHAREDVFORK is defined that the + * child's address space is actually shared with the parent as + * we rely on this. + */ + if (usefork == 0 && cmdentry.cmdtype == CMDNORMAL) { + pid_t pid; + int serrno; + + savelocalvars = localvars; + localvars = NULL; + vforked = 1; + VFORK_BLOCK + switch (pid = vfork()) { + case -1: + serrno = errno; + VTRACE(DBG_EVAL, ("vfork() failed, errno=%d\n", + serrno)); + INTON; + error("Cannot vfork (%s)", strerror(serrno)); + break; + case 0: + /* Make sure that exceptions only unwind to + * after the vfork(2) + */ + SHELL_FORKED(); + if (setjmp(jmploc.loc)) { + if (exception == EXSHELLPROC) { + /* + * We can't progress with the + * vfork, so, set vforked = 2 + * so the parent knows, + * and _exit(); + */ + vforked = 2; + _exit(0); + } else { + _exit(exception == EXEXIT ? + exitstatus : exerrno); + } + } + savehandler = handler; + handler = &jmploc; + listmklocal(varlist.list, + VDOEXPORT | VEXPORT | VNOFUNC); + forkchild(jp, cmd, mode, vforked); + break; + default: + VFORK_UNDO(); + /* restore from vfork(2) */ + handler = savehandler; + poplocalvars(); + localvars = savelocalvars; + if (vforked == 2) { + vforked = 0; + + (void)waitpid(pid, NULL, 0); + /* + * We need to progress in a + * normal fork fashion + */ + goto normal_fork; + } + /* + * Here the child has left home, + * getting on with its life, so + * so must we... + */ + vforked = 0; + forkparent(jp, cmd, mode, pid); + goto parent; + } + VFORK_END + } else { + normal_fork: +#endif + if (forkshell(jp, cmd, mode) != 0) + goto parent; /* at end of routine */ + flags |= EV_EXIT; + FORCEINTON; +#ifdef DO_SHAREDVFORK + } +#endif + if (flags & EV_BACKCMD) { + if (!vforked) { + FORCEINTON; + } + close(pip[0]); + movefd(pip[1], 1); + } + flags |= EV_EXIT; + } + + /* This is the child process if a fork occurred. */ + /* Execute the command. */ + switch (cmdentry.cmdtype) { + volatile int saved; + + case CMDFUNCTION: + VXTRACE(DBG_EVAL, ("Shell function%s: ",vforked?" VF":""), + trargs(argv)); + redirect(cmd->ncmd.redirect, saved = + !(flags & EV_EXIT) || have_traps() ? REDIR_PUSH : 0); + saveparam = shellparam; + shellparam.malloc = 0; + shellparam.reset = 1; + shellparam.nparam = argc - 1; + shellparam.p = argv + 1; + shellparam.optnext = NULL; + INTOFF; + savelocalvars = localvars; + localvars = NULL; + reffunc(cmdentry.u.func); + INTON; + if (setjmp(jmploc.loc)) { + if (exception == EXSHELLPROC) { + freeparam((volatile struct shparam *) + &saveparam); + } else { + freeparam(&shellparam); + shellparam = saveparam; + } + if (saved) + popredir();; + unreffunc(cmdentry.u.func); + poplocalvars(); + localvars = savelocalvars; + funclinebase = savefuncline; + funclineabs = savefuncabs; + handler = savehandler; + longjmp(handler->loc, 1); + } + savehandler = handler; + handler = &jmploc; + if (cmdentry.u.func) { + if (cmdentry.lno_frel) + funclinebase = cmdentry.lineno - 1; + else + funclinebase = 0; + funclineabs = cmdentry.lineno; + + VTRACE(DBG_EVAL, + ("function: node: %d '%s' # %d%s; funclinebase=%d\n", + getfuncnode(cmdentry.u.func)->type, + NODETYPENAME(getfuncnode(cmdentry.u.func)->type), + cmdentry.lineno, cmdentry.lno_frel?" (=1)":"", + funclinebase)); + } + listmklocal(varlist.list, VDOEXPORT | VEXPORT); + /* stop shell blowing its stack */ + if (++funcnest > 1000) + error("too many nested function calls"); + evaltree(getfuncnode(cmdentry.u.func), + flags & (EV_TESTED|EV_EXIT)); + funcnest--; + INTOFF; + unreffunc(cmdentry.u.func); + poplocalvars(); + localvars = savelocalvars; + funclinebase = savefuncline; + funclineabs = savefuncabs; + freeparam(&shellparam); + shellparam = saveparam; + handler = savehandler; + if (saved) + popredir(); + INTON; + if (evalskip == SKIPFUNC) { + evalskip = SKIPNONE; + skipcount = 0; + } + if (flags & EV_EXIT) + exitshell(exitstatus); + break; + + case CMDSPLBLTIN: + VTRACE(DBG_EVAL, ("special ")); + case CMDBUILTIN: + VXTRACE(DBG_EVAL, ("builtin command [%d]%s: ", argc, + vforked ? " VF" : ""), trargs(argv)); + mode = (cmdentry.u.bltin == execcmd) ? 0 : REDIR_PUSH; + if (flags == EV_BACKCMD) { + memout.nleft = 0; + memout.nextc = memout.buf; + memout.bufsize = 64; + mode |= REDIR_BACKQ; + } + e = -1; + savecmdname = commandname; + savetopfile = getcurrentfile(); + savehandler = handler; + temp_path = 0; + if (!setjmp(jmploc.loc)) { + handler = &jmploc; + + /* + * We need to ensure the command hash table isn't + * corrupted by temporary PATH assignments. + * However we must ensure the 'local' command works! + */ + if (path != pathval() && (cmdentry.u.bltin == hashcmd || + cmdentry.u.bltin == typecmd)) { + savelocalvars = localvars; + localvars = 0; + temp_path = 1; + mklocal(path - 5 /* PATH= */, 0); + } + redirect(cmd->ncmd.redirect, mode); + + /* + * the empty command is regarded as a normal + * builtin for the purposes of redirects, but + * is a special builtin for var assigns. + * (unless we are the "command" command.) + */ + if (argc == 0 && !(cmd_flags & DO_NOFUNC)) + cmdentry.cmdtype = CMDSPLBLTIN; + + /* exec is a special builtin, but needs this list... */ + cmdenviron = varlist.list; + /* we must check 'readonly' flag for all builtins */ + listsetvar(varlist.list, + cmdentry.cmdtype == CMDSPLBLTIN ? 0 : VNOSET); + commandname = argv[0]; + /* initialize nextopt */ + argptr = argv + 1; + optptr = NULL; + /* and getopt */ + optreset = 1; + optind = 1; + builtin_flags = flags; + exitstatus = cmdentry.u.bltin(argc, argv); + } else { + e = exception; + if (e == EXINT) + exitstatus = SIGINT + 128; + else if (e == EXEXEC) + exitstatus = exerrno; + else if (e != EXEXIT) + exitstatus = 2; + } + handler = savehandler; + flushall(); + out1 = &output; + out2 = &errout; + freestdout(); + if (temp_path) { + poplocalvars(); + localvars = savelocalvars; + } + cmdenviron = NULL; + if (e != EXSHELLPROC) { + commandname = savecmdname; + if (flags & EV_EXIT) + exitshell(exitstatus); + } + if (e != -1) { + if ((e != EXERROR && e != EXEXEC) + || cmdentry.cmdtype == CMDSPLBLTIN) + exraise(e); + popfilesupto(savetopfile); + FORCEINTON; + } + if (cmdentry.u.bltin != execcmd) + popredir(); + if (flags == EV_BACKCMD) { + backcmd->buf = memout.buf; + backcmd->nleft = memout.nextc - memout.buf; + memout.buf = NULL; + } + break; + + default: + VXTRACE(DBG_EVAL, ("normal command%s: ", vforked?" VF":""), + trargs(argv)); + redirect(cmd->ncmd.redirect, + (vforked ? REDIR_VFORK : 0) | REDIR_KEEP); + if (!vforked) + for (sp = varlist.list ; sp ; sp = sp->next) + setvareq(sp->text, VDOEXPORT|VEXPORT|VSTACK); + envp = environment(); + shellexec(argv, envp, path, cmdentry.u.index, vforked); + break; + } + goto out; + + parent: /* parent process gets here (if we forked) */ + + exitstatus = 0; /* if not altered just below */ + if (mode == FORK_FG) { /* argument to fork */ + exitstatus = waitforjob(jp); + } else if (mode == FORK_NOJOB) { + backcmd->fd = pip[0]; + close(pip[1]); + backcmd->jp = jp; + } + FORCEINTON; + + out: + if (lastarg) + /* implement $_ for whatever use that really is */ + (void) setvarsafe("_", lastarg, VNOERROR); + popstackmark(&smark); +} + + +/* + * 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()); +} + +int +in_function(void) +{ + return funcnest; +} + +enum skipstate +current_skipstate(void) +{ + return evalskip; +} + +void +save_skipstate(struct skipsave *p) +{ + *p = s_k_i_p; +} + +void +restore_skipstate(const struct skipsave *p) +{ + s_k_i_p = *p; +} + +void +stop_skipping(void) +{ + evalskip = SKIPNONE; + skipcount = 0; +} + +/* + * Builtin commands. Builtin commands whose functions are closely + * tied to evaluation are implemented here. + */ + +/* + * No command given. + */ + +int +bltincmd(int argc, char **argv) +{ + /* + * Preserve exitstatus of a previous possible redirection + * as POSIX mandates + */ + return back_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) +{ + int n = argc > 1 ? number(argv[1]) : 1; + + if (n <= 0) + error("invalid count: %d", n); + if (n > loopnest) + n = loopnest; + if (n > 0) { + evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; + skipcount = n; + } + return 0; +} + +int +dotcmd(int argc, char **argv) +{ + exitstatus = 0; + + if (argc >= 2) { /* That's what SVR2 does */ + char *fullname; + /* + * dot_funcnest needs to be 0 when not in a dotcmd, so it + * cannot be restored with (funcnest + 1). + */ + int dot_funcnest_old; + struct stackmark smark; + + setstackmark(&smark); + fullname = find_dot_file(argv[1]); + setinputfile(fullname, 1); + commandname = fullname; + dot_funcnest_old = dot_funcnest; + dot_funcnest = funcnest + 1; + cmdloop(0); + dot_funcnest = dot_funcnest_old; + popfile(); + popstackmark(&smark); + } + return exitstatus; +} + +/* + * 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 *path = pathval(); + struct stat statb; + + /* don't try this for absolute or relative paths */ + if (strchr(basename, '/')) { + if (stat(basename, &statb) == 0) { + if (S_ISDIR(statb.st_mode)) + error("%s: is a directory", basename); + if (S_ISBLK(statb.st_mode)) + error("%s: is a block device", basename); + return basename; + } + } else while ((fullname = padvance(&path, basename, 1)) != NULL) { + if ((stat(fullname, &statb) == 0)) { + /* weird format is to ease future code... */ + if (S_ISDIR(statb.st_mode) || S_ISBLK(statb.st_mode)) + ; +#if notyet + else if (unreadable()) { + /* + * testing this via st_mode is ugly to get + * correct (and would ignore ACLs). + * better way is just to open the file. + * But doing that here would (currently) + * mean opening the file twice, which + * might not be safe. So, defer this + * test until code is restructures so + * we can return a fd. Then we also + * get to fix the mem leak just below... + */ + } +#endif + else { + /* + * Don't bother freeing here, since + * it will be freed by the caller. + * XXX no it won't - a bug for later. + */ + return fullname; + } + } + stunalloc(fullname); + } + + /* not found in the PATH */ + error("%s: not found", basename); + /* NOTREACHED */ +} + + + +/* + * The return command. + * + * Quoth the POSIX standard: + * The return utility shall cause the shell to stop executing the current + * function or dot script. If the shell is not currently executing + * a function or dot script, the results are unspecified. + * + * As for the unspecified part, there seems to be no de-facto standard: bash + * ignores the return with a warning, zsh ignores the return in interactive + * mode but seems to liken it to exit in a script. (checked May 2014) + * + * We choose to silently ignore the return. Older versions of this shell + * set evalskip to SKIPFILE causing the shell to (indirectly) exit. This + * had at least the problem of circumventing the check for stopped jobs, + * which would occur for exit or ^D. + */ + +int +returncmd(int argc, char **argv) +{ + int ret = argc > 1 ? number(argv[1]) : exitstatus; + + if ((dot_funcnest == 0 && funcnest) + || (dot_funcnest > 0 && funcnest - (dot_funcnest - 1) > 0)) { + evalskip = SKIPFUNC; + skipcount = 1; + } else if (dot_funcnest > 0) { + evalskip = SKIPFILE; + skipcount = 1; + } else { + /* XXX: should a warning be issued? */ + ret = 0; + } + + return ret; +} + + +int +falsecmd(int argc, char **argv) +{ + return 1; +} + + +int +truecmd(int argc, char **argv) +{ + return 0; +} + + +int +execcmd(int argc, char **argv) +{ + if (argc > 1) { + struct strlist *sp; + + iflag = 0; /* exit on error */ + mflag = 0; + optschanged(); + for (sp = cmdenviron; sp; sp = sp->next) + setvareq(sp->text, VDOEXPORT|VEXPORT|VSTACK); + shellexec(argv + 1, environment(), pathval(), 0, 0); + } + return 0; +} + +static int +conv_time(clock_t ticks, char *seconds, size_t l) +{ + static clock_t tpm = 0; + clock_t mins; + int i; + + if (!tpm) + tpm = sysconf(_SC_CLK_TCK) * 60; + + mins = ticks / tpm; + snprintf(seconds, l, "%.4f", (ticks - mins * tpm) * 60.0 / tpm ); + + if (seconds[0] == '6' && seconds[1] == '0') { + /* 59.99995 got rounded up... */ + mins++; + strlcpy(seconds, "0.0", l); + return mins; + } + + /* suppress trailing zeros */ + i = strlen(seconds) - 1; + for (; seconds[i] == '0' && seconds[i - 1] != '.'; i--) + seconds[i] = 0; + return mins; +} + +int +timescmd(int argc, char **argv) +{ + struct tms tms; + int u, s, cu, cs; + char us[8], ss[8], cus[8], css[8]; + + nextopt(""); + + times(&tms); + + u = conv_time(tms.tms_utime, us, sizeof(us)); + s = conv_time(tms.tms_stime, ss, sizeof(ss)); + cu = conv_time(tms.tms_cutime, cus, sizeof(cus)); + cs = conv_time(tms.tms_cstime, css, sizeof(css)); + + outfmt(out1, "%dm%ss %dm%ss\n%dm%ss %dm%ss\n", + u, us, s, ss, cu, cus, cs, css); + + return 0; +} diff --git a/bin/sh/eval.h b/bin/sh/eval.h new file mode 100644 index 0000000..b18dd42 --- /dev/null +++ b/bin/sh/eval.h @@ -0,0 +1,87 @@ +/* $NetBSD: eval.h,v 1.22 2018/12/03 06:43:19 kre Exp $ */ + +/*- + * 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 + */ + +extern const char *commandname; /* currently executing command */ +extern int exitstatus; /* exit status of last command */ +extern int back_exitstatus; /* exit status of backquoted command */ +extern struct strlist *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 evalstring(char *, int); +union node; /* BLETCH for ansi C */ +void evaltree(union node *, int); +void evalbackcmd(union node *, struct backcmd *); + +const char *syspath(void); + +/* in_function returns nonzero if we are currently evaluating a function */ +int in_function(void); /* return non-zero, if evaluating a function */ + +/* reasons for skipping commands (see comment on breakcmd routine) */ +enum skipstate { + SKIPNONE = 0, /* not skipping */ + SKIPBREAK, /* break */ + SKIPCONT, /* continue */ + SKIPFUNC, /* return in a function */ + SKIPFILE /* return in a dot command */ +}; + +struct skipsave { + enum skipstate state; /* skipping or not */ + int count; /* when break or continue, how many */ +}; + +enum skipstate current_skipstate(void); +void save_skipstate(struct skipsave *); +void restore_skipstate(const struct skipsave *); +void stop_skipping(void); /* reset internal skipping state to SKIPNONE */ + +/* + * Only for use by reset() in init.c! + */ +void reset_eval(void); + +/* flags in argument to evaltree */ +#define EV_EXIT 0x01 /* exit after evaluating tree */ +#define EV_TESTED 0x02 /* exit status is checked; ignore -e flag */ +#define EV_BACKCMD 0x04 /* command executing within back quotes */ diff --git a/bin/sh/exec.c b/bin/sh/exec.c new file mode 100644 index 0000000..674beb8 --- /dev/null +++ b/bin/sh/exec.c @@ -0,0 +1,1183 @@ +/* $NetBSD: exec.c,v 1.53 2018/07/25 14:42:50 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)exec.c 8.4 (Berkeley) 6/8/95"; +#else +__RCSID("$NetBSD: exec.c,v 1.53 2018/07/25 14:42:50 kre Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <stdio.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 "init.h" +#include "mystring.h" +#include "show.h" +#include "jobs.h" +#include "alias.h" + + +#define CMDTABLESIZE 31 /* should be prime */ +#define ARB 1 /* actual size determined at run time */ + + + +struct tblentry { + struct tblentry *next; /* next entry in hash chain */ + union param param; /* definition of builtin function */ + short cmdtype; /* index identifying command */ + char rehash; /* if set, cd done since entry created */ + char fn_ln1; /* for functions, LINENO from 1 */ + int lineno; /* for functions abs LINENO of definition */ + char cmdname[ARB]; /* name of command */ +}; + + +STATIC struct tblentry *cmdtable[CMDTABLESIZE]; +STATIC int builtinloc = -1; /* index in path of %builtin, or -1 */ +int exerrno = 0; /* Last exec error */ + + +STATIC void tryexec(char *, char **, char **, int); +STATIC void printentry(struct tblentry *, int); +STATIC void addcmdentry(char *, struct cmdentry *); +STATIC void clearcmdentry(int); +STATIC struct tblentry *cmdlookup(const char *, int); +STATIC void delete_cmd_entry(void); + +#ifndef BSD +STATIC void execinterp(char **, char **); +#endif + + +extern const char *const parsekwd[]; + +/* + * Exec a program. Never returns. If you change this routine, you may + * have to change the find_command routine as well. + */ + +void +shellexec(char **argv, char **envp, const char *path, int idx, int vforked) +{ + char *cmdname; + int e; + + if (strchr(argv[0], '/') != NULL) { + tryexec(argv[0], argv, envp, vforked); + e = errno; + } else { + e = ENOENT; + while ((cmdname = padvance(&path, argv[0], 1)) != NULL) { + if (--idx < 0 && pathopt == NULL) { + tryexec(cmdname, argv, envp, vforked); + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + } + stunalloc(cmdname); + } + } + + /* Map to POSIX errors */ + switch (e) { + case EACCES: /* particularly this (unless no search perm) */ + /* + * should perhaps check if this EACCES is an exec() + * EACESS or a namei() EACESS - the latter should be 127 + * - but not today + */ + case EINVAL: /* also explicitly these */ + case ENOEXEC: + default: /* and anything else */ + exerrno = 126; + break; + + case ENOENT: /* these are the "pathname lookup failed" errors */ + case ELOOP: + case ENOTDIR: + case ENAMETOOLONG: + exerrno = 127; + break; + } + CTRACE(DBG_ERRS|DBG_CMDS|DBG_EVAL, + ("shellexec failed for %s, errno %d, vforked %d, suppressint %d\n", + argv[0], e, vforked, suppressint)); + exerror(EXEXEC, "%s: %s", argv[0], errmsg(e, E_EXEC)); + /* NOTREACHED */ +} + + +STATIC void +tryexec(char *cmd, char **argv, char **envp, int vforked) +{ + int e; +#ifndef BSD + char *p; +#endif + +#ifdef SYSV + do { + execve(cmd, argv, envp); + } while (errno == EINTR); +#else + execve(cmd, argv, envp); +#endif + e = errno; + if (e == ENOEXEC) { + if (vforked) { + /* We are currently vfork(2)ed, so raise an + * exception, and evalcommand will try again + * with a normal fork(2). + */ + exraise(EXSHELLPROC); + } +#ifdef DEBUG + VTRACE(DBG_CMDS, ("execve(cmd=%s) returned ENOEXEC\n", cmd)); +#endif + initshellproc(); + setinputfile(cmd, 0); + commandname = arg0 = savestr(argv[0]); +#ifndef BSD + pgetc(); pungetc(); /* fill up input buffer */ + p = parsenextc; + if (parsenleft > 2 && p[0] == '#' && p[1] == '!') { + argv[0] = cmd; + execinterp(argv, envp); + } +#endif + setparam(argv + 1); + exraise(EXSHELLPROC); + } + errno = e; +} + + +#ifndef BSD +/* + * Execute an interpreter introduced by "#!", for systems where this + * feature has not been built into the kernel. If the interpreter is + * the shell, return (effectively ignoring the "#!"). If the execution + * of the interpreter fails, exit. + * + * This code peeks inside the input buffer in order to avoid actually + * reading any input. It would benefit from a rewrite. + */ + +#define NEWARGS 5 + +STATIC void +execinterp(char **argv, char **envp) +{ + int n; + char *inp; + char *outp; + char c; + char *p; + char **ap; + char *newargs[NEWARGS]; + int i; + char **ap2; + char **new; + + n = parsenleft - 2; + inp = parsenextc + 2; + ap = newargs; + for (;;) { + while (--n >= 0 && (*inp == ' ' || *inp == '\t')) + inp++; + if (n < 0) + goto bad; + if ((c = *inp++) == '\n') + break; + if (ap == &newargs[NEWARGS]) +bad: error("Bad #! line"); + STARTSTACKSTR(outp); + do { + STPUTC(c, outp); + } while (--n >= 0 && (c = *inp++) != ' ' && c != '\t' && c != '\n'); + STPUTC('\0', outp); + n++, inp--; + *ap++ = grabstackstr(outp); + } + if (ap == newargs + 1) { /* if no args, maybe no exec is needed */ + p = newargs[0]; + for (;;) { + if (equal(p, "sh") || equal(p, "ash")) { + return; + } + while (*p != '/') { + if (*p == '\0') + goto break2; + p++; + } + p++; + } +break2:; + } + i = (char *)ap - (char *)newargs; /* size in bytes */ + if (i == 0) + error("Bad #! line"); + for (ap2 = argv ; *ap2++ != NULL ; ); + new = ckmalloc(i + ((char *)ap2 - (char *)argv)); + ap = newargs, ap2 = new; + while ((i -= sizeof (char **)) >= 0) + *ap2++ = *ap++; + ap = argv; + while (*ap2++ = *ap++); + shellexec(new, envp, pathval(), 0); + /* NOTREACHED */ +} +#endif + + + +/* + * 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 an option (indicated by + * a percent sign) appears in the path entry then the global variable + * pathopt will be set to point to it; otherwise pathopt will be set to + * NULL. + */ + +const char *pathopt; + +char * +padvance(const char **path, const char *name, int magic_percent) +{ + const char *p; + char *q; + const char *start; + int len; + + if (*path == NULL) + return NULL; + if (magic_percent) + magic_percent = '%'; + + start = *path; + for (p = start ; *p && *p != ':' && *p != magic_percent ; p++) + ; + len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */ + while (stackblocksize() < len) + growstackblock(); + q = stackblock(); + if (p != start) { + memcpy(q, start, p - start); + q += p - start; + if (q[-1] != '/') + *q++ = '/'; + } + strcpy(q, name); + pathopt = NULL; + if (*p == magic_percent) { + pathopt = ++p; + while (*p && *p != ':') + p++; + } + if (*p == ':') + *path = p + 1; + else + *path = NULL; + return grabstackstr(q + strlen(name) + 1); +} + + +/*** Command hashing code ***/ + + +int +hashcmd(int argc, char **argv) +{ + struct tblentry **pp; + struct tblentry *cmdp; + int c; + struct cmdentry entry; + char *name; + int allopt=0, bopt=0, fopt=0, ropt=0, sopt=0, uopt=0, verbose=0; + + while ((c = nextopt("bcfrsuv")) != '\0') + switch (c) { + case 'b': bopt = 1; break; + case 'c': uopt = 1; break; /* c == u */ + case 'f': fopt = 1; break; + case 'r': ropt = 1; break; + case 's': sopt = 1; break; + case 'u': uopt = 1; break; + case 'v': verbose = 1; break; + } + + if (ropt) + clearcmdentry(0); + + if (bopt == 0 && fopt == 0 && sopt == 0 && uopt == 0) + allopt = bopt = fopt = sopt = uopt = 1; + + if (*argptr == NULL) { + for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + switch (cmdp->cmdtype) { + case CMDNORMAL: + if (!uopt) + continue; + break; + case CMDBUILTIN: + if (!bopt) + continue; + break; + case CMDSPLBLTIN: + if (!sopt) + continue; + break; + case CMDFUNCTION: + if (!fopt) + continue; + break; + default: /* never happens */ + continue; + } + if (!allopt || verbose || + cmdp->cmdtype == CMDNORMAL) + printentry(cmdp, verbose); + } + } + return 0; + } + + while ((name = *argptr++) != NULL) { + if ((cmdp = cmdlookup(name, 0)) != NULL) { + switch (cmdp->cmdtype) { + case CMDNORMAL: + if (!uopt) + continue; + delete_cmd_entry(); + break; + case CMDBUILTIN: + if (!bopt) + continue; + if (builtinloc >= 0) + delete_cmd_entry(); + break; + case CMDSPLBLTIN: + if (!sopt) + continue; + break; + case CMDFUNCTION: + if (!fopt) + continue; + break; + } + } + find_command(name, &entry, DO_ERR, pathval()); + if (verbose) { + if (entry.cmdtype != CMDUNKNOWN) { /* if no error msg */ + cmdp = cmdlookup(name, 0); + if (cmdp != NULL) + printentry(cmdp, verbose); + } + flushall(); + } + } + return 0; +} + +STATIC void +printentry(struct tblentry *cmdp, int verbose) +{ + int idx; + const char *path; + char *name; + + switch (cmdp->cmdtype) { + case CMDNORMAL: + idx = cmdp->param.index; + path = pathval(); + do { + name = padvance(&path, cmdp->cmdname, 1); + stunalloc(name); + } while (--idx >= 0); + if (verbose) + out1fmt("Command from PATH[%d]: ", + cmdp->param.index); + out1str(name); + break; + case CMDSPLBLTIN: + if (verbose) + out1str("special "); + /* FALLTHROUGH */ + case CMDBUILTIN: + if (verbose) + out1str("builtin "); + out1fmt("%s", cmdp->cmdname); + break; + case CMDFUNCTION: + if (verbose) + out1str("function "); + out1fmt("%s", cmdp->cmdname); + if (verbose) { + struct procstat ps; + + INTOFF; + commandtext(&ps, getfuncnode(cmdp->param.func)); + INTON; + out1str("() { "); + out1str(ps.cmd); + out1str("; }"); + } + break; + default: + error("internal error: %s cmdtype %d", + cmdp->cmdname, cmdp->cmdtype); + } + if (cmdp->rehash) + out1c('*'); + out1c('\n'); +} + + + +/* + * Resolve a command name. If you change this routine, you may have to + * change the shellexec routine as well. + */ + +void +find_command(char *name, struct cmdentry *entry, int act, const char *path) +{ + struct tblentry *cmdp, loc_cmd; + int idx; + int prev; + char *fullname; + struct stat statb; + int e; + int (*bltin)(int,char **); + + /* If name contains a slash, don't use PATH or hash table */ + if (strchr(name, '/') != NULL) { + if (act & DO_ABS) { + while (stat(name, &statb) < 0) { +#ifdef SYSV + if (errno == EINTR) + continue; +#endif + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + entry->cmdtype = CMDUNKNOWN; + entry->u.index = -1; + return; + } + entry->cmdtype = CMDNORMAL; + entry->u.index = -1; + return; + } + entry->cmdtype = CMDNORMAL; + entry->u.index = 0; + return; + } + + if (path != pathval()) + act |= DO_ALTPATH; + + if (act & DO_ALTPATH && strstr(path, "%builtin") != NULL) + act |= DO_ALTBLTIN; + + /* If name is in the table, check answer will be ok */ + if ((cmdp = cmdlookup(name, 0)) != NULL) { + do { + switch (cmdp->cmdtype) { + case CMDNORMAL: + if (act & DO_ALTPATH) { + cmdp = NULL; + continue; + } + break; + case CMDFUNCTION: + if (act & DO_NOFUNC) { + cmdp = NULL; + continue; + } + break; + case CMDBUILTIN: + if ((act & DO_ALTBLTIN) || builtinloc >= 0) { + cmdp = NULL; + continue; + } + break; + } + /* if not invalidated by cd, we're done */ + if (cmdp->rehash == 0) + goto success; + } while (0); + } + + /* If %builtin not in path, check for builtin next */ + if ((act & DO_ALTPATH ? !(act & DO_ALTBLTIN) : builtinloc < 0) && + (bltin = find_builtin(name)) != 0) + goto builtin_success; + + /* We have to search path. */ + prev = -1; /* where to start */ + if (cmdp) { /* doing a rehash */ + if (cmdp->cmdtype == CMDBUILTIN) + prev = builtinloc; + else + prev = cmdp->param.index; + } + + e = ENOENT; + idx = -1; +loop: + while ((fullname = padvance(&path, name, 1)) != NULL) { + stunalloc(fullname); + idx++; + if (pathopt) { + if (prefix("builtin", pathopt)) { + if ((bltin = find_builtin(name)) == 0) + goto loop; + goto builtin_success; + } else if (prefix("func", pathopt)) { + /* handled below */ + } else { + /* ignore unimplemented options */ + goto loop; + } + } + /* if rehash, don't redo absolute path names */ + if (fullname[0] == '/' && idx <= prev) { + if (idx < prev) + goto loop; + VTRACE(DBG_CMDS, ("searchexec \"%s\": no change\n", + name)); + goto success; + } + while (stat(fullname, &statb) < 0) { +#ifdef SYSV + if (errno == EINTR) + continue; +#endif + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + goto loop; + } + e = EACCES; /* if we fail, this will be the error */ + if (!S_ISREG(statb.st_mode)) + goto loop; + if (pathopt) { /* this is a %func directory */ + char *endname; + + if (act & DO_NOFUNC) + goto loop; + endname = fullname + strlen(fullname) + 1; + grabstackstr(endname); + readcmdfile(fullname); + if ((cmdp = cmdlookup(name, 0)) == NULL || + cmdp->cmdtype != CMDFUNCTION) + error("%s not defined in %s", name, fullname); + ungrabstackstr(fullname, endname); + goto success; + } +#ifdef notdef + /* XXX this code stops root executing stuff, and is buggy + if you need a group from the group list. */ + 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 + VTRACE(DBG_CMDS, ("searchexec \"%s\" returns \"%s\"\n", name, + fullname)); + INTOFF; + if (act & DO_ALTPATH) { + /* + * this should be a grabstackstr() but is not needed: + * fullname is no longer needed for anything + stalloc(strlen(fullname) + 1); + */ + cmdp = &loc_cmd; + } else + cmdp = cmdlookup(name, 1); + cmdp->cmdtype = CMDNORMAL; + cmdp->param.index = idx; + INTON; + goto success; + } + + /* We failed. If there was an entry for this command, delete it */ + if (cmdp) + delete_cmd_entry(); + if (act & DO_ERR) + outfmt(out2, "%s: %s\n", name, errmsg(e, E_EXEC)); + entry->cmdtype = CMDUNKNOWN; + return; + +builtin_success: + INTOFF; + if (act & DO_ALTPATH) + cmdp = &loc_cmd; + else + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) + /* DO_NOFUNC must have been set */ + cmdp = &loc_cmd; + cmdp->cmdtype = CMDBUILTIN; + cmdp->param.bltin = bltin; + INTON; +success: + if (cmdp) { + cmdp->rehash = 0; + entry->cmdtype = cmdp->cmdtype; + entry->lineno = cmdp->lineno; + entry->lno_frel = cmdp->fn_ln1; + entry->u = cmdp->param; + } else + entry->cmdtype = CMDUNKNOWN; +} + + + +/* + * Search the table of builtin commands. + */ + +int +(*find_builtin(char *name))(int, char **) +{ + const struct builtincmd *bp; + + for (bp = builtincmd ; bp->name ; bp++) { + if (*bp->name == *name + && (*name == '%' || equal(bp->name, name))) + return bp->builtin; + } + return 0; +} + +int +(*find_splbltin(char *name))(int, char **) +{ + const struct builtincmd *bp; + + for (bp = splbltincmd ; bp->name ; bp++) { + if (*bp->name == *name && equal(bp->name, name)) + return bp->builtin; + } + return 0; +} + +/* + * At shell startup put special builtins into hash table. + * ensures they are executed first (see posix). + * We stop functions being added with the same name + * (as they are impossible to call) + */ + +void +hash_special_builtins(void) +{ + const struct builtincmd *bp; + struct tblentry *cmdp; + + for (bp = splbltincmd ; bp->name ; bp++) { + cmdp = cmdlookup(bp->name, 1); + cmdp->cmdtype = CMDSPLBLTIN; + cmdp->param.bltin = bp->builtin; + } +} + + + +/* + * Called when a cd is done. Marks all commands so the next time they + * are executed they will be rehashed. + */ + +void +hashcd(void) +{ + struct tblentry **pp; + struct tblentry *cmdp; + + for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL + || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)) + cmdp->rehash = 1; + } + } +} + + + +/* + * Fix command hash table when PATH changed. + * 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) +{ + const char *old, *new; + int idx; + int firstchange; + int bltin; + + old = pathval(); + new = newval; + firstchange = 9999; /* assume no change */ + idx = 0; + bltin = -1; + for (;;) { + if (*old != *new) { + firstchange = idx; + if ((*old == '\0' && *new == ':') + || (*old == ':' && *new == '\0')) + firstchange++; + old = new; /* ignore subsequent differences */ + } + if (*new == '\0') + break; + if (*new == '%' && bltin < 0 && prefix("builtin", new + 1)) + bltin = idx; + if (*new == ':') { + idx++; + } + new++, old++; + } + if (builtinloc < 0 && bltin >= 0) + builtinloc = bltin; /* zap builtins */ + if (builtinloc >= 0 && bltin < 0) + firstchange = 0; + clearcmdentry(firstchange); + builtinloc = bltin; +} + + +/* + * Clear out command entries. The argument specifies the first entry in + * PATH which has changed. + */ + +STATIC void +clearcmdentry(int firstchange) +{ + 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 && + cmdp->param.index >= firstchange) + || (cmdp->cmdtype == CMDBUILTIN && + builtinloc >= firstchange)) { + *pp = cmdp->next; + ckfree(cmdp); + } else { + pp = &cmdp->next; + } + } + } + INTON; +} + + +/* + * Delete all functions. + */ + +#ifdef mkinit +MKINIT void deletefuncs(void); +MKINIT void hash_special_builtins(void); + +INIT { + hash_special_builtins(); +} + +SHELLPROC { + deletefuncs(); +} +#endif + +void +deletefuncs(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 == CMDFUNCTION) { + *pp = cmdp->next; + freefunc(cmdp->param.func); + ckfree(cmdp); + } else { + pp = &cmdp->next; + } + } + } + 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. + */ + +struct tblentry **lastcmdentry; + + +STATIC struct tblentry * +cmdlookup(const char *name, int add) +{ + int hashval; + const char *p; + struct tblentry *cmdp; + struct tblentry **pp; + + p = name; + hashval = *p << 4; + while (*p) + hashval += *p++; + hashval &= 0x7FFF; + 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; + cmdp = *pp = ckmalloc(sizeof (struct tblentry) - ARB + + strlen(name) + 1); + cmdp->next = NULL; + cmdp->cmdtype = CMDUNKNOWN; + cmdp->rehash = 0; + strcpy(cmdp->cmdname, name); + 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; +} + + + +#ifdef notdef +void +getcmdentry(char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp = cmdlookup(name, 0); + + if (cmdp) { + entry->u = cmdp->param; + entry->cmdtype = cmdp->cmdtype; + } else { + entry->cmdtype = CMDUNKNOWN; + entry->u.index = 0; + } +} +#endif + + +/* + * Add a new command entry, replacing any existing command entry for + * the same name - except special builtins. + */ + +STATIC void +addcmdentry(char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp; + + INTOFF; + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype != CMDSPLBLTIN) { + if (cmdp->cmdtype == CMDFUNCTION) + unreffunc(cmdp->param.func); + cmdp->cmdtype = entry->cmdtype; + cmdp->lineno = entry->lineno; + cmdp->fn_ln1 = entry->lno_frel; + cmdp->param = entry->u; + } + INTON; +} + + +/* + * Define a shell function. + */ + +void +defun(char *name, union node *func, int lineno) +{ + struct cmdentry entry; + + INTOFF; + entry.cmdtype = CMDFUNCTION; + entry.lineno = lineno; + entry.lno_frel = fnline1; + entry.u.func = copyfunc(func); + addcmdentry(name, &entry); + INTON; +} + + +/* + * Delete a function if it exists. + */ + +int +unsetfunc(char *name) +{ + struct tblentry *cmdp; + + if ((cmdp = cmdlookup(name, 0)) != NULL && + cmdp->cmdtype == CMDFUNCTION) { + unreffunc(cmdp->param.func); + delete_cmd_entry(); + } + return 0; +} + +/* + * Locate and print what a word is... + * also used for 'command -[v|V]' + */ + +int +typecmd(int argc, char **argv) +{ + struct cmdentry entry; + struct tblentry *cmdp; + const char * const *pp; + struct alias *ap; + int err = 0; + char *arg; + int c; + int V_flag = 0; + int v_flag = 0; + int p_flag = 0; + + while ((c = nextopt("vVp")) != 0) { + switch (c) { + case 'v': v_flag = 1; break; + case 'V': V_flag = 1; break; + case 'p': p_flag = 1; break; + } + } + + if (argv[0][0] != 'c' && v_flag | V_flag | p_flag) + error("usage: %s name...", argv[0]); + + if (v_flag && V_flag) + error("-v and -V cannot both be specified"); + + if (*argptr == NULL) + error("usage: %s%s name ...", argv[0], + argv[0][0] == 'c' ? " [-p] [-v|-V]" : ""); + + while ((arg = *argptr++)) { + if (!v_flag) + out1str(arg); + /* First look at the keywords */ + for (pp = parsekwd; *pp; pp++) + if (**pp == *arg && equal(*pp, arg)) + break; + + if (*pp) { + if (v_flag) + out1fmt("%s\n", arg); + else + out1str(" is a shell keyword\n"); + continue; + } + + /* Then look at the aliases */ + if ((ap = lookupalias(arg, 1)) != NULL) { + int ml = 0; + + if (!v_flag) { + out1str(" is an alias "); + if (strchr(ap->val, '\n')) { + out1str("(multiline)...\n"); + ml = 1; + } else + out1str("for: "); + } + out1fmt("%s\n", ap->val); + if (ml && *argptr != NULL) + out1c('\n'); + continue; + } + + /* Then check if it is a tracked alias */ + if (!p_flag && (cmdp = cmdlookup(arg, 0)) != NULL) { + entry.cmdtype = cmdp->cmdtype; + entry.u = cmdp->param; + } else { + cmdp = NULL; + /* Finally use brute force */ + find_command(arg, &entry, DO_ABS, + p_flag ? syspath() + 5 : pathval()); + } + + switch (entry.cmdtype) { + case CMDNORMAL: { + if (strchr(arg, '/') == NULL) { + const char *path; + char *name; + int j = entry.u.index; + + path = p_flag ? syspath() + 5 : pathval(); + + do { + name = padvance(&path, arg, 1); + stunalloc(name); + } while (--j >= 0); + if (!v_flag) + out1fmt(" is%s ", + cmdp ? " a tracked alias for" : ""); + out1fmt("%s\n", name); + } else { + if (access(arg, X_OK) == 0) { + if (!v_flag) + out1fmt(" is "); + out1fmt("%s\n", arg); + } else { + if (!v_flag) + out1fmt(": %s\n", + strerror(errno)); + else + err = 126; + } + } + break; + } + case CMDFUNCTION: + if (!v_flag) + out1str(" is a shell function\n"); + else + out1fmt("%s\n", arg); + break; + + case CMDBUILTIN: + if (!v_flag) + out1str(" is a shell builtin\n"); + else + out1fmt("%s\n", arg); + break; + + case CMDSPLBLTIN: + if (!v_flag) + out1str(" is a special shell builtin\n"); + else + out1fmt("%s\n", arg); + break; + + default: + if (!v_flag) + out1str(": not found\n"); + err = 127; + break; + } + } + return err; +} diff --git a/bin/sh/exec.h b/bin/sh/exec.h new file mode 100644 index 0000000..90beba1 --- /dev/null +++ b/bin/sh/exec.h @@ -0,0 +1,78 @@ +/* $NetBSD: exec.h,v 1.27 2018/06/22 11:04:55 kre Exp $ */ + +/*- + * 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 + */ + +/* values of cmdtype */ +#define CMDUNKNOWN -1 /* no entry in table for command */ +#define CMDNORMAL 0 /* command is an executable program */ +#define CMDFUNCTION 1 /* command is a shell function */ +#define CMDBUILTIN 2 /* command is a shell builtin */ +#define CMDSPLBLTIN 3 /* command is a special shell builtin */ + + +struct cmdentry { + short cmdtype; + short lno_frel; /* for functions: Line numbers count from 1 */ + int lineno; /* for functions: Abs line number of defn */ + union param { + int index; + int (*bltin)(int, char**); + struct funcdef *func; + } u; +}; + + +/* action to find_command() */ +#define DO_ERR 0x01 /* prints errors */ +#define DO_ABS 0x02 /* checks absolute paths */ +#define DO_NOFUNC 0x04 /* don't return shell functions, for command */ +#define DO_ALTPATH 0x08 /* using alternate path */ +#define DO_ALTBLTIN 0x20 /* %builtin in alt. path */ + +extern const char *pathopt; /* set by padvance */ + +void shellexec(char **, char **, const char *, int, int) __dead; +char *padvance(const char **, const char *, int); +void find_command(char *, struct cmdentry *, int, const char *); +int (*find_builtin(char *))(int, char **); +int (*find_splbltin(char *))(int, char **); +void hashcd(void); +void changepath(const char *); +void deletefuncs(void); +void getcmdentry(char *, struct cmdentry *); +union node; +void defun(char *, union node *, int); +int unsetfunc(char *); +void hash_special_builtins(void); diff --git a/bin/sh/expand.c b/bin/sh/expand.c new file mode 100644 index 0000000..955185b --- /dev/null +++ b/bin/sh/expand.c @@ -0,0 +1,2125 @@ +/* $NetBSD: expand.c,v 1.129 2018/12/03 06:41:30 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95"; +#else +__RCSID("$NetBSD: expand.c,v 1.129 2018/12/03 06:41:30 kre Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <errno.h> +#include <dirent.h> +#include <unistd.h> +#include <pwd.h> +#include <limits.h> +#include <stdlib.h> +#include <stdio.h> +#include <wctype.h> +#include <wchar.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 "arithmetic.h" +#include "parser.h" +#include "jobs.h" +#include "options.h" +#include "builtins.h" +#include "var.h" +#include "input.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "show.h" + +/* + * Structure specifying which parts of the string should be searched + * for IFS characters. + */ + +struct ifsregion { + struct ifsregion *next; /* next region in list */ + int begoff; /* offset of start of region */ + int endoff; /* offset of end of region */ + int inquotes; /* search for nul bytes only */ +}; + + +char *expdest; /* output of current string */ +struct nodelist *argbackq; /* list of back quote expressions */ +struct ifsregion ifsfirst; /* first struct in list of ifs regions */ +struct ifsregion *ifslastp; /* last struct in list */ +struct arglist exparg; /* holds expanded arg list */ + +STATIC const char *argstr(const char *, int); +STATIC const char *exptilde(const char *, int); +STATIC void expbackq(union node *, int, int); +STATIC const char *expari(const char *); +STATIC int subevalvar(const char *, const char *, int, int, int); +STATIC int subevalvar_trim(const char *, int, int, int, int, int); +STATIC const char *evalvar(const char *, int); +STATIC int varisset(const char *, int); +STATIC void varvalue(const char *, int, int, int); +STATIC void recordregion(int, int, int); +STATIC void removerecordregions(int); +STATIC void ifsbreakup(char *, struct arglist *); +STATIC void ifsfree(void); +STATIC void expandmeta(struct strlist *, int); +STATIC void expmeta(char *, char *); +STATIC void addfname(char *); +STATIC struct strlist *expsort(struct strlist *); +STATIC struct strlist *msort(struct strlist *, int); +STATIC int patmatch(const char *, const char *, int); +STATIC char *cvtnum(int, char *); +static int collate_range_cmp(wchar_t, wchar_t); +STATIC void add_args(struct strlist *); +STATIC void rmescapes_nl(char *); + +#ifdef DEBUG +#define NULLTERM_4_TRACE(p) STACKSTRNUL(p) +#else +#define NULLTERM_4_TRACE(p) do { /* nothing */ } while (/*CONSTCOND*/0) +#endif + +#define IS_BORING(_ch) \ + ((_ch) == CTLQUOTEMARK || (_ch) == CTLQUOTEEND || (_ch) == CTLNONL) +#define SKIP_BORING(p) \ + do { \ + char _ch; \ + \ + while ((_ch = *(p)), IS_BORING(_ch)) \ + (p)++; \ + } while (0) + +/* + * Expand shell variables and backquotes inside a here document. + */ + +void +expandhere(union node *arg, int fd) +{ + + herefd = fd; + expandarg(arg, NULL, 0); + xwrite(fd, stackblock(), expdest - stackblock()); +} + + +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)); +} + +/* + * Perform variable substitution and command substitution on an argument, + * placing the resulting list of arguments in arglist. If EXP_FULL is true, + * perform splitting and file name expansion. When arglist is NULL, perform + * here document expansion. + */ + +void +expandarg(union node *arg, struct arglist *arglist, int flag) +{ + struct strlist *sp; + char *p; + + CTRACE(DBG_EXPAND, ("expandarg(fl=%#x)\n", flag)); + if (fflag) /* no filename expandsion */ + flag &= ~EXP_GLOB; + + argbackq = arg->narg.backquote; + STARTSTACKSTR(expdest); + ifsfirst.next = NULL; + ifslastp = NULL; + line_number = arg->narg.lineno; + argstr(arg->narg.text, flag); + if (arglist == NULL) { + STACKSTRNUL(expdest); + CTRACE(DBG_EXPAND, ("expandarg: no arglist, done (%d) \"%s\"\n", + expdest - stackblock(), stackblock())); + return; /* here document expanded */ + } + STPUTC('\0', expdest); + CTRACE(DBG_EXPAND, ("expandarg: arglist got (%d) \"%s\"\n", + expdest - stackblock() - 1, stackblock())); + p = grabstackstr(expdest); + exparg.lastp = &exparg.list; + /* + * TODO - EXP_REDIR + */ + if (flag & EXP_SPLIT) { + ifsbreakup(p, &exparg); + *exparg.lastp = NULL; + exparg.lastp = &exparg.list; + if (flag & EXP_GLOB) + expandmeta(exparg.list, flag); + else + add_args(exparg.list); + } else { + if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */ + rmescapes(p); + sp = stalloc(sizeof(*sp)); + sp->text = p; + *exparg.lastp = sp; + exparg.lastp = &sp->next; + } + ifsfree(); + *exparg.lastp = NULL; + if (exparg.list) { + *arglist->lastp = exparg.list; + arglist->lastp = exparg.lastp; + } +} + + + +/* + * Perform variable and command substitution. + * If EXP_GLOB is set, output CTLESC characters to allow for further processing. + * If EXP_SPLIT is set, remember location of result for later, + * Otherwise treat $@ like $* since no splitting will be performed. + */ + +STATIC const char * +argstr(const char *p, int flag) +{ + char c; + const int quotes = flag & EXP_QNEEDED; /* do CTLESC */ + int firsteq = 1; + const char *ifs = NULL; + int ifs_split = EXP_IFS_SPLIT; + + if (flag & EXP_IFS_SPLIT) + ifs = ifsval(); + + CTRACE(DBG_EXPAND, ("argstr(\"%s\", %#x) quotes=%#x\n", p,flag,quotes)); + + if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE))) + p = exptilde(p, flag); + for (;;) { + switch (c = *p++) { + case '\0': + NULLTERM_4_TRACE(expdest); + VTRACE(DBG_EXPAND, ("argstr returning at \"\" " + "added \"%s\" to expdest\n", stackblock())); + return p - 1; + case CTLENDVAR: /* end of expanding yyy in ${xxx-yyy} */ + case CTLENDARI: /* end of a $(( )) string */ + NULLTERM_4_TRACE(expdest); + VTRACE(DBG_EXPAND, ("argstr returning at \"%.6s\"..." + " after %2.2X; added \"%s\" to expdest\n", + p, (c&0xff), stackblock())); + return p; + case CTLQUOTEMARK: + /* "$@" syntax adherence hack */ + if (p[0] == CTLVAR && p[1] & VSQUOTE && + p[2] == '@' && p[3] == '=') + break; + if ((flag & EXP_SPLIT) != 0) + STPUTC(c, expdest); + ifs_split = 0; + break; + case CTLNONL: + if (flag & EXP_NL) + STPUTC(c, expdest); + line_number++; + break; + case CTLCNL: + STPUTC('\n', expdest); /* no line_number++ */ + break; + case CTLQUOTEEND: + if ((flag & EXP_SPLIT) != 0) + STPUTC(c, expdest); + ifs_split = EXP_IFS_SPLIT; + break; + case CTLESC: + if (quotes) + STPUTC(c, expdest); + c = *p++; + STPUTC(c, expdest); + if (c == '\n') /* should not happen, but ... */ + line_number++; + break; + case CTLVAR: { +#ifdef DEBUG + unsigned int pos = expdest - stackblock(); + NULLTERM_4_TRACE(expdest); +#endif + p = evalvar(p, (flag & ~EXP_IFS_SPLIT) | (flag & ifs_split)); + NULLTERM_4_TRACE(expdest); + VTRACE(DBG_EXPAND, ("argstr evalvar " + "added %zd \"%s\" to expdest\n", + (size_t)(expdest - (stackblock() + pos)), + stackblock() + pos)); + break; + } + case CTLBACKQ: + case CTLBACKQ|CTLQUOTE: { +#ifdef DEBUG + unsigned int pos = expdest - stackblock(); +#endif + expbackq(argbackq->n, c & CTLQUOTE, flag); + argbackq = argbackq->next; + NULLTERM_4_TRACE(expdest); + VTRACE(DBG_EXPAND, ("argstr expbackq added \"%s\" " + "to expdest\n", stackblock() + pos)); + break; + } + case CTLARI: { +#ifdef DEBUG + unsigned int pos = expdest - stackblock(); +#endif + p = expari(p); + NULLTERM_4_TRACE(expdest); + VTRACE(DBG_EXPAND, ("argstr expari " + "+ \"%s\" to expdest p=\"%.5s...\"\n", + stackblock() + pos, p)); + break; + } + case ':': + case '=': + /* + * sort of a hack - expand tildes in variable + * assignments (after the first '=' and after ':'s). + */ + STPUTC(c, expdest); + if (flag & EXP_VARTILDE && *p == '~') { + if (c == '=') { + if (firsteq) + firsteq = 0; + else + break; + } + p = exptilde(p, flag); + } + break; + default: + if (c == '\n') + line_number++; + STPUTC(c, expdest); + if (flag & ifs_split && strchr(ifs, c) != NULL) { + /* We need to get the output split here... */ + recordregion(expdest - stackblock() - 1, + expdest - stackblock(), 0); + } + break; + } + } +} + +STATIC const char * +exptilde(const char *p, int flag) +{ + char c; + const char *startp = p; + struct passwd *pw; + const char *home; + const int quotes = flag & EXP_QNEEDED; + char *user; + struct stackmark smark; +#ifdef DEBUG + unsigned int offs = expdest - stackblock(); +#endif + + setstackmark(&smark); + (void) grabstackstr(expdest); + user = stackblock(); /* we will just borrow top of stack */ + + while ((c = *++p) != '\0') { + switch(c) { + case CTLESC: /* any of these occurring */ + case CTLVAR: /* means ~ expansion */ + case CTLBACKQ: /* does not happen at all */ + case CTLBACKQ | CTLQUOTE: + case CTLARI: /* just leave original unchanged */ + case CTLENDARI: + case CTLQUOTEMARK: + case '\n': + popstackmark(&smark); + return (startp); + case CTLNONL: + continue; + case ':': + if (!posix || flag & EXP_VARTILDE) + goto done; + break; + case CTLENDVAR: + case '/': + goto done; + } + STPUTC(c, user); + } + done: + STACKSTRNUL(user); + user = stackblock(); /* to start of collected username */ + + CTRACE(DBG_EXPAND, ("exptilde, found \"~%s\"", user)); + if (*user == '\0') { + home = lookupvar("HOME"); + /* + * if HOME is unset, results are unspecified... + * we used to just leave the ~ unchanged, but + * (some) other shells do ... and this seems more useful. + */ + if (home == NULL && (pw = getpwuid(getuid())) != NULL) + home = pw->pw_dir; + } else if ((pw = getpwnam(user)) == NULL) { + /* + * If user does not exist, results are undefined. + * so we can abort() here if we want, but let's not! + */ + home = NULL; + } else + home = pw->pw_dir; + + VTRACE(DBG_EXPAND, (" ->\"%s\"", home ? home : "<<NULL>>")); + popstackmark(&smark); /* now expdest is valid again */ + + /* + * Posix XCU 2.6.1: The value of $HOME (for ~) or the initial + * working directory from getpwnam() for ~user + * Nothing there about "except if a null string". So do what it wants. + */ + if (home == NULL /* || *home == '\0' */) { + CTRACE(DBG_EXPAND, (": returning unused \"%s\"\n", startp)); + return startp; + } while ((c = *home++) != '\0') { + if (quotes && NEEDESC(c)) + STPUTC(CTLESC, expdest); + STPUTC(c, expdest); + } + CTRACE(DBG_EXPAND, (": added %d \"%.*s\" returning \"%s\"\n", + expdest - stackblock() - offs, expdest - stackblock() - offs, + stackblock() + offs, p)); + + return (p); +} + + +STATIC void +removerecordregions(int endoff) +{ + + VTRACE(DBG_EXPAND, ("removerecordregions(%d):", endoff)); + if (ifslastp == NULL) { + VTRACE(DBG_EXPAND, (" none\n", endoff)); + return; + } + + if (ifsfirst.endoff > endoff) { + VTRACE(DBG_EXPAND, (" first(%d)", ifsfirst.endoff)); + while (ifsfirst.next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = ifsfirst.next->next; + ckfree(ifsfirst.next); + ifsfirst.next = ifsp; + INTON; + } + if (ifsfirst.begoff > endoff) + ifslastp = NULL; + else { + VTRACE(DBG_EXPAND,("->(%d,%d)",ifsfirst.begoff,endoff)); + ifslastp = &ifsfirst; + ifsfirst.endoff = endoff; + } + VTRACE(DBG_EXPAND, ("\n")); + return; + } + + ifslastp = &ifsfirst; + while (ifslastp->next && ifslastp->next->begoff < endoff) + ifslastp=ifslastp->next; + VTRACE(DBG_EXPAND, (" found(%d,%d)", ifslastp->begoff,ifslastp->endoff)); + while (ifslastp->next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = ifslastp->next->next; + ckfree(ifslastp->next); + ifslastp->next = ifsp; + INTON; + } + if (ifslastp->endoff > endoff) + ifslastp->endoff = endoff; + VTRACE(DBG_EXPAND, ("->(%d,%d)", ifslastp->begoff,ifslastp->endoff)); +} + + +/* + * Expand arithmetic expression. + * + * In this incarnation, we start at the beginning (yes, "Let's start at the + * very beginning. A very good place to start.") and collect the expression + * until the end - which means expanding anything contained within. + * + * Fortunately, argstr() just happens to do that for us... + */ +STATIC const char * +expari(const char *p) +{ + char *q, *start; + intmax_t result; + int adjustment; + int begoff; + int quoted; + struct stackmark smark; + + /* ifsfree(); */ + + /* + * SPACE_NEEDED is enough for all possible digits (rounded up) + * plus possible "-", and the terminating '\0', hence, plus 2 + * + * The calculation produces the number of bytes needed to + * represent the biggest possible value, in octal. We only + * generate decimal, which takes (often) less digits (never more) + * so this is safe, if occasionally slightly wasteful. + */ +#define SPACE_NEEDED ((int)((sizeof(intmax_t) * CHAR_BIT + 2) / 3 + 2)) + + quoted = *p++ == '"'; + begoff = expdest - stackblock(); + VTRACE(DBG_EXPAND, ("expari%s: \"%s\" begoff %d\n", + quoted ? "(quoted)" : "", p, begoff)); + + p = argstr(p, EXP_NL); /* expand $(( )) string */ + STPUTC('\0', expdest); + start = stackblock() + begoff; + + removerecordregions(begoff); /* nothing there is kept */ + rmescapes_nl(start); /* convert CRTNONL back into \n's */ + + setstackmark(&smark); + q = grabstackstr(expdest); /* keep the expression while eval'ing */ + result = arith(start, line_number); + popstackmark(&smark); /* return the stack to before grab */ + + start = stackblock() + begoff; /* block may have moved */ + adjustment = expdest - start; + STADJUST(-adjustment, expdest); /* remove the argstr() result */ + + CHECKSTRSPACE(SPACE_NEEDED, expdest); /* nb: stack block might move */ + fmtstr(expdest, SPACE_NEEDED, "%"PRIdMAX, result); + + for (q = expdest; *q++ != '\0'; ) /* find end of what we added */ + ; + + if (quoted == 0) /* allow weird splitting */ + recordregion(begoff, begoff + q - 1 - expdest, 0); + adjustment = q - expdest - 1; + STADJUST(adjustment, expdest); /* move expdest to end */ + VTRACE(DBG_EXPAND, ("expari: adding %d \"%s\", returning \"%.5s...\"\n", + adjustment, stackblock() + begoff, p)); + + return p; +} + + +/* + * Expand stuff in backwards quotes (these days, any command substitution). + */ + +STATIC void +expbackq(union node *cmd, int quoted, int flag) +{ + struct backcmd in; + int i; + char buf[128]; + char *p; + char *dest = expdest; /* expdest may be reused by eval, use an alt */ + struct ifsregion saveifs, *savelastp; + struct nodelist *saveargbackq; + char lastc; + int startloc = dest - stackblock(); + int saveherefd; + const int quotes = flag & EXP_QNEEDED; + int nnl; + struct stackmark smark; + + VTRACE(DBG_EXPAND, ("expbackq( ..., q=%d flag=%#x) have %d\n", + quoted, flag, startloc)); + INTOFF; + saveifs = ifsfirst; + savelastp = ifslastp; + saveargbackq = argbackq; + saveherefd = herefd; + herefd = -1; + + setstackmark(&smark); /* preserve the stack */ + p = grabstackstr(dest); /* save what we have there currently */ + evalbackcmd(cmd, &in); /* evaluate the $( ) tree (using stack) */ + popstackmark(&smark); /* and return stack to when we entered */ + + ifsfirst = saveifs; + ifslastp = savelastp; + argbackq = saveargbackq; + herefd = saveherefd; + + p = in.buf; /* now extract the results */ + nnl = 0; /* dropping trailing \n's */ + for (;;) { + if (--in.nleft < 0) { + if (in.fd < 0) + break; + INTON; + while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR) + continue; + INTOFF; + VTRACE(DBG_EXPAND, ("expbackq: read returns %d\n", i)); + if (i <= 0) + break; + p = buf; + in.nleft = i - 1; + } + lastc = *p++; + if (lastc != '\0') { + if (lastc == '\n') /* don't save \n yet */ + nnl++; /* it might be trailing */ + else { + /* + * We have something other than \n + * + * Before saving it, we need to insert + * any \n's that we have just skipped. + */ + + /* XXX + * this hack is just because our + * CHECKSTRSPACE() is lazy, and only + * ever grows the stack once, even + * if that does not allocate the space + * we requested. ie: safe for small + * requests, but not large ones. + * FIXME someday... + */ + if (nnl < 20) { + CHECKSTRSPACE(nnl + 2, dest); + while (nnl > 0) { + nnl--; + USTPUTC('\n', dest); + } + } else { + /* The slower, safer, way */ + while (nnl > 0) { + nnl--; + STPUTC('\n', dest); + } + CHECKSTRSPACE(2, dest); + } + if (quotes && quoted && NEEDESC(lastc)) + USTPUTC(CTLESC, dest); + USTPUTC(lastc, dest); + } + } + } + + if (in.fd >= 0) + close(in.fd); + if (in.buf) + ckfree(in.buf); + if (in.jp) + back_exitstatus = waitforjob(in.jp); + if (quoted == 0) + recordregion(startloc, dest - stackblock(), 0); + CTRACE(DBG_EXPAND, ("evalbackq: size=%d: \"%.*s\"\n", + (int)((dest - stackblock()) - startloc), + (int)((dest - stackblock()) - startloc), + stackblock() + startloc)); + + expdest = dest; /* all done, expdest is all ours again */ + INTON; +} + + +STATIC int +subevalvar(const char *p, const char *str, int subtype, int startloc, + int varflags) +{ + char *startp; + int saveherefd = herefd; + struct nodelist *saveargbackq = argbackq; + int amount; + + herefd = -1; + VTRACE(DBG_EXPAND, ("subevalvar(%d) \"%.20s\" ${%.*s} sloc=%d vf=%x\n", + subtype, p, p-str, str, startloc, varflags)); + argstr(p, subtype == VSASSIGN ? EXP_VARTILDE : EXP_TILDE); + STACKSTRNUL(expdest); + herefd = saveherefd; + argbackq = saveargbackq; + startp = stackblock() + startloc; + + switch (subtype) { + case VSASSIGN: + setvar(str, startp, 0); + amount = startp - expdest; /* remove what argstr added */ + STADJUST(amount, expdest); + varflags &= ~VSNUL; /*XXX Huh? What's that achieve? */ + return 1; /* go back and eval var again */ + + case VSQUESTION: + if (*p != CTLENDVAR) { + outfmt(&errout, "%s\n", startp); + error(NULL); + } + error("%.*s: parameter %snot set", + (int)(p - str - 1), + str, (varflags & VSNUL) ? "null or " + : nullstr); + /* NOTREACHED */ + + default: + abort(); + } +} + +STATIC int +subevalvar_trim(const char *p, int strloc, int subtype, int startloc, + int varflags, int quotes) +{ + char *startp; + char *str; + char *loc = NULL; + char *q; + int c = 0; + int saveherefd = herefd; + struct nodelist *saveargbackq = argbackq; + int amount; + + herefd = -1; + switch (subtype) { + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + break; + default: + abort(); + break; + } + + VTRACE(DBG_EXPAND, + ("subevalvar_trim(\"%.9s\", STR@%d, SUBT=%d, start@%d, vf=%x, q=%x)\n", + p, strloc, subtype, startloc, varflags, quotes)); + + argstr(p, (varflags & (VSQUOTE|VSPATQ)) == VSQUOTE ? 0 : EXP_CASE); + STACKSTRNUL(expdest); + herefd = saveherefd; + argbackq = saveargbackq; + startp = stackblock() + startloc; + str = stackblock() + strloc; + + switch (subtype) { + + case VSTRIMLEFT: + for (loc = startp; loc < str; loc++) { + c = *loc; + *loc = '\0'; + if (patmatch(str, startp, quotes)) + goto recordleft; + *loc = c; + if (quotes && *loc == CTLESC) + loc++; + } + return 0; + + case VSTRIMLEFTMAX: + for (loc = str - 1; loc >= startp;) { + c = *loc; + *loc = '\0'; + if (patmatch(str, startp, quotes)) + goto recordleft; + *loc = c; + loc--; + if (quotes && loc > startp && + *(loc - 1) == CTLESC) { + for (q = startp; q < loc; q++) + if (*q == CTLESC) + q++; + if (q > loc) + loc--; + } + } + return 0; + + case VSTRIMRIGHT: + for (loc = str - 1; loc >= startp;) { + if (patmatch(str, loc, quotes)) + goto recordright; + loc--; + if (quotes && loc > startp && + *(loc - 1) == CTLESC) { + for (q = startp; q < loc; q++) + if (*q == CTLESC) + q++; + if (q > loc) + loc--; + } + } + return 0; + + case VSTRIMRIGHTMAX: + for (loc = startp; loc < str - 1; loc++) { + if (patmatch(str, loc, quotes)) + goto recordright; + if (quotes && *loc == CTLESC) + loc++; + } + return 0; + + default: + abort(); + } + + recordleft: + *loc = c; + amount = ((str - 1) - (loc - startp)) - expdest; + STADJUST(amount, expdest); + while (loc != str - 1) + *startp++ = *loc++; + return 1; + + recordright: + amount = loc - expdest; + STADJUST(amount, expdest); + STPUTC('\0', expdest); + STADJUST(-1, expdest); + return 1; +} + + +/* + * Expand a variable, and return a pointer to the next character in the + * input string. + */ + +STATIC const char * +evalvar(const char *p, int flag) +{ + int subtype; + int varflags; + const char *var; + char *val; + int patloc; + int c; + int set; + int special; + int startloc; + int varlen; + int apply_ifs; + const int quotes = flag & EXP_QNEEDED; + + varflags = (unsigned char)*p++; + subtype = varflags & VSTYPE; + var = p; + special = !is_name(*p); + p = strchr(p, '=') + 1; + + CTRACE(DBG_EXPAND, + ("evalvar \"%.*s\", flag=%#X quotes=%#X vf=%#X subtype=%X\n", + p - var - 1, var, flag, quotes, varflags, subtype)); + + again: /* jump here after setting a variable with ${var=text} */ + if (varflags & VSLINENO) { + if (line_num.flags & VUNSET) { + set = 0; + val = NULL; + } else { + set = 1; + special = p - var; + val = NULL; + } + } else if (special) { + set = varisset(var, varflags & VSNUL); + val = NULL; + } else { + val = lookupvar(var); + 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); + /* NOTREACHED */ + } + } + + if (!set && subtype != VSPLUS && special && *var == '@') + if (startloc > 0 && expdest[-1] == CTLQUOTEMARK) + expdest--, startloc--; + + if (set && subtype != VSPLUS) { + /* insert the value of the variable */ + if (special) { + if (varflags & VSLINENO) { + /* + * The LINENO hack (expansion part) + */ + while (--special > 0) { +/* not needed, it is a number... + if (quotes && NEEDESC(*var)) + STPUTC(CTLESC, expdest); +*/ + STPUTC(*var++, expdest); + } + } else + varvalue(var, varflags&VSQUOTE, subtype, flag); + if (subtype == VSLENGTH) { + varlen = expdest - stackblock() - startloc; + STADJUST(-varlen, expdest); + } + } else { + + if (subtype == VSLENGTH) { + for (;*val; val++) + varlen++; + } else if (quotes && varflags & VSQUOTE) { + for (; (c = *val) != '\0'; val++) { + if (NEEDESC(c)) + STPUTC(CTLESC, expdest); + STPUTC(c, expdest); + } + } else { + while (*val) + STPUTC(*val++, expdest); + } + } + } + + + if (varflags & VSQUOTE) { + if (*var == '@' && shellparam.nparam != 1) + apply_ifs = 1; + else { + /* + * Mark so that we don't apply IFS if we recurse through + * here expanding $bar from "${foo-$bar}". + */ + flag |= EXP_IN_QUOTES; + apply_ifs = 0; + } + } else if (flag & EXP_IN_QUOTES) { + apply_ifs = 0; + } else + apply_ifs = 1; + + switch (subtype) { + case VSLENGTH: + expdest = cvtnum(varlen, expdest); + break; + + case VSNORMAL: + break; + + case VSPLUS: + set = !set; + /* FALLTHROUGH */ + case VSMINUS: + if (!set) { + argstr(p, flag | (apply_ifs ? EXP_IFS_SPLIT : 0)); + /* + * ${x-a b c} doesn't get split, but removing the + * 'apply_ifs = 0' apparently breaks ${1+"$@"}.. + * ${x-'a b' c} should generate 2 args. + */ + if (*p != CTLENDVAR) + /* We should have marked stuff already */ + apply_ifs = 0; + } + break; + + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + if (!set) { + set = 1; /* allow argbackq to be advanced if needed */ + break; + } + /* + * Terminate the string and start recording the pattern + * right after it + */ + STPUTC('\0', expdest); + patloc = expdest - stackblock(); + if (subevalvar_trim(p, patloc, subtype, startloc, varflags, + quotes) == 0) { + int amount = (expdest - stackblock() - patloc) + 1; + STADJUST(-amount, expdest); + } + /* Remove any recorded regions beyond start of variable */ + removerecordregions(startloc); + apply_ifs = 1; + break; + + case VSASSIGN: + case VSQUESTION: + if (set) + break; + if (subevalvar(p, var, subtype, startloc, varflags)) { + /* if subevalvar() returns, it always returns 1 */ + + varflags &= ~VSNUL; + /* + * Remove any recorded regions beyond + * start of variable + */ + removerecordregions(startloc); + goto again; + } + apply_ifs = 0; /* never executed */ + break; + + default: + abort(); + } + + if (apply_ifs) + recordregion(startloc, expdest - stackblock(), + varflags & VSQUOTE); + + if (subtype != VSNORMAL) { /* skip to end of alternative */ + int nesting = 1; + for (;;) { + if ((c = *p++) == CTLESC) + p++; + else if (c == CTLNONL) + ; + else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) { + if (set) + 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 parameter is set. + */ + +STATIC int +varisset(const char *name, int nulok) +{ + if (*name == '!') + return backgndpid != -1; + 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; + + /* + * handle overflow sensibly (the *ap tests should never fail) + */ + errno = 0; + num = strtol(name, &ap, 10); + if (errno != 0 || (*ap != '\0' && *ap != '=')) + return 0; + + if (num == 0) + ap = arg0; + else if (num > shellparam.nparam) + return 0; + else + ap = shellparam.p[num - 1]; + + if (nulok && (ap == NULL || *ap == '\0')) + return 0; + } + return 1; +} + + + +/* + * Add the value of a specialized variable to the stack string. + */ + +STATIC void +varvalue(const char *name, int quoted, int subtype, int flag) +{ + int num; + char *p; + int i; + int sep; + char **ap; +#ifdef DEBUG + char *start = expdest; +#endif + + VTRACE(DBG_EXPAND, ("varvalue(%c%s, sub=%d, fl=%#x)", *name, + quoted ? ", quoted" : "", subtype, flag)); + + if (subtype == VSLENGTH) /* no magic required ... */ + flag &= ~EXP_FULL; + +#define STRTODEST(p) \ + do {\ + if ((flag & EXP_QNEEDED) && quoted) { \ + while (*p) { \ + if (NEEDESC(*p)) \ + STPUTC(CTLESC, expdest); \ + STPUTC(*p++, expdest); \ + } \ + } else \ + while (*p) \ + STPUTC(*p++, expdest); \ + } while (0) + + + switch (*name) { + case '$': + num = rootpid; + break; + case '?': + num = exitstatus; + break; + case '#': + num = shellparam.nparam; + break; + case '!': + num = backgndpid; + break; + case '-': + for (i = 0; i < option_flags; i++) { + if (optlist[optorder[i]].val) + STPUTC(optlist[optorder[i]].letter, expdest); + } + VTRACE(DBG_EXPAND, (": %.*s\n", expdest-start, start)); + return; + case '@': + if (flag & EXP_SPLIT && quoted) { + VTRACE(DBG_EXPAND, (": $@ split (%d)\n", + shellparam.nparam)); + /* GROSS HACK */ + if (shellparam.nparam == 0 && + expdest[-1] == CTLQUOTEMARK) + expdest--; + /* KCAH SSORG */ + for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { + STRTODEST(p); + if (*ap) + /* A NUL separates args inside "" */ + STPUTC('\0', expdest); + } + return; + } + /* fall through */ + case '*': + sep = ifsval()[0]; + for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { + STRTODEST(p); + if (!*ap) + break; + if (sep) { + if (quoted && (flag & EXP_QNEEDED) && + NEEDESC(sep)) + STPUTC(CTLESC, expdest); + STPUTC(sep, expdest); + } else + if ((flag & (EXP_SPLIT|EXP_IN_QUOTES)) == EXP_SPLIT + && !quoted && **ap != '\0') + STPUTC('\0', expdest); + } + VTRACE(DBG_EXPAND, (": %.*s\n", expdest-start, start)); + return; + default: + if (is_digit(*name)) { + long lnum; + + errno = 0; + lnum = strtol(name, &p, 10); + if (errno != 0 || (*p != '\0' && *p != '=')) + return; + + if (lnum == 0) + p = arg0; + else if (lnum > 0 && lnum <= shellparam.nparam) + p = shellparam.p[lnum - 1]; + else + return; + STRTODEST(p); + } + VTRACE(DBG_EXPAND, (": %.*s\n", expdest-start, start)); + return; + } + /* + * only the specials with an int value arrive here + */ + VTRACE(DBG_EXPAND, ("(%d)", num)); + expdest = cvtnum(num, expdest); + VTRACE(DBG_EXPAND, (": %.*s\n", expdest-start, start)); +} + + + +/* + * Record the fact that we have to scan this region of the + * string for IFS characters. + */ + +STATIC void +recordregion(int start, int end, int inquotes) +{ + struct ifsregion *ifsp; + + VTRACE(DBG_EXPAND, ("recordregion(%d,%d,%d)\n", start, end, inquotes)); + if (ifslastp == NULL) { + ifsp = &ifsfirst; + } else { + if (ifslastp->endoff == start + && ifslastp->inquotes == inquotes) { + /* extend previous area */ + ifslastp->endoff = end; + return; + } + ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion)); + ifslastp->next = ifsp; + } + ifslastp = ifsp; + ifslastp->next = NULL; + ifslastp->begoff = start; + ifslastp->endoff = end; + ifslastp->inquotes = inquotes; +} + + + +/* + * Break the argument string into pieces based upon IFS and add the + * strings to the argument list. The regions of the string to be + * searched for IFS characters have been stored by recordregion. + */ +STATIC void +ifsbreakup(char *string, struct arglist *arglist) +{ + struct ifsregion *ifsp; + struct strlist *sp; + char *start; + char *p; + char *q; + const char *ifs; + const char *ifsspc; + int had_param_ch = 0; + + start = string; + + VTRACE(DBG_EXPAND, ("ifsbreakup(\"%s\")", string)); /* misses \0's */ + if (ifslastp == NULL) { + /* Return entire argument, IFS doesn't apply to any of it */ + VTRACE(DBG_EXPAND, ("no regions\n", string)); + sp = stalloc(sizeof(*sp)); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + return; + } + + ifs = ifsval(); + + for (ifsp = &ifsfirst; ifsp != NULL; ifsp = ifsp->next) { + p = string + ifsp->begoff; + VTRACE(DBG_EXPAND, (" !%.*s!(%d)", ifsp->endoff-ifsp->begoff, + p, ifsp->endoff-ifsp->begoff)); + while (p < string + ifsp->endoff) { + had_param_ch = 1; + q = p; + if (IS_BORING(*p)) { + p++; + continue; + } + if (*p == CTLESC) + p++; + if (ifsp->inquotes) { + /* Only NULs (should be from "$@") end args */ + if (*p != 0) { + p++; + continue; + } + ifsspc = NULL; + VTRACE(DBG_EXPAND, (" \\0 nxt:\"%s\" ", p)); + } else { + if (!strchr(ifs, *p)) { + p++; + continue; + } + had_param_ch = 0; + ifsspc = strchr(" \t\n", *p); + + /* Ignore IFS whitespace at start */ + if (q == start && ifsspc != NULL) { + p++; + start = p; + continue; + } + } + + /* Save this argument... */ + *q = '\0'; + VTRACE(DBG_EXPAND, ("<%s>", start)); + sp = stalloc(sizeof(*sp)); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + p++; + + if (ifsspc != NULL) { + /* Ignore further trailing IFS whitespace */ + for (; p < string + ifsp->endoff; p++) { + q = p; + if (*p == CTLNONL) + continue; + if (*p == CTLESC) + p++; + if (strchr(ifs, *p) == NULL) { + p = q; + break; + } + if (strchr(" \t\n", *p) == NULL) { + p++; + break; + } + } + } + start = p; + } + } + + while (*start == CTLQUOTEEND) + start++; + + /* + * Save anything left as an argument. + * Traditionally we have treated 'IFS=':'; set -- x$IFS' as + * generating 2 arguments, the second of which is empty. + * Some recent clarification of the Posix spec say that it + * should only generate one.... + */ + if (had_param_ch || *start != 0) { + VTRACE(DBG_EXPAND, (" T<%s>", start)); + sp = stalloc(sizeof(*sp)); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + } + VTRACE(DBG_EXPAND, ("\n")); +} + +STATIC void +ifsfree(void) +{ + while (ifsfirst.next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = ifsfirst.next->next; + ckfree(ifsfirst.next); + ifsfirst.next = ifsp; + INTON; + } + ifslastp = NULL; + ifsfirst.next = NULL; +} + + + +/* + * Expand shell metacharacters. At this point, the only control characters + * should be escapes. The results are stored in the list exparg. + */ + +char *expdir; + + +STATIC void +expandmeta(struct strlist *str, int flag) +{ + char *p; + struct strlist **savelastp; + struct strlist *sp; + char c; + /* TODO - EXP_REDIR */ + + while (str) { + p = str->text; + for (;;) { /* fast check for meta chars */ + if ((c = *p++) == '\0') + goto nometa; + if (c == '*' || c == '?' || c == '[' /* || c == '!' */) + break; + } + savelastp = exparg.lastp; + INTOFF; + if (expdir == NULL) { + int i = strlen(str->text); + expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */ + } + + expmeta(expdir, str->text); + ckfree(expdir); + expdir = NULL; + INTON; + if (exparg.lastp == savelastp) { + /* + * no matches + */ + nometa: + *exparg.lastp = str; + rmescapes(str->text); + exparg.lastp = &str->next; + } else { + *exparg.lastp = NULL; + *savelastp = sp = expsort(*savelastp); + while (sp->next != NULL) + sp = sp->next; + exparg.lastp = &sp->next; + } + str = str->next; + } +} + +STATIC void +add_args(struct strlist *str) +{ + while (str) { + *exparg.lastp = str; + rmescapes(str->text); + exparg.lastp = &str->next; + str = str->next; + } +} + + +/* + * Do metacharacter (i.e. *, ?, [...]) expansion. + */ + +STATIC void +expmeta(char *enddir, char *name) +{ + char *p; + const char *cp; + char *q; + char *start; + char *endname; + int metaflag; + struct stat statb; + DIR *dirp; + struct dirent *dp; + int atend; + int matchdot; + + CTRACE(DBG_EXPAND|DBG_MATCH, ("expmeta(\"%s\")\n", name)); + metaflag = 0; + start = name; + for (p = name ; ; p++) { + if (*p == '*' || *p == '?') + metaflag = 1; + else if (*p == '[') { + q = p + 1; + if (*q == '!' || *q == '^') + q++; + for (;;) { + while (IS_BORING(*q)) + q++; + if (*q == ']') { + q++; + metaflag = 1; + break; + } + if (*q == '[' && q[1] == ':') { + /* + * character class, look for :] ending + * also stop on ']' (end bracket expr) + * or '\0' or '/' (end pattern) + */ + while (*++q != '\0' && *q != ']' && + *q != '/') { + if (*q == CTLESC) { + if (*++q == '\0') + break; + if (*q == '/') + break; + } else if (*q == ':' && + q[1] == ']') + break; + } + if (*q == ':') { + /* + * stopped at ':]' + * still in [...] + * skip ":]" and continue; + */ + q += 2; + continue; + } + + /* done at end of pattern, not [...] */ + if (*q == '\0' || *q == '/') + break; + + /* found the ']', we have a [...] */ + metaflag = 1; + q++; /* skip ']' */ + break; + } + if (*q == CTLESC) + q++; + /* end of pattern cannot be escaped */ + if (*q == '/' || *q == '\0') + break; + q++; + } + } else if (*p == '\0') + break; + else if (IS_BORING(*p)) + continue; + else if (*p == CTLESC) + p++; + if (*p == '/') { + if (metaflag) + break; + start = p + 1; + } + } + if (metaflag == 0) { /* we've reached the end of the file name */ + if (enddir != expdir) + metaflag++; + for (p = name ; ; p++) { + if (IS_BORING(*p)) + continue; + if (*p == CTLESC) + p++; + *enddir++ = *p; + if (*p == '\0') + break; + } + if (metaflag == 0 || lstat(expdir, &statb) >= 0) + addfname(expdir); + return; + } + endname = p; + if (start != name) { + p = name; + while (p < start) { + while (IS_BORING(*p)) + p++; + if (*p == CTLESC) + p++; + *enddir++ = *p++; + } + } + if (enddir == expdir) { + cp = "."; + } else if (enddir == expdir + 1 && *expdir == '/') { + cp = "/"; + } else { + cp = expdir; + enddir[-1] = '\0'; + } + if ((dirp = opendir(cp)) == NULL) + return; + if (enddir != expdir) + enddir[-1] = '/'; + if (*endname == 0) { + atend = 1; + } else { + atend = 0; + *endname++ = '\0'; + } + matchdot = 0; + p = start; + while (IS_BORING(*p)) + p++; + 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, 0)) { + if (atend) { + scopy(dp->d_name, enddir); + addfname(expdir); + } else { + for (p = enddir, cp = dp->d_name; + (*p++ = *cp++) != '\0';) + continue; + p[-1] = '/'; + expmeta(p, endname); + } + } + } + closedir(dirp); + if (! atend) + endname[-1] = '/'; +} + + +/* + * Add a file name to the list. + */ + +STATIC void +addfname(char *name) +{ + char *p; + struct strlist *sp; + + p = stalloc(strlen(name) + 1); + scopy(name, p); + sp = stalloc(sizeof(*sp)); + sp->text = p; + *exparg.lastp = sp; + exparg.lastp = &sp->next; +} + + +/* + * Sort the results of file name expansion. It calculates the number of + * strings to sort and then calls msort (short for merge sort) to do the + * work. + */ + +STATIC struct strlist * +expsort(struct strlist *str) +{ + int len; + struct strlist *sp; + + len = 0; + for (sp = str ; sp ; sp = sp->next) + len++; + return msort(str, len); +} + + +STATIC struct strlist * +msort(struct strlist *list, int len) +{ + struct strlist *p, *q = NULL; + struct strlist **lpp; + int half; + int n; + + if (len <= 1) + return list; + half = len >> 1; + p = list; + for (n = half ; --n >= 0 ; ) { + q = p; + p = p->next; + } + q->next = NULL; /* terminate first half of list */ + q = msort(list, half); /* sort first half of list */ + p = msort(p, len - half); /* sort second half */ + lpp = &list; + for (;;) { + if (strcmp(p->text, q->text) < 0) { + *lpp = p; + lpp = &p->next; + if ((p = *lpp) == NULL) { + *lpp = q; + break; + } + } else { + *lpp = q; + lpp = &q->next; + if ((q = *lpp) == NULL) { + *lpp = p; + break; + } + } + } + return list; +} + + +/* + * 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]; + char *nameend; + wctype_t cclass; + + *end = NULL; + p++; + nameend = strstr(p, ":]"); + if (nameend == NULL || nameend == p) /* not a valid class */ + return 0; + + if (!is_alpha(*p) || strspn(p, /* '_' is a local extension */ + "0123456789" "_" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ") != (size_t)(nameend - p)) + return 0; + + *end = nameend + 2; /* committed to it being a char class */ + if ((size_t)(nameend - p) >= sizeof(name)) /* but too long */ + return 0; /* so no match */ + memcpy(name, p, nameend - p); + name[nameend - p] = '\0'; + 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, int squoted) +{ + const char *p, *q, *end; + const char *bt_p, *bt_q; + char c; + wchar_t wc, wc2; + + VTRACE(DBG_MATCH, ("patmatch(P=\"%s\", W=\"%s\"%s): ", + pattern, string, squoted ? ", SQ" : "")); + p = pattern; + q = string; + bt_p = NULL; + bt_q = NULL; + for (;;) { + switch (c = *p++) { + case '\0': + if (squoted && *q == CTLESC) { + if (q[1] == '\0') + q++; + } + if (*q != '\0') + goto backtrack; + VTRACE(DBG_MATCH, ("match\n")); + return 1; + case CTLESC: + if (squoted && *q == CTLESC) + q++; + if (*p == '\0' && *q == '\0') { + VTRACE(DBG_MATCH, ("match-\\\n")); + return 1; + } + if (*q++ != *p++) + goto backtrack; + break; + case '\\': + if (squoted && *q == CTLESC) + q++; + if (*q++ != *p++) + goto backtrack; + break; + case CTLQUOTEMARK: + case CTLQUOTEEND: + case CTLNONL: + continue; + case '?': + if (squoted && *q == CTLESC) + q++; + if (*q++ == '\0') { + VTRACE(DBG_MATCH, ("?fail\n")); + return 0; + } + break; + case '*': + c = *p; + while (c == CTLQUOTEMARK || c == '*') + c = *++p; + if (c != CTLESC && !IS_BORING(c) && + c != '?' && c != '*' && c != '[') { + while (*q != c) { + if (squoted && *q == CTLESC && + q[1] == c) + break; + if (*q == '\0') { + VTRACE(DBG_MATCH, ("*fail\n")); + return 0; + } + if (squoted && *q == CTLESC) + q++; + q++; + } + } + if (c == CTLESC && p[1] == '\0') { + VTRACE(DBG_MATCH, ("match+\\\n")); + 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, *endp; + int invert, found; + unsigned char chr; + + /* + * First quick check to see if there is a + * possible matching ']' - if not, then this + * is not a char class, and the '[' is just + * a literal '['. + * + * This check will not detect all non classes, but + * that's OK - It just means that we execute the + * harder code sometimes when it it cannot succeed. + */ + endp = p; + if (*endp == '!' || *endp == '^') + endp++; + for (;;) { + while (IS_BORING(*endp)) + endp++; + if (*endp == '\0') + goto dft; /* no matching ] */ + if (*endp++ == ']') + break; + } + /* end shortcut */ + + invert = 0; + savep = p, saveq = q; + invert = 0; + if (*p == '!' || *p == '^') { + invert++; + p++; + } + found = 0; + if (*q == '\0') { + VTRACE(DBG_MATCH, ("[]fail\n")); + return 0; + } + if (squoted && *q == CTLESC) + q++; + chr = (unsigned char)*q++; + c = *p++; + do { + if (IS_BORING(c)) + continue; + 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 == '\\') + c = *p++; + wc = (unsigned char)c; + if (*p == '-' && p[1] != ']') { + p++; + if (*p == CTLESC || *p == '\\') + p++; + 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 (squoted && *q == CTLESC) + q++; + 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) { + VTRACE(DBG_MATCH, ("BTP fail\n")); + return 0; + } + if (*bt_q == '\0') { + VTRACE(DBG_MATCH, ("BTQ fail\n")); + return 0; + } + bt_q++; + p = bt_p; + q = bt_q; + break; + } + } +} + + + +/* + * Remove any CTLESC or CTLNONL characters from a string. + */ + +void +rmescapes(char *str) +{ + char *p, *q; + + p = str; + while (*p != CTLESC && !IS_BORING(*p)) { + if (*p++ == '\0') + return; + } + q = p; + while (*p) { + if (IS_BORING(*p)) { + p++; + continue; + } + if (*p == CTLCNL) { + p++; + *q++ = '\n'; + continue; + } + if (*p == CTLESC) + p++; + *q++ = *p++; + } + *q = '\0'; +} + +/* + * and a special version for dealing with expressions to be parsed + * by the arithmetic evaluator. That needs to be able to count \n's + * even ones that were \newline elided \n's, so we have to put the + * latter back into the string - just being careful to put them only + * at a place where white space can reasonably occur in the string + * -- then the \n we insert will just be white space, and ignored + * for all purposes except line counting. + */ + +void +rmescapes_nl(char *str) +{ + char *p, *q; + int nls = 0, holdnl = 0, holdlast; + + p = str; + while (*p != CTLESC && !IS_BORING(*p)) { + if (*p++ == '\0') + return; + } + if (p > str) /* must reprocess char before stopper (if any) */ + --p; /* so we do not place a \n badly */ + q = p; + while (*p) { + if (*p == CTLQUOTEMARK || *p == CTLQUOTEEND) { + p++; + continue; + } + if (*p == CTLNONL) { + p++; + nls++; + continue; + } + if (*p == CTLCNL) { + p++; + *q++ = '\n'; + continue; + } + if (*p == CTLESC) + p++; + + holdlast = holdnl; + holdnl = is_in_name(*p); /* letters, digits, _ */ + if (q == str || is_space(q[-1]) || (*p != '=' && q[-1] != *p)) { + if (nls > 0 && holdnl != holdlast) { + while (nls > 0) + *q++ = '\n', nls--; + } + } + *q++ = *p++; + } + while (--nls >= 0) + *q++ = '\n'; + *q = '\0'; +} + + + +/* + * See if a pattern matches in a case statement. + */ + +int +casematch(union node *pattern, char *val) +{ + struct stackmark smark; + int result; + char *p; + + CTRACE(DBG_MATCH, ("casematch(P=\"%s\", W=\"%s\")\n", + pattern->narg.text, val)); + setstackmark(&smark); + argbackq = pattern->narg.backquote; + STARTSTACKSTR(expdest); + ifslastp = NULL; + argstr(pattern->narg.text, EXP_TILDE | EXP_CASE); + STPUTC('\0', expdest); + p = grabstackstr(expdest); + result = patmatch(p, val, 0); + popstackmark(&smark); + return result; +} + +/* + * Our own itoa(). Assumes result buffer is on the stack + */ + +STATIC char * +cvtnum(int num, char *buf) +{ + char temp[32]; + int neg = num < 0; + char *p = temp + sizeof temp - 1; + + if (neg) + num = -num; + + *p = '\0'; + do { + *--p = num % 10 + '0'; + } while ((num /= 10) != 0 && p > temp + 1); + + if (neg) + *--p = '-'; + + while (*p) + STPUTC(*p++, buf); + return buf; +} + +/* + * Do most of the work for wordexp(3). + */ + +int +wordexpcmd(int argc, char **argv) +{ + size_t len; + int i; + + out1fmt("%d", argc - 1); + out1c('\0'); + for (i = 1, len = 0; i < argc; i++) + len += strlen(argv[i]); + out1fmt("%zu", len); + out1c('\0'); + for (i = 1; i < argc; i++) { + out1str(argv[i]); + out1c('\0'); + } + return (0); +} diff --git a/bin/sh/expand.h b/bin/sh/expand.h new file mode 100644 index 0000000..d435fd8 --- /dev/null +++ b/bin/sh/expand.h @@ -0,0 +1,71 @@ +/* $NetBSD: expand.h,v 1.24 2018/11/18 17:23:37 kre Exp $ */ + +/*- + * 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 + */ + +#include <inttypes.h> + +struct strlist { + struct strlist *next; + char *text; +}; + + +struct arglist { + struct strlist *list; + struct strlist **lastp; +}; + +/* + * 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_REDIR 0x8 /* file glob for a redirection (1 match only) */ +#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ +#define EXP_IFS_SPLIT 0x20 /* need to record arguments for ifs breakup */ +#define EXP_IN_QUOTES 0x40 /* don't set EXP_IFS_SPLIT again */ +#define EXP_GLOB 0x80 /* perform filename globbing */ +#define EXP_NL 0x100 /* keep CRTNONL in output */ + +#define EXP_FULL (EXP_SPLIT | EXP_GLOB) +#define EXP_QNEEDED (EXP_GLOB | EXP_CASE | EXP_REDIR) + +union node; + +void expandhere(union node *, int); +void expandarg(union node *, struct arglist *, int); +void rmescapes(char *); +int casematch(union node *, char *); diff --git a/bin/sh/funcs/cmv b/bin/sh/funcs/cmv new file mode 100644 index 0000000..0e3eef6 --- /dev/null +++ b/bin/sh/funcs/cmv @@ -0,0 +1,43 @@ +# $NetBSD: cmv,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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. +# +# @(#)cmv 8.2 (Berkeley) 5/4/95 + +# Conditional move--don't replace an existing file. + +cmv() { + if test $# != 2 + then echo "cmv: arg count" + return 2 + fi + if test -f "$2" -o -w "$2" + then echo "$2 exists" + return 2 + fi + /bin/mv "$1" "$2" +} diff --git a/bin/sh/funcs/dirs b/bin/sh/funcs/dirs new file mode 100644 index 0000000..ef2ae0a --- /dev/null +++ b/bin/sh/funcs/dirs @@ -0,0 +1,67 @@ +# $NetBSD: dirs,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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. +# +# @(#)dirs 8.2 (Berkeley) 5/4/95 + +# pushd, popd, and dirs --- written by Chris Bertin +# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris +# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW + +pushd () { + SAVE=`pwd` + if [ "$1" = "" ] + then if [ "$DSTACK" = "" ] + then echo "pushd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 || return + shift 1 + DSTACK="$*" + else cd $1 > /dev/null || return + fi + DSTACK="$SAVE $DSTACK" + dirs +} + +popd () { + if [ "$DSTACK" = "" ] + then echo "popd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 + shift + DSTACK=$* + dirs +} + +dirs () { + echo "`pwd` $DSTACK" + return 0 +} diff --git a/bin/sh/funcs/kill b/bin/sh/funcs/kill new file mode 100644 index 0000000..70f2b27 --- /dev/null +++ b/bin/sh/funcs/kill @@ -0,0 +1,43 @@ +# $NetBSD: kill,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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 8.2 (Berkeley) 5/4/95 + +# Convert job names to process ids and then run /bin/kill. + +kill() { + local args x + args= + for x in "$@" + do case $x in + %*) x=`jobid "$x"` ;; + esac + args="$args $x" + done + /bin/kill $args +} diff --git a/bin/sh/funcs/login b/bin/sh/funcs/login new file mode 100644 index 0000000..a2fe60e --- /dev/null +++ b/bin/sh/funcs/login @@ -0,0 +1,32 @@ +# $NetBSD: login,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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. +# +# @(#)login 8.2 (Berkeley) 5/4/95 + +# replaces the login builtin in the BSD shell +login () exec login "$@" diff --git a/bin/sh/funcs/newgrp b/bin/sh/funcs/newgrp new file mode 100644 index 0000000..1ad46a3 --- /dev/null +++ b/bin/sh/funcs/newgrp @@ -0,0 +1,31 @@ +# $NetBSD: newgrp,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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. +# +# @(#)newgrp 8.2 (Berkeley) 5/4/95 + +newgrp() exec newgrp "$@" diff --git a/bin/sh/funcs/popd b/bin/sh/funcs/popd new file mode 100644 index 0000000..836741c --- /dev/null +++ b/bin/sh/funcs/popd @@ -0,0 +1,67 @@ +# $NetBSD: popd,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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. +# +# @(#)popd 8.2 (Berkeley) 5/4/95 + +# pushd, popd, and dirs --- written by Chris Bertin +# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris +# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW + +pushd () { + SAVE=`pwd` + if [ "$1" = "" ] + then if [ "$DSTACK" = "" ] + then echo "pushd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 || return + shift 1 + DSTACK="$*" + else cd $1 > /dev/null || return + fi + DSTACK="$SAVE $DSTACK" + dirs +} + +popd () { + if [ "$DSTACK" = "" ] + then echo "popd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 + shift + DSTACK=$* + dirs +} + +dirs () { + echo "`pwd` $DSTACK" + return 0 +} diff --git a/bin/sh/funcs/pushd b/bin/sh/funcs/pushd new file mode 100644 index 0000000..c6ec1af --- /dev/null +++ b/bin/sh/funcs/pushd @@ -0,0 +1,67 @@ +# $NetBSD: pushd,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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. +# +# @(#)pushd 8.2 (Berkeley) 5/4/95 + +# pushd, popd, and dirs --- written by Chris Bertin +# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris +# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW + +pushd () { + SAVE=`pwd` + if [ "$1" = "" ] + then if [ "$DSTACK" = "" ] + then echo "pushd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 || return + shift 1 + DSTACK="$*" + else cd $1 > /dev/null || return + fi + DSTACK="$SAVE $DSTACK" + dirs +} + +popd () { + if [ "$DSTACK" = "" ] + then echo "popd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 + shift + DSTACK=$* + dirs +} + +dirs () { + echo "`pwd` $DSTACK" + return 0 +} diff --git a/bin/sh/funcs/suspend b/bin/sh/funcs/suspend new file mode 100644 index 0000000..7643f43 --- /dev/null +++ b/bin/sh/funcs/suspend @@ -0,0 +1,35 @@ +# $NetBSD: suspend,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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. +# +# @(#)suspend 8.2 (Berkeley) 5/4/95 + +suspend() { + local - + set +j + kill -TSTP 0 +} diff --git a/bin/sh/histedit.c b/bin/sh/histedit.c new file mode 100644 index 0000000..5de3adc --- /dev/null +++ b/bin/sh/histedit.c @@ -0,0 +1,576 @@ +/* $NetBSD: histedit.c,v 1.53 2018/07/13 22:43:44 kre Exp $ */ + +/*- + * 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> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: histedit.c,v 1.53 2018/07/13 22:43:44 kre Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.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 "builtins.h" +#include "main.h" +#include "output.h" +#include "mystring.h" +#include "myhistedit.h" +#include "error.h" +#include "alias.h" +#ifndef SMALL +#include "eval.h" +#include "memalloc.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; +unsigned char _el_fn_complete(EditLine *, int); + +STATIC const char *fc_replace(const char *, char *, char *); + +#ifdef DEBUG +extern FILE *tracefile; +#endif + +/* + * Set history and editing status. Called whenever the status may + * have changed (figures out what to do). + */ +void +histedit(void) +{ + FILE *el_err; + +#define editing (Eflag || Vflag) + + if (iflag == 1) { + if (!hist) { + /* + * turn history on + */ + INTOFF; + hist = history_init(); + INTON; + + if (hist != NULL) + sethistsize(histsizeval()); + else + out2str("sh: can't initialize history\n"); + } + if (editing && !el && isatty(0)) { /* && isatty(2) ??? */ + /* + * turn editing on + */ + char *term, *shname; + + INTOFF; + if (el_in == NULL) + el_in = fdopen(0, "r"); + if (el_out == NULL) + el_out = fdopen(2, "w"); + if (el_in == NULL || el_out == NULL) + goto bad; + el_err = el_out; +#if DEBUG + if (tracefile) + el_err = tracefile; +#endif + term = lookupvar("TERM"); + if (term) + setenv("TERM", term, 1); + else + unsetenv("TERM"); + shname = arg0; + if (shname[0] == '-') + shname++; + el = el_init(shname, el_in, el_out, el_err); + if (el != NULL) { + if (hist) + el_set(el, EL_HIST, history, hist); + + set_prompt_lit(lookupvar("PSlit")); + el_set(el, EL_SIGNAL, 1); + el_set(el, EL_ALIAS_TEXT, alias_text, NULL); + el_set(el, EL_ADDFN, "rl-complete", + "ReadLine compatible completion function", + _el_fn_complete); + } else { +bad: + out2str("sh: can't initialize editing\n"); + } + INTON; + } else if (!editing && el) { + INTOFF; + el_end(el); + el = NULL; + INTON; + } + if (el) { + if (Vflag) + el_set(el, EL_EDITOR, "vi"); + else if (Eflag) + el_set(el, EL_EDITOR, "emacs"); + el_set(el, EL_BIND, "^I", + tabcomplete ? "rl-complete" : "ed-insert", NULL); + el_source(el, lookupvar("EDITRC")); + } + } else { + INTOFF; + if (el) { /* no editing if not interactive */ + el_end(el); + el = NULL; + } + if (hist) { + history_end(hist); + hist = NULL; + } + INTON; + } +} + +void +set_prompt_lit(const char *lit_ch) +{ + wchar_t wc; + + if (!(iflag && editing && el)) + return; + + if (lit_ch == NULL) { + el_set(el, EL_PROMPT, getprompt); + return; + } + + mbtowc(&wc, NULL, 1); /* state init */ + + if (mbtowc(&wc, lit_ch, strlen(lit_ch)) <= 0) + el_set(el, EL_PROMPT, getprompt); + else + el_set(el, EL_PROMPT_ESC, getprompt, (int)wc); +} + +void +set_editrc(const char *fname) +{ + if (iflag && editing && el) + el_source(el, fname); +} + +void +sethistsize(const char *hs) +{ + int histsize; + HistEvent he; + + if (hist != NULL) { + if (hs == NULL || *hs == '\0' || *hs == '-' || + (histsize = number(hs)) < 0) + histsize = 100; + history(hist, &he, H_SETSIZE, histsize); + history(hist, &he, H_SETUNIQUE, 1); + } +} + +void +setterm(const char *term) +{ + if (el != NULL && term != NULL) + if (el_set(el, EL_TERMINAL, term) != 0) { + outfmt(out2, "sh: Can't set terminal type %s\n", term); + outfmt(out2, "sh: Using dumb terminal settings.\n"); + } +} + +int +inputrc(int argc, char **argv) +{ + if (argc != 2) { + out2str("usage: inputrc file\n"); + return 1; + } + if (el != NULL) { + if (el_source(el, argv[1])) { + out2str("inputrc: failed\n"); + return 1; + } else + return 0; + } else { + out2str("sh: inputrc ignored, not editing\n"); + return 1; + } +} + +/* + * This command is provided since POSIX decided to standardize + * the Korn shell fc command. Oh well... + */ +int +histcmd(volatile int argc, char ** volatile argv) +{ + int ch; + const char * volatile editor = NULL; + HistEvent he; + volatile int lflg = 0, nflg = 0, rflg = 0, sflg = 0; + int i, retval; + const char *firststr, *laststr; + int first, last, direction; + char * volatile pat = NULL, * volatile repl; /* ksh "fc old=new" crap */ + static int active = 0; + struct jmploc jmploc; + struct jmploc *volatile savehandler; + char editfile[MAXPATHLEN + 1]; + FILE * volatile efp; +#ifdef __GNUC__ + repl = NULL; /* XXX gcc4 */ + efp = NULL; /* XXX gcc4 */ +#endif + + if (hist == NULL) + error("history not active"); + + if (argc == 1) + error("missing history argument"); + + optreset = 1; optind = 1; /* initialize getopt */ + while (not_fcnumber(argv[optind]) && + (ch = getopt(argc, argv, ":e:lnrs")) != -1) + switch ((char)ch) { + case 'e': + editor = optionarg; + break; + case 'l': + lflg = 1; + break; + case 'n': + nflg = 1; + break; + case 'r': + rflg = 1; + break; + case 's': + sflg = 1; + break; + case ':': + error("option -%c expects argument", optopt); + /* NOTREACHED */ + case '?': + default: + error("unknown option: -%c", optopt); + /* NOTREACHED */ + } + argc -= optind, argv += optind; + + /* + * If executing... + */ + if (lflg == 0 || editor || sflg) { + lflg = 0; /* ignore */ + editfile[0] = '\0'; + /* + * Catch interrupts to reset active counter and + * cleanup temp files. + */ + savehandler = handler; + 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 && argc > 0 && + ((repl = strchr(argv[0], '=')) != NULL)) { + pat = argv[0]; + *repl++ = '\0'; + argc--, argv++; + } + + /* + * If -s is specified, accept only one operand + */ + if (sflg && argc >= 2) + error("too many args"); + + /* + * determine [first] and [last] + */ + switch (argc) { + case 0: + firststr = lflg ? "-16" : "-1"; + laststr = "-1"; + break; + case 1: + firststr = argv[0]; + laststr = lflg ? "-1" : argv[0]; + break; + case 2: + firststr = argv[0]; + laststr = argv[1]; + break; + default: + error("too many args"); + /* NOTREACHED */ + } + /* + * 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 */ + snprintf(editfile, sizeof(editfile), "%s_shXXXXXX", _PATH_TMP); + if ((fd = mkstemp(editfile)) < 0) + error("can't create temporary file %s", editfile); + if ((efp = fdopen(fd, "w")) == NULL) { + close(fd); + error("can't allocate stdio buffer for temp"); + } + } + + /* + * 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); + } + + evalstring(strcpy(stalloc(strlen(s) + 1), s), 0); + if (displayhist && hist) { + /* + * XXX what about recursive and + * relative histnums. + */ + history(hist, &he, H_ENTER, s); + } + + break; + } 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; + size_t cmdlen; + + fclose(efp); + cmdlen = strlen(editor) + strlen(editfile) + 2; + editcmd = stalloc(cmdlen); + snprintf(editcmd, cmdlen, "%s %s", editor, editfile); + evalstring(editcmd, 0); /* XXX - should use no JC command */ + INTON; + readcmdfile(editfile); /* XXX - should read back - quick tst */ + unlink(editfile); + } + + if (lflg == 0 && active > 0) + --active; + if (displayhist) + displayhist = 0; + return 0; +} + +STATIC const 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) { + while (*r) + STPUTC(*r++, dest); + s += plen; + *p = '\0'; /* so no more matches */ + } else + STPUTC(*s++, dest); + } + STACKSTRNUL(dest); + dest = grabstackstr(dest); + + return (dest); +} + +int +not_fcnumber(char *s) +{ + if (s == NULL) + return 0; + if (*s == '-') + s++; + return (!is_number(s)); +} + +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 = number(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); +} +#else +int +histcmd(int argc, char **argv) +{ + error("not compiled with history support"); + /* NOTREACHED */ +} +int +inputrc(int argc, char **argv) +{ + error("not compiled with history support"); + /* NOTREACHED */ +} +#endif diff --git a/bin/sh/init.h b/bin/sh/init.h new file mode 100644 index 0000000..60d924e --- /dev/null +++ b/bin/sh/init.h @@ -0,0 +1,39 @@ +/* $NetBSD: init.h,v 1.10 2003/08/07 09:05:32 agc Exp $ */ + +/*- + * 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. + * + * @(#)init.h 8.2 (Berkeley) 5/4/95 + */ + +void init(void); +void reset(void); +void initshellproc(void); diff --git a/bin/sh/input.c b/bin/sh/input.c new file mode 100644 index 0000000..dc686f5 --- /dev/null +++ b/bin/sh/input.c @@ -0,0 +1,695 @@ +/* $NetBSD: input.c,v 1.69 2019/01/16 07:14:17 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)input.c 8.3 (Berkeley) 6/9/95"; +#else +__RCSID("$NetBSD: input.c,v 1.69 2019/01/16 07:14:17 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdio.h> /* defines BUFSIZ */ +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.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 "show.h" + +#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */ + +MKINIT +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. + */ + +MKINIT +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 chars 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 */ +MKINIT int parselleft; /* copy of parsefile->lleft */ +const char *parsenextc; /* copy of parsefile->nextc */ +MKINIT struct parsefile basepf; /* top level input file */ +MKINIT char basebuf[BUFSIZ]; /* buffer for top level input file */ +struct parsefile *parsefile = &basepf; /* current input file */ +int init_editline = 0; /* editline library initialized? */ +int whichprompt; /* 1 == PS1, 2 == PS2 */ + +STATIC void pushfile(void); +static int preadfd(void); + +#ifdef mkinit +INCLUDE <stdio.h> +INCLUDE "input.h" +INCLUDE "error.h" + +INIT { + basepf.nextc = basepf.buf = basebuf; +} + +RESET { + if (exception != EXSHELLPROC) + parselleft = parsenleft = 0; /* clear input buffer */ + popallfiles(); +} + +SHELLPROC { + popallfiles(); +} +#endif + + +#if 0 /* this is unused */ +/* + * Read a line from the script. + */ + +char * +pfgets(char *line, int len) +{ + char *p = line; + int nleft = len; + int c; + + while (--nleft > 0) { + c = pgetc_macro(); + if (c == PFAKE) /* consecutive PFAKEs is impossible */ + c = pgetc_macro(); + if (c == PEOF) { + if (p == line) + return NULL; + break; + } + *p++ = c; + if (c == '\n') { + plinno++; + break; + } + } + *p = '\0'; + return line; +} +#endif + + +/* + * Read a character from the script, returning PEOF on end of file. + * Nul characters in the input are silently discarded. + */ + +int +pgetc(void) +{ + int c; + + c = pgetc_macro(); + if (c == PFAKE) + c = pgetc_macro(); + return c; +} + + +static int +preadfd(void) +{ + int nr; + char *buf = parsefile->buf; + parsenextc = buf; + + retry: +#ifndef SMALL + if (parsefile->fd == 0 && el) { + static const char *rl_cp; + static int el_len; + + if (rl_cp == NULL) + rl_cp = el_gets(el, &el_len); + if (rl_cp == NULL) + nr = el_len == 0 ? 0 : -1; + else { + nr = el_len; + if (nr > BUFSIZ - 8) + nr = BUFSIZ - 8; + memcpy(buf, rl_cp, nr); + if (nr != el_len) { + el_len -= nr; + rl_cp += nr; + } else + rl_cp = 0; + } + + } else +#endif + nr = read(parsefile->fd, buf, BUFSIZ - 8); + + + 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) { + out2str("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 stuff 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; + int more; +#ifndef SMALL + int something; +#endif + char savec; + + while (parsefile->strpush) { + if (parsenleft == -1 && parsefile->strpush->ap != NULL) + return PFAKE; + popstring(); + if (--parsenleft >= 0) + return (*parsenextc++); + } + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return PEOF; + flushout(&output); + flushout(&errout); + + again: + if (parselleft <= 0) { + if ((parselleft = preadfd()) == -1) { + parselleft = parsenleft = EOF_NLEFT; + return PEOF; + } + } + + /* p = (not const char *)parsenextc; */ + p = parsefile->buf + (parsenextc - parsefile->buf); + q = p; + + /* delete nul characters */ +#ifndef SMALL + something = 0; +#endif + for (more = 1; more;) { + switch (*p) { + case '\0': + p++; /* Skip nul */ + goto check; + + case '\t': + case ' ': + break; + + case '\n': + parsenleft = q - parsenextc; + more = 0; /* Stop processing here */ + break; + + default: +#ifndef SMALL + something = 1; +#endif + break; + } + + *q++ = *p++; + check: + if (--parselleft <= 0) { + parsenleft = q - parsenextc - 1; + if (parsenleft < 0) + goto again; + *q = '\0'; + more = 0; + } + } + + savec = *q; + *q = '\0'; + +#ifndef SMALL + if (parsefile->fd == 0 && hist && (something || whichprompt == 2)) { + HistEvent he; + + INTOFF; + history(hist, &he, whichprompt != 2 ? H_ENTER : H_APPEND, + parsenextc); + INTON; + } +#endif + + if (vflag) { + out2str(parsenextc); + flushout(out2); + } + + *q = savec; + + return *parsenextc++; +} + +/* + * Test whether we have reached EOF on input stream. + * Return true only if certain (without attempting a read). + * + * Note the similarity to the opening section of preadbuffer() + */ +int +at_eof(void) +{ + struct strpush *sp = parsefile->strpush; + + if (parsenleft > 0) /* more chars are in the buffer */ + return 0; + + while (sp != NULL) { + /* + * If any pushed string has any remaining data, + * then we are not at EOF (simulating popstring()) + */ + if (sp->prevnleft > 0) + return 0; + sp = sp->prev; + } + + /* + * If we reached real EOF and pushed it back, + * or if we are just processing a string (not reading a file) + * then there is no more. Note that if a file pushes a + * string, the file's ->buf remains present. + */ + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return 1; + + /* + * In other cases, there might be more + */ + 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; + + VTRACE(DBG_INPUT, + ("pushstring(\"%.*s\", %d)%s%s%s had: nl=%d ll=%d \"%.*s\"\n", + len, s, len, ap ? " for alias:'" : "", + ap ? ap->name : "", ap ? "'" : "", + parsenleft, parselleft, parsenleft, parsenextc)); + + INTOFF; + 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; +} + +void +popstring(void) +{ + struct strpush *sp = parsefile->strpush; + + INTOFF; + if (sp->ap) { + int alen; + + if ((alen = strlen(sp->ap->val)) > 0 && + (sp->ap->val[alen - 1] == ' ' || + sp->ap->val[alen - 1] == '\t')) + checkkwd |= CHKALIAS; + sp->ap->flag &= ~ALIASINUSE; + } + parsenextc = sp->prevstring; + parsenleft = sp->prevnleft; + parselleft = sp->prevlleft; + + VTRACE(DBG_INPUT, ("popstring()%s%s%s nl=%d ll=%d \"%.*s\"\n", + sp->ap ? " from alias:'" : "", sp->ap ? sp->ap->name : "", + sp->ap ? "'" : "", parsenleft, parselleft, parsenleft, 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) +{ + unsigned char magic[4]; + int fd; + int fd2; + struct stat sb; + + CTRACE(DBG_INPUT,("setinputfile(\"%s\", %spush)\n",fname,push?"":"no")); + + INTOFF; + if ((fd = open(fname, O_RDONLY)) < 0) + error("Can't open %s", fname); + + /* Since the message "Syntax error: "(" unexpected" is not very + * helpful, we check if the file starts with the ELF magic to + * avoid that message. The first lseek tries to make sure that + * we can later rewind the file. + */ + if (fstat(fd, &sb) == 0 && S_ISREG(sb.st_mode) && + lseek(fd, 0, SEEK_SET) == 0) { + if (read(fd, magic, 4) == 4) { + if (memcmp(magic, "\177ELF", 4) == 0) { + (void)close(fd); + error("Cannot execute ELF binary %s", fname); + } + } + if (lseek(fd, 0, SEEK_SET) != 0) { + (void)close(fd); + error("Cannot rewind the file %s", fname); + } + } + + fd2 = to_upper_fd(fd); /* closes fd, returns higher equiv */ + if (fd2 == fd) { + (void) close(fd); + error("Out of file descriptors"); + } + + setinputfd(fd2, push); + INTON; +} + +/* + * When a shell fd needs to be altered (when the user wants to use + * the same fd - rare, but happens - we need to locate the ref to + * the fd, and update it. This happens via a callback. + * This is the callback func for fd's used for shell input + */ +static void +input_fd_swap(int from, int to) +{ + struct parsefile *pf; + + pf = parsefile; + while (pf != NULL) { /* don't need to stop at basepf */ + if (pf->fd == from) + pf->fd = to; + pf = pf->prev; + } +} + +/* + * Like setinputfile, but takes an open file descriptor. Call this with + * interrupts off. + */ + +void +setinputfd(int fd, int push) +{ + VTRACE(DBG_INPUT, ("setinputfd(%d, %spush)\n", fd, push?"":"no")); + + register_sh_fd(fd, input_fd_swap); + (void) fcntl(fd, F_SETFD, FD_CLOEXEC); + if (push) + pushfile(); + if (parsefile->fd > 0) + sh_close(parsefile->fd); + parsefile->fd = fd; + if (parsefile->buf == NULL) + parsefile->buf = ckmalloc(BUFSIZ); + parselleft = parsenleft = 0; + plinno = 1; + + CTRACE(DBG_INPUT, ("setinputfd(%d, %spush) done; plinno=1\n", fd, + push ? "" : "no")); +} + + +/* + * Like setinputfile, but takes input from a string. + */ + +void +setinputstring(char *string, int push, int line1) +{ + + INTOFF; + if (push) /* XXX: always, as it happens */ + pushfile(); + parsenextc = string; + parselleft = parsenleft = strlen(string); + plinno = line1; + + CTRACE(DBG_INPUT, + ("setinputstring(\"%.20s%s\" (%d), %spush, @ %d)\n", string, + (parsenleft > 20 ? "..." : ""), parsenleft, push?"":"no", line1)); + 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; + + VTRACE(DBG_INPUT, + ("pushfile(): fd=%d buf=%p nl=%d ll=%d \"%.*s\" plinno=%d\n", + parsefile->fd, parsefile->buf, parsenleft, parselleft, + parsenleft, parsenextc, plinno)); + + 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; + pf->buf = NULL; + parsefile = pf; +} + + +void +popfile(void) +{ + struct parsefile *pf = parsefile; + + INTOFF; + if (pf->fd >= 0) + sh_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; + + VTRACE(DBG_INPUT, + ("popfile(): fd=%d buf=%p nl=%d ll=%d \"%.*s\" plinno:%d->%d\n", + parsefile->fd, parsefile->buf, parsenleft, parselleft, + parsenleft, parsenextc, plinno, parsefile->linno)); + + 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. + * + * Takes one arg, vfork, which tells it to not modify its global vars + * as it is still running in the parent. + * + * This code is (probably) unnecessary as the 'close on exec' flag is + * set and should be enough. In the vfork case it is definitely wrong + * to close the fds as another fork() may be done later to feed data + * from a 'here' document into a pipe and we don't want to close the + * pipe! + */ + +void +closescript(int vforked) +{ + if (vforked) + return; + popallfiles(); + if (parsefile->fd > 0) { + sh_close(parsefile->fd); + parsefile->fd = 0; + } +} diff --git a/bin/sh/input.h b/bin/sh/input.h new file mode 100644 index 0000000..c40d4aa --- /dev/null +++ b/bin/sh/input.h @@ -0,0 +1,69 @@ +/* $NetBSD: input.h,v 1.21 2018/08/19 23:50:27 kre Exp $ */ + +/*- + * 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 + */ + +/* 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 */ +extern int init_editline; /* 0 == not setup, 1 == OK, -1 == failed */ +extern int whichprompt; /* 1 ==> PS1, 2 ==> PS2 */ + +struct alias; +struct parsefile; + +char *pfgets(char *, int); +int pgetc(void); +int preadbuffer(void); +int at_eof(void); +void pungetc(void); +void pushstring(const char *, int, struct alias *); +void popstring(void); +void setinputfile(const char *, int); +void setinputfd(int, int); +void setinputstring(char *, int, int); +void popfile(void); +struct parsefile *getcurrentfile(void); +void popfilesupto(struct parsefile *); +void popallfiles(void); +void closescript(int); + +#define pgetc_macro() (--parsenleft >= 0? *parsenextc++ : preadbuffer()) diff --git a/bin/sh/jobs.c b/bin/sh/jobs.c new file mode 100644 index 0000000..d066bb8 --- /dev/null +++ b/bin/sh/jobs.c @@ -0,0 +1,1812 @@ +/* $NetBSD: jobs.c,v 1.103 2018/12/03 02:38:30 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: jobs.c,v 1.103 2018/12/03 02:38:30 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdio.h> +#include <fcntl.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <paths.h> +#include <sys/types.h> +#include <sys/param.h> +#ifdef BSD +#include <sys/wait.h> +#include <sys/time.h> +#include <sys/resource.h> +#endif +#include <sys/ioctl.h> + +#include "shell.h" +#if JOBS +#if OLD_TTY_DRIVER +#include "sgtty.h" +#else +#include <termios.h> +#endif +#undef CEOF /* syntax.h redefines this */ +#endif +#include "redir.h" +#include "show.h" +#include "main.h" +#include "parser.h" +#include "nodes.h" +#include "jobs.h" +#include "var.h" +#include "options.h" +#include "builtins.h" +#include "trap.h" +#include "syntax.h" +#include "input.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" + + +#ifndef WCONTINUED +#define WCONTINUED 0 /* So we can compile on old systems */ +#endif +#ifndef WIFCONTINUED +#define WIFCONTINUED(x) (0) /* ditto */ +#endif + + +static struct job *jobtab; /* array of jobs */ +static int njobs; /* size of array */ +static int jobs_invalid; /* set in child */ +MKINIT pid_t backgndpid = -1; /* pid of last background process */ +#if JOBS +int initialpgrp; /* pgrp of shell on invocation */ +static int curjob = -1; /* current job */ +#endif +static int ttyfd = -1; + +STATIC void restartjob(struct job *); +STATIC void freejob(struct job *); +STATIC struct job *getjob(const char *, int); +STATIC int dowait(int, struct job *, struct job **); +#define WBLOCK 1 +#define WNOFREE 2 +#define WSILENT 4 +STATIC int jobstatus(const struct job *, int); +STATIC int waitproc(int, struct job *, int *); +STATIC void cmdtxt(union node *); +STATIC void cmdlist(union node *, int); +STATIC void cmdputs(const char *); +inline static void cmdputi(int); + +#ifdef SYSV +STATIC int onsigchild(void); +#endif + +#ifdef OLD_TTY_DRIVER +static pid_t tcgetpgrp(int fd); +static int tcsetpgrp(int fd, pid_t pgrp); + +static pid_t +tcgetpgrp(int fd) +{ + pid_t pgrp; + if (ioctl(fd, TIOCGPGRP, (char *)&pgrp) == -1) + return -1; + else + return pgrp; +} + +static int +tcsetpgrp(int fd, pid_tpgrp) +{ + return ioctl(fd, TIOCSPGRP, (char *)&pgrp); +} +#endif + +static void +ttyfd_change(int from, int to) +{ + if (ttyfd == from) + ttyfd = to; +} + +/* + * Turn job control on and off. + * + * Note: This code assumes that the third arg to ioctl is a character + * pointer, which is true on Berkeley systems but not System V. Since + * System V doesn't have job control yet, this isn't a problem now. + */ + +MKINIT int jobctl; + +void +setjobctl(int on) +{ +#ifdef OLD_TTY_DRIVER + int ldisc; +#endif + + if (on == jobctl || rootshell == 0) + return; + if (on) { +#if defined(FIOCLEX) || defined(FD_CLOEXEC) + int i; + + if (ttyfd != -1) + sh_close(ttyfd); + if ((ttyfd = open("/dev/tty", O_RDWR)) == -1) { + for (i = 0; i < 3; i++) { + if (isatty(i) && (ttyfd = dup(i)) != -1) + break; + } + if (i == 3) + goto out; + } + ttyfd = to_upper_fd(ttyfd); /* Move to a high fd */ + register_sh_fd(ttyfd, ttyfd_change); +#else + out2str("sh: Need FIOCLEX or FD_CLOEXEC to support job control"); + goto out; +#endif + do { /* while we are in the background */ + if ((initialpgrp = tcgetpgrp(ttyfd)) < 0) { + out: + out2str("sh: can't access tty; job control turned off\n"); + mflag = 0; + return; + } + if (initialpgrp == -1) + initialpgrp = getpgrp(); + else if (initialpgrp != getpgrp()) { + killpg(0, SIGTTIN); + continue; + } + } while (0); + +#ifdef OLD_TTY_DRIVER + if (ioctl(ttyfd, TIOCGETD, (char *)&ldisc) < 0 + || ldisc != NTTYDISC) { + out2str("sh: need new tty driver to run job control; job control turned off\n"); + mflag = 0; + return; + } +#endif + setsignal(SIGTSTP, 0); + setsignal(SIGTTOU, 0); + setsignal(SIGTTIN, 0); + if (getpgrp() != rootpid && setpgid(0, rootpid) == -1) + error("Cannot set process group (%s) at %d", + strerror(errno), __LINE__); + if (tcsetpgrp(ttyfd, rootpid) == -1) + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + } else { /* turning job control off */ + if (getpgrp() != initialpgrp && setpgid(0, initialpgrp) == -1) + error("Cannot set process group (%s) at %d", + strerror(errno), __LINE__); + if (tcsetpgrp(ttyfd, initialpgrp) == -1) + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + sh_close(ttyfd); + ttyfd = -1; + setsignal(SIGTSTP, 0); + setsignal(SIGTTOU, 0); + setsignal(SIGTTIN, 0); + } + jobctl = on; +} + + +#ifdef mkinit +INCLUDE <stdlib.h> + +SHELLPROC { + backgndpid = -1; +#if JOBS + jobctl = 0; +#endif +} + +#endif + + + +#if JOBS +static int +do_fgcmd(const char *arg_ptr) +{ + struct job *jp; + int i; + int status; + + if (jobs_invalid) + error("No current jobs"); + jp = getjob(arg_ptr, 0); + if (jp->jobctl == 0) + error("job not created under job control"); + out1fmt("%s", jp->ps[0].cmd); + for (i = 1; i < jp->nprocs; i++) + out1fmt(" | %s", jp->ps[i].cmd ); + out1c('\n'); + flushall(); + + for (i = 0; i < jp->nprocs; i++) + if (tcsetpgrp(ttyfd, jp->ps[i].pid) != -1) + break; + + if (i >= jp->nprocs) { + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + } + restartjob(jp); + INTOFF; + status = waitforjob(jp); + INTON; + return status; +} + +int +fgcmd(int argc, char **argv) +{ + nextopt(""); + return do_fgcmd(*argptr); +} + +int +fgcmd_percent(int argc, char **argv) +{ + nextopt(""); + return do_fgcmd(*argv); +} + +static void +set_curjob(struct job *jp, int mode) +{ + struct job *jp1, *jp2; + int i, ji; + + ji = jp - jobtab; + + /* first remove from list */ + if (ji == curjob) + curjob = jp->prev_job; + else { + for (i = 0; i < njobs; i++) { + if (jobtab[i].prev_job != ji) + continue; + jobtab[i].prev_job = jp->prev_job; + break; + } + } + + /* Then re-insert in correct position */ + switch (mode) { + case 0: /* job being deleted */ + jp->prev_job = -1; + break; + case 1: /* newly created job or backgrounded job, + put after all stopped jobs. */ + if (curjob != -1 && jobtab[curjob].state == JOBSTOPPED) { + for (jp1 = jobtab + curjob; ; jp1 = jp2) { + if (jp1->prev_job == -1) + break; + jp2 = jobtab + jp1->prev_job; + if (jp2->state != JOBSTOPPED) + break; + } + jp->prev_job = jp1->prev_job; + jp1->prev_job = ji; + break; + } + /* FALLTHROUGH */ + case 2: /* newly stopped job - becomes curjob */ + jp->prev_job = curjob; + curjob = ji; + break; + } +} + +int +bgcmd(int argc, char **argv) +{ + struct job *jp; + int i; + + nextopt(""); + if (jobs_invalid) + error("No current jobs"); + do { + jp = getjob(*argptr, 0); + if (jp->jobctl == 0) + error("job not created under job control"); + set_curjob(jp, 1); + out1fmt("[%ld] %s", (long)(jp - jobtab + 1), jp->ps[0].cmd); + for (i = 1; i < jp->nprocs; i++) + out1fmt(" | %s", jp->ps[i].cmd ); + out1c('\n'); + flushall(); + restartjob(jp); + } while (*argptr && *++argptr); + return 0; +} + + +STATIC void +restartjob(struct job *jp) +{ + struct procstat *ps; + int i; + + if (jp->state == JOBDONE) + return; + INTOFF; + for (i = 0; i < jp->nprocs; i++) + if (killpg(jp->ps[i].pid, SIGCONT) != -1) + break; + if (i >= jp->nprocs) + error("Cannot continue job (%s)", strerror(errno)); + for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { + if (WIFSTOPPED(ps->status)) { + VTRACE(DBG_JOBS, ( + "restartjob: [%zu] pid %d status change" + " from %#x (stopped) to -1 (running)\n", + (size_t)(jp-jobtab+1), ps->pid, ps->status)); + ps->status = -1; + jp->state = JOBRUNNING; + } + } + INTON; +} +#endif + +inline static void +cmdputi(int n) +{ + char str[20]; + + fmtstr(str, sizeof str, "%d", n); + cmdputs(str); +} + +static void +showjob(struct output *out, struct job *jp, int mode) +{ + int procno; + int st; + struct procstat *ps; + int col; + char s[64]; + +#if JOBS + if (mode & SHOW_PGID) { + /* just output process (group) id of pipeline */ + outfmt(out, "%ld\n", (long)jp->ps->pid); + return; + } +#endif + + procno = jp->nprocs; + if (!procno) + return; + + if (mode & SHOW_PID) + mode |= SHOW_MULTILINE; + + if ((procno > 1 && !(mode & SHOW_MULTILINE)) + || (mode & SHOW_SIGNALLED)) { + /* See if we have more than one status to report */ + ps = jp->ps; + st = ps->status; + do { + int st1 = ps->status; + if (st1 != st) + /* yes - need multi-line output */ + mode |= SHOW_MULTILINE; + if (st1 == -1 || !(mode & SHOW_SIGNALLED) || WIFEXITED(st1)) + continue; + if (WIFSTOPPED(st1) || ((st1 = WTERMSIG(st1) & 0x7f) + && st1 != SIGINT && st1 != SIGPIPE)) + mode |= SHOW_ISSIG; + + } while (ps++, --procno); + procno = jp->nprocs; + } + + if (mode & SHOW_SIGNALLED && !(mode & SHOW_ISSIG)) { + if (jp->state == JOBDONE && !(mode & SHOW_NO_FREE)) { + VTRACE(DBG_JOBS, ("showjob: freeing job %d\n", + jp - jobtab + 1)); + freejob(jp); + } + return; + } + + for (ps = jp->ps; --procno >= 0; ps++) { /* for each process */ + if (ps == jp->ps) + fmtstr(s, 16, "[%ld] %c ", + (long)(jp - jobtab + 1), +#if JOBS + jp - jobtab == curjob ? + '+' : + curjob != -1 && + jp - jobtab == jobtab[curjob].prev_job ? + '-' : +#endif + ' '); + else + fmtstr(s, 16, " " ); + col = strlen(s); + if (mode & SHOW_PID) { + fmtstr(s + col, 16, "%ld ", (long)ps->pid); + col += strlen(s + col); + } + if (ps->status == -1) { + scopy("Running", s + col); + } else if (WIFEXITED(ps->status)) { + st = WEXITSTATUS(ps->status); + if (st) + fmtstr(s + col, 16, "Done(%d)", st); + else + fmtstr(s + col, 16, "Done"); + } else { +#if JOBS + if (WIFSTOPPED(ps->status)) + st = WSTOPSIG(ps->status); + else /* WIFSIGNALED(ps->status) */ +#endif + st = WTERMSIG(ps->status); + scopyn(strsignal(st), s + col, 32); + if (WCOREDUMP(ps->status)) { + col += strlen(s + col); + scopyn(" (core dumped)", s + col, 64 - col); + } + } + col += strlen(s + col); + outstr(s, out); + do { + outc(' ', out); + col++; + } while (col < 30); + outstr(ps->cmd, out); + if (mode & SHOW_MULTILINE) { + if (procno > 0) { + outc(' ', out); + outc('|', out); + } + } else { + while (--procno >= 0) + outfmt(out, " | %s", (++ps)->cmd ); + } + outc('\n', out); + } + flushout(out); + jp->flags &= ~JOBCHANGED; + if (jp->state == JOBDONE && !(mode & SHOW_NO_FREE)) + freejob(jp); +} + +int +jobscmd(int argc, char **argv) +{ + int mode, m; + + mode = 0; + while ((m = nextopt("lp"))) + if (m == 'l') + mode = SHOW_PID; + else + mode = SHOW_PGID; + if (!iflag) + mode |= SHOW_NO_FREE; + if (*argptr) + do + showjob(out1, getjob(*argptr,0), mode); + while (*++argptr); + else + showjobs(out1, mode); + return 0; +} + + +/* + * 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(struct output *out, int mode) +{ + int jobno; + struct job *jp; + int silent = 0, gotpid; + + CTRACE(DBG_JOBS, ("showjobs(%x) called\n", mode)); + + /* If not even one one job changed, there is nothing to do */ + gotpid = dowait(WSILENT, NULL, NULL); + while (dowait(WSILENT, NULL, NULL) > 0) + continue; +#ifdef JOBS + /* + * Check if we are not in our foreground group, and if not + * put us in it. + */ + if (mflag && gotpid != -1 && tcgetpgrp(ttyfd) != getpid()) { + if (tcsetpgrp(ttyfd, getpid()) == -1) + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + VTRACE(DBG_JOBS|DBG_INPUT, ("repaired tty process group\n")); + silent = 1; + } +#endif + + for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) { + if (!jp->used) + continue; + if (jp->nprocs == 0) { + if (!jobs_invalid) + freejob(jp); + continue; + } + if ((mode & SHOW_CHANGED) && !(jp->flags & JOBCHANGED)) + continue; + if (silent && (jp->flags & JOBCHANGED)) { + jp->flags &= ~JOBCHANGED; + continue; + } + showjob(out, jp, mode); + } +} + +/* + * Mark a job structure as unused. + */ + +STATIC void +freejob(struct job *jp) +{ + INTOFF; + if (jp->ps != &jp->ps0) { + ckfree(jp->ps); + jp->ps = &jp->ps0; + } + jp->nprocs = 0; + jp->used = 0; +#if JOBS + set_curjob(jp, 0); +#endif + INTON; +} + +/* + * Extract the status of a completed job (for $?) + */ +STATIC int +jobstatus(const struct job *jp, int raw) +{ + int status = 0; + int retval; + + if ((jp->flags & JPIPEFAIL) && jp->nprocs) { + int i; + + for (i = 0; i < jp->nprocs; i++) + if (jp->ps[i].status != 0) + status = jp->ps[i].status; + } else + status = jp->ps[jp->nprocs ? jp->nprocs - 1 : 0].status; + + if (raw) + return status; + + if (WIFEXITED(status)) + retval = WEXITSTATUS(status); +#if JOBS + else if (WIFSTOPPED(status)) + retval = WSTOPSIG(status) + 128; +#endif + else { + /* XXX: limits number of signals */ + retval = WTERMSIG(status) + 128; + } + + return retval; +} + + + +int +waitcmd(int argc, char **argv) +{ + struct job *job, *last; + int retval; + struct job *jp; + int i; + int any = 0; + int found; + char *pid = NULL, *fpid; + char **arg; + char idstring[20]; + + while ((i = nextopt("np:")) != '\0') { + switch (i) { + case 'n': + any = 1; + break; + case 'p': + if (pid) + error("more than one -p unsupported"); + pid = optionarg; + break; + } + } + + if (pid != NULL) { + if (!validname(pid, '\0', NULL)) + error("invalid name: -p '%s'", pid); + if (unsetvar(pid, 0)) + error("%s readonly", pid); + } + + /* + * If we have forked, and not yet created any new jobs, then + * we have no children, whatever jobtab claims, + * so simply return in that case. + * + * The return code is 127 if we had any pid args (none are found) + * or if we had -n (nothing exited), but 0 for plain old "wait". + */ + if (jobs_invalid) { + CTRACE(DBG_WAIT, ("builtin wait%s%s in child, invalid jobtab\n", + any ? " -n" : "", *argptr ? " pid..." : "")); + return (any || *argptr) ? 127 : 0; + } + + /* clear stray flags left from previous waitcmd */ + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + jp->flags &= ~JOBWANTED; + jp->ref = NULL; + } + + CTRACE(DBG_WAIT, + ("builtin wait%s%s\n", any ? " -n" : "", *argptr ? " pid..." : "")); + + /* + * First, validate the jobnum args, count how many refer to + * (different) running jobs, and if we had -n, and found that one has + * already finished, we return that one. Otherwise remember + * which ones we are looking for (JOBWANTED). + */ + found = 0; + last = NULL; + for (arg = argptr; *arg; arg++) { + last = jp = getjob(*arg, 1); + if (!jp) + continue; + if (jp->ref == NULL) + jp->ref = *arg; + if (any && jp->state == JOBDONE) { + /* + * We just want any of them, and this one is + * ready for consumption, bon apetit ... + */ + retval = jobstatus(jp, 0); + if (pid) + setvar(pid, *arg, 0); + if (!iflag) + freejob(jp); + CTRACE(DBG_WAIT, ("wait -n found %s already done: %d\n", *arg, retval)); + return retval; + } + if (!(jp->flags & JOBWANTED)) { + /* + * It is possible to list the same job several + * times - the obvious "wait 1 1 1" or + * "wait %% %2 102" where job 2 is current and pid 102 + * However many times it is requested, it is found once. + */ + found++; + jp->flags |= JOBWANTED; + } + job = jp; + } + + VTRACE(DBG_WAIT, ("wait %s%s%sfound %d candidates (last %s)\n", + any ? "-n " : "", *argptr ? *argptr : "", + argptr[0] && argptr[1] ? "... " : " ", found, + job ? (job->ref ? job->ref : "<no-arg>") : "none")); + + /* + * If we were given a list of jobnums: + * and none of those exist, then we're done. + */ + if (*argptr && found == 0) + return 127; + + /* + * Otherwise we need to wait for something to complete + * When it does, we check and see if it is one of the + * jobs we're waiting on, and if so, we clean it up. + * If we had -n, then we're done, otherwise we do it all again + * until all we had listed are done, of if there were no + * jobnum args, all are done. + */ + + retval = any || *argptr ? 127 : 0; + fpid = NULL; + for (;;) { + VTRACE(DBG_WAIT, ("wait waiting (%d remain): ", found)); + for (jp = jobtab, i = njobs; --i >= 0; jp++) { + if (jp->used && jp->flags & JOBWANTED && + jp->state == JOBDONE) + break; + if (jp->used && jp->state == JOBRUNNING) + break; + } + if (i < 0) { + CTRACE(DBG_WAIT, ("nothing running (ret: %d) fpid %s\n", + retval, fpid ? fpid : "unset")); + if (pid && fpid) + setvar(pid, fpid, 0); + return retval; + } + VTRACE(DBG_WAIT, ("found @%d/%d state: %d\n", njobs-i, njobs, + jp->state)); + + /* + * There is at least 1 job running, so we can + * safely wait() for something to exit. + */ + if (jp->state == JOBRUNNING) { + job = NULL; + if ((i = dowait(WBLOCK|WNOFREE, NULL, &job)) == -1) + return 128 + lastsig(); + + /* + * one of the job's processes exited, + * but there are more + */ + if (job->state == JOBRUNNING) + continue; + } else + job = jp; /* we want this, and it is done */ + + if (job->flags & JOBWANTED || (*argptr == 0 && any)) { + int rv; + + job->flags &= ~JOBWANTED; /* got it */ + rv = jobstatus(job, 0); + VTRACE(DBG_WAIT, ( + "wanted %d (%s) done: st=%d", i, + job->ref ? job->ref : "", rv)); + if (any || job == last) { + retval = rv; + fpid = job->ref; + + VTRACE(DBG_WAIT, (" save")); + if (pid) { + /* + * don't need fpid unless we are going + * to return it. + */ + if (fpid == NULL) { + /* + * this only happens with "wait -n" + * (that is, no pid args) + */ + snprintf(idstring, sizeof idstring, + "%d", job->ps[ job->nprocs ? + job->nprocs-1 : + 0 ].pid); + fpid = idstring; + } + VTRACE(DBG_WAIT, (" (for %s)", fpid)); + } + } + + if (job->state == JOBDONE) { + VTRACE(DBG_WAIT, (" free")); + freejob(job); + } + + if (any || (found > 0 && --found == 0)) { + if (pid && fpid) + setvar(pid, fpid, 0); + VTRACE(DBG_WAIT, (" return %d\n", retval)); + return retval; + } + VTRACE(DBG_WAIT, ("\n")); + continue; + } + + /* this is to handle "wait" (no args) */ + if (found == 0 && job->state == JOBDONE) { + VTRACE(DBG_JOBS|DBG_WAIT, ("Cleanup: %d\n", i)); + freejob(job); + } + } +} + + +int +jobidcmd(int argc, char **argv) +{ + struct job *jp; + int i; + int pg = 0, onep = 0, job = 0; + + while ((i = nextopt("gjp"))) { + switch (i) { + case 'g': pg = 1; break; + case 'j': job = 1; break; + case 'p': onep = 1; break; + } + } + CTRACE(DBG_JOBS, ("jobidcmd%s%s%s%s %s\n", pg ? " -g" : "", + onep ? " -p" : "", job ? " -j" : "", jobs_invalid ? " [inv]" : "", + *argptr ? *argptr : "<implicit %%>")); + if (pg + onep + job > 1) + error("-g -j and -p options cannot be combined"); + + if (argptr[0] && argptr[1]) + error("usage: jobid [-g|-p|-r] jobid"); + + jp = getjob(*argptr, 0); + if (job) { + out1fmt("%%%zu\n", (size_t)(jp - jobtab + 1)); + return 0; + } + if (pg) { + if (jp->pgrp != 0) { + out1fmt("%ld\n", (long)jp->pgrp); + return 0; + } + return 1; + } + if (onep) { + i = jp->nprocs - 1; + if (i < 0) + return 1; + out1fmt("%ld\n", (long)jp->ps[i].pid); + return 0; + } + for (i = 0 ; i < jp->nprocs ; ) { + out1fmt("%ld", (long)jp->ps[i].pid); + out1c(++i < jp->nprocs ? ' ' : '\n'); + } + return 0; +} + +int +getjobpgrp(const char *name) +{ + struct job *jp; + + if (jobs_invalid) + error("No such job: %s", name); + jp = getjob(name, 1); + if (jp == 0) + return 0; + return -jp->ps[0].pid; +} + +/* + * Convert a job name to a job structure. + */ + +STATIC struct job * +getjob(const char *name, int noerror) +{ + int jobno = -1; + struct job *jp; + int pid; + int i; + const char *err_msg = "No such job: %s"; + + if (name == NULL) { +#if JOBS + jobno = curjob; +#endif + err_msg = "No current job"; + } else if (name[0] == '%') { + if (is_number(name + 1)) { + jobno = number(name + 1) - 1; + } else if (!name[1] || !name[2]) { + switch (name[1]) { +#if JOBS + case 0: + case '+': + case '%': + jobno = curjob; + err_msg = "No current job"; + break; + case '-': + jobno = curjob; + if (jobno != -1) + jobno = jobtab[jobno].prev_job; + err_msg = "No previous job"; + break; +#endif + default: + goto check_pattern; + } + } else { + struct job *found; + check_pattern: + found = NULL; + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (!jp->used || jp->nprocs <= 0) + continue; + if ((name[1] == '?' + && strstr(jp->ps[0].cmd, name + 2)) + || prefix(name + 1, jp->ps[0].cmd)) { + if (found) { + err_msg = "%s: ambiguous"; + found = 0; + break; + } + found = jp; + } + } + if (found) + return found; + } + + } else if (is_number(name)) { + pid = 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; + } + } + + if (jobno >= 0 && jobno < njobs) { + jp = jobtab + jobno; + if (jp->used) + return jp; + } + if (!noerror) + error(err_msg, name); + return 0; +} + + + +/* + * Return a new job structure, + */ + +struct job * +makejob(union node *node, int nprocs) +{ + int i; + struct job *jp; + + if (jobs_invalid) { + for (i = njobs, jp = jobtab ; --i >= 0 ; jp++) { + if (jp->used) + freejob(jp); + } + jobs_invalid = 0; + } + + for (i = njobs, jp = jobtab ; ; jp++) { + if (--i < 0) { + INTOFF; + if (njobs == 0) { + jobtab = ckmalloc(4 * sizeof jobtab[0]); + } else { + jp = ckmalloc((njobs + 4) * sizeof jobtab[0]); + memcpy(jp, jobtab, njobs * sizeof jp[0]); + /* 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 ; njobs++) { + jobtab[njobs].used = 0; + jobtab[njobs].prev_job = -1; + } + INTON; + break; + } + if (jp->used == 0) + break; + } + INTOFF; + jp->state = JOBRUNNING; + jp->used = 1; + jp->flags = pipefail ? JPIPEFAIL : 0; + jp->nprocs = 0; + jp->pgrp = 0; +#if JOBS + jp->jobctl = jobctl; + set_curjob(jp, 1); +#endif + if (nprocs > 1) { + jp->ps = ckmalloc(nprocs * sizeof (struct procstat)); + } else { + jp->ps = &jp->ps0; + } + INTON; + VTRACE(DBG_JOBS, ("makejob(%p, %d)%s returns %%%d\n", (void *)node, + nprocs, (jp->flags&JPIPEFAIL)?" PF":"", jp - jobtab + 1)); + return jp; +} + + +/* + * Fork off 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). + */ + +int +forkshell(struct job *jp, union node *n, int mode) +{ + pid_t pid; + int serrno; + + CTRACE(DBG_JOBS, ("forkshell(%%%d, %p, %d) called\n", + jp - jobtab, n, mode)); + + switch ((pid = fork())) { + case -1: + serrno = errno; + VTRACE(DBG_JOBS, ("Fork failed, errno=%d\n", serrno)); + INTON; + error("Cannot fork (%s)", strerror(serrno)); + break; + case 0: + SHELL_FORKED(); + forkchild(jp, n, mode, 0); + return 0; + default: + return forkparent(jp, n, mode, pid); + } +} + +int +forkparent(struct job *jp, union node *n, int mode, pid_t pid) +{ + int pgrp; + + if (rootshell && mode != FORK_NOJOB && mflag) { + if (jp == NULL || jp->nprocs == 0) + pgrp = pid; + else + pgrp = jp->ps[0].pid; + jp->pgrp = pgrp; + /* This can fail because we are doing it in the child also */ + (void)setpgid(pid, pgrp); + } + if (mode == FORK_BG) + backgndpid = pid; /* set $! */ + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd[0] = 0; + if (/* iflag && rootshell && */ n) + commandtext(ps, n); + } + CTRACE(DBG_JOBS, ("In parent shell: child = %d (mode %d)\n",pid,mode)); + return pid; +} + +void +forkchild(struct job *jp, union node *n, int mode, int vforked) +{ + int wasroot; + int pgrp; + const char *devnull = _PATH_DEVNULL; + const char *nullerr = "Can't open %s"; + + wasroot = rootshell; + CTRACE(DBG_JOBS, ("Child shell %d %sforked from %d (mode %d)\n", + getpid(), vforked?"v":"", getppid(), mode)); + + if (!vforked) { + rootshell = 0; + handler = &main_handler; + } + + closescript(vforked); + clear_traps(vforked); +#if JOBS + if (!vforked) + 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; + /* This can fail because we are doing it in the parent also */ + (void)setpgid(0, pgrp); + if (mode == FORK_FG) { + if (tcsetpgrp(ttyfd, pgrp) == -1) + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + } + setsignal(SIGTSTP, vforked); + setsignal(SIGTTOU, vforked); + } else if (mode == FORK_BG) { + ignoresig(SIGINT, vforked); + ignoresig(SIGQUIT, vforked); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(devnull, O_RDONLY) != 0) + error(nullerr, devnull); + } + } +#else + if (mode == FORK_BG) { + ignoresig(SIGINT, vforked); + ignoresig(SIGQUIT, vforked); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(devnull, O_RDONLY) != 0) + error(nullerr, devnull); + } + } +#endif + if (wasroot && iflag) { + setsignal(SIGINT, vforked); + setsignal(SIGQUIT, vforked); + setsignal(SIGTERM, vforked); + } + + if (!vforked) + jobs_invalid = 1; +} + +/* + * 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 + * forground 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) +{ +#if JOBS + int mypgrp = getpgrp(); +#endif + int status; + int st; + + INTOFF; + VTRACE(DBG_JOBS, ("waitforjob(%%%d) called\n", jp - jobtab + 1)); + while (jp->state == JOBRUNNING) { + dowait(WBLOCK, jp, NULL); + } +#if JOBS + if (jp->jobctl) { + if (tcsetpgrp(ttyfd, mypgrp) == -1) + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + } + if (jp->state == JOBSTOPPED && curjob != jp - jobtab) + set_curjob(jp, 2); +#endif + status = jobstatus(jp, 1); + + /* 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; + + VTRACE(DBG_JOBS, ("waitforjob: job %d, nproc %d, status %d, st %x\n", + jp - jobtab + 1, jp->nprocs, status, st)); +#if JOBS + if (jp->jobctl) { + /* + * This is truly gross. + * If we're doing job control, then we did a TIOCSPGRP which + * caused us (the shell) to no longer be in the controlling + * session -- so we wouldn't have seen any ^C/SIGINT. So, we + * intuit from the subprocess exit status whether a SIGINT + * occurred, and if so interrupt ourselves. Yuck. - mycroft + */ + if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) + raise(SIGINT); + } +#endif + if (! JOBS || jp->state == JOBDONE) + freejob(jp); + INTON; + return st; +} + + + +/* + * Wait for a process to terminate. + */ + +STATIC int +dowait(int flags, struct job *job, struct job **changed) +{ + int pid; + int status; + struct procstat *sp; + struct job *jp; + struct job *thisjob; + int done; + int stopped; + + VTRACE(DBG_JOBS|DBG_PROCS, ("dowait(%x) called\n", flags)); + + if (changed != NULL) + *changed = NULL; + + do { + pid = waitproc(flags & WBLOCK, job, &status); + VTRACE(DBG_JOBS|DBG_PROCS, ("wait returns pid %d, status %#x\n", + pid, status)); + } while (pid == -1 && errno == EINTR && pendingsigs == 0); + if (pid <= 0) + return pid; + INTOFF; + thisjob = NULL; + for (jp = jobtab ; jp < jobtab + njobs ; jp++) { + if (jp->used) { + 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))) { + VTRACE(DBG_JOBS | DBG_PROCS, + ("Job %d: changing status of proc %d from %#x to %#x\n", + jp - jobtab + 1, pid, + sp->status, status)); + if (WIFCONTINUED(status)) { + if (sp->status != -1) + jp->flags |= JOBCHANGED; + sp->status = -1; + jp->state = 0; + } else + sp->status = status; + thisjob = jp; + if (changed != NULL) + *changed = 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) { + VTRACE(DBG_JOBS, + ("Job %d: changing state from %d to %d\n", + jp - jobtab + 1, jp->state, state)); + jp->state = state; +#if JOBS + if (done) + set_curjob(jp, 0); +#endif + } + } + } + } + + if (thisjob && + (thisjob->state != JOBRUNNING || thisjob->flags & JOBCHANGED)) { + int mode = 0; + + if (!rootshell || !iflag) + mode = SHOW_SIGNALLED; + if ((job == thisjob && (flags & WNOFREE) == 0) || + job != thisjob) + mode = SHOW_SIGNALLED | SHOW_NO_FREE; + if (mode && (flags & WSILENT) == 0) + showjob(out2, thisjob, mode); + else { + VTRACE(DBG_JOBS, + ("Not printing status, rootshell=%d, job=%p\n", + rootshell, job)); + thisjob->flags |= JOBCHANGED; + } + } + + INTON; + return pid; +} + + + +/* + * Do a wait system call. If job control is compiled in, we accept + * stopped processes. If block is zero, we return a value of zero + * rather than blocking. + * + * System V doesn't have a non-blocking wait system call. It does + * have a SIGCLD signal that is sent to a process when one of its + * children dies. The obvious way to use SIGCLD would be to install + * a handler for SIGCLD which simply bumped a counter when a SIGCLD + * was received, and have waitproc bump another counter when it got + * the status of a process. Waitproc would then know that a wait + * system call would not block if the two counters were different. + * This approach doesn't work because if a process has children that + * have not been waited for, System V will send it a SIGCLD when it + * installs a signal handler for SIGCLD. What this means is that when + * a child exits, the shell will be sent SIGCLD signals continuously + * until is runs out of stack space, unless it does a wait call before + * restoring the signal handler. The code below takes advantage of + * this (mis)feature by installing a signal handler for SIGCLD and + * then checking to see whether it was called. If there are any + * children to be waited for, it will be. + * + * If neither SYSV nor BSD is defined, we don't implement nonblocking + * waits at all. In this case, the user will not be informed when + * a background process until the next time she runs a real program + * (as opposed to running a builtin command or just typing return), + * and the jobs command may give out of date information. + */ + +#ifdef SYSV +STATIC int gotsigchild; + +STATIC int onsigchild() { + gotsigchild = 1; +} +#endif + + +STATIC int +waitproc(int block, struct job *jp, int *status) +{ +#ifdef BSD + int flags = 0; + +#if JOBS + if (mflag || (jp != NULL && jp->jobctl)) + flags |= WUNTRACED | WCONTINUED; +#endif + if (block == 0) + flags |= WNOHANG; + VTRACE(DBG_WAIT, ("waitproc: doing waitpid(flags=%#x)\n", flags)); + return waitpid(-1, status, flags); +#else +#ifdef SYSV + int (*save)(); + + if (block == 0) { + gotsigchild = 0; + save = signal(SIGCLD, onsigchild); + signal(SIGCLD, save); + if (gotsigchild == 0) + return 0; + } + return wait(status); +#else + if (block == 0) + return 0; + return wait(status); +#endif +#endif +} + +/* + * return 1 if there are stopped jobs, otherwise 0 + */ +int job_warning = 0; +int +stoppedjobs(void) +{ + int jobno; + struct job *jp; + + if (job_warning || jobs_invalid) + return (0); + for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) { + if (jp->used == 0) + continue; + if (jp->state == JOBSTOPPED) { + out2str("You have stopped jobs.\n"); + job_warning = 2; + return (1); + } + } + + return (0); +} + +/* + * Return a string identifying a command (to be printed by the + * jobs command). + */ + +STATIC char *cmdnextc; +STATIC int cmdnleft; + +void +commandtext(struct procstat *ps, union node *n) +{ + int len; + + cmdnextc = ps->cmd; + if (iflag || mflag || sizeof(ps->cmd) <= 60) + len = sizeof(ps->cmd); + else if (sizeof ps->cmd <= 400) + len = 50; + else if (sizeof ps->cmd <= 800) + len = 80; + else + len = sizeof(ps->cmd) / 10; + cmdnleft = len; + cmdtxt(n); + if (cmdnleft <= 0) { + char *p = ps->cmd + len - 4; + p[0] = '.'; + p[1] = '.'; + p[2] = '.'; + p[3] = 0; + } else + *cmdnextc = '\0'; + + VTRACE(DBG_JOBS, + ("commandtext: ps->cmd %p, end %p, left %d\n\t\"%s\"\n", + ps->cmd, cmdnextc, cmdnleft, ps->cmd)); +} + + +STATIC void +cmdtxt(union node *n) +{ + union node *np; + struct nodelist *lp; + const char *p; + int i; + + if (n == NULL || cmdnleft <= 0) + 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 NDNOT: + cmdputs("! "); + /* FALLTHROUGH */ + case NNOT: + cmdputs("! "); + cmdtxt(n->nnot.com); + break; + case NPIPE: + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + cmdtxt(lp->n); + if (lp->next) + cmdputs(" | "); + } + if (n->npipe.backgnd) + 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); + if (n->nif.elsepart) { + cmdputs("; else "); + cmdtxt(n->nif.elsepart); + } + cmdputs("; fi"); + break; + case NWHILE: + cmdputs("while "); + goto until; + case NUNTIL: + cmdputs("until "); + until: + cmdtxt(n->nbinary.ch1); + cmdputs("; do "); + cmdtxt(n->nbinary.ch2); + cmdputs("; done"); + break; + case NFOR: + cmdputs("for "); + cmdputs(n->nfor.var); + cmdputs(" in "); + cmdlist(n->nfor.args, 1); + cmdputs("; do "); + cmdtxt(n->nfor.body); + cmdputs("; done"); + break; + case NCASE: + cmdputs("case "); + cmdputs(n->ncase.expr->narg.text); + cmdputs(" in "); + for (np = n->ncase.cases; np; np = np->nclist.next) { + cmdtxt(np->nclist.pattern); + cmdputs(") "); + cmdtxt(np->nclist.body); + switch (n->type) { /* switch (not if) for later */ + case NCLISTCONT: + cmdputs(";& "); + break; + default: + cmdputs(";; "); + break; + } + } + cmdputs("esac"); + break; + case NDEFUN: + cmdputs(n->narg.text); + cmdputs("() { ... }"); + break; + case NCMD: + cmdlist(n->ncmd.args, 1); + cmdlist(n->ncmd.redirect, 0); + if (n->ncmd.backgnd) + cmdputs(" &"); + break; + case NARG: + cmdputs(n->narg.text); + break; + case NTO: + p = ">"; i = 1; goto redir; + case NCLOBBER: + p = ">|"; i = 1; goto redir; + case NAPPEND: + p = ">>"; i = 1; goto redir; + case NTOFD: + p = ">&"; i = 1; goto redir; + case NFROM: + p = "<"; i = 0; goto redir; + case NFROMFD: + p = "<&"; i = 0; goto redir; + case NFROMTO: + p = "<>"; i = 0; goto redir; + redir: + if (n->nfile.fd != i) + cmdputi(n->nfile.fd); + cmdputs(p); + if (n->type == NTOFD || n->type == NFROMFD) { + if (n->ndup.dupfd < 0) + cmdputs("-"); + else + cmdputi(n->ndup.dupfd); + } else { + cmdtxt(n->nfile.fname); + } + break; + case NHERE: + case NXHERE: + cmdputs("<<..."); + break; + default: + cmdputs("???"); + break; + } +} + +STATIC void +cmdlist(union node *np, int sep) +{ + for (; np; np = np->narg.next) { + if (!sep) + cmdputs(" "); + cmdtxt(np); + if (sep && np->narg.next) + cmdputs(" "); + } +} + + +STATIC void +cmdputs(const char *s) +{ + const char *p, *str = 0; + char c, cc[2] = " "; + char *nextc; + int nleft; + int subtype = 0; + int quoted = 0; + static char vstype[16][4] = { "", "}", "-", "+", "?", "=", + "#", "##", "%", "%%", "}" }; + + p = s; + nextc = cmdnextc; + nleft = cmdnleft; + while (nleft > 0 && (c = *p++) != 0) { + switch (c) { + case CTLNONL: + c = '\0'; + break; + case CTLESC: + c = *p++; + break; + case CTLVAR: + subtype = *p++; + if (subtype & VSLINENO) { /* undo LINENO hack */ + if ((subtype & VSTYPE) == VSLENGTH) + str = "${#LINENO"; /*}*/ + else + str = "${LINENO"; /*}*/ + while (is_digit(*p)) + p++; + } else if ((subtype & VSTYPE) == VSLENGTH) + str = "${#"; /*}*/ + else + str = "${"; /*}*/ + if (!(subtype & VSQUOTE) != !(quoted & 1)) { + quoted ^= 1; + c = '"'; + } else { + c = *str++; + } + break; + case CTLENDVAR: /*{*/ + c = '}'; + if (quoted & 1) + str = "\""; + quoted >>= 1; + subtype = 0; + break; + case CTLBACKQ: + c = '$'; + str = "(...)"; + break; + case CTLBACKQ+CTLQUOTE: + c = '"'; + str = "$(...)\""; + break; + case CTLARI: + c = '$'; + if (*p == ' ') + p++; + str = "(("; /*))*/ + break; + case CTLENDARI: /*((*/ + c = ')'; + str = ")"; + break; + case CTLQUOTEMARK: + quoted ^= 1; + c = '"'; + break; + case CTLQUOTEEND: + quoted >>= 1; + c = '"'; + break; + case '=': + if (subtype == 0) + break; + str = vstype[subtype & VSTYPE]; + if (subtype & VSNUL) + c = ':'; + else + c = *str++; /*{*/ + if (c != '}') + quoted <<= 1; + else if (*p == CTLENDVAR) + c = *str++; + subtype = 0; + break; + case '\'': + case '\\': + case '"': + case '$': + /* These can only happen inside quotes */ + cc[0] = c; + str = cc; + c = '\\'; + break; + default: + break; + } + if (c != '\0') do { /* c == 0 implies nothing in str */ + *nextc++ = c; + } while (--nleft > 0 && str && (c = *str++)); + str = 0; + } + if ((quoted & 1) && nleft) { + *nextc++ = '"'; + nleft--; + } + cmdnleft = nleft; + cmdnextc = nextc; +} diff --git a/bin/sh/jobs.h b/bin/sh/jobs.h new file mode 100644 index 0000000..a587e9e --- /dev/null +++ b/bin/sh/jobs.h @@ -0,0 +1,105 @@ +/* $NetBSD: jobs.h,v 1.23 2018/09/11 03:30:40 kre Exp $ */ + +/*- + * 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 + */ + +#include "output.h" + +/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ +#define FORK_FG 0 +#define FORK_BG 1 +#define FORK_NOJOB 2 + +/* mode flags for showjob(s) */ +#define SHOW_PGID 0x01 /* only show pgid - for jobs -p */ +#define SHOW_MULTILINE 0x02 /* one line per process */ +#define SHOW_PID 0x04 /* include process pid */ +#define SHOW_CHANGED 0x08 /* only jobs whose state has changed */ +#define SHOW_SIGNALLED 0x10 /* only if stopped/exited on signal */ +#define SHOW_ISSIG 0x20 /* job was signalled */ +#define SHOW_NO_FREE 0x40 /* do not free job */ + + +/* + * 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. + */ +#define MAXCMDTEXT 200 + +struct procstat { + pid_t pid; /* process id */ + int status; /* last process status from wait() */ + char cmd[MAXCMDTEXT];/* text of command being run */ +}; + +struct job { + struct procstat ps0; /* status of process */ + struct procstat *ps; /* status or processes when more than one */ + void *ref; /* temporary reference, used variously */ + int nprocs; /* number of processes */ + pid_t pgrp; /* process group of this job */ + char state; +#define JOBRUNNING 0 /* at least one proc running */ +#define JOBSTOPPED 1 /* all procs are stopped */ +#define JOBDONE 2 /* all procs are completed */ + char used; /* true if this entry is in used */ + char flags; +#define JOBCHANGED 1 /* set if status has changed */ +#define JOBWANTED 2 /* set if this is a job being sought */ +#define JPIPEFAIL 4 /* set if -o pipefail when job created */ +#if JOBS + char jobctl; /* job running under job control */ + int prev_job; /* previous job index */ +#endif +}; + +extern pid_t backgndpid; /* pid of last background process */ +extern int job_warning; /* user was warned about stopped jobs */ + +void setjobctl(int); +void showjobs(struct output *, int); +struct job *makejob(union node *, int); +int forkshell(struct job *, union node *, int); +void forkchild(struct job *, union node *, int, int); +int forkparent(struct job *, union node *, int, pid_t); +int waitforjob(struct job *); +int stoppedjobs(void); +void commandtext(struct procstat *, union node *); +int getjobpgrp(const char *); + +#if ! JOBS +#define setjobctl(on) /* do nothing */ +#endif diff --git a/bin/sh/machdep.h b/bin/sh/machdep.h new file mode 100644 index 0000000..14e803b --- /dev/null +++ b/bin/sh/machdep.h @@ -0,0 +1,47 @@ +/* $NetBSD: machdep.h,v 1.11 2003/08/07 09:05:33 agc Exp $ */ + +/*- + * 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. + * + * @(#)machdep.h 8.2 (Berkeley) 5/4/95 + */ + +/* + * Most machines require the value returned from malloc to be aligned + * in some way. The following macro will get this right on many machines. + */ + +#define SHELL_SIZE (sizeof(union {int i; char *cp; double d; }) - 1) +/* + * It appears that grabstackstr() will barf with such alignments + * because stalloc() will return a string allocated in a new stackblock. + */ +#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE) diff --git a/bin/sh/mail.c b/bin/sh/mail.c new file mode 100644 index 0000000..484b50f --- /dev/null +++ b/bin/sh/mail.c @@ -0,0 +1,144 @@ +/* $NetBSD: mail.c,v 1.18 2017/06/04 20:28:13 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mail.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: mail.c,v 1.18 2017/06/04 20:28:13 kre Exp $"); +#endif +#endif /* not lint */ + +/* + * Routines to check for mail. (Perhaps make part of main.c?) + */ +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> + +#include "shell.h" +#include "exec.h" /* defines padvance() */ +#include "var.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mail.h" + + +#define MAXMBOXES 10 + + +STATIC int nmboxes; /* number of mailboxes */ +STATIC off_t mailsize[MAXMBOXES]; /* sizes 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; + const char *mpath; + char *p; + char *q; + struct stackmark smark; + struct stat statb; + + if (silent) + nmboxes = 10; + if (nmboxes == 0) + return; + setstackmark(&smark); + mpath = mpathset() ? mpathval() : mailval(); + for (i = 0 ; i < nmboxes ; i++) { + p = padvance(&mpath, nullstr, 1); + if (p == NULL) + break; + stunalloc(p); + if (*p == '\0') + continue; + for (q = p ; *q ; q++) + ; + if (q[-1] != '/') + abort(); + q[-1] = '\0'; /* delete trailing '/' */ +#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(pathopt ? pathopt : "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 > mailsize[i] && ! silent) { + const char *pp; + + if ((pp = pathopt) != NULL) { + int len = 0; + + while (*pp && *pp != ':') + len++, pp++; + if (len == 0) { + CHECKSTRSPACE(16, p); + strcat(p, ": file changed"); + } else { + while (stackblocksize() <= len) + growstackblock(); + p = stackblock(); + memcpy(p, pathopt, len); + p[len] = '\0'; + } + pp = p; + } else + pp = "you have mail"; + + out2str(pp); + out2c('\n'); + } + mailsize[i] = statb.st_size; +#endif + } + nmboxes = i; + popstackmark(&smark); +} diff --git a/bin/sh/mail.h b/bin/sh/mail.h new file mode 100644 index 0000000..9ea7c21 --- /dev/null +++ b/bin/sh/mail.h @@ -0,0 +1,37 @@ +/* $NetBSD: mail.h,v 1.10 2003/08/07 09:05:34 agc Exp $ */ + +/*- + * 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 + */ + +void chkmail(int); diff --git a/bin/sh/main.c b/bin/sh/main.c new file mode 100644 index 0000000..a023611 --- /dev/null +++ b/bin/sh/main.c @@ -0,0 +1,393 @@ +/* $NetBSD: main.c,v 1.80 2019/01/19 14:20:22 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1991, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)main.c 8.7 (Berkeley) 7/19/95"; +#else +__RCSID("$NetBSD: main.c,v 1.80 2019/01/19 14:20:22 kre Exp $"); +#endif +#endif /* not lint */ + +#include <errno.h> +#include <stdio.h> +#include <signal.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <locale.h> +#include <fcntl.h> + + +#include "shell.h" +#include "main.h" +#include "mail.h" +#include "options.h" +#include "builtins.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 "init.h" +#include "mystring.h" +#include "exec.h" +#include "cd.h" +#include "redir.h" + +#define PROFILE 0 + +int rootpid; +int rootshell; +struct jmploc main_handler; +int max_user_fd; +#if PROFILE +short profile_buf[16384]; +extern int etext(); +#endif + +STATIC void read_profile(const 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; + volatile int state; + char *shinit; + uid_t uid; + gid_t gid; + + uid = getuid(); + gid = getgid(); + + max_user_fd = fcntl(0, F_MAXFD); + if (max_user_fd < 2) + max_user_fd = 2; + + setlocale(LC_ALL, ""); + + posix = getenv("POSIXLY_CORRECT") != NULL; +#if PROFILE + monitor(4, etext, profile_buf, sizeof profile_buf, 50); +#endif + state = 0; + if (setjmp(main_handler.loc)) { + /* + * When a shell procedure is executed, we raise the + * exception EXSHELLPROC to clean up before executing + * the shell procedure. + */ + switch (exception) { + case EXSHELLPROC: + rootpid = getpid(); + rootshell = 1; + minusc = NULL; + state = 3; + break; + + case EXEXEC: + exitstatus = exerrno; + break; + + case EXERROR: + exitstatus = 2; + break; + + default: + break; + } + + if (exception != EXSHELLPROC) { + if (state == 0 || iflag == 0 || ! rootshell || + exception == EXEXIT) + exitshell(exitstatus); + } + reset(); + if (exception == EXINT) { + out2c('\n'); + flushout(&errout); + } + 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 +#if DEBUG >= 2 + debug = 1; /* this may be reset by procargs() later */ +#endif + opentrace(); + trputs("Shell args: "); trargs(argv); +#if DEBUG >= 3 + set_debug(((DEBUG)==3 ? "_@" : "++"), 1); +#endif +#endif + rootpid = getpid(); + rootshell = 1; + init(); + initpwd(); + setstackmark(&smark); + procargs(argc, argv); + + /* + * Limit bogus system(3) or popen(3) calls in setuid binaries, + * by requiring the -p flag + */ + if (!pflag && (uid != geteuid() || gid != getegid())) { + setuid(uid); + setgid(gid); + /* PS1 might need to be changed accordingly. */ + choose_ps1(); + } + + if (argv[0] && argv[0][0] == '-') { + state = 1; + read_profile("/etc/profile"); + state1: + state = 2; + read_profile(".profile"); + } + state2: + state = 3; + if ((iflag || !posix) && + getuid() == geteuid() && getgid() == getegid()) { + struct stackmark env_smark; + + setstackmark(&env_smark); + if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') { + state = 3; + read_profile(expandenv(shinit)); + } + popstackmark(&env_smark); + } + state3: + state = 4; + line_number = 1; /* undo anything from profile files */ + + if (sflag == 0 || minusc) { + static int sigs[] = { + SIGINT, SIGQUIT, SIGHUP, +#ifdef SIGTSTP + SIGTSTP, +#endif + SIGPIPE + }; +#define SIGSSIZE (sizeof(sigs)/sizeof(sigs[0])) + size_t i; + + for (i = 0; i < SIGSSIZE; i++) + setsignal(sigs[i], 0); + } + + if (minusc) + evalstring(minusc, sflag ? 0 : EV_EXIT); + + if (sflag || minusc == NULL) { + state4: /* XXX ??? - why isn't this before the "if" statement */ + cmdloop(1); + } +#if PROFILE + monitor(0); +#endif + line_number = plinno; + exitshell(exitstatus); + /* NOTREACHED */ +} + + +/* + * Read and execute commands. "Top" is nonzero for the top level command + * loop; it turns on prompting if the shell is interactive. + */ + +void +cmdloop(int top) +{ + union node *n; + struct stackmark smark; + int inter; + int numeof = 0; + enum skipstate skip; + + CTRACE(DBG_ALWAYS, ("cmdloop(%d) called\n", top)); + setstackmark(&smark); + for (;;) { + if (pendingsigs) + dotrap(); + inter = 0; + if (iflag == 1 && top) { + inter = 1; + showjobs(out2, SHOW_CHANGED); + chkmail(0); + flushout(&errout); + nflag = 0; + } + n = parsecmd(inter); + VXTRACE(DBG_PARSE|DBG_EVAL|DBG_CMDS,("cmdloop: "),showtree(n)); + if (n == NEOF) { + if (!top || numeof >= 50) + break; + if (nflag) + break; + if (!stoppedjobs()) { + if (!iflag || !Iflag) + break; + out2str("\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); + } + rststackmark(&smark); + + /* + * Any SKIP* can occur here! SKIP(FUNC|BREAK|CONT) occur when + * a dotcmd is in a loop or a function body and appropriate + * built-ins occurs in file scope in the sourced file. Values + * other than SKIPFILE are reset by the appropriate eval*() + * that contained the dotcmd() call. + */ + skip = current_skipstate(); + if (skip != SKIPNONE) { + if (skip == SKIPFILE) + stop_skipping(); + break; + } + } + popstackmark(&smark); +} + + + +/* + * Read /etc/profile or .profile. Return on error. + */ + +STATIC void +read_profile(const char *name) +{ + int fd; + int xflag_set = 0; + int vflag_set = 0; + + if (*name == '\0') + return; + + INTOFF; + if ((fd = open(name, O_RDONLY)) >= 0) + setinputfd(fd, 1); + INTON; + if (fd < 0) + return; + /* -q turns off -x and -v just when executing init files */ + if (qflag) { + if (xflag) + xflag = 0, xflag_set = 1; + if (vflag) + vflag = 0, vflag_set = 1; + } + cmdloop(0); + if (qflag) { + if (xflag_set) + xflag = 1; + if (vflag_set) + vflag = 1; + } + popfile(); +} + + + +/* + * Read a file containing shell functions. + */ + +void +readcmdfile(char *name) +{ + int fd; + + INTOFF; + if ((fd = open(name, O_RDONLY)) >= 0) + setinputfd(fd, 1); + else + error("Can't open %s", name); + INTON; + cmdloop(0); + popfile(); +} + + + +int +exitcmd(int argc, char **argv) +{ + if (stoppedjobs()) + return 0; + if (argc > 1) + exitshell(number(argv[1])); + else + exitshell_savedstatus(); + /* NOTREACHED */ +} diff --git a/bin/sh/main.h b/bin/sh/main.h new file mode 100644 index 0000000..db1d576 --- /dev/null +++ b/bin/sh/main.h @@ -0,0 +1,42 @@ +/* $NetBSD: main.h,v 1.12 2018/12/03 02:38:30 kre Exp $ */ + +/*- + * 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 + */ + +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(char *); +void cmdloop(int); diff --git a/bin/sh/memalloc.c b/bin/sh/memalloc.c new file mode 100644 index 0000000..da8ff3c --- /dev/null +++ b/bin/sh/memalloc.c @@ -0,0 +1,334 @@ +/* $NetBSD: memalloc.c,v 1.32 2018/08/22 20:08:54 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)memalloc.c 8.3 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: memalloc.c,v 1.32 2018/08/22 20:08:54 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdlib.h> +#include <unistd.h> + +#include "shell.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "machdep.h" +#include "mystring.h" + +/* + * Like malloc, but returns an error when out of space. + */ + +pointer +ckmalloc(size_t nbytes) +{ + pointer p; + + p = malloc(nbytes); + if (p == NULL) + error("Out of space"); + return p; +} + + +/* + * Same for realloc. + */ + +pointer +ckrealloc(pointer p, int nbytes) +{ + p = realloc(p, nbytes); + if (p == NULL) + error("Out of space"); + return p; +} + + +/* + * Make a copy of a string in safe storage. + */ + +char * +savestr(const char *s) +{ + char *p; + + p = ckmalloc(strlen(s) + 1); + scopy(s, p); + 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 504 was chosen because the Ultrix malloc handles that size + * well. + */ + +#define MINSIZE 504 /* minimum size of a block */ + +struct stack_block { + struct stack_block *prev; + char space[MINSIZE]; +}; + +struct stack_block stackbase; +struct stack_block *stackp = &stackbase; +struct stackmark *markp; +char *stacknxt = stackbase.space; +int stacknleft = MINSIZE; +int sstrnleft; +int herefd = -1; + +pointer +stalloc(int nbytes) +{ + char *p; + + nbytes = SHELL_ALIGN(nbytes); + if (nbytes > stacknleft) { + int blocksize; + struct stack_block *sp; + + blocksize = nbytes; + if (blocksize < MINSIZE) + blocksize = MINSIZE; + INTOFF; + sp = ckmalloc(sizeof(struct stack_block) - MINSIZE + blocksize); + sp->prev = stackp; + stacknxt = sp->space; + stacknleft = blocksize; + stackp = sp; + INTON; + } + p = stacknxt; + stacknxt += nbytes; + stacknleft -= nbytes; + return p; +} + + +void +stunalloc(pointer p) +{ + if (p == NULL) { /*DEBUG */ + write(2, "stunalloc\n", 10); + abort(); + } + stacknleft += stacknxt - (char *)p; + stacknxt = p; +} + + +/* save the current status of the sh stack */ +void +setstackmark(struct stackmark *mark) +{ + mark->stackp = stackp; + mark->stacknxt = stacknxt; + mark->stacknleft = stacknleft; + mark->sstrnleft = sstrnleft; + mark->marknext = markp; + markp = mark; +} + +/* reset the stack mark, and remove it from the list of marks */ +void +popstackmark(struct stackmark *mark) +{ + markp = mark->marknext; /* delete mark from the list */ + rststackmark(mark); /* and reset stack */ +} + +/* reset the shell stack to its state recorded in the stack mark */ +void +rststackmark(struct stackmark *mark) +{ + struct stack_block *sp; + + INTOFF; + while (stackp != mark->stackp) { + /* delete any recently allocated mem blocks */ + sp = stackp; + stackp = sp->prev; + ckfree(sp); + } + stacknxt = mark->stacknxt; + stacknleft = mark->stacknleft; + sstrnleft = mark->sstrnleft; + 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. + */ + +void +growstackblock(void) +{ + int newlen = SHELL_ALIGN(stacknleft * 2 + 100); + + INTOFF; + if (stacknxt == stackp->space && stackp != &stackbase) { + struct stack_block *oldstackp; + struct stackmark *xmark; + struct stack_block *sp; + + oldstackp = stackp; + sp = stackp; + stackp = sp->prev; + sp = ckrealloc((pointer)sp, + sizeof(struct stack_block) - MINSIZE + newlen); + sp->prev = stackp; + stackp = sp; + stacknxt = sp->space; + sstrnleft += newlen - stacknleft; + stacknleft = newlen; + + /* + * Stack marks pointing to the start of the old block + * must be relocated to point to the new block + */ + xmark = markp; + while (xmark != NULL && xmark->stackp == oldstackp) { + xmark->stackp = stackp; + xmark->stacknxt = stacknxt; + xmark->sstrnleft += stacknleft - xmark->stacknleft; + xmark->stacknleft = stacknleft; + xmark = xmark->marknext; + } + } else { + char *oldspace = stacknxt; + int oldlen = stacknleft; + char *p = stalloc(newlen); + + (void)memcpy(p, oldspace, oldlen); + stacknxt = p; /* free the space */ + stacknleft += newlen; /* we just allocated */ + } + INTON; +} + +void +grabstackblock(int len) +{ + len = SHELL_ALIGN(len); + stacknxt += len; + stacknleft -= len; +} + +/* + * The following routines are somewhat easier to use than 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. + */ + +char * +growstackstr(void) +{ + int len = stackblocksize(); + if (herefd >= 0 && len >= 1024) { + xwrite(herefd, stackblock(), len); + sstrnleft = len - 1; + return stackblock(); + } + growstackblock(); + sstrnleft = stackblocksize() - len - 1; + return stackblock() + len; +} + +/* + * Called from CHECKSTRSPACE. + */ + +char * +makestrspace(void) +{ + int len = stackblocksize() - sstrnleft; + growstackblock(); + sstrnleft = stackblocksize() - len; + return stackblock() + len; +} + +/* + * Note that this only works to release stack space for reuse + * if nothing else has allocated space on the stack since the grabstackstr() + * + * "s" is the start of the area to be released, and "p" represents the end + * of the string we have stored beyond there and are now releasing. + * (ie: "p" should be the same as in the call to grabstackstr()). + * + * stunalloc(s) and ungrabstackstr(s, p) are almost interchangable after + * a grabstackstr(), however the latter also returns string space so we + * can just continue with STPUTC() etc without needing a new STARTSTACKSTR(s) + */ +void +ungrabstackstr(char *s, char *p) +{ +#ifdef DEBUG + if (s < stacknxt || stacknxt + stacknleft < s) + abort(); +#endif + stacknleft += stacknxt - s; + stacknxt = s; + sstrnleft = stacknleft - (p - s); +} diff --git a/bin/sh/memalloc.h b/bin/sh/memalloc.h new file mode 100644 index 0000000..ed6669e --- /dev/null +++ b/bin/sh/memalloc.h @@ -0,0 +1,79 @@ +/* $NetBSD: memalloc.h,v 1.18 2018/08/22 20:08:54 kre Exp $ */ + +/*- + * 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 + */ + +struct stackmark { + struct stack_block *stackp; + char *stacknxt; + int stacknleft; + int sstrnleft; + struct stackmark *marknext; +}; + + +extern char *stacknxt; +extern int stacknleft; +extern int sstrnleft; +extern int herefd; + +pointer ckmalloc(size_t); +pointer ckrealloc(pointer, int); +char *savestr(const char *); +pointer stalloc(int); +void stunalloc(pointer); +void setstackmark(struct stackmark *); +void popstackmark(struct stackmark *); +void rststackmark(struct stackmark *); +void growstackblock(void); +void grabstackblock(int); +char *growstackstr(void); +char *makestrspace(void); +void ungrabstackstr(char *, char *); + + + +#define stackblock() stacknxt +#define stackblocksize() stacknleft +#define STARTSTACKSTR(p) p = stackblock(), sstrnleft = stackblocksize() +#define STPUTC(c, p) (--sstrnleft >= 0? (*p++ = (c)) : (p = growstackstr(), *p++ = (c))) +#define CHECKSTRSPACE(n, p) { if (sstrnleft < n) p = makestrspace(); } +#define USTPUTC(c, p) (--sstrnleft, *p++ = (c)) +#define STACKSTRNUL(p) (sstrnleft == 0? (p = growstackstr(), sstrnleft++, *p = '\0') : (*p = '\0')) +#define STUNPUTC(p) (++sstrnleft, --p) +#define STTOPC(p) p[-1] +#define STADJUST(amount, p) (p += (amount), sstrnleft -= (amount)) +#define grabstackstr(p) stalloc((p) - stackblock()) + +#define ckfree(p) free((pointer)(p)) diff --git a/bin/sh/miscbltin.c b/bin/sh/miscbltin.c new file mode 100644 index 0000000..3988532 --- /dev/null +++ b/bin/sh/miscbltin.c @@ -0,0 +1,458 @@ +/* $NetBSD: miscbltin.c,v 1.44 2017/05/13 15:03:34 gson Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)miscbltin.c 8.4 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: miscbltin.c,v 1.44 2017/05/13 15:03:34 gson Exp $"); +#endif +#endif /* not lint */ + +/* + * Miscelaneous builtins. + */ + +#include <sys/types.h> /* quad_t */ +#include <sys/param.h> /* BSD4_4 */ +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> + +#include "shell.h" +#include "options.h" +#include "var.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "builtins.h" +#include "mystring.h" + +#undef rflag + + + +/* + * The read builtin. + * Backslahes escape the next char unless -r is specified. + * + * 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, char **argv) +{ + char **ap; + char c; + int rflag; + char *prompt; + const char *ifs; + char *p; + int startword; + int status; + int i; + int is_ifs; + int saveall = 0; + + rflag = 0; + prompt = NULL; + while ((i = nextopt("p:r")) != '\0') { + if (i == 'p') + prompt = optionarg; + else + rflag = 1; + } + + if (prompt && isatty(0)) { + out2str(prompt); + flushall(); + } + + if (*(ap = argptr) == NULL) + error("arg count"); + + if ((ifs = bltinlookup("IFS", 1)) == NULL) + ifs = " \t\n"; + + status = 0; + startword = 2; + STARTSTACKSTR(p); + for (;;) { + if (read(0, &c, 1) != 1) { + status = 1; + break; + } + if (c == '\0') + continue; + if (c == '\\' && !rflag) { + if (read(0, &c, 1) != 1) { + status = 1; + break; + } + if (c != '\n') + STPUTC(c, p); + 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) + STPUTC(c, p); + continue; + } + if (is_ifs == 2 && startword == 1) { + /* Only one non-whitespace IFS per word */ + startword = 2; + if (saveall) + STPUTC(c, p); + continue; + } + } + + if (is_ifs == 0) { + /* append this character to the current variable */ + startword = 0; + if (saveall) + /* Not just a spare terminator */ + saveall++; + STPUTC(c, p); + continue; + } + + /* end of variable... */ + startword = is_ifs; + + if (ap[1] == NULL) { + /* Last variable needs all IFS chars */ + saveall++; + STPUTC(c, p); + continue; + } + + STACKSTRNUL(p); + setvar(*ap, stackblock(), 0); + ap++; + STARTSTACKSTR(p); + } + STACKSTRNUL(p); + + /* Remove trailing IFS chars */ + for (; stackblock() <= --p; *p = 0) { + if (!strchr(ifs, *p)) + break; + if (strchr(" \t\n", *p)) + /* Always remove whitespace */ + continue; + if (saveall > 1) + /* Don't remove non-whitespace unless it was naked */ + break; + } + setvar(*ap, stackblock(), 0); + + /* Set any remaining args to "" */ + while (*++ap != NULL) + setvar(*ap, nullstr, 0); + return status; +} + + + +int +umaskcmd(int argc, char **argv) +{ + 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 (isdigit((unsigned char)*ap)) { + mask = 0; + do { + if (*ap >= '8' || *ap < '0') + error("Illegal number: %s", argv[1]); + mask = (mask << 3) + (*ap - '0'); + } while (*++ap != '\0'); + umask(mask); + } else { + void *set; + + INTOFF; + if ((set = setmode(ap)) != 0) { + mask = getmode(set, ~mask & 0777); + ckfree(set); + } + INTON; + if (!set) + error("Cannot set mode `%s' (%s)", ap, + strerror(errno)); + + umask(~mask & 0777); + } + } + 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 *unit; + int cmd; + int factor; /* multiply by to get rlim_{cur,max} values */ + char option; +}; + +static const struct limits limits[] = { +#ifdef RLIMIT_CPU + { "time", "seconds", RLIMIT_CPU, 1, 't' }, +#endif +#ifdef RLIMIT_FSIZE + { "file", "blocks", RLIMIT_FSIZE, 512, 'f' }, +#endif +#ifdef RLIMIT_DATA + { "data", "kbytes", RLIMIT_DATA, 1024, 'd' }, +#endif +#ifdef RLIMIT_STACK + { "stack", "kbytes", RLIMIT_STACK, 1024, 's' }, +#endif +#ifdef RLIMIT_CORE + { "coredump", "blocks", RLIMIT_CORE, 512, 'c' }, +#endif +#ifdef RLIMIT_RSS + { "memory", "kbytes", RLIMIT_RSS, 1024, 'm' }, +#endif +#ifdef RLIMIT_MEMLOCK + { "locked memory","kbytes", RLIMIT_MEMLOCK, 1024, 'l' }, +#endif +#ifdef RLIMIT_NTHR + { "thread", "threads", RLIMIT_NTHR, 1, 'r' }, +#endif +#ifdef RLIMIT_NPROC + { "process", "processes", RLIMIT_NPROC, 1, 'p' }, +#endif +#ifdef RLIMIT_NOFILE + { "nofiles", "descriptors", RLIMIT_NOFILE, 1, 'n' }, +#endif +#ifdef RLIMIT_VMEM + { "vmemory", "kbytes", RLIMIT_VMEM, 1024, 'v' }, +#endif +#ifdef RLIMIT_SWAP + { "swap", "kbytes", RLIMIT_SWAP, 1024, 'w' }, +#endif +#ifdef RLIMIT_SBSIZE + { "sbsize", "bytes", RLIMIT_SBSIZE, 1, 'b' }, +#endif + { NULL, NULL, 0, 0, '\0' } +}; + +int +ulimitcmd(int argc, char **argv) +{ + int c; + rlim_t val = 0; + enum { SOFT = 0x1, HARD = 0x2 } + how = SOFT | HARD; + const struct limits *l; + int set, all = 0; + int optc, what; + struct rlimit limit; + + what = 'f'; + while ((optc = nextopt("HSabtfdscmlrpnv")) != '\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 { + val = (rlim_t) 0; + + while ((c = *p++) >= '0' && c <= '9') + val = (val * 10) + (long)(c - '0'); + if (c) + error("bad number"); + val *= l->factor; + } + } + if (all) { + for (l = limits; l->name; l++) { + getrlimit(l->cmd, &limit); + if (how & SOFT) + val = limit.rlim_cur; + else if (how & HARD) + val = limit.rlim_max; + + out1fmt("%-13s (-%c %-11s) ", l->name, l->option, + l->unit); + if (val == RLIM_INFINITY) + out1fmt("unlimited\n"); + else + { + val /= l->factor; +#ifdef BSD4_4 + out1fmt("%lld\n", (long long) val); +#else + out1fmt("%ld\n", (long) val); +#endif + } + } + return 0; + } + + if (getrlimit(l->cmd, &limit) == -1) + error("error getting limit (%s)", strerror(errno)); + if (set) { + if (how & HARD) + limit.rlim_max = val; + if (how & SOFT) + limit.rlim_cur = val; + if (setrlimit(l->cmd, &limit) < 0) + error("error setting limit (%s)", strerror(errno)); + } else { + if (how & SOFT) + val = limit.rlim_cur; + else if (how & HARD) + val = limit.rlim_max; + + if (val == RLIM_INFINITY) + out1fmt("unlimited\n"); + else + { + val /= l->factor; +#ifdef BSD4_4 + out1fmt("%lld\n", (long long) val); +#else + out1fmt("%ld\n", (long) val); +#endif + } + } + return 0; +} diff --git a/bin/sh/miscbltin.h b/bin/sh/miscbltin.h new file mode 100644 index 0000000..4c12c82 --- /dev/null +++ b/bin/sh/miscbltin.h @@ -0,0 +1,31 @@ +/* $NetBSD: miscbltin.h,v 1.3 2003/08/21 17:57:53 christos Exp $ */ + +/* + * Copyright (c) 1997 Christos Zoulas. 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. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +int readcmd(int, char **); +int umaskcmd(int, char **); +int ulimitcmd(int, char **); diff --git a/bin/sh/mkbuiltins b/bin/sh/mkbuiltins new file mode 100644 index 0000000..2ebf7ac --- /dev/null +++ b/bin/sh/mkbuiltins @@ -0,0 +1,136 @@ +#!/bin/sh - +# $NetBSD: mkbuiltins,v 1.22 2009/10/06 19:56:58 apb Exp $ +# +# 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 + +havehist=1 +if [ x"$1" = x"-h" ]; then + havehist=0 + shift +fi + +shell=$1 +builtins=$2 +objdir=$3 + +havejobs=0 +if grep '^#define JOBS[ ]*1' ${shell} > /dev/null +then + havejobs=1 +fi + +exec <$builtins 3> ${objdir}/builtins.c 4> ${objdir}/builtins.h + +echo '/* + * This file was generated by the mkbuiltins program. + */ + +#include "shell.h" +#include "builtins.h" + +const struct builtincmd builtincmd[] = { +' >&3 + +echo '/* + * This file was generated by the mkbuiltins program. + */ + +#include <sys/cdefs.h> + +struct builtincmd { + const char *name; + int (*builtin)(int, char **); +}; + +extern const struct builtincmd builtincmd[]; +extern const struct builtincmd splbltincmd[]; + +' >&4 + +specials= + +while read line +do + set -- $line + [ -z "$1" ] && continue + case "$1" in + \#if*|\#def*|\#end*) + echo $line >&3 + echo $line >&4 + continue + ;; + \#*) + continue + ;; + esac + + func=$1 + shift + [ x"$1" = x'-j' ] && { + [ $havejobs = 0 ] && continue + shift + } + [ x"$1" = x'-h' ] && { + [ $havehist = 0 ] && continue + shift + } + echo 'int '"$func"'(int, char **);' >&4 + while + [ $# != 0 ] && [ x"$1" != x'#' ] + do + [ x"$1" = x'-s' ] && { + specials="$specials $2 $func" + shift 2 + continue; + } + [ x"$1" = x'-u' ] && shift + echo ' { "'$1'", '"$func"' },' >&3 + shift + done +done + +echo ' { 0, 0 },' >&3 +echo '};' >&3 +echo >&3 +echo 'const struct builtincmd splbltincmd[] = {' >&3 + +set -- $specials +while + [ $# != 0 ] +do + echo ' { "'$1'", '"$2"' },' >&3 + shift 2 +done + +echo ' { 0, 0 },' >&3 +echo "};" >&3 diff --git a/bin/sh/mkinit.sh b/bin/sh/mkinit.sh new file mode 100755 index 0000000..da4f46f --- /dev/null +++ b/bin/sh/mkinit.sh @@ -0,0 +1,224 @@ +#! /bin/sh +# $NetBSD: mkinit.sh,v 1.10 2018/12/05 09:20:18 kre Exp $ + +# Copyright (c) 2003 The NetBSD Foundation, Inc. +# All rights reserved. +# +# This code is derived from software contributed to The NetBSD Foundation +# by David Laight. +# +# 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. +# +# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + +srcs="$*" + +# use of echo in this script is broken + +# some echo versions will expand \n in the args, which breaks C +# Note: this script is a HOST_PROG ... it must run in the +# build host's environment, with its shell. + +# Fortunately, use of echo here is also trivially simplistic, +# we can easily replace all uses with ... + +echo() +{ + printf '%s\n' "$1" +} + +# CAUTION: for anyone modifying this script.... use printf +# rather than echo to output anything at all... then +# you will avoid being bitten by the simplicity of this function. +# This was done this way rather than wholesale replacement +# to avoid unnecessary code churn. + + +nl=' +' +openparen='(' + +# shells have bugs (including older NetBSD sh) in how \ is +# used in pattern matching. So work out what the shell +# running this script expects. We could also just use a +# literal \ in the pattern, which would need to be quoted +# of course, but then we'd run into a whole host of potential +# other shell bugs (both with the quoting in the pattern, and +# with the matching that follows if that works as inended). +# Far easier, and more reliable, is to just work out what works, +# and then use it, which more or less mandates using a variable... +backslash='\\' +var='abc\' # dummy test case. +if [ "$var" = "${var%$backslash}" ] +then + # buggy sh, try the broken way + backslash='\' + if [ "$var" = "${var%$backslash}" ] + then + printf >&2 "$0: %s\n" 'No pattern match with \ (broken shell)' + exit 1 + fi +fi +# We know we can detect the presence of a trailing \, which is all we need. +# Now to confirm we will not generate false matches. +var='abc' +if [ "$var" != "${var%$backslash}" ] +then + printf >&2 "$0: %s\n" 'Bogus pattern match with \ (broken shell)' + exit 1 +fi +unset var + +includes=' "shell.h" "mystring.h" "init.h" ' +defines= +decles= +event_init= +event_reset= +event_shellproc= + +for src in $srcs; do + exec <$src + decnl="$nl" + while IFS=; read -r line; do + [ "$line" = x ] + case "$line " in + INIT["{ "]* ) event=init;; + RESET["{ "]* ) event=reset;; + SHELLPROC["{ "]* ) event=shellproc;; + INCLUDE[\ \ ]* ) + IFS=' ' + set -- $line + # ignore duplicates + [ "${includes}" != "${includes% $2 *}" ] && continue + includes="$includes$2 " + continue + ;; + MKINIT\ ) + # struct declaration + decles="$decles$nl" + while + read -r line + decles="${decles}${line}${nl}" + [ "$line" != "};" ] + do + : + done + decnl="$nl" + continue + ;; + MKINIT["{ "]* ) + # strip initialiser + def=${line#MKINIT} + comment="${def#*;}" + def="${def%;$comment}" + def="${def%%=*}" + def="${def% }" + decles="${decles}${decnl}extern${def};${comment}${nl}" + decnl= + continue + ;; + \#define[\ \ ]* ) + IFS=' ' + set -- $line + # Ignore those with arguments + [ "$2" = "${2##*$openparen}" ] || continue + # and multiline definitions + [ "$line" = "${line%$backslash}" ] || continue + defines="${defines}#undef $2${nl}${line}${nl}" + continue + ;; + * ) continue;; + esac + # code for events + ev="${nl} /* from $src: */${nl} {${nl}" + # Indent the text by an extra <tab> + while + read -r line + [ "$line" != "}" ] + do + case "$line" in + ('') ;; + ('#'*) ;; + (*) line=" $line";; + esac + ev="${ev}${line}${nl}" + done + ev="${ev} }${nl}" + eval event_$event=\"\$event_$event\$ev\" + done +done + +exec >init.c.tmp + +echo "/*" +echo " * This file was generated by the mkinit program." +echo " */" +echo + +IFS=' ' +for f in $includes; do + echo "#include $f" +done + +echo +echo +echo +echo "$defines" +echo +echo "$decles" +echo +echo +echo "/*" +echo " * Initialization code." +echo " */" +echo +echo "void" +echo "init(void)" +echo "{" +echo "${event_init}" +echo "}" +echo +echo +echo +echo "/*" +echo " * This routine is called when an error or an interrupt occurs in an" +echo " * interactive shell and control is returned to the main command loop." +echo " */" +echo +echo "void" +echo "reset(void)" +echo "{" +echo "${event_reset}" +echo "}" +echo +echo +echo +echo "/*" +echo " * This routine is called to initialize the shell to run a shell procedure." +echo " */" +echo +echo "void" +echo "initshellproc(void)" +echo "{" +echo "${event_shellproc}" +echo "}" + +exec >&- +mv init.c.tmp init.c diff --git a/bin/sh/mknodenames.sh b/bin/sh/mknodenames.sh new file mode 100755 index 0000000..1d03200 --- /dev/null +++ b/bin/sh/mknodenames.sh @@ -0,0 +1,69 @@ +#! /bin/sh + +# $NetBSD: mknodenames.sh,v 1.6 2018/08/18 03:09:37 kre Exp $ + +# Use this script however you like, but it would be amazing if +# it has any purpose other than as part of building the shell... + +if [ -z "$1" ]; then + echo "Usage: $0 nodes.h" 1>&2 + exit 1 +fi + +NODES=$1 + +test -t 1 && test -z "$2" && exec > nodenames.h + +echo "\ +/* + * Automatically generated by $0 + * DO NOT EDIT. Do Not 'cvs add'. + */ +" +echo "#ifndef NODENAMES_H_INCLUDED" +echo "#define NODENAMES_H_INCLUDED" +echo +echo "#ifdef DEBUG" + +MAX=$(awk < "$NODES" ' + /#define/ { + if ($3 > MAX) MAX = $3 + } + END { print MAX } +') + +echo +echo '#ifdef DEFINE_NODENAMES' +echo "STATIC const char * const NodeNames[${MAX} + 1] = {" + +grep '^#define' "$NODES" | sort -k3n | while read define name number opt_comment +do + : ${next:=0} + while [ "$number" -gt "$next" ] + do + echo ' "???",' + next=$(( next + 1)) + done + echo ' "'"$name"'",' + next=$(( number + 1 )) +done + +echo "};" +echo '#else' +echo "extern const char * const NodeNames[${MAX} + 1];" +echo '#endif' +echo +echo '#define NODETYPENAME(type) \' +echo ' ((unsigned)(type) <= '"${MAX}"' ? NodeNames[(type)] : "??OOR??")' +echo +echo '#define NODETYPE(type) NODETYPENAME(type), (type)' +echo '#define PRIdsNT "s(%d)"' +echo +echo '#else /* DEBUG */' +echo +echo '#define NODETYPE(type) (type)' +echo '#define PRIdsNT "d"' +echo +echo '#endif /* DEBUG */' +echo +echo '#endif /* !NODENAMES_H_INCLUDED */' diff --git a/bin/sh/mknodes.sh b/bin/sh/mknodes.sh new file mode 100755 index 0000000..0b1ab80 --- /dev/null +++ b/bin/sh/mknodes.sh @@ -0,0 +1,242 @@ +#! /bin/sh +# $NetBSD: mknodes.sh,v 1.4 2019/01/19 13:08:50 kre Exp $ + +# Copyright (c) 2003 The NetBSD Foundation, Inc. +# All rights reserved. +# +# This code is derived from software contributed to The NetBSD Foundation +# by David Laight. +# +# 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. +# +# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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=$1 +nodes_pat=$2 +objdir="$3" + +exec <$nodetypes +exec >$objdir/nodes.h.tmp + +echo "/*" +echo " * This file was generated by mknodes.sh" +echo " */" +echo + +tagno=0 +while IFS=; read -r line; do + line="${line%%#*}" + IFS=' ' + set -- $line + IFS= + [ -z "$2" ] && continue + case "$line" in + [" "]* ) + IFS=' ' + [ $field = 0 ] && struct_list="$struct_list $struct" + eval field_${struct}_$field=\"\$*\" + eval numfld_$struct=\$field + field=$(($field + 1)) + ;; + * ) + define=$1 + struct=$2 + echo "#define $define $tagno" + tagno=$(($tagno + 1)) + eval define_$struct=\"\$define_$struct \$define\" + struct_define="$struct_define $struct" + field=0 + ;; + esac +done + +echo + +IFS=' ' +for struct in $struct_list; do + echo + echo + echo "struct $struct {" + field=0 + while + eval line=\"\$field_${struct}_$field\" + field=$(($field + 1)) + [ -n "$line" ] + do + IFS=' ' + set -- $line + name=$1 + case "$name" in + type) if [ -n "$typetype" ] && [ "$typetype" != "$2" ] + then + echo >&2 "Conflicting type fields: node" \ + "$struct has $2, others $typetype" + exit 1 + fi + if [ $field -ne 1 ] + then + echo >&2 "Node $struct has type as field" \ + "$field (should only be first)" + exit 1 + fi + typetype=$2 + ;; + *) + if [ $field -eq 1 ] + then + echo >&2 "Node $struct does not have" \ + "type as first field" + exit 1 + fi + ;; + esac + case $2 in + nodeptr ) type="union node *";; + nodelist ) type="struct nodelist *";; + string ) type="char *";; + int*_t | uint*_t | int ) type="$2 ";; + * ) name=; shift 2; type="$*";; + esac + echo " $type$name;" + done + echo "};" +done + +echo +echo +echo "union node {" +echo " $typetype type;" +for struct in $struct_list; do + echo " struct $struct $struct;" +done +echo "};" +echo +echo +echo "struct nodelist {" +echo " struct nodelist *next;" +echo " union node *n;" +echo "};" +echo +echo +echo 'struct funcdef;' +echo 'struct funcdef *copyfunc(union node *);' +echo 'union node *getfuncnode(struct funcdef *);' +echo 'void reffunc(struct funcdef *);' +echo 'void unreffunc(struct funcdef *);' +echo 'void freefunc(struct funcdef *);' + +mv $objdir/nodes.h.tmp $objdir/nodes.h || exit 1 + +exec <$nodes_pat +exec >$objdir/nodes.c.tmp + +echo "/*" +echo " * This file was generated by mknodes.sh" +echo " */" +echo + +while IFS=; read -r line; do + IFS=' ' + set -- $line + IFS= + case "$1" in + '%SIZES' ) + echo "static const short nodesize[$tagno] = {" + IFS=' ' + for struct in $struct_define; do + echo " SHELL_ALIGN(sizeof (struct $struct))," + done + echo "};" + ;; + '%CALCSIZE' ) + echo " if (n == NULL)" + echo " return;" + echo " res->bsize += nodesize[n->type];" + echo " switch (n->type) {" + IFS=' ' + for struct in $struct_list; do + eval defines=\"\$define_$struct\" + for define in $defines; do + echo " case $define:" + done + eval field=\$numfld_$struct + while + [ $field != 0 ] + do + eval line=\"\$field_${struct}_$field\" + field=$(($field - 1)) + IFS=' ' + set -- $line + name=$1 + cl=", res)" + case $2 in + nodeptr ) fn=calcsize;; + nodelist ) fn=sizenodelist;; + string ) fn="res->ssize += strlen" + cl=") + 1";; + * ) continue;; + esac + echo " ${fn}(n->$struct.$name${cl};" + done + echo " break;" + done + echo " };" + ;; + '%COPY' ) + echo " if (n == NULL)" + echo " return NULL;" + echo " new = st->block;" + echo " st->block = (char *) st->block + nodesize[n->type];" + echo " switch (n->type) {" + IFS=' ' + for struct in $struct_list; do + eval defines=\"\$define_$struct\" + for define in $defines; do + echo " case $define:" + done + eval field=\$numfld_$struct + while + [ $field != 0 ] + do + eval line=\"\$field_${struct}_$field\" + field=$(($field - 1)) + IFS=' ' + set -- $line + name=$1 + case $2 in + nodeptr ) fn="copynode(";; + nodelist ) fn="copynodelist(";; + string ) fn="nodesavestr(";; + int*_t| uint*_t | int ) fn=;; + * ) continue;; + esac + f="$struct.$name" + echo " new->$f = ${fn}n->$f${fn:+, st)};" + done + echo " break;" + done + echo " };" + echo " new->type = n->type;" + ;; + * ) echo "$line";; + esac +done + +mv $objdir/nodes.c.tmp $objdir/nodes.c || exit 1 diff --git a/bin/sh/mkoptions.sh b/bin/sh/mkoptions.sh new file mode 100644 index 0000000..aecb6df --- /dev/null +++ b/bin/sh/mkoptions.sh @@ -0,0 +1,198 @@ +#! /bin/sh + +# $NetBSD: mkoptions.sh,v 1.5 2017/11/15 09:21:19 kre Exp $ + +# +# It would be more sensible to generate 2 .h files, one which +# is for everyone to use, defines the "variables" and (perhaps) generates +# the externs (though they could just be explicit in options.h) +# and one just for options.c which generates the initialisation. +# +# But then I'd have to deal with making the Makefile handle that properly... +# (this is simpler there, and it just means a bit more sh compile time.) + +set -f +IFS=' ' # blank, tab (no newline) + +IF="$1" +OF="${3+$3/}$2" + +E_FILE=$(${MKTEMP:-mktemp} -t MKO.E.$$) +O_FILE=$(${MKTEMP:-mktemp} -t MKO.O.$$) +trap 'rm -f "${E_FILE}" "${O_FILE}"' EXIT + +exec 5> "${E_FILE}" +exec 6> "${O_FILE}" + +{ + printf '/*\n * File automatically generated by %s.\n' "$0" + printf ' * Do not edit, do not add to cvs.\n' + printf ' */\n\n' + + printf '#ifdef DEFINE_OPTIONS\n' + printf 'struct optent optlist[] = {\n' +} >"${OF}" + +FIRST=true + +${SED:-sed} <"${IF}" \ + -e '/^$/d' \ + -e '/^#/d' \ + -e '/^[ ]*\//d' \ + -e '/^[ ]*\*/d' \ + -e '/^[ ]*;/d' | +sort -b -k2,2f -k2,2 < "${IF}" | +while read line +do + # Look for comments in various styles, and ignore them + # (these should generally be already removed by sed above) + + case "${line}" in + '') continue;; + /*) continue;; + \**) continue;; + \;*) continue;; + \#*) continue;; + esac + + case "${line}" in + *'#if'*) + COND="${line#*#}" + COND="#${COND%%#*}" + ;; + *) + COND= + ;; + esac + set -- ${line%%[ ]#*} + + var="$1" name="$2" + + case "${var}" in + ('' | [!A-Za-z_]* | *[!A-Za-z0-9_]*) + printf >&2 "Bad var name: '%s'\\n" "${var}" + # exit 1 + continue # just ignore it for now + esac + + case "${name}" in + ?) set -- ${var} '' $name $3 $4; name= ;; + esac + + chr="$3" set="$4" dflt="$5" + + case "${chr}" in + -) chr= set= dflt="$4";; + ''|?) ;; + *) printf >&2 'flag "%s": Not a character\n' "${chr}"; continue;; + esac + + # options must have some kind of name, or they are useless... + test -z "${name}${chr}" && continue + + case "${set}" in + -) set= ;; + [01] | [Oo][Nn] | [Oo][Ff][Ff]) dflt="${set}"; set= ;; + ''|?) ;; + *) printf >&2 'set "%s": Not a character\n' "${set}"; continue;; + esac + + case "${dflt}" in + '') ;; + [Oo][Nn]) dflt=1;; + [Oo][Ff][Ff]) dflt=0;; + [01]) ;; + *) printf >&2 'default "%s" invalid, use 0 off 1 on\n'; continue;; + esac + + # validation complete, now to generate output + + if [ -n "${COND}" ] + then + printf '%s\n' "${COND}" >&4 + printf '%s\n' "${COND}" >&5 + printf '%s\n' "${COND}" >&6 + fi + + printf '\t_SH_OPT_%s,\n' "${var}" >&5 + + if [ -n "${name}" ] + then + printf ' { "%s", ' "${name}" >&4 + else + printf ' { 0, ' >&4 + fi + + if [ -n "${chr}" ] + then + printf "'%s', " "${chr}" >&4 + else + chr= + printf '0, ' >&4 + fi + + if [ -n "${set}" ] + then + printf "'%s', 0, " "${set}" >&4 + else + printf '0, 0, ' >&4 + fi + + if [ -n "${dflt}" ] + then + printf '%s },\n' "${dflt}" >&4 + else + printf '0 },\n' >&4 + fi + + printf '#define %s\toptlist[_SH_OPT_%s].val\n' "${var}" "${var}" >&6 + + if [ -n "${COND}" ] + then + printf '#endif\n' >&4 + printf '#endif\n' >&5 + printf '#endif\n' >&6 + fi + + test -z "${chr}" && continue + + printf '%s _SH_OPT_%s %s\n' "${chr}" "${var}" "${COND}" + +done 4>>"${OF}" | sort -t' ' -k1,1f -k1,1 | while read chr index COND +do + if $FIRST + then + printf ' { 0, 0, 0, 0, 0 }\n};\n' + printf '#endif\n\n' + + printf 'enum shell_opt_names {\n' + cat "${E_FILE}" + printf '};\n\n' + + printf '#ifdef DEFINE_OPTIONS\n' + printf 'const unsigned char optorder[] = {\n' + FIRST=false + fi + [ -n "${COND}" ] && printf '%s\n' "${COND}" + printf '\t%s,\n' "${index}" + [ -n "${COND}" ] && printf '#endif\n' + +done >>"${OF}" + +{ + printf '};\n\n' + printf '#define NOPTS (sizeof optlist / sizeof optlist[0] - 1)\n' + printf 'int sizeof_optlist = sizeof optlist;\n\n' + printf \ + 'const int option_flags = (sizeof optorder / sizeof optorder[0]);\n' + printf '\n#else\n\n' + printf 'extern struct optent optlist[];\n' + printf 'extern int sizeof_optlist;\n' + printf 'extern const unsigned char optorder[];\n' + printf 'extern const int option_flags;\n' + printf '\n#endif\n\n' + + cat "${O_FILE}" +} >> "${OF}" + +exit 0 diff --git a/bin/sh/mktokens b/bin/sh/mktokens new file mode 100644 index 0000000..a8f81c4 --- /dev/null +++ b/bin/sh/mktokens @@ -0,0 +1,99 @@ +#!/bin/sh - +# $NetBSD: mktokens,v 1.14 2017/07/26 03:46:54 kre Exp $ +# +# 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 + +: ${AWK:=awk} +: ${SED:=sed} + +# 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. + +# Note that all the keyword tokens come after TWORD, and all the others +# come before it. We rely upon that relationship - keep it! + +cat > /tmp/ka$$ <<\! +TEOF 1 end of file +TNL 0 newline +TSEMI 0 ";" +TBACKGND 0 "&" +TAND 0 "&&" +TOR 0 "||" +TPIPE 0 "|" +TLP 0 "(" +TRP 1 ")" +TENDCASE 1 ";;" +TCASEFALL 1 ";&" +TENDBQUOTE 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 /tmp/ka$$` +exec > token.h +${AWK} '{print "#define " $1 " " NR-1}' /tmp/ka$$ +echo ' +/* Array indicating which tokens mark the end of a list */ +const char tokendlist[] = {' +${AWK} '{print "\t" $2 ","}' /tmp/ka$$ +echo '}; + +const char *const tokname[] = {' +${SED} -e 's/"/\\"/g' \ + -e 's/[^ ]*[ ][ ]*[^ ]*[ ][ ]*\(.*\)/ "\1",/' \ + /tmp/ka$$ +echo '}; +' +${SED} 's/"//g' /tmp/ka$$ | ${AWK} ' +/TWORD/{print "#define KWDOFFSET " NR; print ""; + print "const char *const parsekwd[] = {"} +/TIF/,/neverfound/{print " \"" $3 "\","}' +echo ' 0 +};' + +rm /tmp/ka$$ diff --git a/bin/sh/myhistedit.h b/bin/sh/myhistedit.h new file mode 100644 index 0000000..855c1bc --- /dev/null +++ b/bin/sh/myhistedit.h @@ -0,0 +1,48 @@ +/* $NetBSD: myhistedit.h,v 1.13 2017/06/28 13:46:06 kre Exp $ */ + +/*- + * 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 + */ + +#include <histedit.h> + +extern History *hist; +extern EditLine *el; +extern int displayhist; + +void histedit(void); +void sethistsize(const char *); +void setterm(const char *); +int inputrc(int, char **); +void set_editrc(const char *); +void set_prompt_lit(const char *); +int not_fcnumber(char *); +int str_to_event(const char *, int); + diff --git a/bin/sh/mystring.c b/bin/sh/mystring.c new file mode 100644 index 0000000..9b6b40b --- /dev/null +++ b/bin/sh/mystring.c @@ -0,0 +1,140 @@ +/* $NetBSD: mystring.c,v 1.18 2018/07/13 22:43:44 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mystring.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: mystring.c,v 1.18 2018/07/13 22:43:44 kre Exp $"); +#endif +#endif /* not lint */ + +/* + * String functions. + * + * equal(s1, s2) Return true if strings are equal. + * scopy(from, to) Copy a string. + * scopyn(from, to, n) Like scopy, but checks for overflow. + * number(s) Convert a string of digits to an integer. + * is_number(s) Return true if s is a string of digits. + */ + +#include <inttypes.h> +#include <limits.h> +#include <stdlib.h> +#include "shell.h" +#include "syntax.h" +#include "error.h" +#include "mystring.h" + + +const char nullstr[1]; /* zero length string */ + +/* + * equal - #defined in mystring.h + */ + +/* + * scopy - #defined in mystring.h + */ + + +/* + * scopyn - copy a string from "from" to "to", truncating the string + * if necessary. "To" is always nul terminated, even if + * truncation is performed. "Size" is the size of "to". + */ + +void +scopyn(const char *from, char *to, int size) +{ + + while (--size > 0) { + if ((*to++ = *from++) == '\0') + return; + } + *to = '\0'; +} + + +/* + * prefix -- see if pfx is a prefix of string. + */ + +int +prefix(const char *pfx, const char *string) +{ + while (*pfx) { + if (*pfx++ != *string++) + return 0; + } + return 1; +} + + +/* + * Convert a string of digits to an integer, printing an error message on + * failure. + */ + +int +number(const char *s) +{ + char *ep = NULL; + intmax_t n; + + if (!is_digit(*s) || ((n = strtoimax(s, &ep, 10)), + (ep == NULL || ep == s || *ep != '\0'))) + error("Illegal number: '%s'", s); + if (n < INT_MIN || n > INT_MAX) + error("Number out of range: %s", s); + return (int)n; +} + + + +/* + * Check for a valid number. This should be elsewhere. + */ + +int +is_number(const char *p) +{ + do { + if (! is_digit(*p)) + return 0; + } while (*++p != '\0'); + return 1; +} diff --git a/bin/sh/mystring.h b/bin/sh/mystring.h new file mode 100644 index 0000000..08a73e9 --- /dev/null +++ b/bin/sh/mystring.h @@ -0,0 +1,45 @@ +/* $NetBSD: mystring.h,v 1.11 2003/08/07 09:05:35 agc Exp $ */ + +/*- + * 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 + */ + +#include <string.h> + +void scopyn(const char *, char *, int); +int prefix(const char *, const char *); +int number(const char *); +int is_number(const char *); + +#define equal(s1, s2) (strcmp(s1, s2) == 0) +#define scopy(s1, s2) ((void)strcpy(s2, s1)) diff --git a/bin/sh/nodes.c.pat b/bin/sh/nodes.c.pat new file mode 100644 index 0000000..599597c --- /dev/null +++ b/bin/sh/nodes.c.pat @@ -0,0 +1,210 @@ +/* $NetBSD: nodes.c.pat,v 1.14 2018/06/22 11:04:55 kre Exp $ */ + +/*- + * 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 + */ + +#include <stdlib.h> +#include <stddef.h> + +/* + * Routine for dealing with parsed shell commands. + */ + +#include "shell.h" +#include "nodes.h" +#include "memalloc.h" +#include "machdep.h" +#include "mystring.h" + + +/* used to accumulate sizes of nodes */ +struct nodesize { + int bsize; /* size of structures in function */ + int ssize; /* size of strings in node */ +}; + +/* provides resources for node copies */ +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(char *, struct nodecopystate *); + +struct funcdef { + unsigned int refcount; + union node n; /* must be last */ +}; + + +/* + * 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.bsize = offsetof(struct funcdef, n); + sz.ssize = 0; + calcsize(n, &sz); + fn = ckmalloc(sz.bsize + sz.ssize); + fn->refcount = 1; + st.block = (char *)fn + offsetof(struct funcdef, n); + st.string = (char *)fn + sz.bsize; + copynode(n, &st); + return fn; +} + +union node * +getfuncnode(struct funcdef *fn) +{ + if (fn == NULL) + return NULL; + return &fn->n; +} + + +STATIC void +calcsize(union node *n, struct nodesize *res) +{ + %CALCSIZE +} + + + +STATIC void +sizenodelist(struct nodelist *lp, struct nodesize *res) +{ + while (lp) { + res->bsize += SHELL_ALIGN(sizeof(struct nodelist)); + calcsize(lp->n, res); + lp = lp->next; + } +} + + + +STATIC union node * +copynode(union node *n, struct nodecopystate *st) +{ + union node *new; + + %COPY + return new; +} + + +STATIC struct nodelist * +copynodelist(struct nodelist *lp, struct nodecopystate *st) +{ + struct nodelist *start; + struct nodelist **lpp; + + lpp = &start; + while (lp) { + *lpp = st->block; + st->block = (char *)st->block + + SHELL_ALIGN(sizeof(struct nodelist)); + (*lpp)->n = copynode(lp->n, st); + lp = lp->next; + lpp = &(*lpp)->next; + } + *lpp = NULL; + return start; +} + + + +STATIC char * +nodesavestr(char *s, struct nodecopystate *st) +{ + register char *p = s; + register char *q = st->string; + char *rtn = st->string; + + while ((*q++ = *p++) != 0) + continue; + st->string = q; + return rtn; +} + + + +/* + * Handle making a reference to a function, and releasing it. + * Free the func code when there are no remaining references. + */ + +void +reffunc(struct funcdef *fn) +{ + if (fn != NULL) + fn->refcount++; +} + +void +unreffunc(struct funcdef *fn) +{ + if (fn != NULL) { + if (--fn->refcount > 0) + return; + ckfree(fn); + } +} + +/* + * this is used when we need to free the func, regardless of refcount + * which only happens when re-initing the shell for a SHELLPROC + */ +void +freefunc(struct funcdef *fn) +{ + if (fn != NULL) + ckfree(fn); +} diff --git a/bin/sh/nodetypes b/bin/sh/nodetypes new file mode 100644 index 0000000..dc5ee4a --- /dev/null +++ b/bin/sh/nodetypes @@ -0,0 +1,149 @@ +# $NetBSD: nodetypes,v 1.18 2017/06/08 13:12:17 kre Exp $ +# 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 + +# 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 + backgnd int # set to run command in background + args nodeptr # the arguments + redirect nodeptr # list of file redirections + lineno int + +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 complex 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) + lineno int + +NCLISTCONT nclist # a case terminated by ';&' (fall through) +NCLIST nclist # a case + 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 + lineno int + + +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 + lineno int + +NTO nfile # fd> fname +NCLOBBER nfile # fd>| fname +NFROM nfile # fd< fname +NFROMTO nfile # fd<> fname +NAPPEND nfile # fd>> fname + type int + next nodeptr # next redirection in list + fd int # file descriptor being redirected + 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 + next nodeptr # next redirection in list + fd int # file descriptor being redirected + dupfd int # file descriptor to duplicate + vname nodeptr # file name if fd>&$var + + +NHERE nhere # fd<<\! +NXHERE nhere # fd<<! + type int + next nodeptr # next redirection in list + fd int # file descriptor being redirected + doc nodeptr # input to command (NARG node) + +NNOT nnot # ! command (actually pipeline) +NDNOT nnot # ! ! pipeline (optimisation) + type int + com nodeptr diff --git a/bin/sh/option.list b/bin/sh/option.list new file mode 100644 index 0000000..3be683a --- /dev/null +++ b/bin/sh/option.list @@ -0,0 +1,79 @@ +/* $NetBSD: option.list,v 1.9 2018/11/23 20:40:06 kre Exp $ */ + +/* + * define the shell's settable options + * + * new options can be defined by adding them here, + * but they do nothing until code to implement them + * is added (using the "var name" field) + */ + +/* + * format is up to 5 columns... (followed by anything) + * end of line comments can be introduced by ' #' (space/tab hash) to eol. + * + * The columns are: + * 1. internal shell "var name" (required) + * 2. option long name + * if a single char, then no long name, and remaining + * columns shift left (this becomes the short name) + * 3. option short name (single character name) + * if '-' or absent then no short name + * if neither long nor short name, line is ignored + * 4. option set short name (name of option equiv class) + * if '-' or absent then no class + * 5. default value of option + * if absent, default is 0 + * only 0 or 1 possible (0==off 1==on) ("on" and "off" can be used) + * + * Data may be followed by any C preprocessor #if expression (incl the #if..) + * (including #ifdef #ifndef) to conditionalise output for that option. + * The #if expression continues until \n or next following '#' + */ + +// the POSIX defined options +aflag allexport a # export all variables +eflag errexit e # exit on command error ($? != 0) +mflag monitor m # enable job control +Cflag noclobber C # do not overwrite files when using > +nflag noexec n # do not execue commands +fflag noglob f # no pathname expansion +uflag nounset u # expanding unset var is an error +vflag verbose v # echo commands as read +xflag xtrace x # trace command execution + +// the long name (ignoreeof) is standard, the I flag is not +Iflag ignoreeof I # do not exit interactive shell on EOF + +// defined but not really implemented by the shell (yet) - they do nothing +bflag notify b # [U] report bg job completion +nolog nolog # [U] no func definitions in history +// 'h' is standard, long name (trackall) is not +hflag trackall h # [U] locate cmds in funcs during defn + +// 's' is standard for command line, not as 'set' option, nor 'stdin' name +sflag stdin s # read from standard input +// minusc c # command line option only. +// -- o # handled differently... + +// non-standard options -- 'i' is just a state, not an option in standard. +iflag interactive i # interactive shell +cdprint cdprint # always print result of a cd +usefork fork F # use fork(2) instead of vfork(2) +pflag nopriv p # preserve privs if set[ug]id +posix posix # be closer to POSIX compat +qflag quietprofile q # disable -v/-x in startup files +fnline1 local_lineno L on # number lines in funcs starting at 1 +promptcmds promptcmds # allow $( ) in PS1 (et al). +pipefail pipefail # pipe exit status +Xflag xlock X #ifndef SMALL # sticky stderr for -x (implies -x) + +// editline/history related options ("vi" is standard, 'V' and others are not) +// only one of vi/emacs can be set, hence the "set" definition, value +// of that can be any char (not used for a different set) +Vflag vi V V # enable vi style editing +Eflag emacs E V # enable emacs style editing +tabcomplete tabcomplete # make <tab> cause filename expansion + +// internal debug option (not usually included in the shell) +debug debug #ifdef DEBUG # enable internal shell debugging diff --git a/bin/sh/options.c b/bin/sh/options.c new file mode 100644 index 0000000..d2c3e68 --- /dev/null +++ b/bin/sh/options.c @@ -0,0 +1,631 @@ +/* $NetBSD: options.c,v 1.53 2018/07/13 22:43:44 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)options.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: options.c,v 1.53 2018/07/13 22:43:44 kre Exp $"); +#endif +#endif /* not lint */ + +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> + +#include "shell.h" +#define DEFINE_OPTIONS +#include "options.h" +#undef DEFINE_OPTIONS +#include "builtins.h" +#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 "syntax.h" +#ifndef SMALL +#include "myhistedit.h" +#endif +#include "show.h" + +char *arg0; /* value of $0 */ +struct shparam shellparam; /* current positional parameters */ +char **argptr; /* argument list for builtin commands */ +char *optionarg; /* set by nextopt (like getopt) */ +char *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 int getopts(char *, char *, char **, char ***, char **); + + +/* + * Process the shell command line arguments. + */ + +void +procargs(int argc, char **argv) +{ + size_t i; + int psx; + + argptr = argv; + if (argc > 0) + argptr++; + + psx = posix; /* save what we set it to earlier */ + /* + * option values are mostly boolean 0:off 1:on + * we use 2 (just in this routine) to mean "unknown yet" + */ + for (i = 0; i < NOPTS; i++) + optlist[i].val = 2; + posix = psx; /* restore before processing -o ... */ + + options(1); + + if (*argptr == NULL && minusc == NULL) + sflag = 1; + if (iflag == 2 && sflag == 1 && isatty(0) && isatty(2)) + iflag = 1; + if (iflag == 1 && sflag == 2) + iflag = 2; + if (mflag == 2) + mflag = iflag; +#ifndef DO_SHAREDVFORK + if (usefork == 2) + usefork = 1; +#endif +#if DEBUG >= 2 + if (debug == 2) + debug = 1; +#endif + /* + * Any options not dealt with as special cases just above, + * and which were not set on the command line, are set to + * their expected default values (mostly "off") + * + * then as each option is initialised, save its setting now + * as its "default" value for future use ("set -o default"). + */ + for (i = 0; i < NOPTS; i++) { + if (optlist[i].val == 2) + optlist[i].val = optlist[i].dflt; + optlist[i].dflt = optlist[i].val; + } + + arg0 = argv[0]; + if (sflag == 0 && minusc == NULL) { + commandname = argv[0]; + arg0 = *argptr++; + setinputfile(arg0, 0); + commandname = arg0; + } + /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */ + if (minusc != NULL) { + if (argptr == NULL || *argptr == NULL) + error("Bad -c option"); + minusc = *argptr++; + if (*argptr != 0) + 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(iflag); +#ifndef SMALL + histedit(); +#endif + setjobctl(mflag); +} + +/* + * Process shell options. The global variable argptr contains a pointer + * to the argument list; we advance it past the options. + */ + +STATIC void +options(int cmdline) +{ + static char empty[] = ""; + char *p; + int val; + int c; + + if (cmdline) + minusc = NULL; + while ((p = *argptr) != NULL) { + argptr++; + if ((c = *p++) == '-') { + val = 1; + if (p[0] == '\0' || (p[0] == '-' && p[1] == '\0')) { + if (!cmdline) { + /* "-" means turn off -x and -v */ + if (p[0] == '\0') + xflag = vflag = 0; + /* "--" means reset params */ + else if (*argptr == NULL) + setparam(argptr); + } + break; /* "-" or "--" terminates options */ + } + } else if (c == '+') { + val = 0; + } else { + argptr--; + break; + } + while ((c = *p++) != '\0') { + if (val == 1 && c == 'c' && cmdline) { + /* command is after shell args*/ + minusc = empty; + } else if (c == 'o') { + if (*p != '\0') + minus_o(p, val + (cmdline ? val : 0)); + else if (*argptr) + minus_o(*argptr++, + val + (cmdline ? val : 0)); + else if (!cmdline) + minus_o(NULL, val); + else + error("arg for %co missing", "+-"[val]); + break; +#ifdef DEBUG + } else if (c == 'D') { + if (*p) { + set_debug(p, val); + break; + } else if (*argptr) + set_debug(*argptr++, val); + else + set_debug("*$", val); +#endif + } else { + setoption(c, val); + } + } + } +} + +static void +set_opt_val(size_t i, int val) +{ + size_t j; + int flag; + + if (val && (flag = optlist[i].opt_set)) { + /* some options (eg vi/emacs) are mutually exclusive */ + for (j = 0; j < NOPTS; j++) + if (optlist[j].opt_set == flag) + optlist[j].val = 0; + } +#ifndef SMALL + if (i == _SH_OPT_Xflag) + xtracefdsetup(val); +#endif + optlist[i].val = val; +#ifdef DEBUG + if (&optlist[i].val == &debug) + opentrace(); /* different "trace" than the -x one... */ +#endif +} + +STATIC void +minus_o(char *name, int val) +{ + size_t i; + const char *sep = ": "; + + if (name == NULL) { + if (val) { + out1str("Current option settings"); + for (i = 0; i < NOPTS; i++) { + if (optlist[i].name == NULL) { + out1fmt("%s%c%c", sep, + "+-"[optlist[i].val], + optlist[i].letter); + sep = ", "; + } + } + out1c('\n'); + for (i = 0; i < NOPTS; i++) { + if (optlist[i].name) + out1fmt("%-19s %s\n", optlist[i].name, + optlist[i].val ? "on" : "off"); + } + } else { + out1str("set -o default"); + for (i = 0; i < NOPTS; i++) { + if (optlist[i].val == optlist[i].dflt) + continue; + if (optlist[i].name) + out1fmt(" %co %s", + "+-"[optlist[i].val], optlist[i].name); + else + out1fmt(" %c%c", "+-"[optlist[i].val], + optlist[i].letter); + } + out1c('\n'); + } + } else { + if (val == 1 && equal(name, "default")) { /* special case */ + for (i = 0; i < NOPTS; i++) + set_opt_val(i, optlist[i].dflt); + return; + } + if (val) + val = 1; + for (i = 0; i < NOPTS; i++) + if (optlist[i].name && equal(name, optlist[i].name)) { + set_opt_val(i, val); +#ifndef SMALL + if (i == _SH_OPT_Xflag) + set_opt_val(_SH_OPT_xflag, val); +#endif + return; + } + error("Illegal option %co %s", "+-"[val], name); + } +} + + +STATIC void +setoption(int flag, int val) +{ + size_t i; + + for (i = 0; i < NOPTS; i++) + if (optlist[i].letter == flag) { + set_opt_val(i, val); +#ifndef SMALL + if (i == _SH_OPT_Xflag) + set_opt_val(_SH_OPT_xflag, val); +#endif + return; + } + error("Illegal option %c%c", "+-"[val], flag); + /* NOTREACHED */ +} + + + +#ifdef mkinit +INCLUDE "options.h" + +SHELLPROC { + int i; + + for (i = 0; optlist[i].name; i++) + optlist[i].val = 0; + optschanged(); + +} +#endif + + +/* + * Set the shell parameters. + */ + +void +setparam(char **argv) +{ + char **newparam; + char **ap; + int nparam; + + for (nparam = 0 ; argv[nparam] ; nparam++) + continue; + ap = newparam = ckmalloc((nparam + 1) * sizeof *ap); + while (*argv) { + *ap++ = savestr(*argv++); + } + *ap = NULL; + freeparam(&shellparam); + shellparam.malloc = 1; + shellparam.nparam = nparam; + shellparam.p = newparam; + shellparam.optnext = NULL; +} + + +/* + * Free the list of positional parameters. + */ + +void +freeparam(volatile struct shparam *param) +{ + char **ap; + + if (param->malloc) { + for (ap = param->p ; *ap ; ap++) + ckfree(*ap); + ckfree(param->p); + } +} + + + +/* + * The shift builtin command. + */ + +int +shiftcmd(int argc, char **argv) +{ + int n; + char **ap1, **ap2; + + if (argc > 2) + error("Usage: shift [n]"); + n = 1; + if (argc > 1) + n = number(argv[1]); + if (n > shellparam.nparam) + error("can't shift that many"); + INTOFF; + shellparam.nparam -= n; + for (ap1 = shellparam.p ; --n >= 0 ; ap1++) { + if (shellparam.malloc) + ckfree(*ap1); + } + ap2 = shellparam.p; + while ((*ap2++ = *ap1++) != NULL) + continue; + shellparam.optnext = NULL; + INTON; + return 0; +} + + + +/* + * The set command builtin. + */ + +int +setcmd(int argc, char **argv) +{ + if (argc == 1) + return showvars(0, 0, 1, 0); + INTOFF; + options(0); + optschanged(); + if (*argptr != NULL) { + setparam(argptr); + } + INTON; + return 0; +} + + +void +getoptsreset(const char *value) +{ + /* + * This is just to detect the case where OPTIND=1 + * is executed. Any other string assigned to OPTIND + * is OK, but is not a reset. No errors, so cannot use number() + */ + if (is_digit(*value) && strtol(value, NULL, 10) == 1) { + shellparam.optnext = NULL; + 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; + + if (argc < 3) + error("usage: getopts optstring var [arg]"); + else if (argc == 3) + optbase = shellparam.p; + else + optbase = &argv[3]; + + if (shellparam.reset == 1) { + shellparam.optnext = optbase; + shellparam.optptr = NULL; + shellparam.reset = 0; + } + + return getopts(argv[1], argv[2], optbase, &shellparam.optnext, + &shellparam.optptr); +} + +STATIC int +getopts(char *optstr, char *optvar, char **optfirst, char ***optnext, char **optpptr) +{ + char *p, *q; + char c = '?'; + int done = 0; + int ind = 0; + int err = 0; + char s[12]; + + if ((p = *optpptr) == 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'; + err |= setvarsafe("OPTARG", s, 0); + } else { + outfmt(&errout, "Illegal option -%c\n", c); + (void) unsetvar("OPTARG", 0); + } + c = '?'; + goto bad; + } + if (*++q == ':') + q++; + } + + if (*++q == ':') { + if (*p == '\0' && (p = **optnext) == NULL) { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + err |= setvarsafe("OPTARG", s, 0); + c = ':'; + } else { + outfmt(&errout, "No arg for -%c option\n", c); + (void) unsetvar("OPTARG", 0); + c = '?'; + } + goto bad; + } + + if (p == **optnext) + (*optnext)++; + err |= setvarsafe("OPTARG", p, 0); + p = NULL; + } else + err |= setvarsafe("OPTARG", "", 0); + ind = *optnext - optfirst + 1; + goto out; + +bad: + ind = 1; + *optnext = NULL; + p = NULL; +out: + *optpptr = p; + 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; + *optpptr = NULL; + flushall(); + exraise(EXERROR); + } + return done; +} + +/* + * XXX - should get rid of. have all builtins use getopt(3). the + * library getopt must have the BSD extension static variable "optreset" + * otherwise it can't be used within the shell safely. + * + * 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 return the character, or '\0' on + * end of input. + */ + +int +nextopt(const char *optstring) +{ + char *p; + const char *q; + char c; + + if ((p = 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); + optionarg = p; + p = NULL; + } + optptr = p; + return c; +} diff --git a/bin/sh/options.h b/bin/sh/options.h new file mode 100644 index 0000000..4857d18 --- /dev/null +++ b/bin/sh/options.h @@ -0,0 +1,72 @@ +/* $NetBSD: options.h,v 1.27 2017/05/28 00:38:01 kre Exp $ */ + +/*- + * 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 + */ + +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 **optnext; /* next parameter to be processed by getopts */ + char *optptr; /* used by getopts */ +}; + +/* + * Note that option default values can be changed at shell startup + * depending upon the environment in which the shell is running. + */ +struct optent { + const char *name; /* for set -o <name> */ + const char letter; /* set [+/-]<letter> and $- */ + const char opt_set; /* mutually exclusive option set */ + unsigned char val; /* value of <letter>flag */ + unsigned char dflt; /* default value of flag */ +}; + +#include "optinit.h" + +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 *optionarg; /* set by nextopt */ +extern char *optptr; /* used by nextopt */ + +void procargs(int, char **); +void optschanged(void); +void setparam(char **); +void freeparam(volatile struct shparam *); +int nextopt(const char *); +void getoptsreset(const char *); diff --git a/bin/sh/output.c b/bin/sh/output.c new file mode 100644 index 0000000..99bd913 --- /dev/null +++ b/bin/sh/output.c @@ -0,0 +1,755 @@ +/* $NetBSD: output.c,v 1.40 2017/11/21 03:42:39 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)output.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: output.c,v 1.40 2017/11/21 03:42:39 kre Exp $"); +#endif +#endif /* not lint */ + +/* + * 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. + * Our output routines may be smaller than the stdio routines. + */ + +#include <sys/types.h> /* quad_t */ +#include <sys/param.h> /* BSD4_4 */ +#include <sys/ioctl.h> + +#include <stdio.h> /* defines BUFSIZ */ +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> + +#include "shell.h" +#include "syntax.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "redir.h" +#include "options.h" +#include "show.h" + + +#define OUTBUFSIZ BUFSIZ +#define BLOCK_OUT -2 /* output to a fixed block of memory */ +#define MEM_OUT -3 /* output to dynamically allocated memory */ + +#ifdef SMALL +#define CHAIN +#else +#define CHAIN ,NULL +#endif + + + /* nextc nleft bufsize buf fd flags chain */ +struct output output = {NULL, 0, OUTBUFSIZ, NULL, 1, 0 CHAIN }; +struct output errout = {NULL, 0, 100, NULL, 2, 0 CHAIN }; +struct output memout = {NULL, 0, 0, NULL, MEM_OUT, 0 CHAIN }; +struct output *out1 = &output; +struct output *out2 = &errout; +#ifndef SMALL +struct output *outx = &errout; +struct output *outxtop = NULL; +#endif + + +#ifdef mkinit + +INCLUDE "output.h" +INCLUDE "memalloc.h" + +RESET { + out1 = &output; + out2 = &errout; + if (memout.buf != NULL) { + ckfree(memout.buf); + memout.buf = NULL; + } +} + +#endif + + +#ifdef notdef /* no longer used */ +/* + * Set up an output file to write to memory rather than a file. + */ + +void +open_mem(char *block, int length, struct output *file) +{ + file->nextc = block; + file->nleft = --length; + file->fd = BLOCK_OUT; + file->flags = 0; +} +#endif + + +void +out1str(const char *p) +{ + outstr(p, out1); +} + + +void +out2str(const char *p) +{ + outstr(p, out2); +} + +#ifndef SMALL +void +outxstr(const char *p) +{ + outstr(p, outx); +} +#endif + + +void +outstr(const char *p, struct output *file) +{ + char c = 0; + + while (*p) + outc((c = *p++), file); + if (file == out2 || (file == outx && c == '\n')) + flushout(file); +} + + +void +out2shstr(const char *p) +{ + outshstr(p, out2); +} + +#ifndef SMALL +void +outxshstr(const char *p) +{ + outshstr(p, outx); +} +#endif + +/* + * ' is in this list, not because it does not require quoting + * (which applies to all the others) but because '' quoting cannot + * be used to quote it. + */ +static const char norm_chars [] = \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/+-=_,.'"; + +static int +inquote(const char *p) +{ + size_t l = strspn(p, norm_chars); + char *s = strchr(p, '\''); + + return s == NULL ? p[l] != '\0' : s - p > (off_t)l; +} + +void +outshstr(const char *p, struct output *file) +{ + int need_q; + int inq; + char c; + + if (strchr(p, '\'') != NULL && p[1] != '\0') { + /* + * string contains ' in it, and is not only the ' + * see if " quoting will work + */ + size_t i = strcspn(p, "\\\"$`"); + + if (p[i] == '\0') { + /* + * string contains no $ ` \ or " chars, perfect... + * + * We know it contains ' so needs quoting, so + * this is easy... + */ + outc('"', file); + outstr(p, file); + outc('"', file); + return; + } + } + + need_q = p[0] == 0 || p[strspn(p, norm_chars)] != 0; + + /* + * Don't emit ' unless something needs quoting before closing ' + */ + if (need_q && (p[0] == 0 || inquote(p) != 0)) { + outc('\'', file); + inq = 1; + } else + inq = 0; + + while ((c = *p++) != '\0') { + if (c != '\'') { + outc(c, file); + continue; + } + + if (inq) + outc('\'', file); /* inq = 0, implicit */ + outc('\\', file); + outc(c, file); + if (need_q && *p != '\0') { + if ((inq = inquote(p)) != 0) + outc('\'', file); + } else + inq = 0; + } + + if (inq) + outc('\'', file); + + if (file == out2) + flushout(file); +} + + +char out_junk[16]; + + +void +emptyoutbuf(struct output *dest) +{ + int offset; + + if (dest->fd == BLOCK_OUT) { + dest->nextc = out_junk; + dest->nleft = sizeof out_junk; + dest->flags |= OUTPUT_ERR; + } else if (dest->buf == NULL) { + INTOFF; + dest->buf = ckmalloc(dest->bufsize); + dest->nextc = dest->buf; + dest->nleft = dest->bufsize; + INTON; + VTRACE(DBG_OUTPUT, ("emptyoutbuf now %d @%p for fd %d\n", + dest->nleft, dest->buf, dest->fd)); + } else if (dest->fd == MEM_OUT) { + offset = dest->bufsize; + INTOFF; + dest->bufsize <<= 1; + dest->buf = ckrealloc(dest->buf, dest->bufsize); + dest->nleft = dest->bufsize - offset; + dest->nextc = dest->buf + offset; + INTON; + } else { + flushout(dest); + } + dest->nleft--; +} + + +void +flushall(void) +{ + flushout(&output); + flushout(&errout); +} + + +void +flushout(struct output *dest) +{ + + if (dest->buf == NULL || dest->nextc == dest->buf || dest->fd < 0) + return; + VTRACE(DBG_OUTPUT, ("flushout fd=%d %zd to write\n", dest->fd, + (size_t)(dest->nextc - dest->buf))); + if (xwrite(dest->fd, dest->buf, dest->nextc - dest->buf) < 0) + dest->flags |= OUTPUT_ERR; + dest->nextc = dest->buf; + dest->nleft = dest->bufsize; +} + + +void +freestdout(void) +{ + INTOFF; + if (output.buf) { + ckfree(output.buf); + output.buf = NULL; + output.nleft = 0; + } + INTON; +} + + +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); +} + +#ifdef DEBUG +void +debugprintf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(out2, fmt, ap); + va_end(ap); + flushout(out2); +} +#endif + +void +fmtstr(char *outbuf, size_t length, const char *fmt, ...) +{ + va_list ap; + struct output strout; + + va_start(ap, fmt); + strout.nextc = outbuf; + strout.nleft = length; + strout.fd = BLOCK_OUT; + strout.flags = 0; + doformat(&strout, fmt, ap); + outc('\0', &strout); + if (strout.flags & OUTPUT_ERR) + outbuf[length - 1] = '\0'; + va_end(ap); +} + +/* + * Formatted output. This routine handles a subset of the printf formats: + * - Formats supported: d, u, o, p, X, s, and c. + * - The x format is also accepted but is treated like X. + * - The l, ll and q modifiers are accepted. + * - The - and # flags are accepted; # only works with the o format. + * - Width and precision may be specified with any format except c. + * - An * may be given for the width or precision. + * - The obsolete practice of preceding the width with a zero to get + * zero padding is not supported; use the precision field. + * - A % may be printed by writing %% in the format string. + */ + +#define TEMPSIZE 24 + +#ifdef BSD4_4 +#define HAVE_VASPRINTF 1 +#endif + +void +doformat(struct output *dest, const char *f, va_list ap) +{ +#if HAVE_VASPRINTF + char *s; + + vasprintf(&s, f, ap); + if (s == NULL) + error("Could not allocate formatted output buffer"); + outstr(s, dest); + free(s); +#else /* !HAVE_VASPRINTF */ + static const char digit[] = "0123456789ABCDEF"; + char c; + char temp[TEMPSIZE]; + int flushleft; + int sharp; + int width; + int prec; + int islong; + int isquad; + char *p; + int sign; +#ifdef BSD4_4 + quad_t l; + u_quad_t num; +#else + long l; + u_long num; +#endif + unsigned base; + int len; + int size; + int pad; + + while ((c = *f++) != '\0') { + if (c != '%') { + outc(c, dest); + continue; + } + flushleft = 0; + sharp = 0; + width = 0; + prec = -1; + islong = 0; + isquad = 0; + for (;;) { + if (*f == '-') + flushleft++; + else if (*f == '#') + sharp++; + else + break; + f++; + } + if (*f == '*') { + width = va_arg(ap, int); + f++; + } else { + while (is_digit(*f)) { + width = 10 * width + digit_val(*f++); + } + } + if (*f == '.') { + if (*++f == '*') { + prec = va_arg(ap, int); + f++; + } else { + prec = 0; + while (is_digit(*f)) { + prec = 10 * prec + digit_val(*f++); + } + } + } + if (*f == 'l') { + f++; + if (*f == 'l') { + isquad++; + f++; + } else + islong++; + } else if (*f == 'q') { + isquad++; + f++; + } + switch (*f) { + case 'd': +#ifdef BSD4_4 + if (isquad) + l = va_arg(ap, quad_t); + else +#endif + if (islong) + l = va_arg(ap, long); + else + l = va_arg(ap, int); + sign = 0; + num = l; + if (l < 0) { + num = -l; + sign = 1; + } + base = 10; + goto number; + case 'u': + base = 10; + goto uns_number; + case 'o': + base = 8; + goto uns_number; + case 'p': + outc('0', dest); + outc('x', dest); + /*FALLTHROUGH*/ + case 'x': + /* we don't implement 'x'; treat like 'X' */ + case 'X': + base = 16; +uns_number: /* an unsigned number */ + sign = 0; +#ifdef BSD4_4 + if (isquad) + num = va_arg(ap, u_quad_t); + else +#endif + if (islong) + num = va_arg(ap, unsigned long); + else + num = va_arg(ap, unsigned int); +number: /* process a number */ + p = temp + TEMPSIZE - 1; + *p = '\0'; + while (num) { + *--p = digit[num % base]; + num /= base; + } + len = (temp + TEMPSIZE - 1) - p; + if (prec < 0) + prec = 1; + if (sharp && *f == 'o' && prec <= len) + prec = len + 1; + pad = 0; + if (width) { + size = len; + if (size < prec) + size = prec; + size += sign; + pad = width - size; + if (flushleft == 0) { + while (--pad >= 0) + outc(' ', dest); + } + } + if (sign) + outc('-', dest); + prec -= len; + while (--prec >= 0) + outc('0', dest); + while (*p) + outc(*p++, dest); + while (--pad >= 0) + outc(' ', dest); + break; + case 's': + p = va_arg(ap, char *); + pad = 0; + if (width) { + len = strlen(p); + if (prec >= 0 && len > prec) + len = prec; + pad = width - len; + if (flushleft == 0) { + while (--pad >= 0) + outc(' ', dest); + } + } + prec++; + while (--prec != 0 && *p) + outc(*p++, dest); + while (--pad >= 0) + outc(' ', dest); + break; + case 'c': + c = va_arg(ap, int); + outc(c, dest); + break; + default: + outc(*f, dest); + break; + } + f++; + } +#endif /* !HAVE_VASPRINTF */ +} + + + +/* + * Version of write which resumes after a signal is caught. + */ + +int +xwrite(int fd, char *buf, int nbytes) +{ + int ntry; + int i; + int n; + + n = nbytes; + ntry = 0; + while (n > 0) { + 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; + } + } + return nbytes; +} + +#ifndef SMALL +static void +xtrace_fd_swap(int from, int to) +{ + struct output *o = outxtop; + + while (o != NULL) { + if (o->fd == from) + o->fd = to; + o = o->chain; + } +} + +/* + * the -X flag is to be set or reset (not necessarily changed) + * Do what is needed to make tracing go to where it should + * + * Note: Xflag has not yet been altered, "on" indicates what it will become + */ + +void +xtracefdsetup(int on) +{ + if (!on) { + flushout(outx); + + if (Xflag != 1) /* Was already +X */ + return; /* so nothing to do */ + + outx = out2; + CTRACE(DBG_OUTPUT, ("Tracing to stderr\n")); + return; + } + + if (Xflag == 1) { /* was already set */ + /* + * This is a change of output file only + * Just close the current one, and reuse the struct output + */ + if (!(outx->flags & OUTPUT_CLONE)) + sh_close(outx->fd); + } else if (outxtop == NULL) { + /* + * -X is just turning on, for the forst time, + * need a new output struct to become outx + */ + xtrace_clone(1); + } else + outx = outxtop; + + if (outx != out2) { + outx->flags &= ~OUTPUT_CLONE; + outx->fd = to_upper_fd(dup(out2->fd)); + register_sh_fd(outx->fd, xtrace_fd_swap); + } + + CTRACE(DBG_OUTPUT, ("Tracing now to fd %d (from %d)\n", + outx->fd, out2->fd)); +} + +void +xtrace_clone(int new) +{ + struct output *o; + + CTRACE(DBG_OUTPUT, + ("xtrace_clone(%d): %sfd=%d buf=%p nleft=%d flags=%x ", + new, (outx == out2 ? "out2: " : ""), + outx->fd, outx->buf, outx->nleft, outx->flags)); + + flushout(outx); + + if (!new && outxtop == NULL && Xflag == 0) { + /* following stderr, nothing to save */ + CTRACE(DBG_OUTPUT, ("+X\n")); + return; + } + + o = ckmalloc(sizeof(*o)); + o->fd = outx->fd; + o->flags = OUTPUT_CLONE; + o->bufsize = outx->bufsize; + o->nleft = 0; + o->buf = NULL; + o->nextc = NULL; + o->chain = outxtop; + outx = o; + outxtop = o; + + CTRACE(DBG_OUTPUT, ("-> fd=%d flags=%x[CLONE]\n", outx->fd, o->flags)); +} + +void +xtrace_pop(void) +{ + struct output *o; + + CTRACE(DBG_OUTPUT, ("trace_pop: fd=%d buf=%p nleft=%d flags=%x ", + outx->fd, outx->buf, outx->nleft, outx->flags)); + + flushout(outx); + + if (outxtop == NULL) { + /* + * No -X has been used, so nothing much to do + */ + CTRACE(DBG_OUTPUT, ("+X\n")); + return; + } + + o = outxtop; + outx = o->chain; + if (outx == NULL) + outx = &errout; + outxtop = o->chain; + if (o != &errout) { + if (o->fd >= 0 && !(o->flags & OUTPUT_CLONE)) + sh_close(o->fd); + if (o->buf) + ckfree(o->buf); + ckfree(o); + } + + CTRACE(DBG_OUTPUT, ("-> fd=%d buf=%p nleft=%d flags=%x\n", + outx->fd, outx->buf, outx->nleft, outx->flags)); +} +#endif /* SMALL */ diff --git a/bin/sh/output.h b/bin/sh/output.h new file mode 100644 index 0000000..a19df43 --- /dev/null +++ b/bin/sh/output.h @@ -0,0 +1,109 @@ +/* $NetBSD: output.h,v 1.27 2017/11/21 03:42:39 kre Exp $ */ + +/*- + * 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 + */ + +#ifndef OUTPUT_INCL + +#include <stdarg.h> + +struct output { + char *nextc; + int nleft; + int bufsize; + char *buf; + short fd; + short flags; +#ifndef SMALL + struct output *chain; +#endif +}; + +/* flags for ->flags */ +#define OUTPUT_ERR 0x01 /* error occurred on output */ +#define OUTPUT_CLONE 0x02 /* this is a clone of another */ + +extern struct output output; +extern struct output errout; +extern struct output memout; +extern struct output *out1; +extern struct output *out2; +#ifdef SMALL +#define outx out2 +#else +extern struct output *outx; +#endif + +void open_mem(char *, int, struct output *); +void out1str(const char *); +void out2str(const char *); +void outstr(const char *, struct output *); +void out2shstr(const char *); +#ifdef SMALL +#define outxstr out2str +#define outxshstr out2shstr +#else +void outxstr(const char *); +void outxshstr(const char *); +#endif +void outshstr(const char *, struct output *); +void emptyoutbuf(struct output *); +void flushall(void); +void flushout(struct output *); +void freestdout(void); +void outfmt(struct output *, const char *, ...) __printflike(2, 3); +void out1fmt(const char *, ...) __printflike(1, 2); +#ifdef DEBUG +void debugprintf(const char *, ...) __printflike(1, 2); +#endif +void fmtstr(char *, size_t, const char *, ...) __printflike(3, 4); +void doformat(struct output *, const char *, va_list) __printflike(2, 0); +int xwrite(int, char *, int); +#ifdef SMALL +#define xtracefdsetup(x) do { break; } while (0) +#define xtrace_clone(x) do { break; } while (0) +#define xtrace_pop() do { break; } while (0) +#else +void xtracefdsetup(int); +void xtrace_clone(int); +void xtrace_pop(void); +#endif + +#define outc(c, file) (--(file)->nleft < 0? (emptyoutbuf(file), *(file)->nextc++ = (c)) : (*(file)->nextc++ = (c))) +#define out1c(c) outc(c, out1) +#define out2c(c) outc(c, out2) +#define outxc(c) outc(c, outx) + +#define OUTPUT_INCL +#endif diff --git a/bin/sh/parser.c b/bin/sh/parser.c new file mode 100644 index 0000000..6b153aa --- /dev/null +++ b/bin/sh/parser.c @@ -0,0 +1,2756 @@ +/* $NetBSD: parser.c,v 1.164 2019/01/22 14:32:17 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)parser.c 8.7 (Berkeley) 5/16/95"; +#else +__RCSID("$NetBSD: parser.c,v 1.164 2019/01/22 14:32:17 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> + +#include "shell.h" +#include "parser.h" +#include "nodes.h" +#include "expand.h" /* defines rmescapes() */ +#include "eval.h" /* defines commandname */ +#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" +#ifndef SMALL +#include "myhistedit.h" +#endif +#ifdef DEBUG +#include "nodenames.h" +#endif + +/* + * Shell command parser. + */ + +/* values returned by readtoken */ +#include "token.h" + +#define OPENBRACE '{' +#define CLOSEBRACE '}' + +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 */ + int startline; /* line number where << seen */ +}; + +MKINIT struct parse_state parse_state; +union parse_state_p psp = { .c_current_parser = &parse_state }; + +static const struct parse_state init_parse_state = { /* all 0's ... */ + .ps_heredoclist = NULL, + .ps_parsebackquote = 0, + .ps_doprompt = 0, + .ps_needprompt = 0, + .ps_lasttoken = 0, + .ps_tokpushback = 0, + .ps_wordtext = NULL, + .ps_checkkwd = 0, + .ps_redirnode = NULL, + .ps_heredoc = NULL, + .ps_quoteflag = 0, + .ps_startlinno = 0, + .ps_funclinno = 0, + .ps_elided_nl = 0, +}; + +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 *makeword(int); +STATIC void parsefname(void); +STATIC int slurp_heredoc(char *const, const int, const int); +STATIC void readheredocs(void); +STATIC int peektoken(void); +STATIC int readtoken(void); +STATIC int xxreadtoken(void); +STATIC int readtoken1(int, char const *, int); +STATIC int noexpand(char *); +STATIC void linebreak(void); +STATIC void consumetoken(int); +STATIC void synexpect(int, const char *) __dead; +STATIC void synerror(const char *) __dead; +STATIC void setprompt(int); +STATIC int pgetc_linecont(void); + +static const char EOFhere[] = "EOF reading here (<<) document"; + +#ifdef DEBUG +int parsing = 0; +#endif + +/* + * 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; + union node *n; + +#ifdef DEBUG + parsing++; +#endif + tokpushback = 0; + checkkwd = 0; + doprompt = interact; + if (doprompt) + setprompt(1); + else + setprompt(0); + needprompt = 0; + t = readtoken(); +#ifdef DEBUG + parsing--; +#endif + if (t == TEOF) + return NEOF; + if (t == TNL) + return NULL; + +#ifdef DEBUG + parsing++; +#endif + tokpushback++; + n = list(1); +#ifdef DEBUG + parsing--; +#endif + if (heredoclist) + error("%d: Here document (<<%s) expected but not present", + heredoclist->startline, heredoclist->eofmark); + return n; +} + + +STATIC union node * +list(int nlflag) +{ + union node *ntop, *n1, *n2, *n3; + int tok; + + CTRACE(DBG_PARSE, ("list(%d): entered @%d\n",nlflag,plinno)); + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (nlflag == 0 && tokendlist[peektoken()]) + return NULL; + ntop = n1 = NULL; + for (;;) { + n2 = andor(); + tok = readtoken(); + if (tok == TBACKGND) { + if (n2->type == NCMD || n2->type == NPIPE) + n2->ncmd.backgnd = 1; + else if (n2->type == NREDIR) + n2->type = NBACKGND; + else { + n3 = 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 = stalloc(sizeof(struct nbinary)); + n1->type = NSEMI; + n1->nbinary.ch1 = ntop; + n1->nbinary.ch2 = n2; + ntop = n1; + } else { + n3 = stalloc(sizeof(struct nbinary)); + n3->type = NSEMI; + n3->nbinary.ch1 = n1->nbinary.ch2; + n3->nbinary.ch2 = n2; + n1->nbinary.ch2 = n3; + n1 = n3; + } + + switch (tok) { + case TBACKGND: + case TSEMI: + tok = readtoken(); + /* FALLTHROUGH */ + case TNL: + if (tok == TNL) { + readheredocs(); + if (nlflag) + return ntop; + } else if (tok == TEOF && nlflag) + return ntop; + else + tokpushback++; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (!nlflag && tokendlist[peektoken()]) + return ntop; + break; + case TEOF: + pungetc(); /* push back EOF on input */ + return ntop; + default: + if (nlflag) + synexpect(-1, 0); + tokpushback++; + return ntop; + } + } +} + +STATIC union node * +andor(void) +{ + union node *n1, *n2, *n3; + int t; + + CTRACE(DBG_PARSE, ("andor: entered @%d\n", plinno)); + + n1 = pipeline(); + for (;;) { + if ((t = readtoken()) == TAND) { + t = NAND; + } else if (t == TOR) { + t = NOR; + } else { + tokpushback++; + return n1; + } + n2 = pipeline(); + n3 = stalloc(sizeof(struct nbinary)); + n3->type = t; + n3->nbinary.ch1 = n1; + n3->nbinary.ch2 = n2; + n1 = n3; + } +} + +STATIC union node * +pipeline(void) +{ + union node *n1, *n2, *pipenode; + struct nodelist *lp, *prev; + int negate; + + CTRACE(DBG_PARSE, ("pipeline: entered @%d\n", plinno)); + + negate = 0; + checkkwd = CHKNL | CHKKWD | CHKALIAS; + while (readtoken() == TNOT) { + CTRACE(DBG_PARSE, ("pipeline: TNOT recognized\n")); +#ifndef BOGUS_NOT_COMMAND + if (posix && negate) + synerror("2nd \"!\" unexpected"); +#endif + negate++; + } + tokpushback++; + n1 = command(); + if (readtoken() == TPIPE) { + pipenode = stalloc(sizeof(struct npipe)); + pipenode->type = NPIPE; + pipenode->npipe.backgnd = 0; + lp = stalloc(sizeof(struct nodelist)); + pipenode->npipe.cmdlist = lp; + lp->n = n1; + do { + prev = lp; + lp = stalloc(sizeof(struct nodelist)); + lp->n = command(); + prev->next = lp; + } while (readtoken() == TPIPE); + lp->next = NULL; + n1 = pipenode; + } + tokpushback++; + if (negate) { + CTRACE(DBG_PARSE, ("%snegate pipeline\n", + (negate&1) ? "" : "double ")); + n2 = stalloc(sizeof(struct nnot)); + n2->type = (negate & 1) ? NNOT : NDNOT; + 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; +#ifdef BOGUS_NOT_COMMAND + int negate = 0; +#endif + + CTRACE(DBG_PARSE, ("command: entered @%d\n", plinno)); + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + 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++; + +#ifdef BOGUS_NOT_COMMAND /* only in pileline() */ + while (readtoken() == TNOT) { + CTRACE(DBG_PARSE, ("command: TNOT (bogus) recognized\n")); + negate++; + } + tokpushback++; +#endif + + switch (readtoken()) { + case TIF: + n1 = stalloc(sizeof(struct nif)); + n1->type = NIF; + n1->nif.test = list(0); + consumetoken(TTHEN); + n1->nif.ifpart = list(0); + n2 = n1; + while (readtoken() == TELIF) { + n2->nif.elsepart = stalloc(sizeof(struct nif)); + n2 = n2->nif.elsepart; + n2->type = NIF; + n2->nif.test = list(0); + 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: + n1 = stalloc(sizeof(struct nbinary)); + n1->type = (lasttoken == TWHILE)? NWHILE : NUNTIL; + n1->nbinary.ch1 = list(0); + consumetoken(TDO); + n1->nbinary.ch2 = list(0); + consumetoken(TDONE); + checkkwd = CHKKWD | CHKALIAS; + break; + case TFOR: + if (readtoken() != TWORD || quoteflag || ! goodname(wordtext)) + synerror("Bad for loop variable"); + n1 = stalloc(sizeof(struct nfor)); + n1->type = NFOR; + n1->nfor.var = wordtext; + linebreak(); + if (lasttoken==TWORD && !quoteflag && equal(wordtext,"in")) { + app = ≈ + while (readtoken() == TWORD) { + n2 = makeword(startlinno); + *app = n2; + app = &n2->narg.next; + } + *app = NULL; + n1->nfor.args = ap; + if (lasttoken != TNL && lasttoken != TSEMI) + synexpect(TSEMI, 0); + } else { + static char argvars[5] = { + CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' + }; + + n2 = stalloc(sizeof(struct narg)); + n2->type = NARG; + n2->narg.text = argvars; + n2->narg.backquote = NULL; + n2->narg.next = NULL; + n2->narg.lineno = startlinno; + 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(TDO, 0); + n1->nfor.body = list(0); + consumetoken(t); + checkkwd = CHKKWD | CHKALIAS; + break; + case TCASE: + n1 = stalloc(sizeof(struct ncase)); + n1->type = NCASE; + n1->ncase.lineno = startlinno - elided_nl; + consumetoken(TWORD); + n1->ncase.expr = makeword(startlinno); + linebreak(); + if (lasttoken != TWORD || !equal(wordtext, "in")) + synexpect(-1, "in"); + cpp = &n1->ncase.cases; + checkkwd = CHKNL | CHKKWD; + readtoken(); + /* + * Both ksh and bash accept 'case x in esac' + * so configure scripts started taking advantage of this. + * The page: http://pubs.opengroup.org/onlinepubs/\ + * 009695399/utilities/xcu_chap02.html contradicts itself, + * as to if this is legal; the "Case Conditional Format" + * paragraph shows one case is required, but the "Grammar" + * section shows a grammar that explicitly allows the no + * case option. + * + * The standard also says (section 2.10): + * This formal syntax shall take precedence over the + * preceding text syntax description. + * ie: the "Grammar" section wins. The text is just + * a rough guide (introduction to the common case.) + */ + while (lasttoken != TESAC) { + *cpp = cp = stalloc(sizeof(struct nclist)); + cp->type = NCLIST; + app = &cp->nclist.pattern; + if (lasttoken == TLP) + readtoken(); + for (;;) { + if (lasttoken < TWORD) + synexpect(TWORD, 0); + *app = ap = makeword(startlinno); + checkkwd = CHKNL | CHKKWD; + if (readtoken() != TPIPE) + break; + app = &ap->narg.next; + readtoken(); + } + if (lasttoken != TRP) + synexpect(TRP, 0); + cp->nclist.lineno = startlinno; + cp->nclist.body = list(0); + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if ((t = readtoken()) != TESAC) { + if (t != TENDCASE && t != TCASEFALL) { + synexpect(TENDCASE, 0); + } else { + if (t == TCASEFALL) + cp->type = NCLISTCONT; + checkkwd = CHKNL | CHKKWD; + readtoken(); + } + } + cpp = &cp->nclist.next; + } + *cpp = NULL; + checkkwd = CHKKWD | CHKALIAS; + break; + case TLP: + n1 = stalloc(sizeof(struct nredir)); + n1->type = NSUBSHELL; + n1->nredir.n = list(0); + n1->nredir.redirect = NULL; + if (n1->nredir.n == NULL) + synexpect(-1, 0); + consumetoken(TRP); + checkkwd = CHKKWD | CHKALIAS; + break; + case TBEGIN: + n1 = list(0); + if (posix && n1 == NULL) + synexpect(-1, 0); + consumetoken(TEND); + checkkwd = CHKKWD | CHKALIAS; + break; + + case TBACKGND: + case TSEMI: + case TAND: + case TOR: + case TPIPE: + case TNL: + case TEOF: + case TRP: + case TENDCASE: + case TCASEFALL: + /* + * simple commands must have something in them, + * either a word (which at this point includes a=b) + * or a redirection. If we reached the end of the + * command (which one of these tokens indicates) + * when we are just starting, and have not had a + * redirect, then ... + * + * nb: it is still possible to end up with empty + * simple commands, if the "command" is a var + * expansion that produces nothing: + * X= ; $X && $X + * --> && + * That is OK and is handled after word expansions. + */ + if (!redir) + synexpect(-1, 0); + /* + * continue to build a node containing the redirect. + * the tokpushback means that our ending token will be + * read again in simplecmd, causing it to terminate, + * so only the redirect(s) will be contained in the + * returned n1 + */ + /* FALLTHROUGH */ + case TWORD: + tokpushback++; + n1 = simplecmd(rpp, redir); + goto checkneg; + default: + synexpect(-1, 0); + /* NOTREACHED */ + } + + /* 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 (n1 == NULL || n1->type != NSUBSHELL) { + n2 = stalloc(sizeof(struct nredir)); + n2->type = NREDIR; + n2->nredir.n = n1; + n1 = n2; + } + n1->nredir.redirect = redir; + } + + checkneg: +#ifdef BOGUS_NOT_COMMAND + if (negate) { + VTRACE(DBG_PARSE, ("bogus %snegate command\n", + (negate&1) ? "" : "double ")); + n2 = stalloc(sizeof(struct nnot)); + n2->type = (negate & 1) ? NNOT : NDNOT; + n2->nnot.com = n1; + return n2; + } + else +#endif + return n1; +} + + +STATIC union node * +simplecmd(union node **rpp, union node *redir) +{ + union node *args, **app; + union node *n = NULL; + int line = 0; + int savecheckkwd; +#ifdef BOGUS_NOT_COMMAND + union node *n2; + int negate = 0; +#endif + + CTRACE(DBG_PARSE, ("simple command with%s redir already @%d\n", + redir ? "" : "out", plinno)); + + /* If we don't have any redirections already, then we must reset */ + /* rpp to be the address of the local redir variable. */ + if (redir == 0) + rpp = &redir; + + args = NULL; + app = &args; + +#ifdef BOGUS_NOT_COMMAND /* pipelines get negated, commands do not */ + while (readtoken() == TNOT) { + VTRACE(DBG_PARSE, ("simplcmd: bogus TNOT recognized\n")); + negate++; + } + tokpushback++; +#endif + + savecheckkwd = CHKALIAS; + for (;;) { + checkkwd = savecheckkwd; + if (readtoken() == TWORD) { + if (line == 0) + line = startlinno; + n = makeword(startlinno); + *app = n; + app = &n->narg.next; + if (savecheckkwd != 0 && !isassignment(wordtext)) + savecheckkwd = 0; + } else if (lasttoken == TREDIR) { + if (line == 0) + line = startlinno; + *rpp = n = redirnode; + rpp = &n->nfile.next; + parsefname(); /* read name of redirection file */ + } else if (lasttoken == TLP && app == &args->narg.next + && redir == 0) { + /* We have a function */ + consumetoken(TRP); + funclinno = plinno; + rmescapes(n->narg.text); + if (strchr(n->narg.text, '/')) + synerror("Bad function name"); + VTRACE(DBG_PARSE, ("Function '%s' seen @%d\n", + n->narg.text, plinno)); + n->type = NDEFUN; + n->narg.lineno = plinno - elided_nl; + n->narg.next = command(); + funclinno = 0; + goto checkneg; + } else { + tokpushback++; + break; + } + } + + if (args == NULL && redir == NULL) + synexpect(-1, 0); + *app = NULL; + *rpp = NULL; + n = stalloc(sizeof(struct ncmd)); + n->type = NCMD; + n->ncmd.lineno = line - elided_nl; + n->ncmd.backgnd = 0; + n->ncmd.args = args; + n->ncmd.redirect = redir; + n->ncmd.lineno = startlinno; + + checkneg: +#ifdef BOGUS_NOT_COMMAND + if (negate) { + VTRACE(DBG_PARSE, ("bogus %snegate simplecmd\n", + (negate&1) ? "" : "double ")); + n2 = stalloc(sizeof(struct nnot)); + n2->type = (negate & 1) ? NNOT : NDNOT; + n2->nnot.com = n; + return n2; + } + else +#endif + return n; +} + +STATIC union node * +makeword(int lno) +{ + union node *n; + + n = stalloc(sizeof(struct narg)); + n->type = NARG; + n->narg.next = NULL; + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + n->narg.lineno = lno; + return n; +} + +void +fixredir(union node *n, const char *text, int err) +{ + + VTRACE(DBG_PARSE, ("Fix redir %s %d\n", text, err)); + if (!err) + n->ndup.vname = NULL; + + if (is_number(text)) + n->ndup.dupfd = number(text); + else if (text[0] == '-' && text[1] == '\0') + n->ndup.dupfd = -1; + else { + + if (err) + synerror("Bad fd number"); + else + n->ndup.vname = makeword(startlinno - elided_nl); + } +} + + +STATIC void +parsefname(void) +{ + union node *n = redirnode; + + if (readtoken() != TWORD) + synexpect(-1, 0); + if (n->type == NHERE) { + struct HereDoc *here = heredoc; + struct HereDoc *p; + + if (quoteflag == 0) + n->type = NXHERE; + VTRACE(DBG_PARSE, ("Here document %d @%d\n", n->type, plinno)); + if (here->striptabs) { + while (*wordtext == '\t') + wordtext++; + } + + /* + * this test is not really necessary, we are not + * required to expand wordtext, but there's no reason + * it cannot be $$ or something like that - that would + * not mean the pid, but literally two '$' characters. + * There is no need for limits on what the word can be. + * However, it needs to stay literal as entered, not + * have $ converted to CTLVAR or something, which as + * the parser is, at the minute, is impossible to prevent. + * So, leave it like this until the rest of the parser is fixed. + */ + 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) + continue; + p->next = here; + } + } else if (n->type == NTOFD || n->type == NFROMFD) { + fixredir(n, wordtext, 0); + } else { + n->nfile.fname = makeword(startlinno - elided_nl); + } +} + +/* + * 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, char * const eofmark, const int striptabs) +{ + + if (striptabs) { + while (c == '\t') + c = pgetc(); + } + if (c == PEOF) { + if (*eofmark == '\0') + return (c); + synerror(EOFhere); + } + if (c == *eofmark) { + int c2; + char *q; + + for (q = eofmark + 1; c2 = pgetc(), *q != '\0' && c2 == *q; q++) + if (c2 == '\n') { + plinno++; + needprompt = doprompt; + } + 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); +} + + +/* + * Input any here documents. + */ + +STATIC int +slurp_heredoc(char *const eofmark, const int striptabs, const int sq) +{ + int c; + char *out; + int lines = plinno; + + c = pgetc(); + + /* + * If we hit EOF on the input, and the eofmark is a null string ('') + * we consider this empty line to be the eofmark, and exit without err. + */ + if (c == PEOF && *eofmark != '\0') + synerror(EOFhere); + + STARTSTACKSTR(out); + + while ((c = checkend(c, eofmark, striptabs)) != PEOF) { + do { + if (sq) { + /* + * in single quoted mode (eofmark quoted) + * all we look for is \n so we can check + * for the epfmark - everything saved literally. + */ + STPUTC(c, out); + if (c == '\n') { + plinno++; + break; + } + continue; + } + /* + * In double quoted (non-quoted eofmark) + * we must handle \ followed by \n here + * otherwise we can mismatch the end mark. + * All other uses of \ will be handled later + * when the here doc is expanded. + * + * This also makes sure \\ followed by \n does + * not suppress the newline (the \ quotes itself) + */ + if (c == '\\') { /* A backslash */ + STPUTC(c, out); + c = pgetc(); /* followed by */ + if (c == '\n') { /* a newline? */ + STPUTC(c, out); + plinno++; + continue; /* don't break */ + } + } + STPUTC(c, out); /* keep the char */ + if (c == '\n') { /* at end of line */ + plinno++; + break; /* look for eofmark */ + } + } while ((c = pgetc()) != PEOF); + + /* + * If we have read a line, and reached EOF, without + * finding the eofmark, whether the EOF comes before + * or immediately after the \n, that is an error. + */ + if (c == PEOF || (c = pgetc()) == PEOF) + synerror(EOFhere); + } + STPUTC('\0', out); + + c = out - stackblock(); + out = stackblock(); + grabstackblock(c); + wordtext = out; + + VTRACE(DBG_PARSE, + ("Slurped a %d line %sheredoc (to '%s')%s: len %d, \"%.*s%s\" @%d\n", + plinno - lines, sq ? "quoted " : "", eofmark, + striptabs ? " tab stripped" : "", c, (c > 16 ? 16 : c), + wordtext, (c > 16 ? "..." : ""), plinno)); + + return (plinno - lines); +} + +static char * +insert_elided_nl(char *str) +{ + while (elided_nl > 0) { + STPUTC(CTLNONL, str); + elided_nl--; + } + return str; +} + +STATIC void +readheredocs(void) +{ + struct HereDoc *here; + union node *n; + int line, l; + + line = 0; /*XXX - gcc! obviously unneeded */ + if (heredoclist) + line = heredoclist->startline + 1; + l = 0; + while (heredoclist) { + line += l; + here = heredoclist; + heredoclist = here->next; + if (needprompt) { + setprompt(2); + needprompt = 0; + } + + l = slurp_heredoc(here->eofmark, here->striptabs, + here->here->nhere.type == NHERE); + + here->here->nhere.doc = n = makeword(line); + + if (here->here->nhere.type == NHERE) + continue; + + /* + * Now "parse" here docs that have unquoted eofmarkers. + */ + setinputstring(wordtext, 1, line); + VTRACE(DBG_PARSE, ("Reprocessing %d line here doc from %d\n", + l, line)); + readtoken1(pgetc(), DQSYNTAX, 1); + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + popfile(); + } +} + +STATIC int +peektoken(void) +{ + int t; + + t = readtoken(); + tokpushback++; + return (t); +} + +STATIC int +readtoken(void) +{ + int t; +#ifdef DEBUG + int alreadyseen = tokpushback; + int savecheckkwd = checkkwd; +#endif + struct alias *ap; + + top: + t = xxreadtoken(); + + if (checkkwd & CHKNL) { + while (t == TNL) { + readheredocs(); + 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; + VTRACE(DBG_PARSE, + ("keyword %s recognized @%d\n", + tokname[t], plinno)); + goto out; + } + } + + if (checkkwd & CHKALIAS && + (ap = lookupalias(wordtext, 1)) != NULL) { + VTRACE(DBG_PARSE, + ("alias '%s' recognized -> <:%s:>\n", + wordtext, ap->val)); + pushstring(ap->val, strlen(ap->val), ap); + goto top; + } + } + out: + if (t != TNOT) + checkkwd = 0; + + VTRACE(DBG_PARSE, ("%stoken %s %s @%d (chkkwd %x->%x)\n", + alreadyseen ? "reread " : "", tokname[t], + t == TWORD ? wordtext : "", plinno, savecheckkwd, checkkwd)); + 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; + CTRACE(DBG_LEXER, + ("xxreadtoken() returns %s (%d) again\n", + tokname[lasttoken], lasttoken)); + return lasttoken; + } + if (needprompt) { + setprompt(2); + needprompt = 0; + } + elided_nl = 0; + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_macro(); + CTRACE(DBG_LEXER, ("xxreadtoken() sees '%c' (%#.2x) ", + c&0xFF, c&0x1FF)); + switch (c) { + case ' ': case '\t': case PFAKE: + CTRACE(DBG_LEXER, (" ignored\n")); + continue; + case '#': + while ((c = pgetc()) != '\n' && c != PEOF) + continue; + CTRACE(DBG_LEXER, + ("skipped comment to (not incl) \\n\n")); + pungetc(); + continue; + + case '\n': + plinno++; + CTRACE(DBG_LEXER, ("newline now @%d\n", plinno)); + needprompt = doprompt; + RETURN(TNL); + case PEOF: + CTRACE(DBG_LEXER, ("EOF -> TEOF (return)\n")); + RETURN(TEOF); + + case '&': + if (pgetc_linecont() == '&') { + CTRACE(DBG_LEXER, + ("and another -> TAND (return)\n")); + RETURN(TAND); + } + pungetc(); + CTRACE(DBG_LEXER, (" -> TBACKGND (return)\n")); + RETURN(TBACKGND); + case '|': + if (pgetc_linecont() == '|') { + CTRACE(DBG_LEXER, + ("and another -> TOR (return)\n")); + RETURN(TOR); + } + pungetc(); + CTRACE(DBG_LEXER, (" -> TPIPE (return)\n")); + RETURN(TPIPE); + case ';': + switch (pgetc_linecont()) { + case ';': + CTRACE(DBG_LEXER, + ("and another -> TENDCASE (return)\n")); + RETURN(TENDCASE); + case '&': + CTRACE(DBG_LEXER, + ("and '&' -> TCASEFALL (return)\n")); + RETURN(TCASEFALL); + default: + pungetc(); + CTRACE(DBG_LEXER, (" -> TSEMI (return)\n")); + RETURN(TSEMI); + } + case '(': + CTRACE(DBG_LEXER, (" -> TLP (return)\n")); + RETURN(TLP); + case ')': + CTRACE(DBG_LEXER, (" -> TRP (return)\n")); + RETURN(TRP); + + case '\\': + switch (pgetc()) { + case '\n': + startlinno = ++plinno; + CTRACE(DBG_LEXER, ("\\\n ignored, now @%d\n", + plinno)); + if (doprompt) + setprompt(2); + else + setprompt(0); + continue; + case PEOF: + CTRACE(DBG_LEXER, + ("then EOF -> TEOF (return) '\\' dropped\n")); + RETURN(TEOF); + default: + CTRACE(DBG_LEXER, ("not \\\n or EOF: ")); + pungetc(); + break; + } + /* FALLTHROUGH */ + default: + CTRACE(DBG_LEXER, ("getting a word\n")); + return readtoken1(c, BASESYNTAX, 0); + } + } +#undef RETURN +} + + + +/* + * 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. + */ + +/* + * We used to remember only the current syntax, variable nesting level, + * double quote state for each var nesting level, and arith nesting + * level (unrelated to var nesting) and one prev syntax when in arith + * syntax. This worked for simple cases, but can't handle arith inside + * var expansion inside arith inside var with some quoted and some not. + * + * Inspired by FreeBSD's implementation (though it was the obvious way) + * though implemented differently, we now have a stack that keeps track + * of what we are doing now, and what we were doing previously. + * Every time something changes, which will eventually end and should + * revert to the previous state, we push this stack, and then pop it + * again later (that is every ${} with an operator (to parse the word + * or pattern that follows) ${x} and $x are too simple to need it) + * $(( )) $( ) and "...". Always. Really, always! + * + * The stack is implemented as one static (on the C stack) base block + * containing LEVELS_PER_BLOCK (8) stack entries, which should be + * enough for the vast majority of cases. For torture tests, we + * malloc more blocks as needed. All accesses through the inline + * functions below. + */ + +/* + * varnest & arinest will typically be 0 or 1 + * (varnest can increment in usages like ${x=${y}} but probably + * does not really need to) + * parenlevel allows balancing parens inside a $(( )), it is reset + * at each new nesting level ( $(( ( x + 3 ${unset-)} )) does not work. + * quoted is special - we need to know 2 things ... are we inside "..." + * (even if inherited from some previous nesting level) and was there + * an opening '"' at this level (so the next will be closing). + * "..." can span nesting levels, but cannot be opened in one and + * closed in a different one. + * To handle this, "quoted" has two fields, the bottom 4 (really 2) + * bits are 0, 1, or 2, for un, single, and double quoted (single quoted + * is really so special that this setting is not very important) + * and 0x10 that indicates that an opening quote has been seen. + * The bottom 4 bits are inherited, the 0x10 bit is not. + */ +struct tokenstate { + const char *ts_syntax; + unsigned short ts_parenlevel; /* counters */ + unsigned short ts_varnest; /* 64000 levels should be enough! */ + unsigned short ts_arinest; + unsigned short ts_quoted; /* 1 -> single, 2 -> double */ + unsigned short ts_magicq; /* heredoc or word expand */ +}; + +#define NQ 0x00 /* Unquoted */ +#define SQ 0x01 /* Single Quotes */ +#define DQ 0x02 /* Double Quotes (or equivalent) */ +#define CQ 0x03 /* C style Single Quotes */ +#define QF 0x0F /* Mask to extract previous values */ +#define QS 0x10 /* Quoting started at this level in stack */ + +#define LEVELS_PER_BLOCK 8 +#define VSS struct statestack + +struct statestack { + VSS *prev; /* previous block in list */ + int cur; /* which of our tokenstates is current */ + struct tokenstate tokenstate[LEVELS_PER_BLOCK]; +}; + +static inline struct tokenstate * +currentstate(VSS *stack) +{ + return &stack->tokenstate[stack->cur]; +} + +#ifdef notdef +static inline struct tokenstate * +prevstate(VSS *stack) +{ + if (stack->cur != 0) + return &stack->tokenstate[stack->cur - 1]; + if (stack->prev == NULL) /* cannot drop below base */ + return &stack->tokenstate[0]; + return &stack->prev->tokenstate[LEVELS_PER_BLOCK - 1]; +} +#endif + +static inline VSS * +bump_state_level(VSS *stack) +{ + struct tokenstate *os, *ts; + + os = currentstate(stack); + + if (++stack->cur >= LEVELS_PER_BLOCK) { + VSS *ss; + + ss = (VSS *)ckmalloc(sizeof (struct statestack)); + ss->cur = 0; + ss->prev = stack; + stack = ss; + } + + ts = currentstate(stack); + + ts->ts_parenlevel = 0; /* parens inside never match outside */ + + ts->ts_quoted = os->ts_quoted & QF; /* these are default settings */ + ts->ts_varnest = os->ts_varnest; + ts->ts_arinest = os->ts_arinest; /* when appropriate */ + ts->ts_syntax = os->ts_syntax; /* they will be altered */ + ts->ts_magicq = os->ts_magicq; + + return stack; +} + +static inline VSS * +drop_state_level(VSS *stack) +{ + if (stack->cur == 0) { + VSS *ss; + + ss = stack; + stack = ss->prev; + if (stack == NULL) + return ss; + ckfree(ss); + } + --stack->cur; + return stack; +} + +static inline void +cleanup_state_stack(VSS *stack) +{ + while (stack->prev != NULL) { + stack->cur = 0; + stack = drop_state_level(stack); + } +} + +#define PARSESUB() {goto parsesub; parsesub_return:;} +#define PARSEARITH() {goto parsearith; parsearith_return:;} + +/* + * The following macros all assume the existance of a local var "stack" + * which contains a pointer to the current struct stackstate + */ + +/* + * These are macros rather than inline funcs to avoid code churn as much + * as possible - they replace macros of the same name used previously. + */ +#define ISDBLQUOTE() (currentstate(stack)->ts_quoted & QS) +#define SETDBLQUOTE() (currentstate(stack)->ts_quoted = QS | DQ) +#ifdef notdef +#define CLRDBLQUOTE() (currentstate(stack)->ts_quoted = \ + stack->cur != 0 || stack->prev ? \ + prevstate(stack)->ts_quoted & QF : 0) +#endif + +/* + * This set are just to avoid excess typing and line lengths... + * The ones that "look like" var names must be implemented to be lvalues + */ +#define syntax (currentstate(stack)->ts_syntax) +#define parenlevel (currentstate(stack)->ts_parenlevel) +#define varnest (currentstate(stack)->ts_varnest) +#define arinest (currentstate(stack)->ts_arinest) +#define quoted (currentstate(stack)->ts_quoted) +#define magicq (currentstate(stack)->ts_magicq) +#define TS_PUSH() (stack = bump_state_level(stack)) +#define TS_POP() (stack = drop_state_level(stack)) + +/* + * Called to parse command substitutions. oldstyle is true if the command + * is enclosed inside `` (otherwise it was enclosed in "$( )") + * + * Internally nlpp is a pointer to the head of the linked + * list of commands (passed by reference), and savelen is the number of + * characters on the top of the stack which must be preserved. + */ +static char * +parsebackq(VSS *const stack, char * const in, + struct nodelist **const pbqlist, const int oldstyle) +{ + struct nodelist **nlpp; + const int savepbq = parsebackquote; + union node *n; + char *out; + char *str = NULL; + char *volatile sstr = str; + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + struct parsefile *const savetopfile = getcurrentfile(); + const int savelen = in - stackblock(); + int saveprompt; + int lno; + + if (setjmp(jmploc.loc)) { + popfilesupto(savetopfile); + if (sstr) + ckfree(__UNVOLATILE(sstr)); + cleanup_state_stack(stack); + parsebackquote = 0; + handler = savehandler; + CTRACE(DBG_LEXER, ("parsebackq() err (%d), unwinding\n", + exception)); + longjmp(handler->loc, 1); + } + INTOFF; + sstr = str = NULL; + if (savelen > 0) { + sstr = str = ckmalloc(savelen); + memcpy(str, stackblock(), savelen); + } + handler = &jmploc; + 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. + */ + int pc; + int psavelen; + char *pstr; + int line1 = plinno; + + VTRACE(DBG_PARSE|DBG_LEXER, + ("parsebackq: repackaging `` as $( )")); + /* + * Because the entire `...` is read here, we don't + * need to bother the state stack. That will be used + * (as appropriate) when the processed string is re-read. + */ + STARTSTACKSTR(out); +#ifdef DEBUG + for (psavelen = 0;;psavelen++) { /* } */ +#else + for (;;) { +#endif + if (needprompt) { + setprompt(2); + needprompt = 0; + } + pc = pgetc(); + VTRACE(DBG_LEXER, + ("parsebackq() got '%c'(%#.2x) in `` %s", pc&0xFF, + pc&0x1FF, pc == '`' ? "terminator\n" : "")); + if (pc == '`') + break; + switch (pc) { + case '\\': + pc = pgetc(); + VTRACE(DBG_LEXER, ("then '%c'(%#.2x) ", + pc&0xFF, pc&0x1FF)); +#ifdef DEBUG + psavelen++; +#endif + if (pc == '\n') { /* keep \ \n for later */ + plinno++; + VTRACE(DBG_LEXER, ("@%d ", plinno)); + needprompt = doprompt; + } + if (pc != '\\' && pc != '`' && pc != '$' + && (!ISDBLQUOTE() || pc != '"')) { + VTRACE(DBG_LEXER, ("keep '\\' ")); + STPUTC('\\', out); + } + break; + + case '\n': + plinno++; + VTRACE(DBG_LEXER, ("@%d ", plinno)); + needprompt = doprompt; + break; + + case PEOF: + startlinno = line1; + VTRACE(DBG_LEXER, ("EOF\n", plinno)); + synerror("EOF in backquote substitution"); + break; + + default: + break; + } + VTRACE(DBG_LEXER, (".\n", plinno)); + STPUTC(pc, out); + } + STPUTC('\0', out); + VTRACE(DBG_LEXER, ("parsebackq() ``:")); + VTRACE(DBG_PARSE|DBG_LEXER, (" read %d", psavelen)); + psavelen = out - stackblock(); + VTRACE(DBG_PARSE|DBG_LEXER, (" produced %d\n", psavelen)); + if (psavelen > 0) { + pstr = grabstackstr(out); + CTRACE(DBG_LEXER, + ("parsebackq() reprocessing as $(%s)\n", pstr)); + setinputstring(pstr, 1, line1); + } + } + nlpp = pbqlist; + while (*nlpp) + nlpp = &(*nlpp)->next; + *nlpp = stalloc(sizeof(struct nodelist)); + (*nlpp)->next = NULL; + parsebackquote = oldstyle; + + if (oldstyle) { + saveprompt = doprompt; + doprompt = 0; + } else + saveprompt = 0; + + lno = -plinno; + CTRACE(DBG_LEXER, ("parsebackq() parsing embedded command list\n")); + n = list(0); + CTRACE(DBG_LEXER, ("parsebackq() parsed $() (%d -> %d)\n", -lno, + lno + plinno)); + lno += plinno; + + if (oldstyle) { + if (peektoken() != TEOF) + synexpect(-1, 0); + 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 + */ + CTRACE(DBG_LEXER, ("parsebackq() back to previous input\n")); + popfile(); + tokpushback = 0; + } + + while (stackblocksize() <= savelen) + growstackblock(); + STARTSTACKSTR(out); + if (str) { + memcpy(out, str, savelen); + STADJUST(savelen, out); + INTOFF; + ckfree(str); + sstr = str = NULL; + INTON; + } + parsebackquote = savepbq; + handler = savehandler; + if (arinest || ISDBLQUOTE()) { + STPUTC(CTLBACKQ | CTLQUOTE, out); + while (--lno >= 0) + STPUTC(CTLNONL, out); + } else + STPUTC(CTLBACKQ, out); + + return out; +} + +/* + * Parse a redirection operator. The parameter "out" points to a string + * specifying the fd to be redirected. It is guaranteed to be either "" + * or a numeric string (for now anyway). The parameter "c" contains the + * first character of the redirection operator. + * + * Note the string "out" is on the stack, which we are about to clobber, + * so process it first... + */ + +static void +parseredir(const char *out, int c) +{ + union node *np; + int fd; + + fd = (*out == '\0') ? -1 : number(out); + + np = stalloc(sizeof(struct nfile)); + VTRACE(DBG_LEXER, ("parseredir after '%s%c' ", out, c)); + if (c == '>') { + if (fd < 0) + fd = 1; + c = pgetc_linecont(); + VTRACE(DBG_LEXER, ("is '%c'(%#.2x) ", c&0xFF, c&0x1FF)); + if (c == '>') + np->type = NAPPEND; + else if (c == '|') + np->type = NCLOBBER; + else if (c == '&') + np->type = NTOFD; + else { + np->type = NTO; + VTRACE(DBG_LEXER, ("unwanted ", c)); + pungetc(); + } + } else { /* c == '<' */ + if (fd < 0) + fd = 0; + c = pgetc_linecont(); + VTRACE(DBG_LEXER, ("is '%c'(%#.2x) ", c&0xFF, c&0x1FF)); + switch (c) { + case '<': + /* if sizes differ, just discard the old one */ + if (sizeof (struct nfile) != sizeof (struct nhere)) + np = stalloc(sizeof(struct nhere)); + np->type = NHERE; + np->nhere.fd = 0; + heredoc = stalloc(sizeof(struct HereDoc)); + heredoc->here = np; + heredoc->startline = plinno; + if ((c = pgetc_linecont()) == '-') { + CTRACE(DBG_LEXER, ("and '%c'(%#.2x) ", + c & 0xFF, c & 0x1FF)); + heredoc->striptabs = 1; + } else { + heredoc->striptabs = 0; + pungetc(); + } + break; + + case '&': + np->type = NFROMFD; + break; + + case '>': + np->type = NFROMTO; + break; + + default: + np->type = NFROM; + VTRACE(DBG_LEXER, ("unwanted('%c'0#.2x)", c&0xFF, + c&0x1FF)); + pungetc(); + break; + } + } + np->nfile.fd = fd; + + VTRACE(DBG_LEXER, (" ->%"PRIdsNT" fd=%d\n", NODETYPENAME(np->type),fd)); + + redirnode = np; /* this is the "value" of TRENODE */ +} + +/* + * 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(); + VTRACE(DBG_LEXER, ("CSTR(\\%c)(\\%#x)", c&0xFF, c&0x1FF)); + switch (c) { + case '\0': + case PEOF: + synerror("Unterminated quoted string"); + case '\n': + plinno++; + VTRACE(DBG_LEXER, ("@%d ", 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 '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 \\c escape sequence"); + if (c == '\\' && pgetc() != '\\') + synerror("Bad \\c\\ escape sequence"); + if (c == '?') + v = 127; + else + v = c & 0x1f; + break; + + case 'x': + n = 2; + goto hexval; + case 'u': + n = 4; + goto hexval; + case 'U': + n = 8; + hexval: + 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 { + pungetc(); + break; + } + } + if (n > 2 && v > 127) { + if (v >= 0xd800 && v <= 0xdfff) + synerror("Invalid \\u escape sequence"); + + /* XXX should we use iconv here. What locale? */ + CHECKSTRSPACE(4, out); + + 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("Unknown $'' escape sequence"); + } + vc = (char)v; + VTRACE(DBG_LEXER, ("->%u(%#x)['%c']", v, v, vc&0xFF)); + + /* + * If we managed to create a \n from a \ sequence (no matter how) + * then we replace it with the magic CRTCNL control char, which + * will turn into a \n again later, but in the meantime, never + * causes LINENO increments. + */ + if (vc == '\n') { + VTRACE(DBG_LEXER, ("CTLCNL.")); + USTPUTC(CTLCNL, out); + return out; + } + + /* + * We can't handle NUL bytes. + * POSIX says we should skip till the closing quote. + */ + if (vc == '\0') { + CTRACE(DBG_LEXER, ("\\0: skip to '", v, v, vc&0xFF)); + 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; + } + CVTRACE(DBG_LEXER, NEEDESC(vc), ("CTLESC-")); + VTRACE(DBG_LEXER, ("'%c'(%#.2x)", vc&0xFF, vc&0x1FF)); + if (NEEDESC(vc)) + USTPUTC(CTLESC, out); + USTPUTC(vc, out); + return out; +} + +/* + * The lowest level basic tokenizer. + * + * The next input byte (character) is in firstc, syn says which + * syntax tables we are to use (basic, single or double quoted, or arith) + * and magicq (used with sqsyntax and dqsyntax only) indicates that the + * quote character itself is not special (used parsing here docs and similar) + * + * The result is the type of the next token (its value, when there is one, + * is saved in the relevant global var - must fix that someday!) which is + * also saved for re-reading ("lasttoken"). + * + * Overall, this routine does far more parsing than it is supposed to. + * That will also need fixing, someday... + */ +STATIC int +readtoken1(int firstc, char const *syn, int oneword) +{ + int c; + char * out; + int len; + struct nodelist *bqlist; + int quotef; + VSS static_stack; + VSS *stack = &static_stack; + + stack->prev = NULL; + stack->cur = 0; + + syntax = syn; + +#ifdef DEBUG +#define SYNTAX ( syntax == BASESYNTAX ? "BASE" : \ + syntax == DQSYNTAX ? "DQ" : \ + syntax == SQSYNTAX ? "SQ" : \ + syntax == ARISYNTAX ? "ARI" : \ + "???" ) +#endif + + startlinno = plinno; + varnest = 0; + quoted = 0; + if (syntax == DQSYNTAX) + SETDBLQUOTE(); + quotef = 0; + bqlist = NULL; + arinest = 0; + parenlevel = 0; + elided_nl = 0; + magicq = oneword; + + CTRACE(DBG_LEXER, ("readtoken1(%c) syntax=%s %s%s(quoted=%x)\n", + firstc&0xFF, SYNTAX, magicq ? "magic quotes" : "", + ISDBLQUOTE()?" ISDBLQUOTE":"", quoted)); + + STARTSTACKSTR(out); + + for (c = firstc ;; c = pgetc_macro()) { /* until of token */ + if (syntax == ARISYNTAX) + out = insert_elided_nl(out); + CHECKSTRSPACE(6, out); /* permit 6 calls to USTPUTC */ + switch (syntax[c]) { + case CFAKE: + VTRACE(DBG_LEXER, ("CFAKE")); + if (syntax == BASESYNTAX && varnest == 0) + break; + VTRACE(DBG_LEXER, (",")); + continue; + case CNL: /* '\n' */ + VTRACE(DBG_LEXER, ("CNL")); + if (syntax == BASESYNTAX && varnest == 0) + break; /* exit loop */ + USTPUTC(c, out); + plinno++; + VTRACE(DBG_LEXER, ("@%d,", plinno)); + if (doprompt) + setprompt(2); + else + setprompt(0); + continue; + + case CSBACK: /* single quoted backslash */ + if ((quoted & QF) == CQ) { + out = readcstyleesc(out); + continue; + } + VTRACE(DBG_LEXER, ("ESC:")); + USTPUTC(CTLESC, out); + /* FALLTHROUGH */ + case CWORD: + VTRACE(DBG_LEXER, ("'%c'", c)); + USTPUTC(c, out); + continue; + + case CCTL: + CVTRACE(DBG_LEXER, !magicq || ISDBLQUOTE(), + ("%s%sESC:",!magicq?"!m":"",ISDBLQUOTE()?"DQ":"")); + if (!magicq || ISDBLQUOTE()) + USTPUTC(CTLESC, out); + VTRACE(DBG_LEXER, ("'%c'", c)); + USTPUTC(c, out); + continue; + case CBACK: /* backslash */ + c = pgetc(); + VTRACE(DBG_LEXER, ("\\'%c'(%#.2x)", c&0xFF, c&0x1FF)); + if (c == PEOF) { + VTRACE(DBG_LEXER, ("EOF, keep \\ ")); + USTPUTC('\\', out); + pungetc(); + continue; + } + if (c == '\n') { + plinno++; + elided_nl++; + VTRACE(DBG_LEXER, ("eli \\n (%d) @%d ", + elided_nl, plinno)); + if (doprompt) + setprompt(2); + else + setprompt(0); + continue; + } + CVTRACE(DBG_LEXER, quotef==0, (" QF=1 ")); + quotef = 1; /* current token is quoted */ + if (quoted && c != '\\' && c != '`' && + c != '$' && (c != '"' || magicq)) { + /* + * retain the \ (which we *know* needs CTLESC) + * when in "..." and the following char is + * not one of the magic few.) + * Otherwise the \ has done its work, and + * is dropped. + */ + VTRACE(DBG_LEXER, ("ESC:'\\'")); + USTPUTC(CTLESC, out); + USTPUTC('\\', out); + } + CVTRACE(DBG_LEXER, NEEDESC(c) || !magicq, + ("%sESC:", NEEDESC(c) ? "+" : "m")); + VTRACE(DBG_LEXER, ("'%c'(%#.2x)", c&0xFF, c&0x1FF)); + if (NEEDESC(c)) + USTPUTC(CTLESC, out); + else if (!magicq) { + USTPUTC(CTLESC, out); + USTPUTC(c, out); + continue; + } + USTPUTC(c, out); + continue; + case CSQUOTE: + if (syntax != SQSYNTAX) { + CVTRACE(DBG_LEXER, !magicq, (" CQM ")); + if (!magicq) + USTPUTC(CTLQUOTEMARK, out); + CVTRACE(DBG_LEXER, quotef==0, (" QF=1 ")); + quotef = 1; + TS_PUSH(); + syntax = SQSYNTAX; + quoted = SQ; + VTRACE(DBG_LEXER, (" TS_PUSH(SQ)")); + continue; + } + if (magicq && arinest == 0 && varnest == 0) { + /* Ignore inside quoted here document */ + VTRACE(DBG_LEXER, ("<<'>>")); + USTPUTC(c, out); + continue; + } + /* End of single quotes... */ + TS_POP(); + VTRACE(DBG_LEXER, ("SQ TS_POP->%s ", SYNTAX)); + CVTRACE(DBG_LEXER, syntax == BASESYNTAX, (" CQE ")); + if (syntax == BASESYNTAX) + USTPUTC(CTLQUOTEEND, out); + continue; + case CDQUOTE: + if (magicq && arinest == 0 /* && varnest == 0 */) { + VTRACE(DBG_LEXER, ("<<\">>")); + /* Ignore inside here document */ + USTPUTC(c, out); + continue; + } + CVTRACE(DBG_LEXER, quotef==0, (" QF=1 ")); + quotef = 1; + if (arinest) { + if (ISDBLQUOTE()) { + VTRACE(DBG_LEXER, + (" CQE ari(%d", arinest)); + USTPUTC(CTLQUOTEEND, out); + TS_POP(); + VTRACE(DBG_LEXER, ("%d)TS_POP->%s ", + arinest, SYNTAX)); + } else { + VTRACE(DBG_LEXER, + (" ari(%d) %s TS_PUSH->DQ CQM ", + arinest, SYNTAX)); + TS_PUSH(); + syntax = DQSYNTAX; + SETDBLQUOTE(); + USTPUTC(CTLQUOTEMARK, out); + } + continue; + } + CVTRACE(DBG_LEXER, magicq, (" MQignDQ ")); + if (magicq) + continue; + if (ISDBLQUOTE()) { + TS_POP(); + VTRACE(DBG_LEXER, + (" DQ TS_POP->%s CQE ", SYNTAX)); + USTPUTC(CTLQUOTEEND, out); + } else { + VTRACE(DBG_LEXER, + (" %s TS_POP->DQ CQM ", SYNTAX)); + TS_PUSH(); + syntax = DQSYNTAX; + SETDBLQUOTE(); + USTPUTC(CTLQUOTEMARK, out); + } + continue; + case CVAR: /* '$' */ + VTRACE(DBG_LEXER, ("'$'...")); + out = insert_elided_nl(out); + PARSESUB(); /* parse substitution */ + continue; + case CENDVAR: /* CLOSEBRACE */ + if (varnest > 0 && !ISDBLQUOTE()) { + VTRACE(DBG_LEXER, ("vn=%d !DQ", varnest)); + TS_POP(); + VTRACE(DBG_LEXER, (" TS_POP->%s CEV ", SYNTAX)); + USTPUTC(CTLENDVAR, out); + } else { + VTRACE(DBG_LEXER, ("'%c'", c)); + USTPUTC(c, out); + } + out = insert_elided_nl(out); + continue; + case CLP: /* '(' in arithmetic */ + parenlevel++; + VTRACE(DBG_LEXER, ("'('(%d)", parenlevel)); + USTPUTC(c, out); + continue;; + case CRP: /* ')' in arithmetic */ + if (parenlevel > 0) { + USTPUTC(c, out); + --parenlevel; + VTRACE(DBG_LEXER, ("')'(%d)", parenlevel)); + } else { + VTRACE(DBG_LEXER, ("')'(%d)", parenlevel)); + if (pgetc_linecont() == /*(*/ ')') { + out = insert_elided_nl(out); + if (--arinest == 0) { + TS_POP(); + USTPUTC(CTLENDARI, out); + } else + USTPUTC(/*(*/ ')', out); + } else { + break; /* to synerror() just below */ +#if 0 /* the old way, causes weird errors on bad input */ + /* + * unbalanced parens + * (don't 2nd guess - no error) + */ + pungetc(); + USTPUTC(/*(*/ ')', out); +#endif + } + } + continue; + case CBQUOTE: /* '`' */ + VTRACE(DBG_LEXER, ("'`' -> parsebackq()\n")); + out = parsebackq(stack, out, &bqlist, 1); + VTRACE(DBG_LEXER, ("parsebackq() -> readtoken1: ")); + continue; + case CEOF: /* --> c == PEOF */ + VTRACE(DBG_LEXER, ("EOF ")); + break; /* will exit loop */ + default: + VTRACE(DBG_LEXER, ("['%c'(%#.2x)]", c&0xFF, c&0x1FF)); + if (varnest == 0 && !ISDBLQUOTE()) + break; /* exit loop */ + USTPUTC(c, out); + VTRACE(DBG_LEXER, (",")); + continue; + } + VTRACE(DBG_LEXER, (" END TOKEN\n", c&0xFF, c&0x1FF)); + break; /* break from switch -> break from for loop too */ + } + + if (syntax == ARISYNTAX) { + cleanup_state_stack(stack); + synerror(/*((*/ "Missing '))'"); + } + if (syntax != BASESYNTAX && /* ! parsebackquote && */ !magicq) { + cleanup_state_stack(stack); + synerror("Unterminated quoted string"); + } + if (varnest != 0) { + cleanup_state_stack(stack); + startlinno = plinno; + /* { */ + synerror("Missing '}'"); + } + + STPUTC('\0', out); + len = out - stackblock(); + out = stackblock(); + + if (!magicq) { + if ((c == '<' || c == '>') + && quotef == 0 && (*out == '\0' || is_number(out))) { + parseredir(out, c); + cleanup_state_stack(stack); + return lasttoken = TREDIR; + } else { + pungetc(); + } + } + + VTRACE(DBG_PARSE|DBG_LEXER, + ("readtoken1 %sword \"%s\", completed%s (%d) left %d enl\n", + (quotef ? "quoted " : ""), out, (bqlist ? " with cmdsubs" : ""), + len, elided_nl)); + + quoteflag = quotef; + backquotelist = bqlist; + grabstackblock(len); + wordtext = out; + cleanup_state_stack(stack); + 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[] = "}-+?="; + + c = pgetc_linecont(); + VTRACE(DBG_LEXER, ("\"$%c\"(%#.2x)", c&0xFF, c&0x1FF)); + if (c == '(' /*)*/) { /* $(command) or $((arith)) */ + if (pgetc_linecont() == '(' /*')'*/ ) { + VTRACE(DBG_LEXER, ("\"$((\" ARITH ")); + out = insert_elided_nl(out); + PARSEARITH(); + } else { + VTRACE(DBG_LEXER, ("\"$(\" CSUB->parsebackq()\n")); + out = insert_elided_nl(out); + pungetc(); + out = parsebackq(stack, out, &bqlist, 0); + VTRACE(DBG_LEXER, ("parseback()->readtoken1(): ")); + } + } else if (c == OPENBRACE || is_name(c) || is_special(c)) { + VTRACE(DBG_LEXER, (" $EXP:CTLVAR ")); + USTPUTC(CTLVAR, out); + typeloc = out - stackblock(); + USTPUTC(VSNORMAL, out); + subtype = VSNORMAL; + flags = 0; + if (c == OPENBRACE) { + c = pgetc_linecont(); + if (c == '#') { + if ((c = pgetc_linecont()) == CLOSEBRACE) + c = '#'; + else if (is_name(c) || isdigit(c)) + subtype = VSLENGTH; + else if (is_special(c)) { + /* + * ${#} is $# - the number of sh params + * ${##} is the length of ${#} + * ${###} is ${#} with as much nothing + * as possible removed from start + * ${##1} is ${#} with leading 1 gone + * ${##\#} is ${#} with leading # gone + * + * this stuff is UGLY! + */ + if (pgetc_linecont() == CLOSEBRACE) { + pungetc(); + subtype = VSLENGTH; + } else { + static char cbuf[2]; + + pungetc(); /* would like 2 */ + cbuf[0] = c; /* so ... */ + cbuf[1] = '\0'; + pushstring(cbuf, 1, NULL); + c = '#'; /* ${#:...} */ + subtype = 0; /* .. or similar */ + } + } else { + pungetc(); + c = '#'; + subtype = 0; + } + } + else + subtype = 0; + VTRACE(DBG_LEXER, ("${ st=%d ", subtype)); + } + if (is_name(c)) { + p = out; + do { + VTRACE(DBG_LEXER, ("%c", c)); + STPUTC(c, out); + c = pgetc_linecont(); + } while (is_in_name(c)); + +#if 0 + if (out - p == 6 && strncmp(p, "LINENO", 6) == 0) { + int i; + int linno; + char buf[10]; + + /* + * The "LINENO hack" + * + * Replace the variable name with the + * current line number. + */ + linno = plinno; + if (funclinno != 0) + linno -= funclinno - 1; + snprintf(buf, sizeof(buf), "%d", linno); + STADJUST(-6, out); + for (i = 0; buf[i] != '\0'; i++) + STPUTC(buf[i], out); + flags |= VSLINENO; + } +#endif + } else if (is_digit(c)) { + do { + VTRACE(DBG_LEXER, ("%c", c)); + STPUTC(c, out); + c = pgetc_linecont(); + } while (subtype != VSNORMAL && is_digit(c)); + } + else if (is_special(c)) { + VTRACE(DBG_LEXER, ("\"$%c", c)); + USTPUTC(c, out); + c = pgetc_linecont(); + } + else { + VTRACE(DBG_LEXER, ("\"$%c(%#.2x)??\n", c&0xFF,c&0x1FF)); + badsub: + cleanup_state_stack(stack); + synerror("Bad substitution"); + } + + STPUTC('=', out); + if (subtype == 0) { + switch (c) { + case ':': + flags |= VSNUL; + c = pgetc_linecont(); + /*FALLTHROUGH*/ + default: + p = strchr(types, c); + if (p == NULL) + goto badsub; + 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 == VSLENGTH && c != /*{*/ '}') + synerror("no modifiers allowed with ${#var}"); + pungetc(); + } + if (quoted || arinest) + flags |= VSQUOTE; + if (subtype >= VSTRIMLEFT && subtype <= VSTRIMRIGHTMAX) + flags |= VSPATQ; + VTRACE(DBG_LEXER, (" st%d:%x", subtype, flags)); + *(stackblock() + typeloc) = subtype | flags; + if (subtype != VSNORMAL) { + TS_PUSH(); + varnest++; + arinest = 0; + if (subtype > VSASSIGN) { /* # ## % %% */ + syntax = BASESYNTAX; + quoted = 0; + magicq = 0; + } + VTRACE(DBG_LEXER, (" TS_PUSH->%s vn=%d%s ", + SYNTAX, varnest, quoted ? " Q" : "")); + } + } else if (c == '\'' && syntax == BASESYNTAX) { + USTPUTC(CTLQUOTEMARK, out); + VTRACE(DBG_LEXER, (" CSTR \"$'\" CQM ")); + CVTRACE(DBG_LEXER, quotef==0, ("QF=1 ")); + quotef = 1; + TS_PUSH(); + syntax = SQSYNTAX; + quoted = CQ; + VTRACE(DBG_LEXER, ("%s->TS_PUSH()->SQ ", SYNTAX)); + } else { + VTRACE(DBG_LEXER, ("$unk -> '$' (pushback '%c'%#.2x)", + c & 0xFF, c & 0x1FF)); + USTPUTC('$', out); + pungetc(); + } + goto parsesub_return; +} + + +/* + * Parse an arithmetic expansion (indicate start of one and set state) + */ +parsearith: { + +#if 0 + if (syntax == ARISYNTAX) { + /* + * we collapse embedded arithmetic expansion to + * parentheses, which should be equivalent + * + * XXX It isn't, must fix, soonish... + */ + USTPUTC('(' /*)*/, out); + USTPUTC('(' /*)*/, out); + /* + * Need 2 of them because there will (should be) + * two closing ))'s to follow later. + */ + parenlevel += 2; + } else +#endif + { + VTRACE(DBG_LEXER, (" CTLARI%c ", ISDBLQUOTE()?'"':'_')); + USTPUTC(CTLARI, out); + if (ISDBLQUOTE()) + USTPUTC('"',out); + else + USTPUTC(' ',out); + + VTRACE(DBG_LEXER, ("%s->TS_PUSH->ARI(1)", SYNTAX)); + TS_PUSH(); + syntax = ARISYNTAX; + arinest = 1; + varnest = 0; + magicq = 1; + } + goto parsearith_return; +} + +} /* end of readtoken */ + + + + +#ifdef mkinit +INCLUDE "parser.h" + +RESET { + psp.v_current_parser = &parse_state; + + parse_state.ps_tokpushback = 0; + parse_state.ps_checkkwd = 0; + parse_state.ps_heredoclist = NULL; +} +#endif + +/* + * 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 || c == CTLQUOTEEND) + 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; + while (*++p != '=') + if (*p == '\0' || !is_in_name(*p)) + return 0; + return 1; +} + +/* + * skip past any \n's, and leave lasttoken set to whatever follows + */ +STATIC void +linebreak(void) +{ + while (readtoken() == TNL) + ; +} + +/* + * The next token must be "token" -- check, then move past it + */ +STATIC void +consumetoken(int token) +{ + if (readtoken() != token) { + VTRACE(DBG_PARSE, ("consumetoken(%d): expecting %s got %s", + token, tokname[token], tokname[lasttoken])); + CVTRACE(DBG_PARSE, (lasttoken==TWORD), (" \"%s\"", wordtext)); + VTRACE(DBG_PARSE, ("\n")); + synexpect(token, NULL); + } +} + +/* + * 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, const char *text) +{ + char msg[64]; + char *p; + + if (lasttoken == TWORD) { + size_t len = strlen(wordtext); + + if (len <= 13) + fmtstr(msg, 34, "Word \"%.13s\" unexpected", wordtext); + else + fmtstr(msg, 34, + "Word \"%.10s...\" unexpected", wordtext); + } else + fmtstr(msg, 34, "%s unexpected", tokname[lasttoken]); + + p = strchr(msg, '\0'); + if (text) + fmtstr(p, 30, " (expecting \"%.10s\")", text); + else if (token >= 0) + fmtstr(p, 30, " (expecting %s)", tokname[token]); + + synerror(msg); + /* NOTREACHED */ +} + + +STATIC void +synerror(const char *msg) +{ + error("%d: Syntax error: %s", startlinno, msg); + /* NOTREACHED */ +} + +STATIC void +setprompt(int which) +{ + whichprompt = which; + +#ifndef SMALL + if (!el) +#endif + out2str(getprompt(NULL)); +} + +/* + * handle getting the next character, while ignoring \ \n + * (which is a little tricky as we only have one char of pushback + * and we need that one elsewhere). + */ +STATIC int +pgetc_linecont(void) +{ + int c; + + while ((c = pgetc()) == '\\') { + c = pgetc(); + if (c == '\n') { + plinno++; + elided_nl++; + VTRACE(DBG_LEXER, ("\"\\n\"drop(el=%d@%d)", + elided_nl, plinno)); + if (doprompt) + setprompt(2); + else + setprompt(0); + } else { + pungetc(); + /* Allow the backslash to be pushed back. */ + pushstring("\\", 1, NULL); + return (pgetc()); + } + } + return (c); +} + +/* + * called by editline -- any expansions to the prompt + * should be added here. + */ +const char * +getprompt(void *unused) +{ + char *p; + const char *cp; + int wp; + + if (!doprompt) + return ""; + + VTRACE(DBG_PARSE|DBG_EXPAND, ("getprompt %d\n", whichprompt)); + + switch (wp = whichprompt) { + case 0: + return ""; + case 1: + p = ps1val(); + break; + case 2: + p = ps2val(); + break; + default: + return "<internal prompt error>"; + } + if (p == NULL) + return ""; + + VTRACE(DBG_PARSE|DBG_EXPAND, ("prompt <<%s>>\n", p)); + + cp = expandstr(p, plinno); + whichprompt = wp; /* history depends on it not changing */ + + VTRACE(DBG_PARSE|DBG_EXPAND, ("prompt -> <<%s>>\n", cp)); + + return cp; +} + +/* + * Expand a string ... used for expanding prompts (PS1...) + * + * Never return NULL, always some string (return input string if invalid) + * + * The internal routine does the work, leaving the result on the + * stack (or in a static string, or even the input string) and + * handles parser recursion, and cleanup after an error while parsing. + * + * The visible interface copies the result off the stack (if it is there), + * and handles stack management, leaving the stack in the exact same + * state it was when expandstr() was called (so it can be used part way + * through building a stack data structure - as in when PS2 is being + * expanded half way through reading a "command line") + * + * on error, expandonstack() cleans up the parser state, but then + * simply jumps out through expandstr() withut doing any stack cleanup, + * which is OK, as the error handler must deal with that anyway. + * + * The split into two funcs is to avoid problems with setjmp/longjmp + * and local variables which could otherwise be optimised into bizarre + * behaviour. + */ +static const char * +expandonstack(char *ps, int cmdsub, int lineno) +{ + union node n; + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + struct parsefile *const savetopfile = getcurrentfile(); + const int save_x = xflag; + struct parse_state new_state = init_parse_state; + struct parse_state *const saveparser = psp.v_current_parser; + const char *result = NULL; + + if (!setjmp(jmploc.loc)) { + handler = &jmploc; + + psp.v_current_parser = &new_state; + setinputstring(ps, 1, lineno); + + readtoken1(pgetc(), DQSYNTAX, 1); + if (backquotelist != NULL) { + if (!cmdsub) + result = ps; + else if (!promptcmds) + result = "-o promptcmds not set: "; + } + if (result == NULL) { + n.narg.type = NARG; + n.narg.next = NULL; + n.narg.text = wordtext; + n.narg.lineno = lineno; + n.narg.backquote = backquotelist; + + xflag = 0; /* we might be expanding PS4 ... */ + expandarg(&n, NULL, 0); + result = stackblock(); + } + } else { + psp.v_current_parser = saveparser; + xflag = save_x; + popfilesupto(savetopfile); + handler = savehandler; + + if (exception == EXEXIT) + longjmp(handler->loc, 1); + if (exception == EXINT) + exraise(SIGINT); + return ps; + } + psp.v_current_parser = saveparser; + xflag = save_x; + popfilesupto(savetopfile); + handler = savehandler; + + + if (result == NULL) + result = ps; + + return result; +} + +const char * +expandstr(char *ps, int lineno) +{ + const char *result = NULL; + struct stackmark smark; + static char *buffer = NULL; /* storage for prompt, never freed */ + static size_t bufferlen = 0; + + setstackmark(&smark); + /* + * At this point we anticipate that there may be a string + * growing on the stack, but we have no idea how big it is. + * However we know that it cannot be bigger than the current + * allocated stack block, so simply reserve the whole thing, + * then we can use the stack without barfing all over what + * is there already... (the stack mark undoes this later.) + */ + (void) stalloc(stackblocksize()); + + result = expandonstack(ps, 1, lineno); + + if (__predict_true(result == stackblock())) { + size_t len = strlen(result) + 1; + + /* + * the result (usual case) is on the stack, which we + * are just about to discard (popstackmark()) so we + * need to move it somewhere safe first. + */ + + if (__predict_false(len > bufferlen)) { + char *new; + size_t newlen = bufferlen; + + if (__predict_false(len > (SIZE_MAX >> 4))) { + result = "huge prompt: "; + goto getout; + } + + if (newlen == 0) + newlen = 32; + while (newlen <= len) + newlen <<= 1; + + new = (char *)realloc(buffer, newlen); + + if (__predict_false(new == NULL)) { + /* + * this should rarely (if ever) happen + * but we must do something when it does... + */ + result = "No mem for prompt: "; + goto getout; + } else { + buffer = new; + bufferlen = newlen; + } + } + (void)memcpy(buffer, result, len); + result = buffer; + } + + getout:; + popstackmark(&smark); + + return result; +} + +/* + * and a simpler version, which does no $( ) expansions, for + * use during shell startup when we know we are not parsing, + * and so the stack is not in use - we can do what we like, + * and do not need to clean up (that's handled externally). + * + * Simply return the result, even if it is on the stack + */ +const char * +expandenv(char *arg) +{ + return expandonstack(arg, 0, 0); +} diff --git a/bin/sh/parser.h b/bin/sh/parser.h new file mode 100644 index 0000000..7545b4f --- /dev/null +++ b/bin/sh/parser.h @@ -0,0 +1,169 @@ +/* $NetBSD: parser.h,v 1.27 2018/12/11 13:31:20 kre Exp $ */ + +/*- + * 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 + */ + +/* control characters in argument strings */ +#define CTL_FIRST '\201' /* first 'special' character */ +#define CTLESC '\201' /* escape next character */ +#define CTLVAR '\202' /* variable defn */ +#define CTLENDVAR '\203' +#define CTLBACKQ '\204' +#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ +/* CTLBACKQ | CTLQUOTE == '\205' */ +#define CTLARI '\206' /* arithmetic expression */ +#define CTLENDARI '\207' +#define CTLQUOTEMARK '\210' +#define CTLQUOTEEND '\211' /* only inside ${...} */ +#define CTLNONL '\212' /* The \n in a deleted \ \n sequence */ + /* pure concidence that (CTLNONL & 0x7f) == '\n' */ +#define CTLCNL '\213' /* A $'\n' - newline not counted */ +#define CTL_LAST '\213' /* last 'special' character */ + +/* 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 VSPATQ 0x40 /* ensure correct pattern quoting in ${x#pat} */ +#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} */ + +union node *parsecmd(int); +void fixredir(union node *, const char *, int); +int goodname(const char *); +int isassignment(const char *); +const char *getprompt(void *); +const char *expandstr(char *, int); +const char *expandenv(char *); + +struct HereDoc; +union node; +struct nodelist; + +struct parse_state { + struct HereDoc *ps_heredoclist; /* list of here documents to read */ + int ps_parsebackquote; /* nonzero inside backquotes */ + int ps_doprompt; /* if set, prompt the user */ + int ps_needprompt; /* true if interactive at line start */ + int ps_lasttoken; /* last token read */ + int ps_tokpushback; /* last token pushed back */ + char *ps_wordtext; /* text of last word returned by readtoken */ + int ps_checkkwd; /* word expansion flags, see below */ + struct nodelist *ps_backquotelist; /* list of cmdsubs to process */ + union node *ps_redirnode; /* node for current redirect */ + struct HereDoc *ps_heredoc; /* current heredoc << beign parsed */ + int ps_quoteflag; /* set if (part) of token was quoted */ + int ps_startlinno; /* line # where last token started */ + int ps_funclinno; /* line # of the current function */ + int ps_elided_nl; /* count of \ \n pairs we have seen */ +}; + +/* + * The parser references the elements of struct parse_state quite + * frequently - they used to be simple globals, so one memory ref + * per access, adding an indirect through global ptr would not be + * nice. The following gross hack allows most of that cost to be + * avoided, by allowing the compiler to understand that the global + * pointer is in fact constant in any function, and so its value can + * be cached, rather than needing to be fetched every time in case + * some other called function has changed it. + * + * The rule to make this work is that any function that wants + * to alter the global must restore it before it returns (and thus + * must have an error trap handler). That means that the struct + * used for the new parser state can be a local in that function's + * stack frame, it never needs to be malloc'd. + */ + +union parse_state_p { + struct parse_state *const c_current_parser; + struct parse_state * v_current_parser; +}; + +extern union parse_state_p psp; + +#define current_parser (psp.c_current_parser) + +/* + * Perhaps one day emulate "static" by moving most of these definitions into + * parser.c ... (only checkkwd & tokpushback are used outside parser.c, + * and only in init.c as a RESET activity) + */ +#define tokpushback (current_parser->ps_tokpushback) +#define checkkwd (current_parser->ps_checkkwd) + +#define noalias (current_parser->ps_noalias) +#define heredoclist (current_parser->ps_heredoclist) +#define parsebackquote (current_parser->ps_parsebackquote) +#define doprompt (current_parser->ps_doprompt) +#define needprompt (current_parser->ps_needprompt) +#define lasttoken (current_parser->ps_lasttoken) +#define wordtext (current_parser->ps_wordtext) +#define backquotelist (current_parser->ps_backquotelist) +#define redirnode (current_parser->ps_redirnode) +#define heredoc (current_parser->ps_heredoc) +#define quoteflag (current_parser->ps_quoteflag) +#define startlinno (current_parser->ps_startlinno) +#define funclinno (current_parser->ps_funclinno) +#define elided_nl (current_parser->ps_elided_nl) + +/* + * Values that can be set in checkkwd + */ +#define CHKKWD 0x01 /* turn word into keyword (if it is) */ +#define CHKNL 0x02 /* ignore leading \n's */ +#define CHKALIAS 0x04 /* lookup words as aliases and ... */ + +/* + * NEOF is returned by parsecmd when it encounters an end of file. It + * must be distinct from NULL, so we use the address of a variable that + * happens to be handy. + */ +#define NEOF ((union node *)&psp) + +#ifdef DEBUG +extern int parsing; +#endif diff --git a/bin/sh/redir.c b/bin/sh/redir.c new file mode 100644 index 0000000..751cce7 --- /dev/null +++ b/bin/sh/redir.c @@ -0,0 +1,982 @@ +/* $NetBSD: redir.c,v 1.62 2018/11/26 20:03:39 kamil Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)redir.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: redir.c,v 1.62 2018/11/26 20:03:39 kamil Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/param.h> /* PIPE_BUF */ +#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 "main.h" +#include "builtins.h" +#include "shell.h" +#include "nodes.h" +#include "jobs.h" +#include "options.h" +#include "expand.h" +#include "redir.h" +#include "output.h" +#include "memalloc.h" +#include "mystring.h" +#include "error.h" +#include "show.h" + + +#define EMPTY -2 /* marks an unused slot in redirtab */ +#define CLOSED -1 /* fd was not open before redir */ +#ifndef PIPE_BUF +# define PIPESIZE 4096 /* amount of buffering in a pipe */ +#else +# define PIPESIZE PIPE_BUF +#endif + + +MKINIT +struct renamelist { + struct renamelist *next; + int orig; + int into; +}; + +MKINIT +struct redirtab { + struct redirtab *next; + struct renamelist *renamed; +}; + + +MKINIT 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; + +/* + * And also where to put internal use fds that should be out of the + * way of user defined fds (normally) + */ +STATIC int big_sh_fd = 0; + +STATIC const struct renamelist *is_renamed(const struct renamelist *, int); +STATIC void fd_rename(struct redirtab *, int, int); +STATIC void free_rl(struct redirtab *, int); +STATIC void openredirect(union node *, char[10], int); +STATIC int openhere(const union node *); +STATIC int copyfd(int, int, int); +STATIC void find_big_fd(void); + + +struct shell_fds { /* keep track of internal shell fds */ + struct shell_fds *nxt; + void (*cb)(int, int); + int fd; +}; + +STATIC struct shell_fds *sh_fd_list; + +STATIC void renumber_sh_fd(struct shell_fds *); +STATIC struct shell_fds *sh_fd(int); + +STATIC const struct renamelist * +is_renamed(const struct renamelist *rl, int fd) +{ + while (rl != NULL) { + if (rl->orig == fd) + return rl; + rl = rl->next; + } + return NULL; +} + +STATIC void +free_rl(struct redirtab *rt, int reset) +{ + struct renamelist *rl, *rn = rt->renamed; + + while ((rl = rn) != NULL) { + rn = rl->next; + if (rl->orig == 0) + fd0_redirected--; + VTRACE(DBG_REDIR, ("popredir %d%s: %s", + rl->orig, rl->orig==0 ? " (STDIN)" : "", + reset ? "" : "no reset\n")); + if (reset) { + if (rl->into < 0) { + VTRACE(DBG_REDIR, ("closed\n")); + close(rl->orig); + } else { + VTRACE(DBG_REDIR, ("from %d\n", rl->into)); + movefd(rl->into, rl->orig); + } + } + ckfree(rl); + } + rt->renamed = NULL; +} + +STATIC void +fd_rename(struct redirtab *rt, int from, int to) +{ + /* XXX someday keep a short list (8..10) of freed renamelists XXX */ + struct renamelist *rl = ckmalloc(sizeof(struct renamelist)); + + rl->next = rt->renamed; + rt->renamed = rl; + + rl->orig = from; + rl->into = to; +} + +/* + * 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. + */ + +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 */ + + CTRACE(DBG_REDIR, ("redirect(F=0x%x):%s\n", flags, redir?"":" NONE")); + for (i = 10 ; --i >= 0 ; ) + memory[i] = 0; + memory[1] = flags & REDIR_BACKQ; + if (flags & REDIR_PUSH) { + /* + * We don't have to worry about REDIR_VFORK here, as + * flags & REDIR_PUSH is never true if REDIR_VFORK is set. + */ + sv = ckmalloc(sizeof (struct redirtab)); + sv->renamed = NULL; + sv->next = redirlist; + redirlist = sv; + } + for (n = redir ; n ; n = n->nfile.next) { + fd = n->nfile.fd; + VTRACE(DBG_REDIR, ("redir %d (max=%d) ", fd, max_user_fd)); + if (fd > max_user_fd) + max_user_fd = fd; + renumber_sh_fd(sh_fd(fd)); + if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) && + n->ndup.dupfd == fd) { + /* redirect from/to same file descriptor */ + /* make sure it stays open */ + if (fcntl(fd, F_SETFD, 0) < 0) + error("fd %d: %s", fd, strerror(errno)); + VTRACE(DBG_REDIR, ("!cloexec\n")); + continue; + } + + if ((flags & REDIR_PUSH) && !is_renamed(sv->renamed, fd)) { + INTOFF; + if (big_sh_fd < 10) + find_big_fd(); + if ((i = fcntl(fd, F_DUPFD, big_sh_fd)) == -1) { + switch (errno) { + case EBADF: + i = CLOSED; + break; + case EMFILE: + case EINVAL: + find_big_fd(); + i = fcntl(fd, F_DUPFD, big_sh_fd); + if (i >= 0) + break; + /* FALLTHRU */ + default: + i = errno; + INTON; /* XXX not needed here ? */ + error("%d: %s", fd, strerror(i)); + /* NOTREACHED */ + } + } + if (i >= 0) + (void)fcntl(i, F_SETFD, FD_CLOEXEC); + fd_rename(sv, fd, i); + VTRACE(DBG_REDIR, ("saved as %d ", i)); + INTON; + } + VTRACE(DBG_REDIR, ("%s\n", fd == 0 ? "STDIN" : "")); + if (fd == 0) + fd0_redirected++; + openredirect(n, memory, flags); + } + if (memory[1]) + out1 = &memout; + if (memory[2]) + out2 = &memout; +} + + +STATIC void +openredirect(union node *redir, char memory[10], int flags) +{ + struct stat sb; + int fd = redir->nfile.fd; + char *fname; + int f; + int eflags, cloexec; + + /* + * We suppress interrupts so that we won't leave open file + * descriptors around. This may not be such a good idea because + * an open of a device or a fifo can block indefinitely. + */ + INTOFF; + if (fd < 10) + memory[fd] = 0; + switch (redir->nfile.type) { + case NFROM: + fname = redir->nfile.expfname; + if (flags & REDIR_VFORK) + eflags = O_NONBLOCK; + else + eflags = 0; + if ((f = open(fname, O_RDONLY|eflags)) < 0) + goto eopen; + VTRACE(DBG_REDIR, ("openredirect(< '%s') -> %d [%#x]", + fname, f, eflags)); + if (eflags) + (void)fcntl(f, F_SETFL, fcntl(f, F_GETFL, 0) & ~eflags); + break; + case NFROMTO: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDWR|O_CREAT, 0666)) < 0) + goto ecreate; + VTRACE(DBG_REDIR, ("openredirect(<> '%s') -> %d", fname, f)); + break; + case NTO: + if (Cflag) { + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY)) == -1) { + if ((f = open(fname, O_WRONLY|O_CREAT|O_EXCL, + 0666)) < 0) + goto ecreate; + } else if (fstat(f, &sb) == -1) { + int serrno = errno; + close(f); + errno = serrno; + goto ecreate; + } else if (S_ISREG(sb.st_mode)) { + close(f); + errno = EEXIST; + goto ecreate; + } + VTRACE(DBG_REDIR, ("openredirect(>| '%s') -> %d", + fname, f)); + break; + } + /* FALLTHROUGH */ + case NCLOBBER: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) + goto ecreate; + VTRACE(DBG_REDIR, ("openredirect(> '%s') -> %d", fname, f)); + break; + case NAPPEND: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0) + goto ecreate; + VTRACE(DBG_REDIR, ("openredirect(>> '%s') -> %d", fname, f)); + break; + case NTOFD: + case NFROMFD: + if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ + if (fd < 10 && redir->ndup.dupfd < 10 && + memory[redir->ndup.dupfd]) + memory[fd] = 1; + else if (copyfd(redir->ndup.dupfd, fd, + (flags & REDIR_KEEP) == 0) < 0) + error("Redirect (from %d to %d) failed: %s", + redir->ndup.dupfd, fd, strerror(errno)); + VTRACE(DBG_REDIR, ("openredirect: %d%c&%d\n", fd, + "<>"[redir->nfile.type==NTOFD], redir->ndup.dupfd)); + } else { + (void) close(fd); + VTRACE(DBG_REDIR, ("openredirect: %d%c&-\n", fd, + "<>"[redir->nfile.type==NTOFD])); + } + INTON; + return; + case NHERE: + case NXHERE: + VTRACE(DBG_REDIR, ("openredirect: %d<<...", fd)); + f = openhere(redir); + break; + default: + abort(); + } + + cloexec = fd > 2 && (flags & REDIR_KEEP) == 0 && !posix; + if (f != fd) { + VTRACE(DBG_REDIR, (" -> %d", fd)); + if (copyfd(f, fd, cloexec) < 0) { + int e = errno; + + close(f); + error("redirect reassignment (fd %d) failed: %s", fd, + strerror(e)); + } + close(f); + } else if (cloexec) + (void)fcntl(f, F_SETFD, FD_CLOEXEC); + VTRACE(DBG_REDIR, ("%s\n", cloexec ? " cloexec" : "")); + + INTON; + return; + ecreate: + exerrno = 1; + error("cannot create %s: %s", fname, errmsg(errno, E_CREAT)); + eopen: + exerrno = 1; + error("cannot open %s: %s", fname, errmsg(errno, E_OPEN)); +} + + +/* + * 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(const union node *redir) +{ + int pip[2]; + int len = 0; + + if (pipe(pip) < 0) + error("Pipe call failed"); + if (redir->type == NHERE) { + len = strlen(redir->nhere.doc->narg.text); + if (len <= PIPESIZE) { + xwrite(pip[1], redir->nhere.doc->narg.text, len); + goto out; + } + } + VTRACE(DBG_REDIR, (" forking [%d,%d]\n", pip[0], pip[1])); + if (forkshell(NULL, NULL, FORK_NOJOB) == 0) { + close(pip[0]); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGHUP, SIG_IGN); +#ifdef SIGTSTP + signal(SIGTSTP, SIG_IGN); +#endif + signal(SIGPIPE, SIG_DFL); + if (redir->type == NHERE) + xwrite(pip[1], redir->nhere.doc->narg.text, len); + else + expandhere(redir->nhere.doc, pip[1]); + _exit(0); + } + VTRACE(DBG_REDIR, ("openhere (closing %d)", pip[1])); + out: + close(pip[1]); + VTRACE(DBG_REDIR, (" (pipe fd=%d)", pip[0])); + return pip[0]; +} + + + +/* + * Undo the effects of the last redirection. + */ + +void +popredir(void) +{ + struct redirtab *rp = redirlist; + + INTOFF; + free_rl(rp, 1); + redirlist = rp->next; + ckfree(rp); + INTON; +} + +/* + * Undo all redirections. Called on error or interrupt. + */ + +#ifdef mkinit + +INCLUDE "redir.h" + +RESET { + while (redirlist) + popredir(); +} + +SHELLPROC { + clearredir(0); +} + +#endif + +/* 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(int vforked) +{ + struct redirtab *rp; + struct renamelist *rl; + + for (rp = redirlist ; rp ; rp = rp->next) { + if (!vforked) + free_rl(rp, 0); + else for (rl = rp->renamed; rl; rl = rl->next) + if (rl->into >= 0) + close(rl->into); + } +} + + + +/* + * Copy a file descriptor to be == to. + * cloexec indicates if we want close-on-exec or not. + * Returns -1 if any error occurs. + */ + +STATIC int +copyfd(int from, int to, int cloexec) +{ + int newfd; + + if (cloexec && to > 2) + newfd = dup3(from, to, O_CLOEXEC); + else + newfd = dup2(from, to); + + return newfd; +} + +/* + * rename fd from to be fd to (closing from). + * close-on-exec is never set on 'to' (unless + * from==to and it was set on from) - ie: a no-op + * returns to (or errors() if an error occurs). + * + * This is mostly used for rearranging the + * results from pipe(). + */ +int +movefd(int from, int to) +{ + if (from == to) + return to; + + (void) close(to); + if (copyfd(from, to, 0) != to) { + int e = errno; + + (void) close(from); + error("Unable to make fd %d: %s", to, strerror(e)); + } + (void) close(from); + + return to; +} + +STATIC void +find_big_fd(void) +{ + int i, fd; + static int last_start = 3; /* aim to keep sh fd's under 20 */ + + if (last_start < 10) + last_start++; + + for (i = (1 << last_start); i >= 10; i >>= 1) { + if ((fd = fcntl(0, F_DUPFD, i - 1)) >= 0) { + close(fd); + break; + } + } + + fd = (i / 5) * 4; + if (fd < 10) + fd = 10; + + big_sh_fd = fd; +} + +/* + * If possible, move file descriptor fd out of the way + * of expected user fd values. Returns the new fd + * (which may be the input fd if things do not go well.) + * Always set close-on-exec on the result, and close + * the input fd unless it is to be our result. + */ +int +to_upper_fd(int fd) +{ + int i; + + VTRACE(DBG_REDIR|DBG_OUTPUT, ("to_upper_fd(%d)", fd)); + if (big_sh_fd < 10) + find_big_fd(); + do { + i = fcntl(fd, F_DUPFD_CLOEXEC, big_sh_fd); + if (i >= 0) { + if (fd != i) + close(fd); + VTRACE(DBG_REDIR|DBG_OUTPUT, ("-> %d\n", i)); + return i; + } + if (errno != EMFILE && errno != EINVAL) + break; + find_big_fd(); + } while (big_sh_fd > 10); + + /* + * If we wanted to move this fd to some random high number + * we certainly do not intend to pass it through exec, even + * if the reassignment failed. + */ + (void)fcntl(fd, F_SETFD, FD_CLOEXEC); + VTRACE(DBG_REDIR|DBG_OUTPUT, (" fails ->%d\n", fd)); + return fd; +} + +void +register_sh_fd(int fd, void (*cb)(int, int)) +{ + struct shell_fds *fp; + + fp = ckmalloc(sizeof (struct shell_fds)); + if (fp != NULL) { + fp->nxt = sh_fd_list; + sh_fd_list = fp; + + fp->fd = fd; + fp->cb = cb; + } +} + +void +sh_close(int fd) +{ + struct shell_fds **fpp, *fp; + + fpp = &sh_fd_list; + while ((fp = *fpp) != NULL) { + if (fp->fd == fd) { + *fpp = fp->nxt; + ckfree(fp); + break; + } + fpp = &fp->nxt; + } + (void)close(fd); +} + +STATIC struct shell_fds * +sh_fd(int fd) +{ + struct shell_fds *fp; + + for (fp = sh_fd_list; fp != NULL; fp = fp->nxt) + if (fp->fd == fd) + return fp; + return NULL; +} + +STATIC void +renumber_sh_fd(struct shell_fds *fp) +{ + int to; + + if (fp == NULL) + return; + +#ifndef F_DUPFD_CLOEXEC +#define F_DUPFD_CLOEXEC F_DUPFD +#define CLOEXEC(fd) (fcntl((fd), F_SETFD, fcntl((fd),F_GETFD) | FD_CLOEXEC)) +#else +#define CLOEXEC(fd) +#endif + + /* + * if we have had a collision, and the sh fd was a "big" one + * try moving the sh fd base to a higher number (if possible) + * so future sh fds are less likely to be in the user's sights + * (incl this one when moved) + */ + if (fp->fd >= big_sh_fd) + find_big_fd(); + + to = fcntl(fp->fd, F_DUPFD_CLOEXEC, big_sh_fd); + if (to == -1) + to = fcntl(fp->fd, F_DUPFD_CLOEXEC, big_sh_fd/2); + if (to == -1) + to = fcntl(fp->fd, F_DUPFD_CLOEXEC, fp->fd + 1); + if (to == -1) + to = fcntl(fp->fd, F_DUPFD_CLOEXEC, 10); + if (to == -1) + to = fcntl(fp->fd, F_DUPFD_CLOEXEC, 3); + if (to == -1) + error("insufficient file descriptors available"); + CLOEXEC(to); + + if (fp->fd == to) /* impossible? */ + return; + + (*fp->cb)(fp->fd, to); + (void)close(fp->fd); + fp->fd = to; +} + +static const struct flgnames { + const char *name; + uint16_t minch; + uint32_t value; +} nv[] = { +#ifdef O_APPEND + { "append", 2, O_APPEND }, +#endif +#ifdef O_ASYNC + { "async", 2, O_ASYNC }, +#endif +#ifdef O_SYNC + { "sync", 2, O_SYNC }, +#endif +#ifdef O_NONBLOCK + { "nonblock", 3, O_NONBLOCK }, +#endif +#ifdef O_FSYNC + { "fsync", 2, O_FSYNC }, +#endif +#ifdef O_DSYNC + { "dsync", 2, O_DSYNC }, +#endif +#ifdef O_RSYNC + { "rsync", 2, O_RSYNC }, +#endif +#ifdef O_ALT_IO + { "altio", 2, O_ALT_IO }, +#endif +#ifdef O_DIRECT + { "direct", 2, O_DIRECT }, +#endif +#ifdef O_NOSIGPIPE + { "nosigpipe", 3, O_NOSIGPIPE }, +#endif +#ifdef O_CLOEXEC + { "cloexec", 2, O_CLOEXEC }, +#endif + { 0, 0, 0 } +}; +#define ALLFLAGS (O_APPEND|O_ASYNC|O_SYNC|O_NONBLOCK|O_DSYNC|O_RSYNC|\ + O_ALT_IO|O_DIRECT|O_NOSIGPIPE|O_CLOEXEC) + +static int +getflags(int fd, int p) +{ + int c, f; + + if (sh_fd(fd) != NULL) { + if (!p) + return -1; + error("Can't get status for fd=%d (%s)", fd, + "Bad file descriptor"); /*XXX*/ + } + + if ((c = fcntl(fd, F_GETFD)) == -1) { + if (!p) + return -1; + error("Can't get status for fd=%d (%s)", fd, strerror(errno)); + } + if ((f = fcntl(fd, F_GETFL)) == -1) { + if (!p) + return -1; + error("Can't get flags for fd=%d (%s)", fd, strerror(errno)); + } + if (c & FD_CLOEXEC) + f |= O_CLOEXEC; + return f & ALLFLAGS; +} + +static void +printone(int fd, int p, int verbose, int pfd) +{ + int f = getflags(fd, p); + const struct flgnames *fn; + + if (f == -1) + return; + + if (pfd) + outfmt(out1, "%d: ", fd); + for (fn = nv; fn->name; fn++) { + if (f & fn->value) { + outfmt(out1, "%s%s", verbose ? "+" : "", fn->name); + f &= ~fn->value; + } else if (verbose) + outfmt(out1, "-%s", fn->name); + else + continue; + if (f || (verbose && fn[1].name)) + outfmt(out1, ","); + } + if (verbose && f) /* f should be normally be 0 */ + outfmt(out1, " +%#x", f); + outfmt(out1, "\n"); +} + +static void +parseflags(char *s, int *p, int *n) +{ + int *v, *w; + const struct flgnames *fn; + size_t len; + + *p = 0; + *n = 0; + for (s = strtok(s, ","); s; s = strtok(NULL, ",")) { + switch (*s++) { + case '+': + v = p; + w = n; + break; + case '-': + v = n; + w = p; + break; + default: + error("Missing +/- indicator before flag %s", s-1); + } + + len = strlen(s); + for (fn = nv; fn->name; fn++) + if (len >= fn->minch && strncmp(s,fn->name,len) == 0) { + *v |= fn->value; + *w &=~ fn->value; + break; + } + if (fn->name == 0) + error("Bad flag `%s'", s); + } +} + +static void +setone(int fd, int pos, int neg, int verbose) +{ + int f = getflags(fd, 1); + int n, cloexec; + + if (f == -1) + return; + + cloexec = -1; + if ((pos & O_CLOEXEC) && !(f & O_CLOEXEC)) + cloexec = FD_CLOEXEC; + if ((neg & O_CLOEXEC) && (f & O_CLOEXEC)) + cloexec = 0; + + if (cloexec != -1 && fcntl(fd, F_SETFD, cloexec) == -1) + error("Can't set status for fd=%d (%s)", fd, strerror(errno)); + + pos &= ~O_CLOEXEC; + neg &= ~O_CLOEXEC; + f &= ~O_CLOEXEC; + n = f; + n |= pos; + n &= ~neg; + if (n != f && fcntl(fd, F_SETFL, n) == -1) + error("Can't set flags for fd=%d (%s)", fd, strerror(errno)); + if (verbose) + printone(fd, 1, verbose, 1); +} + +int +fdflagscmd(int argc, char *argv[]) +{ + char *num; + int verbose = 0, ch, pos = 0, neg = 0; + char *setflags = NULL; + + optreset = 1; optind = 1; /* initialize getopt */ + while ((ch = getopt(argc, argv, ":vs:")) != -1) + switch ((char)ch) { + case 'v': + verbose = 1; + break; + case 's': + if (setflags) + goto msg; + setflags = optarg; + break; + case '?': + default: + msg: + error("Usage: fdflags [-v] [-s <flags> fd] [fd...]"); + /* NOTREACHED */ + } + + argc -= optind, argv += optind; + + if (setflags) + parseflags(setflags, &pos, &neg); + + if (argc == 0) { + int i; + + if (setflags) + goto msg; + + for (i = 0; i <= max_user_fd; i++) + printone(i, 0, verbose, 1); + return 0; + } + + while ((num = *argv++) != NULL) { + int fd = number(num); + + while (num[0] == '0' && num[1] != '\0') /* skip 0's */ + num++; + if (strlen(num) > 5) + error("%s too big to be a file descriptor", num); + + if (setflags) + setone(fd, pos, neg, verbose); + else + printone(fd, 1, verbose, argc > 1); + } + return 0; +} + +#undef MAX /* in case we inherited them from somewhere */ +#undef MIN + +#define MIN(a,b) (/*CONSTCOND*/((a)<=(b)) ? (a) : (b)) +#define MAX(a,b) (/*CONSTCOND*/((a)>=(b)) ? (a) : (b)) + + /* now make the compiler work for us... */ +#define MIN_REDIR MIN(MIN(MIN(MIN(NTO,NFROM), MIN(NTOFD,NFROMFD)), \ + MIN(MIN(NCLOBBER,NAPPEND), MIN(NHERE,NXHERE))), NFROMTO) +#define MAX_REDIR MAX(MAX(MAX(MAX(NTO,NFROM), MAX(NTOFD,NFROMFD)), \ + MAX(MAX(NCLOBBER,NAPPEND), MAX(NHERE,NXHERE))), NFROMTO) + +static const char *redir_sym[MAX_REDIR - MIN_REDIR + 1] = { + [NTO - MIN_REDIR]= ">", + [NFROM - MIN_REDIR]= "<", + [NTOFD - MIN_REDIR]= ">&", + [NFROMFD - MIN_REDIR]= "<&", + [NCLOBBER - MIN_REDIR]= ">|", + [NAPPEND - MIN_REDIR]= ">>", + [NHERE - MIN_REDIR]= "<<", + [NXHERE - MIN_REDIR]= "<<", + [NFROMTO - MIN_REDIR]= "<>", +}; + +int +outredir(struct output *out, union node *n, int sep) +{ + if (n == NULL) + return 0; + if (n->type < MIN_REDIR || n->type > MAX_REDIR || + redir_sym[n->type - MIN_REDIR] == NULL) + return 0; + + if (sep) + outc(sep, out); + + /* + * ugly, but all redir node types have "fd" in same slot... + * (and code other places assumes it as well) + */ + if ((redir_sym[n->type - MIN_REDIR][0] == '<' && n->nfile.fd != 0) || + (redir_sym[n->type - MIN_REDIR][0] == '>' && n->nfile.fd != 1)) + outfmt(out, "%d", n->nfile.fd); + + outstr(redir_sym[n->type - MIN_REDIR], out); + + switch (n->type) { + case NHERE: + outstr("'...'", out); + break; + case NXHERE: + outstr("...", out); + break; + case NTOFD: + case NFROMFD: + if (n->ndup.dupfd < 0) + outc('-', out); + else + outfmt(out, "%d", n->ndup.dupfd); + break; + default: + outstr(n->nfile.expfname, out); + break; + } + return 1; +} diff --git a/bin/sh/redir.h b/bin/sh/redir.h new file mode 100644 index 0000000..b186bb4 --- /dev/null +++ b/bin/sh/redir.h @@ -0,0 +1,55 @@ +/* $NetBSD: redir.h,v 1.24 2017/06/30 23:01:21 kre Exp $ */ + +/*- + * 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 + */ + +/* flags passed to redirect */ +#define REDIR_PUSH 0x01 /* save previous values of file descriptors */ +#define REDIR_BACKQ 0x02 /* save the command output in memory */ +#define REDIR_VFORK 0x04 /* running under vfork(2), be careful */ +#define REDIR_KEEP 0x08 /* don't close-on-exec */ + +union node; +void redirect(union node *, int); +void popredir(void); +int fd0_redirected_p(void); +void clearredir(int); +int movefd(int, int); +int to_upper_fd(int); +void register_sh_fd(int, void (*)(int, int)); +void sh_close(int); +struct output; +int outredir(struct output *, union node *, int); + +int max_user_fd; /* highest fd used by user */ diff --git a/bin/sh/sh.1 b/bin/sh/sh.1 new file mode 100644 index 0000000..4630689 --- /dev/null +++ b/bin/sh/sh.1 @@ -0,0 +1,4549 @@ +.\" $NetBSD: sh.1,v 1.217 2019/01/21 14:09:24 kre Exp $ +.\" 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. +.\" +.\" @(#)sh.1 8.6 (Berkeley) 5/4/95 +.\" +.Dd December 12, 2018 +.Dt SH 1 +.\" everything except c o and s (keep them ordered) +.ds flags abCEeFfhIiLmnpquVvXx +.Os +.Sh NAME +.Nm sh +.Nd command interpreter (shell) +.Sh SYNOPSIS +.Nm +.Bk -words +.Op Fl \*[flags] +.Op Cm +\*[flags] +.Ek +.Bk -words +.Op Fl o Ar option_name +.Op Cm +o Ar option_name +.Ek +.Bk -words +.Op Ar command_file Op Ar argument ... +.Ek +.Nm +.Fl c +.Bk -words +.Op Fl s +.Op Fl \*[flags] +.Op Cm +\*[flags] +.Ek +.Bk -words +.Op Fl o Ar option_name +.Op Cm +o Ar option_name +.Ek +.Bk -words +.Ar command_string +.Op Ar command_name Op Ar argument ... +.Ek +.Nm +.Fl s +.Bk -words +.Op Fl \*[flags] +.Op Cm +\*[flags] +.Ek +.Bk -words +.Op Fl o Ar option_name +.Op Cm +o Ar option_name +.Ek +.Bk -words +.Op Ar argument ... +.Ek +.Sh DESCRIPTION +.Nm +is the standard command interpreter for the system. +The current version of +.Nm +is in the process of being changed to conform more closely to the +POSIX 1003.2 and 1003.2a specifications for the shell. +This version has many +features which make it appear similar in some respects to the Korn shell, +but it is not a Korn shell clone (see +.Xr ksh 1 ) . +This man page is not intended +to be a tutorial or 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. +A shell is the program that is running when a user logs into the system. +(Users can select which shell is executed for them at login 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 and +the file can be executed directly by the shell. +.Ss Invocation +If no arguments are present and if the standard input, +and standard error output, of the shell +are connected to a terminal (or terminals, or if the +.Fl i +flag is set), +and the +.Fl c +option is not present, 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 +.Sq - , +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 +.Pa .profile +in the user's home directory +.Pq \&$HOME , +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, +and either the shell is interactive, or the +.Cm posix +option is not set, +the shell then performs parameter and arithmetic +expansion on the value of +.Ev ENV , +(these are described later) +and then reads commands from the file name that results. +If +.Ev ENV +contains a command substitution, or one of the +other expansions fails, or if there are no expansions +to expand, the value of +.Ev ENV +is used as the file name. +.Pp +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. +To set the +.Ev ENV +variable to some file, place the following line in your +.Pa .profile +of your home directory +.Pp +.Dl ENV=$HOME/.shinit; export ENV +.Pp +substituting for +.Dq .shinit +any filename you wish. +Since the +.Ev ENV +file can be read for every invocation of the shell, including shell scripts +and non-interactive shells, the following paradigm is useful for +restricting commands in the +.Ev ENV +file to interactive invocations. +Place commands within the +.Dq Ic case +and +.Dq Ic esac +below (these commands are described later): +.Bd -literal -offset indent +case $- in *i*) + # commands for interactive use only + ... +esac +.Ed +.Pp +If command line arguments besides the options have been specified, and +neither +.Fl c +nor +.Fl s +was given, then the shell treats the first argument +as the name of a file from which to read commands (a shell script). +This also becomes +.Li $0 +and the remaining arguments are set as the +positional parameters of the shell +.Li ( $1 , $2 , +etc). +Otherwise, if +.Fl c +was given, then the first argument, which must exist, +is taken to be a string of +.Nm +commands to execute. +Then if any additional arguments follow the command string, +those arguments become +.Li $0 , $1 , +\&... +Otherwise, if additional arguments were given +(which implies that +.Fl s +was set) +those arguments become +.Li $1 , $2 , +\&... +If +.Li $0 +has not been set by the preceding processing, it +will be set to +.Va argv\^ Ns [ 0 ] +as passed to the shell, which will +usually be the name of the shell itself. +If +.Fl s +was given, or if neither +.Fl c +nor any additional (non-option) arguments were present, +the shell reads commands from its standard input. +.\" +.\" +.Ss Argument List Processing +.\" +Currently, all of the single letter options that can meaningfully +be set using the +.Ic set +built-in, have a corresponding name +that can be used as an argument to the +.Fl o +option. +The +.Ic set Fl o +name is provided next to the single letter option in +the description below. +Some options have only a long name, they are described after +the flag options, they are used with +.Fl o +or +.Cm +o +only, either on the command line, or with the +.Ic set +built-in command. +Other options described are for the command line only. +Specifying a dash +.Dq Cm \- +turns the option on, while using a plus +.Dq Cm + +disables the option. +The following options can be set from the command line and, +unless otherwise stated, with the +.Ic set +built-in (described later). +.\" +.\" strlen("quietprofile") == strlen("local_lineno"): pick the latter +.\" to give the indent as the _ in local_lineno, and the fi ligature in +.\" quietprofile combine to make "local_lineno' slightly wider when printed +.\" (in italics) in a variable width font. +.Bl -tag -width ".Fl L Em local_lineno" -offset indent +.\" +.It Fl a Em allexport +Automatically export any variable to which a value is assigned +while this flag is set, unless the variable has been marked as +not for export. +.It Fl b Em notify +Enable asynchronous notification of background job completion. +(Not implemented.) +.It Fl C Em noclobber +Don't overwrite existing files with +.Dq > . +.It Fl c +Read commands from the +.Ar command_string +operand instead of, or in addition to, from the standard input. +Special parameter +.Dv 0 \" $0 (comments like this for searching sources only) +will be set from the +.Ar command_name +operand if given, and the positional parameters +.Dv ( 1 , 2 , +etc.) +set from the remaining argument operands, if any. +.Fl c +is only available at invocation, it cannot be +.Ic set , +and there is no form using +.Dq Cm \&+ . +.It Fl E Em emacs +Enable the built-in emacs style +command line editor (disables +.Fl V +if it has been set). +(See the +.Sx Command Line Editing +section below.) +.It Fl e Em errexit +If not interactive, exit immediately if any untested command fails. +If interactive, and an untested command fails, +cease all processing of the current command and return to +prompt for a new command. +The exit status of a command is considered to be +explicitly tested if the command is used to control an +.Ic if , +.Ic elif , +.Ic while , +or +.Ic until , +or if the command is the left hand operand of an +.Dq && +or +.Dq || +operator, +or if it is a pipeline (or simple command) preceded by the +.Dq \&! +operator. +With pipelines, only the status of the entire pipeline +(indicated by the last command it contains) +is tested when +.Fl e +is set to determine if the shell should exit. +.It Fl F Em fork +Cause the shell to always use +.Xr fork 2 +instead of attempting +.Xr vfork 2 +when it needs to create a new process. +This should normally have no visible effect, +but can slow execution. +The +.Nm +can be compiled to always use +.Xr fork 2 +in which case altering the +.Fl F +flag has no effect. +.It Fl f Em noglob +Disable pathname expansion. +.It Fl h Em trackall +Functions defined while this option is set will have paths bound to +commands to be executed by the function at the time of the definition. +When off when a function is defined, +the file system is searched for commands each time the function is invoked. +(Not implemented.) +.It Fl I Em ignoreeof +Ignore EOFs from input when interactive. +(After a large number of consecutive EOFs the shell will exit anyway.) +.It Fl i Em interactive +Force the shell to behave interactively. +.It Fl L Em local_lineno +When set, before a function is defined, +causes the variable +.Dv LINENO +when used within the function, +to refer to the line number defined such that +first line of the function is line 1. +When reset, +.Dv LINENO +in a function refers to the line number within the file +within which the definition of the function occurs. +This option defaults to +.Dq on +in this shell. +For more details see the section +.Sx LINENO +below. +.It Fl m Em monitor +Turn on job control (set automatically when interactive). +.It Fl n Em noexec +Read and parse commands, but do not execute them. +This is useful for checking the syntax of shell scripts. +If +.Fl n +becomes set in an interactive shell, it will automatically be +cleared just before the next time the command line prompt +.Pq Ev PS1 +is written. +.It Fl p Em nopriv +Do not attempt to reset effective UID if it does not match UID. +This is not set by default to help avoid incorrect usage by setuid +root programs via +.Xr system 3 +or +.Xr popen 3 . +.It Fl q Em quietprofile +If the +.Fl v +or +.Fl x +options have been set, do not apply them when reading +initialization files, these being +.Pa /etc/profile , +.Pa .profile , +and the file specified by the +.Ev ENV +environment variable. +.It Fl s Em stdin +Read commands from standard input (set automatically if +neither +.Fl c +nor file arguments are present). +If after processing a command_string with the +.Fl c +option, the shell has not exited, and the +.Fl s +option is set, it will continue reading more commands from standard input. +This option has no effect when set or reset after the shell has +already started reading from the command_file, or from standard input. +Note that the +.Fl s +flag being set does not cause the shell to be interactive. +.It Fl u Em nounset +Write a message to standard error when attempting to obtain a +value from a variable that is not set, +and if the shell is not interactive, exit immediately. +For interactive shells, instead return immediately to the command prompt +and read the next command. +Note that expansions (described later, see +.Sx Word Expansions +below) using the +.Sq \&+ , +.Sq \&\- , +.Sq \&= , +or +.Sq \&? +operators test if the variable is set, before attempting to +obtain its value, and hence are unaffected by +.Fl u . +.It Fl V Em vi +Enable the built-in +.Xr vi 1 +command line editor (disables +.Fl E +if it has been set). +(See the +.Sx Command Line Editing +section below.) +.It Fl v Em verbose +The shell writes its input to standard error as it is read. +Useful for debugging. +.It Fl X Em xlock +Cause output from the +.Ic xtrace +.Pq Fl x +option to be sent to standard error as it exists when the +.Fl X +option is enabled (regardless of its previous state.) +For example: +.Bd -literal -compact + set -X 2>/tmp/trace-file +.Ed +will arrange for tracing output to be sent to the file named, +instead of wherever it was previously being sent, +until the X option is set again, or cleared. +.Pp +Each change (set or clear) to +.Fl X +is also performed upon +.Fl x , +but not the converse. +.It Fl x Em xtrace +Write each command to standard error (preceded by the expanded value of +.Li $PS4 ) +before it is executed. +Unless +.Fl X +is set, +.Dq "standard error" +means that which existed immediately before any redirections to +be applied to the command are performed. +Useful for debugging. +.It "\ \ " Em cdprint +Make an interactive shell always print the new directory name when +changed by the +.Ic cd +command. +In a non-interactive shell this option has no effect. +.It "\ \ " Em nolog +Prevent the entry of function definitions into the command history (see +.Ic fc +in the +.Sx Built-ins +section.) +(Not implemented.) +.It "\ \ " Em pipefail +If set when a pipeline is created, +the way the exit status of the pipeline is determined +is altered. +See +.Sx Pipelines +below for the details. +.It "\ \ " Em posix +Enables closer adherence to the POSIX shell standard. +This option will default set at shell startup if the +environment variable +.Ev POSIXLY_CORRECT +is present. +That can be overridden (set or reset) by the +.Fl o +option on the command line. +Currently this option controls whether (!posix) or not (posix) +the file given by the +.Ev ENV +variable is read at startup by a non-interactive shell. +It also controls whether file descriptors greater than 2 +opened using the +.Ic exec +built-in command are passed on to utilities executed +.Dq ( yes +in posix mode), +whether a colon (:) terminates the user name in tilde (~) expansions +other than in assignment statements +.Dq ( no +in posix mode), +the format of the output of the +.Ic kill Fl l +command, where posix mode causes the names of the signals +be separated by either a single space or newline, and where otherwise +sufficient spaces are inserted to generate nice looking columns, +and whether the shell treats +an empty brace-list compound statement as a syntax error +(expected by POSIX) or permits it. +Such statements +.Dq "{\ }" +can be useful when defining dummy functions. +Lastly, in posix mode, only one +.Dq \&! +is permitted before a pipeline. +.It "\ \ " Em promptcmds +Allows command substitutions (as well as parameter +and arithmetic expansions, which are always performed) +upon the prompt strings +.Ev PS1 , +.Ev PS2 , +and +.Ev PS4 +each time, before they are output. +This option should not be set until after the prompts +have been set (or verified) to avoid accidentally importing +unwanted command substitutions from the environment. +.It "\ \ " Em tabcomplete +Enables filename completion in the command line editor. +Typing a tab character will extend the current input word to match a +filename. +If more than one filename matches it is only extended to be the common prefix. +Typing a second tab character will list all the matching names. +One of the editing modes, either +.Fl E +or +.Fl V , +must be enabled for this to work. +.El +.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 that are special to the shell called +.Dq operators . +There are two types of operators: control operators and redirection +operators (their meaning is discussed later). +The following is a list of operators: +.Bl -ohang -offset indent +.It "Control operators:" +.Dl & && \&( \&) \&; ;; ;& \&| || <newline> +.It "Redirection operators:" +.Dl < > >| << >> <& >& <<- <> +.El +.Ss Quoting +Quoting is used to remove the special meaning of certain characters or +words to the shell, such as operators, whitespace, or keywords. +There are four types of quoting: +matched single quotes, +matched double quotes, +backslash, +and +dollar preceding matched single quotes (enhanced C style strings.) +.Ss Backslash +An unquoted backslash preserves the literal meaning of the following +character, with the exception of +.Aq newline . +An unquoted backslash preceding a +.Aq newline +is treated as a line continuation, the two characters are simply removed. +.Ss 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). +.Ss Double Quotes +Enclosing characters within double quotes preserves the literal +meaning of all characters except dollar sign +.Pq Li \&$ , +backquote +.Pq Li \&` , +and backslash +.Pq Li \e . +The backslash inside double quotes is historically weird, and serves to +quote only the following characters (and these not in all contexts): +.Dl $ ` \*q \e <newline> , +where a backslash newline is a line continuation as above. +Otherwise it remains literal. +.\" +.\" +.Ss Dollar Single Quotes ( Li \&$'...' ) +.\" +.Bd -filled -offset indent +.Bf Em +Note: this form of quoting is still somewhat experimental, +and yet to be included in the POSIX standard. +This implementation is based upon the current proposals for +standardization, and is subject to change should the eventual +adopted text differ. +.Ef +.Ed +.Pp +Enclosing characters in a matched pair of single quotes, with the +first immediately preceded by an unquoted dollar sign +.Pq Li \&$ +provides a quoting mechanism similar to single quotes, except +that within the sequence of characters, any backslash +.Pq Li \e , +is an escape character, which causes the following character to +be treated specially. +Only a subset of the characters that can occur in the string +are defined after a backslash, others are reserved for future +definition, and currently generate a syntax error if used. +The escape sequences are modeled after the similar sequences +in strings in the C programming language, with some extensions. +.Pp +The following characters are treated literally when following +the escape character (backslash): +.Dl \e \&' \(dq +The sequence +.Dq Li \e\e +allows the escape character (backslash) to appear in the string literally. +.Dq Li \e' +allows a single quote character into the string, such an +escaped single quote does not terminate the quoted string. +.Dq Li \e\(dq +is for compatibility with C strings, the double quote has +no special meaning in a shell C-style string, +and does not need to be escaped, but may be. +.Pp +A newline following the escape character is treated as a line continuation, +like the same sequence in a double quoted string, +or when not quoted \(en +the two characters, the backslash escape and the newline, +are removed from the input string. +.Pp +The following characters, when escaped, are converted in a +manner similar to the way they would be in a string in the C language: +.Dl a b e f n r t v +An escaped +.Sq a +generates an alert (or +.Sq BEL ) +character, that is, control-G, or 0x07. +In a similar way, +.Sq b +is backspace (0x08), +.Sq e +(an extension to C) is escape (0x1B), +.Sq f +is form feed (0x0C), +.Sq n +is newline (or line feed, 0x0A), +.Sq r +is return (0x0D), +.Sq t +is horizontal tab (0x09), +and +.Sq v +is vertical tab (0x13). +.Pp +In addition to those there are 5 forms that need additional +data, which is obtained from the subsequent characters. +An escape +.Pq Li \e +followed by one, two or three, octal digits +.Po So 0 Sc Ns \&.. Ns So 7 Sc Ns Pc +is processed to form an 8 bit character value. +If only one or two digits are present, the following +character must be something other than an octal digit. +It is safest to always use all 3 digits, with leading +zeros if needed. +If all three digits are present, the first must be one of +.So 0 Sc Ns \&.. Ns So 3 Sc . +.Pp +An escape followed by +.Sq x +(lower case only) can be followed by one or two +hexadecimal digits +.Po So 0 Sc Ns \&.. Ns So 9 Sc , So A Sc Ns \&.. Ns So F Sc , or So a Sc Ns \&.. Ns So f Sc . Pc +As with octal, if only one hex digit is present, the following +character must be something other than a hex digit, +so always giving 2 hex digits is best. +However, unlike octal, it is unspecified in the standard +how many hex digits can be consumed. +This +.Nm +takes at most two, but other shells will continue consuming +characters as long as they remain valid hex digits. +Consequently, users should ensure that the character +following the hex escape sequence is something other than +a hex digit. +One way to achieve this is to end the +.Li $'...' +string immediately +after the final hex digit, and then, immediately start +another, so +.Dl \&$'\ex33'$'4...' +always gives the character with value 0x33 +.Pq Sq 3 , +followed by the character +.Sq 4 , +whereas +.Dl \&$'\ex334' +in some other shells would be the hex value 0x334 (10, or more, bits). +.Pp +There are two escape sequences beginning with +.Sq Li \eu +or +.Sq Li \eU . +The former is followed by from 1 to 4 hex digits, the latter by +from 1 to 8 hex digits. +Leading zeros can be used to pad the sequences to the maximum +permitted length, to avoid any possible ambiguity problem with +the following character, and because there are some shells that +insist on exactly 4 (or 8) hex digits. +These sequences are evaluated to form the value of a Unicode code +point, which is then encoded into UTF-8 form, and entered into the +string. +(The code point should be converted to the appropriate +code point value for the corresponding character in the character +set given by the current locale, or perhaps the locale in use +when the shell was started, but is not... currently.) +Not all values that are possible to write are valid, values that +specify (known) invalid Unicode code points will be rejected, or +simply produce +.Sq \&? . +.Pp +Lastly, as another addition to what is available in C, the escape +character (backslash), followed by +.Sq c +(lower case only) followed by one additional character, which must +be an alphabetic character (a letter), or one of the following: +.Dl \&@ \&[ \&\e \&] \&^ \&_ \&? +Other than +.Sq Li \ec? +the value obtained is the least significant 5 bits of the +ASCII value of the character following the +.Sq Li \ec +escape sequence. +That is what is commonly known as the +.Dq control +character obtained from the given character. +The escape sequence +.Sq Li \ec? +yields the ASCII DEL character (0x7F). +Note that to obtain the ASCII FS character (0x1C) this way, +.Pq "that is control-\e" +the trailing +.Sq Li \e +must be escaped itself, and so for this one case, the full +escape sequence is +.Dq Li \ec\e\e . +The sequence +.Dq Li \ec\e Ns Ar X\^ +where +.Sq Ar X\^ +is some character other than +.Sq Li \e +is reserved for future use, its meaning is unspecified. +In this +.Nm +an error is generated. +.Pp +If any of the preceding escape sequences generate the value +.Sq \e0 +(a NUL character) that character, and all that follow in the same +.Li $'...' +string, are omitted from the resulting word. +.Pp +After the +.Li $'...' +string has had any included escape sequences +converted, it is treated as if it had been a single quoted string. +.\" +.\" +.Ss Reserved Words +.\" +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 reserved words: +.Bl -column while while while while -offset indent +.It Ic \&! Ta Ic \&{ Ta Ic \&} Ta Ic case +.It Ic do Ta Ic done Ta Ic elif Ta Ic else +.It Ic esac Ta Ic fi Ta Ic for Ta Ic if +.It Ic in Ta Ic then Ta Ic until Ta Ic while +.El +.Pp +Their meanings are discussed later. +.\" +.\" +.Ss Aliases +.\" +An alias is a name and corresponding value set using the +.Ic alias +built-in command. +Whenever a reserved word (see above) may occur, +and after checking for reserved words, 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 lf +with the value +.Dq "ls -F" , +then the input: +.Pp +.Dl lf foobar Aq return +.Pp +would become +.Pp +.Dl ls -F foobar Aq return +.Pp +Aliases provide a convenient way for naive users to create shorthands for +commands without having to learn how to create functions with arguments. +They can also be used to create lexically obscure code. +This use is strongly discouraged. +.\" +.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 POSIX 1003.2 document). +Essentially though, a line is read and if the first +word of the line (or after a control operator) is not a reserved word, +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 -offset indent +.It +Leading words of the form +.Dq Ar name Ns Li = Ns Ar value +are stripped off, the value is expanded, as described below, +and the results are assigned to the environment of the simple command. +Redirection operators and their arguments (as described below) are +stripped off and saved for processing in step 3 below. +.It +The remaining words are expanded as described in the +.Sx Word Expansions +section below. +The first remaining word is considered the command name and the +command is located. +Any remaining words are considered the arguments of the command. +If no command name resulted, then the +.Dq Ar name Ns Li = Ns Ar value +variable assignments recognized in item 1 affect the current shell. +.It +Redirections are performed, from first to last, in the order given, +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 +.Dl Oo Ar n Oc Ns Va redir-op Ar file +.Pp +where +.Va redir-op +is one of the redirection operators mentioned previously. +The following is a list of the possible redirections. +The +.Op Ar n +is an optional number, as in +.Sq Li 3 +(not +.Li [3] ) , +that refers to a file descriptor. +If present it must occur immediately before the redirection +operator, with no intervening white space, and becomes a +part of that operator. +.Bl -tag -width aaabsfiles -offset indent +.It Oo Ar n Oc Ns Ic > Ar file +Redirect standard output (or +.Ar n ) +to +.Ar file . +.It Oo Ar n Oc Ns Ic >| Ar file +The same, but override the +.Fl C +option. +.It Oo Ar n Oc Ns Ic >> Ar file +Append standard output (or +.Ar n ) +to +.Ar file . +.It Oo Ar n Oc Ns Ic < Ar file +Redirect standard input (or +.Ar n ) +from +.Ar file . +.It Oo Ar n1 Oc Ns Ic <& Ns Ar n2 +Duplicate standard input (or +.Ar n1 ) +from file descriptor +.Ar n2 . +.Ar n2 +is expanded if not a digit string, the result must be a number. +.It Oo Ar n Oc Ns Ic <&- +Close standard input (or +.Ar n ) . +.It Oo Ar n1 Oc Ns Ic >& Ns Ar n2 +Duplicate standard output (or +.Ar n1 ) +to +.Ar n2 . +.It Oo Ar n Oc Ns Ic >&- +Close standard output (or +.Ar n ) . +.It Oo Ar n Oc Ns Ic <> Ar file +Open +.Ar file +for reading and writing on standard input (or +.Ar n ) . +.El +.Pp +The following redirection is often called a +.Dq here-document . +.Bd -unfilled -offset indent +.Oo Ar n Oc Ns Ic << Ar delimiter +.Li \&... here-doc-text ... +.Ar delimiter +.Ed +.Pp +The +.Dq here-doc-text +starts immediately after the next unquoted newline character following +the here-document redirection operator. +If there is more than one here-document redirection on the same +line, then the text for the first (from left to right) is read +first, and subsequent here-doc-text for later here-document redirections +follows immediately after, until all such redirections have been +processed. +.Pp +All the text on successive lines up to the delimiter, +which must appear on a line by itself, with nothing other +than an immediately following newline, is +saved away and made available to the command on standard input, or file +descriptor n if it is specified. +If the delimiter as specified on the initial line is +quoted, then the here-doc-text is treated literally; otherwise, the text is +treated much like a double quoted string, except that +.Sq Li \(dq +characters have no special meaning, and are not escaped by +.Sq Li \&\e , +and is subjected to parameter expansion, command substitution, and arithmetic +expansion as described in the +.Sx Word Expansions +section below. +If the operator is +.Ic <<- +instead of +.Ic << , +then leading tabs in all lines in the here-doc-text, including before the +end delimiter, are stripped. +If the delimiter is not quoted, lines in here-doc-text that end with +an unquoted +.Li \e +are joined to the following line, the +.Li \e +and following +newline are simply removed while reading the here-document, +which thus guarantees +that neither of those lines can be the end delimiter. +.Pp +It is a syntax error for the end of the input file (or string) to be +reached before the delimiter is encountered. +.\" +.\" +.Ss Search and Execution +.\" +There are three types of commands: shell functions, built-in commands, and +normal programs \(em and the command is searched for (by name) in that order. +A command that contains a slash +.Sq \&/ +in its name is always a normal program. +They each are executed in a different way. +.Pp +When a shell function is executed, all of the shell positional parameters +(note: excluding +.Li 0 , \" $0 +which is a special, not positional, parameter, and 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, +and exported for the benefit of programs executed with the function. +Then the command given in the function definition is executed. +The positional parameters, and local variables, are restored to +their original values when the command completes. +This all occurs within the current shell, and the function +can alter variables, or other settings, of the shell, but +not the positional parameters nor their related special parameters. +.Pp +Shell built-ins are executed internally to the shell, without spawning a +new process. +.Pp +Otherwise, if the command name doesn't match a function or built-in, 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, and if it does +not begin with the +.Dq magic number +whose ASCII representation is +.Dq Li "#!" , +so +.Xr execve 2 +returns +.Er ENOEXEC +then) the shell will interpret the program in a sub-shell. +The child shell will reinitialize itself in this case, +so that the effect will be as if a +new shell had been invoked to handle the ad-hoc shell script, except that +the location of hashed commands located in the parent shell will be +remembered by the child. +.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 +Otherwise, the shell searches each entry in +.Ev PATH +in turn for the command. +The value of the +.Ev 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. +If a directory searched contains an executable file with the same +name as the command given, +the search terminates, and that program is executed. +.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 in normal cases, or to indicate 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 consists entirely of variable assignments then the +exit status of the command is that of the last command substitution +if any, otherwise 0. +.Pp +If redirections are present, and any fail to be correctly performed, +any command present is not executed, and an exit status of 2 +is returned. +.Ss Complex Commands +Complex commands are combinations of simple commands with control +operators or reserved words, together creating a larger complex command. +Overall, a shell program is a: +.Bl -tag -width XpipelineX +.It list +Which is a sequence of one or more AND-OR lists. +.It "AND-OR list" +is a sequence of one or more pipelines. +.It pipeline +is a sequence of one or more commands. +.It command +is one of a simple command, a compound command, or a function definition. +.It "simple command" +has been explained above, and is the basic building block. +.It "compound command" +provides mechanisms to group lists to achieve different effects. +.It "function definition" +allows new simple commands to be created as groupings of existing commands. +.El +.Pp +Unless otherwise stated, the exit status of a list +is that of the last simple command executed by the list. +.\" +.\" +.Ss Pipelines +.\" +A pipeline is a sequence of one or more commands separated +by the control operator +.Sq Ic \(ba , +and optionally preceded by the +.Dq Ic \&! +reserved word. +Note that +.Sq Ic \(ba +is an operator, and so is recognized anywhere it appears unquoted, +it does not require surrounding white space or other syntax elements. +On the other hand +.Dq Ic \&! +being a reserved word, must be separated from adjacent words by +white space (or other operators, perhaps redirects) and is only +recognized as the reserved word when it appears in a command word +position (such as at the beginning of a pipeline.) +.Pp +The standard output of all but +the last command in the sequence is connected to the standard input +of the next command. +The standard output of the last +command is inherited from the shell, as usual, +as is the standard input of the first command. +.Pp +The format for a pipeline is: +.Pp +.Dl [!] command1 Op Li \&| command2 No ... +.Pp +The standard output of command1 is connected to the standard input of +command2. +The standard input, standard output, or both of each command is +considered to be assigned by the pipeline before any redirection specified +by redirection operators that are part of the command are performed. +.Pp +If the pipeline is not in the background (discussed later), the shell +waits for all commands to complete. +.Pp +The commands in a pipeline can either be simple commands, +or one of the compound commands described below. +The simplest case of a pipeline is a single simple command. +.Pp +If the +.Ic pipefail +option was set when a pipeline was started, +the pipeline status is the status of +the last (lexically last, i.e.: rightmost) command in the +pipeline to exit with non-zero exit status, or zero, if, +and only if, all commands in the pipeline exited with a status of zero. +If the +.Ic pipefail +option was not set, which is the default state, +the pipeline status is the exit +status of the last (rightmost) command in the pipeline, +and the exit status of any other commands in the pipeline is ignored. +.Pp +If the reserved word +.Dq Ic \&! +precedes the pipeline, the exit status +becomes the logical NOT of the pipeline status as determined above. +That is, if the pipeline status is zero, the exit status is 1; +if the pipeline status is other than zero, the exit status is zero. +If there is no +.Dq Ic \&! +reserved word, the pipeline status becomes the exit status. +.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 command1 +to the standard input of command2. +.Pp +Note that unlike some other shells, each process in the pipeline is a +child of the invoking shell (unless it is a shell built-in, in which case +it executes in the current shell \(em but any effect it has on the +environment is wiped). +.Pp +A pipeline is a simple case of an AND-OR-list (described below.) +A +.Li \&; +or +.Aq newline +terminator causes the preceding pipeline, or more generally, +the preceding AND-OR-list to be executed sequentially; +that is, the shell executes the commands, and waits for them +to finish before proceeding to following commands. +An +.Li \&& +terminator causes asynchronous (background) execution +of the preceding AND-OR-list (see the next paragraph below). +The exit status of an asynchronous AND-OR-list is zero. +The actual status of the commands, +after they have completed, +can be obtained using the +.Ic wait +built-in command described later. +.\" +.\" +.Ss Background Commands \(em Ic \&& +.\" +If a command, pipeline, or AND-OR-list +is terminated by the control operator ampersand +.Pq Li \&& , +the +shell executes the command asynchronously \(em that is, 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 +.Dl command1 & Op Li command2 & No ... +.Pp +If the shell is not interactive, the standard input of an asynchronous +command is set to +.Pa /dev/null . +The process identifier of the most recent command started in the +background can be obtained from the value of the special parameter +.Dq Dv \&! \" $! +(see +.Sx Special Parameters ) +provided it is accessed before the next asynchronous command is started. +.\" +.\" +.Ss Lists \(em Generally Speaking +.\" +A list is a sequence of one or more commands separated by newlines, +semicolons, or ampersands, and optionally terminated by one of these three +characters. +A shell program, which includes the commands given to an +interactive shell, is a list. +Each command in such a list is executed when it is fully parsed. +Another use of a list is as a complete-command, +which is parsed in its entirety, and then later the commands in +the list are executed only if there were no parsing errors. +.Pp +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 to the next command; otherwise it waits +for the command to terminate before proceeding to the next one. +A newline is equivalent to a +.Sq Li \&; +when no other operator is present, and the command being input +could syntactically correctly be terminated at the point where +the newline is encountered, otherwise it is just whitespace. +.\" +.\" +.Ss AND-OR Lists (Short-Circuit List Operators) +.\" +.Dq Li \&&& +and +.Dq Li \&|| +are AND-OR list operators. +After executing the commands that precede the +.Dq Li \&&& +the subsequent command is executed +if and only if the exit status of the preceding command(s) is zero. +.Dq Li \&|| +is similar, but executes the subsequent command if and only if the exit status +of the preceding command is nonzero. +If a command is not executed, the exit status remains unchanged +and the following AND-OR list operator (if any) uses that status. +.Dq Li \&&& +and +.Dq Li \&|| +both have the same priority. +Note that these operators are left-associative, so +.Dl true || echo bar && echo baz +writes +.Dq baz +and nothing else. +This is not the way it works in C. +.\" +.\" +.Ss Flow-Control Constructs \(em Ic if , while , until , for , case +.\" +These commands are instances of compound commands. +The syntax of the +.Ic if +command is +.Bd -literal -offset indent +.Ic if Ar list +.Ic then Ar list +.Ic [ elif Ar list +.Ic then Ar list ] No ... +.Ic [ else Ar list ] +.Ic fi +.Ed +.Pp +The first list is executed, and if the exit status of that list is zero, +the list following the +.Ic then +is executed. +Otherwise the list after an +.Ic elif +(if any) is executed and the process repeats. +When no more +.Ic elif +reserved words, and accompanying lists, appear, +the list after the +.Ic else +reserved word, if any, is executed. +.Pp +The syntax of the +.Ic while +command is +.Bd -literal -offset indent +.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 syntax of the +.Ic for +command is +.Bd -literal -offset indent +.Ic for Ar variable Op Ic in Ar word No ... +.Ic do Ar list +.Ic done +.Ed +.Pp +The words are expanded, or +.Li \*q$@\*q +if +.Ic in +(and the following words) is not present, +and then the list is executed repeatedly with the +variable set to each word in turn. +If +.Ic in +appears after the variable, but no words are +present, the list is not executed, and the exit status is zero. +.Ic do +and +.Ic done +may be replaced with +.Sq Ic \&{ +and +.Sq Ic \&} , +but doing so is non-standard and not recommended. +.Pp +The syntax of the +.Ic break +and +.Ic continue +commands is +.Bd -literal -offset indent +.Ic break Op Ar num +.Ic continue Op Ar num +.Ed +.Pp +.Ic break +terminates the +.Ar num +innermost +.Ic for , while , +or +.Ic until +loops. +.Ic continue +breaks execution of the +.Ar num\^ Ns -1 +innermost +.Ic for , while , +or +.Ic until +loops, and then continues with the next iteration of the enclosing loop. +These are implemented as special built-in commands. +The parameter +.Ar num , +if given, must be an unsigned positive integer (greater than zero). +If not given, 1 is used. +.Pp +The syntax of the +.Ic case +command is +.Bd -literal -offset indent +.Ic case Ar word Ic in +.Oo Ic \&( Oc Ar pattern Ns Ic \&) Oo Ar list Oc Ic \&;& +.Oo Ic \&( Oc Ar pattern Ns Ic \&) Oo Ar list Oc Ic \&;; +.No \&... +.Ic esac +.Ed +.Pp +The pattern can actually be one or more patterns (see +.Sx Shell Patterns +described later), separated by +.Dq \(or +characters. +.Pp +.Ar word +is expanded and matched against each +.Ar pattern +in turn, +from first to last, +with each pattern being expanded just before the match is attempted. +When a match is found, pattern comparisons cease, and the associated +.Ar list , +if given, +is evaluated. +If the list is terminated with +.Dq Ic \&;& +execution then falls through to the following list, if any, +without evaluating its pattern, or attempting a match. +When a list terminated with +.Dq Ic \&;; +has been executed, or when +.Ic esac +is reached, execution of the +.Ic case +statement is complete. +The exit status is that of the last command executed +from the last list evaluated, if any, or zero otherwise. +.\" +.\" +.Ss Grouping Commands Together +.\" +Commands may be grouped by writing either +.Dl Ic \&( Ns Ar list Ns Ic \&) +or +.Dl Ic \&{ Ar list Ns Ic \&; \&} +These also form compound commands. +.Pp +Note that while parentheses are operators, and do not require +any extra syntax, braces are reserved words, so the opening brace +must be followed by white space (or some other operator), and the +closing brace must occur in a position where a new command word might +otherwise appear. +.Pp +The first of these executes the commands in a sub-shell. +Built-in commands grouped into a +.Li \&( Ns Ar list Ns \&) +will not affect the current shell. +The second form does not fork another shell so is slightly more efficient, +and allows for commands which do affect the current shell. +Grouping commands together this way allows you to redirect +their output as though they were one program: +.Bd -literal -offset indent +{ echo -n \*qhello \*q ; echo \*qworld\*q ; } > greeting +.Ed +.Pp +Note that +.Dq Ic } +must follow a control operator (here, +.Dq Ic \&; ) +so that it is recognized as a reserved word and not as another command argument. +.\" +.\" +.Ss Functions +.\" +The syntax of a function definition is +.Pp +.Dl Ar name Ns Ic \&() Ar command Op Ar redirect No ... +.Pp +A function definition is an executable statement; when executed it +installs a function named name and returns an exit status of zero. +The command is normally a list enclosed between +.Dq { +and +.Dq } . +The standard syntax also allows the command to be any of the other +compound commands, including a sub-shell, all of which are supported. +As an extension, this shell also allows a simple command +(or even another function definition) to be +used, though users should be aware this is non-standard syntax. +This means that +.Dl l() ls \*q$@\*q +works to make +.Dq l +an alternative name for the +.Ic ls +command. +.Pp +If the optional redirect, (see +.Sx Redirections ) , +which may be of any of the normal forms, +is given, it is applied each time the +function is called. +This means that a simple +.Dq Hello World +function might be written (in the extended syntax) as: +.Bd -literal -offset indent +hello() cat <<EOF +Hello World! +EOF +.Ed +.Pp +To be correctly standards conforming this should be re-written as: +.Bd -literal -offset indent +hello() { cat; } <<EOF +Hello World! +EOF +.Ed +.Pp +Note the distinction between those forms, and +.Bd -literal -offset indent +hello() { cat <<EOF +Hello World! +EOF +\&} +.Ed +.Pp +which reads and processes the here-document +each time the shell executes the function, and which applies +that input only to the cat command, not to any other commands +that might appear in the function. +.Pp +Variables may be declared to be local to a function by using the +.Ic local +command. +This should usually appear as the first statement of a function, +though +.Ic local +is an executable command which can be used anywhere in a function. +See +.Sx Built-ins +below for its definition. +.Pp +The function completes after having executed +.Ar command +with exit status set to the status returned by +.Ar command . +If +.Ar command +is a compound-command +it can use the +.Ic return +command (see +.Sx Built-ins +below) +to finish before completing all of +.Ar command . +.Ss Variables and Parameters +The shell maintains a set of parameters. +A parameter denoted by a name is called a variable. +When starting up, the shell turns all the environment +variables into shell variables, and exports them. +New variables can be set using the form +.Pp +.Dl Ar name Ns Li = Ns Ar value +.Pp +Variables set by the user must have a name consisting solely of +alphabetics, numerics, and underscores \(em the first of which must not be +numeric. +A parameter can also be denoted by a number or a special +character as explained below. +.Ss Positional Parameters +A positional parameter is a parameter denoted by a number (n > 0). +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 can also be used to set or reset them, and +.Ic shift +can be used to manipulate the list. +.Pp +To refer to the 10th (and later) positional parameters, +the form +.Li \&${ Ns Ar n Ns Li \&} +must be used. +Without the braces, a digit following +.Dq $ +can only refer to one of the first 9 positional parameters, +or the special parameter +.Dv 0 . \" $0 +The word +.Dq Li $10 +is treated identically to +.Dq Li ${1}0 . +.\" +.\" +.Ss Special Parameters +.\" +A special parameter is a parameter denoted by one of the following special +characters. +The value of the parameter is listed next to its character. +.Bl -tag -width thinhyphena +.It Dv * +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 +.Ev IFS +variable, or by a +.Aq space +if +.Ev IFS +is unset. +.It Dv @ \" $@ +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 @ generates zero arguments, even when +.Dv $@ +is double-quoted. +What this basically means, for example, is +if +.Li $1 +is +.Dq abc +and +.Li $2 +is +.Dq def\ ghi , +then +.Li \*q$@\*q +expands to +the two arguments: +.Pp +.Sm off +.Dl \*q abc \*q \ \*q def\ ghi \*q +.Sm on +.It Dv # +Expands to the number of positional parameters. +.It Dv \&? +Expands to the exit status of the most recent pipeline. +.It Dv \- No (hyphen, or minus) +Expands to the current option flags (the single-letter +option names concatenated into a string) as specified on +invocation, by the set built-in command, or implicitly +by the shell. +.It Dv $ +Expands to the process ID of the invoked shell. +A sub-shell retains the same value of +.Dv $ +as its parent. +.It Dv \&! +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 no background commands have yet been started by the shell, then +.Dq Dv \&! +will be unset. +Once set, the value of +.Dq Dv \&! +will be retained until another background command is started. +.It Dv 0 No (zero) \" $0 +Expands to the name of the shell or shell script. +.El +.\" +.\" +.Ss Word Expansions +.\" +This section 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 +.Dv @ \" $@ +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 +.Ev IFS +variable is null. +.It +Pathname Expansion (unless set +.Fl f +is in effect). +.It +Quote Removal. +.El +.Pp +The $ character is used to introduce parameter expansion, command +substitution, or arithmetic evaluation. +.Ss Tilde Expansion (substituting a user's home directory) +A word beginning with an unquoted tilde character (~) is +subjected to tilde expansion. +Provided all of the subsequent characters in the word are unquoted +up to an unquoted slash (/) +or when in an assignment or not in posix mode, an unquoted colon (:), +or if neither of those appear, the end of the word, +they are treated as a user name +and are replaced with the pathname of the named user's home directory. +If the user name is missing (as in +.Pa ~/foobar ) , +the tilde is replaced with the value of the +.Dv HOME +variable (the current user's home directory). +.Pp +In variable assignments, +an unquoted tilde immediately after the assignment operator (=), and +each unquoted tilde immediately after an unquoted colon in the value +to be assigned is also subject to tilde expansion as just stated. +.\" +.\" +.Ss Parameter Expansion +.\" +The format for parameter expansion is as follows: +.Pp +.Dl ${ Ns Ar expression Ns Li } +.Pp +where +.Ar expression +consists of all characters until the matching +.Sq Li } . +Any +.Sq Li } +escaped by a backslash or within a quoted string, and characters in +embedded arithmetic expansions, command substitutions, and variable +expansions, are not examined in determining the matching +.Sq Li } . +.Pp +The simplest form for parameter expansion is: +.Pp +.Dl ${ 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 in this simple case, +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 +pathname expansion is not performed on the results of the expansion; +.It +field splitting is not performed on the results of the +expansion, with the exception of the special rules for +.Dv @ . \" $@ +.El +.Pp +In addition, a parameter expansion where braces are used, +can be modified by using one of the following formats. +If the +.Sq Ic \&: +is omitted in the following modifiers, then the test in the expansion +applies only to unset parameters, not null ones. +.Bl -tag -width aaparameterwordaaaaa +.It Li ${ Ns Ar parameter Ns Ic :- Ns Ar word Ns Li } +.Sy 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 Ic := Ns Ar word Ns Li } +.Sy 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. +Only variables, not positional parameters or special +parameters, can be assigned in this way. +.It Li ${ Ns Ar parameter Ns Ic :? Ns Oo Ar word\^ Oc Ns Li } +.Sy 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 a non-interactive shell exits with +a nonzero exit status. +An interactive shell will not exit, but any associated command(s) will +not be executed. +If the +.Ar parameter +is set, its value is substituted. +.It Li ${ Ns Ar parameter Ns Ic :+ Ns Ar word Ns Li } +.Sy Use Alternative Value. +If +.Ar parameter +is unset or null, null is substituted; +otherwise, the expansion of +.Ar word +is substituted. +The value of +.Ar parameter +.Em is not used +in this expansion. +.It Li ${ Ns Ic # Ns Ar parameter Ns Li } +.Sy 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 +.Dv * +or +.Dv @ , \" $@ +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 aaparameterwordaaaaa +.It Li ${ Ns Ar parameter Ns Ic % Ns Ar word Ns Li } +.Sy 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. +If the +.Ar word +is to start with a +.Sq Li \&% +character, it must be quoted. +.It Li ${ Ns Ar parameter Ns Ic %% Ns Ar word Ns Li } +.Sy 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. +The +.Dq Ic %% +pattern operator only produces different results from the +.Dq Ic \&% +operator when the pattern contains at least one unquoted +.Sq Li \&* . +.It Li ${ Ns Ar parameter Ns Ic \&# Ns Ar word Ns Li } +.Sy 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. +If the +.Ar word +is to start with a +.Sq Li \&# +character, it must be quoted. +.It Li ${ Ns Ar parameter Ns Ic \&## Ns Ar word Ns Li } +.Sy 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. +This has the same relationship with the +.Dq Ic \&# +pattern operator as +.Dq Ic %% +has with +.Dq Ic \&% . +.El +.\" +.\" +.Ss Command Substitution +.\" +Command substitution allows the output of a command to be substituted in +place of the command (and surrounding syntax). +Command substitution occurs when a word contains +a command list enclosed as follows: +.Pp +.Dl Ic $( Ns Ar list Ns Ic \&) +.Pp +or the older +.Pq Dq backquoted +version, which is best avoided: +.Pp +.Dl Ic \&` Ns Ar list Ns Ns Ic \&` +.Pp +See the section +.Sx Complex Commands +above for the definition of +.Ic list . +.Pp +The shell expands the command substitution by executing the +.Ar list +in a sub-shell environment and replacing the command substitution with the +standard output of the +.Ar list +after removing any sequence of one or more +.Ao newline Ac Ns s +from the end of the substitution. +(Embedded +.Ao newline Ac Ns s +before +the end of the output are not removed; however, during field splitting, +they may be used to separate fields +.Pq "as spaces usually are" +depending on the value of +.Ev IFS +and any quoting that is in effect.) +.Pp +Note that if a command substitution includes commands +to be run in the background, +the sub-shell running those commands +will only wait for them to complete if an appropriate +.Ic wait +command is included in the command list. +However, the shell in which the result of the command substitution +will be used will wait for both the sub-shell to exit and for the +file descriptor that was initially standard output for the command +substitution sub-shell to be closed. +In some circumstances this might not happen until all processes +started by the command substitution have finished. +.\" While the exit of the sub-shell closes its standard output, +.\" any background process left running may still +.\" have that file descriptor open. +.\" This includes yet another sub-shell which might have been +.\" (almost invisibly) created to wait for some other command to complete, +.\" and even where the background command has had its +.\" standard output redirected or closed, +.\" the waiting sub-shell may still have it open. +.\" Thus there is no guarantee that the result of a command substitution +.\" will be available in the shell which is to use it before all of +.\" the commands started by the command substitution have completed, +.\" though careful coding can often avoid delays beyond the termination +.\" of the command substitution sub-shell. +.\" .Pp +.\" For example, assuming a script were to contain the following +.\" code (which could be done better other ways, attempting +.\" this kind of trickery is not recommended): +.\" .Bd -literal -offset indent +.\" if [ "$( printf "Ready? " >&2; read ans; printf "${ans}"; +.\" { sleep 120 >/dev/null && kill $$ >/dev/null 2>&1; }& )" = go ] +.\" then +.\" printf Working... +.\" # more code +.\" fi +.\" .Ed +.\" .Pp +.\" the +.\" .Dq Working... +.\" output will not be printed, and code that follows will never be executed. +.\" Nor will anything later in the script +.\" .Po +.\" unless +.\" .Dv SIGTERM +.\" is trapped or ignored +.\" .Pc . +.\" .Pp +.\" The intent is to prompt the user, wait for the user to +.\" answer, then if the answer was +.\" .Dq go +.\" do the appropriate work, but set a 2 minute time limit +.\" on completing that work. +.\" If the work is not done by then, kill the shell doing it. +.\" .Pp +.\" It will usually not work as written, as while the 2 minute +.\" .Sq sleep +.\" has its standard output redirected, as does the +.\" .Sq kill +.\" that follows (which also redirects standard error, +.\" so the user would not see an error if the work were +.\" completed and there was no parent process left to kill); +.\" the sub-shell waiting for the +.\" .Sq sleep +.\" to finish successfully, so it can start the +.\" .Sq kill , +.\" does not. +.\" It waits, with standard output still open, +.\" for the 2 minutes until the sleep is done, +.\" even though the kill is not going to need that file descriptor, +.\" and there is nothing else which could. +.\" The command substitution does not complete until after +.\" the kill has executed and the background sub-shell +.\" finishes \(en at which time the shell running it is +.\" presumably dead. +.\" .Pp +.\" Rewriting the background sub-shell part of that as +.\" .Dl "{ sleep 120 && kill $$ 2>&1; } >/dev/null &" +.\" would allow this +.\" .Nm +.\" to perform as expected, but that is not guaranteed, +.\" not all shells would behave as planned. +.\" It is advised to avoid starting background processes +.\" from within a command substitution. +.\" +.\" +.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 +.Dl Ic $(( Ns Ar expression Ns Ic \&)) +.Pp +The expression in an arithmetic expansion is treated as if it were in +double quotes, except that a double quote character inside the expression +is just a normal character (it quotes nothing.) +The shell expands all tokens in the expression for parameter expansion, +command substitution, and quote removal (the only quoting character is +the backslash +.Sq \&\e , +and only when followed by another +.Sq \&\e , +a dollar sign +.Sq \&$ , +a backquote +.Sq \&` +or a newline.) +.Pp +Next, the shell evaluates the expanded result as an arithmetic expression +and substitutes the calculated value of that expression. +.Pp +Arithmetic expressions use a syntax similar to that +of the C language, and are evaluated using the +.Ql intmax_t +data type (this is an extension to POSIX, which requires only +.Ql long +arithmetic.) +Shell variables may be referenced by name inside an arithmetic +expression, without needing a +.Dq \&$ +sign. +Variables that are not set, or which have an empty (null string) value, +used this way evaluate as zero (that is, +.Dq x +in arithmetic, as an R-Value, is evaluated as +.Dq ${x:-0} ) +unless the +.Nm +.Fl u +flag is set, in which case a reference to an unset variable is an error. +Note that unset variables used in the ${var} form expand to a null +string, which might result in syntax errors. +Referencing the value of a variable which is not numeric is an error. +.Pp +All of the C expression operators applicable to integers are supported, +and operate as they would in a C expression. +Use white space, or parentheses, to disambiguate confusing syntax, +otherwise, as in C, the longest sequence of consecutive characters +which make a valid token (operator, variable name, or number) is taken +to be that token, even if the token designated cannot be used +and a different interpretation could produce a successful parse. +This means, as an example, that +.Dq a+++++b +is parsed as the gibberish sequence +.Dq "a ++ ++ + b" , +rather than as the valid alternative +.Dq "a ++ + ++ b" . +Similarly, separate the +.Sq \&, +operator from numbers with white space to avoid the possibility +of confusion with the decimal indicator in some locales (though +fractional, or floating-point, numbers are not supported in this +implementation.) +.Pp +It should not be necessary to state that the C operators which +operate on, or produce, pointer types, are not supported. +Those include unary +.Dq \&* +and +.Dq \&& +and the struct and array referencing binary operators: +.Dq \&. , +.Dq \&-> +and +.Dq \&[ . +.\" +.\" +.Ss White Space Splitting (Field Splitting) +.\" +After parameter expansion, command substitution, and +arithmetic expansion the shell scans the results of +expansions and substitutions that did not occur in double quotes, +and +.Dq Li $@ +even if it did, +for field splitting and multiple fields can result. +.Pp +The shell treats each character of the +.Ev IFS +as a delimiter and uses the delimiters to split the results of parameter +expansion and command substitution into fields. +.Pp +Non-whitespace characters in +.Ev IFS +are treated strictly as parameter separators. +So adjacent non-whitespace +.Ev IFS +characters will produce empty parameters. +On the other hand, any sequence of whitespace +characters that occur in +.Ev IFS +(known as +.Ev IFS +whitespace) +can occur, leading and trailing +.Ev IFS +whitespace, and any +.Ev IFS +whitespace surrounding a non whitespace +.Ev IFS +delimiter, is removed. +Any sequence of +.Ev IFS +whitespace characters without a non-whitespace +.Ev IFS +delimiter acts as a single field separator. +.Pp +If +.Ev IFS +is unset it is assumed to contain space, tab, and newline, +all of which are +.Ev IFS +whitespace characters. +If +.Ev IFS +is set to a null string, there are no delimiters, +and no field splitting occurs. +.Ss Pathname Expansion (File Name Generation) +Unless the +.Fl f +flag 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 both Pathname Expansion and the +.Ic case +command. +.Ss Shell Patterns +A pattern consists of normal characters, which match themselves, +and meta-characters. +The meta-characters are +.Dq \&! , +.Dq * , +.Dq \&? , +and +.Dq \&[ . +These characters lose their special meanings if they are quoted. +When command or variable substitution is performed +and the dollar sign or backquotes 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 Dq * +matches any string of characters. +A question mark +.Pq Dq \&? +matches any single character. +A left bracket +.Pq Dq \&[ +introduces a character class. +The end of the character class is indicated by a right bracket +.Pq Dq \&] ; +if this +.Dq \&] +is missing then the +.Dq \&[ +matches a +.Dq \&[ +rather than introducing a character class. +A character class matches any of the characters between the square brackets. +A named class of characters (see +.Xr wctype 3 ) +may be specified by surrounding the name with +.Pq Dq [: +and +.Pq Dq :] . +For example, +.Pq Dq [[:alpha:]] +is a shell pattern that matches a single letter. +A range of characters may be specified using a minus sign +.Pq Dq \(mi . +The character class may be complemented +by making an exclamation mark +.Pq Dq \&! +the first character of the character class. +.Pp +To include a +.Dq \&] +in a character class, make it the first character listed (after the +.Dq \&! , +if any). +To include a +.Dq \(mi , +make it the first (after !) or last character listed. +If both +.Dq \&] +and +.Dq \(mi +are to be included, the +.Dq \&] +must be first (after !) +and the +.Dq \(mi +last, in the character class. +.\" +.\" +.Ss Built-ins +.\" +This section lists the built-in commands which are built-in because they +need to perform some operation that can't be performed by a separate +process. +Or just because they traditionally are. +In addition to these, there are several other commands that may +be built in for efficiency (e.g. +.Xr printf 1 , +.Xr echo 1 , +.Xr test 1 , +etc). +.Bl -tag -width 5n +.It Ic \&: Op Ar arg ... +A null command that returns a 0 (true) exit value. +Any arguments or redirects are evaluated, then ignored. +.\" +.It Ic \&. Ar file +The dot command reads and executes the commands from the specified +.Ar file +in the current shell environment. +The file does not need to be executable and is looked up from the directories +listed in the +.Ev PATH +variable if its name does not contain a directory separator +.Pq Sq / . +The +.Ic return +command (see below) +can be used for a premature return from the sourced file. +.Pp +The POSIX standard has been unclear on how loop control keywords (break +and continue) behave across a dot command boundary. +This implementation allows them to control loops surrounding the dot command, +but obviously such behavior should not be relied on. +It is now permitted by the standard, but not required. +.\" +.It Ic alias Op Ar name Ns Op Li = Ns Ar string ... +If +.Ar name Ns Li = 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 prints the +names and values of all defined aliases (see +.Ic unalias ) . +.\" +.It Ic bg Op Ar job ... +Continue the specified jobs (or the current job if no +jobs are given) in the background. +.\" +.It Ic command Oo Fl pVv Oc Ar command Op Ar arg ... +Execute the specified command but ignore shell functions when searching +for it. +(This is useful when you +have a shell function with the same name as a command.) +.Bl -tag -width 5n +.It Fl p +search for command using a +.Ev PATH +that guarantees to find all the standard utilities. +.It Fl V +Do not execute the command but +search for the command and print the resolution of the +command search. +This is the same as the +.Ic type +built-in. +.It Fl v +Do not execute the command but +search for the command and print the absolute pathname +of utilities, the name for built-ins or the expansion of aliases. +.El +.\" +.It Ic cd Oo Fl P Oc Op Ar directory Op Ar replace +Switch to the specified directory (default +.Ev $HOME ) . +If +.Ar replace +is specified, then the new directory name is generated by replacing +the first occurrence of the string +.Ar directory +in the current directory name with +.Ar replace . +Otherwise if +.Ar directory +is +.Sq Li - , +then the current working directory is changed to the previous current +working directory as set in +.Ev OLDPWD . +Otherwise if an entry for +.Ev CDPATH +appears in the environment of the +.Ic cd +command or the shell variable +.Ev CDPATH +is set and the directory name does not begin with a slash, +and its first (or only) component isn't dot or dot dot, +then the directories listed in +.Ev CDPATH +will be searched for the specified directory. +The format of +.Ev CDPATH +is the same as that of +.Ev PATH . +.Pp +The +.Fl P +option instructs the shell to update +.Ev PWD +with the specified physical directory path and change to that directory. +This is the default. +.Pp +When the directory changes, the variable +.Ev OLDPWD +is set to the working directory before the change. +.Pp +Some shells also support a +.Fl L +option, which instructs the shell to update +.Ev PWD +with the logical path and to change the current directory +accordingly. +This is not supported. +.Pp +In an interactive shell, the +.Ic cd +command will print out the name of the +directory that it actually switched to if this is different from the name +that the user gave, +or always if the +.Cm cdprint +option is set. +The destination may be different either because the +.Ev CDPATH +mechanism was used +.\" or because a symbolic link was crossed. +or if the +.Ar replace +argument was used. +.\" +.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 Ar 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 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. +When the +.Cm posix +option is not set, +file descriptors created via such redirections are marked close-on-exec +(see +.Xr open 2 +.Dv O_CLOEXEC +or +.Xr fcntl 2 +.Dv F_SETFD / +.Dv FD_CLOEXEC ) , +unless the descriptors refer to the standard input, +output, or error (file descriptors 0, 1, 2). +Traditionally Bourne-like shells +(except +.Xr ksh 1 ) , +made those file descriptors available to exec'ed processes. +To be assured the close-on-exec setting is off, +redirect the descriptor to (or from) itself, +either when invoking a command for which the descriptor is wanted open, +or by using +.Ic exec +(perhaps the same +.Ic exec +as opened it, after the open) +to leave the descriptor open in the shell +and pass it to all commands invoked subsequently. +Alternatively, see the +.Ic fdflags +command below, which can set, or clear, this, and other, +file descriptor flags. +.\" +.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 the +exit status of the preceding command (the current value of $?) is used. +.\" +.It Ic export Oo Fl nx Oc Ar name Ns Oo =value Oc ... +.It Ic export Oo Fl x Oc Oo Fl p Oo Ar name ... Oc Oc +.It Ic export Fl q Oo Fl x Oc Ar name ... +With no options, +but one or more names, +the specified names are exported so that they will appear in the +environment of subsequent commands. +With +.Fl n +the specified names are un-exported. +Variables can also be un-exported using the +.Ic unset +built in command. +With +.Fl x +(exclude) the specified names are marked not to be exported, +and any that had been exported, will be un-exported. +Later attempts to export the variable will be refused. +Note this does not prevent explicitly exporting a variable +to a single command, script or function by preceding that +command invocation by a variable assignment to that variable, +provided the variable is not also read-only. +That is +.Bd -literal -offset indent +export -x FOO # FOO will now not be exported by default +export FOO # this command will fail (non-fatally) +FOO=some_value my_command +.Ed +.Pp +still passes the value +.Pq Li FOO=some_value +to +.Li my_command +through the environment. +.Pp +The shell allows the value of a variable to be set at the +same time it is exported (or unexported, etc) by writing +.Pp +.Dl export [-nx] name=value +.Pp +With no arguments the export command lists the names of all +set exported variables, +or if +.Fl x +was given, all set variables marked not for export. +With the +.Fl p +option specified, the output will be formatted suitably for +non-interactive use, and unset variables are included. +When +.Fl p +is given, variable names, but not values, may also be +given, in which case output is limited to the variables named. +.Pp +With +.Fl q +and a list of variable names, the +.Ic export +command will exit with status 0 if all the named +variables have been marked for export, or 1 if +any are not so marked. +If +.Fl x +is also given, +the test is instead for variables marked not to be exported. +.Pp +Other than with +.Fl q , +the +.Ic export +built-in exits with status 0, +unless an attempt is made to export a variable which has +been marked as unavailable for export, +in which cases it exits with status 1. +In all cases if +an invalid option, or option combination, is given, +or an invalid variable name is present, +.Ic export +will write a message to the standard error output, +and exit with a non-zero status. +A non-interactive shell will terminate. +.Pp +Note that there is no restriction upon exporting, +or un-exporting, read-only variables. +The no-export flag can be reset by unsetting the variable +and creating it again \(en provided the variable is not also read-only. +.\" +.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=new Oc Op Ar first +The +.Ic fc +built-in lists, or edits and re-executes, commands previously entered +to an interactive shell. +.Bl -tag -width 5n +.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 +.Ev PATH +variable. +The value in the +.Ev FCEDIT +variable is used as a default when +.Fl e +is not specified. +If +.Ev FCEDIT +is null or unset, the value of the +.Ev EDITOR +variable is used. +If +.Ev 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 first and 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 +.Ev HISTSIZE +variable. +The value of +.Ar first +or +.Ar last +or both are one of the following: +.Bl -tag -width 5n +.It Oo Cm + Oc Ns Ar number +A positive number representing a command number; command numbers can be +displayed with the +.Fl l +option. +.It Cm \- Ns Ar number +A negative decimal number representing the command that was executed +number of commands previously. +For example, \-1 is the immediately previous command. +.El +.It Ar string +A string indicating the most recently entered command that begins with +that string. +If the +.Ar old Ns Li = 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 +.Pp +The following environment variables affect the execution of +.Ic fc : +.Bl -tag -width HISTSIZE +.It Ev FCEDIT +Name of the editor to use. +.It Ev HISTSIZE +The number of previous commands that are accessible. +.El +.\" +.It Ic fg Op Ar job +Move the specified job or the current job to the foreground. +A foreground job can interact with the user via standard input, +and receive signals from the terminal. +.\" +.It Ic fdflags Oo Fl v Oc Op Ar fd ... +.It Ic fdflags Oo Fl v Oc Fl s Ar flags fd Op ... +Get or set file descriptor flags. +The +.Fl v +argument enables verbose printing, printing flags that are also off, and +the flags of the file descriptor being set after setting. +The +.Fl s +flag interprets the +.Ar flags +argument as a comma separated list of file descriptor flags, each preceded +with a +.Dq \(pl +or a +.Dq \(mi +indicating to set or clear the respective flag. +Valid flags are: +.Cm append , +.Cm async , +.Cm sync , +.Cm nonblock , +.Cm fsync , +.Cm dsync , +.Cm rsync , +.Cm direct , +.Cm nosigpipe , +and +.Cm cloexec . +Unique abbreviations of these names, of at least 2 characters, +may be used on input. +See +.Xr fcntl 2 +and +.Xr open 2 +for more information. +.\" +.It Ic getopts Ar optstring var +The POSIX +.Ic getopts +command, not to be confused with the +Bell Labs\[en]derived +.Xr getopt 1 . +.Pp +The first argument should be a series of letters, each of which may be +optionally followed by a colon to indicate that the option requires an +argument. +The variable specified is set to the parsed option. +.Pp +The +.Ic getopts +command deprecates the older +.Xr getopt 1 +utility due to its handling of arguments containing whitespace. +.Pp +The +.Ic getopts +built-in may be used to obtain options and their arguments +from a list of parameters. +When invoked, +.Ic getopts +places the value of the next option from the option string in the list in +the shell variable specified by +.Ar var +and its index in the shell variable +.Ev OPTIND . +When the shell is invoked, +.Ev OPTIND +is initialized to 1. +For each option that requires an argument, the +.Ic getopts +built-in will place it in the shell variable +.Ev OPTARG . +If an option is not allowed for in the +.Ar optstring , +then +.Ev OPTARG +will be unset. +.Pp +.Ar optstring +is a string of recognized option letters (see +.Xr getopt 3 ) . +If a letter is followed by a colon, the option is expected to have an +argument which may or may not be separated from it by whitespace. +If an option character is not found where expected, +.Ic getopts +will set the variable +.Ar var +to a +.Sq Li \&? ; +.Ic getopts +will then unset +.Ev OPTARG +and write output to standard error. +By specifying a colon as the first character of +.Ar optstring +all errors will be ignored. +.Pp +A nonzero value is returned when the last option is reached. +If there are no remaining arguments, +.Ic getopts +will set +.Ar var +to the special option, +.Dq Li \-\- , +otherwise, it will set +.Ar var +to +.Sq Li \&? . +.Pp +The following code fragment shows how one might process the arguments +for a command that can take the options +.Fl a +and +.Fl b , +and the option +.Fl c , +which requires an argument. +.Bd -literal -offset indent +while getopts abc: f +do + case $f in + a | b) flag=$f;; + c) carg=$OPTARG;; + \e?) echo $USAGE; exit 1;; + esac +done +shift $((OPTIND - 1)) +.Ed +.Pp +This code will accept any of the following as equivalent: +.Bd -literal -offset indent +cmd \-acarg file file +cmd \-a \-c arg file file +cmd \-carg -a file file +cmd \-a \-carg \-\- file file +.Ed +.\" +.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. +Entries which have not been looked at since the last +.Ic cd +command are marked with an asterisk; it is possible for these entries +to be invalid. +.Pp +With arguments, the +.Ic hash +command removes the specified commands from the hash table (unless +they are functions) and then locates them. +With the +.Fl v +option, hash prints the locations of the commands as it finds them. +The +.Fl r +option causes the hash command to delete all the entries in the hash table +except for functions. +.\" +.It Ic inputrc Ar file +Read the +.Ar file +to set key bindings as defined by +.Xr editrc 5 . +.\" +.It Ic jobid Oo Fl g Ns \&| Ns Fl j Ns \&| Ns Fl p Oc Op Ar job +With no flags, print the process identifiers of the processes in the job. +If the +.Ar job +argument is omitted, the current job is used. +Any of the ways to select a job may be used for +.Ar job , +including the +.Sq Li \&% +forms, or the process id of the job leader +.Po +.Dq Li \&$! +if the job was created in the background. +.Pc +.Pp +If one of the flags is given, then instead of the list of +process identifiers, the +.Ic jobid +command prints: +.Bl -tag -width ".Fl g" +.It Fl g +the process group, if one was created for this job, +or nothing otherwise (the job is in the same process +group as the shell.) +.It Fl j +the job identifier (using +.Dq Li \&% Ns Ar n +notation, where +.Ar n +is a number) is printed. +.It Fl p +only the process id of the process group leader is printed. +.El +.Pp +These flags are mutually exclusive. +.Pp +.Ic jobid +exits with status 2 if there is an argument error, +status 1, if with +.Fl g +the job had no separate process group, +or with +.Fl p +there is no process group leader (should not happen), +and otherwise exits with status 0. +.\" +.It Ic jobs Oo Fl l Ns \&| Ns Fl p Oc Op Ar job ... +Without +.Ar job +arguments, +this command lists out all the background processes +which are children of the current shell process. +With +.Ar job +arguments, the listed jobs are shown instead. +Without flags, the output contains the job +identifier (see +.Sx Job Control +below), an indicator character if the job is the current or previous job, +the current status of the job (running, suspended, or terminated successfully, +unsuccessfully, or by a signal) +and a (usually abbreviated) command string. +.Pp +With the +.Fl l +flag the output is in a longer form, with the process identifiers +of each process (run from the top level, as in a pipeline), and the +status of each process, rather than the job status. +.Pp +With the +.Fl p +flag, the output contains only the process identifier of the lead +process. +.Pp +In an interactive shell, each job shown as completed in the output +from the jobs command is implicitly waited for, and is removed from +the jobs table, never to be seen again. +In an interactive shell, when a background job terminates, the +.Ic jobs +command (with that job as an argument) is implicitly run just +before outputting the next PS1 command prompt, after the job +terminated. +This indicates that the job finished, shows its status, +and cleans up the job table entry for that job. +Non-interactive shells need to execute +.Ic wait +commands to clean up terminated background jobs. +.\" +.It Ic local Oo Fl INx Oc Oo Ar variable | \- Oc ... +Define local variables for a function. +Local variables have their attributes, and values, +as they were before the +.Ic local +declaration, restored when the function terminates. +.Pp +With the +.Fl N +flag, variables made local, are unset initially inside +the function. +Unless the +.Fl x +flag is also given, such variables are also unexported. +The +.Fl I +flag, which is the default in this shell, causes +the initial value and exported attribute +of local variables +to be inherited from the variable +with the same name in the surrounding +scope, if there is one. +If there is not, the variable is initially unset, +and not exported. +The +.Fl N +and +.Fl I +flags are mutually exclusive, if both are given, the last specified applies. +The read-only and unexportable attributes are always +inherited, if a variable with the same name already exists. +.Pp +The +.Fl x +flag (lower case) causes the local variable to be exported, +while the function runs, unless it has the unexportable attribute. +This can also be accomplished by using the +.Ic export +command, giving the same +.Ar variable +names, after the +.Ic local +command. +.Pp +Making an existing read-only variable local is possible, +but pointless. +If an attempt is made to assign an initial value to such +a variable, the +.Ic local +command fails, as does any later attempted assignment. +If the +.Ic readonly +command is applied to a variable that has been declared local, +the variable cannot be (further) modified within the function, +or any other functions it calls, however when the function returns, +the previous status (and value) of the variable is returned. +.Pp +Values may be given to local variables on the +.Ic local +command line in a similar fashion as used for +.Ic export +and +.Ic readonly . +These values are assigned immediately after the initialization +described above. +Note that any variable references on the command line will have +been expanded before +.Ic local +is executed, so expressions like +.Pp +.Dl "local -N X=${X}" +.Pp +are well defined, first $X is expanded, and then the command run is +.Pp +.Dl "local -N X=old-value-of-X" +.Pp +After arranging to preserve the old value and attributes, of +.Dv X +.Dq ( old-value-of X ) +.Ic local +unsets +.Dv X , +unexports it, and then assigns the +.Dq old-value-of-X +to +.Ev X . +.Pp +The shell uses dynamic scoping, so that if you make the variable +.Dv x +local to +function +.Dv f , +which then calls function +.Dv g , +references to the variable +.Dv x +made inside +.Dv g +will refer to the variable +.Dv x +declared inside +.Dv f , +not to the global variable named +.Dv x . +.Pp +Another way to view this, is as if the shell just has one flat, global, +namespace, in which all variables exist. +The +.Ic local +command conceptually copies the variable(s) named to unnamed temporary +variables, and when the function ends, copies them back again. +All references to the variables reference the same global variables, +but while the function is active, after the +.Ic local +command has run, the values and attributes of the variables might +be altered, and later, when the function completes, be restored. +.Pp +Note that the positional parameters +.Dv 1 , \" $1 +.Dv 2 , \" $2 +\&... (see +.Sx Positional Parameters ) , +and the special parameters +.Dv \&# , \" $# +.Dv \&* \" $* +and +.Dv \&@ \" $@ +(see +.Sx Special Parameters ) , +are always made local in all functions, and are reset inside the +function to represent the options and arguments passed to the function. +Note that +.Li $0 +however retains the value it had outside the function, +as do all the other special parameters. +.Pp +The only special parameter that can optionally be made local is +.Dq Li \- . +Making +.Dq Li \- +local causes any shell options that are changed via the set command inside the +function to be restored to their original values when the function +returns. +If +.Fl X +option is altered after +.Dq Li \- +has been made local, then when the function returns, the previous +destination for +.Cm xtrace +output (as of the time of the +.Ic local +command) will also be restored. +If any of the shell's magic variables +(those which return a value which may vary without +the variable being explicitly altered, +e.g.: +.Dv SECONDS +or +.Dv HOSTNAME ) +are made local in a function, +they will lose their special properties when set +within the function, including by the +.Ic local +command itself +(if not to be set in the function, there is little point +in making a variable local) +but those properties will be restored when the function returns. +.Pp +It is an error to use +.Ic local +outside the scope of a function definition. +When used inside a function, it exits with status 0, +unless an undefined option is used, or an attempt is made to +assign a value to a read-only variable. +.Pp +Note that either +.Fl I +or +.Fl N +should always be used, or variables made local should always +be given a value, or explicitly unset, as the default behavior +(inheriting the earlier value, or starting unset after +.Ic local ) +differs amongst shell implementations. +Using +.Dq Li local \&\- +is an extension not implemented by most shells. +.Pp +See the section +.Sx LINENO +below for details of the effects of making the variable +.Dv LINENO +local. +.\" +.It Ic pwd Op Fl \&LP +Print the current directory. +If +.Fl L +is specified the cached value (initially set from +.Ev PWD ) +is checked to see if it refers to the current directory; if it does +the value is printed. +Otherwise the current directory name is found using +.Xr getcwd 3 . +The environment variable +.Ev PWD +is set to the printed value. +.Pp +The default is +.Ic pwd +.Fl L , +but note that the built-in +.Ic cd +command doesn't support the +.Fl L +option and will cache (almost) the absolute path. +If +.Ic cd +is changed (as unlikely as that is), +.Ic pwd +may be changed to default to +.Ic pwd +.Fl P . +.Pp +If the current directory is renamed and replaced by a symlink to the +same directory, or the initial +.Ev PWD +value followed a symbolic link, then the cached value may not +be the absolute path. +.Pp +The built-in command may differ from the program of the same name because +the program will use +.Ev PWD +and the built-in uses a separately cached value. +.\" +.It Ic read Oo Fl p Ar prompt Oc Oo Fl r Oc Ar variable Op Ar ... +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 field splitting section of the +.Sx Word Expansions +section 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 +.Ev 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. +The +.Ic read +built-in will indicate success unless EOF is encountered on input, in +which case failure is returned. +.Pp +By default, unless the +.Fl r +option is specified, the backslash +.Dq \e +acts as an escape character, causing the following character to be treated +literally. +If a backslash is followed by a newline, the backslash and the +newline will be deleted. +.\" +.It Ic readonly Ar name Ns Oo =value Oc ... +.It Ic readonly Oo Fl p Oo Ar name ... Oc Oc +.It Ic readonly Fl q Ar name ... +With no options, +the specified names are marked as read only, so that they cannot be +subsequently modified or unset. +The shell allows the value of a variable +to be set at the same time it is marked read only by writing +.Pp +.Dl readonly name=value +.Pp +With no arguments the +.Ic readonly +command lists the names of all set read only variables. +With the +.Fl p +option specified, +the output will be formatted suitably for non-interactive use, +and unset variables are included. +When the +.Fl p +option is given, +a list of variable names (without values) may also be specified, +in which case output is limited to the named variables. +.Pp +With the +.Fl q +option, the +.Ic readonly +command tests the read-only status of the variables listed +and exits with status 0 if all named variables are read-only, +or with status 1 if any are not read-only. +.Pp +Other than as specified for +.Fl q +the +.Ic readonly +command normally exits with status 0. +In all cases, if an unknown option, or an invalid option combination, +or an invalid variable name, is given; +or a variable which was already read-only is attempted to be set; +the exit status will not be zero, a diagnostic +message will be written to the standard error output, +and a non-interactive shell will terminate. +.\" +.It Ic return Op Ar n +Stop executing the current function or a dot command with return value of +.Ar n +or the value of the last executed command, if not specified. +For portability, +.Ar n +should be in the range from 0 to 255. +.Pp +The POSIX standard says that the results of +.Ic return +outside a function or a dot command are unspecified. +This implementation treats such a return as a no-op with a return value of 0 +(success, true). +Use the +.Ic exit +command instead, if you want to return from a script or exit +your shell. +.\" +.It Ic set Oo { Fl options | Cm +options | Cm \-- } Oc Ar arg ... +The +.Ic set +command performs four different functions. +.Pp +With no arguments, it lists the values of all shell variables. +.Pp +With a single option of either +.Dq Fl o +or +.Dq Cm +o +.Ic set +outputs the current values of the options. +In the +.Fl o +form, all options are listed, with their current values. +In the +.Cm +o +form, the shell outputs a string that can later be used +as a command to reset all options to their current values. +.Pp +If options are given, it sets the specified option +flags, or clears them as described in the +.Sx Argument List Processing +section. +In addition to the options listed there, +when the +.Dq "option name" +given to +.Ic set Fl o +is +.Cm default +all of the options are reset to the values they had immediately +after +.Nm +initialization, before any startup scripts, or other input, had been processed. +While this may be of use to users or scripts, its primary purpose +is for use in the output of +.Dq Ic set Cm +o , +to avoid that command needing to list every available option. +There is no +.Cm +o default . +.Pp +The fourth use of the +.Ic set +command is to set the values of the shell's +positional parameters to the specified arguments. +To change the positional +parameters without changing any options, use +.Dq -\|- +as the first argument to +.Ic set . +If no following arguments are present, the +.Ic set +command +will clear all the positional parameters (equivalent to executing +.Dq Li shift $# . ) +Otherwise the following arguments become +.Li \&$1 , +.Li \&$2 , +\&..., +and +.Li \&$# +is set to the number of arguments present. +.\" +.It Ic setvar Ar variable Ar value +Assigns +.Ar value +to +.Ar variable . +(In general it is better to write +.Li variable=value +rather than using +.Ic setvar . +.Ic setvar +is intended to be used in +functions that assign values to variables whose names are passed as +parameters.) +.\" +.It Ic shift Op Ar n +Shift the positional parameters +.Ar n +times. +If +.Ar n +is omitted, 1 is assumed. +Each +.Ic shift +sets the value of +.Li $1 +to the previous value of +.Li $2 , +the value of +.Li $2 +to the previous value of +.Li $3 , +and so on, decreasing +the value of +.Li $# +by one. +The shift count must be less than or equal to the number of +positional parameters ( +.Dq Li $# ) +before the shift. +.\" +.It Ic times +Prints two lines to standard output. +Each line contains two accumulated time values, expressed +in minutes and seconds (including fractions of a second.) +The first value gives the user time consumed, the second the system time. +.Pp +The first output line gives the CPU and system times consumed by the +shell itself. +The second line gives the accumulated times for children of this +shell (and their descendants) which have exited, and then been +successfully waited for by the relevant parent. +See +.Xr times 3 +for more information. +.Pp +.Ic times +has no parameters, and exits with an exit status of 0 unless +an attempt is made to give it an option. +.\" +.It Ic trap Ar action signal ... +.It Ic trap \- +.It Ic trap Op Fl l +.It Ic trap Oo Fl p Oc Ar signal ... +.It Ic trap Ar N signal ... +.Pp +Cause the shell to parse and execute action when any of the specified +signals are received. +The signals are specified by signal number or as the name of the signal. +If +.Ar signal +is +.Li 0 \" $0 +or its equivalent, +.Li EXIT , +the action is executed when the shell exits. +The +.Ar action +may be a null (empty) string, +which causes the specified signals to be ignored. +With +.Ar action +set to +.Sq Li - +the specified signals are set to their default actions. +If the first +.Ar signal +is specified in its numeric form, then +.Ar action +can be omitted to achieve the same effect. +This archaic, +but still standard, +form should not be relied upon, use the explicit +.Sq Li - +action. +If no signals are specified with an action of +.Sq Li - , +all signals are reset. +.Pp +When the shell forks off a sub-shell, it resets trapped (but not ignored) +signals to the default action. +On non-interactive shells, the +.Ic trap +command has no effect on signals that were +ignored on entry to the shell. +On interactive shells, the +.Ic trap +command will catch or reset signals ignored on entry. +.Pp +Issuing +.Ic trap +with option +.Fl l +will print a list of valid signal names. +.Ic trap +without any arguments causes it to write a list of signals and their +associated non-default actions to the standard output in a format +that is suitable as an input to the shell that achieves the same +trapping results. +With the +.Fl p +flag, trap prints the same information for the signals specified, +or if none are given, for all signals, including those where the +action is the default. +These variants of the trap command may be executed in a sub-shell +.Pq "such as in a command substitution" , +provided they appear as the sole, or first, command in that sub-shell, +in which case the state of traps from the parent of that +sub-shell is reported. +.Pp +Examples: +.Pp +.Dl trap +.Pp +List trapped signals and their corresponding actions. +.Pp +.Dl trap -l +.Pp +Print a list of valid signals. +.Pp +.Dl trap '' INT QUIT tstp 30 +.Pp +Ignore signals INT QUIT TSTP USR1. +.Pp +.Dl trap date INT +.Pp +Run the +.Dq date +command (print the date) upon receiving signal INT. +.Pp +.Dl trap HUP INT +.Pp +Run the +.Dq HUP +command, or function, upon receiving signal INT. +.Pp +.Dl trap 1 2 +.Pp +Reset the actions for signals 1 (HUP) and 2 (INT) to their defaults. +.Bd -literal -offset indent +traps=$(trap -p) + # more commands ... +trap 'action' SIG + # more commands ... +eval "$traps" +.Ed +.Pp +Save the trap status, execute commands, changing some traps, +and then reset all traps to their values at the start of the sequence. +The +.Fl p +option is required in the first command here, +or any signals that were previously +untrapped (in their default states) +and which were altered during the intermediate code, +would not be reset by the final +.Ic eval . +.\" +.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, shell built-in, +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 H Ns \*(Ba Ns Fl S Oc Op Fl a \*(Ba Fl btfdscmlrpnv Op Ar value +Inquire about or set the hard or soft limits on processes or set new +limits. +The choice between hard limit (which no process is allowed to +violate, and which may not be raised once it has been lowered) and soft +limit (which causes processes to be signaled but not necessarily killed, +and which may be raised) is made with these flags: +.Bl -tag -width Fl +.It Fl H +set or inquire about hard limits +.It Fl S +set or inquire about soft limits. +.El +.Pp +If neither +.Fl H +nor +.Fl S +is specified, the soft limit is displayed or both limits are set. +If both are specified, the last one wins. +.Pp +The limit to be interrogated or set, then, is chosen by specifying +any one of these flags: +.Bl -tag -width Fl +.It Fl a +show all the current limits +.It Fl b +the socket buffer size of a process (bytes) +.It Fl c +the largest core dump size that can be produced +(512-byte blocks) +.It Fl d +the data segment size of a process (kilobytes) +.It Fl f +the largest file that can be created +(512-byte blocks) +.It Fl l +how much memory a process can lock with +.Xr mlock 2 +(kilobytes) +.It Fl m +the total physical memory that can be +in use by a process (kilobytes) +.It Fl n +the number of files a process can have open at once +.It Fl p +the number of processes this user can +have at one time +.It Fl r +the number of threads this user can +have at one time +.It Fl s +the stack size of a process (kilobytes) +.It Fl t +CPU time (seconds) +.It Fl v +how large a process address space can be +.El +.Pp +If none of these is specified, it is the limit on file size that is shown +or set. +If value is specified, the limit is set to that number; otherwise +the current limit is displayed. +.Pp +Limits of an arbitrary process can be displayed or set using the +.Xr sysctl 8 +utility. +.It Ic umask Oo Fl S Oc Op Ar mask +Set the value of umask (see +.Xr umask 2 ) +to the specified octal value. +If the argument is omitted, the umask value is printed. +With +.Fl S +a symbolic form is used instead of an octal number. +.It Ic unalias Oo Fl a Oc Op Ar name +If +.Ar name +is specified, the shell removes that alias. +If +.Fl a +is specified, all aliases are removed. +.It Ic unset Oo Fl efvx Oc Ar name ... +If +.Fl v +is specified, the specified variables are unset and unexported. +Readonly variables cannot be unset. +If +.Fl f +is specified, the specified functions are undefined. +If +.Fl e +is given, the specified variables are unexported, but otherwise unchanged, +alternatively, if +.Fl x +is given, the exported status of the variable will be retained, +even after it is unset. +.Pp +If no flags are provided +.Fl v +is assumed. +If +.Fl f +is given with one of the other flags, +then the named variables will be unset, or unexported, and functions +of the same names will be undefined. +The +.Fl e +and +.Fl x +flags both imply +.Fl v . +If +.Fl e +is given, the +.Fl x +flag is ignored. +.Pp +The exit status is 0, unless an attempt was made to unset +a readonly variable, in which case the exit status is 1. +It is not an error to unset (or undefine) a variable (or function) +that is not currently set (or defined.) +.\" +.It Ic wait Oo Fl n Oc Oo Fl p Ar var Oc Op Ar job ... +Wait for the specified jobs to complete +and return the exit status of the last job in the parameter list, +or 127 if that job is not a current child of the shell. +.Pp +If no +.Ar job +arguments are given, wait for all jobs to +complete and then return an exit status of zero +(including when there were no jobs, and so nothing exited.) +.Pp +With the +.Fl n +option, wait instead for any one of the given +.Ar job Ns s, +or if none are given, any job, to complete, and +return the exit status of that job. +If none of the given +.Ar job +arguments is a current child of the shell, +or if no +.Ar job +arguments are given and the shell has no unwaited for children, +then the exit status will be 127. +.Pp +The +.Fl p Ar var +option allows the process (or job) identifier of the +job for which the exit status is returned to be obtained. +The variable named (which must not be readonly) will be +unset initially, then if a job has exited and its status is +being returned, set to the identifier from the +arg list (if given) of that job, +or the lead process identifier of the job to exit when used with +.Fl n +and no job arguments. +Note that +.Fl p +with neither +.Fl n +nor +.Ar job +arguments is useless, as in that case no job status is +returned, the variable named is simply unset. +.Pp +If the wait is interrupted by a signal, +its exit status will be greater than 128, +and +.Ar var , +if given, will remain unset. +.Pp +Once waited upon, by specific process number or job-id, +or by a +.Ic wait +with no arguments, +knowledge of the child is removed from the system, +and it cannot be waited upon again. +.Pp +Note than when a list of jobs are given, more that +one argument might refer to the same job. +In that case, if the final argument represents a job +that is also given earlier in the list, it is not +defined whether the status returned will be the +exit status of the job, or 127 indicating that +the child no longer existed when the wait command +reached the later argument in the list. +In this +.Nm +the exit status will be that from the job. +.Nm +waits for each job exactly once, regardless of +how many times (or how many different ways) it +is listed in the arguments to +.Ic wait . +That is +.Bd -literal -offset indent -compact +wait 100 100 100 +.Ed +is identical to +.Bd -literal -offset indent -compact +wait 100 +.Ed +.El +.\" +.\" +.Ss Job Control +.\" +Each process (or set of processes) started by +.Nm +is created as a +.Dq job +and added to the jobs table. +When enabled by the +.Fl m +option +.Pq aka Fl o Cm monitor +when the job is created, +.Nm +places each job (if run from the top level shell) +into a process group of its own, which allows control +of the process(es), and its/their descendants, as a unit. +When the +.Fl m +option is off, or when started from a sub-shell environment, +jobs share the same process group as the parent shell. +The +.Fl m +option is enabled by default in interactive shells with +a terminal as standard input and standard error. +.Pp +Jobs with separate process groups may be stopped, and then later +resumed in the foreground (with access to the terminal) +or in the background (where attempting to read from the +terminal will result in the job stopping.) +A list of current jobs can be obtained using the +.Ic jobs +built-in command. +Jobs are identified using either the process identifier +of the lead process of the job (the value available in +the special parameter +.Dq Dv \&! +if the job is started in the background), or using percent +notation. +Each job is given a +.Dq job number +which is a small integer, starting from 1, and can be +referenced as +.Dq Li \&% Ns Ar n +where +.Ar n +is that number. +Note that this applies to jobs both with and without their own process groups. +Job numbers are shown in the output from the +.Ic jobs +command enclosed in brackets +.Po +.Sq Li \&[ +and +.Sq Li \&] +.Pc . +Whenever the job table becomes empty, the numbers begin at one again. +In addition, there is the concept of a current, and a previous job, +identified by +.Dq Li \&%+ +.Po +or +.Dq Li \&%% +or even just +.Dq Li \&% +.Pc , +and a previous job, identified by +.Dq Li \&%\- . +Whenever a background job is started, +or a job is resumed in the background, +it becomes the current job. +The job that was the current job +(prepare for a big surprise here, drum roll..., wait for it...\&) +becomes the previous job. +When the current job terminates, the previous job is +promoted to be the current job. +In addition the form +.Dq Li \&% Ns Ar string\^ +finds the job for which the command starts with +.Ar string +and the form +.Dq Li \&%? Ns Ar string\^ +finds the job which contains the +.Ar string +in its command somewhere. +Both forms require the result to be unambiguous. +For this purpose the +.Dq command +is that shown in the output from the +.Ic jobs +command, not the original command line. +.Pp +The +.Ic bg , +.Ic fg , +.Ic jobid , +.Ic jobs , +.Ic kill , +and +.Ic wait +commands all accept job identifiers as arguments, in addition to +process identifiers (larger integers). +See the +.Sx Built-ins +section above, and +.Xr kill 1 , +for more details of those commands. +In addition, a job identifier +(using one of the +.Dq \&% forms ) +issued as a command, without arguments, is interpreted as +if it had been given as the argument to the +.Ic fg +command. +.Pp +To cause a foreground process to stop, enter the terminal's +.Ic stop +character (usually control-Z). +To cause a background process to stop, send it a +.Dv STOP +signal, using the kill command. +A useful function to define is +.Bd -literal -offset indent +stop() { kill -s STOP "${@:-%%}"; } +.Ed +.Pp +The +.Ic fg +command resumes a stopped job, placing it in the foreground, +and +.Ic bg +resumes a stopped job in the background. +The +.Ic jobid +command provides information about process identifiers, job identifiers, +and the process group identifier, for a job. +.Pp +Whenever a sub-shell is created, the jobs table becomes invalid +(the sub-shell has no children.) +However, to enable uses like +.Bd -literal -offset indent +PID=$(jobid -p %1) +.Ed +.Pp +the table is only actually cleared in a sub-shell when needed to +create the first job there (built-in commands run in the foreground +do not create jobs.) +Note that in this environment, there is no useful current job +.Dq ( Li \&%% +actually refers to the sub-shell itself, but is not accessible) +but the job which is the current job in the parent can be accessed as +.Dq Li \&%\- . +.\" +.\" +.Ss Command Line Editing +.\" +When +.Nm +is being used interactively from a terminal, the current command +and the command history (see +.Ic fc +in the +.Sx Built-ins +section) +can be edited using emacs-mode or vi-mode command-line editing. +The command +.Ql set -o emacs +(or +.Fl E +option) +enables emacs-mode editing. +The command +.Ql set -o vi +(or +.Fl V +option) +enables vi-mode editing and places the current shell process into +vi insert mode. +(See the +.Sx Argument List Processing +section above.) +.Pp +The vi-mode uses commands similar to a subset of those described in the +.Xr vi 1 +man page. +With vi-mode +enabled, +.Nm sh +can be switched between insert mode and command mode. +It's similar to +.Ic vi : +pressing the +.Aq ESC +key will throw you into vi command mode. +Pressing the +.Aq return +key while in command mode will pass the line to the shell. +.Pp +The emacs-mode uses commands similar to a subset available in the +.Ic emacs +editor. +With emacs-mode enabled, special keys can be used to modify the text +in the buffer using the control key. +.Pp +.Nm +uses the +.Xr editline 3 +library. +See +.Xr editline 7 +for a list of the possible command bindings, +and the default settings in emacs and vi modes. +Also see +.Xr editrc 5 +for the commands that can be given to configure +.Xr editline 7 +in the file named by the +.Ev EDITRC +parameter, +or a file used with the +.Ic inputrc +built-in command, +or using +.Xr editline 7 Ap s +configuration command line. +.Pp +When command line editing is enabled, the +.Xr editline 7 +functions control printing of the +.Ev PS1 +and +.Ev PS2 +prompts when required. +As, in this mode, the command line editor needs to +keep track of what characters are in what position on +the command line, care needs to be taken when setting +the prompts. +Normal printing characters are handled automatically, +however mode setting sequences, which do not actually display +on the terminal, need to be identified to +.Xr editline 7 . +This is done, when needed, by choosing a character that +is not needed anywhere in the prompt, including in the mode +setting sequences, any single character is acceptable, +and assigning it to the shell parameter +.Dv PSlit . +Then that character should be used, in pairs, in the +prompt string. +Between each pair of +.Dv PSlit +characters are mode setting sequences, which affect the printing +attributes of the following (normal) characters of the prompt, +but do not themselves appear visibly, nor change the terminal's +cursor position. +.Pp +Each such sequence, that is +.Dv PSlit +character, mode setting character sequence, and another +.Dv PSlit +character, must currently be followed by at least one following +normal prompt character, or it will be ignored. +That is, a +.Dv PSlit +character cannot be the final character of +.Ev PS1 +or +.Ev PS2 , +nor may two +.Dv PSlit +delimited sequences appear adjacent to each other. +Each sequence can contain as many mode altering sequences as are +required however. +Only the first character from +.Dv PSlit +will be used. +When set +.Dv PSlit +should usually be set to a string containing just one +character, then it can simply be embedded in +.Ev PS1 +(or +.Ev PS2 ) +as in +.Pp +.D1 Li PS1=\*q${PSlit} Ns Ar mset\^ Ns Li ${PSlit}XYZ${PSlit} Ns Ar mclr\^ Ns Li ${PSlit}ABC\*q +.Pp +The prompt visible will be +.Dq XYZABC +with the +.Dq XYZ +part shown according as defined by the mode setting characters +.Ar mset , +and then cleared again by +.Ar mclr . +See +.Xr tput 1 +for one method to generate appropriate mode sequences. +Note that both parts, XYZ and ABC, must each contain at least one +character. +.Pp +If +.Dv PSlit +is unset, which is its initial state, or set to a null string, +no literal character will be defined, +and all characters of the prompt strings will be assumed +to be visible characters (which includes spaces etc.) +To allow smooth use of prompts, without needing redefinition, when +.Xr editline 7 +is disabled, the character chosen should be one which will be +ignored by the terminal if received, as when +.Xr editline 7 +is not in use, the prompt strings are simply written to the terminal. +For example, setting: +.\" XXX: PS1 line is too long for -offset indent +.Bd -literal -offset left + PSlit="$(printf\ '\e1')" + PS1="${PSlit}$(tput\ bold\ blink)${PSlit}\e$${PSlit}$(tput\ sgr0)${PSlit}\ " +.Ed +.Pp +will arrange for the primary prompt to be a bold blinking dollar sign, +if supported by the current terminal, followed by an (ordinary) space, +and, as the SOH (control-A) character +.Pq Sq \e1 +will not normally affect +a terminal, this same prompt will usually work with +.Xr editline 7 +enabled or disabled. +.Sh ENVIRONMENT +.Bl -tag -width MAILCHECK +.It Ev CDPATH +The search path used with the +.Ic cd +built-in. +.It Ev EDITRC +Gives the name of the file containing commands for +.Xr editline 7 . +See +.Xr editrc 5 +for possible content and format. +The file is processed, when in interactive mode with +command line editing enabled, whenever +.Ev EDITRC +is set (even with no actual value change,) +and if command line editing changes from disabled to enabled, +or the editor style used is changed. +(See the +.Fl E +and +.Fl V +options of the +.Ic set +built-in command, described in +.Sx Built-ins +above, which are documented further above in +.Sx Argument List Processing . ) +If unset +.Dq $HOME/.editrc +is used. +.It Ev ENV +Names the file sourced at startup by the shell. +Unused by this shell after initialization, +but is usually passed through the environment to +descendant shells. +.It Ev EUSER +Set to the login name of the effective user id running the shell, +as returned by +.Bd -compact -literal -offset indent +getpwuid(geteuid())->pw_name +.Ed +.Po +See +.Xr getpwuid 3 +and +.Xr geteuid 2 +for more details. +.Pc +This is obtained each time +.Ev EUSER +is expanded, so changes to the shell's execution identity +cause updates without further action. +If unset, it returns nothing. +If set it loses its special properties, and is simply a variable. +.It Ev HISTSIZE +The number of lines in the history buffer for the shell. +.It Ev HOME +Set automatically by +.Xr login 1 +from the user's login directory in the password file +.Pq Xr passwd 5 . +This environment variable also functions as the default argument for the +.Ic cd +built-in. +.It Ev HOSTNAME +Set to the current hostname of the system, as returned by +.Xr gethostname 3 . +This is obtained each time +.Ev HOSTNAME +is expanded, so changes to the system's name are reflected +without further action. +If unset, it returns nothing. +If set it loses its special properties, and is simply a variable. +.It Ev IFS +Input Field Separators. +This is normally set to +.Aq space , +.Aq tab , +and +.Aq newline . +See the +.Sx White Space Splitting +section for more details. +.It Ev LANG +The string used to specify localization information that allows users +to work with different culture-specific and language conventions. +See +.Xr nls 7 . +.It Dv LINENO +The current line number in the script or function. +See the section +.Sx LINENO +below for more details. +.It Ev MAIL +The name of a mail file, that will be checked for the arrival of new mail. +Overridden by +.Ev MAILPATH . +The check occurs just before +.Ev PS1 +is written, immediately after reporting jobs which have changed status, +in interactive shells only. +New mail is considered to have arrived if the monitored file +has increased in size since the last check. +.\" .It Ev MAILCHECK +.\" The frequency in seconds that the shell checks for the arrival of mail +.\" in the files specified by the +.\" .Ev MAILPATH +.\" or the +.\" .Ev MAIL +.\" file. +.\" If set to 0, the check will occur at each prompt. +.It Ev MAILPATH +A colon +.Dq \&: +separated list of file names, for the shell to check for incoming mail. +This environment setting overrides the +.Ev MAIL +setting. +There is a maximum of 10 mailboxes that can be monitored at once. +.It Ev PATH +The default search path for executables. +See the +.Sx Path Search +section above. +.It Ev POSIXLY_CORRECT +If set in the environment upon initialization of the shell, +then the shell option +.Ic posix +will be set. +.Po +See the description of the +.Ic set +command in the +.Sx Built-ins +section. +.Pc +After initialization it is unused by the shell, +but is usually passed through the environment to +descendant processes, including other instances of the shell, +which may interpret it in a similar way. +.It Ev PPID +The process identified of the parent process of the +current shell. +This value is set at shell startup, ignoring +any value in the environment, and then made readonly. +.It Ev PS1 +The primary prompt string, which defaults to +.Dq Li "$ " , +unless you are the superuser, in which case it defaults to +.Dq Li "# " . +This string is subject to parameter, arithmetic, and if +enabled by setting the +.Ic promptcmds +option, command substitution before being output. +During execution of commands used by command substitution, +execution tracing, the +.Ic xtrace +.Ic ( set Fl x ) +option is temporarily disabled. +If +.Ic promptcmds +is not set and the prompt string uses command substitution, +the prompt used will be an appropriate error string. +For other expansion errors, a message will be output, +and the unexpanded string will then be used as the prompt. +.It Ev PS2 +The secondary prompt string, which defaults to +.Dq Li "> " . +After expansion (as for +.Ev PS1 ) +it is written whenever more input is required to complete the +current command. +.It Ev PS4 +Output, after expansion like +.Ev PS1 , +before each line when execution trace +.Ic ( set Fl x ) +is enabled. +.Ev PS4 +defaults to +.Dq Li "+ " . +.It Ev PSc +Initialized by the shell, ignoring any value from the environment, +to a single character string, either +.Sq \&# +or +.Sq \&$ , +depending upon whether the current user is the superuser or not. +This is intended for use when building a custom +.Ev PS1 . +.It Ev PSlit +Defines the character which may be embedded in pairs, in +.Ev PS1 +or +.Ev PS2 +to indicate to +.Xr editline 7 +that the characters between each pair of occurrences of the +.Dv PSlit +character will not appear in the visible prompt, and will not +cause the terminal's cursor to change position, but rather set terminal +attributes for the following prompt character(s) at least one of +which must be present. +See +.Sx Command Line Editing +above for more information. +.It Ev RANDOM +Returns a different pseudo-random integer, +in the range [0,32767] each time it is accessed. +.Ev RANDOM +can be assigned an integer value to seed the PRNG. +If the value assigned is a constant, then the +sequence of values produces on subsequent references of +.Ev RANDOM +will repeat after the next time the same constant is assigned. +Note, this is not guaranteed to remain constant from one version +of the shell to another \(en the PRNG algorithm, or seeding +method is subject to change. +If +.Ev RANDOM +is assigned an empty value (null string) then the next time +.Ev RANDOM +is accessed, it will be seeded from a more genuinely random source. +The sequence of pseudo-random numbers generated will not be able to +be generated again (except by luck, whether good or bad, depends!) +This is also how the initial seed is generated, if none has been +assigned before +.Ev RANDOM +is first accessed after shell initialization. +Should the error message +.Dq "RANDOM initialisation failed" +appear on standard error, it indicates that the source +of good random numbers was not available, and +.Ev RANDOM +has instead been seeded with a more predictable value. +The following sequence of random numbers will +not be as unpredictable as they otherwise would be. +.It Ev SECONDS +Returns the number of seconds since the current shell was started. +If unset, it remains unset, and returns nothing, unless set again. +If set, it loses its special properties, and becomes a normal variable. +.It Ev START_TIME +Initialized by the shell to the number of seconds since the Epoch +(see +.Xr localtime 3 ) +when the shell was started. +The value of +.Dl $(( Ns Ev START_TIME + Ev SECONDS Ns )) +represents the current time, if +.Ev START_TIME +has not been modified, and +.Ev SECONDS +has not been set or unset. +.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. +.\" This is explicitly last, not in sort order - please leave! +.It Ev ToD +When referenced, uses the value of +.Ev ToD_FORMAT +(or +.Dq \&%T +if +.Ev ToD_FORMAT +is unset) as the format argument to +.Xr strftime 3 +to encode the current time of day, in the time zone +defined by +.Ev TZ +if set, or current local time if not, and returns the result. +If unset +.Ev ToD +returns nothing. +If set, it loses its special properties, and becomes a normal variable. +.It Ev ToD_FORMAT +Can be set to the +.Xr strftime 3 +format string to be used when expanding +.Ev ToD . +Initially unset. +.It Ev TZ +If set, gives the time zone +(see +.Xr localtime 3 , +.Xr environ 7 ) +to use when formatting +.Ev ToD +and if exported, other utilities that deal with times. +If unset, the system's local wall clock time zone is used. +.It Ev NETBSD_SHELL +Unlike the variables previously mentioned, +this variable is somewhat strange, +in that it cannot be set, +inherited from the environment, +modified, or exported from the shell. +If set, by the shell, it indicates that the shell is the +.Ic sh +defined by this manual page, and gives its version information. +It can also give information in additional space separated words, +after the version string. +If the shell was built as part of a reproducible build, +the relevant date that was used for that build will be included. +Finally, any non-standard compilation options, +which may affect features available, +that were used when building the shell will be listed. +.Ev NETBSD_SHELL +behaves like any other variable that has the read-only +and un-exportable attributes set. +.El +.Ss Dv LINENO +.Dv LINENO +is in many respects a normal shell variable, containing an +integer value. and can be expanded using any of the forms +mentioned above which can be used for any other variable. +.Pp +.Dv LINENO +can be exported, made readonly, or unset, as with any other +variable, with similar effects. +Note that while being readonly prevents later attempts to +set, or unset, +.Dv LINENO , +it does not prevent its value changing. +References to +.Dv LINENO +.Pq "when not unset" +always obtain the current line number. +However, +.Dv LINENO +should normally not ever be set or unset. +In this shell setting +.Dv LINENO +reverses the effect of an earlier +.Ic unset , +but does not otherwise affect the value obtained. +If unset, +.Dv LINENO +should not normally be set again, doing so is not portable. +If +.Dv LINENO +is set or unset, different shells act differently. +The value of +.Dv LINENO +is never imported from the environment when the shell is +started, though if present there, as with any other variable, +.Dv LINENO +will be exported by this shell. +.Pp +.Dv LINENO +is set automatically by the shell to be the number of the source +line on which it occurs. +When exported, +.Dv LINENO +is exported with its value set to the line number it would have +had had it been referenced on the command line of the command to +which it is exported. +Line numbers are counted from 1, which is the first line the shell +reads from any particular file. +For this shell, standard input, including in an interactive shell, +the user's terminal, is just another file and lines are counted +there as well. +However note that not all shells count interactive +lines this way, it is not wise to rely upon +.Dv LINENO +having a useful value, except in a script, or a function. +.Pp +The role of +.Dv LINENO +in functions is less clear. +In some shells, +.Dv LINENO +continues to refer to the line number in the script which defines +the function, +in others lines count from one within the function, always (and +resume counting normally once the function definition is complete) +and others count in functions from one if the function is defined +interactively, but otherwise just reference the line number in the +script in which the function is defined. +This shell gives the user the option to choose. +If the +.Fl L +flag (the +.Ic local_lineno +option, see +.Sx Argument List Processing ) +is set, when the function is defined, then the function +defaults to counting lines with one being the first line of the +function. +When the +.Fl L +flag is not set, the shell counts lines in a function definition +in the same continuous sequence as the lines that surround the +function definition. +Further, if +.Dv LINENO +is made local +(see +.Sx Built-ins +above) +inside the function, the function can decide which +behavior it prefers. +If +.Dv LINENO +is made local and inherited, and not given a value, as in +.Dl local Fl I Dv LINENO +then from that point in the function, +.Dv LINENO +will give the line number as if lines are counted in sequence +with the lines that surround the function definition (and +any other function definitions in which this is nested.) +If +.Dv LINENO +is made local, and in that same command, given a value, as +.Dl local Oo Fl I Ns | Ns Fl N Oc Dv LINENO Ns = Ns Ar value +then +.Dv LINENO +will give the line number as if lines are counted from one +from the beginning of the function. +The value nominally assigned in this case is irrelevant, and ignored. +For completeness, if lineno is made local and unset, as in +.Dl local Fl N Dv LINENO +then +.Dv LINENO +is simply unset inside the function, and gives no value at all. +.Pp +Now for some technical details. +The line on which +.Dv LINENO +occurs in a parameter expansion, is the line that contains the +.Sq \&$ +that begins the expansion of +.Dv LINENO . +In the case of nested expansions, that +.Sq \&$ +is the one that actually has +.Dv LINENO +as its parameter. +In an arithmetic expansion, where no +.Sq \&$ +is used to evaluate +.Dv LINENO +but +.Dv LINENO +is simply referenced as a variable, then the value is the +line number of the line that contains the +.Sq L +of +.Dv LINENO . +For functions line one of the function definition (when relevant) +is the line that contains the first character of the +function name in the definition. +When exported, the line number of the command is the line number +where the first character of the word which becomes the command name occurs. +.Pp +When the shell opens a new file, for any reason, +it counts lines from one in that file, +and then resumes its original counting once it resumes reading the +previous input stream. +When handling a string passed to +.Ic eval +the line number starts at the line on which the string starts, +and then if the string contains internal newline characters, +those characters increase the line number. +This means that references to +.Dv LINENO +in such a case can produce values larger than would be +produced by a reference on the line after the +.Ic eval . +.Sh FILES +.Bl -item +.It +.Pa $HOME/.profile +.It +.Pa /etc/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 exit built-in is used with a numeric argument, it will return the +argument. +.Sh SEE ALSO +.Xr csh 1 , +.Xr echo 1 , +.Xr getopt 1 , +.Xr ksh 1 , +.Xr login 1 , +.Xr printf 1 , +.Xr test 1 , +.Xr editline 3 , +.Xr getopt 3 , +.\" .Xr profile 4 , +.Xr editrc 5 , +.Xr passwd 5 , +.Xr editline 7 , +.Xr environ 7 , +.Xr nls 7 , +.Xr sysctl 8 +.Sh HISTORY +A +.Nm +command appeared in +.At v1 . +It was replaced in +.At v7 +with a version that introduced the basis of the current syntax. +That was, however, unmaintainable so we wrote this one. +.Sh BUGS +Setuid shell scripts should be avoided at all costs, as they are a +significant security risk. +.Pp +The characters generated by filename completion should probably be quoted +to ensure that the filename is still valid after the input line has been +processed. +.Pp +Job control of compound statements (loops, etc) is a complete mess. +.Pp +Many, many, more. +(But less than there were...) diff --git a/bin/sh/shell.h b/bin/sh/shell.h new file mode 100644 index 0000000..318d56e --- /dev/null +++ b/bin/sh/shell.h @@ -0,0 +1,224 @@ +/* $NetBSD: shell.h,v 1.29 2019/01/22 13:48:28 kre Exp $ */ + +/*- + * 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 + */ + +/* + * The follow should be set to reflect the type of system you have: + * JOBS -> 1 if you have Berkeley job control, 0 otherwise. + * define BSD if you are running 4.2 BSD or later. + * define SYSV if you are running under System V. + * define DEBUG=1 to compile in debugging ('set -o debug' to turn on) + * define DEBUG=2 to compile in and enable debugging. + * define DEBUG=3 for DEBUG==2 + enable most standard debug output + * define DEBUG=4 for DEBUG==2 + enable absolutely everything + * define DO_SHAREDVFORK to indicate that vfork(2) shares its address + * with its parent. + * define BOGUS_NOT_COMMAND to allow ! reserved words in weird places + * (traditional ash behaviour.) + * + * When debugging is on, debugging info will be written to ./trace and + * a quit signal will generate a core dump. + */ + +#ifndef SHELL_H +#define SHELL_H +#include <sys/param.h> + +#define JOBS 1 +#ifndef BSD +#define BSD 1 +#endif + +#ifndef DO_SHAREDVFORK +#if defined(__NetBSD_Version__) && __NetBSD_Version__ >= 104000000 +#define DO_SHAREDVFORK +#endif +#endif + +typedef void *pointer; +#ifndef NULL +#define NULL (void *)0 +#endif +#ifndef STATIC +#define STATIC /* empty */ +#endif +#define MKINIT /* empty */ + +#include <sys/cdefs.h> + +extern const char nullstr[1]; /* null string */ + +#ifdef SMALL +#undef DEBUG +#endif + +#ifdef DEBUG + +extern uint64_t DFlags; +extern int ShNest; + +/* + * This is selected as there are 26 letters in ascii - not that that + * matters for anything, just makes it easier to assign a different + * command letter to each debug option. We currently use only 18 + * so this could be reduced, but that is of no real benefit. It can also + * be increased, but that both limits the maximum value tha can be + * used with DBG_EXTRAS(), and causes problems with verbose option naming. + */ +#define DBG_VBOSE_SHIFT 27 +#define DBG_EXTRAS(n) ((DBG_VBOSE_SHIFT * 2) + (n)) + +/* + * Macros to enable tracing, so the mainainer can control + * just how much debug output is dumped to the trace file + * + * In the X forms, "xtra" can be any legal C statement(s) without (bare) commas + * executed if the relevant debug flag is enabled, after any tracing output. + */ +#define CTRACE(when, param) do { \ + if ((DFlags & (when)) != 0) \ + trace param; \ + } while (/*CONSTCOND*/ 0) + +#define CCTRACE(when,cond,param) do { \ + if ((cond) && (DFlags & (when)) != 0) \ + trace param; \ + } while (/*CONSTCOND*/ 0) + +#define CTRACEV(when, param) do { \ + if ((DFlags & (when)) != 0) \ + tracev param; \ + } while (/*CONSTCOND*/ 0) + +#define XTRACE(when, param, xtra) do { \ + if ((DFlags & (when)) != 0) { \ + trace param; \ + xtra; \ + } \ + } while (/*CONSTCOND*/ 0) + +#define VTRACE(when, param) do { \ + if ((DFlags & \ + (when)<<DBG_VBOSE_SHIFT) != 0) \ + trace param; \ + } while (/*CONSTCOND*/ 0) + +#define CVTRACE(when,cond,param) do { \ + if ((cond) && (DFlags & \ + (when)<<DBG_VBOSE_SHIFT) != 0) \ + trace param; \ + } while (/*CONSTCOND*/ 0) + +#define VTRACEV(when, param) do { \ + if ((DFlags & \ + (when)<<DBG_VBOSE_SHIFT) != 0) \ + tracev param; \ + } while (/*CONSTCOND*/ 0) + +#define VXTRACE(when, param, xtra) do { \ + if ((DFlags & \ + (when)<<DBG_VBOSE_SHIFT) != 0) {\ + trace param; \ + xtra; \ + } \ + } while (/*CONSTCOND*/ 0) + +#define SHELL_FORKED() ShNest++ +#define VFORK_BLOCK { const int _ShNest = ShNest; +#define VFORK_END } +#define VFORK_UNDO() ShNest = _ShNest + +#define DBG_ALWAYS (1LL << 0) +#define DBG_PARSE (1LL << 1) /* r (read commands) */ +#define DBG_EVAL (1LL << 2) /* e */ +#define DBG_EXPAND (1LL << 3) /* x */ +#define DBG_JOBS (1LL << 4) /* j */ +#define DBG_PROCS (1LL << 5) /* p */ +#define DBG_REDIR (1LL << 6) /* f (fds) */ +#define DBG_CMDS (1LL << 7) /* c */ +#define DBG_ERRS (1LL << 8) /* z (?) */ +#define DBG_WAIT (1LL << 9) /* w */ +#define DBG_TRAP (1LL << 10) /* t */ +#define DBG_VARS (1LL << 11) /* v */ +#define DBG_INPUT (1LL << 12) /* i */ +#define DBG_OUTPUT (1LL << 13) /* o */ +#define DBG_MEM (1LL << 14) /* m */ +#define DBG_ARITH (1LL << 15) /* a */ +#define DBG_HISTORY (1LL << 16) /* h */ +#define DBG_SIG (1LL << 17) /* s */ +#define DBG_MATCH (1LL << 18) /* g (glob) */ +#define DBG_LEXER (1LL << 19) /* l */ + +/* + * reserved extras: b=builtins y=alias + * still free: d k n q u + */ + + /* use VTRACE(DBG_ALWAYS, (...)) to test this one */ +#define DBG_VERBOSE (1LL << DBG_VBOSE_SHIFT) + + /* DBG_EXTRAS 0 .. 9 (max) only - non-alpha options (no VTRACE !!) */ +#define DBG_U0 (1LL << DBG_EXTRAS(0)) /* 0 - ad-hoc extra flags */ +#define DBG_U1 (1LL << DBG_EXTRAS(1)) /* 1 - for short term */ +#define DBG_U2 (1LL << DBG_EXTRAS(2)) /* 2 - extra tracing */ +#define DBG_U3 (1LL << DBG_EXTRAS(3)) /* 3 - when needed */ + /* 4, 5, & 6 currently free */ +#define DBG_LINE (1LL << DBG_EXTRAS(7)) /* @ ($LINENO) */ +#define DBG_PID (1LL << DBG_EXTRAS(8)) /* $ ($$) */ +#define DBG_NEST (1LL << DBG_EXTRAS(9)) /* ^ */ + +/* 26 lower case, 26 upper case, always, verbose, and 10 extras: 64 bits */ + +extern void set_debug(const char *, int); + +#else /* DEBUG */ + +#define CTRACE(when, param) /* conditional normal trace */ +#define CCTRACE(when, cond, param) /* more conditional normal trace */ +#define CTRACEV(when, param) /* conditional varargs trace */ +#define XTRACE(when, param, extra) /* conditional trace, plus more */ +#define VTRACE(when, param) /* conditional verbose trace */ +#define CVTRACE(when, cond, param) /* more conditional verbose trace */ +#define VTRACEV(when, param) /* conditional verbose varargs trace */ +#define VXTRACE(when, param, extra) /* cond verbose trace, plus more */ + +#define SHELL_FORKED() +#define VFORK_BLOCK +#define VFORK_END +#define VFORK_UNDO() + +#endif /* DEBUG */ + +#endif /* SHELL_H */ diff --git a/bin/sh/show.c b/bin/sh/show.c new file mode 100644 index 0000000..fa9f5aa --- /dev/null +++ b/bin/sh/show.c @@ -0,0 +1,1175 @@ +/* $NetBSD: show.c,v 1.52 2019/01/22 13:48:28 kre Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Copyright (c) 2017 The NetBSD Foundation, Inc. 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> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)show.c 8.3 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: show.c,v 1.52 2019/01/22 13:48:28 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <limits.h> + +#include <sys/types.h> +#include <sys/uio.h> + +#include "shell.h" +#include "parser.h" +#include "nodes.h" +#include "mystring.h" +#include "show.h" +#include "options.h" +#include "redir.h" +#include "error.h" +#include "syntax.h" +#include "input.h" +#include "output.h" +#include "var.h" +#include "builtins.h" + +#define DEFINE_NODENAMES +#include "nodenames.h" /* does almost nothing if !defined(DEBUG) */ + +#define TR_STD_WIDTH 60 /* tend to fold lines wider than this */ +#define TR_IOVECS 10 /* number of lines or trace (max) / write */ + +typedef struct traceinfo { + int tfd; /* file descriptor for open trace file */ + int nxtiov; /* the buffer we should be writing to */ + char lastc; /* the last non-white character output */ + uint8_t supr; /* char classes to suppress after \n */ + pid_t pid; /* process id of process that opened that file */ + size_t llen; /* number of chars in current output line */ + size_t blen; /* chars used in current buffer being filled */ + char * tracefile; /* name of the tracefile */ + struct iovec lines[TR_IOVECS]; /* filled, flling, pending buffers */ +} TFILE; + +/* These are auto turned off when non white space is printed */ +#define SUP_NL 0x01 /* don't print \n */ +#define SUP_SP 0x03 /* suppress spaces */ +#define SUP_WSP 0x04 /* suppress all white space */ + +#ifdef DEBUG /* from here to end of file ... */ + +TFILE tracedata, *tracetfile; +FILE *tracefile; /* just for histedit */ + +uint64_t DFlags; /* currently enabled debug flags */ +int ShNest; /* depth of shell (internal) nesting */ + +static void shtree(union node *, int, int, int, TFILE *); +static void shcmd(union node *, TFILE *); +static void shsubsh(union node *, TFILE *); +static void shredir(union node *, TFILE *, int); +static void sharg(union node *, TFILE *); +static void indent(int, TFILE *); +static void trstring(const char *); +static void trace_putc(char, TFILE *); +static void trace_puts(const char *, TFILE *); +static void trace_flush(TFILE *, int); +static char *trace_id(TFILE *); +static void trace_fd_swap(int, int); + +inline static int trlinelen(TFILE *); + + +/* + * These functions are the externally visible interface + * (but only for a DEBUG shell.) + */ + +void +opentrace(void) +{ + char *s; + int fd; + int i; + pid_t pid; + + if (debug != 1) { + /* leave fd open because libedit might be using it */ + if (tracefile) + fflush(tracefile); + if (tracetfile) + trace_flush(tracetfile, 1); + return; + } +#if DBG_PID == 1 /* using old shell.h, old tracing method */ + DFlags = DBG_PID; /* just force DBG_PID on, and leave it ... */ +#endif + pid = getpid(); + if (asprintf(&s, "trace.%jd", (intmax_t)pid) <= 0) { + debug = 0; + error("Cannot asprintf tracefilename"); + }; + + fd = open(s, O_WRONLY|O_APPEND|O_CREAT, 0666); + if (fd == -1) { + debug = 0; + error("Can't open tracefile: %s (%s)\n", s, strerror(errno)); + } + fd = to_upper_fd(fd); + if (fd <= 2) { + (void) close(fd); + debug = 0; + error("Attempt to use fd %d as tracefile thwarted\n", fd); + } + register_sh_fd(fd, trace_fd_swap); + + /* + * This stuff is just so histedit has a FILE * to use + */ + if (tracefile) + (void) fclose(tracefile); /* also closes tfd */ + tracefile = fdopen(fd, "a"); /* don't care if it is NULL */ + if (tracefile) /* except here... */ + setlinebuf(tracefile); + + /* + * Now the real tracing setup + */ + if (tracedata.tfd > 0 && tracedata.tfd != fd) + (void) close(tracedata.tfd); /* usually done by fclose() */ + + tracedata.tfd = fd; + tracedata.pid = pid; + tracedata.nxtiov = 0; + tracedata.blen = 0; + tracedata.llen = 0; + tracedata.lastc = '\0'; + tracedata.supr = SUP_NL | SUP_WSP; + +#define replace(f, v) do { \ + if (tracedata.f != NULL) \ + free(tracedata.f); \ + tracedata.f = v; \ + } while (/*CONSTCOND*/ 0) + + replace(tracefile, s); + + for (i = 0; i < TR_IOVECS; i++) { + replace(lines[i].iov_base, NULL); + tracedata.lines[i].iov_len = 0; + } + +#undef replace + + tracetfile = &tracedata; + + trace_puts("\nTracing started.\n", tracetfile); +} + +void +trace(const char *fmt, ...) +{ + va_list va; + char *s; + + if (debug != 1 || !tracetfile) + return; + va_start(va, fmt); + (void) vasprintf(&s, fmt, va); + va_end(va); + + trace_puts(s, tracetfile); + free(s); + if (tracetfile->llen == 0) + trace_flush(tracetfile, 0); +} + +void +tracev(const char *fmt, va_list va) +{ + va_list ap; + char *s; + + if (debug != 1 || !tracetfile) + return; + va_copy(ap, va); + (void) vasprintf(&s, fmt, ap); + va_end(ap); + + trace_puts(s, tracetfile); + free(s); + if (tracetfile->llen == 0) + trace_flush(tracetfile, 0); +} + + +void +trputs(const char *s) +{ + if (debug != 1 || !tracetfile) + return; + trace_puts(s, tracetfile); +} + +void +trputc(int c) +{ + if (debug != 1 || !tracetfile) + return; + trace_putc(c, tracetfile); +} + +void +showtree(union node *n) +{ + TFILE *fp; + + if ((fp = tracetfile) == NULL) + return; + + trace_puts("showtree(", fp); + if (n == NULL) + trace_puts("NULL", fp); + else if (n == NEOF) + trace_puts("NEOF", fp); + else + trace("%p", n); + trace_puts(") called\n", fp); + if (n != NULL && n != NEOF) + shtree(n, 1, 1, 1, fp); +} + +void +trargs(char **ap) +{ + if (debug != 1 || !tracetfile) + return; + while (*ap) { + trstring(*ap++); + if (*ap) + trace_putc(' ', tracetfile); + } + trace_putc('\n', tracetfile); +} + +void +trargstr(union node *n) +{ + sharg(n, tracetfile); +} + + +/* + * Beyond here we just have the implementation of all of that + */ + + +inline static int +trlinelen(TFILE * fp) +{ + return fp->llen; +} + +static void +shtree(union node *n, int ind, int ilvl, int nl, TFILE *fp) +{ + struct nodelist *lp; + const char *s; + + if (n == NULL) { + if (nl) + trace_putc('\n', fp); + return; + } + + indent(ind, fp); + switch (n->type) { + case NSEMI: + s = NULL; + goto binop; + case NAND: + s = " && "; + goto binop; + case NOR: + s = " || "; +binop: + shtree(n->nbinary.ch1, 0, ilvl, 0, fp); + if (s != NULL) + trace_puts(s, fp); + if (trlinelen(fp) >= TR_STD_WIDTH) { + trace_putc('\n', fp); + indent(ind < 0 ? 2 : ind + 1, fp); + } else if (s == NULL) { + if (fp->lastc != '&') + trace_puts("; ", fp); + else + trace_putc(' ', fp); + } + shtree(n->nbinary.ch2, 0, ilvl, nl, fp); + break; + case NCMD: + shcmd(n, fp); + if (n->ncmd.backgnd) + trace_puts(" &", fp); + if (nl && trlinelen(fp) > 0) + trace_putc('\n', fp); + break; + case NPIPE: + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + shtree(lp->n, 0, ilvl, 0, fp); + if (lp->next) { + trace_puts(" |", fp); + if (trlinelen(fp) >= TR_STD_WIDTH) { + trace_putc('\n', fp); + indent((ind < 0 ? ilvl : ind) + 1, fp); + } else + trace_putc(' ', fp); + } + } + if (n->npipe.backgnd) + trace_puts(" &", fp); + if (nl || trlinelen(fp) >= TR_STD_WIDTH) + trace_putc('\n', fp); + break; + case NBACKGND: + case NSUBSHELL: + shsubsh(n, fp); + if (n->type == NBACKGND) + trace_puts(" &", fp); + if (nl && trlinelen(fp) > 0) + trace_putc('\n', fp); + break; + case NDEFUN: + trace_puts(n->narg.text, fp); + trace_puts("() {\n", fp); + indent(ind, fp); + shtree(n->narg.next, (ind < 0 ? ilvl : ind) + 1, ilvl+1, 1, fp); + indent(ind, fp); + trace_puts("}\n", fp); + break; + case NDNOT: + trace_puts("! ", fp); + /* FALLTHROUGH */ + case NNOT: + trace_puts("! ", fp); + shtree(n->nnot.com, -1, ilvl, nl, fp); + break; + case NREDIR: + shtree(n->nredir.n, -1, ilvl, 0, fp); + shredir(n->nredir.redirect, fp, n->nredir.n == NULL); + if (nl) + trace_putc('\n', fp); + break; + + case NIF: + itsif: + trace_puts("if ", fp); + shtree(n->nif.test, -1, ilvl, 0, fp); + if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) { + if (fp->lastc != '&') + trace_puts(" ;", fp); + } else + indent(ilvl, fp); + trace_puts(" then ", fp); + if (nl || trlinelen(fp) > TR_STD_WIDTH - 24) + indent(ilvl+1, fp); + shtree(n->nif.ifpart, -1, ilvl + 1, 0, fp); + if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) { + if (fp->lastc != '&') + trace_puts(" ;", fp); + } else + indent(ilvl, fp); + if (n->nif.elsepart && n->nif.elsepart->type == NIF) { + if (nl || trlinelen(fp) > TR_STD_WIDTH - 24) + indent(ilvl, fp); + n = n->nif.elsepart; + trace_puts(" el", fp); + goto itsif; + } + if (n->nif.elsepart) { + if (nl || trlinelen(fp) > TR_STD_WIDTH - 24) + indent(ilvl+1, fp); + trace_puts(" else ", fp); + shtree(n->nif.elsepart, -1, ilvl + 1, 0, fp); + if (fp->lastc != '&') + trace_puts(" ;", fp); + } + trace_puts(" fi", fp); + if (nl) + trace_putc('\n', fp); + break; + + case NWHILE: + trace_puts("while ", fp); + goto aloop; + case NUNTIL: + trace_puts("until ", fp); + aloop: + shtree(n->nbinary.ch1, -1, ilvl, 0, fp); + if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) { + if (fp->lastc != '&') + trace_puts(" ;", fp); + } else + trace_putc('\n', fp); + trace_puts(" do ", fp); + shtree(n->nbinary.ch1, -1, ilvl + 1, 1, fp); + trace_puts(" done ", fp); + if (nl) + trace_putc('\n', fp); + break; + + case NFOR: + trace_puts("for ", fp); + trace_puts(n->nfor.var, fp); + if (n->nfor.args) { + union node *argp; + + trace_puts(" in ", fp); + for (argp = n->nfor.args; argp; argp=argp->narg.next) { + sharg(argp, fp); + trace_putc(' ', fp); + } + if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) { + if (fp->lastc != '&') + trace_putc(';', fp); + } else + trace_putc('\n', fp); + } + trace_puts(" do ", fp); + shtree(n->nfor.body, -1, ilvl + 1, 0, fp); + if (fp->lastc != '&') + trace_putc(';', fp); + trace_puts(" done", fp); + if (nl) + trace_putc('\n', fp); + break; + + case NCASE: + trace_puts("case ", fp); + sharg(n->ncase.expr, fp); + trace_puts(" in", fp); + if (nl) + trace_putc('\n', fp); + { + union node *cp; + + for (cp = n->ncase.cases ; cp ; cp = cp->nclist.next) { + union node *patp; + + if (nl || trlinelen(fp) > TR_STD_WIDTH - 16) + indent(ilvl, fp); + else + trace_putc(' ', fp); + trace_putc('(', fp); + patp = cp->nclist.pattern; + while (patp != NULL) { + trace_putc(' ', fp); + sharg(patp, fp); + trace_putc(' ', fp); + if ((patp = patp->narg.next) != NULL) + trace_putc('|', fp); + } + trace_putc(')', fp); + if (nl) + indent(ilvl + 1, fp); + else + trace_putc(' ', fp); + shtree(cp->nclist.body, -1, ilvl+2, 0, fp); + if (cp->type == NCLISTCONT) + trace_puts(" ;&", fp); + else + trace_puts(" ;;", fp); + if (nl) + trace_putc('\n', fp); + } + } + if (nl) { + trace_putc('\n', fp); + indent(ind, fp); + } else + trace_putc(' ', fp); + trace_puts("esac", fp); + if (nl) + trace_putc('\n', fp); + break; + + default: { + char *str; + + asprintf(&str, "<node type %d [%s]>", n->type, + NODETYPENAME(n->type)); + trace_puts(str, fp); + free(str); + if (nl) + trace_putc('\n', fp); + } + break; + } +} + + +static void +shcmd(union node *cmd, TFILE *fp) +{ + union node *np; + int first; + + first = 1; + for (np = cmd->ncmd.args ; np ; np = np->narg.next) { + if (! first) + trace_putc(' ', fp); + sharg(np, fp); + first = 0; + } + shredir(cmd->ncmd.redirect, fp, first); +} + +static void +shsubsh(union node *cmd, TFILE *fp) +{ + trace_puts(" ( ", fp); + shtree(cmd->nredir.n, -1, 3, 0, fp); + trace_puts(" ) ", fp); + shredir(cmd->ncmd.redirect, fp, 1); +} + +static void +shredir(union node *np, TFILE *fp, int first) +{ + const char *s; + int dftfd; + char buf[106]; + + for ( ; np ; np = np->nfile.next) { + if (! first) + trace_putc(' ', fp); + switch (np->nfile.type) { + case NTO: s = ">"; dftfd = 1; break; + case NCLOBBER: s = ">|"; dftfd = 1; break; + case NAPPEND: s = ">>"; dftfd = 1; break; + case NTOFD: s = ">&"; dftfd = 1; break; + case NFROM: s = "<"; dftfd = 0; break; + case NFROMFD: s = "<&"; dftfd = 0; break; + case NFROMTO: s = "<>"; dftfd = 0; break; + case NXHERE: /* FALLTHROUGH */ + case NHERE: s = "<<"; dftfd = 0; break; + default: s = "*error*"; dftfd = 0; break; + } + if (np->nfile.fd != dftfd) { + sprintf(buf, "%d", np->nfile.fd); + trace_puts(buf, fp); + } + trace_puts(s, fp); + if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) { + if (np->ndup.vname) + sharg(np->ndup.vname, fp); + else { + if (np->ndup.dupfd < 0) + trace_puts("-", fp); + else { + sprintf(buf, "%d", np->ndup.dupfd); + trace_puts(buf, fp); + } + } + } else + if (np->nfile.type == NHERE || np->nfile.type == NXHERE) { + if (np->nfile.type == NHERE) + trace_putc('\\', fp); + trace_puts("!!!\n", fp); + s = np->nhere.doc->narg.text; + if (strlen(s) > 100) { + memmove(buf, s, 100); + buf[100] = '\0'; + strcat(buf, " ...\n"); + s = buf; + } + trace_puts(s, fp); + trace_puts("!!! ", fp); + } else { + sharg(np->nfile.fname, fp); + } + first = 0; + } +} + +static void +sharg(union node *arg, TFILE *fp) +{ + char *p, *s; + struct nodelist *bqlist; + int subtype = 0; + int quoted = 0; + + if (arg->type != NARG) { + asprintf(&s, "<node type %d> ! NARG\n", arg->type); + trace_puts(s, fp); + abort(); /* no need to free s, better not to */ + } + + bqlist = arg->narg.backquote; + for (p = arg->narg.text ; *p ; p++) { + switch (*p) { + case CTLESC: + trace_putc('\\', fp); + trace_putc(*++p, fp); + break; + + case CTLNONL: + trace_putc('\\', fp); + trace_putc('\n', fp); + break; + + case CTLVAR: + subtype = *++p; + if (!quoted != !(subtype & VSQUOTE)) + trace_putc('"', fp); + trace_putc('$', fp); + trace_putc('{', fp); /*}*/ + if ((subtype & VSTYPE) == VSLENGTH) + trace_putc('#', fp); + if (subtype & VSLINENO) + trace_puts("LINENO=", fp); + + while (*++p != '=') + trace_putc(*p, fp); + + if (subtype & VSNUL) + trace_putc(':', fp); + + switch (subtype & VSTYPE) { + case VSNORMAL: + /* { */ + trace_putc('}', fp); + if (!quoted != !(subtype & VSQUOTE)) + trace_putc('"', fp); + break; + case VSMINUS: + trace_putc('-', fp); + break; + case VSPLUS: + trace_putc('+', fp); + break; + case VSQUESTION: + trace_putc('?', fp); + break; + case VSASSIGN: + trace_putc('=', fp); + break; + case VSTRIMLEFTMAX: + trace_putc('#', fp); + /* FALLTHROUGH */ + case VSTRIMLEFT: + trace_putc('#', fp); + break; + case VSTRIMRIGHTMAX: + trace_putc('%', fp); + /* FALLTHROUGH */ + case VSTRIMRIGHT: + trace_putc('%', fp); + break; + case VSLENGTH: + break; + default: { + char str[32]; + + snprintf(str, sizeof str, + "<subtype %d>", subtype); + trace_puts(str, fp); + } + break; + } + break; + case CTLENDVAR: + /* { */ + trace_putc('}', fp); + if (!quoted != !(subtype & VSQUOTE)) + trace_putc('"', fp); + subtype = 0; + break; + + case CTLBACKQ|CTLQUOTE: + if (!quoted) + trace_putc('"', fp); + /* FALLTHRU */ + case CTLBACKQ: + trace_putc('$', fp); + trace_putc('(', fp); + if (bqlist) { + shtree(bqlist->n, -1, 3, 0, fp); + bqlist = bqlist->next; + } else + trace_puts("???", fp); + trace_putc(')', fp); + if (!quoted && *p == (CTLBACKQ|CTLQUOTE)) + trace_putc('"', fp); + break; + + case CTLQUOTEMARK: + if (subtype != 0 || !quoted) { + trace_putc('"', fp); + quoted++; + } + break; + case CTLQUOTEEND: + trace_putc('"', fp); + quoted--; + break; + case CTLARI: + if (*p == ' ') + p++; + trace_puts("$(( ", fp); + break; + case CTLENDARI: + trace_puts(" ))", fp); + break; + + default: + if (*p == '$') + trace_putc('\\', fp); + trace_putc(*p, fp); + break; + } + } + if (quoted) + trace_putc('"', fp); +} + + +static void +indent(int amount, TFILE *fp) +{ + int i; + + if (amount <= 0) + return; + + amount <<= 2; /* indent slots -> chars */ + + i = trlinelen(fp); + fp->supr = SUP_NL; + if (i > amount) { + trace_putc('\n', fp); + i = 0; + } + fp->supr = 0; + for (; i < amount - 7 ; i++) { + trace_putc('\t', fp); + i |= 7; + } + while (i < amount) { + trace_putc(' ', fp); + i++; + } + fp->supr = SUP_WSP; +} + +static void +trace_putc(char c, TFILE *fp) +{ + char *p; + + if (c == '\0') + return; + if (debug == 0 || fp == NULL) + return; + + if (fp->llen == 0) { + if (fp->blen != 0) + abort(); + + if ((fp->supr & SUP_NL) && c == '\n') + return; + if ((fp->supr & (SUP_WSP|SUP_SP)) && c == ' ') + return; + if ((fp->supr & SUP_WSP) && c == '\t') + return; + + if (fp->nxtiov >= TR_IOVECS - 1) /* should be rare */ + trace_flush(fp, 0); + + p = trace_id(fp); + if (p != NULL) { + fp->lines[fp->nxtiov].iov_base = p; + fp->lines[fp->nxtiov].iov_len = strlen(p); + fp->nxtiov++; + } + } else if (fp->blen && fp->blen >= fp->lines[fp->nxtiov].iov_len) { + fp->blen = 0; + if (++fp->nxtiov >= TR_IOVECS) + trace_flush(fp, 0); + } + + if (fp->lines[fp->nxtiov].iov_len == 0) { + p = (char *)malloc(2 * TR_STD_WIDTH); + if (p == NULL) { + trace_flush(fp, 1); + debug = 0; + return; + } + *p = '\0'; + fp->lines[fp->nxtiov].iov_base = p; + fp->lines[fp->nxtiov].iov_len = 2 * TR_STD_WIDTH; + fp->blen = 0; + } + + p = (char *)fp->lines[fp->nxtiov].iov_base + fp->blen++; + *p++ = c; + *p = 0; + + if (c != ' ' && c != '\t' && c != '\n') { + fp->lastc = c; + fp->supr = 0; + } + + if (c == '\n') { + fp->lines[fp->nxtiov++].iov_len = fp->blen; + fp->blen = 0; + fp->llen = 0; + fp->supr |= SUP_NL; + return; + } + + if (c == '\t') + fp->llen |= 7; + fp->llen++; +} + +void +trace_flush(TFILE *fp, int all) +{ + int niov, i; + ssize_t written; + + niov = fp->nxtiov; + if (all && fp->blen > 0) { + fp->lines[niov].iov_len = fp->blen; + fp->blen = 0; + fp->llen = 0; + niov++; + } + if (niov == 0) + return; + if (fp->blen > 0 && --niov == 0) + return; + written = writev(fp->tfd, fp->lines, niov); + for (i = 0; i < niov; i++) { + free(fp->lines[i].iov_base); + fp->lines[i].iov_base = NULL; + fp->lines[i].iov_len = 0; + } + if (written == -1) { + if (fp->blen > 0) { + free(fp->lines[niov].iov_base); + fp->lines[niov].iov_base = NULL; + fp->lines[niov].iov_len = 0; + } + debug = 0; + fp->blen = 0; + fp->llen = 0; + return; + } + if (fp->blen > 0) { + fp->lines[0].iov_base = fp->lines[niov].iov_base; + fp->lines[0].iov_len = fp->lines[niov].iov_len; + fp->lines[niov].iov_base = NULL; + fp->lines[niov].iov_len = 0; + } + fp->nxtiov = 0; +} + +void +trace_puts(const char *s, TFILE *fp) +{ + char c; + + while ((c = *s++) != '\0') + trace_putc(c, fp); +} + +inline static char * +trace_id(TFILE *tf) +{ + int i; + char indent[16]; + char *p; + int lno; + char c; + + if (DFlags & DBG_NEST) { + if ((unsigned)ShNest >= sizeof indent - 1) { + (void) snprintf(indent, sizeof indent, + "### %*d ###", (int)(sizeof indent) - 9, ShNest); + p = strchr(indent, '\0'); + } else { + p = indent; + for (i = 0; i < 6; i++) + *p++ = (i < ShNest) ? '#' : ' '; + while (i++ < ShNest && p < &indent[sizeof indent - 1]) + *p++ = '#'; + *p = '\0'; + } + } else + indent[0] = '\0'; + + /* + * If we are in the parser, then plinno is the current line + * number being processed (parser line no). + * If we are elsewhere, then line_number gives the source + * line of whatever we are currently doing (close enough.) + */ + if (parsing) + lno = plinno; + else + lno = line_number; + + c = ((i = getpid()) == tf->pid) ? ':' : '='; + + if (DFlags & DBG_PID) { + if (DFlags & DBG_LINE) + (void) asprintf(&p, "%5d%c%s\t%4d%c@\t", i, c, + indent, lno, parsing?'-':'+'); + else + (void) asprintf(&p, "%5d%c%s\t", i, c, indent); + return p; + } else if (DFlags & DBG_NEST) { + if (DFlags & DBG_LINE) + (void) asprintf(&p, "%c%s\t%4d%c@\t", c, indent, lno, + parsing?'-':'+'); + else + (void) asprintf(&p, "%c%s\t", c, indent); + return p; + } else if (DFlags & DBG_LINE) { + (void) asprintf(&p, "%c%4d%c@\t", c, lno, parsing?'-':'+'); + return p; + } + return NULL; +} + +/* + * Used only from trargs(), which itself is used only to print + * arg lists (argv[]) either that passed into this shell, or + * the arg list about to be given to some other command (incl + * builtin, and function) as their argv[]. If any of the CTL* + * chars seem to appear, they really should be just treated as data, + * not special... But this is just debug, so, who cares! + */ +static void +trstring(const char *s) +{ + const char *p; + char c; + TFILE *fp; + + if (debug != 1 || !tracetfile) + return; + fp = tracetfile; + trace_putc('"', fp); + 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: trace_putc('\\', fp); + trace_putc(c, fp); + break; + default: + if (*p >= ' ' && *p <= '~') + trace_putc(*p, fp); + else { + trace_putc('\\', fp); + trace_putc(*p >> 6 & 03, fp); + trace_putc(*p >> 3 & 07, fp); + trace_putc(*p & 07, fp); + } + break; + } + } + trace_putc('"', fp); +} + +/* + * deal with the user "accidentally" picking our fd to use. + */ +static void +trace_fd_swap(int from, int to) +{ + if (tracetfile == NULL || from == to) + return; + + tracetfile->tfd = to; + + /* + * This is just so histedit has a stdio FILE* to use. + */ + if (tracefile) + fclose(tracefile); + tracefile = fdopen(to, "a"); + if (tracefile) + setlinebuf(tracefile); +} + + +static struct debug_flag { + char label; + uint64_t flag; +} debug_flags[] = { + { 'a', DBG_ARITH }, /* arithmetic ( $(( )) ) */ + { 'c', DBG_CMDS }, /* command searching, ... */ + { 'e', DBG_EVAL }, /* evaluation of the parse tree */ + { 'f', DBG_REDIR }, /* file descriptors & redirections */ + { 'g', DBG_MATCH }, /* pattern matching (glob) */ + { 'h', DBG_HISTORY }, /* history & cmd line editing */ + { 'i', DBG_INPUT }, /* shell input routines */ + { 'j', DBG_JOBS }, /* job control, structures */ + { 'l', DBG_LEXER }, /* lexical analysis */ + { 'm', DBG_MEM }, /* memory management */ + { 'o', DBG_OUTPUT }, /* output routines */ + { 'p', DBG_PROCS }, /* process management, fork, ... */ + { 'r', DBG_PARSE }, /* parser, lexer, ... tree building */ + { 's', DBG_SIG }, /* signals and everything related */ + { 't', DBG_TRAP }, /* traps & signals */ + { 'v', DBG_VARS }, /* variables and parameters */ + { 'w', DBG_WAIT }, /* waits for processes to finish */ + { 'x', DBG_EXPAND }, /* word expansion ${} $() $(( )) */ + { 'z', DBG_ERRS }, /* error control, jumps, cleanup */ + + { '0', DBG_U0 }, /* ad-hoc temp debug flag #0 */ + { '1', DBG_U1 }, /* ad-hoc temp debug flag #1 */ + { '2', DBG_U2 }, /* ad-hoc temp debug flag #2 */ + { '3', DBG_U3 }, /* ad-hoc temp debug flag #3 */ + + { '@', DBG_LINE }, /* prefix trace lines with line# */ + { '$', DBG_PID }, /* prefix trace lines with sh pid */ + { '^', DBG_NEST }, /* show shell nesting level */ + + /* alpha options only - but not DBG_LEXER */ + { '_', DBG_PARSE | DBG_EVAL | DBG_EXPAND | DBG_JOBS | DBG_SIG | + DBG_PROCS | DBG_REDIR | DBG_CMDS | DBG_ERRS | + DBG_WAIT | DBG_TRAP | DBG_VARS | DBG_MEM | DBG_MATCH | + DBG_INPUT | DBG_OUTPUT | DBG_ARITH | DBG_HISTORY }, + + /* { '*', DBG_ALLVERBOSE }, is handled in the code */ + + { '#', DBG_U0 | DBG_U1 | DBG_U2 | DBG_U3 }, + + { 0, 0 } +}; + +void +set_debug(const char * flags, int on) +{ + char f; + struct debug_flag *df; + int verbose; + + while ((f = *flags++) != '\0') { + verbose = 0; + if (is_upper(f)) { + verbose = 1; + f += 'a' - 'A'; + } + if (f == '*') + f = '_', verbose = 1; + if (f == '+') { + if (*flags == '+') + flags++, verbose=1; + } + + /* + * Note: turning on any debug option also enables DBG_ALWAYS + * turning on any verbose option also enables DBG_VERBOSE + * Once enabled, those flags cannot be disabled. + * (tracing can still be turned off with "set +o debug") + */ + for (df = debug_flags; df->label != '\0'; df++) { + if (f == '+' || df->label == f) { + if (on) { + DFlags |= DBG_ALWAYS | df->flag; + if (verbose) + DFlags |= DBG_VERBOSE | + (df->flag << DBG_VBOSE_SHIFT); + } else { + DFlags &= ~(df->flag<<DBG_VBOSE_SHIFT); + if (!verbose) + DFlags &= ~df->flag; + } + } + } + } +} + + +int +debugcmd(int argc, char **argv) +{ + if (argc == 1) { + struct debug_flag *df; + + out1fmt("Debug: %sabled. Flags: ", debug ? "en" : "dis"); + for (df = debug_flags; df->label != '\0'; df++) { + if (df->flag & (df->flag - 1)) + continue; + if (is_alpha(df->label) && + (df->flag << DBG_VBOSE_SHIFT) & DFlags) + out1c(df->label - ('a' - 'A')); + else if (df->flag & DFlags) + out1c(df->label); + } + out1c('\n'); + return 0; + } + + while (*++argv) { + if (**argv == '-') + set_debug(*argv + 1, 1); + else if (**argv == '+') + set_debug(*argv + 1, 0); + else + return 1; + } + return 0; +} + +#endif /* DEBUG */ diff --git a/bin/sh/show.h b/bin/sh/show.h new file mode 100644 index 0000000..2c48873 --- /dev/null +++ b/bin/sh/show.h @@ -0,0 +1,46 @@ +/* $NetBSD: show.h,v 1.11 2017/06/30 23:00:40 kre Exp $ */ + +/*- + * 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 + */ + +#include <stdarg.h> + +#ifdef DEBUG +union node; +void showtree(union node *); +void trace(const char *, ...); +void tracev(const char *, va_list); +void trargs(char **); +void trargstr(union node *); +void trputc(int); +void trputs(const char *); +void opentrace(void); +#endif diff --git a/bin/sh/syntax.c b/bin/sh/syntax.c new file mode 100644 index 0000000..7b460d4 --- /dev/null +++ b/bin/sh/syntax.c @@ -0,0 +1,111 @@ +/* $NetBSD: syntax.c,v 1.7 2018/12/03 06:40:26 kre Exp $ */ + +#include <sys/cdefs.h> +__RCSID("$NetBSD: syntax.c,v 1.7 2018/12/03 06:40:26 kre Exp $"); + +#include <limits.h> +#include "shell.h" +#include "syntax.h" +#include "parser.h" + +#if CWORD != 0 +#error initialisation assumes 'CWORD' is zero +#endif + +#define ndx(ch) (ch + 2 - CHAR_MIN) +#define set(ch, val) [ndx(ch)] = val, +#define set_range(s, e, val) [ndx(s) ... ndx(e)] = val, + +/* syntax table used when not in quotes */ +const char basesyntax[258] = { CFAKE, CEOF, + set_range(CTL_FIRST, CTL_LAST, CCTL) + set('\n', CNL) + set('\\', CBACK) + set('\'', CSQUOTE) + set('"', CDQUOTE) + set('`', CBQUOTE) + set('$', CVAR) + set('}', CENDVAR) + set('<', CSPCL) + set('>', CSPCL) + set('(', CSPCL) + set(')', CSPCL) + set(';', CSPCL) + set('&', CSPCL) + set('|', CSPCL) + set(' ', CSPCL) + set('\t', CSPCL) +}; + +/* syntax table used when in double quotes */ +const char dqsyntax[258] = { CFAKE, CEOF, + set_range(CTL_FIRST, CTL_LAST, CCTL) + set('\n', CNL) + set('\\', CBACK) + set('"', CDQUOTE) + set('`', CBQUOTE) + set('$', CVAR) + set('}', CENDVAR) + /* ':/' for tilde expansion, '-]' for [a\-x] pattern ranges */ + set('!', CCTL) + set('*', CCTL) + set('?', CCTL) + set('[', CCTL) + set('=', CCTL) + set('~', CCTL) + set(':', CCTL) + set('/', CCTL) + set('-', CCTL) + set(']', CCTL) +}; + +/* syntax table used when in single quotes */ +const char sqsyntax[258] = { CFAKE, CEOF, + set_range(CTL_FIRST, CTL_LAST, CCTL) + set('\n', CNL) + set('\'', CSQUOTE) + set('\\', CSBACK) + /* ':/' for tilde expansion, '-]' for [a\-x] pattern ranges */ + set('!', CCTL) + set('*', CCTL) + set('?', CCTL) + set('[', CCTL) + set('=', CCTL) + set('~', CCTL) + set(':', CCTL) + set('/', CCTL) + set('-', CCTL) + set(']', CCTL) +}; + +/* syntax table used when in arithmetic */ +const char arisyntax[258] = { CFAKE, CEOF, + set_range(CTL_FIRST, CTL_LAST, CCTL) + set('\n', CNL) + set('\\', CBACK) + set('`', CBQUOTE) + set('\'', CSQUOTE) + set('"', CDQUOTE) + set('$', CVAR) + set('}', CENDVAR) + set('(', CLP) + set(')', CRP) +}; + +/* character classification table */ +const char is_type[258] = { 0, 0, + set_range('0', '9', ISDIGIT) + set_range('a', 'z', ISLOWER) + set_range('A', 'Z', ISUPPER) + set('_', ISUNDER) + set('#', ISSPECL) + set('?', ISSPECL) + set('$', ISSPECL) + set('!', ISSPECL) + set('-', ISSPECL) + set('*', ISSPECL) + set('@', ISSPECL) + set(' ', ISSPACE) + set('\t', ISSPACE) + set('\n', ISSPACE) +}; diff --git a/bin/sh/syntax.h b/bin/sh/syntax.h new file mode 100644 index 0000000..ad064b8 --- /dev/null +++ b/bin/sh/syntax.h @@ -0,0 +1,98 @@ +/* $NetBSD: syntax.h,v 1.11 2018/12/03 06:40:26 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#include <ctype.h> + +/* Syntax classes */ +#define CWORD 0 /* character is nothing special */ +#define CNL 1 /* newline character */ +#define CBACK 2 /* a backslash character */ +#define CSQUOTE 3 /* single quote */ +#define CDQUOTE 4 /* double quote */ +#define CBQUOTE 5 /* backwards single quote */ +#define CVAR 6 /* a dollar sign */ +#define CENDVAR 7 /* a '}' character */ +#define CLP 8 /* a left paren in arithmetic */ +#define CRP 9 /* a right paren in arithmetic */ +#define CEOF 10 /* end of file */ +#define CSPCL 11 /* these terminate a word */ +#define CCTL 12 /* like CWORD, except it must be escaped */ +#define CSBACK 13 /* a backslash in a single quote syntax */ +#define CFAKE 14 /* a delimiter that does not exist */ + /* + * note CSBACK == (CCTL|1) + * the code does not rely upon that, but keeping it allows a + * smart enough compiler to optimise some tests + */ + +/* Syntax classes for is_ functions */ +#define ISDIGIT 01 /* a digit */ +#define ISUPPER 02 /* an upper case letter */ +#define ISLOWER 04 /* a lower case letter */ +#define ISUNDER 010 /* an underscore */ +#define ISSPECL 020 /* the name of a special parameter */ +#define ISSPACE 040 /* a white space character */ + +#define PEOF (CHAR_MIN - 1) +#define PFAKE (CHAR_MIN - 2) +#define SYNBASE (-PFAKE) + + +#define BASESYNTAX (basesyntax + SYNBASE) +#define DQSYNTAX (dqsyntax + SYNBASE) +#define SQSYNTAX (sqsyntax + SYNBASE) +#define ARISYNTAX (arisyntax + SYNBASE) + +/* These defines assume that the digits are contiguous (which is guaranteed) */ +#define is_digit(c) ((unsigned)((c) - '0') <= 9) +#define sh_ctype(c) (is_type+SYNBASE)[(int)(c)] +#define is_upper(c) (sh_ctype(c) & ISUPPER) +#define is_lower(c) (sh_ctype(c) & ISLOWER) +#define is_alpha(c) (sh_ctype(c) & (ISUPPER|ISLOWER)) +#define is_name(c) (sh_ctype(c) & (ISUPPER|ISLOWER|ISUNDER)) +#define is_in_name(c) (sh_ctype(c) & (ISUPPER|ISLOWER|ISUNDER|ISDIGIT)) +#define is_special(c) (sh_ctype(c) & (ISSPECL|ISDIGIT)) +#define is_space(c) (sh_ctype(c) & ISSPACE) +#define digit_val(c) ((c) - '0') + +/* true if the arg char needs CTLESC to protect it */ +#define NEEDESC(c) (SQSYNTAX[(int)(c)] == CCTL || \ + SQSYNTAX[(int)(c)] == CSBACK) + +extern const char basesyntax[]; +extern const char dqsyntax[]; +extern const char sqsyntax[]; +extern const char arisyntax[]; +extern const char is_type[]; diff --git a/bin/sh/trap.c b/bin/sh/trap.c new file mode 100644 index 0000000..cb641fd --- /dev/null +++ b/bin/sh/trap.c @@ -0,0 +1,837 @@ +/* $NetBSD: trap.c,v 1.51 2019/01/18 06:28:09 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)trap.c 8.5 (Berkeley) 6/5/95"; +#else +__RCSID("$NetBSD: trap.c,v 1.51 2019/01/18 06:28:09 kre Exp $"); +#endif +#endif /* not lint */ + +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <termios.h> + +#undef CEOF /* from <termios.h> but concflicts with sh use */ + +#include <sys/ioctl.h> +#include <sys/resource.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 "builtins.h" +#include "syntax.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "trap.h" +#include "mystring.h" +#include "var.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 permenantly */ +#define S_RESET 5 /* temporary - to reset a hard ignored sig */ + + +MKINIT char sigmode[NSIG]; /* current value of signal */ +static volatile sig_atomic_t gotsig[NSIG];/* indicates specified signal received */ +volatile sig_atomic_t pendingsigs; /* indicates some signal received */ + +int traps_invalid; /* in a subshell, but trap[] not yet cleared */ +static char * volatile trap[NSIG]; /* trap handler commands */ +static int in_dotrap; +static int last_trapsig; + +static int exiting; /* exitshell() has been done */ +static int exiting_status; /* the status to use for exit() */ + +static int getsigaction(int, sig_t *); +STATIC const char *trap_signame(int); +void printsignals(struct output *, int); + +/* + * return the signal number described by `p' (as a number or a name) + * or -1 if it isn't one + */ + +static int +signame_to_signum(const char *p) +{ + int i; + + if (is_number(p)) + return number(p); + + if (strcasecmp(p, "exit") == 0 ) + return 0; + + i = signalnumber(p); + if (i == 0) + i = -1; + return i; +} + +/* + * return the name of a signal used by the "trap" command + */ +STATIC const char * +trap_signame(int signo) +{ + static char nbuf[12]; + const char *p; + + if (signo == 0) + return "EXIT"; + p = signalname(signo); + if (p != NULL) + return p; + (void)snprintf(nbuf, sizeof nbuf, "%d", signo); + return nbuf; +} + +#ifdef SMALL +/* + * Print a list of valid signal names + */ +void +printsignals(struct output *out, int len) +{ + int n; + + if (len != 0) + outc(' ', out); + for (n = 1; n < NSIG; n++) { + outfmt(out, "%s", trap_signame(n)); + if ((n == NSIG/2) || n == (NSIG - 1)) + outstr("\n", out); + else + outc(' ', out); + } +} +#else /* !SMALL */ +/* + * Print the names of all the signals (neatly) to fp + * "len" gives the number of chars already printed to + * the current output line (in kill.c, always 0) + */ +void +printsignals(struct output *out, int len) +{ + int sig; + int nl, pad; + const char *name; + int termwidth = 80; + + if ((name = bltinlookup("COLUMNS", 1)) != NULL) + termwidth = (int)strtol(name, NULL, 10); + else if (isatty(1)) { + struct winsize win; + + if (ioctl(1, TIOCGWINSZ, &win) == 0 && win.ws_col > 0) + termwidth = win.ws_col; + } + + if (posix) + pad = 1; + else + pad = (len | 7) + 1 - len; + + for (sig = 0; (sig = signalnext(sig)) != 0; ) { + name = signalname(sig); + if (name == NULL) + continue; + + nl = strlen(name); + + if (len > 0 && nl + len + pad >= termwidth) { + outc('\n', out); + len = 0; + pad = 0; + } else if (pad > 0 && len != 0) + outfmt(out, "%*s", pad, ""); + else + pad = 0; + + len += nl + pad; + if (!posix) + pad = (nl | 7) + 1 - nl; + else + pad = 1; + + outstr(name, out); + } + if (len != 0) + outc('\n', out); +} +#endif /* SMALL */ + +/* + * The trap builtin. + */ + +int +trapcmd(int argc, char **argv) +{ + char *action; + char **ap; + int signo; + int errs = 0; + int printonly = 0; + + ap = argv + 1; + + CTRACE(DBG_TRAP, ("trapcmd: ")); + if (argc == 2 && strcmp(*ap, "-l") == 0) { + CTRACE(DBG_TRAP, ("-l\n")); + out1str("EXIT"); + printsignals(out1, 4); + return 0; + } + if (argc == 2 && strcmp(*ap, "-") == 0) { + CTRACE(DBG_TRAP, ("-\n")); + for (signo = 0; signo < NSIG; signo++) { + if (trap[signo] == NULL) + continue; + INTOFF; + ckfree(trap[signo]); + trap[signo] = NULL; + if (signo != 0) + setsignal(signo, 0); + INTON; + } + traps_invalid = 0; + return 0; + } + if (argc >= 2 && strcmp(*ap, "-p") == 0) { + CTRACE(DBG_TRAP, ("-p ")); + printonly = 1; + ap++; + argc--; + } + + if (argc > 1 && strcmp(*ap, "--") == 0) { + argc--; + ap++; + } + + if (argc <= 1) { + int count; + + CTRACE(DBG_TRAP, ("*all*\n")); + if (printonly) { + for (count = 0, signo = 0 ; signo < NSIG ; signo++) + if (trap[signo] == NULL) { + if (count == 0) + out1str("trap -- -"); + out1fmt(" %s", trap_signame(signo)); + /* oh! unlucky 13 */ + if (++count >= 13) { + out1str("\n"); + count = 0; + } + } + if (count) + out1str("\n"); + } + + for (count = 0, signo = 0 ; signo < NSIG ; signo++) + if (trap[signo] != NULL && trap[signo][0] == '\0') { + if (count == 0) + out1str("trap -- ''"); + out1fmt(" %s", trap_signame(signo)); + /* + * the prefix is 10 bytes, with 4 byte + * signal names (common) we have room in + * the 70 bytes left on a normal line for + * 70/(4+1) signals, that's 14, but to + * allow for the occasional longer sig name + * we output one less... + */ + if (++count >= 13) { + out1str("\n"); + count = 0; + } + } + if (count) + out1str("\n"); + + for (signo = 0 ; signo < NSIG ; signo++) + if (trap[signo] != NULL && trap[signo][0] != '\0') { + out1str("trap -- "); + print_quoted(trap[signo]); + out1fmt(" %s\n", trap_signame(signo)); + } + + return 0; + } + CTRACE(DBG_TRAP, ("\n")); + + action = NULL; + + if (!printonly && traps_invalid) + free_traps(); + + if (!printonly && !is_number(*ap)) { + if ((*ap)[0] == '-' && (*ap)[1] == '\0') + ap++; /* reset to default */ + else + action = *ap++; /* can be '' for "ignore" */ + argc--; + } + + if (argc < 2) { /* there must be at least 1 condition */ + out2str("Usage: trap [-l]\n" + " trap -p [condition ...]\n" + " trap action condition ...\n" + " trap N condition ...\n"); + return 2; + } + + + while (*ap) { + signo = signame_to_signum(*ap); + + if (signo < 0 || signo >= NSIG) { + /* This is not a fatal error, so sayeth posix */ + outfmt(out2, "trap: '%s' bad condition\n", *ap); + errs = 1; + ap++; + continue; + } + ap++; + + if (printonly) { + out1str("trap -- "); + if (trap[signo] == NULL) + out1str("-"); + else + print_quoted(trap[signo]); + out1fmt(" %s\n", trap_signame(signo)); + continue; + } + + INTOFF; + if (action) + action = savestr(action); + + VTRACE(DBG_TRAP, ("trap for %d from %s%s%s to %s%s%s\n", signo, + trap[signo] ? "'" : "", trap[signo] ? trap[signo] : "-", + trap[signo] ? "'" : "", action ? "'" : "", + action ? action : "-", action ? "'" : "")); + + if (trap[signo]) + ckfree(trap[signo]); + + trap[signo] = action; + + if (signo != 0) + setsignal(signo, 0); + INTON; + } + return errs; +} + + + +/* + * Clear traps on a fork or vfork. + * Takes one arg vfork, to tell it to not be destructive of + * the parents variables. + */ +void +clear_traps(int vforked) +{ + char * volatile *tp; + + VTRACE(DBG_TRAP, ("clear_traps(%d)\n", vforked)); + if (!vforked) + traps_invalid = 1; + + for (tp = &trap[1] ; tp < &trap[NSIG] ; tp++) { + if (*tp && **tp) { /* trap not NULL or SIG_IGN */ + INTOFF; + setsignal(tp - trap, vforked == 1); + INTON; + } + } + if (vforked == 2) + free_traps(); +} + +void +free_traps(void) +{ + char * volatile *tp; + + VTRACE(DBG_TRAP, ("free_traps%s\n", traps_invalid ? "(invalid)" : "")); + INTOFF; + for (tp = trap ; tp < &trap[NSIG] ; tp++) + if (*tp && **tp) { + ckfree(*tp); + *tp = NULL; + } + traps_invalid = 0; + INTON; +} + +/* + * See if there are any defined traps + */ +int +have_traps(void) +{ + char * volatile *tp; + + if (traps_invalid) + return 0; + + for (tp = trap ; tp < &trap[NSIG] ; 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 vforked) +{ + int action; + sig_t sigact = SIG_DFL, sig; + char *t, tsig; + + if (traps_invalid || (t = trap[signo]) == NULL) + action = S_DFL; + else if (*t != '\0') + action = S_CATCH; + else + action = S_IGN; + + VTRACE(DBG_TRAP, ("setsignal(%d%s) -> %d", signo, + vforked ? ", VF" : "", action)); + if (rootshell && !vforked && action == S_DFL) { + switch (signo) { + case SIGINT: + if (iflag || minusc || sflag == 0) + action = S_CATCH; + break; + case SIGQUIT: +#ifdef DEBUG + if (debug) + break; +#endif + /* FALLTHROUGH */ + case SIGTERM: + if (rootshell && iflag) + action = S_IGN; + break; +#if JOBS + case SIGTSTP: + case SIGTTOU: + if (rootshell && mflag) + action = S_IGN; + break; +#endif + } + } + + /* + * Never let users futz with SIGCHLD + * instead we will give them pseudo SIGCHLD's + * when background jobs complete. + */ + if (signo == SIGCHLD) + action = S_DFL; + + VTRACE(DBG_TRAP, (" -> %d", action)); + + t = &sigmode[signo]; + tsig = *t; + if (tsig == 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. + */ + VTRACE(DBG_TRAP, (" getsigaction (%d)\n", errno)); + return; + } + VTRACE(DBG_TRAP, (" [%s]%s%s", sigact==SIG_IGN ? "IGN" : + sigact==SIG_DFL ? "DFL" : "caught", + iflag ? "i" : "", mflag ? "m" : "")); + + if (sigact == SIG_IGN) { + /* + * POSIX 3.14.13 states that non-interactive shells + * should ignore trap commands for signals that were + * ignored upon entry, and leaves the behavior + * unspecified for interactive shells. On interactive + * shells, or if job control is on, and we have a job + * control related signal, we allow the trap to work. + * + * This change allows us to be POSIX compliant, and + * at the same time override the default behavior if + * we need to by setting the interactive flag. + */ + if ((mflag && (signo == SIGTSTP || + signo == SIGTTIN || signo == SIGTTOU)) || iflag) { + tsig = S_IGN; + } else + tsig = S_HARD_IGN; + } else { + tsig = S_RESET; /* force to be set */ + } + } + VTRACE(DBG_TRAP, (" tsig=%d\n", tsig)); + + if (tsig == S_HARD_IGN || tsig == action) + return; + + switch (action) { + case S_DFL: sigact = SIG_DFL; break; + case S_CATCH: sigact = onsig; break; + case S_IGN: sigact = SIG_IGN; break; + } + + sig = signal(signo, sigact); + + if (sig != SIG_ERR) { + sigset_t ss; + + if (!vforked) + *t = action; + + if (action == S_CATCH) + (void)siginterrupt(signo, 1); + /* + * If our parent accidentally blocked signals for + * us make sure we unblock them + */ + (void)sigemptyset(&ss); + (void)sigaddset(&ss, signo); + (void)sigprocmask(SIG_UNBLOCK, &ss, NULL); + } + return; +} + +/* + * 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, int vforked) +{ + if (sigmode[signo] == 0) + setsignal(signo, vforked); + + VTRACE(DBG_TRAP, ("ignoresig(%d%s)\n", signo, vforked ? ", VF" : "")); + if (sigmode[signo] != S_IGN && sigmode[signo] != S_HARD_IGN) { + signal(signo, SIG_IGN); + if (!vforked) + sigmode[signo] = S_IGN; + } +} + +char * +child_trap(void) +{ + char * p; + + p = trap[SIGCHLD]; + + if (traps_invalid || (p != NULL && *p == '\0')) + p = NULL; + + return p; +} + + +#ifdef mkinit +INCLUDE <signal.h> +INCLUDE "trap.h" +INCLUDE "shell.h" +INCLUDE "show.h" + +SHELLPROC { + char *sm; + + INTOFF; + clear_traps(2); + for (sm = sigmode ; sm < sigmode + NSIG ; sm++) { + if (*sm == S_IGN) { + *sm = S_HARD_IGN; + VTRACE(DBG_TRAP, ("SHELLPROC: %d -> hard_ign\n", + (sm - sigmode))); + } + } + INTON; +} +#endif + + + +/* + * Signal handler. + */ + +void +onsig(int signo) +{ + CTRACE(DBG_SIG, ("Signal %d, had: pending %d, gotsig[%d]=%d\n", + signo, pendingsigs, signo, gotsig[signo])); + + /* This should not be needed. + signal(signo, onsig); + */ + + if (signo == SIGINT && (traps_invalid || trap[SIGINT] == NULL)) { + onint(); + return; + } + + /* + * if the signal will do nothing, no point reporting it + */ + if (!traps_invalid && trap[signo] != NULL && trap[signo][0] != '\0' && + signo != SIGCHLD) { + gotsig[signo] = 1; + pendingsigs++; + } +} + + + +/* + * Called to execute a trap. Perhaps we should avoid entering new trap + * handlers while we are executing a trap handler. + */ + +void +dotrap(void) +{ + int i; + char *tr; + int savestatus; + struct skipsave saveskip; + + in_dotrap++; + + CTRACE(DBG_TRAP, ("dotrap[%d]: %d pending, traps %sinvalid\n", + in_dotrap, pendingsigs, traps_invalid ? "" : "not ")); + for (;;) { + pendingsigs = 0; + for (i = 1 ; ; i++) { + if (i >= NSIG) + return; + if (gotsig[i]) + break; + } + gotsig[i] = 0; + + if (traps_invalid) + continue; + + tr = trap[i]; + + CTRACE(DBG_TRAP|DBG_SIG, ("dotrap %d: %s%s%s\n", i, + tr ? "\"" : "", tr ? tr : "NULL", tr ? "\"" : "")); + + if (tr != NULL) { + last_trapsig = i; + save_skipstate(&saveskip); + savestatus = exitstatus; + + tr = savestr(tr); /* trap code may free trap[i] */ + evalstring(tr, 0); + ckfree(tr); + + if (current_skipstate() == SKIPNONE || + saveskip.state != SKIPNONE) { + restore_skipstate(&saveskip); + exitstatus = savestatus; + } + } + } + + in_dotrap--; +} + +int +lastsig(void) +{ + int i; + + for (i = NSIG; --i > 0; ) + if (gotsig[i]) + return i; + return SIGINT; /* XXX */ +} + +/* + * Controls whether the shell is interactive or not. + */ + + +void +setinteractive(int on) +{ + static int is_interactive; + + if (on == is_interactive) + return; + setsignal(SIGINT, 0); + setsignal(SIGQUIT, 0); + setsignal(SIGTERM, 0); + is_interactive = on; +} + + + +/* + * Called to exit the shell. + */ +void +exitshell(int status) +{ + CTRACE(DBG_ERRS|DBG_PROCS|DBG_CMDS|DBG_TRAP, + ("pid %d: exitshell(%d)\n", getpid(), status)); + + exiting = 1; + exiting_status = status; + exitshell_savedstatus(); +} + +void +exitshell_savedstatus(void) +{ + struct jmploc loc; + char *p; + volatile int sig = 0; + int s; + sigset_t sigs; + + CTRACE(DBG_ERRS|DBG_PROCS|DBG_CMDS|DBG_TRAP, + ("pid %d: exitshell_savedstatus()%s $?=%d xs=%d dt=%d ts=%d\n", + getpid(), exiting ? " exiting" : "", exitstatus, + exiting_status, in_dotrap, last_trapsig)); + + if (!exiting) { + if (in_dotrap && last_trapsig) { + sig = last_trapsig; + exiting_status = sig + 128; + } else + exiting_status = exitstatus; + } + exitstatus = exiting_status; + + if (!setjmp(loc.loc)) { + handler = &loc; + + if (!traps_invalid && (p = trap[0]) != NULL && *p != '\0') { + reset_eval(); + trap[0] = NULL; + VTRACE(DBG_TRAP, ("exit trap: \"%s\"\n", p)); + evalstring(p, 0); + } + } + + INTOFF; /* we're done, no more interrupts. */ + + if (!setjmp(loc.loc)) { + handler = &loc; /* probably unnecessary */ + flushall(); +#if JOBS + setjobctl(0); +#endif + } + + if ((s = sig) != 0 && s != SIGSTOP && s != SIGTSTP && s != SIGTTIN && + s != SIGTTOU) { + struct rlimit nocore; + + /* + * if the signal is of the core dump variety, don't... + */ + nocore.rlim_cur = nocore.rlim_max = 0; + (void) setrlimit(RLIMIT_CORE, &nocore); + + signal(s, SIG_DFL); + sigemptyset(&sigs); + sigaddset(&sigs, s); + sigprocmask(SIG_UNBLOCK, &sigs, NULL); + + kill(getpid(), s); + } + _exit(exiting_status); + /* NOTREACHED */ +} diff --git a/bin/sh/trap.h b/bin/sh/trap.h new file mode 100644 index 0000000..7ea3ef1 --- /dev/null +++ b/bin/sh/trap.h @@ -0,0 +1,52 @@ +/* $NetBSD: trap.h,v 1.25 2018/12/03 10:53:29 martin Exp $ */ + +/*- + * 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 + */ + +extern volatile sig_atomic_t pendingsigs; + +extern int traps_invalid; + +void clear_traps(int); +void free_traps(void); +int have_traps(void); +void setsignal(int, int); +void ignoresig(int, int); +void onsig(int); +void dotrap(void); +char *child_trap(void); +void setinteractive(int); +void exitshell(int) __dead; +void exitshell_savedstatus(void) __dead; +int lastsig(void); diff --git a/bin/sh/var.c b/bin/sh/var.c new file mode 100644 index 0000000..378598c --- /dev/null +++ b/bin/sh/var.c @@ -0,0 +1,1587 @@ +/* $NetBSD: var.c,v 1.75 2019/01/21 13:27:29 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: var.c,v 1.75 2019/01/21 13:27:29 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <paths.h> +#include <limits.h> +#include <time.h> +#include <pwd.h> +#include <fcntl.h> +#include <inttypes.h> + +/* + * Shell variables. + */ + +#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 "builtins.h" +#include "mail.h" +#include "var.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "parser.h" +#include "show.h" +#include "machdep.h" +#ifndef SMALL +#include "myhistedit.h" +#endif + +#ifdef SMALL +#define VTABSIZE 39 +#else +#define VTABSIZE 517 +#endif + + +struct varinit { + struct var *var; + int flags; + const char *text; + union var_func_union v_u; +}; +#define func v_u.set_func +#define rfunc v_u.ref_func + +char *get_lineno(struct var *); + +#ifndef SMALL +char *get_tod(struct var *); +char *get_hostname(struct var *); +char *get_seconds(struct var *); +char *get_euser(struct var *); +char *get_random(struct var *); +#endif + +struct localvar *localvars; + +#ifndef SMALL +struct var vhistsize; +struct var vterm; +struct var editrc; +struct var ps_lit; +#endif +struct var vifs; +struct var vmail; +struct var vmpath; +struct var vpath; +struct var vps1; +struct var vps2; +struct var vps4; +struct var vvers; +struct var voptind; +struct var line_num; +#ifndef SMALL +struct var tod; +struct var host_name; +struct var seconds; +struct var euname; +struct var random_num; + +intmax_t sh_start_time; +#endif + +struct var line_num; +int line_number; +int funclinebase = 0; +int funclineabs = 0; + +char ifs_default[] = " \t\n"; + +const struct varinit varinit[] = { +#ifndef SMALL + { &vhistsize, VSTRFIXED|VTEXTFIXED|VUNSET, "HISTSIZE=", + { .set_func= sethistsize } }, +#endif + { &vifs, VSTRFIXED|VTEXTFIXED, "IFS= \t\n", + { NULL } }, + { &vmail, VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL=", + { NULL } }, + { &vmpath, VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH=", + { NULL } }, + { &vvers, VSTRFIXED|VTEXTFIXED|VNOEXPORT, "NETBSD_SHELL=", + { NULL } }, + { &vpath, VSTRFIXED|VTEXTFIXED, "PATH=" _PATH_DEFPATH, + { .set_func= changepath } }, + /* + * vps1 depends on uid + */ + { &vps2, VSTRFIXED|VTEXTFIXED, "PS2=> ", + { NULL } }, + { &vps4, VSTRFIXED|VTEXTFIXED, "PS4=+ ", + { NULL } }, +#ifndef SMALL + { &vterm, VSTRFIXED|VTEXTFIXED|VUNSET, "TERM=", + { .set_func= setterm } }, + { &editrc, VSTRFIXED|VTEXTFIXED|VUNSET, "EDITRC=", + { .set_func= set_editrc } }, + { &ps_lit, VSTRFIXED|VTEXTFIXED|VUNSET, "PSlit=", + { .set_func= set_prompt_lit } }, +#endif + { &voptind, VSTRFIXED|VTEXTFIXED|VNOFUNC, "OPTIND=1", + { .set_func= getoptsreset } }, + { &line_num, VSTRFIXED|VTEXTFIXED|VFUNCREF|VSPECIAL, "LINENO=1", + { .ref_func= get_lineno } }, +#ifndef SMALL + { &tod, VSTRFIXED|VTEXTFIXED|VFUNCREF, "ToD=", + { .ref_func= get_tod } }, + { &host_name, VSTRFIXED|VTEXTFIXED|VFUNCREF, "HOSTNAME=", + { .ref_func= get_hostname } }, + { &seconds, VSTRFIXED|VTEXTFIXED|VFUNCREF, "SECONDS=", + { .ref_func= get_seconds } }, + { &euname, VSTRFIXED|VTEXTFIXED|VFUNCREF, "EUSER=", + { .ref_func= get_euser } }, + { &random_num, VSTRFIXED|VTEXTFIXED|VFUNCREF|VSPECIAL, "RANDOM=", + { .ref_func= get_random } }, +#endif + { NULL, 0, NULL, + { NULL } } +}; + +struct var *vartab[VTABSIZE]; + +STATIC int strequal(const char *, const char *); +STATIC struct var *find_var(const char *, struct var ***, int *); +STATIC void showvar(struct var *, const char *, const char *, int); +static void export_usage(const char *) __dead; + +/* + * Initialize the varable symbol tables and import the environment + */ + +#ifdef mkinit +INCLUDE <stdio.h> +INCLUDE <unistd.h> +INCLUDE <time.h> +INCLUDE "var.h" +INCLUDE "version.h" +MKINIT char **environ; +INIT { + char **envp; + char buf[64]; + +#ifndef SMALL + sh_start_time = (intmax_t)time((time_t *)0); +#endif + /* + * Set up our default variables and their values. + */ + initvar(); + + /* + * Import variables from the environment, which will + * override anything initialised just previously. + */ + for (envp = environ ; *envp ; envp++) { + if (strchr(*envp, '=')) { + setvareq(*envp, VEXPORT|VTEXTFIXED); + } + } + + /* + * Set variables which override anything read from environment. + * + * PPID is readonly + * Always default IFS + * POSIX: "Whenever the shell is invoked, OPTIND shall + * be initialized to 1." + * PSc indicates the root/non-root status of this shell. + * START_TIME belongs only to this shell. + * NETBSD_SHELL is a constant (readonly), and is never exported + * LINENO is simply magic... + */ + snprintf(buf, sizeof(buf), "%d", (int)getppid()); + setvar("PPID", buf, VREADONLY); + setvar("IFS", ifs_default, VTEXTFIXED); + setvar("OPTIND", "1", VTEXTFIXED); + setvar("PSc", (geteuid() == 0 ? "#" : "$"), VTEXTFIXED); + +#ifndef SMALL + snprintf(buf, sizeof(buf), "%jd", sh_start_time); + setvar("START_TIME", buf, VTEXTFIXED); +#endif + + setvar("NETBSD_SHELL", NETBSD_SHELL +#ifdef BUILD_DATE + " BUILD:" BUILD_DATE +#endif +#ifdef DEBUG + " DEBUG" +#endif +#if !defined(JOBS) || JOBS == 0 + " -JOBS" +#endif +#ifndef DO_SHAREDVFORK + " -VFORK" +#endif +#ifdef SMALL + " SMALL" +#endif +#ifdef TINY + " TINY" +#endif +#ifdef OLD_TTY_DRIVER + " OLD_TTY" +#endif +#ifdef SYSV + " SYSV" +#endif +#ifndef BSD + " -BSD" +#endif +#ifdef BOGUS_NOT_COMMAND + " BOGUS_NOT" +#endif + , VTEXTFIXED|VREADONLY|VNOEXPORT); + + setvar("LINENO", "1", VTEXTFIXED); +} +#endif + + +/* + * This routine initializes the builtin variables. It is called when the + * shell is initialized and again when a shell procedure is spawned. + */ + +void +initvar(void) +{ + const struct varinit *ip; + struct var *vp; + struct var **vpp; + + 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 = strdup(ip->text); + vp->flags = (ip->flags & ~VTEXTFIXED) | VSTRFIXED; + vp->v_u = ip->v_u; + } + /* + * PS1 depends on uid + */ + if (find_var("PS1", &vpp, &vps1.name_len) == NULL) { + vps1.next = *vpp; + *vpp = &vps1; + vps1.flags = VSTRFIXED; + vps1.text = NULL; + choose_ps1(); + } +} + +void +choose_ps1(void) +{ + uid_t u = geteuid(); + + if ((vps1.flags & (VTEXTFIXED|VSTACK)) == 0) + free(vps1.text); + vps1.text = strdup(u != 0 ? "PS1=$ " : "PS1=# "); + vps1.flags &= ~(VTEXTFIXED|VSTACK); + + /* + * Update PSc whenever we feel the need to update PS1 + */ + setvarsafe("PSc", (u == 0 ? "#" : "$"), 0); +} + +/* + * Validate a string as a valid variable name + * nb: not parameter - special params and such are "invalid" here. + * Name terminated by either \0 or the term param (usually '=' or '\0'). + * + * If not NULL, the length of the (intended) name is returned via len + */ + +int +validname(const char *name, int term, int *len) +{ + const char *p = name; + int ok = 1; + + if (p == NULL || *p == '\0' || *p == term) { + if (len != NULL) + *len = 0; + return 0; + } + + if (!is_name(*p)) + ok = 0; + p++; + for (;;) { + if (*p == '\0' || *p == term) + break; + if (!is_in_name(*p)) + ok = 0; + p++; + } + if (len != NULL) + *len = p - name; + + return ok; +} + +/* + * 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 volatile err = 0; + + if (setjmp(jmploc.loc)) + err = 1; + else { + handler = &jmploc; + setvar(name, val, flags); + } + handler = savehandler; + return err; +} + +/* + * Set the value of a variable. The flags argument is ored with the + * flags of the variable. If val is NULL, the variable is unset. + * + * This always copies name and val when setting a variable, so + * the source strings can be from anywhere, and are no longer needed + * after this function returns. The VTEXTFIXED and VSTACK flags should + * not be used (but just in case they were, clear them.) + */ + +void +setvar(const char *name, const char *val, int flags) +{ + const char *p; + const char *q; + char *d; + int len; + int namelen; + char *nameeq; + + p = name; + + if (!validname(p, '=', &namelen)) + error("%.*s: bad variable name", namelen, name); + len = namelen + 2; /* 2 is space for '=' and '\0' */ + if (val == NULL) { + flags |= VUNSET; + } else { + len += strlen(val); + } + d = nameeq = ckmalloc(len); + q = name; + while (--namelen >= 0) + *d++ = *q++; + *d++ = '='; + *d = '\0'; + if (val) + scopy(val, d); + setvareq(nameeq, flags & ~(VTEXTFIXED | VSTACK)); +} + + + +/* + * 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. The flags (VTEXTFIXED or VSTACK) can be used to + * indicate the source of the string (if neither is set, the string will + * eventually be free()d when a replacement value is assigned.) + */ + +void +setvareq(char *s, int flags) +{ + struct var *vp, **vpp; + int nlen; + + VTRACE(DBG_VARS, ("setvareq([%s],%#x) aflag=%d ", s, flags, aflag)); + if (aflag && !(flags & VNOEXPORT)) + flags |= VEXPORT; + vp = find_var(s, &vpp, &nlen); + if (vp != NULL) { + VTRACE(DBG_VARS, ("was [%s] fl:%#x\n", vp->text, + vp->flags)); + if (vp->flags & VREADONLY) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + if (flags & VNOERROR) + return; + 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 && !(vp->flags & VFUNCREF) && !(flags & VNOFUNC)) + (*vp->func)(s + vp->name_len + 1); + + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(vp->text); + + /* + * if we set a magic var, the magic dissipates, + * unless it is very special indeed. + */ + if (vp->rfunc && (vp->flags & (VFUNCREF|VSPECIAL)) == VFUNCREF) + vp->rfunc = NULL; + + vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET); + if (flags & VNOEXPORT) + vp->flags &= ~VEXPORT; + if (flags & VDOEXPORT) + vp->flags &= ~VNOEXPORT; + if (vp->flags & VNOEXPORT) + flags &= ~VEXPORT; + vp->flags |= flags & ~(VNOFUNC | VDOEXPORT); + vp->text = s; + + /* + * We could roll this to a function, to handle it as + * a regular variable function callback, but why bother? + */ + if (vp == &vmpath || (vp == &vmail && ! mpathset())) + chkmail(1); + + INTON; + return; + } + /* not found */ + if (flags & VNOSET) { + VTRACE(DBG_VARS, ("new noset\n")); + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + return; + } + vp = ckmalloc(sizeof (*vp)); + vp->flags = flags & ~(VNOFUNC|VFUNCREF|VDOEXPORT); + vp->text = s; + vp->name_len = nlen; + vp->func = NULL; + vp->next = *vpp; + *vpp = vp; + + VTRACE(DBG_VARS, ("new [%s] (%d) %#x\n", s, nlen, vp->flags)); +} + + + +/* + * Process a linked list of variable assignments. + */ + +void +listsetvar(struct strlist *list, int flags) +{ + struct strlist *lp; + + INTOFF; + for (lp = list ; lp ; lp = lp->next) { + setvareq(savestr(lp->text), flags); + } + INTON; +} + +void +listmklocal(struct strlist *list, int flags) +{ + struct strlist *lp; + + for (lp = list ; lp ; lp = lp->next) + mklocal(lp->text, flags); +} + + +/* + * 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; + if (v->rfunc && (v->flags & VFUNCREF) != 0) + return (*v->rfunc)(v) + v->name_len + 1; + 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 strlist *sp; + struct var *v; + + for (sp = cmdenviron ; sp ; sp = sp->next) { + if (strequal(sp->text, name)) + return strchr(sp->text, '=') + 1; + } + + v = find_var(name, NULL, NULL); + + if (v == NULL || v->flags & VUNSET || (!doall && !(v->flags & VEXPORT))) + return NULL; + if (v->rfunc && (v->flags & VFUNCREF) != 0) + return (*v->rfunc)(v) + v->name_len + 1; + return v->text + v->name_len + 1; +} + + + +/* + * 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; + char **ep; + + nenv = 0; + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) + if ((vp->flags & (VEXPORT|VUNSET)) == VEXPORT) + nenv++; + } + CTRACE(DBG_VARS, ("environment: %d vars to export\n", 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|VUNSET)) == VEXPORT) { + if (vp->rfunc && (vp->flags & VFUNCREF)) + *ep++ = (*vp->rfunc)(vp); + else + *ep++ = vp->text; + VTRACE(DBG_VARS, ("environment: %s\n", ep[-1])); + } + } + *ep = NULL; + return env; +} + + +/* + * Called when a shell procedure is invoked to clear out nonexported + * variables. It is also necessary to reallocate variables of with + * VSTACK set since these are currently allocated on the stack. + */ + +#ifdef mkinit +void shprocvar(void); + +SHELLPROC { + shprocvar(); +} +#endif + +void +shprocvar(void) +{ + struct var **vpp; + struct var *vp, **prev; + + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (prev = vpp ; (vp = *prev) != NULL ; ) { + if ((vp->flags & VEXPORT) == 0) { + *prev = vp->next; + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + if ((vp->flags & VSTRFIXED) == 0) + ckfree(vp); + } else { + if (vp->flags & VSTACK) { + vp->text = savestr(vp->text); + vp->flags &=~ VSTACK; + } + prev = &vp->next; + } + } + } + initvar(); +} + + + +/* + * Command to list all variables which are set. Currently this command + * is invoked from the set command when the set command is called without + * any variables. + */ + +void +print_quoted(const char *p) +{ + const char *q; + + if (p[0] == '\0') { + out1fmt("''"); + return; + } + if (strcspn(p, "|&;<>()$`\\\"' \t\n*?[]#~=%") == strlen(p)) { + out1fmt("%s", p); + return; + } + while (*p) { + if (*p == '\'') { + out1fmt("\\'"); + p++; + continue; + } + q = strchr(p, '\''); + if (!q) { + out1fmt("'%s'", p ); + return; + } + out1fmt("'%.*s'", (int)(q - p), p ); + p = q; + } +} + +static int +sort_var(const void *v_v1, const void *v_v2) +{ + const struct var * const *v1 = v_v1; + const struct var * const *v2 = v_v2; + char *t1 = (*v1)->text, *t2 = (*v2)->text; + + if (*t1 == *t2) { + char *p, *s; + + STARTSTACKSTR(p); + + /* + * note: if lengths are equal, strings must be different + * so we don't care which string we pick for the \0 in + * that case. + */ + if ((strchr(t1, '=') - t1) <= (strchr(t2, '=') - t2)) { + s = t1; + t1 = p; + } else { + s = t2; + t2 = p; + } + + while (*s && *s != '=') { + STPUTC(*s, p); + s++; + } + STPUTC('\0', p); + } + + return strcoll(t1, t2); +} + +/* + * POSIX requires that 'set' (but not export or readonly) output the + * variables in lexicographic order - by the locale's collating order (sigh). + * Maybe we could keep them in an ordered balanced binary tree + * instead of hashed lists. + * For now just roll 'em through qsort for printing... + */ + +STATIC void +showvar(struct var *vp, const char *cmd, const char *xtra, int show_value) +{ + const char *p; + + if (cmd) + out1fmt("%s ", cmd); + if (xtra) + out1fmt("%s ", xtra); + p = vp->text; + if (vp->rfunc && (vp->flags & VFUNCREF) != 0) { + p = (*vp->rfunc)(vp); + if (p == NULL) + p = vp->text; + } + for ( ; *p != '=' ; p++) + out1c(*p); + if (!(vp->flags & VUNSET) && show_value) { + out1fmt("="); + print_quoted(++p); + } + out1c('\n'); +} + +int +showvars(const char *cmd, int flag, int show_value, const char *xtra) +{ + struct var **vpp; + struct var *vp; + + static struct var **list; /* static in case we are interrupted */ + static int list_len; + int count = 0; + + if (!list) { + list_len = 32; + list = ckmalloc(list_len * sizeof *list); + } + + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) { + if (flag && !(vp->flags & flag)) + continue; + if (vp->flags & VUNSET && !(show_value & 2)) + continue; + if (count >= list_len) { + list = ckrealloc(list, + (list_len << 1) * sizeof *list); + list_len <<= 1; + } + list[count++] = vp; + } + } + + qsort(list, count, sizeof *list, sort_var); + + for (vpp = list; count--; vpp++) + showvar(*vpp, cmd, xtra, show_value); + + /* no free(list), will be used again next time ... */ + + return 0; +} + + + +/* + * The export and readonly commands. + */ + +static void __dead +export_usage(const char *cmd) +{ +#ifdef SMALL + if (*cmd == 'r') + error("Usage: %s [ -p | var[=val]... ]", cmd); + else + error("Usage: %s [ -p | [-n] var[=val]... ]", cmd); +#else + if (*cmd == 'r') + error("Usage: %s [-p [var...] | -q var... | var[=val]... ]", cmd); + else + error( + "Usage: %s [ -px [var...] | -q[x] var... | [-n|x] var[=val]... ]", + cmd); +#endif +} + +int +exportcmd(int argc, char **argv) +{ + struct var *vp; + char *name; + const char *p = argv[0]; + int flag = p[0] == 'r'? VREADONLY : VEXPORT; + int pflg = 0; + int nflg = 0; +#ifndef SMALL + int xflg = 0; + int qflg = 0; +#endif + int res; + int c; + int f; + +#ifdef SMALL +#define EXPORT_OPTS "np" +#else +#define EXPORT_OPTS "npqx" +#endif + + while ((c = nextopt(EXPORT_OPTS)) != '\0') { + +#undef EXPORT_OPTS + + switch (c) { + case 'n': + if (pflg || flag == VREADONLY +#ifndef SMALL + || qflg || xflg +#endif + ) + export_usage(p); + nflg = 1; + break; + case 'p': + if (nflg +#ifndef SMALL + || qflg +#endif + ) + export_usage(p); + pflg = 3; + break; +#ifndef SMALL + case 'q': + if (nflg || pflg) + export_usage(p); + qflg = 1; + break; + case 'x': + if (nflg || flag == VREADONLY) + export_usage(p); + flag = VNOEXPORT; + xflg = 1; + break; +#endif + } + } + + if ((nflg +#ifndef SMALL + || qflg +#endif + ) && *argptr == NULL) + export_usage(p); + +#ifndef SMALL + if (pflg && *argptr != NULL) { + while ((name = *argptr++) != NULL) { + int len; + + vp = find_var(name, NULL, &len); + if (name[len] == '=') + export_usage(p); + if (!goodname(name)) + error("%s: bad variable name", name); + + if (vp && vp->flags & flag) + showvar(vp, p, xflg ? "-x" : NULL, 1); + } + return 0; + } +#endif + + if (pflg || *argptr == NULL) + return showvars( pflg ? p : 0, flag, pflg, +#ifndef SMALL + pflg && xflg ? "-x" : +#endif + NULL ); + + res = 0; +#ifndef SMALL + if (qflg) { + while ((name = *argptr++) != NULL) { + int len; + + vp = find_var(name, NULL, &len); + if (name[len] == '=') + export_usage(p); + if (!goodname(name)) + error("%s: bad variable name", name); + + if (vp == NULL || !(vp->flags & flag)) + res = 1; + } + return res; + } +#endif + + while ((name = *argptr++) != NULL) { + int len; + + f = flag; + + vp = find_var(name, NULL, &len); + p = name + len; + if (*p++ != '=') + p = NULL; + + if (vp != NULL) { + if (nflg) + vp->flags &= ~flag; + else if (flag&VEXPORT && vp->flags&VNOEXPORT) { + /* note we go ahead and do any assignment */ + sh_warnx("%.*s: not available for export", + len, name); + res = 1; + } else { + if (flag == VNOEXPORT) + vp->flags &= ~VEXPORT; + + /* if not NULL will be done in setvar below */ + if (p == NULL) + vp->flags |= flag; + } + if (p == NULL) + continue; + } else if (nflg && p == NULL && !goodname(name)) + error("%s: bad variable name", name); + + if (!nflg || p != NULL) + setvar(name, p, f); + } + return res; +} + + +/* + * The "local" command. + */ + +int +localcmd(int argc, char **argv) +{ + char *name; + int c; + int flags = 0; /*XXX perhaps VUNSET from a -o option value */ + + if (! in_function()) + error("Not in a function"); + + /* upper case options, as bash stole all the good ones ... */ + while ((c = nextopt("INx")) != '\0') + switch (c) { + case 'I': flags &= ~VUNSET; break; + case 'N': flags |= VUNSET; break; + case 'x': flags |= VEXPORT; break; + } + + while ((name = *argptr++) != NULL) { + mklocal(name, flags); + } + return 0; +} + + +/* + * Make a variable a local variable. When a variable is made local, its + * 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(const char *name, int flags) +{ + struct localvar *lvp; + struct var **vpp; + struct var *vp; + + INTOFF; + lvp = ckmalloc(sizeof (struct localvar)); + if (name[0] == '-' && name[1] == '\0') { + char *p; + p = ckmalloc(sizeof_optlist); + lvp->text = memcpy(p, optlist, sizeof_optlist); + lvp->rfunc = NULL; + vp = NULL; + xtrace_clone(0); + } else { + vp = find_var(name, &vpp, NULL); + if (vp == NULL) { + flags &= ~VNOEXPORT; + if (strchr(name, '=')) + setvareq(savestr(name), + VSTRFIXED | (flags & ~VUNSET)); + else + setvar(name, NULL, VSTRFIXED|flags); + vp = *vpp; /* the new variable */ + lvp->text = NULL; + lvp->flags = VUNSET; + lvp->rfunc = NULL; + } else { + lvp->text = vp->text; + lvp->flags = vp->flags; + lvp->v_u = vp->v_u; + vp->flags |= VSTRFIXED|VTEXTFIXED; + if (flags & (VDOEXPORT | VUNSET)) + vp->flags &= ~VNOEXPORT; + if (vp->flags & VNOEXPORT && + (flags & (VEXPORT|VDOEXPORT|VUNSET)) == VEXPORT) + flags &= ~VEXPORT; + if (flags & (VNOEXPORT | VUNSET)) + vp->flags &= ~VEXPORT; + flags &= ~VNOEXPORT; + if (name[vp->name_len] == '=') + setvareq(savestr(name), flags & ~VUNSET); + else if (flags & VUNSET) + unsetvar(name, 0); + else + vp->flags |= flags & (VUNSET|VEXPORT); + + if (vp == &line_num) { + if (name[vp->name_len] == '=') + funclinebase = funclineabs -1; + else + funclinebase = 0; + } + } + } + lvp->vp = vp; + lvp->next = localvars; + localvars = lvp; + INTON; +} + + +/* + * Called after a function returns. + */ + +void +poplocalvars(void) +{ + struct localvar *lvp; + struct var *vp; + + while ((lvp = localvars) != NULL) { + localvars = lvp->next; + vp = lvp->vp; + VTRACE(DBG_VARS, ("poplocalvar %s\n", vp ? vp->text : "-")); + if (vp == NULL) { /* $- saved */ + memcpy(optlist, lvp->text, sizeof_optlist); + ckfree(lvp->text); + xtrace_pop(); + optschanged(); + } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + (void)unsetvar(vp->text, 0); + } else { + if (lvp->func && (lvp->flags & (VNOFUNC|VFUNCREF)) == 0) + (*lvp->func)(lvp->text + vp->name_len + 1); + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + vp->flags = lvp->flags; + vp->text = lvp->text; + vp->v_u = lvp->v_u; + } + ckfree(lvp); + } +} + + +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("List assignment not implemented"); + return 0; +} + + +/* + * The unset builtin command. We unset the function before we unset the + * variable to allow a function to be unset when there is a readonly variable + * with the same name. + */ + +int +unsetcmd(int argc, char **argv) +{ + char **ap; + int i; + int flg_func = 0; + int flg_var = 0; + int flg_x = 0; + int ret = 0; + + while ((i = nextopt("efvx")) != '\0') { + switch (i) { + case 'f': + flg_func = 1; + break; + case 'e': + case 'x': + flg_x = (2 >> (i == 'e')); + /* FALLTHROUGH */ + case 'v': + flg_var = 1; + break; + } + } + + if (flg_func == 0 && flg_var == 0) + flg_var = 1; + + for (ap = argptr; *ap ; ap++) { + if (flg_func) + ret |= unsetfunc(*ap); + if (flg_var) + ret |= unsetvar(*ap, flg_x); + } + return ret; +} + + +/* + * Unset the specified variable. + */ + +int +unsetvar(const char *s, int unexport) +{ + struct var **vpp; + struct var *vp; + + vp = find_var(s, &vpp, NULL); + if (vp == NULL) + return 0; + + if (vp->flags & VREADONLY && !(unexport & 1)) + return 1; + + INTOFF; + if (unexport & 1) { + vp->flags &= ~VEXPORT; + } else { + if (vp->text[vp->name_len + 1] != '\0') + setvar(s, nullstr, 0); + if (!(unexport & 2)) + vp->flags &= ~VEXPORT; + vp->flags |= VUNSET; + if ((vp->flags&(VEXPORT|VSTRFIXED|VREADONLY|VNOEXPORT)) == 0) { + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + *vpp = vp->next; + ckfree(vp); + } + } + INTON; + return 0; +} + + +/* + * Returns true if the two strings specify the same varable. The first + * variable name is terminated by '='; the second may be terminated by + * either '=' or '\0'. + */ + +STATIC int +strequal(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; +} + +/* + * The following are the functions that create the values for + * shell variables that are dynamically produced when needed. + * + * The output strings cannot be malloc'd as there is nothing to + * free them - callers assume these are ordinary variables where + * the value returned is vp->text + * + * Each function needs its own storage space, as the results are + * used to create processes' environment, and (if exported) all + * the values will (might) be needed simultaneously. + * + * It is not a problem if a var is updated while nominally in use + * somewhere, all these are intended to be dynamic, the value they + * return is not guaranteed, an updated vaue is just as good. + * + * So, malloc a single buffer for the result of each function, + * grow, and even shrink, it as needed, but once we have one that + * is a suitable size for the actual usage, simply hold it forever. + * + * For a SMALL shell we implement only LINENO, none of the others, + * and give it just a fixed length static buffer for its result. + */ + +#ifndef SMALL + +struct space_reserved { /* record of space allocated for results */ + char *b; + int len; +}; + +/* rough (over-)estimate of the number of bytes needed to hold a number */ +static int +digits_in(intmax_t number) +{ + int res = 0; + + if (number & ~((1LL << 62) - 1)) + res = 64; /* enough for 2^200 and a bit more */ + else if (number & ~((1LL << 32) - 1)) + res = 20; /* enough for 2^64 */ + else if (number & ~((1 << 23) - 1)) + res = 10; /* enough for 2^32 */ + else + res = 8; /* enough for 2^23 or smaller */ + + return res; +} + +static int +make_space(struct space_reserved *m, int bytes) +{ + void *p; + + if (m->len >= bytes && m->len <= (bytes<<2)) + return 1; + + bytes = SHELL_ALIGN(bytes); + /* not ckrealloc() - we want failure, not error() here */ + p = realloc(m->b, bytes); + if (p == NULL) /* what we had should still be there */ + return 0; + + m->b = p; + m->len = bytes; + m->b[bytes - 1] = '\0'; + + return 1; +} +#endif + +char * +get_lineno(struct var *vp) +{ +#ifdef SMALL +#define length (8 + 10) /* 10 digits is enough for a 32 bit line num */ + static char result[length]; +#else + static struct space_reserved buf; +#define result buf.b +#define length buf.len +#endif + int ln = line_number; + + if (vp->flags & VUNSET) + return NULL; + + ln -= funclinebase; + +#ifndef SMALL + if (!make_space(&buf, vp->name_len + 2 + digits_in(ln))) + return vp->text; +#endif + + snprintf(result, length, "%.*s=%d", vp->name_len, vp->text, ln); + return result; +} +#undef result +#undef length + +#ifndef SMALL + +char * +get_hostname(struct var *vp) +{ + static struct space_reserved buf; + + if (vp->flags & VUNSET) + return NULL; + + if (!make_space(&buf, vp->name_len + 2 + 256)) + return vp->text; + + memcpy(buf.b, vp->text, vp->name_len + 1); /* include '=' */ + (void)gethostname(buf.b + vp->name_len + 1, + buf.len - vp->name_len - 3); + return buf.b; +} + +char * +get_tod(struct var *vp) +{ + static struct space_reserved buf; /* space for answers */ + static struct space_reserved tzs; /* remember TZ last used */ + static timezone_t last_zone; /* timezone data for tzs zone */ + const char *fmt; + char *tz; + time_t now; + struct tm tm_now, *tmp; + timezone_t zone = NULL; + static char t_err[] = "time error"; + int len; + + if (vp->flags & VUNSET) + return NULL; + + fmt = lookupvar("ToD_FORMAT"); + if (fmt == NULL) + fmt="%T"; + tz = lookupvar("TZ"); + (void)time(&now); + + if (tz != NULL) { + if (tzs.b == NULL || strcmp(tzs.b, tz) != 0) { + if (make_space(&tzs, strlen(tz) + 1)) { + INTOFF; + strcpy(tzs.b, tz); + if (last_zone) + tzfree(last_zone); + last_zone = zone = tzalloc(tz); + INTON; + } else + zone = tzalloc(tz); + } else + zone = last_zone; + + tmp = localtime_rz(zone, &now, &tm_now); + } else + tmp = localtime_r(&now, &tm_now); + + len = (strlen(fmt) * 4) + vp->name_len + 2; + while (make_space(&buf, len)) { + memcpy(buf.b, vp->text, vp->name_len+1); + if (tmp == NULL) { + if (buf.len >= vp->name_len+2+(int)(sizeof t_err - 1)) { + strcpy(buf.b + vp->name_len + 1, t_err); + if (zone && zone != last_zone) + tzfree(zone); + return buf.b; + } + len = vp->name_len + 4 + sizeof t_err - 1; + continue; + } + if (strftime_z(zone, buf.b + vp->name_len + 1, + buf.len - vp->name_len - 2, fmt, tmp)) { + if (zone && zone != last_zone) + tzfree(zone); + return buf.b; + } + if (len >= 4096) /* Let's be reasonable */ + break; + len <<= 1; + } + if (zone && zone != last_zone) + tzfree(zone); + return vp->text; +} + +char * +get_seconds(struct var *vp) +{ + static struct space_reserved buf; + intmax_t secs; + + if (vp->flags & VUNSET) + return NULL; + + secs = (intmax_t)time((time_t *)0) - sh_start_time; + if (!make_space(&buf, vp->name_len + 2 + digits_in(secs))) + return vp->text; + + snprintf(buf.b, buf.len, "%.*s=%jd", vp->name_len, vp->text, secs); + return buf.b; +} + +char * +get_euser(struct var *vp) +{ + static struct space_reserved buf; + static uid_t lastuid = 0; + uid_t euid; + struct passwd *pw; + + if (vp->flags & VUNSET) + return NULL; + + euid = geteuid(); + if (buf.b != NULL && lastuid == euid) + return buf.b; + + pw = getpwuid(euid); + if (pw == NULL) + return vp->text; + + if (make_space(&buf, vp->name_len + 2 + strlen(pw->pw_name))) { + lastuid = euid; + snprintf(buf.b, buf.len, "%.*s=%s", vp->name_len, vp->text, + pw->pw_name); + return buf.b; + } + + return vp->text; +} + +char * +get_random(struct var *vp) +{ + static struct space_reserved buf; + static intmax_t random_val = 0; + +#ifdef USE_LRAND48 +#define random lrand48 +#define srandom srand48 +#endif + + if (vp->flags & VUNSET) + return NULL; + + if (vp->text != buf.b) { + /* + * Either initialisation, or a new seed has been set + */ + if (vp->text[vp->name_len + 1] == '\0') { + int fd; + + /* + * initialisation (without pre-seeding), + * or explictly requesting a truly random seed. + */ + fd = open("/dev/urandom", 0); + if (fd == -1) { + out2str("RANDOM initialisation failed\n"); + random_val = (getpid()<<3) ^ time((time_t *)0); + } else { + int n; + + do { + n = read(fd,&random_val,sizeof random_val); + } while (n != sizeof random_val); + close(fd); + } + } else + /* good enough for today */ + random_val = strtoimax(vp->text+vp->name_len+1,NULL,0); + + srandom((long)random_val); + } + +#if 0 + random_val = (random_val + 1) & 0x7FFF; /* 15 bit "random" numbers */ +#else + random_val = (random() >> 5) & 0x7FFF; +#endif + + if (!make_space(&buf, vp->name_len + 2 + digits_in(random_val))) + return vp->text; + + snprintf(buf.b, buf.len, "%.*s=%jd", vp->name_len, vp->text, + random_val); + + if (buf.b != vp->text && (vp->flags & (VTEXTFIXED|VSTACK)) == 0) + free(vp->text); + vp->flags |= VTEXTFIXED; + vp->text = buf.b; + + return vp->text; +#undef random +#undef srandom +} + +#endif /* SMALL */ diff --git a/bin/sh/var.h b/bin/sh/var.h new file mode 100644 index 0000000..43bef4b --- /dev/null +++ b/bin/sh/var.h @@ -0,0 +1,151 @@ +/* $NetBSD: var.h,v 1.38 2018/12/04 14:03:30 kre Exp $ */ + +/*- + * 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 + */ + +#ifndef VUNSET /* double include protection */ +/* + * Shell variables. + */ + +/* flags */ +#define VUNSET 0x0001 /* the variable is not set */ +#define VEXPORT 0x0002 /* variable is exported */ +#define VREADONLY 0x0004 /* variable cannot be modified */ +#define VNOEXPORT 0x0008 /* variable may not be exported */ + +#define VSTRFIXED 0x0010 /* variable struct is statically allocated */ +#define VTEXTFIXED 0x0020 /* text is statically allocated */ +#define VSTACK 0x0040 /* text is allocated on the stack */ +#define VNOFUNC 0x0100 /* don't call the callback function */ +#define VFUNCREF 0x0200 /* the function is called on ref, not set */ + +#define VSPECIAL 0x1000 /* magic properties not lost when set */ +#define VDOEXPORT 0x2000 /* obey VEXPORT even if VNOEXPORT */ +#define VNOSET 0x4000 /* do not set variable - just readonly test */ +#define VNOERROR 0x8000 /* be quiet if set fails (no error msg) */ + +struct var; + +union var_func_union { /* function to be called when: */ + void (*set_func)(const char *); /* variable gets set/unset */ + char*(*ref_func)(struct var *); /* variable is referenced */ +}; + +struct var { + struct var *next; /* next entry in hash list */ + int flags; /* flags are defined above */ + char *text; /* name=value */ + int name_len; /* length of name */ + union var_func_union v_u; /* function to apply (sometimes) */ +}; + + +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 */ + union var_func_union v_u; /* saved function */ +}; + + +extern struct localvar *localvars; + +extern struct var vifs; +extern char ifs_default[]; +extern struct var vmail; +extern struct var vmpath; +extern struct var vpath; +extern struct var vps1; +extern struct var vps2; +extern struct var vps4; +extern struct var line_num; +#ifndef SMALL +extern struct var editrc; +extern struct var vterm; +extern struct var vtermcap; +extern struct var vhistsize; +extern struct var ps_lit; +extern struct var euname; +extern struct var random_num; +extern intmax_t sh_start_time; +#endif + +extern int line_number; +extern int funclinebase; +extern int funclineabs; + +/* + * 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 ifsset() ((vifs.flags & VUNSET) == 0) +#define ifsval() (ifsset() ? (vifs.text + 4) : ifs_default) +#define mailval() (vmail.text + 5) +#define mpathval() (vmpath.text + 9) +#define pathval() (vpath.text + 5) +#define ps1val() (vps1.text + 4) +#define ps2val() (vps2.text + 4) +#define ps4val() (vps4.text + 4) +#define optindval() (voptind.text + 7) +#ifndef SMALL +#define histsizeval() (vhistsize.text + 9) +#define termval() (vterm.text + 5) +#endif + +#define mpathset() ((vmpath.flags & VUNSET) == 0) + +void initvar(void); +void setvar(const char *, const char *, int); +void setvareq(char *, int); +struct strlist; +void listsetvar(struct strlist *, int); +char *lookupvar(const char *); +char *bltinlookup(const char *, int); +char **environment(void); +void shprocvar(void); +int showvars(const char *, int, int, const char *); +void mklocal(const char *, int); +void listmklocal(struct strlist *, int); +void poplocalvars(void); +int unsetvar(const char *, int); +void choose_ps1(void); +int setvarsafe(const char *, const char *, int); +void print_quoted(const char *); +int validname(const char *, int, int *); + +#endif diff --git a/bin/sh/version.h b/bin/sh/version.h new file mode 100644 index 0000000..59069e4 --- /dev/null +++ b/bin/sh/version.h @@ -0,0 +1,36 @@ +/* $NetBSD: version.h,v 1.3 2018/12/12 12:16:42 kre Exp $ */ + +/*- + * Copyright (c) 2014 The NetBSD Foundation, Inc. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + */ + +/* + * This value should be modified rarely - only when significant changes + * to the shell (which does not mean a bug fixed, or some new feature added) + * have been made. The most likely reason to change this value would be + * when a new (shell only) release is to be exported. This should not be + * updated just because a new NetBSD release is to include this code. + */ +#define NETBSD_SHELL "20181212" diff --git a/bin/sleep/sleep.1 b/bin/sleep/sleep.1 new file mode 100644 index 0000000..1ee73b3 --- /dev/null +++ b/bin/sleep/sleep.1 @@ -0,0 +1,177 @@ +.\" $NetBSD: sleep.1,v 1.27 2019/01/27 17:42:53 wiz Exp $ +.\" +.\" Copyright (c) 1990, 1993, 1994 +.\" 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. +.\" +.\" @(#)sleep.1 8.3 (Berkeley) 4/18/94 +.\" +.Dd January 26, 2019 +.Dt SLEEP 1 +.Os +.Sh NAME +.Nm sleep +.Nd suspend execution for an interval of time +.Sh SYNOPSIS +.Nm +.Ar seconds +.Sh DESCRIPTION +The +.Nm +utility suspends execution for a minimum of +.Ar seconds +seconds, then exits. +It is usually used to schedule the execution of other commands (see +.Sx EXAMPLES +below). +.Pp +Note: The +.Nx +.Nm +command will accept and honor a non-integer number of specified seconds. +Note however, that if the request is for much more than 2.5 hours, +any fractional seconds will be ignored. +Permitting non-integral delays is a non-portable extension, +and its use will decrease the probability that +a shell script will execute properly on another system. +.Pp +When the +.Dv SIGINFO +signal is received, an estimate of the number of seconds remaining to +sleep is printed on the standard output. +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Bl -tag -width flag +.It Li \&0 +On successful completion, or if the signal +.Dv SIGALRM +was received. +.It Li \&>\&0 +An error occurred. +.El +.Sh EXAMPLES +To schedule the execution of a command for 1800 seconds later: +.Pp +.Dl (sleep 1800; sh command_file >errors 2>&1)& +.Pp +This incantation would wait half an hour before +running the script +.Dq command_file . +(See the +.Xr at 1 +utility.) +.Pp +To repeatedly run a command (using +.Xr csh 1 ) : +.Pp +.Bd -literal -offset indent -compact +while (1) + if (! -r zzz.rawdata) then + sleep 300 + else + foreach i (*.rawdata) + sleep 70 + awk -f collapse_data $i >> results + end + break + endif +end +.Ed +.Pp +The scenario for a script such as this might be: a program currently +running is taking longer than expected to process a series of +files, and it would be nice to have +another program start processing the files created by the first +program as soon as it is finished (when zzz.rawdata is created). +The script checks every five minutes for the file zzz.rawdata. +When the file is found, processing the generated files (*.rawdata) +is done courteously by sleeping for 70 seconds in between each +awk job. +.Pp +To wait until a particular time, the following, +with some error checking added, might be used (using +.Xr sh 1 +on +.Nx ) : +.Bd -literal -offset indent +END=$(( $( date -d "$1" +%s ) - START_TIME )) +while [ "${SECONDS}" -lt "${END}" ] +do + sleep "$((END - SECONDS))" +done +.Ed +.Pp +where the argument +.Sq \&$1 +specifies the desired date and time in any format the +.Fl d +option to the +.Xr date 1 +command accepts. +.Sh SEE ALSO +.Xr at 1 , +.Xr csh 1 , +.Xr date 1 , +.Xr sh 1 , +.Xr nanosleep 2 , +.Xr sleep 3 +.Sh STANDARDS +The +.Nm +command is expected to be +.St -p1003.2 +compatible. +.Sh HISTORY +A +.Nm +utility appeared in +.At v4 . +Processing fractional seconds, and processing the +.Ic seconds +argument respecting the current locale, was added in +.Nx 1.3 . +The ability to sleep for extended periods appeared in +.Nx 9 . +.Sh BUGS +This +.Nm +command cannot handle requests for durations +much longer than about 250 billion years. +Any such attempt will result in an error, +and immediate termination. +It is suggested that when there is a need +for sleeps exceeding this period, the +.Nm +command be executed in a loop, with each +individual +.Nm +invocation limited to 200 billion years +approximately. diff --git a/bin/sleep/sleep.c b/bin/sleep/sleep.c new file mode 100644 index 0000000..dfaf302 --- /dev/null +++ b/bin/sleep/sleep.c @@ -0,0 +1,229 @@ +/* $NetBSD: sleep.c,v 1.29 2019/01/27 02:00:45 christos Exp $ */ + +/* + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)sleep.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: sleep.c,v 1.29 2019/01/27 02:00:45 christos Exp $"); +#endif +#endif /* not lint */ + +#include <ctype.h> +#include <err.h> +#include <locale.h> +#include <math.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +__dead static void alarmhandle(int); +__dead static void usage(void); + +static void report(const time_t, const time_t, const char *const); + +static volatile sig_atomic_t report_requested; +static void +report_request(int signo __unused) +{ + + report_requested = 1; +} + +int +main(int argc, char *argv[]) +{ + char *arg, *temp; + const char *msg; + double fval, ival, val; + struct timespec ntime; + time_t original; + int ch, fracflag; + unsigned delay; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + (void)signal(SIGALRM, alarmhandle); + + while ((ch = getopt(argc, argv, "")) != -1) + switch(ch) { + default: + usage(); + } + argc -= optind; + argv += optind; + + if (argc != 1) + usage(); + + /* + * Okay, why not just use atof for everything? Why bother + * checking if there is a fraction in use? Because the old + * sleep handled the full range of integers, that's why, and a + * double can't handle a large long. This is fairly useless + * given how large a number a double can hold on most + * machines, but now we won't ever have trouble. If you want + * 1000000000.9 seconds of sleep, well, that's your + * problem. Why use an isdigit() check instead of checking for + * a period? Because doing it this way means locales will be + * handled transparently by the atof code. + * + * Since fracflag is set for any non-digit, we also fall + * into the floating point conversion path if the input + * is hex (the 'x' in 0xA is not a digit). Then if + * strtod() handles hex (on NetBSD it does) so will we. + * That path is also taken for scientific notation (1.2e+3) + * and when the input is simply nonsense. + */ + fracflag = 0; + arg = *argv; + for (temp = arg; *temp != '\0'; temp++) + if (!isdigit((unsigned char)*temp)) { + ch = *temp; + fracflag++; + } + + if (fracflag) { + /* + * If we cannot convert the value using the user's locale + * then try again using the C locale, so strtod() can always + * parse values like 2.5, even if the user's locale uses + * a different decimal radix character (like ',') + * + * (but only if that is the potential problem) + */ + val = strtod(arg, &temp); + if (*temp != '\0') + val = strtod_l(arg, &temp, LC_C_LOCALE); + if (val < 0 || temp == arg || *temp != '\0') + usage(); + + ival = floor(val); + fval = (1000000000 * (val-ival)); + ntime.tv_sec = ival; + if ((double)ntime.tv_sec != ival) + errx(1, "requested delay (%s) out of range", arg); + ntime.tv_nsec = fval; + + if (ntime.tv_sec == 0 && ntime.tv_nsec == 0) + return EXIT_SUCCESS; /* was 0.0 or underflowed */ + } else { + ntime.tv_sec = strtol(arg, &temp, 10); + if (ntime.tv_sec < 0 || temp == arg || *temp != '\0') + usage(); + + if (ntime.tv_sec == 0) + return EXIT_SUCCESS; + ntime.tv_nsec = 0; + } + + original = ntime.tv_sec; + if (ntime.tv_nsec != 0) + msg = " and a bit"; + else + msg = ""; + + signal(SIGINFO, report_request); + + if (ntime.tv_sec <= 10000) { /* arbitrary */ + while (nanosleep(&ntime, &ntime) != 0) { + if (report_requested) { + report(ntime.tv_sec, original, msg); + report_requested = 0; + } else + err(EXIT_FAILURE, "nanosleep failed"); + } + } else while (ntime.tv_sec > 0) { + delay = (unsigned int)ntime.tv_sec; + + if ((time_t)delay != ntime.tv_sec || delay > 30 * 86400) + delay = 30 * 86400; + + ntime.tv_sec -= delay; + delay = sleep(delay); + ntime.tv_sec += delay; + + if (delay != 0 && report_requested) { + report(ntime.tv_sec, original, ""); + report_requested = 0; + } else + break; + } + + return EXIT_SUCCESS; + /* NOTREACHED */ +} + + /* Reporting does not bother with nanoseconds. */ +static void +report(const time_t remain, const time_t original, const char * const msg) +{ + if (remain == 0) + warnx("In the final moments of the original" + " %jd%s second%s", (intmax_t)original, msg, + original == 1 && *msg == '\0' ? "" : "s"); + else if (remain < 2000) + warnx("Between %jd and %jd seconds left" + " out of the original %g%s", + (intmax_t)remain, (intmax_t)remain + 1, (double)original, + msg); + else if ((original - remain) < 100000 && (original-remain) < original/8) + warnx("Have waited only %jd seconds of the original %g", + (intmax_t)(original - remain), (double)original); + else + warnx("Approximately %g seconds left out of the original %g", + (double)remain, (double)original); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: %s seconds\n", getprogname()); + exit(EXIT_FAILURE); + /* NOTREACHED */ +} + +/* ARGSUSED */ +static void +alarmhandle(int i) +{ + _exit(EXIT_SUCCESS); + /* NOTREACHED */ +} diff --git a/bin/stty/cchar.c b/bin/stty/cchar.c new file mode 100644 index 0000000..ad88c76 --- /dev/null +++ b/bin/stty/cchar.c @@ -0,0 +1,148 @@ +/* $NetBSD: cchar.c,v 1.16 2006/10/16 00:37:55 christos Exp $ */ + +/*- + * Copyright (c) 1991, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)cchar.c 8.5 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: cchar.c,v 1.16 2006/10/16 00:37:55 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> + +#include <err.h> +#include <limits.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include "stty.h" +#include "extern.h" + +/* + * Special control characters. + * + * Cchars1 are the standard names, cchars2 are the old aliases. + * The first are displayed, but both are recognized on the + * command line. + */ +const struct cchar cchars1[] = { + { "discard", VDISCARD, CDISCARD }, + { "dsusp", VDSUSP, CDSUSP }, + { "eof", VEOF, CEOF }, + { "eol", VEOL, CEOL }, + { "eol2", VEOL2, CEOL }, + { "erase", VERASE, CERASE }, + { "intr", VINTR, CINTR }, + { "kill", VKILL, CKILL }, + { "lnext", VLNEXT, CLNEXT }, + { "min", VMIN, CMIN }, + { "quit", VQUIT, CQUIT }, + { "reprint", VREPRINT, CREPRINT }, + { "start", VSTART, CSTART }, + { "status", VSTATUS, CSTATUS }, + { "stop", VSTOP, CSTOP }, + { "susp", VSUSP, CSUSP }, + { "time", VTIME, CTIME }, + { "werase", VWERASE, CWERASE }, + { .name = NULL }, +}; + +const struct cchar cchars2[] = { + { "brk", VEOL, CEOL }, + { "flush", VDISCARD, CDISCARD }, + { "rprnt", VREPRINT, CREPRINT }, + { .name = NULL }, +}; + +static int c_cchar(const void *, const void *); + +static int +c_cchar(const void *a, const void *b) +{ + return (strcmp(((const struct cchar *)a)->name, + ((const struct cchar *)b)->name)); +} + +int +csearch(char ***argvp, struct info *ip) +{ + struct cchar *cp, tmp; + long val; + char *arg, *ep, *name; + + name = **argvp; + + tmp.name = name; + if (!(cp = (struct cchar *)bsearch(&tmp, cchars1, + sizeof(cchars1)/sizeof(cchars1[0]) - 1, sizeof(cchars1[0]), + c_cchar)) && + !(cp = (struct cchar *)bsearch(&tmp, cchars2, + sizeof(cchars2)/sizeof(cchars2[0]) - 1, sizeof(cchars2[0]), + c_cchar))) + return (0); + + arg = *++*argvp; + if (!arg) { + warnx("option requires an argument -- %s", name); + usage(); + } + +#define CHK(s) (*arg == s[0] && !strcmp(arg, s)) + if (CHK("undef") || CHK("<undef>")) + ip->t.c_cc[cp->sub] = _POSIX_VDISABLE; + else if (cp->sub == VMIN || cp->sub == VTIME) { + val = strtol(arg, &ep, 10); + if (val == _POSIX_VDISABLE) { + warnx("value of %ld would disable the option -- %s", + val, name); + usage(); + } + if (val > UCHAR_MAX) { + warnx("maximum option value is %d -- %s", + UCHAR_MAX, name); + usage(); + } + if (*ep != '\0') { + warnx("option requires a numeric argument -- %s", name); + usage(); + } + ip->t.c_cc[cp->sub] = (cc_t)val; + } else if (arg[0] == '^') + ip->t.c_cc[cp->sub] = (arg[1] == '?') ? 0177 : + (arg[1] == '-') ? _POSIX_VDISABLE : arg[1] & 037; + else + ip->t.c_cc[cp->sub] = arg[0]; + ip->set = 1; + return (1); +} diff --git a/bin/stty/extern.h b/bin/stty/extern.h new file mode 100644 index 0000000..92598eb --- /dev/null +++ b/bin/stty/extern.h @@ -0,0 +1,51 @@ +/* $NetBSD: extern.h,v 1.13 2013/09/12 19:47:23 christos Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)extern.h 8.1 (Berkeley) 5/31/93 + */ + +#ifndef _EXTERN_H_ +#define _EXTERN_H_ + +int c_cchars(const void *, const void *); +int c_modes(const void *, const void *); +int csearch(char ***, struct info *); +void checkredirect(void); +void gprint(struct termios *); +void gread(struct termios *, char *); +int ksearch(char ***, struct info *); +int msearch(char ***, struct info *); +void optlist(void); +void print(struct termios *, struct winsize *, int, const char *, enum FMT); +__dead void usage(void); + +extern const struct cchar cchars1[], cchars2[]; + +#endif /* !_EXTERN_H_ */ diff --git a/bin/stty/gfmt.c b/bin/stty/gfmt.c new file mode 100644 index 0000000..55ec0c2 --- /dev/null +++ b/bin/stty/gfmt.c @@ -0,0 +1,132 @@ +/* $NetBSD: gfmt.c,v 1.17 2011/08/29 14:51:19 joerg Exp $ */ + +/*- + * Copyright (c) 1991, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)gfmt.c 8.6 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: gfmt.c,v 1.17 2011/08/29 14:51:19 joerg Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> + +#include <err.h> +#include <stdio.h> +#include <string.h> + +#include "stty.h" +#include "extern.h" + +__dead static void gerr(char *); + +static void +gerr(char *s) +{ + if (s) + errx(1, "illegal gfmt1 option -- %s", s); + else + errx(1, "illegal gfmt1 option"); +} + +void +gprint(struct termios *tp) +{ + const struct cchar *cp; + + (void)printf("gfmt1:cflag=%x:iflag=%x:lflag=%x:oflag=%x:", + tp->c_cflag, tp->c_iflag, tp->c_lflag, tp->c_oflag); + for (cp = cchars1; cp->name; ++cp) + (void)printf("%s=%x:", cp->name, tp->c_cc[cp->sub]); + (void)printf("ispeed=%d:ospeed=%d\n", cfgetispeed(tp), cfgetospeed(tp)); +} + +void +gread(struct termios *tp, char *s) +{ + const struct cchar *cp; + char *ep, *p; + long tmp; + + if ((s = strchr(s, ':')) == NULL) + gerr(NULL); + for (++s; s != NULL;) { + p = strsep(&s, ":\0"); + if (!p || !*p) + break; + if (!(ep = strchr(p, '='))) + gerr(p); + *ep++ = '\0'; + (void)sscanf(ep, "%lx", &tmp); + +#define CHK(s) (*p == s[0] && !strcmp(p, s)) + if (CHK("cflag")) { + tp->c_cflag = tmp; + continue; + } + if (CHK("iflag")) { + tp->c_iflag = tmp; + continue; + } +#ifdef BSD4_4 + if (CHK("ispeed")) { + (void)sscanf(ep, "%ld", &tmp); + tp->c_ispeed = tmp; + continue; + } +#endif + if (CHK("lflag")) { + tp->c_lflag = tmp; + continue; + } + if (CHK("oflag")) { + tp->c_oflag = tmp; + continue; + } +#ifdef BSD4_4 + if (CHK("ospeed")) { + (void)sscanf(ep, "%ld", &tmp); + tp->c_ospeed = tmp; + continue; + } +#endif + for (cp = cchars1; cp->name != NULL; ++cp) + if (CHK(cp->name)) { + if (cp->sub == VMIN || cp->sub == VTIME) + (void)sscanf(ep, "%ld", &tmp); + tp->c_cc[cp->sub] = tmp; + break; + } + if (cp->name == NULL) + gerr(p); + } +} diff --git a/bin/stty/key.c b/bin/stty/key.c new file mode 100644 index 0000000..0094d3b --- /dev/null +++ b/bin/stty/key.c @@ -0,0 +1,332 @@ +/* $NetBSD: key.c,v 1.22 2017/01/10 20:44:05 christos Exp $ */ + +/*- + * Copyright (c) 1991, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)key.c 8.4 (Berkeley) 2/20/95"; +#else +__RCSID("$NetBSD: key.c,v 1.22 2017/01/10 20:44:05 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <time.h> +#include <paths.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "stty.h" +#include "extern.h" + +__BEGIN_DECLS +void f_all(struct info *); +void f_cbreak(struct info *); +void f_columns(struct info *); +void f_dec(struct info *); +void f_everything(struct info *); +void f_extproc(struct info *); +void f_insane(struct info *); +void f_ispeed(struct info *); +void f_nl(struct info *); +void f_ospeed(struct info *); +void f_raw(struct info *); +void f_rows(struct info *); +void f_sane(struct info *); +void f_size(struct info *); +void f_speed(struct info *); +void f_ostart(struct info *); +void f_ostop(struct info *); +void f_tty(struct info *); +__END_DECLS + +static const struct key { + const char *name; /* name */ + void (*f)(struct info *); /* function */ +#define F_NEEDARG 0x01 /* needs an argument */ +#define F_OFFOK 0x02 /* can turn off */ + int flags; +} keys[] = { + { "all", f_all, 0 }, + { "cbreak", f_cbreak, F_OFFOK }, + { "cols", f_columns, F_NEEDARG }, + { "columns", f_columns, F_NEEDARG }, + { "cooked", f_sane, 0 }, + { "dec", f_dec, 0 }, + { "everything", f_everything, 0 }, + { "extproc", f_extproc, F_OFFOK }, + { "insane", f_insane, 0 }, + { "ispeed", f_ispeed, F_NEEDARG }, + { "new", f_tty, 0 }, + { "nl", f_nl, F_OFFOK }, + { "old", f_tty, 0 }, + { "ospeed", f_ospeed, F_NEEDARG }, + { "ostart", f_ostart, 0 }, + { "ostop", f_ostop, 0 }, + { "raw", f_raw, F_OFFOK }, + { "rows", f_rows, F_NEEDARG }, + { "sane", f_sane, 0 }, + { "size", f_size, 0 }, + { "speed", f_speed, 0 }, + { "tty", f_tty, 0 }, +}; + +static int c_key(const void *, const void *); + +static int +c_key(const void *a, const void *b) +{ + + return (strcmp(((const struct key *)a)->name, + ((const struct key *)b)->name)); +} + +int +ksearch(char ***argvp, struct info *ip) +{ + char *name; + struct key *kp, tmp; + + name = **argvp; + if (*name == '-') { + ip->off = 1; + ++name; + } else + ip->off = 0; + + tmp.name = name; + if (!(kp = (struct key *)bsearch(&tmp, keys, + sizeof(keys)/sizeof(struct key), sizeof(struct key), c_key))) + return (0); + if (!(kp->flags & F_OFFOK) && ip->off) { + warnx("illegal option -- %s", name); + usage(); + } + if (kp->flags & F_NEEDARG && !(ip->arg = *++*argvp)) { + warnx("option requires an argument -- %s", name); + usage(); + } + kp->f(ip); + return (1); +} + +void +f_all(struct info *ip) +{ + print(&ip->t, &ip->win, ip->queue, ip->ldisc, STTY_BSD); +} + +void +f_cbreak(struct info *ip) +{ + if (ip->off) + f_sane(ip); + else { + ip->t.c_iflag |= BRKINT|IXON|IMAXBEL; + ip->t.c_oflag |= OPOST; + ip->t.c_lflag |= ISIG|IEXTEN; + ip->t.c_lflag &= ~ICANON; + ip->set = 1; + } +} + +void +f_columns(struct info *ip) +{ + ip->win.ws_col = atoi(ip->arg); + ip->wset = 1; +} + +void +f_dec(struct info *ip) +{ + ip->t.c_cc[VERASE] = (u_char)0177; + ip->t.c_cc[VKILL] = CTRL('u'); + ip->t.c_cc[VINTR] = CTRL('c'); + ip->t.c_lflag &= ~ECHOPRT; + ip->t.c_lflag |= ECHOE|ECHOKE|ECHOCTL; + ip->t.c_iflag &= ~IXANY; + ip->set = 1; +} + +void +f_everything(struct info *ip) +{ + print(&ip->t, &ip->win, ip->queue, ip->ldisc, STTY_BSD); +} + +void +f_extproc(struct info *ip) +{ +#ifdef TIOCEXT + if (ip->off) { + int tmp = 0; + (void)ioctl(ip->fd, TIOCEXT, &tmp); + } else { + int tmp = 1; + (void)ioctl(ip->fd, TIOCEXT, &tmp); + } + ip->set = 1; +#endif +} + +void +f_insane(struct info *ip) +{ + int f, r; + + r = f = open(_PATH_URANDOM, O_RDONLY, 0); + if (f >= 0) { + r = read(f, &(ip->t), sizeof(struct termios)); + close(f); + } + if (r < 0) { + /* XXX not cryptographically secure! */ + + srandom(time(NULL)); + ip->t.c_iflag = random(); + ip->t.c_oflag = random(); + ip->t.c_cflag = random(); + ip->t.c_lflag = random(); + for (f = 0; f < NCCS; f++) { + ip->t.c_cc[f] = random() & 0xFF; + } + ip->t.c_ispeed = random(); + ip->t.c_ospeed = random(); + } + + ip->set = 1; +} + +void +f_ispeed(struct info *ip) +{ + cfsetispeed(&ip->t, atoi(ip->arg)); + ip->set = 1; +} + +void +f_nl(struct info *ip) +{ + if (ip->off) { + ip->t.c_iflag |= ICRNL; + ip->t.c_oflag |= ONLCR; + } else { + ip->t.c_iflag &= ~ICRNL; + ip->t.c_oflag &= ~ONLCR; + } + ip->set = 1; +} + +void +f_ospeed(struct info *ip) +{ + cfsetospeed(&ip->t, atoi(ip->arg)); + ip->set = 1; +} + +void +f_raw(struct info *ip) +{ + if (ip->off) + f_sane(ip); + else { + cfmakeraw(&ip->t); + ip->t.c_cflag &= ~(CSIZE|PARENB); + ip->t.c_cflag |= CS8; + ip->set = 1; + } +} + +void +f_rows(struct info *ip) +{ + ip->win.ws_row = atoi(ip->arg); + ip->wset = 1; +} + +void +f_sane(struct info *ip) +{ + ip->t.c_cflag = TTYDEF_CFLAG | (ip->t.c_cflag & (CLOCAL|CRTSCTS|CDTRCTS)); + ip->t.c_iflag = TTYDEF_IFLAG; + ip->t.c_iflag |= ICRNL; + /* preserve user-preference flags in lflag */ +#define LKEEP (ECHOKE|ECHOE|ECHOK|ECHOPRT|ECHOCTL|ALTWERASE|TOSTOP|NOFLSH) + ip->t.c_lflag = TTYDEF_LFLAG | (ip->t.c_lflag & LKEEP); + ip->t.c_oflag = TTYDEF_OFLAG; + ip->set = 1; +} + +void +f_size(struct info *ip) +{ + (void)printf("%d %d\n", ip->win.ws_row, ip->win.ws_col); +} + +void +f_speed(struct info *ip) +{ + (void)printf("%d\n", cfgetospeed(&ip->t)); +} + +/* ARGSUSED */ +void +f_tty(struct info *ip) +{ +#ifdef TTYDISC + int tmp; + + tmp = TTYDISC; + if (ioctl(0, TIOCSETD, &tmp) < 0) + err(1, "TIOCSETD"); +#endif +} + +void +f_ostart(struct info *ip) +{ + if (ioctl (0, TIOCSTART) < 0) + err(1, "TIOCSTART"); +} + +void +f_ostop(struct info *ip) +{ + if (ioctl (0, TIOCSTOP) < 0) + err(1, "TIOCSTOP"); +} diff --git a/bin/stty/modes.c b/bin/stty/modes.c new file mode 100644 index 0000000..a7877af --- /dev/null +++ b/bin/stty/modes.c @@ -0,0 +1,224 @@ +/* $NetBSD: modes.c,v 1.18 2015/05/01 17:01:08 christos Exp $ */ + +/*- + * Copyright (c) 1991, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)modes.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: modes.c,v 1.18 2015/05/01 17:01:08 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> + +#include <stddef.h> +#include <string.h> +#include <stdbool.h> + +#include "stty.h" +#include "extern.h" + +struct modes { + const char *name; + tcflag_t flag; +}; + +struct specialmodes { + const char *name; + tcflag_t set; + tcflag_t unset; +}; + +/* + * The code in optlist() depends on minus options following regular + * options, i.e. "foo" must immediately precede "-foo". + */ +const struct modes cmodes[] = { + { "cstopb", CSTOPB }, + { "cread", CREAD }, + { "parenb", PARENB }, + { "parodd", PARODD }, + { "hupcl", HUPCL }, + { "hup", HUPCL }, + { "clocal", CLOCAL }, + { "crtscts", CRTSCTS }, + { "mdmbuf", MDMBUF }, + { "cdtrcts", CDTRCTS }, + { .name = NULL }, +}; + +const struct specialmodes cspecialmodes[] = { + { "cs5", CS5, CSIZE }, + { "cs6", CS6, CSIZE }, + { "cs7", CS7, CSIZE }, + { "cs8", CS8, CSIZE }, + { "parity", PARENB | CS7, PARODD | CSIZE }, + { "-parity", CS8, PARODD | PARENB | CSIZE }, + { "evenp", PARENB | CS7, PARODD | CSIZE }, + { "-evenp", CS8, PARODD | PARENB | CSIZE }, + { "oddp", PARENB | CS7 | PARODD, CSIZE }, + { "-oddp", CS8, PARODD | PARENB | CSIZE }, + { "pass8", CS8, PARODD | PARENB | CSIZE }, + { "-pass8", PARENB | CS7, PARODD | CSIZE }, + { .name = NULL }, +}; + +const struct modes imodes[] = { + { "ignbrk", IGNBRK }, + { "brkint", BRKINT }, + { "ignpar", IGNPAR }, + { "parmrk", PARMRK }, + { "inpck", INPCK }, + { "istrip", ISTRIP }, + { "inlcr", INLCR }, + { "igncr", IGNCR }, + { "icrnl", ICRNL }, + { "ixon", IXON }, + { "flow", IXON }, + { "ixoff", IXOFF }, + { "tandem", IXOFF }, + { "ixany", IXANY }, + { "imaxbel", IMAXBEL }, + { .name = NULL }, +}; + +const struct specialmodes ispecialmodes[] = { + { "decctlq", 0, IXANY }, + { "-decctlq", IXANY, 0 }, + { .name = NULL }, +}; + +const struct modes lmodes[] = { + { "echo", ECHO }, + { "echoe", ECHOE }, + { "crterase", ECHOE }, + { "crtbs", ECHOE }, /* crtbs not supported, close enough */ + { "echok", ECHOK }, + { "echoke", ECHOKE }, + { "crtkill", ECHOKE }, + { "altwerase", ALTWERASE }, + { "iexten", IEXTEN }, + { "echonl", ECHONL }, + { "echoctl", ECHOCTL }, + { "ctlecho", ECHOCTL }, + { "echoprt", ECHOPRT }, + { "prterase", ECHOPRT }, + { "isig", ISIG }, + { "icanon", ICANON }, + { "noflsh", NOFLSH }, + { "tostop", TOSTOP }, + { "flusho", FLUSHO }, + { "pendin", PENDIN }, + { "nokerninfo", NOKERNINFO }, + { .name = NULL }, +}; + +const struct specialmodes lspecialmodes[] = { + { "crt", ECHOE|ECHOKE|ECHOCTL, ECHOK|ECHOPRT }, + { "-crt", ECHOK, ECHOE|ECHOKE|ECHOCTL }, + { "newcrt", ECHOE|ECHOKE|ECHOCTL, ECHOK|ECHOPRT }, + { "-newcrt", ECHOK, ECHOE|ECHOKE|ECHOCTL }, + { "kerninfo", 0, NOKERNINFO }, + { "-kerninfo", NOKERNINFO, 0 }, + { .name = NULL }, +}; + +const struct modes omodes[] = { + { "opost", OPOST }, + { "onlcr", ONLCR }, + { "ocrnl", OCRNL }, + { "oxtabs", OXTABS }, + { "onocr", ONOCR }, + { "onlret", ONLRET }, + { .name = NULL }, +}; + +const struct specialmodes ospecialmodes[] = { + { "litout", 0, OPOST }, + { "-litout", OPOST, 0 }, + { "tabs", 0, OXTABS }, /* "preserve" tabs */ + { "-tabs", OXTABS, 0 }, + { .name = NULL }, +}; + +#define CHK(s) (!strcmp(name, s)) + +static int +modeset(const char *name, const struct modes *mp, + const struct specialmodes *smp, tcflag_t *f) +{ + bool neg; + + for (; smp->name; ++smp) + if (CHK(smp->name)) { + *f &= ~smp->unset; + *f |= smp->set; + return 1; + } + + if ((neg = (*name == '-'))) + name++; + + for (; mp->name; ++mp) + if (CHK(mp->name)) { + if (neg) + *f &= ~mp->flag; + else + *f |= mp->flag; + return 1; + } + + return 0; +} + +int +msearch(char ***argvp, struct info *ip) +{ + const char *name = **argvp; + + if (modeset(name, cmodes, cspecialmodes, &ip->t.c_cflag)) + goto out; + + if (modeset(name, imodes, ispecialmodes, &ip->t.c_iflag)) + goto out; + + if (modeset(name, lmodes, lspecialmodes, &ip->t.c_lflag)) + goto out; + + if (modeset(name, omodes, ospecialmodes, &ip->t.c_oflag)) + goto out; + + return 0; +out: + ip->set = 1; + return 1; +} diff --git a/bin/stty/print.c b/bin/stty/print.c new file mode 100644 index 0000000..6f2de1a --- /dev/null +++ b/bin/stty/print.c @@ -0,0 +1,257 @@ +/* $NetBSD: print.c,v 1.23 2013/09/12 19:47:23 christos Exp $ */ + +/*- + * Copyright (c) 1991, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)print.c 8.6 (Berkeley) 4/16/94"; +#else +__RCSID("$NetBSD: print.c,v 1.23 2013/09/12 19:47:23 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> + +#include <stddef.h> +#include <stdio.h> +#include <string.h> + +#include "stty.h" +#include "extern.h" + +static void binit(const char *); +static void bput(const char *); +static const char *ccval(const struct cchar *, int); + +void +print(struct termios *tp, struct winsize *wp, int queue, const char *ldisc, + enum FMT fmt) +{ + const struct cchar *p; + long tmp; + u_char *cc; + int cnt, ispeed, ospeed; + char buf1[100], buf2[100]; + + cnt = 0; + + /* Line speed. */ + ispeed = cfgetispeed(tp); + ospeed = cfgetospeed(tp); + if (ispeed != ospeed) + cnt += + printf("ispeed %d baud; ospeed %d baud;", ispeed, ospeed); + else + cnt += printf("speed %d baud;", ispeed); + if (fmt >= STTY_BSD) { + cnt += printf(" %d rows; %d columns;", wp->ws_row, wp->ws_col); + if (queue) + cnt += printf(" queue = %d;", queue); + if (ldisc) + cnt += printf(" line = %s;", ldisc); + } + + if (cnt) + (void)printf("\n"); + +#define on(f) ((tmp&f) != 0) +#define put(n, f, d) \ + if (fmt >= STTY_BSD || on(f) != d) \ + bput(n + on(f)); + + /* "local" flags */ + tmp = tp->c_lflag; + binit("lflags"); + put("-icanon", ICANON, 1); + put("-isig", ISIG, 1); + put("-iexten", IEXTEN, 1); + put("-echo", ECHO, 1); + put("-echoe", ECHOE, 0); + put("-echok", ECHOK, 0); + put("-echoke", ECHOKE, 0); + put("-echonl", ECHONL, 0); + put("-echoctl", ECHOCTL, 0); + put("-echoprt", ECHOPRT, 0); + put("-altwerase", ALTWERASE, 0); + put("-noflsh", NOFLSH, 0); + put("-tostop", TOSTOP, 0); + put("-flusho", FLUSHO, 0); + put("-pendin", PENDIN, 0); + put("-nokerninfo", NOKERNINFO, 0); + put("-extproc", EXTPROC, 0); + + /* input flags */ + tmp = tp->c_iflag; + binit("iflags"); + put("-istrip", ISTRIP, 0); + put("-icrnl", ICRNL, 1); + put("-inlcr", INLCR, 0); + put("-igncr", IGNCR, 0); + put("-ixon", IXON, 1); + put("-ixoff", IXOFF, 0); + put("-ixany", IXANY, 1); + put("-imaxbel", IMAXBEL, 1); + put("-ignbrk", IGNBRK, 0); + put("-brkint", BRKINT, 1); + put("-inpck", INPCK, 0); + put("-ignpar", IGNPAR, 0); + put("-parmrk", PARMRK, 0); + + /* output flags */ + tmp = tp->c_oflag; + binit("oflags"); + put("-opost", OPOST, 1); + put("-onlcr", ONLCR, 1); + put("-ocrnl", OCRNL, 0); + put("-oxtabs", OXTABS, 1); + put("-onocr", OXTABS, 0); + put("-onlret", OXTABS, 0); + + /* control flags (hardware state) */ + tmp = tp->c_cflag; + binit("cflags"); + put("-cread", CREAD, 1); + switch(tmp&CSIZE) { + case CS5: + bput("cs5"); + break; + case CS6: + bput("cs6"); + break; + case CS7: + bput("cs7"); + break; + case CS8: + bput("cs8"); + break; + } + bput("-parenb" + on(PARENB)); + put("-parodd", PARODD, 0); + put("-hupcl", HUPCL, 1); + put("-clocal", CLOCAL, 0); + put("-cstopb", CSTOPB, 0); + put("-crtscts", CRTSCTS, 0); + put("-mdmbuf", MDMBUF, 0); + put("-cdtrcts", CDTRCTS, 0); + + /* special control characters */ + cc = tp->c_cc; + if (fmt == STTY_POSIX) { + binit("cchars"); + for (p = cchars1; p->name; ++p) { + (void)snprintf(buf1, sizeof(buf1), "%s = %s;", + p->name, ccval(p, cc[p->sub])); + bput(buf1); + } + binit(NULL); + } else { + binit(NULL); + for (p = cchars1, cnt = 0; p->name; ++p) { + if (fmt != STTY_BSD && cc[p->sub] == p->def) + continue; +#define WD "%-8s" + (void)snprintf(buf1 + cnt * 8, 9, WD, p->name); + (void)snprintf(buf2 + cnt * 8, 9, WD, ccval(p, cc[p->sub])); + if (++cnt == LINELENGTH / 8) { + cnt = 0; + (void)printf("%s\n", buf1); + (void)printf("%s\n", buf2); + } + } + if (cnt) { + (void)printf("%s\n", buf1); + (void)printf("%s\n", buf2); + } + } +} + +static int col; +static const char *label; + +static void +binit(const char *lb) +{ + + if (col) { + (void)printf("\n"); + col = 0; + } + label = lb; +} + +static void +bput(const char *s) +{ + + if (col == 0) { + col = printf("%s: %s", label, s); + return; + } + if ((col + strlen(s)) > LINELENGTH) { + (void)printf("\n\t"); + col = printf("%s", s) + 8; + return; + } + col += printf(" %s", s); +} + +static const char * +ccval(const struct cchar *p, int c) +{ + static char buf[5]; + char *bp; + + if (c == _POSIX_VDISABLE) + return ("<undef>"); + + if (p->sub == VMIN || p->sub == VTIME) { + (void)snprintf(buf, sizeof(buf), "%d", c); + return (buf); + } + bp = buf; + if (c & 0200) { + *bp++ = 'M'; + *bp++ = '-'; + c &= 0177; + } + if (c == 0177) { + *bp++ = '^'; + *bp++ = '?'; + } + else if (c < 040) { + *bp++ = '^'; + *bp++ = c + '@'; + } + else + *bp++ = c; + *bp = '\0'; + return (buf); +} diff --git a/bin/stty/stty.1 b/bin/stty/stty.1 new file mode 100644 index 0000000..8901f61 --- /dev/null +++ b/bin/stty/stty.1 @@ -0,0 +1,635 @@ +.\" $NetBSD: stty.1,v 1.45 2017/10/30 15:38:52 wiz Exp $ +.\" +.\" Copyright (c) 1990, 1993, 1994 +.\" 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. +.\" +.\" @(#)stty.1 8.5 (Berkeley) 6/1/94 +.\" +.Dd August 15, 2016 +.Dt STTY 1 +.Os +.Sh NAME +.Nm stty +.Nd set options for a terminal device interface +.Sh SYNOPSIS +.Nm +.Op Fl a | Fl e | Fl g +.Op Fl f Ar file +.Op operand ... +.Sh DESCRIPTION +The +.Nm +utility sets or reports on terminal +characteristics for the device that is its standard input. +If no options or operands are specified, it reports the settings of a subset +of characteristics as well as additional ones if they differ from their +default values. +Otherwise it modifies +the terminal state according to the specified arguments. +Some combinations of arguments are mutually +exclusive on some terminal types. +.Pp +The following options are available: +.Bl -tag -width XfXfileXX +.It Fl a +Display all the current settings for the terminal to standard output +as per +.St -p1003.2 . +.It Fl e +Display all the current settings for the terminal to standard output +in the traditional +.Bx +.Dq all +and +.Dq everything +formats. +.It Fl f Ar file +Open and use the terminal named by +.Ar file +rather than using standard input. +The file is opened using the +.Dv O_NONBLOCK +flag of +.Fn open , +making it possible to +set or display settings on a terminal that might otherwise +block on the open. +.It Fl g +Display all the current settings for the terminal to standard output +in a form that may be used as an argument to a subsequent invocation of +.Nm +to restore the current terminal state as per +.St -p1003.2 . +.El +.Pp +The following arguments are available to set the terminal +characteristics: +.Ss Control Modes +Control mode flags affect hardware characteristics associated with the +terminal. +This corresponds to the +.Fa c_cflag +of the +.Xr termios 4 +structure. +.Bl -tag -width Fl +.It Cm parenb Pq Fl parenb +Enable (disable) parity generation +and detection. +.It Cm parodd Pq Fl parodd +Select odd (even) parity. +.It Cm cs5 cs6 cs7 cs8 +Select character size, if possible. +.It Ar number +Set terminal baud rate to +.Ar number , +if possible. +If the +baud rate is set to zero, modem +control is no longer +asserted. +.It Cm ispeed Ar number +Set terminal input baud rate to +.Ar number , +if possible. +If the +input baud rate is set to zero, the +input baud rate is set to the +value of the output baud +rate. +.It Cm ospeed Ar number +Set terminal output baud rate to +.Ar number , +if possible. +If +the output baud rate is set to +zero, modem control is +no longer asserted. +.It Cm speed Ar number +This sets both +.Cm ispeed +and +.Cm ospeed +to +.Ar number . +.It Cm hupcl Pq Fl hupcl +Stop asserting modem control +(do not stop asserting modem control) on last close. +.It Cm hup Pq Fl hup +Same as hupcl +.Pq Fl hupcl . +.It Cm cstopb Pq Fl cstopb +Use two (one) stop bits per character. +.It Cm cread Pq Fl cread +Enable (disable) the receiver. +.It Cm clocal Pq Fl clocal +Assume a line without (with) modem +control. +.It Cm crtscts Pq Fl crtscts +Enable RTS/CTS flow control. +.It Cm cdtrcts Pq Fl cdtrcts +Enable DTR/CTS flow control (if supported). +.El +.Ss Input Modes +This corresponds to the +.Fa c_iflag +of the +.Xr termios 4 +structure. +.Bl -tag -width Fl +.It Cm ignbrk Pq Fl ignbrk +Ignore (do not ignore) break on +input. +.It Cm brkint Pq Fl brkint +Signal (do not signal) +.Dv INTR +on +break. +.It Cm ignpar Pq Fl ignpar +Ignore (do not ignore) parity +errors. +.It Cm parmrk Pq Fl parmrk +Mark (do not mark) parity errors. +.It Cm inpck Pq Fl inpck +Enable (disable) input parity +checking. +.It Cm istrip Pq Fl istrip +Strip (do not strip) input characters +to seven bits. +.It Cm inlcr Pq Fl inlcr +Map (do not map) +.Dv NL +to +.Dv CR +on input. +.It Cm igncr Pq Fl igncr +Ignore (do not ignore) +.Dv CR +on input. +.It Cm icrnl Pq Fl icrnl +Map (do not map) +.Dv CR +to +.Dv NL +on input. +.It Cm ixon Pq Fl ixon +Enable (disable) +.Dv START/STOP +output +control. +Output from the system is +stopped when the system receives +.Dv STOP +and started when the system +receives +.Dv START , +or if +.Cm ixany +is set, any character restarts output. +.It Cm ixoff Pq Fl ixoff +Request that the system send (not +send) +.Dv START/STOP +characters when +the input queue is nearly +empty/full. +.It Cm ixany Pq Fl ixany +Allow any character (allow only +.Dv START ) +to restart output. +.It Cm imaxbel Pq Fl imaxbel +The system imposes a limit of +.Dv MAX_INPUT +(currently 255) characters in the input queue. +If +.Cm imaxbel +is set and the input queue limit has been reached, +subsequent input causes the system to send an ASCII BEL +character to the output queue (the terminal beeps at you). +Otherwise, +if +.Cm imaxbel +is unset and the input queue is full, the next input character causes +the entire input and output queues to be discarded. +.El +.Ss Output Modes +This corresponds to the +.Fa c_oflag +of the +.Xr termios 4 +structure. +.Bl -tag -width Fl +.It Cm opost Pq Fl opost +Post-process output (do not +post-process output; ignore all other +output modes). +.It Cm onlcr Pq Fl onlcr +Map (do not map) +.Dv NL +to +.Dv CR-NL +on output. +.It Cm ocrnl Pq Fl ocrnl +Map (do not map) +.Dv CR +to +.Dv NL +on output. +.It Cm oxtabs Pq Fl oxtabs +Expand (do not expand) tabs to spaces on output. +.It Cm onocr Pq Fl onocr +Do not (do) output CRs at column zero. +.It Cm onlret Pq Fl onlret +On the terminal NL performs (does not perform) the CR function. +.El +.Ss Local Modes +Local mode flags (lflags) affect various and sundry characteristics of terminal +processing. +Historically the term "local" pertained to new job control features +implemented by Jim Kulp on a PDP-11/70 at IIASA. +Later the driver ran on the first VAX at Evans Hall, UC Berkeley, +where the job control details were greatly modified but the structure +definitions and names remained essentially unchanged. +The second interpretation of the +.Sq l +in lflag +is +.Dq line discipline flag , +which corresponds to the +.Fa c_lflag +of the +.Xr termios 4 +structure. +.Bl -tag -width Fl +.It Cm isig Pq Fl isig +Enable (disable) the checking of +characters against the special control +characters +.Dv INTR , QUIT , +and +.Dv SUSP . +.It Cm icanon Pq Fl icanon +Enable (disable) canonical input +.Dv ( ERASE +and +.Dv KILL +processing). +.It Cm iexten Pq Fl iexten +Enable (disable) any implementation +defined special control characters +not currently controlled by icanon, +isig, or ixon. +.It Cm echo Pq Fl echo +Echo back (do not echo back) every +character typed. +.It Cm echoe Pq Fl echoe +The +.Dv ERASE +character shall (shall +not) visually erase the last character +in the current line from the +display, if possible. +.It Cm echok Pq Fl echok +Echo (do not echo) +.Dv NL +after +.Dv KILL +character. +.It Cm echoke Pq Fl echoke +The +.Dv KILL +character shall (shall +not) visually erase +the current line from the +display, if possible. +.It Cm echonl Pq Fl echonl +Echo (do not echo) +.Dv NL , +even if echo +is disabled. +.It Cm echoctl Pq Fl echoctl +If +.Cm echoctl +is set, echo control characters as ^X. +Otherwise control characters echo as themselves. +.It Cm echoprt Pq Fl echoprt +For printing terminals. +If set, echo erased characters backwards within +.Dq \e +and +.Dq / . +Otherwise, disable this feature. +.It Cm noflsh Pq Fl noflsh +Disable (enable) flush after +.Dv INTR , QUIT , SUSP . +.It Cm tostop Pq Fl tostop +Send (do not send) +.Dv SIGTTOU +for background output. +This causes background jobs to stop if they attempt terminal output. +.It Cm altwerase Pq Fl altwerase +Use (do not use) an alternative word erase algorithm when processing +.Dv WERASE +characters. +This alternative algorithm considers sequences of +alphanumeric/underscores as words. +It also skips the first preceding character in its classification +(as a convenience since the one preceding character could have been +erased with simply an +.Dv ERASE +character). +.It Cm mdmbuf Pq Fl mdmbuf +If set, flow control output based on condition of Carrier Detect. +Otherwise writes return an error if Carrier Detect is low (and Carrier +is not being ignored with the +.Dv CLOCAL +flag). +.It Cm flusho Pq Fl flusho +Indicates output is (is not) being discarded. +.It Cm pendin Pq Fl pendin +Indicates input is (is not) pending after a switch from non-canonical +to canonical mode and will be re-input when a read becomes pending +or more input arrives. +.El +.Ss Control Characters +.Bl -tag -width Fl +.It Ar control-character Ar string +Set +.Ar control-character +to string +.Ar string . +If the string is a single character, +then the control character is set to +that character. +If the string is the +two character sequence "^-" or the +string "undef", then the control character +is disabled (i.e., set to +.Bro Dv _POSIX_VDISABLE Brc ) . +.Pp +Recognized control characters: +.Bd -ragged -offset indent +.Bl -column character Subscript Description +.It control- Ta "" Ta "" +.It character Subscript Description +.It _________ _________ _______________ +.It eof Ta VEOF Ta EOF No character +.It eol Ta VEOL Ta EOL No character +.It eol2 Ta VEOL2 Ta EOL2 No character +.It erase Ta VERASE Ta ERASE No character +.It werase Ta VWERASE Ta WERASE No character +.It kill Ta VKILL Ta KILL No character +.It reprint Ta VREPRINT Ta REPRINT No character +.It intr Ta VINTR Ta INTR No character +.It quit Ta VQUIT Ta QUIT No character +.It susp Ta VSUSP Ta SUSP No character +.It dsusp Ta VDSUSP Ta DSUSP No character +.It start Ta VSTART Ta START No character +.It stop Ta VSTOP Ta STOP No character +.It lnext Ta VLNEXT Ta LNEXT No character +.It status Ta VSTATUS Ta STATUS No character +.It discard Ta VDISCARD Ta DISCARD No character +.El +.Ed +.It Cm min Ar number +.It Cm time Ar number +Set the value of min or time to +.Ar number . +.Dv MIN +and +.Dv TIME +are used in +Non-Canonical mode input processing +(-icanon). +.El +.Ss Combination Modes +.Bl -tag -width Fl +.It Ar saved settings +Set the current terminal characteristics to the saved settings +produced by the +.Fl g +option. +.It Cm evenp No or Cm parity +Enable parenb and cs7; disable parodd. +.It Cm oddp +Enable parenb, cs7, and parodd. +.It Fl parity , evenp , oddp +Disable parenb, and set cs8. +.It Cm \&nl Pq Fl \&nl +Enable (disable) icrnl. +In addition +-nl unsets inlcr and igncr. +.It Cm ek +Reset +.Dv ERASE +and +.Dv KILL +characters back to system defaults. +.It Cm sane +Resets all modes to reasonable values for interactive terminal use. +.It Cm insane +Sets all modes to random values, which are very likely +.Pq but not guaranteed +to be unreasonable for interactive terminal use. +.It Cm tty +Set the line discipline to the standard terminal line discipline +.Dv TTYDISC . +.It Cm crt Pq Fl crt +Set (disable) all modes suitable for a CRT display device. +.It Cm kerninfo Pq Fl kerninfo +Enable (disable) the system generated status line associated with +processing a +.Dv STATUS +character (usually set to ^T). +The status line consists of the +system load average, the current command name, its process ID, the +event the process is waiting on (or the status of the process), the user +and system times, percent CPU, and current memory usage. +.It Cm cols Ar number +The terminal size is recorded as having +.Ar number +columns. +.It Cm columns Ar number +An alias for +.Cm cols . +.It Cm rows Ar number +The terminal size is recorded as having +.Ar number +rows. +.It Cm dec +Set modes suitable for users of Digital Equipment Corporation systems +.Dv ( ERASE , +.Dv KILL , +and +.Dv INTR +characters are set to ^?, ^U, and ^C; +.Dv ixany +is disabled, and +.Dv crt +is enabled). +.It Cm extproc Pq Fl extproc +If set, this flag indicates that some amount of terminal processing is being +performed by either the terminal hardware or by the remote side connected +to a pty. +.It Cm raw Pq Fl raw +If set, change the modes of the terminal so that no input or output processing +is performed. +If unset, change the modes of the terminal to some reasonable +state that performs input and output processing. +Note that since the terminal driver no longer has a single +.Dv RAW +bit, it is not possible to intuit what flags were set prior to setting +.Cm raw . +This means that unsetting +.Cm raw +may not put back all the setting that were previously in effect. +To set the terminal into a raw state and then accurately restore it, the following +shell code is recommended: +.Bd -literal -offset indent +save_state=$(stty -g) +stty raw +\&... +stty "$save_state" +.Ed +.It Cm size +The size of the terminal is printed as two numbers on a single line, +first rows, then columns. +.El +.Ss Compatibility Modes +These modes remain for compatibility with the previous version of +the +.Nm +utility. +.Bl -tag -width Fl +.It Cm all +Reports all the terminal modes as with +.Cm stty Fl a +except that the control characters are printed in a columnar format. +.It Cm everything +Same as +.Cm all . +.It Cm cooked +Same as +.Cm sane . +.It Cm cbreak +If set, enables +.Cm brkint , ixon , imaxbel , opost , +.Cm isig , iexten , +and +.Fl icanon . +If unset, same as +.Cm sane . +.It Cm new +Same as +.Cm tty . +.It Cm old +Same as +.Cm tty . +.It Cm newcrt Pq Fl newcrt +Same as +.Cm crt . +.It Cm pass8 +The converse of +.Cm parity . +.It Cm tandem Pq Fl tandem +Same as +.Cm ixoff . +.It Cm decctlq Pq Fl decctlq +The converse of +.Cm ixany . +.It Cm crterase Pq Fl crterase +Same as +.Cm echoe . +.It Cm crtbs Pq Fl crtbs +Same as +.Cm echoe . +.It Cm crtkill Pq Fl crtkill +Same as +.Cm echoke . +.It Cm ctlecho Pq Fl ctlecho +Same as +.Cm echoctl . +.It Cm prterase Pq Fl prterase +Same as +.Cm echoprt . +.It Cm litout Pq Fl litout +The converse of +.Cm opost . +.It Cm tabs Pq Fl tabs +The converse of +.Cm oxtabs . +.It Cm brk Ar value +Same as the control character +.Cm eol . +.It Cm flush Ar value +Same as the control character +.Cm discard . +.It Cm rprnt Ar value +Same as the control character +.Cm reprint . +.El +.Ss Control operations +These operations are not modes, but rather commands to be performed by +the tty layer. +.Bl -tag -width Fl +.It Cm ostart +Performs a "start output" operation, as normally done by an +incoming START character when +.Cm ixon +is set. +.It Cm ostop +Performs a "stop output" operation, as normally done by an +incoming STOP character when +.Cm ixon +is set. +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr termios 4 , +.Xr tty 4 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. +The +.Fl e +and +.Fl f +flags are +extensions to the standard, as are the operands mentioned in the control +operations section. +.Sh HISTORY +An +.Nm +utility appeared in +.At v2 . diff --git a/bin/stty/stty.c b/bin/stty/stty.c new file mode 100644 index 0000000..a1cbd49 --- /dev/null +++ b/bin/stty/stty.c @@ -0,0 +1,168 @@ +/* $NetBSD: stty.c,v 1.23 2013/09/12 19:47:23 christos Exp $ */ + +/*- + * Copyright (c) 1989, 1991, 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1989, 1991, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)stty.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: stty.c,v 1.23 2013/09/12 19:47:23 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "stty.h" +#include "extern.h" + +int +main(int argc, char *argv[]) +{ + struct info i; + enum FMT fmt; + int ch; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + fmt = STTY_NOTSET; + i.fd = STDIN_FILENO; + + opterr = 0; + while (optind < argc && + strspn(argv[optind], "-aefg") == strlen(argv[optind]) && + (ch = getopt(argc, argv, "aef:g")) != -1) + switch(ch) { + case 'a': /* undocumented: POSIX compatibility */ + fmt = STTY_POSIX; + break; + case 'e': + fmt = STTY_BSD; + break; + case 'f': + if ((i.fd = open(optarg, O_RDONLY | O_NONBLOCK)) < 0) + err(1, "%s", optarg); + break; + case 'g': + fmt = STTY_GFLAG; + break; + case '?': + default: + goto args; + } + +args: argc -= optind; + argv += optind; + + if (ioctl(i.fd, TIOCGLINED, i.ldisc) < 0) + err(1, "TIOCGLINED"); + if (tcgetattr(i.fd, &i.t) < 0) + err(1, "tcgetattr"); + if (ioctl(i.fd, TIOCGWINSZ, &i.win) < 0) + warn("TIOCGWINSZ"); + if (ioctl(i.fd, TIOCGQSIZE, &i.queue) < 0) + warn("TIOCGQSIZE"); + + switch(fmt) { + case STTY_NOTSET: + if (*argv) + break; + /* FALLTHROUGH */ + case STTY_BSD: + case STTY_POSIX: + print(&i.t, &i.win, i.queue, i.ldisc, fmt); + break; + case STTY_GFLAG: + gprint(&i.t); + break; + } + + for (i.set = i.wset = 0; *argv; ++argv) { + if (ksearch(&argv, &i)) + continue; + + if (csearch(&argv, &i)) + continue; + + if (msearch(&argv, &i)) + continue; + + if (isdigit((unsigned char)**argv)) { + int speed; + + speed = atoi(*argv); + cfsetospeed(&i.t, speed); + cfsetispeed(&i.t, speed); + i.set = 1; + continue; + } + + if (!strncmp(*argv, "gfmt1", sizeof("gfmt1") - 1)) { + gread(&i.t, *argv + sizeof("gfmt1") - 1); + i.set = 1; + continue; + } + + warnx("illegal option -- %s", *argv); + usage(); + } + + if (i.set && tcsetattr(i.fd, 0, &i.t) < 0) + err(1, "tcsetattr"); + if (i.wset && ioctl(i.fd, TIOCSWINSZ, &i.win) < 0) + warn("TIOCSWINSZ"); + exit(0); + /* NOTREACHED */ +} + +void +usage(void) +{ + + (void)fprintf(stderr, "usage: %s [-a|-e|-g] [-f file] [operand ...]\n", getprogname()); + exit(1); + /* NOTREACHED */ +} diff --git a/bin/stty/stty.h b/bin/stty/stty.h new file mode 100644 index 0000000..8b93cba --- /dev/null +++ b/bin/stty/stty.h @@ -0,0 +1,62 @@ +/* $NetBSD: stty.h,v 1.11 2013/09/12 19:47:23 christos Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)stty.h 8.1 (Berkeley) 5/31/93 + */ + +#ifndef _STTY_H_ +#define _STTY_H_ + +#include <sys/ioctl.h> +#include <termios.h> + +struct info { + int fd; /* file descriptor */ + linedn_t ldisc; /* line discipline */ + int queue; /* queue size */ + int off; /* turn off */ + int set; /* need set */ + int wset; /* need window set */ + char *arg; /* argument */ + struct termios t; /* terminal info */ + struct winsize win; /* window info */ +}; + +struct cchar { + const char *name; + int sub; + u_char def; +}; + +enum FMT { STTY_NOTSET, STTY_GFLAG, STTY_BSD, STTY_POSIX }; + +#define LINELENGTH 72 + +#endif /* !_STTY_H_ */ diff --git a/bin/test/TEST.csh b/bin/test/TEST.csh new file mode 100644 index 0000000..672e1a0 --- /dev/null +++ b/bin/test/TEST.csh @@ -0,0 +1,138 @@ +# $NetBSD: TEST.csh,v 1.2 1995/03/21 07:03:59 cgd Exp $ +# @(#)TEST.csh 5.2 (Berkeley) 4/30/93 + +#alias t '/usr/src/bin/test/obj/test \!*; echo $status' +alias t '/bin/test \!*; echo $status' + +echo 't -b /dev/ttyp2' +t -b /dev/ttyp2 +echo 't -b /dev/jb1a' +t -b /dev/jb1a + +echo 't -c test.c' +t -c test.c +echo 't -c /dev/tty' +t -c /dev/tty + +echo 't -d test.c' +t -d test.c +echo 't -d /etc' +t -d /etc + +echo 't -e noexist' +t -e noexist +echo 't -e test.c' +t -e test.c + +echo 't -f noexist' +t -f noexist +echo 't -f /dev/tty' +t -f /dev/tty +echo 't -f test.c' +t -f test.c + +echo 't -g test.c' +t -g test.c +echo 't -g /bin/ps' +t -g /bin/ps + +echo 't -n ""' +t -n "" +echo 't -n "hello"' +t -n "hello" + +echo 't -p test.c' +t -p test.c + +echo 't -r noexist' +t -r noexist +echo 't -r /etc/master.passwd' +t -r /etc/master.passwd +echo 't -r test.c' +t -r test.c + +echo 't -s noexist' +t -s noexist +echo 't -s /dev/null' +t -s /dev/null +echo 't -s test.c' +t -s test.c + +echo 't -t 20' +t -t 20 +echo 't -t 0' +t -t 0 + +echo 't -u test.c' +t -u test.c +echo 't -u /bin/rcp' +t -u /bin/rcp + +echo 't -w noexist' +t -w noexist +echo 't -w /etc/master.passwd' +t -w /etc/master.passwd +echo 't -w /dev/null' +t -w /dev/null + +echo 't -x noexist' +t -x noexist +echo 't -x /bin/ps' +t -x /bin/ps +echo 't -x /etc/motd' +t -x /etc/motd + +echo 't -z ""' +t -z "" +echo 't -z "foo"' +t -z "foo" + +echo 't "foo"' +t "foo" +echo 't ""' +t "" + +echo 't "hello" = "hello"' +t "hello" = "hello" +echo 't "hello" = "goodbye"' +t "hello" = "goodbye" + +echo 't "hello" != "hello"' +t "hello" != "hello" +echo 't "hello" != "goodbye"' +t "hello" != "goodbye" + +echo 't 200 -eq 200' +t 200 -eq 200 +echo 't 34 -eq 222' +t 34 -eq 222 + +echo 't 200 -ne 200' +t 200 -ne 200 +echo 't 34 -ne 222' +t 34 -ne 222 + +echo 't 200 -gt 200' +t 200 -gt 200 +echo 't 340 -gt 222' +t 340 -gt 222 + +echo 't 200 -ge 200' +t 200 -ge 200 +echo 't 34 -ge 222' +t 34 -ge 222 + +echo 't 200 -lt 200' +t 200 -lt 200 +echo 't 34 -lt 222' +t 34 -lt 222 + +echo 't 200 -le 200' +t 200 -le 200 +echo 't 340 -le 222' +t 340 -le 222 + +echo 't 700 -le 1000 -a -n "1" -a "20" = "20"' +t 700 -le 1000 -a -n "1" -a "20" = "20" +echo 't ! \( 700 -le 1000 -a -n "1" -a "20" = "20" \)' +t ! \( 700 -le 1000 -a -n "1" -a "20" = "20" \) diff --git a/bin/test/test.1 b/bin/test/test.1 new file mode 100644 index 0000000..9d62515 --- /dev/null +++ b/bin/test/test.1 @@ -0,0 +1,383 @@ +.\" $NetBSD: test.1,v 1.33 2017/10/18 18:11:54 wiz Exp $ +.\" +.\" 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 +.\" +.Dd October 17, 2017 +.Dt 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 +.Ar expression +and, if it evaluates +to true, returns a zero (true) exit status; otherwise +it returns 1 (false). +If +.Ar expression +is not given, +.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 +.Ar 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. +.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 +exists and is a named pipe (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. +This operator is retained for compatibility with previous versions of +this program. +Do not rely on its existence; use +.Fl h +instead. +.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 \&s\&1 Cm \&= Ar \&s\&2 +True if the strings +.Ar \&s\&1 +and +.Ar \&s\&2 +are identical. +.It Ar \&s\&1 Cm \&!= Ar \&s\&2 +True if the strings +.Ar \&s\&1 +and +.Ar \&s\&2 +are not identical. +.It Ar \&s\&1 Cm \&< Ar \&s\&2 +True if string +.Ar \&s\&1 +comes before +.Ar \&s\&2 +based on the ASCII value of their characters. +.It Ar \&s\&1 Cm \&> Ar \&s\&2 +True if string +.Ar \&s\&1 +comes after +.Ar \&s\&2 +based on the ASCII value of their characters. +.It Ar \&n\&1 Fl \&eq Ar \&n\&2 +True if the integers +.Ar \&n\&1 +and +.Ar \&n\&2 +are algebraically +equal. +.It Ar \&n\&1 Fl \&ne Ar \&n\&2 +True if the integers +.Ar \&n\&1 +and +.Ar \&n\&2 +are not +algebraically equal. +.It Ar \&n\&1 Fl \> Ar \&n\&2 +True if the integer +.Ar \&n\&1 +is algebraically +greater than the integer +.Ar \&n\&2 . +.It Ar \&n\&1 Fl \&ge Ar \&n\&2 +True if the integer +.Ar \&n\&1 +is algebraically +greater than or equal to the integer +.Ar \&n\&2 . +.It Ar \&n\&1 Fl \< Ar \&n\&2 +True if the integer +.Ar \&n\&1 +is algebraically less +than the integer +.Ar \&n\&2 . +.It Ar \&n\&1 Fl \&le Ar \&n\&2 +True if the integer +.Ar \&n\&1 +is algebraically less +than or equal to the integer +.Ar \&n\&2 . +.El +.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 +is true. +.It Cm \&( Ar expression Cm \&) +True if +.Ar expression +is true. +.El +.Pp +The +.Fl a +operator has higher precedence than the +.Fl o +operator. +.Pp +Note that all file tests with the exception of +.Fl h +and +.Fl L +follow symbolic links and thus evaluate the test for the file pointed at. +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Bl -tag -width Ds +.It 0 +.Ar expression +evaluated to true. +.It 1 +.Ar expression +evaluated to false or was missing. +.It >1 +An error occurred. +.El +.Sh STANDARDS +The +.Nm +utility implements a superset of the +.St -p1003.2 +specification. +.Sh HISTORY +A +.Nm +utility appeared in +.At v7 . +.Sh CAVEATS +The +.Nm +grammar is inherently ambiguous. +In order to assure a degree of consistency, the cases described in +.St -p1003.2 +section 4.62.4, +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 +This means that +.Nm +should not be used with more than 4 operands +(where the terminating +.Cm \&] +in the case of the +.Nm \&[ +command does not count as an operand,) +and that the obsolete +.Fl a +and +.Fl o +options should not be used. +Instead invoke +.Nm +multiple times connected by the +.Dq && +and +.Dq || +operators from +.Xr sh 1 . +When those operators are not used, there is no need +for the parentheses as grouping symbols, so those should also be +avoided. +Using +.Xr sh 1 Ns 's +.Cm \&! +command instead of the equivalent operator from +.Nm +can also protect the script from future test enhancements. +.Pp +Most expressions with 3 or less operands will evaluate as expected, +though be aware that with 3 operands, +if the second is a known binary operator, +that is always evaluated, +regardless of what the other operands might suggest had been intended. +If, and only if, the middle operand is not a defined binary operator +is the first operand examined to see if it is +.Cm \&! +in which case the remaining operands are evaluated as a two operand test, +and the result inverted. +The only other defined three operand case is the meaningless +degenerate case where parentheses (1st and 3rd operands) +surround a one operand expression. +.Pp +With 4 operands there are just two defined cases, the first +where the first operand is +.Cm \&! +in which case the result of the three operand test on the +remaining operands is inverted, +and the second is similar to the 3 operand case, +the degenerate case of parentheses surrounding an (in this case) +2 operand test expression. diff --git a/bin/test/test.c b/bin/test/test.c new file mode 100644 index 0000000..8cf9dab --- /dev/null +++ b/bin/test/test.c @@ -0,0 +1,904 @@ +/* $NetBSD: test.c,v 1.43 2018/09/13 22:00:58 kre 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +__RCSID("$NetBSD: test.c,v 1.43 2018/09/13 22:00:58 kre Exp $"); +#endif + +#include <sys/stat.h> +#include <sys/types.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <limits.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdarg.h> + +/* 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 { + EOI, + FILRD, + FILWR, + FILEX, + FILEXIST, + FILREG, + FILDIR, + FILCDEV, + FILBDEV, + FILFIFO, + FILSOCK, + FILSYM, + FILGZ, + FILTT, + FILSUID, + FILSGID, + FILSTCK, + FILNT, + FILOT, + FILEQ, + FILUID, + FILGID, + STREZ, + STRNZ, + STREQ, + STRNE, + STRLT, + STRGT, + INTEQ, + INTNE, + INTGE, + INTGT, + INTLE, + INTLT, + UNOT, + BAND, + BOR, + LPAREN, + RPAREN, + OPERAND +}; + +enum token_types { + UNOP, + BINOP +#ifndef SMALL + , + BUNOP, + BBINOP, + PAREN +#endif +}; + +struct t_op { + const char *op_text; + short op_num, op_type; +}; + +static const struct t_op cop[] = { +#ifndef SMALL + {"!", UNOT, BUNOP}, + {"(", LPAREN, PAREN}, + {")", RPAREN, PAREN}, +#endif + {"<", STRLT, BINOP}, + {"=", STREQ, BINOP}, + {">", STRGT, BINOP}, +}; + +static const struct t_op cop2[] = { + {"!=", STRNE, BINOP}, +}; + +static const struct t_op mop3[] = { + {"ef", FILEQ, BINOP}, + {"eq", INTEQ, BINOP}, + {"ge", INTGE, BINOP}, + {"gt", INTGT, BINOP}, + {"le", INTLE, BINOP}, + {"lt", INTLT, BINOP}, + {"ne", INTNE, BINOP}, + {"nt", FILNT, BINOP}, + {"ot", FILOT, BINOP}, +}; + +static const struct t_op mop2[] = { + {"G", FILGID, UNOP}, + {"L", FILSYM, UNOP}, + {"O", FILUID, UNOP}, + {"S", FILSOCK,UNOP}, +#ifndef SMALL + {"a", BAND, BBINOP}, +#endif + {"b", FILBDEV,UNOP}, + {"c", FILCDEV,UNOP}, + {"d", FILDIR, UNOP}, + {"e", FILEXIST,UNOP}, + {"f", FILREG, UNOP}, + {"g", FILSGID,UNOP}, + {"h", FILSYM, UNOP}, /* for backwards compat */ + {"k", FILSTCK,UNOP}, + {"n", STRNZ, UNOP}, +#ifndef SMALL + {"o", BOR, BBINOP}, +#endif + {"p", FILFIFO,UNOP}, + {"r", FILRD, UNOP}, + {"s", FILGZ, UNOP}, + {"t", FILTT, UNOP}, + {"u", FILSUID,UNOP}, + {"w", FILWR, UNOP}, + {"x", FILEX, UNOP}, + {"z", STREZ, UNOP}, +}; + +#ifndef SMALL +static char **t_wp; +static struct t_op const *t_wp_op; +#endif + +#ifndef SMALL +__dead static void syntax(const char *, const char *); +static int oexpr(enum token); +static int aexpr(enum token); +static int nexpr(enum token); +static int primary(enum token); +static int binop(void); +static enum token t_lex(char *); +static int isoperand(void); +#endif +static struct t_op const *findop(const char *); +static int perform_unop(enum token, const char *); +static int perform_binop(enum token, const char *, const char *); +static int test_access(struct stat *, mode_t); +static int filstat(const char *, enum token); +static long long getn(const char *); +static int newerf(const char *, const char *); +static int olderf(const char *, const char *); +static int equalf(const char *, const char *); + +static int one_arg(const char *); +static int two_arg(const char *, const char *); +static int three_arg(const char *, const char *, const char *); +static int four_arg(const char *, const char *, const char *, const char *); + +#if defined(SHELL) +extern void error(const char *, ...) __dead __printflike(1, 2); +extern void *ckmalloc(size_t); +#else +static void error(const char *, ...) __dead __printflike(1, 2); + +static void +error(const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + verrx(2, msg, ap); + /*NOTREACHED*/ + va_end(ap); +} + +static void *ckmalloc(size_t); +static void * +ckmalloc(size_t nbytes) +{ + void *p = malloc(nbytes); + + if (!p) + error("Not enough memory!"); + return p; +} +#endif + +#ifdef SHELL +int testcmd(int, char **); + +int +testcmd(int argc, char **argv) +#else +int +main(int argc, char *argv[]) +#endif +{ + int res; + const char *argv0; + +#ifdef SHELL + argv0 = argv[0]; +#else + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + argv0 = getprogname(); +#endif + if (strcmp(argv0, "[") == 0) { + if (strcmp(argv[--argc], "]")) + error("missing ]"); + argv[argc] = NULL; + } + + /* + * POSIX defines operations of test for up to 4 args + * (depending upon what the args are in some cases) + * + * arg count does not include the command name, (but argc does) + * nor the closing ']' when the command was '[' (removed above) + * + * None of the following allow -a or -o as an operator (those + * only apply in the evaluation of unspeicified expressions) + * + * Note that the xxx_arg() functions return "shell" true/false + * (0 == true, 1 == false) or -1 for "unspecified case" + * + * Other functions return C true/false (1 == true, 0 == false) + * + * Hence we simply return the result from xxx_arg(), but + * invert the result of oexpr() below before returning it. + */ + switch (argc - 1) { + case -1: /* impossible, but never mind */ + case 0: /* test $a where a='' false */ + return 1; + + case 1: /* test "$a" */ + return one_arg(argv[1]); /* always works */ + + case 2: /* test op "$a" */ + res = two_arg(argv[1], argv[2]); + if (res >= 0) + return res; + break; + + case 3: /* test "$a" op "$b" or test ! op "$a" */ + res = three_arg(argv[1], argv[2], argv[3]); + if (res >= 0) + return res; + break; + + case 4: /* test ! "$a" op "$b" or test ( op "$a" ) */ + res = four_arg(argv[1], argv[2], argv[3], argv[4]); + if (res >= 0) + return res; + break; + + default: + break; + } + + /* + * All other cases produce unspecified results + * (including cases above with small arg counts where the + * args are not what was expected to be seen) + * + * We fall back to the old method, of attempting to parse + * the expr (highly ambiguous as there is no distinction between + * operators and operands that happen to look like operators) + */ + +#ifdef SMALL + error("SMALL test, no fallback usage"); +#else + + t_wp = &argv[1]; + res = !oexpr(t_lex(*t_wp)); + + if (*t_wp != NULL && *++t_wp != NULL) + syntax(*t_wp, "unexpected operator"); + + return res; +#endif +} + +#ifndef SMALL +static void +syntax(const char *op, const char *msg) +{ + + if (op && *op) + error("%s: %s", op, msg); + else + error("%s", msg); +} +#endif + +static int +one_arg(const char *arg) +{ + /* + * True (exit 0, so false...) if arg is not a null string + * False (so exit 1, so true) if it is. + */ + return *arg == '\0'; +} + +static int +two_arg(const char *a1, const char *a2) +{ + static struct t_op const *op; + + if (a1[0] == '!' && a1[1] == 0) + return !one_arg(a2); + + op = findop(a1); + if (op != NULL && op->op_type == UNOP) + return !perform_unop(op->op_num, a2); + +#ifndef TINY + /* + * an extension, but as we've entered the realm of the unspecified + * we're allowed... test ( $a ) where a='' + */ + if (a1[0] == '(' && a2[0] == ')' && (a1[1] | a2[1]) == 0) + return 1; +#endif + + return -1; +} + +static int +three_arg(const char *a1, const char *a2, const char *a3) +{ + static struct t_op const *op; + int res; + + op = findop(a2); + if (op != NULL && op->op_type == BINOP) + return !perform_binop(op->op_num, a1, a3); + + if (a1[1] != '\0') + return -1; + + if (a1[0] == '!') { + res = two_arg(a2, a3); + if (res >= 0) + res = !res; + return res; + } + +#ifndef TINY + if (a1[0] == '(' && a3[0] == ')' && a3[1] == '\0') + return one_arg(a2); +#endif + + return -1; +} + +static int +four_arg(const char *a1, const char *a2, const char *a3, const char *a4) +{ + int res; + + if (a1[1] != '\0') + return -1; + + if (a1[0] == '!') { + res = three_arg(a2, a3, a4); + if (res >= 0) + res = !res; + return res; + } + +#ifndef TINY + if (a1[0] == '(' && a4[0] == ')' && a4[1] == '\0') + return two_arg(a2, a3); +#endif + + return -1; +} + +#ifndef SMALL +static int +oexpr(enum token n) +{ + int res; + + res = aexpr(n); + if (*t_wp == NULL) + return res; + if (t_lex(*++t_wp) == BOR) + return oexpr(t_lex(*++t_wp)) || res; + t_wp--; + return res; +} + +static int +aexpr(enum token n) +{ + int res; + + res = nexpr(n); + if (*t_wp == NULL) + return res; + if (t_lex(*++t_wp) == BAND) + return aexpr(t_lex(*++t_wp)) && res; + t_wp--; + return res; +} + +static int +nexpr(enum token n) +{ + + if (n == UNOT) + return !nexpr(t_lex(*++t_wp)); + return primary(n); +} + +static int +primary(enum token n) +{ + enum token nn; + int res; + + if (n == EOI) + return 0; /* missing expression */ + if (n == LPAREN) { + if ((nn = t_lex(*++t_wp)) == RPAREN) + return 0; /* missing expression */ + res = oexpr(nn); + if (t_lex(*++t_wp) != RPAREN) + syntax(NULL, "closing paren expected"); + return res; + } + if (t_wp_op && t_wp_op->op_type == UNOP) { + /* unary expression */ + if (*++t_wp == NULL) + syntax(t_wp_op->op_text, "argument expected"); + return perform_unop(n, *t_wp); + } + + if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { + return binop(); + } + + return strlen(*t_wp) > 0; +} +#endif /* !SMALL */ + +static int +perform_unop(enum token n, const char *opnd) +{ + switch (n) { + case STREZ: + return strlen(opnd) == 0; + case STRNZ: + return strlen(opnd) != 0; + case FILTT: + return isatty((int)getn(opnd)); + default: + return filstat(opnd, n); + } +} + +#ifndef SMALL +static int +binop(void) +{ + const char *opnd1, *opnd2; + struct t_op const *op; + + opnd1 = *t_wp; + (void) t_lex(*++t_wp); + op = t_wp_op; + + if ((opnd2 = *++t_wp) == NULL) + syntax(op->op_text, "argument expected"); + + return perform_binop(op->op_num, opnd1, opnd2); +} +#endif + +static int +perform_binop(enum token op_num, const char *opnd1, const char *opnd2) +{ + switch (op_num) { + 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 getn(opnd1) == getn(opnd2); + case INTNE: + return getn(opnd1) != getn(opnd2); + case INTGE: + return getn(opnd1) >= getn(opnd2); + case INTGT: + return getn(opnd1) > getn(opnd2); + case INTLE: + return getn(opnd1) <= getn(opnd2); + case INTLT: + return getn(opnd1) < getn(opnd2); + case FILNT: + return newerf(opnd1, opnd2); + case FILOT: + return olderf(opnd1, opnd2); + case FILEQ: + return equalf(opnd1, opnd2); + default: + abort(); + /* NOTREACHED */ + } +} + +/* + * The manual, and IEEE POSIX 1003.2, suggests this should check the mode bits, + * not use access(): + * + * True shall indicate only that the write flag is on. The file is not + * writable on a read-only file system even if this test indicates true. + * + * Unfortunately IEEE POSIX 1003.1-2001, as quoted in SuSv3, says only: + * + * True shall indicate that permission to read from file will be granted, + * as defined in "File Read, Write, and Creation". + * + * and that section says: + * + * When a file is to be read or written, the file shall be opened with an + * access mode corresponding to the operation to be performed. If file + * access permissions deny access, the requested operation shall fail. + * + * and of course access permissions are described as one might expect: + * + * * If a process has the appropriate privilege: + * + * * If read, write, or directory search permission is requested, + * access shall be granted. + * + * * If execute permission is requested, access shall be granted if + * execute permission is granted to at least one user by the file + * permission bits or by an alternate access control mechanism; + * otherwise, access shall be denied. + * + * * Otherwise: + * + * * The file permission bits of a file contain read, write, and + * execute/search permissions for the file owner class, file group + * class, and file other class. + * + * * Access shall be granted if an alternate access control mechanism + * is not enabled and the requested access permission bit is set for + * the class (file owner class, file group class, or file other class) + * to which the process belongs, or if an alternate access control + * mechanism is enabled and it allows the requested access; otherwise, + * access shall be denied. + * + * and when I first read this I thought: surely we can't go about using + * open(O_WRONLY) to try this test! However the POSIX 1003.1-2001 Rationale + * section for test does in fact say: + * + * On historical BSD systems, test -w directory always returned false + * because test tried to open the directory for writing, which always + * fails. + * + * and indeed this is in fact true for Seventh Edition UNIX, UNIX 32V, and UNIX + * System III, and thus presumably also for BSD up to and including 4.3. + * + * Secondly I remembered why using open() and/or access() are bogus. They + * don't work right for detecting read and write permissions bits when called + * by root. + * + * Interestingly the 'test' in 4.4BSD was closer to correct (as per + * 1003.2-1992) and it was implemented efficiently with stat() instead of + * open(). + * + * This was apparently broken in NetBSD around about 1994/06/30 when the old + * 4.4BSD implementation was replaced with a (arguably much better coded) + * implementation derived from pdksh. + * + * Note that modern pdksh is yet different again, but still not correct, at + * least not w.r.t. 1003.2-1992. + * + * As I think more about it and read more of the related IEEE docs I don't like + * that wording about 'test -r' and 'test -w' in 1003.1-2001 at all. I very + * much prefer the original wording in 1003.2-1992. It is much more useful, + * and so that's what I've implemented. + * + * (Note that a strictly conforming implementation of 1003.1-2001 is in fact + * totally useless for the case in question since its 'test -w' and 'test -r' + * can never fail for root for any existing files, i.e. files for which 'test + * -e' succeeds.) + * + * The rationale for 1003.1-2001 suggests that the wording was "clarified" in + * 1003.1-2001 to align with the 1003.2b draft. 1003.2b Draft 12 (July 1999), + * which is the latest copy I have, does carry the same suggested wording as is + * in 1003.1-2001, with its rationale saying: + * + * This change is a clarification and is the result of interpretation + * request PASC 1003.2-92 #23 submitted for IEEE Std 1003.2-1992. + * + * That interpretation can be found here: + * + * http://www.pasc.org/interps/unofficial/db/p1003.2/pasc-1003.2-23.html + * + * Not terribly helpful, unfortunately. I wonder who that fence sitter was. + * + * Worse, IMVNSHO, I think the authors of 1003.2b-D12 have mis-interpreted the + * PASC interpretation and appear to be gone against at least one widely used + * implementation (namely 4.4BSD). The problem is that for file access by root + * this means that if test '-r' and '-w' are to behave as if open() were called + * then there's no way for a shell script running as root to check if a file + * has certain access bits set other than by the grotty means of interpreting + * the output of 'ls -l'. This was widely considered to be a bug in V7's + * "test" and is, I believe, one of the reasons why direct use of access() was + * avoided in some more recent implementations! + * + * I have always interpreted '-r' to match '-w' and '-x' as per the original + * wording in 1003.2-1992, not the other way around. I think 1003.2b goes much + * too far the wrong way without any valid rationale and that it's best if we + * stick with 1003.2-1992 and test the flags, and not mimic the behaviour of + * open() since we already know very well how it will work -- existance of the + * file is all that matters to open() for root. + * + * Unfortunately the SVID is no help at all (which is, I guess, partly why + * we're in this mess in the first place :-). + * + * The SysV implementation (at least in the 'test' builtin in /bin/sh) does use + * access(name, 2) even though it also goes to much greater lengths for '-x' + * matching the 1003.2-1992 definition (which is no doubt where that definition + * came from). + * + * The ksh93 implementation uses access() for '-r' and '-w' if + * (euid==uid&&egid==gid), but uses st_mode for '-x' iff running as root. + * i.e. it does strictly conform to 1003.1-2001 (and presumably 1003.2b). + */ +static int +test_access(struct stat *sp, mode_t stmode) +{ + gid_t *groups; + register int n; + uid_t euid; + int maxgroups; + + /* + * I suppose we could use access() if not running as root and if we are + * running with ((euid == uid) && (egid == gid)), but we've already + * done the stat() so we might as well just test the permissions + * directly instead of asking the kernel to do it.... + */ + euid = geteuid(); + if (euid == 0) /* any bit is good enough */ + stmode = (stmode << 6) | (stmode << 3) | stmode; + else if (sp->st_uid == euid) + stmode <<= 6; + else if (sp->st_gid == getegid()) + stmode <<= 3; + else { + /* XXX stolen almost verbatim from ksh93.... */ + /* on some systems you can be in several groups */ + if ((maxgroups = getgroups(0, NULL)) <= 0) + maxgroups = NGROUPS_MAX; /* pre-POSIX system? */ + groups = ckmalloc((maxgroups + 1) * sizeof(gid_t)); + n = getgroups(maxgroups, groups); + while (--n >= 0) { + if (groups[n] == sp->st_gid) { + stmode <<= 3; + break; + } + } + free(groups); + } + + return sp->st_mode & stmode; +} + +static int +filstat(const char *nm, enum token mode) +{ + struct stat s; + + if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) + return 0; + + switch (mode) { + case FILRD: + return test_access(&s, S_IROTH); + case FILWR: + return test_access(&s, S_IWOTH); + case FILEX: + return test_access(&s, S_IXOTH); + case FILEXIST: + return 1; /* the successful lstat()/stat() is good enough */ + 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; + } +} + +#define VTOC(x) (const unsigned char *)((const struct t_op *)x)->op_text + +static int +compare1(const void *va, const void *vb) +{ + const unsigned char *a = va; + const unsigned char *b = VTOC(vb); + + return a[0] - b[0]; +} + +static int +compare2(const void *va, const void *vb) +{ + const unsigned char *a = va; + const unsigned char *b = VTOC(vb); + int z = a[0] - b[0]; + + return z ? z : (a[1] - b[1]); +} + +static struct t_op const * +findop(const char *s) +{ + if (s[0] == '-') { + if (s[1] == '\0') + return NULL; + if (s[2] == '\0') + return bsearch(s + 1, mop2, __arraycount(mop2), + sizeof(*mop2), compare1); + else if (s[3] != '\0') + return NULL; + else + return bsearch(s + 1, mop3, __arraycount(mop3), + sizeof(*mop3), compare2); + } else { + if (s[1] == '\0') + return bsearch(s, cop, __arraycount(cop), sizeof(*cop), + compare1); + else if (strcmp(s, cop2[0].op_text) == 0) + return cop2; + else + return NULL; + } +} + +#ifndef SMALL +static enum token +t_lex(char *s) +{ + struct t_op const *op; + + if (s == NULL) { + t_wp_op = NULL; + return EOI; + } + + if ((op = findop(s)) != NULL) { + if (!((op->op_type == UNOP && isoperand()) || + (op->op_num == LPAREN && *(t_wp+1) == 0))) { + t_wp_op = op; + return op->op_num; + } + } + t_wp_op = NULL; + return OPERAND; +} + +static int +isoperand(void) +{ + struct t_op const *op; + char *s, *t; + + if ((s = *(t_wp+1)) == 0) + return 1; + if ((t = *(t_wp+2)) == 0) + return 0; + if ((op = findop(s)) != NULL) + return op->op_type == BINOP && (t[0] != ')' || t[1] != '\0'); + return 0; +} +#endif + +/* atoi with error detection */ +static long long +getn(const char *s) +{ + char *p; + long long r; + + errno = 0; + r = strtoll(s, &p, 10); + + if (errno != 0) + if (errno == ERANGE && (r == LLONG_MAX || r == LLONG_MIN)) + error("%s: out of range", s); + + if (p != s) + while (isspace((unsigned char)*p)) + p++; + + if (*p || p == s) + error("'%s': bad number", s); + + return r; +} + +static int +newerf(const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat(f1, &b1) == 0 && + stat(f2, &b2) == 0 && + timespeccmp(&b1.st_mtim, &b2.st_mtim, >)); +} + +static int +olderf(const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat(f1, &b1) == 0 && + stat(f2, &b2) == 0 && + timespeccmp(&b1.st_mtim, &b2.st_mtim, <)); +} + +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); +} |