18fbe7ebfSGarance A Drosehn /*-
2*4d846d26SWarner Losh * SPDX-License-Identifier: BSD-2-Clause
31de7b4b8SPedro F. Giffuni *
48fbe7ebfSGarance A Drosehn * Copyright (c) 2005 - Garance Alistair Drosehn <gad@FreeBSD.org>.
58fbe7ebfSGarance A Drosehn * All rights reserved.
68fbe7ebfSGarance A Drosehn *
78fbe7ebfSGarance A Drosehn * Redistribution and use in source and binary forms, with or without
88fbe7ebfSGarance A Drosehn * modification, are permitted provided that the following conditions
98fbe7ebfSGarance A Drosehn * are met:
108fbe7ebfSGarance A Drosehn * 1. Redistributions of source code must retain the above copyright
118fbe7ebfSGarance A Drosehn * notice, this list of conditions and the following disclaimer.
128fbe7ebfSGarance A Drosehn * 2. Redistributions in binary form must reproduce the above copyright
138fbe7ebfSGarance A Drosehn * notice, this list of conditions and the following disclaimer in the
148fbe7ebfSGarance A Drosehn * documentation and/or other materials provided with the distribution.
158fbe7ebfSGarance A Drosehn *
168fbe7ebfSGarance A Drosehn * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
178fbe7ebfSGarance A Drosehn * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
188fbe7ebfSGarance A Drosehn * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
198fbe7ebfSGarance A Drosehn * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
208fbe7ebfSGarance A Drosehn * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
218fbe7ebfSGarance A Drosehn * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
228fbe7ebfSGarance A Drosehn * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
238fbe7ebfSGarance A Drosehn * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
248fbe7ebfSGarance A Drosehn * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
258fbe7ebfSGarance A Drosehn * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
268fbe7ebfSGarance A Drosehn * SUCH DAMAGE.
278fbe7ebfSGarance A Drosehn *
288fbe7ebfSGarance A Drosehn * The views and conclusions contained in the software and documentation
298fbe7ebfSGarance A Drosehn * are those of the authors and should not be interpreted as representing
308fbe7ebfSGarance A Drosehn * official policies, either expressed or implied, of the FreeBSD Project.
318fbe7ebfSGarance A Drosehn */
328fbe7ebfSGarance A Drosehn
338fbe7ebfSGarance A Drosehn #include <sys/cdefs.h>
348fbe7ebfSGarance A Drosehn #include <sys/stat.h>
358fbe7ebfSGarance A Drosehn #include <sys/param.h>
368fbe7ebfSGarance A Drosehn #include <err.h>
378fbe7ebfSGarance A Drosehn #include <errno.h>
388fbe7ebfSGarance A Drosehn #include <ctype.h>
398fbe7ebfSGarance A Drosehn #include <stdio.h>
408fbe7ebfSGarance A Drosehn #include <stdlib.h>
418fbe7ebfSGarance A Drosehn #include <string.h>
428fbe7ebfSGarance A Drosehn #include <unistd.h>
438fbe7ebfSGarance A Drosehn
448fbe7ebfSGarance A Drosehn #include "envopts.h"
458fbe7ebfSGarance A Drosehn
46530aafd4SGarance A Drosehn static const char *
47b5418f51SGarance A Drosehn expand_vars(int in_thisarg, char **thisarg_p, char **dest_p,
48b5418f51SGarance A Drosehn const char **src_p);
498fbe7ebfSGarance A Drosehn static int is_there(char *candidate);
508fbe7ebfSGarance A Drosehn
518fbe7ebfSGarance A Drosehn /*
528fbe7ebfSGarance A Drosehn * The is*() routines take a parameter of 'int', but expect values in the range
538fbe7ebfSGarance A Drosehn * of unsigned char. Define some wrappers which take a value of type 'char',
548fbe7ebfSGarance A Drosehn * whether signed or unsigned, and ensure the value ends up in the right range.
558fbe7ebfSGarance A Drosehn */
568fbe7ebfSGarance A Drosehn #define isalnumch(Anychar) isalnum((u_char)(Anychar))
578fbe7ebfSGarance A Drosehn #define isalphach(Anychar) isalpha((u_char)(Anychar))
588fbe7ebfSGarance A Drosehn #define isspacech(Anychar) isspace((u_char)(Anychar))
598fbe7ebfSGarance A Drosehn
608fbe7ebfSGarance A Drosehn /*
618fbe7ebfSGarance A Drosehn * Routine to determine if a given fully-qualified filename is executable.
628fbe7ebfSGarance A Drosehn * This is copied almost verbatim from FreeBSD's usr.bin/which/which.c.
638fbe7ebfSGarance A Drosehn */
648fbe7ebfSGarance A Drosehn static int
is_there(char * candidate)658fbe7ebfSGarance A Drosehn is_there(char *candidate)
668fbe7ebfSGarance A Drosehn {
678fbe7ebfSGarance A Drosehn struct stat fin;
688fbe7ebfSGarance A Drosehn
698fbe7ebfSGarance A Drosehn /* XXX work around access(2) false positives for superuser */
708fbe7ebfSGarance A Drosehn if (access(candidate, X_OK) == 0 &&
718fbe7ebfSGarance A Drosehn stat(candidate, &fin) == 0 &&
728fbe7ebfSGarance A Drosehn S_ISREG(fin.st_mode) &&
738fbe7ebfSGarance A Drosehn (getuid() != 0 ||
748fbe7ebfSGarance A Drosehn (fin.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0)) {
758fbe7ebfSGarance A Drosehn if (env_verbosity > 1)
768fbe7ebfSGarance A Drosehn fprintf(stderr, "#env matched:\t'%s'\n", candidate);
778fbe7ebfSGarance A Drosehn return (1);
788fbe7ebfSGarance A Drosehn }
798fbe7ebfSGarance A Drosehn return (0);
808fbe7ebfSGarance A Drosehn }
818fbe7ebfSGarance A Drosehn
828fbe7ebfSGarance A Drosehn /**
838fbe7ebfSGarance A Drosehn * Routine to search through an alternate path-list, looking for a given
848fbe7ebfSGarance A Drosehn * filename to execute. If the file is found, replace the original
858fbe7ebfSGarance A Drosehn * unqualified name with a fully-qualified path. This allows `env' to
868fbe7ebfSGarance A Drosehn * execute programs from a specific strict list of possible paths, without
878fbe7ebfSGarance A Drosehn * changing the value of PATH seen by the program which will be executed.
888fbe7ebfSGarance A Drosehn * E.G.:
898fbe7ebfSGarance A Drosehn * #!/usr/bin/env -S-P/usr/local/bin:/usr/bin perl
908fbe7ebfSGarance A Drosehn * will execute /usr/local/bin/perl or /usr/bin/perl (whichever is found
918fbe7ebfSGarance A Drosehn * first), no matter what the current value of PATH is, and without
928fbe7ebfSGarance A Drosehn * changing the value of PATH that the script will see when it runs.
938fbe7ebfSGarance A Drosehn *
948fbe7ebfSGarance A Drosehn * This is similar to the print_matches() routine in usr.bin/which/which.c.
958fbe7ebfSGarance A Drosehn */
968fbe7ebfSGarance A Drosehn void
search_paths(char * path,char ** argv)978fbe7ebfSGarance A Drosehn search_paths(char *path, char **argv)
988fbe7ebfSGarance A Drosehn {
998fbe7ebfSGarance A Drosehn char candidate[PATH_MAX];
1008fbe7ebfSGarance A Drosehn const char *d;
1018fbe7ebfSGarance A Drosehn char *filename, *fqname;
1028fbe7ebfSGarance A Drosehn
1038fbe7ebfSGarance A Drosehn /* If the file has a `/' in it, then no search is done */
1048fbe7ebfSGarance A Drosehn filename = *argv;
1058fbe7ebfSGarance A Drosehn if (strchr(filename, '/') != NULL)
1068fbe7ebfSGarance A Drosehn return;
1078fbe7ebfSGarance A Drosehn
1088fbe7ebfSGarance A Drosehn if (env_verbosity > 1) {
1098fbe7ebfSGarance A Drosehn fprintf(stderr, "#env Searching:\t'%s'\n", path);
1108fbe7ebfSGarance A Drosehn fprintf(stderr, "#env for file:\t'%s'\n", filename);
1118fbe7ebfSGarance A Drosehn }
1128fbe7ebfSGarance A Drosehn
1138fbe7ebfSGarance A Drosehn fqname = NULL;
1148fbe7ebfSGarance A Drosehn while ((d = strsep(&path, ":")) != NULL) {
1158fbe7ebfSGarance A Drosehn if (*d == '\0')
1168fbe7ebfSGarance A Drosehn d = ".";
1178fbe7ebfSGarance A Drosehn if (snprintf(candidate, sizeof(candidate), "%s/%s", d,
1188fbe7ebfSGarance A Drosehn filename) >= (int)sizeof(candidate))
1198fbe7ebfSGarance A Drosehn continue;
1208fbe7ebfSGarance A Drosehn if (is_there(candidate)) {
1218fbe7ebfSGarance A Drosehn fqname = candidate;
1228fbe7ebfSGarance A Drosehn break;
1238fbe7ebfSGarance A Drosehn }
1248fbe7ebfSGarance A Drosehn }
1258fbe7ebfSGarance A Drosehn
1268fbe7ebfSGarance A Drosehn if (fqname == NULL) {
1278fbe7ebfSGarance A Drosehn errno = ENOENT;
1288fbe7ebfSGarance A Drosehn err(127, "%s", filename);
1298fbe7ebfSGarance A Drosehn }
1308fbe7ebfSGarance A Drosehn *argv = strdup(candidate);
1318fbe7ebfSGarance A Drosehn }
1328fbe7ebfSGarance A Drosehn
1338fbe7ebfSGarance A Drosehn /**
1348fbe7ebfSGarance A Drosehn * Routine to split a string into multiple parameters, while recognizing a
1358fbe7ebfSGarance A Drosehn * few special characters. It recognizes both single and double-quoted
1368fbe7ebfSGarance A Drosehn * strings. This processing is designed entirely for the benefit of the
1378fbe7ebfSGarance A Drosehn * parsing of "#!"-lines (aka "shebang" lines == the first line of an
1388fbe7ebfSGarance A Drosehn * executable script). Different operating systems parse that line in very
1398fbe7ebfSGarance A Drosehn * different ways, and this split-on-spaces processing is meant to provide
1408fbe7ebfSGarance A Drosehn * ways to specify arbitrary arguments on that line, no matter how the OS
1418fbe7ebfSGarance A Drosehn * parses it.
1428fbe7ebfSGarance A Drosehn *
1438fbe7ebfSGarance A Drosehn * Within a single-quoted string, the two characters "\'" are treated as
1448fbe7ebfSGarance A Drosehn * a literal "'" character to add to the string, and "\\" are treated as
1458fbe7ebfSGarance A Drosehn * a literal "\" character to add. Other than that, all characters are
1468fbe7ebfSGarance A Drosehn * copied until the processing gets to a terminating "'".
1478fbe7ebfSGarance A Drosehn *
1488fbe7ebfSGarance A Drosehn * Within a double-quoted string, many more "\"-style escape sequences
1498fbe7ebfSGarance A Drosehn * are recognized, mostly copied from what is recognized in the `printf'
1508fbe7ebfSGarance A Drosehn * command. Some OS's will not allow a literal blank character to be
1518fbe7ebfSGarance A Drosehn * included in the one argument that they recognize on a shebang-line,
1528fbe7ebfSGarance A Drosehn * so a few additional escape-sequences are defined to provide ways to
1538fbe7ebfSGarance A Drosehn * specify blanks.
1548fbe7ebfSGarance A Drosehn *
1558fbe7ebfSGarance A Drosehn * Within a double-quoted string "\_" is turned into a literal blank.
1568fbe7ebfSGarance A Drosehn * (Inside of a single-quoted string, the two characters are just copied)
1578fbe7ebfSGarance A Drosehn * Outside of a quoted string, "\_" is treated as both a blank, and the
1588fbe7ebfSGarance A Drosehn * end of the current argument. So with a shelbang-line of:
1598fbe7ebfSGarance A Drosehn * #!/usr/bin/env -SA=avalue\_perl
1608fbe7ebfSGarance A Drosehn * the -S value would be broken up into arguments "A=avalue" and "perl".
1618fbe7ebfSGarance A Drosehn */
1628fbe7ebfSGarance A Drosehn void
split_spaces(const char * str,int * origind,int * origc,char *** origv)1638fbe7ebfSGarance A Drosehn split_spaces(const char *str, int *origind, int *origc, char ***origv)
1648fbe7ebfSGarance A Drosehn {
165530aafd4SGarance A Drosehn static const char *nullarg = "";
166530aafd4SGarance A Drosehn const char *bq_src, *copystr, *src;
1678fbe7ebfSGarance A Drosehn char *dest, **newargv, *newstr, **nextarg, **oldarg;
1688fbe7ebfSGarance A Drosehn int addcount, bq_destlen, copychar, found_sep, in_arg, in_dq, in_sq;
1698fbe7ebfSGarance A Drosehn
1708fbe7ebfSGarance A Drosehn /*
1718fbe7ebfSGarance A Drosehn * Ignore leading space on the string, and then malloc enough room
1728fbe7ebfSGarance A Drosehn * to build a copy of it. The copy might end up shorter than the
1738fbe7ebfSGarance A Drosehn * original, due to quoted strings and '\'-processing.
1748fbe7ebfSGarance A Drosehn */
1758fbe7ebfSGarance A Drosehn while (isspacech(*str))
1768fbe7ebfSGarance A Drosehn str++;
1778fbe7ebfSGarance A Drosehn if (*str == '\0')
1788fbe7ebfSGarance A Drosehn return;
1798fbe7ebfSGarance A Drosehn newstr = malloc(strlen(str) + 1);
1808fbe7ebfSGarance A Drosehn
1818fbe7ebfSGarance A Drosehn /*
1828fbe7ebfSGarance A Drosehn * Allocate plenty of space for the new array of arg-pointers,
1838fbe7ebfSGarance A Drosehn * and start that array off with the first element of the old
1848fbe7ebfSGarance A Drosehn * array.
1858fbe7ebfSGarance A Drosehn */
1868fbe7ebfSGarance A Drosehn newargv = malloc((*origc + (strlen(str) / 2) + 2) * sizeof(char *));
1878fbe7ebfSGarance A Drosehn nextarg = newargv;
1888fbe7ebfSGarance A Drosehn *nextarg++ = **origv;
1898fbe7ebfSGarance A Drosehn
1908fbe7ebfSGarance A Drosehn /* Come up with the new args by splitting up the given string. */
1918fbe7ebfSGarance A Drosehn addcount = 0;
1928fbe7ebfSGarance A Drosehn bq_destlen = in_arg = in_dq = in_sq = 0;
1938fbe7ebfSGarance A Drosehn bq_src = NULL;
1948fbe7ebfSGarance A Drosehn for (src = str, dest = newstr; *src != '\0'; src++) {
195530aafd4SGarance A Drosehn /*
196530aafd4SGarance A Drosehn * This switch will look at a character in *src, and decide
197530aafd4SGarance A Drosehn * what should be copied to *dest. It only decides what
198530aafd4SGarance A Drosehn * character(s) to copy, it should not modify *dest. In some
199530aafd4SGarance A Drosehn * cases, it will look at multiple characters from *src.
200530aafd4SGarance A Drosehn */
2018fbe7ebfSGarance A Drosehn copychar = found_sep = 0;
202530aafd4SGarance A Drosehn copystr = NULL;
2038fbe7ebfSGarance A Drosehn switch (*src) {
2048fbe7ebfSGarance A Drosehn case '"':
2058fbe7ebfSGarance A Drosehn if (in_sq)
2068fbe7ebfSGarance A Drosehn copychar = *src;
2078fbe7ebfSGarance A Drosehn else if (in_dq)
2088fbe7ebfSGarance A Drosehn in_dq = 0;
2098fbe7ebfSGarance A Drosehn else {
210530aafd4SGarance A Drosehn /*
211530aafd4SGarance A Drosehn * Referencing nullarg ensures that a new
212530aafd4SGarance A Drosehn * argument is created, even if this quoted
213530aafd4SGarance A Drosehn * string ends up with zero characters.
214530aafd4SGarance A Drosehn */
215530aafd4SGarance A Drosehn copystr = nullarg;
2168fbe7ebfSGarance A Drosehn in_dq = 1;
2178fbe7ebfSGarance A Drosehn bq_destlen = dest - *(nextarg - 1);
2188fbe7ebfSGarance A Drosehn bq_src = src;
2198fbe7ebfSGarance A Drosehn }
2208fbe7ebfSGarance A Drosehn break;
2218fbe7ebfSGarance A Drosehn case '$':
2228fbe7ebfSGarance A Drosehn if (in_sq)
2238fbe7ebfSGarance A Drosehn copychar = *src;
2248fbe7ebfSGarance A Drosehn else {
225b5418f51SGarance A Drosehn copystr = expand_vars(in_arg, (nextarg - 1),
226b5418f51SGarance A Drosehn &dest, &src);
2278fbe7ebfSGarance A Drosehn }
2288fbe7ebfSGarance A Drosehn break;
2298fbe7ebfSGarance A Drosehn case '\'':
2308fbe7ebfSGarance A Drosehn if (in_dq)
2318fbe7ebfSGarance A Drosehn copychar = *src;
2328fbe7ebfSGarance A Drosehn else if (in_sq)
2338fbe7ebfSGarance A Drosehn in_sq = 0;
2348fbe7ebfSGarance A Drosehn else {
235530aafd4SGarance A Drosehn /*
236530aafd4SGarance A Drosehn * Referencing nullarg ensures that a new
237530aafd4SGarance A Drosehn * argument is created, even if this quoted
238530aafd4SGarance A Drosehn * string ends up with zero characters.
239530aafd4SGarance A Drosehn */
240530aafd4SGarance A Drosehn copystr = nullarg;
2418fbe7ebfSGarance A Drosehn in_sq = 1;
2428fbe7ebfSGarance A Drosehn bq_destlen = dest - *(nextarg - 1);
2438fbe7ebfSGarance A Drosehn bq_src = src;
2448fbe7ebfSGarance A Drosehn }
2458fbe7ebfSGarance A Drosehn break;
2468fbe7ebfSGarance A Drosehn case '\\':
2478fbe7ebfSGarance A Drosehn if (in_sq) {
2488fbe7ebfSGarance A Drosehn /*
2498fbe7ebfSGarance A Drosehn * Inside single-quoted strings, only the
2508fbe7ebfSGarance A Drosehn * "\'" and "\\" are recognized as special
2518fbe7ebfSGarance A Drosehn * strings.
2528fbe7ebfSGarance A Drosehn */
2538fbe7ebfSGarance A Drosehn copychar = *(src + 1);
2548fbe7ebfSGarance A Drosehn if (copychar == '\'' || copychar == '\\')
2558fbe7ebfSGarance A Drosehn src++;
2568fbe7ebfSGarance A Drosehn else
2578fbe7ebfSGarance A Drosehn copychar = *src;
2588fbe7ebfSGarance A Drosehn break;
2598fbe7ebfSGarance A Drosehn }
2608fbe7ebfSGarance A Drosehn src++;
2618fbe7ebfSGarance A Drosehn switch (*src) {
2628fbe7ebfSGarance A Drosehn case '"':
2638fbe7ebfSGarance A Drosehn case '#':
2648fbe7ebfSGarance A Drosehn case '$':
2658fbe7ebfSGarance A Drosehn case '\'':
2668fbe7ebfSGarance A Drosehn case '\\':
2678fbe7ebfSGarance A Drosehn copychar = *src;
2688fbe7ebfSGarance A Drosehn break;
2698fbe7ebfSGarance A Drosehn case '_':
2708fbe7ebfSGarance A Drosehn /*
2718fbe7ebfSGarance A Drosehn * Alternate way to get a blank, which allows
2728fbe7ebfSGarance A Drosehn * that blank be used to separate arguments
2738fbe7ebfSGarance A Drosehn * when it is not inside a quoted string.
2748fbe7ebfSGarance A Drosehn */
2758fbe7ebfSGarance A Drosehn if (in_dq)
2768fbe7ebfSGarance A Drosehn copychar = ' ';
2778fbe7ebfSGarance A Drosehn else {
2788fbe7ebfSGarance A Drosehn found_sep = 1;
2798fbe7ebfSGarance A Drosehn src++;
2808fbe7ebfSGarance A Drosehn }
2818fbe7ebfSGarance A Drosehn break;
2828fbe7ebfSGarance A Drosehn case 'c':
2838fbe7ebfSGarance A Drosehn /*
2848fbe7ebfSGarance A Drosehn * Ignore remaining characters in the -S string.
2858fbe7ebfSGarance A Drosehn * This would not make sense if found in the
2868fbe7ebfSGarance A Drosehn * middle of a quoted string.
2878fbe7ebfSGarance A Drosehn */
2888fbe7ebfSGarance A Drosehn if (in_dq)
2898fbe7ebfSGarance A Drosehn errx(1, "Sequence '\\%c' is not allowed"
2908fbe7ebfSGarance A Drosehn " in quoted strings", *src);
2918fbe7ebfSGarance A Drosehn goto str_done;
2928fbe7ebfSGarance A Drosehn case 'f':
2938fbe7ebfSGarance A Drosehn copychar = '\f';
2948fbe7ebfSGarance A Drosehn break;
2958fbe7ebfSGarance A Drosehn case 'n':
2968fbe7ebfSGarance A Drosehn copychar = '\n';
2978fbe7ebfSGarance A Drosehn break;
2988fbe7ebfSGarance A Drosehn case 'r':
2998fbe7ebfSGarance A Drosehn copychar = '\r';
3008fbe7ebfSGarance A Drosehn break;
3018fbe7ebfSGarance A Drosehn case 't':
3028fbe7ebfSGarance A Drosehn copychar = '\t';
3038fbe7ebfSGarance A Drosehn break;
3048fbe7ebfSGarance A Drosehn case 'v':
3058fbe7ebfSGarance A Drosehn copychar = '\v';
3068fbe7ebfSGarance A Drosehn break;
3078fbe7ebfSGarance A Drosehn default:
3088fbe7ebfSGarance A Drosehn if (isspacech(*src))
3098fbe7ebfSGarance A Drosehn copychar = *src;
3108fbe7ebfSGarance A Drosehn else
3118fbe7ebfSGarance A Drosehn errx(1, "Invalid sequence '\\%c' in -S",
3128fbe7ebfSGarance A Drosehn *src);
3138fbe7ebfSGarance A Drosehn }
3148fbe7ebfSGarance A Drosehn break;
3158fbe7ebfSGarance A Drosehn default:
3168fbe7ebfSGarance A Drosehn if ((in_dq || in_sq) && in_arg)
3178fbe7ebfSGarance A Drosehn copychar = *src;
318b5418f51SGarance A Drosehn else if (isspacech(*src))
3198fbe7ebfSGarance A Drosehn found_sep = 1;
3208fbe7ebfSGarance A Drosehn else {
3218fbe7ebfSGarance A Drosehn /*
3228fbe7ebfSGarance A Drosehn * If the first character of a new argument
3238fbe7ebfSGarance A Drosehn * is `#', then ignore the remaining chars.
3248fbe7ebfSGarance A Drosehn */
3258fbe7ebfSGarance A Drosehn if (!in_arg && *src == '#')
3268fbe7ebfSGarance A Drosehn goto str_done;
3278fbe7ebfSGarance A Drosehn copychar = *src;
3288fbe7ebfSGarance A Drosehn }
3298fbe7ebfSGarance A Drosehn }
330530aafd4SGarance A Drosehn /*
331530aafd4SGarance A Drosehn * Now that the switch has determined what (if anything)
332530aafd4SGarance A Drosehn * needs to be copied, copy whatever that is to *dest.
333530aafd4SGarance A Drosehn */
334530aafd4SGarance A Drosehn if (copychar || copystr != NULL) {
3358fbe7ebfSGarance A Drosehn if (!in_arg) {
3368fbe7ebfSGarance A Drosehn /* This is the first byte of a new argument */
3378fbe7ebfSGarance A Drosehn *nextarg++ = dest;
3388fbe7ebfSGarance A Drosehn addcount++;
3398fbe7ebfSGarance A Drosehn in_arg = 1;
3408fbe7ebfSGarance A Drosehn }
341530aafd4SGarance A Drosehn if (copychar)
3428fbe7ebfSGarance A Drosehn *dest++ = (char)copychar;
343530aafd4SGarance A Drosehn else if (copystr != NULL)
344530aafd4SGarance A Drosehn while (*copystr != '\0')
345530aafd4SGarance A Drosehn *dest++ = *copystr++;
3468fbe7ebfSGarance A Drosehn } else if (found_sep) {
3478fbe7ebfSGarance A Drosehn *dest++ = '\0';
3488fbe7ebfSGarance A Drosehn while (isspacech(*src))
3498fbe7ebfSGarance A Drosehn src++;
3508fbe7ebfSGarance A Drosehn --src;
3518fbe7ebfSGarance A Drosehn in_arg = 0;
3528fbe7ebfSGarance A Drosehn }
3538fbe7ebfSGarance A Drosehn }
3548fbe7ebfSGarance A Drosehn str_done:
3558fbe7ebfSGarance A Drosehn *dest = '\0';
3568fbe7ebfSGarance A Drosehn *nextarg = NULL;
3578fbe7ebfSGarance A Drosehn if (in_dq || in_sq) {
3588fbe7ebfSGarance A Drosehn errx(1, "No terminating quote for string: %.*s%s",
3598fbe7ebfSGarance A Drosehn bq_destlen, *(nextarg - 1), bq_src);
3608fbe7ebfSGarance A Drosehn }
3618fbe7ebfSGarance A Drosehn if (env_verbosity > 1) {
3628fbe7ebfSGarance A Drosehn fprintf(stderr, "#env split -S:\t'%s'\n", str);
3638fbe7ebfSGarance A Drosehn oldarg = newargv + 1;
3648fbe7ebfSGarance A Drosehn fprintf(stderr, "#env into:\t'%s'\n", *oldarg);
3658fbe7ebfSGarance A Drosehn for (oldarg++; *oldarg; oldarg++)
3668fbe7ebfSGarance A Drosehn fprintf(stderr, "#env &\t'%s'\n", *oldarg);
3678fbe7ebfSGarance A Drosehn }
3688fbe7ebfSGarance A Drosehn
3698fbe7ebfSGarance A Drosehn /* Copy the unprocessed arg-pointers from the original array */
3708fbe7ebfSGarance A Drosehn for (oldarg = *origv + *origind; *oldarg; oldarg++)
3718fbe7ebfSGarance A Drosehn *nextarg++ = *oldarg;
3728fbe7ebfSGarance A Drosehn *nextarg = NULL;
3738fbe7ebfSGarance A Drosehn
3748fbe7ebfSGarance A Drosehn /* Update optind/argc/argv in the calling routine */
3756ab1d4d9SJilles Tjoelker *origc += addcount - *origind + 1;
3768fbe7ebfSGarance A Drosehn *origv = newargv;
3776ab1d4d9SJilles Tjoelker *origind = 1;
3788fbe7ebfSGarance A Drosehn }
3798fbe7ebfSGarance A Drosehn
3808fbe7ebfSGarance A Drosehn /**
3818fbe7ebfSGarance A Drosehn * Routine to split expand any environment variables referenced in the string
3828fbe7ebfSGarance A Drosehn * that -S is processing. For now it only supports the form ${VARNAME}. It
3838fbe7ebfSGarance A Drosehn * explicitly does not support $VARNAME, and obviously can not handle special
3848fbe7ebfSGarance A Drosehn * shell-variables such as $?, $*, $1, etc. It is called with *src_p pointing
3858fbe7ebfSGarance A Drosehn * at the initial '$', and if successful it will update *src_p, *dest_p, and
3868fbe7ebfSGarance A Drosehn * possibly *thisarg_p in the calling routine.
3878fbe7ebfSGarance A Drosehn */
388530aafd4SGarance A Drosehn static const char *
expand_vars(int in_thisarg,char ** thisarg_p,char ** dest_p,const char ** src_p)389b5418f51SGarance A Drosehn expand_vars(int in_thisarg, char **thisarg_p, char **dest_p, const char **src_p)
3908fbe7ebfSGarance A Drosehn {
3918fbe7ebfSGarance A Drosehn const char *vbegin, *vend, *vvalue;
392530aafd4SGarance A Drosehn char *newstr, *vname;
3938fbe7ebfSGarance A Drosehn int bad_reference;
3948fbe7ebfSGarance A Drosehn size_t namelen, newlen;
3958fbe7ebfSGarance A Drosehn
3968fbe7ebfSGarance A Drosehn bad_reference = 1;
3978fbe7ebfSGarance A Drosehn vbegin = vend = (*src_p) + 1;
3988fbe7ebfSGarance A Drosehn if (*vbegin++ == '{')
3998fbe7ebfSGarance A Drosehn if (*vbegin == '_' || isalphach(*vbegin)) {
4008fbe7ebfSGarance A Drosehn vend = vbegin + 1;
4018fbe7ebfSGarance A Drosehn while (*vend == '_' || isalnumch(*vend))
4028fbe7ebfSGarance A Drosehn vend++;
4038fbe7ebfSGarance A Drosehn if (*vend == '}')
4048fbe7ebfSGarance A Drosehn bad_reference = 0;
4058fbe7ebfSGarance A Drosehn }
4068fbe7ebfSGarance A Drosehn if (bad_reference)
4078fbe7ebfSGarance A Drosehn errx(1, "Only ${VARNAME} expansion is supported, error at: %s",
4088fbe7ebfSGarance A Drosehn *src_p);
4098fbe7ebfSGarance A Drosehn
4108fbe7ebfSGarance A Drosehn /*
4118fbe7ebfSGarance A Drosehn * We now know we have a valid environment variable name, so update
4128fbe7ebfSGarance A Drosehn * the caller's source-pointer to the last character in that reference,
4138fbe7ebfSGarance A Drosehn * and then pick up the matching value. If the variable is not found,
4148fbe7ebfSGarance A Drosehn * or if it has a null value, then our work here is done.
4158fbe7ebfSGarance A Drosehn */
4168fbe7ebfSGarance A Drosehn *src_p = vend;
4178fbe7ebfSGarance A Drosehn namelen = vend - vbegin + 1;
4188fbe7ebfSGarance A Drosehn vname = malloc(namelen);
4198fbe7ebfSGarance A Drosehn strlcpy(vname, vbegin, namelen);
4208fbe7ebfSGarance A Drosehn vvalue = getenv(vname);
4218fbe7ebfSGarance A Drosehn if (vvalue == NULL || *vvalue == '\0') {
4228fbe7ebfSGarance A Drosehn if (env_verbosity > 2)
4238fbe7ebfSGarance A Drosehn fprintf(stderr,
4248fbe7ebfSGarance A Drosehn "#env replacing ${%s} with null string\n",
4258fbe7ebfSGarance A Drosehn vname);
4269afe6a5bSGarance A Drosehn free(vname);
427530aafd4SGarance A Drosehn return (NULL);
4288fbe7ebfSGarance A Drosehn }
4298fbe7ebfSGarance A Drosehn
4308fbe7ebfSGarance A Drosehn if (env_verbosity > 2)
4318fbe7ebfSGarance A Drosehn fprintf(stderr, "#env expanding ${%s} into '%s'\n", vname,
4328fbe7ebfSGarance A Drosehn vvalue);
4338fbe7ebfSGarance A Drosehn
4348fbe7ebfSGarance A Drosehn /*
4358fbe7ebfSGarance A Drosehn * There is some value to copy to the destination. If the value is
436530aafd4SGarance A Drosehn * shorter than the ${VARNAME} reference that it replaces, then our
437530aafd4SGarance A Drosehn * caller can just copy the value to the existing destination.
4388fbe7ebfSGarance A Drosehn */
4399afe6a5bSGarance A Drosehn if (strlen(vname) + 3 >= strlen(vvalue)) {
4409afe6a5bSGarance A Drosehn free(vname);
441530aafd4SGarance A Drosehn return (vvalue);
4429afe6a5bSGarance A Drosehn }
4438fbe7ebfSGarance A Drosehn
4448fbe7ebfSGarance A Drosehn /*
4458fbe7ebfSGarance A Drosehn * The value is longer than the string it replaces, which means the
4468fbe7ebfSGarance A Drosehn * present destination area is too small to hold it. Create a new
447b5418f51SGarance A Drosehn * destination area, and update the caller's 'dest' variable to match.
448b5418f51SGarance A Drosehn * If the caller has already started copying some info for 'thisarg'
449b5418f51SGarance A Drosehn * into the present destination, then the new destination area must
450b5418f51SGarance A Drosehn * include a copy of that data, and the pointer to 'thisarg' must also
451b5418f51SGarance A Drosehn * be updated. Note that it is still the caller which copies this
452b5418f51SGarance A Drosehn * vvalue to the new *dest.
4538fbe7ebfSGarance A Drosehn */
454b5418f51SGarance A Drosehn newlen = strlen(vvalue) + strlen(*src_p) + 1;
455b5418f51SGarance A Drosehn if (in_thisarg) {
456530aafd4SGarance A Drosehn **dest_p = '\0'; /* Provide terminator for 'thisarg' */
457b5418f51SGarance A Drosehn newlen += strlen(*thisarg_p);
4588fbe7ebfSGarance A Drosehn newstr = malloc(newlen);
4598fbe7ebfSGarance A Drosehn strcpy(newstr, *thisarg_p);
4608fbe7ebfSGarance A Drosehn *thisarg_p = newstr;
461b5418f51SGarance A Drosehn } else {
462b5418f51SGarance A Drosehn newstr = malloc(newlen);
463b5418f51SGarance A Drosehn *newstr = '\0';
464b5418f51SGarance A Drosehn }
4658fbe7ebfSGarance A Drosehn *dest_p = strchr(newstr, '\0');
4669afe6a5bSGarance A Drosehn free(vname);
467530aafd4SGarance A Drosehn return (vvalue);
4688fbe7ebfSGarance A Drosehn }
469