summaryrefslogtreecommitdiffstats
path: root/usr.bin/env
diff options
context:
space:
mode:
authorgad <gad@FreeBSD.org>2005-06-20 03:43:25 +0000
committergad <gad@FreeBSD.org>2005-06-20 03:43:25 +0000
commit82ebcba30a9758c7d78686445ec2eaf32d2f4481 (patch)
tree3fc7c846fa2293810c4c2f91e4161ef12ce38915 /usr.bin/env
parentb785b6343ef380b2072604d84d7dacc05e51a7c6 (diff)
downloadFreeBSD-src-82ebcba30a9758c7d78686445ec2eaf32d2f4481.zip
FreeBSD-src-82ebcba30a9758c7d78686445ec2eaf32d2f4481.tar.gz
Add the '-S' and '-P' options. The '-S' option can be used to split
apart a string, and supports some text substitutions. This can be used to provide all the flexibility (and more!) that was lost by recent changes to how the kernel parses #!-lines in shell scripts. The '-P' option provides a way to specify an alternate set of directories to use when searching for the 'utility' program to run. This way you can be sure what directories are used for that search, without changing the value of PATH that the user has set. Note that on FreeBSD 6.0, this option is worthless unless the '-S' option is also used. Approved by: re (blanket `env')
Diffstat (limited to 'usr.bin/env')
-rw-r--r--usr.bin/env/Makefile2
-rw-r--r--usr.bin/env/env.1370
-rw-r--r--usr.bin/env/env.c22
-rw-r--r--usr.bin/env/envopts.c430
-rw-r--r--usr.bin/env/envopts.h37
5 files changed, 829 insertions, 32 deletions
diff --git a/usr.bin/env/Makefile b/usr.bin/env/Makefile
index 7c74937..bc1eea4 100644
--- a/usr.bin/env/Makefile
+++ b/usr.bin/env/Makefile
@@ -2,5 +2,7 @@
# $FreeBSD$
PROG= env
+SRCS= env.c envopts.c
+WARNS?= 6
.include <bsd.prog.mk>
diff --git a/usr.bin/env/env.1 b/usr.bin/env/env.1
index e6530e2..9316175 100644
--- a/usr.bin/env/env.1
+++ b/usr.bin/env/env.1
@@ -35,7 +35,7 @@
.\" From FreeBSD: src/usr.bin/printenv/printenv.1,v 1.17 2002/11/26 17:33:35 ru Exp
.\" $FreeBSD$
.\"
-.Dd May 12, 2003
+.Dd June 20, 2005
.Dt ENV 1
.Os
.Sh NAME
@@ -43,7 +43,9 @@
.Nd set and print environment
.Sh SYNOPSIS
.Nm
-.Op Fl i
+.Op Fl iv
+.Op Fl P Ar altpath
+.Op Fl S Ar string
.Op Ar name Ns = Ns Ar value ...
.Op Ar utility Op Ar argument ...
.Sh DESCRIPTION
@@ -53,52 +55,275 @@ utility executes another
.Ar utility
after modifying the environment as
specified on the command line.
-The option
+Each
.Ar name Ns = Ns Ar value
-specifies
-an environment variable,
+option specifies the setting of an environment variable,
.Ar name ,
with a value of
.Ar value .
+All such environment variables are set before the
+.Ar utility
+is executed.
.Pp
The options are as follows:
.Bl -tag -width indent
.It Fl i
Execute the
.Ar utility
-with only those environment variables specified.
+with only those environment variables specified by
+.Ar name Ns = Ns Ar value
+options.
The environment inherited
by
.Nm
is ignored completely.
+.\" -P
+.It Fl P Ar altpath
+Search the set of directories as specified by
+.Ar altpath
+to locate the specified
+.Ar utility
+program, instead of using the value of the PATH environment variable.
+.\" -S
+.It Fl S Ar string
+Split apart the given
+.Ar string
+into multiple strings, and process each of the resulting strings
+as separate arguments to the
+.Nm
+utility.
+The
+.Fl S
+option recognizes some special character escape sequences and
+also supports environment-variable substitution, as described
+below.
+.\" -v
+.It Fl v
+Print verbose information for each step of processing done by the
+.Nm
+utility.
+Additional information will be printed if
+.Fl v
+is specified multiple times.
.El
.Pp
+The above options are only recognized when they are specified
+before any
+.Ar name Ns = Ns Ar value
+options.
+.Pp
If no
.Ar utility
is specified,
.Nm
prints out the names and values
of the variables in the environment, with one name/value pair per line.
+.\"
+.Ss Details of -S (split-string) processing
+.Pp
+The processing of the
+.Fl S
+option will split the given
+.Ar string
+into separate arguments based on any space or <tab> characters found in the
+.Ar string .
+Each of those new arguments will then be treated as if it had been
+specified as a separate argument on the original
+.Nm
+command.
+.Pp
+Spaces and tabs may be embedded in one of those new arguments by using
+single (``\ '\ '') or double (``"'') quotes, or backslashes (``\e'').
+Single quotes will escape all non-single quote characters, up to
+the matching single quote.
+Double quotes will escape all non-double quote characters, up to
+the matching double quote.
+It is an error if the end of the
+.Ar string
+is reached before the matching quote character.
+.Pp
+If
+.Fl S
+would create a new argument that starts with the
+.Ql #
+character, then that argument and the remainder of the
+.Ar string
+will be ignored.
+The
+.Ql \e#
+sequence can be used when you want a new argument to start
+with a
+.Ql #
+character, without causing the remainder of the
+.Ar string
+to be skipped.
+.Pp
+While processing the
+.Ar string
+value,
+.Fl S
+processing will treat certain character combinations as escape
+sequences which represent some action to take.
+The character escape sequences are in backslash notation.
+The characters and their meanings are as follows:
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It Cm \ec
+Ignore the remaining characters in the
+.Ar string .
+This must not appear inside a double-quoted string.
+.It Cm \ef
+Replace with a <form-feed> character.
+.It Cm \en
+Replace with a <new-line> character.
+.It Cm \er
+Replace with a <carriage return> character.
+.It Cm \et
+Replace with a <tab> character.
+.It Cm \ev
+Replace with a <vertical tab> character.
+.It Cm \e#
+Replace with a
+.Ql #
+character.
+This would be useful when you need a
+.Ql #
+as the first character in one of the arguments created
+by splitting apart the given
+.Ar string .
+.It Cm \e$
+Replace with a
+.Ql $
+character.
+.It Cm \e_
+If this is found inside of a double-quoted string, then replace it
+with a single blank.
+If this is found outside of a quoted string, then treat this as the
+separator character between new arguments in the original
+.Ar string .
+.It Cm \e"
+Replace with a <double quote> character.
+.It Cm \e\'
+Replace with a <single quote> character.
+.It Cm \e\e
+Replace with a backslash character.
+.El
.Pp
+The sequences for <single-quote> and backslash are the only sequences
+which are recognized inside of a single-quoted string.
+The other sequences have no special meaning inside a single-quoted
+string.
+All escape sequences are recognized inside of a double-quoted string.
+It is an error if a single
+.Ql \e
+character is followed by a character other than the ones listed above.
+.Pp
+The processing of
+.Fl S
+also supports substitution of values from environment variables.
+To do this, the name of the environment variable must be inside of
+.Ql ${} ,
+such as: ${SOMEVAR}.
+The common shell syntax of $SOMEVAR is not supported.
+All values substituted will be the values of the environment variables
+as they were when the
+.Nm
+utility was originally invoked.
+Those values will not be checked for any of the escape sequences as
+described above.
+And any settings of
+.Ar name Ns = Ns Ar value
+will not effect the values used for substitution in
+.Fl S
+processing.
+.Pp
+Also,
+.Fl S
+processing can not reference the value of the special parameters
+which are defined by most shells.
+For instance,
+.Fl S
+can not recognize special parameters such as:
+.Ql $* ,
+.Ql $@ ,
+.Ql $# ,
+.Ql $?
+or
+.Ql $$
+if they appear inside the given
+.Ar string .
+.\"
+.Ss Use in shell-scripts
The
.Nm
-utility is sometimes useful with the
+utility is often used as the
+.Ar interpreter
+on the first line of interpreted scripts, as
+described in
+.Xr execve 2 .
+.Pp
+Note that the way the kernel parses the
.Ql #!
-construct (see
-.Xr execve 2 ) .
-The only difference between
-.Dq Li #!/usr/local/bin/foo
-and
-.Dq Li "#!/usr/bin/env /usr/local/bin/foo"
-is that the latter works even if
-.Pa /usr/local/bin/foo
-is itself interpreted.
-Using
+(first line) of an interpreted script has changed as of
+.Fx 6.0 .
+Prior to that, the
+.Fx
+kernel would split that first line into separate arguments based
+on any whitespace (space or <tab> characters) found in the line.
+So, if a script named
+.Pa /usr/local/bin/someport
+had a first line of:
+.Pp
+.D1 Li "#!/usr/local/bin/php -n -q -dsafe_mode=0"
+.Pp
+then the
+.Pa /usr/local/bin/php
+program would have been started with the arguments of:
+.Pp
+.D1 Li "arg[0] = '/usr/local/bin/php'"
+.D1 Li "arg[1] = '-n'"
+.D1 Li "arg[2] = '-q'"
+.D1 Li "arg[3] = '-dsafe_mode=0'"
+.D1 Li "arg[4] = '/usr/local/bin/someport'"
+.Pp
+plus any arguments the user specifed when executing
+.Pa someport .
+However, this processing of multiple options on the
+.Ql #!
+line is not the way any other operating system parses the
+first line of an interpreted script.
+So after a change which was made for
+.Fx 6.0
+release, that script will result in
+.Pa /usr/local/bin/php
+being started with the arguments of:
+.Pp
+.D1 Li "arg[0] = '/usr/local/bin/php'"
+.D1 Li "arg[1] = '-n -q -dsafe_mode=0'"
+.D1 Li "arg[2] = '/usr/local/bin/someport'"
+.Pp
+plus any arguments the user specifed.
+This caused a significant change in the behavior of a few scripts.
+In the case of above script, to have it behave the same way under
+.Fx 6.0
+as it did under earlier releases, the first line should be
+changed to:
+.Pp
+.D1 Li "#!/usr/bin/env -S /usr/local/bin/php -n -q -dsafe_mode=0"
+.Pp
+The
.Nm
-this way also allows one to reference
-.Pa foo
-without the path,
-as well as set up the environment as desired.
+utility will be started with the entire line as a single
+argument:
+.Pp
+.D1 Li "arg[1] = '-S /usr/local/bin/php -n -q -dsafe_mode=0'"
+.Pp
+and then
+.Fl S
+processing will split that line into separate arguments before
+executing
+.Pa /usr/local/bin/php .
+.\"
.Sh ENVIRONMENT
The
.Nm
@@ -108,7 +333,9 @@ environment variable to locate the requested
.Ar utility
if the name contains no
.Ql /
-characters.
+characters, unless the
+.Fl P
+option has been specifed.
.Sh EXIT STATUS
.Ex -std
An exit status of 126 indicates that
@@ -117,6 +344,65 @@ was found, but could not be executed.
An exit status of 127 indicates that
.Ar utility
could not be found.
+.Sh EXAMPLES
+Since the
+.Nm
+utility is often used as part of the first line of an interpreted script,
+the following examples show a number of ways that the
+.Nm
+utility can be useful in scripts.
+.Pp
+The kernel processing of an interpreted script does not allow a script
+to directly reference some other script as its own interpreter.
+As a way around this, the main difference between
+.Pp
+.D1 Li #!/usr/local/bin/foo
+and
+.D1 Li "#!/usr/bin/env /usr/local/bin/foo"
+.Pp
+is that the latter works even if
+.Pa /usr/local/bin/foo
+is itself an interpreted script.
+.Pp
+Probably the most common use of
+.Nm
+is to find the correct interpreter for a script, when the interpreter
+may be in different directories on different systems.
+The following example will find the
+.Ql perl
+interpreter by searching through the directories specifed by PATH.
+.Pp
+.D1 Li "#!/usr/bin/env perl"
+.Pp
+One limitation of that example is that it assumes the user's value
+for PATH is set to a value which will find the interpreter you want
+to execute.
+The
+.Fl P
+option can be used to make sure a specific list of directories are
+used in the search for
+.Ar utility .
+Note that the
+.Fl S
+option is also required for this example to work correctly.
+.Pp
+.D1 Li "#!/usr/bin/env -S -P/usr/local/bin:/usr/bin perl"
+.Pp
+The above finds
+.Ql perl
+only if it is in
+.Pa /usr/local/bin
+or
+.Pa /usr/bin .
+That could be combined with the present value of PATH, to provide
+more flexibility.
+Note that spaces are not required between the
+.Fl S
+and
+.Fl P
+options:
+.Pp
+.D1 Li "#!/usr/bin/env -S-P/usr/local/bin:/usr/bin:${PATH} perl"
.Sh COMPATIBILITY
The
.Nm
@@ -134,14 +420,40 @@ The
.Nm
utility conforms to
.St -p1003.1-2001 .
-.\".Sh HISTORY
-.\"The
-.\".Nm
-.\"command appeared in
-.\".Bx 3.0 .
+The
+.Fl P , S
+and
+.Fl v
+options are non-standard
+.Fx
+extensions which may not be available on other operating systems.
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.4 .
+The
+.Fl P , S
+and
+.Fl v
+options were added in
+.Fx 6.0 .
.Sh BUGS
The
.Nm
-utility does not handle utility arguments with equal signs
+utility does not handle values of
+.Ar utility
+which have an equals sign
.Pq Ql =
-in their names, for obvious reasons.
+in their name (for obvious reasons), unless the
+.Ar utility
+name begins with a
+.Ql /
+character.
+.Pp
+The
+.Nm
+utility does not take multibyte characters into account when
+processing the
+.Fl S
+option, which may lead to incorrect results in some locales.
diff --git a/usr.bin/env/env.c b/usr.bin/env/env.c
index a7eb536..56c671b 100644
--- a/usr.bin/env/env.c
+++ b/usr.bin/env/env.c
@@ -53,6 +53,8 @@ __FBSDID("$FreeBSD$");
#include <stdlib.h>
#include <unistd.h>
+#include "envopts.h"
+
extern char **environ;
int env_verbosity;
@@ -62,17 +64,28 @@ static void usage(void);
int
main(int argc, char **argv)
{
- char **ep, *p, **parg;
+ char *altpath, **ep, *p, **parg;
char *cleanenv[1];
int ch, want_clear;
+ altpath = NULL;
want_clear = 0;
- while ((ch = getopt(argc, argv, "-iv")) != -1)
+ while ((ch = getopt(argc, argv, "-iP:S:v")) != -1)
switch(ch) {
case '-':
case 'i':
want_clear = 1;
break;
+ case 'P':
+ altpath = strdup(optarg);
+ break;
+ case 'S':
+ /*
+ * The -S option, for "split string on spaces, with
+ * support for some simple substitutions"...
+ */
+ split_spaces(optarg, &optind, &argc, &argv);
+ break;
case 'v':
env_verbosity++;
if (env_verbosity > 1)
@@ -96,6 +109,8 @@ main(int argc, char **argv)
(void)setenv(*argv, ++p, 1);
}
if (*argv) {
+ if (altpath)
+ search_paths(altpath, argv);
if (env_verbosity) {
fprintf(stderr, "#env executing:\t%s\n", *argv);
for (parg = argv, argc = 0; *parg; parg++, argc++)
@@ -116,6 +131,7 @@ static void
usage(void)
{
(void)fprintf(stderr,
- "usage: env [-iv] [name=value ...] [utility [argument ...]]\n");
+ "usage: env [-iv] [-P utilpath] [-S string] [name=value ...]"
+ " [utility [argument ...]]\n");
exit(1);
}
diff --git a/usr.bin/env/envopts.c b/usr.bin/env/envopts.c
new file mode 100644
index 0000000..66e80f6
--- /dev/null
+++ b/usr.bin/env/envopts.c
@@ -0,0 +1,430 @@
+/*-
+ * Copyright (c) 2005 - Garance Alistair Drosehn <gad@FreeBSD.org>.
+ * 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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <err.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "envopts.h"
+
+static void expand_vars(char **thisarg_p, char **dest_p, const char
+ **src_p);
+static int is_there(char *candidate);
+
+/*
+ * The is*() routines take a parameter of 'int', but expect values in the range
+ * of unsigned char. Define some wrappers which take a value of type 'char',
+ * whether signed or unsigned, and ensure the value ends up in the right range.
+ */
+#define isalnumch(Anychar) isalnum((u_char)(Anychar))
+#define isalphach(Anychar) isalpha((u_char)(Anychar))
+#define isspacech(Anychar) isspace((u_char)(Anychar))
+
+/*
+ * Routine to determine if a given fully-qualified filename is executable.
+ * This is copied almost verbatim from FreeBSD's usr.bin/which/which.c.
+ */
+static int
+is_there(char *candidate)
+{
+ struct stat fin;
+
+ /* XXX work around access(2) false positives for superuser */
+ if (access(candidate, X_OK) == 0 &&
+ stat(candidate, &fin) == 0 &&
+ S_ISREG(fin.st_mode) &&
+ (getuid() != 0 ||
+ (fin.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0)) {
+ if (env_verbosity > 1)
+ fprintf(stderr, "#env matched:\t'%s'\n", candidate);
+ return (1);
+ }
+ return (0);
+}
+
+/**
+ * Routine to search through an alternate path-list, looking for a given
+ * filename to execute. If the file is found, replace the original
+ * unqualified name with a fully-qualified path. This allows `env' to
+ * execute programs from a specific strict list of possible paths, without
+ * changing the value of PATH seen by the program which will be executed.
+ * E.G.:
+ * #!/usr/bin/env -S-P/usr/local/bin:/usr/bin perl
+ * will execute /usr/local/bin/perl or /usr/bin/perl (whichever is found
+ * first), no matter what the current value of PATH is, and without
+ * changing the value of PATH that the script will see when it runs.
+ *
+ * This is similar to the print_matches() routine in usr.bin/which/which.c.
+ */
+void
+search_paths(char *path, char **argv)
+{
+ char candidate[PATH_MAX];
+ const char *d;
+ char *filename, *fqname;
+
+ /* If the file has a `/' in it, then no search is done */
+ filename = *argv;
+ if (strchr(filename, '/') != NULL)
+ return;
+
+ if (env_verbosity > 1) {
+ fprintf(stderr, "#env Searching:\t'%s'\n", path);
+ fprintf(stderr, "#env for file:\t'%s'\n", filename);
+ }
+
+ fqname = NULL;
+ while ((d = strsep(&path, ":")) != NULL) {
+ if (*d == '\0')
+ d = ".";
+ if (snprintf(candidate, sizeof(candidate), "%s/%s", d,
+ filename) >= (int)sizeof(candidate))
+ continue;
+ if (is_there(candidate)) {
+ fqname = candidate;
+ break;
+ }
+ }
+
+ if (fqname == NULL) {
+ errno = ENOENT;
+ err(127, "%s", filename);
+ }
+ *argv = strdup(candidate);
+}
+
+/**
+ * Routine to split a string into multiple parameters, while recognizing a
+ * few special characters. It recognizes both single and double-quoted
+ * strings. This processing is designed entirely for the benefit of the
+ * parsing of "#!"-lines (aka "shebang" lines == the first line of an
+ * executable script). Different operating systems parse that line in very
+ * different ways, and this split-on-spaces processing is meant to provide
+ * ways to specify arbitrary arguments on that line, no matter how the OS
+ * parses it.
+ *
+ * Within a single-quoted string, the two characters "\'" are treated as
+ * a literal "'" character to add to the string, and "\\" are treated as
+ * a literal "\" character to add. Other than that, all characters are
+ * copied until the processing gets to a terminating "'".
+ *
+ * Within a double-quoted string, many more "\"-style escape sequences
+ * are recognized, mostly copied from what is recognized in the `printf'
+ * command. Some OS's will not allow a literal blank character to be
+ * included in the one argument that they recognize on a shebang-line,
+ * so a few additional escape-sequences are defined to provide ways to
+ * specify blanks.
+ *
+ * Within a double-quoted string "\_" is turned into a literal blank.
+ * (Inside of a single-quoted string, the two characters are just copied)
+ * Outside of a quoted string, "\_" is treated as both a blank, and the
+ * end of the current argument. So with a shelbang-line of:
+ * #!/usr/bin/env -SA=avalue\_perl
+ * the -S value would be broken up into arguments "A=avalue" and "perl".
+ */
+void
+split_spaces(const char *str, int *origind, int *origc, char ***origv)
+{
+ const char *bq_src, *src;
+ char *dest, **newargv, *newstr, **nextarg, **oldarg;
+ int addcount, bq_destlen, copychar, found_sep, in_arg, in_dq, in_sq;
+
+ /*
+ * Ignore leading space on the string, and then malloc enough room
+ * to build a copy of it. The copy might end up shorter than the
+ * original, due to quoted strings and '\'-processing.
+ */
+ while (isspacech(*str))
+ str++;
+ if (*str == '\0')
+ return;
+ newstr = malloc(strlen(str) + 1);
+
+ /*
+ * Allocate plenty of space for the new array of arg-pointers,
+ * and start that array off with the first element of the old
+ * array.
+ */
+ newargv = malloc((*origc + (strlen(str) / 2) + 2) * sizeof(char *));
+ nextarg = newargv;
+ *nextarg++ = **origv;
+
+ /* Come up with the new args by splitting up the given string. */
+ addcount = 0;
+ bq_destlen = in_arg = in_dq = in_sq = 0;
+ bq_src = NULL;
+ for (src = str, dest = newstr; *src != '\0'; src++) {
+ copychar = found_sep = 0;
+ switch (*src) {
+ case '"':
+ if (in_sq)
+ copychar = *src;
+ else if (in_dq)
+ in_dq = 0;
+ else {
+ in_dq = 1;
+ bq_destlen = dest - *(nextarg - 1);
+ bq_src = src;
+ }
+ break;
+ case '$':
+ if (in_sq)
+ copychar = *src;
+ else {
+ expand_vars((nextarg - 1), &dest, &src);
+ }
+ break;
+ case '\'':
+ if (in_dq)
+ copychar = *src;
+ else if (in_sq)
+ in_sq = 0;
+ else {
+ in_sq = 1;
+ bq_destlen = dest - *(nextarg - 1);
+ bq_src = src;
+ }
+ break;
+ case '\\':
+ if (in_sq) {
+ /*
+ * Inside single-quoted strings, only the
+ * "\'" and "\\" are recognized as special
+ * strings.
+ */
+ copychar = *(src + 1);
+ if (copychar == '\'' || copychar == '\\')
+ src++;
+ else
+ copychar = *src;
+ break;
+ }
+ src++;
+ switch (*src) {
+ case '"':
+ case '#':
+ case '$':
+ case '\'':
+ case '\\':
+ copychar = *src;
+ break;
+ case '_':
+ /*
+ * Alternate way to get a blank, which allows
+ * that blank be used to separate arguments
+ * when it is not inside a quoted string.
+ */
+ if (in_dq)
+ copychar = ' ';
+ else {
+ found_sep = 1;
+ src++;
+ }
+ break;
+ case 'c':
+ /*
+ * Ignore remaining characters in the -S string.
+ * This would not make sense if found in the
+ * middle of a quoted string.
+ */
+ if (in_dq)
+ errx(1, "Sequence '\\%c' is not allowed"
+ " in quoted strings", *src);
+ goto str_done;
+ case 'f':
+ copychar = '\f';
+ break;
+ case 'n':
+ copychar = '\n';
+ break;
+ case 'r':
+ copychar = '\r';
+ break;
+ case 't':
+ copychar = '\t';
+ break;
+ case 'v':
+ copychar = '\v';
+ break;
+ default:
+ if (isspacech(*src))
+ copychar = *src;
+ else
+ errx(1, "Invalid sequence '\\%c' in -S",
+ *src);
+ }
+ break;
+ default:
+ if ((in_dq || in_sq) && in_arg)
+ copychar = *src;
+ else if (in_arg && isspacech(*src))
+ found_sep = 1;
+ else {
+ /*
+ * If the first character of a new argument
+ * is `#', then ignore the remaining chars.
+ */
+ if (!in_arg && *src == '#')
+ goto str_done;
+ copychar = *src;
+ }
+ }
+ if (copychar) {
+ if (!in_arg) {
+ /* This is the first byte of a new argument */
+ *nextarg++ = dest;
+ addcount++;
+ in_arg = 1;
+ }
+ *dest++ = (char)copychar;
+ } else if (found_sep) {
+ *dest++ = '\0';
+ while (isspacech(*src))
+ src++;
+ --src;
+ in_arg = 0;
+ }
+ }
+str_done:
+ *dest = '\0';
+ *nextarg = NULL;
+ if (in_dq || in_sq) {
+ errx(1, "No terminating quote for string: %.*s%s",
+ bq_destlen, *(nextarg - 1), bq_src);
+ }
+ if (env_verbosity > 1) {
+ fprintf(stderr, "#env split -S:\t'%s'\n", str);
+ oldarg = newargv + 1;
+ fprintf(stderr, "#env into:\t'%s'\n", *oldarg);
+ for (oldarg++; *oldarg; oldarg++)
+ fprintf(stderr, "#env &\t'%s'\n", *oldarg);
+ }
+
+ /* Copy the unprocessed arg-pointers from the original array */
+ for (oldarg = *origv + *origind; *oldarg; oldarg++)
+ *nextarg++ = *oldarg;
+ *nextarg = NULL;
+
+ /* Update optind/argc/argv in the calling routine */
+ *origind = 1;
+ *origc += addcount;
+ *origv = newargv;
+}
+
+/**
+ * Routine to split expand any environment variables referenced in the string
+ * that -S is processing. For now it only supports the form ${VARNAME}. It
+ * explicitly does not support $VARNAME, and obviously can not handle special
+ * shell-variables such as $?, $*, $1, etc. It is called with *src_p pointing
+ * at the initial '$', and if successful it will update *src_p, *dest_p, and
+ * possibly *thisarg_p in the calling routine.
+ */
+void
+expand_vars(char **thisarg_p, char **dest_p, const char **src_p)
+{
+ const char *vbegin, *vend, *vvalue;
+ char *edest, *newstr, *vname;
+ int bad_reference;
+ size_t namelen, newlen;
+
+ bad_reference = 1;
+ vbegin = vend = (*src_p) + 1;
+ if (*vbegin++ == '{')
+ if (*vbegin == '_' || isalphach(*vbegin)) {
+ vend = vbegin + 1;
+ while (*vend == '_' || isalnumch(*vend))
+ vend++;
+ if (*vend == '}')
+ bad_reference = 0;
+ }
+ if (bad_reference)
+ errx(1, "Only ${VARNAME} expansion is supported, error at: %s",
+ *src_p);
+
+ /*
+ * We now know we have a valid environment variable name, so update
+ * the caller's source-pointer to the last character in that reference,
+ * and then pick up the matching value. If the variable is not found,
+ * or if it has a null value, then our work here is done.
+ */
+ *src_p = vend;
+ namelen = vend - vbegin + 1;
+ vname = malloc(namelen);
+ strlcpy(vname, vbegin, namelen);
+ vvalue = getenv(vname);
+ if (vvalue == NULL || *vvalue == '\0') {
+ if (env_verbosity > 2)
+ fprintf(stderr,
+ "#env replacing ${%s} with null string\n",
+ vname);
+ return;
+ }
+
+ if (env_verbosity > 2)
+ fprintf(stderr, "#env expanding ${%s} into '%s'\n", vname,
+ vvalue);
+
+ /*
+ * There is some value to copy to the destination. If the value is
+ * shorter than the ${VARNAME} reference that it replaces, then we
+ * can just copy the value to the existing destination.
+ */
+ edest = *dest_p;
+ if (strlen(vname) + 3 >= strlen(vvalue)) {
+ while (*vvalue != '\0')
+ *edest++ = *vvalue++;
+ *dest_p = edest;
+ return;
+ }
+
+ /*
+ * The value is longer than the string it replaces, which means the
+ * present destination area is too small to hold it. Create a new
+ * destination area, copy the present 'thisarg' value and the value
+ * of the referenced-variable to it, and then update the caller's
+ * 'thisarg' and 'dest' variables to match.
+ */
+ *edest = '\0'; /* Provide terminator for 'thisarg' */
+ newlen = strlen(*thisarg_p) + strlen(vvalue) + strlen(*src_p) + 1;
+ newstr = malloc(newlen);
+ strcpy(newstr, *thisarg_p);
+ strcat(newstr, vvalue);
+ *thisarg_p = newstr;
+ *dest_p = strchr(newstr, '\0');
+}
diff --git a/usr.bin/env/envopts.h b/usr.bin/env/envopts.h
new file mode 100644
index 0000000..1f15c69
--- /dev/null
+++ b/usr.bin/env/envopts.h
@@ -0,0 +1,37 @@
+/*-
+ * Copyright (c) 2005 - Garance Alistair Drosehn <gad@FreeBSD.org>.
+ * 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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ *
+ * $FreeBSD$
+ */
+
+void search_paths(char *path, char **argv);
+void split_spaces(const char *str, int *origind, int *origc,
+ char ***origv);
+
+extern int env_verbosity;
OpenPOWER on IntegriCloud