wordexp.c revision 1.4 1 1.4 christos /* $NetBSD: wordexp.c,v 1.4 2024/01/20 14:52:47 christos Exp $ */
2 1.1 seb
3 1.1 seb /*-
4 1.1 seb * Copyright (c) 2002 Tim J. Robbins.
5 1.1 seb * All rights reserved.
6 1.1 seb *
7 1.1 seb * Redistribution and use in source and binary forms, with or without
8 1.1 seb * modification, are permitted provided that the following conditions
9 1.1 seb * are met:
10 1.1 seb * 1. Redistributions of source code must retain the above copyright
11 1.1 seb * notice, this list of conditions and the following disclaimer.
12 1.1 seb * 2. Redistributions in binary form must reproduce the above copyright
13 1.1 seb * notice, this list of conditions and the following disclaimer in the
14 1.1 seb * documentation and/or other materials provided with the distribution.
15 1.1 seb *
16 1.1 seb * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 1.1 seb * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 1.1 seb * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 1.1 seb * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 1.1 seb * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 1.1 seb * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 1.1 seb * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 1.1 seb * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 1.1 seb * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 1.1 seb * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 1.1 seb * SUCH DAMAGE.
27 1.1 seb */
28 1.1 seb
29 1.1 seb #include "namespace.h"
30 1.1 seb #include <sys/cdefs.h>
31 1.1 seb #include <sys/types.h>
32 1.1 seb #include <assert.h>
33 1.1 seb #include <sys/wait.h>
34 1.1 seb #include <fcntl.h>
35 1.1 seb #include <paths.h>
36 1.1 seb #include <stdio.h>
37 1.1 seb #include <stdlib.h>
38 1.1 seb #include <string.h>
39 1.1 seb #include <unistd.h>
40 1.1 seb #include <wordexp.h>
41 1.4 christos #include "extern.h"
42 1.1 seb
43 1.1 seb #if defined(LIBC_SCCS) && !defined(lint)
44 1.1 seb #if 0
45 1.1 seb __FBSDID("$FreeBSD: /repoman/r/ncvs/src/lib/libc/gen/wordexp.c,v 1.5 2004/04/09 11:32:32 tjr Exp $");
46 1.1 seb #else
47 1.4 christos __RCSID("$NetBSD: wordexp.c,v 1.4 2024/01/20 14:52:47 christos Exp $");
48 1.1 seb #endif
49 1.1 seb #endif /* LIBC_SCCS and not lint */
50 1.1 seb
51 1.1 seb static int we_askshell(const char *, wordexp_t *, int);
52 1.1 seb static int we_check(const char *, int);
53 1.1 seb
54 1.1 seb /*
55 1.1 seb * wordexp --
56 1.1 seb * Perform shell word expansion on `words' and place the resulting list
57 1.1 seb * of words in `we'. See wordexp(3).
58 1.1 seb *
59 1.1 seb */
60 1.1 seb int
61 1.1 seb wordexp(const char * __restrict words, wordexp_t * __restrict we, int flags)
62 1.1 seb {
63 1.1 seb int error;
64 1.1 seb
65 1.1 seb _DIAGASSERT(we != NULL);
66 1.1 seb _DIAGASSERT(words != NULL);
67 1.1 seb if (flags & WRDE_REUSE)
68 1.1 seb wordfree(we);
69 1.1 seb if ((flags & WRDE_APPEND) == 0) {
70 1.1 seb we->we_wordc = 0;
71 1.1 seb we->we_wordv = NULL;
72 1.1 seb we->we_strings = NULL;
73 1.1 seb we->we_nbytes = 0;
74 1.1 seb }
75 1.1 seb if ((error = we_check(words, flags)) != 0) {
76 1.1 seb wordfree(we);
77 1.1 seb return (error);
78 1.1 seb }
79 1.1 seb if ((error = we_askshell(words, we, flags)) != 0) {
80 1.1 seb wordfree(we);
81 1.1 seb return (error);
82 1.1 seb }
83 1.1 seb return (0);
84 1.1 seb }
85 1.1 seb
86 1.1 seb /*
87 1.1 seb * we_askshell --
88 1.1 seb * Use the `wordexp' /bin/sh builtin function to do most of the work
89 1.1 seb * in expanding the word string. This function is complicated by
90 1.1 seb * memory management.
91 1.1 seb */
92 1.1 seb static int
93 1.1 seb we_askshell(const char *words, wordexp_t *we, int flags)
94 1.1 seb {
95 1.1 seb int pdes[2]; /* Pipe to child */
96 1.1 seb size_t nwords, nbytes; /* Number of words, bytes from child */
97 1.1 seb int i; /* Handy integer */
98 1.3 lukem unsigned int ui; /* For array iteration */
99 1.1 seb size_t sofs; /* Offset into we->we_strings */
100 1.1 seb size_t vofs; /* Offset into we->we_wordv */
101 1.1 seb pid_t pid; /* Process ID of child */
102 1.1 seb int status; /* Child exit status */
103 1.2 christos const char *ifs; /* IFS env. var. */
104 1.1 seb char *np, *p; /* Handy pointers */
105 1.1 seb char *nstrings; /* Temporary for realloc() */
106 1.1 seb char **nwv; /* Temporary for realloc() */
107 1.1 seb FILE *fp; /* Stream to read pipe */
108 1.1 seb char *cmd;
109 1.1 seb
110 1.1 seb if ((ifs = getenv("IFS")) == NULL)
111 1.1 seb ifs = " \t\n";
112 1.1 seb if (asprintf(&cmd, "wordexp%c%s\n", *ifs, words) < 0)
113 1.1 seb return (WRDE_NOSPACE);
114 1.1 seb if (pipe(pdes) < 0) {
115 1.1 seb free(cmd);
116 1.1 seb return (WRDE_ERRNO);
117 1.1 seb }
118 1.1 seb if ((fp = fdopen(pdes[0], "r")) == NULL) {
119 1.1 seb free(cmd);
120 1.1 seb return (WRDE_ERRNO);
121 1.1 seb }
122 1.1 seb if ((pid = fork()) < 0) {
123 1.1 seb free(cmd);
124 1.1 seb fclose(fp);
125 1.1 seb close(pdes[1]);
126 1.1 seb return (WRDE_ERRNO);
127 1.1 seb }
128 1.1 seb else if (pid == 0) {
129 1.1 seb /*
130 1.1 seb * We are the child; just get /bin/sh to run the wordexp
131 1.1 seb * builtin on `words'.
132 1.1 seb */
133 1.1 seb int devnull;
134 1.1 seb
135 1.1 seb close(pdes[0]);
136 1.1 seb if (pdes[1] != STDOUT_FILENO) {
137 1.1 seb if (dup2(pdes[1], STDOUT_FILENO) < 0)
138 1.1 seb _exit(1);
139 1.1 seb close(pdes[1]);
140 1.1 seb }
141 1.1 seb if ((flags & WRDE_SHOWERR) == 0) {
142 1.1 seb if ((devnull = open(_PATH_DEVNULL, O_RDWR, 0666)) < 0)
143 1.1 seb _exit(1);
144 1.1 seb if (dup2(devnull, STDERR_FILENO) < 0)
145 1.1 seb _exit(1);
146 1.1 seb close(devnull);
147 1.1 seb }
148 1.1 seb execle(_PATH_BSHELL, "sh", flags & WRDE_UNDEF ? "-u" : "+u",
149 1.1 seb "-c", cmd, (char *)NULL, environ);
150 1.1 seb _exit(1);
151 1.1 seb }
152 1.1 seb
153 1.1 seb /*
154 1.1 seb * We are the parent; read the output of the shell wordexp function,
155 1.1 seb * which is a decimal word count, an null, a decimal byte count,
156 1.1 seb * (not including terminating null bytes), a null and then followed
157 1.1 seb * by the expanded words separated by nulls.
158 1.1 seb */
159 1.1 seb free(cmd);
160 1.1 seb close(pdes[1]);
161 1.1 seb /* read the word count */
162 1.1 seb nwords = 0;
163 1.1 seb while ((i = getc(fp)) != EOF) {
164 1.1 seb if (i == '\0')
165 1.1 seb break;
166 1.1 seb nwords *= 10;
167 1.1 seb nwords += (i - '0');
168 1.1 seb }
169 1.1 seb /* read the byte count */
170 1.1 seb nbytes = 0;
171 1.1 seb while ((i = getc(fp)) != EOF) {
172 1.1 seb if (i == '\0')
173 1.1 seb break;
174 1.1 seb nbytes *= 10;
175 1.1 seb nbytes += (i - '0');
176 1.1 seb }
177 1.1 seb if (i == EOF) {
178 1.1 seb fclose(fp);
179 1.1 seb waitpid(pid, &status, 0);
180 1.1 seb return (flags & WRDE_UNDEF ? WRDE_BADVAL : WRDE_SYNTAX);
181 1.1 seb }
182 1.1 seb nbytes += nwords;
183 1.1 seb
184 1.1 seb /*
185 1.1 seb * Allocate or reallocate (when flags & WRDE_APPEND) the word vector
186 1.1 seb * and string storage buffers for the expanded words we're about to
187 1.1 seb * read from the child.
188 1.1 seb */
189 1.1 seb sofs = we->we_nbytes;
190 1.1 seb vofs = we->we_wordc;
191 1.1 seb if ((flags & (WRDE_DOOFFS|WRDE_APPEND)) == (WRDE_DOOFFS|WRDE_APPEND))
192 1.1 seb vofs += we->we_offs;
193 1.1 seb we->we_wordc += nwords;
194 1.1 seb we->we_nbytes += nbytes;
195 1.1 seb if ((nwv = realloc(we->we_wordv, (we->we_wordc + 1 +
196 1.1 seb (flags & WRDE_DOOFFS ? we->we_offs : 0)) *
197 1.1 seb sizeof(char *))) == NULL) {
198 1.1 seb fclose(fp);
199 1.1 seb waitpid(pid, &status, 0);
200 1.1 seb return (WRDE_NOSPACE);
201 1.1 seb }
202 1.1 seb we->we_wordv = nwv;
203 1.1 seb if ((nstrings = realloc(we->we_strings, we->we_nbytes)) == NULL) {
204 1.1 seb fclose(fp);
205 1.1 seb waitpid(pid, &status, 0);
206 1.1 seb return (WRDE_NOSPACE);
207 1.1 seb }
208 1.3 lukem for (ui = 0; ui < vofs; ui++)
209 1.3 lukem if (we->we_wordv[ui] != NULL)
210 1.3 lukem we->we_wordv[ui] += nstrings - we->we_strings;
211 1.1 seb we->we_strings = nstrings;
212 1.1 seb
213 1.1 seb if (fread(we->we_strings + sofs, sizeof(char), nbytes, fp) != nbytes) {
214 1.1 seb fclose(fp);
215 1.1 seb waitpid(pid, &status, 0);
216 1.1 seb return (flags & WRDE_UNDEF ? WRDE_BADVAL : WRDE_SYNTAX);
217 1.1 seb }
218 1.1 seb
219 1.1 seb if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status) ||
220 1.1 seb WEXITSTATUS(status) != 0) {
221 1.1 seb fclose(fp);
222 1.1 seb return (flags & WRDE_UNDEF ? WRDE_BADVAL : WRDE_SYNTAX);
223 1.1 seb }
224 1.1 seb fclose(fp);
225 1.1 seb
226 1.1 seb /*
227 1.1 seb * Break the null-terminated expanded word strings out into
228 1.1 seb * the vector.
229 1.1 seb */
230 1.1 seb if (vofs == 0 && flags & WRDE_DOOFFS)
231 1.1 seb while (vofs < we->we_offs)
232 1.1 seb we->we_wordv[vofs++] = NULL;
233 1.1 seb p = we->we_strings + sofs;
234 1.1 seb while (nwords-- != 0) {
235 1.1 seb we->we_wordv[vofs++] = p;
236 1.1 seb if ((np = memchr(p, '\0', nbytes)) == NULL)
237 1.1 seb return (WRDE_NOSPACE); /* XXX */
238 1.1 seb nbytes -= np - p + 1;
239 1.1 seb p = np + 1;
240 1.1 seb }
241 1.1 seb we->we_wordv[vofs] = NULL;
242 1.1 seb
243 1.1 seb return (0);
244 1.1 seb }
245 1.1 seb
246 1.1 seb /*
247 1.1 seb * we_check --
248 1.1 seb * Check that the string contains none of the following unquoted
249 1.1 seb * special characters: <newline> |&;<>(){}
250 1.1 seb * or command substitutions when WRDE_NOCMD is set in flags.
251 1.1 seb */
252 1.1 seb static int
253 1.1 seb we_check(const char *words, int flags)
254 1.1 seb {
255 1.1 seb char c;
256 1.1 seb int dquote, level, quote, squote;
257 1.1 seb
258 1.1 seb quote = squote = dquote = 0;
259 1.1 seb while ((c = *words++) != '\0') {
260 1.1 seb switch (c) {
261 1.1 seb case '\\':
262 1.1 seb quote ^= 1;
263 1.1 seb continue;
264 1.1 seb case '\'':
265 1.1 seb if (quote + dquote == 0)
266 1.1 seb squote ^= 1;
267 1.1 seb break;
268 1.1 seb case '"':
269 1.1 seb if (quote + squote == 0)
270 1.1 seb dquote ^= 1;
271 1.1 seb break;
272 1.1 seb case '`':
273 1.1 seb if (quote + squote == 0 && flags & WRDE_NOCMD)
274 1.1 seb return (WRDE_CMDSUB);
275 1.1 seb while ((c = *words++) != '\0' && c != '`')
276 1.1 seb if (c == '\\' && (c = *words++) == '\0')
277 1.1 seb break;
278 1.1 seb if (c == '\0')
279 1.1 seb return (WRDE_SYNTAX);
280 1.1 seb break;
281 1.1 seb case '|': case '&': case ';': case '<': case '>':
282 1.1 seb case '{': case '}': case '(': case ')': case '\n':
283 1.1 seb if (quote + squote + dquote == 0)
284 1.1 seb return (WRDE_BADCHAR);
285 1.1 seb break;
286 1.1 seb case '$':
287 1.1 seb if ((c = *words++) == '\0')
288 1.1 seb break;
289 1.1 seb else if (quote + squote == 0 && c == '(') {
290 1.1 seb if (flags & WRDE_NOCMD && *words != '(')
291 1.1 seb return (WRDE_CMDSUB);
292 1.1 seb level = 1;
293 1.1 seb while ((c = *words++) != '\0') {
294 1.1 seb if (c == '\\') {
295 1.1 seb if ((c = *words++) == '\0')
296 1.1 seb break;
297 1.1 seb } else if (c == '(')
298 1.1 seb level++;
299 1.1 seb else if (c == ')' && --level == 0)
300 1.1 seb break;
301 1.1 seb }
302 1.1 seb if (c == '\0' || level != 0)
303 1.1 seb return (WRDE_SYNTAX);
304 1.1 seb } else if (quote + squote == 0 && c == '{') {
305 1.1 seb level = 1;
306 1.1 seb while ((c = *words++) != '\0') {
307 1.1 seb if (c == '\\') {
308 1.1 seb if ((c = *words++) == '\0')
309 1.1 seb break;
310 1.1 seb } else if (c == '{')
311 1.1 seb level++;
312 1.1 seb else if (c == '}' && --level == 0)
313 1.1 seb break;
314 1.1 seb }
315 1.1 seb if (c == '\0' || level != 0)
316 1.1 seb return (WRDE_SYNTAX);
317 1.1 seb } else
318 1.1 seb c = *--words;
319 1.1 seb break;
320 1.1 seb default:
321 1.1 seb break;
322 1.1 seb }
323 1.1 seb quote = 0;
324 1.1 seb }
325 1.1 seb if (quote + squote + dquote != 0)
326 1.1 seb return (WRDE_SYNTAX);
327 1.1 seb
328 1.1 seb return (0);
329 1.1 seb }
330 1.1 seb
331 1.1 seb /*
332 1.1 seb * wordfree --
333 1.1 seb * Free the result of wordexp(). See wordexp(3).
334 1.1 seb *
335 1.1 seb */
336 1.1 seb void
337 1.1 seb wordfree(wordexp_t *we)
338 1.1 seb {
339 1.1 seb _DIAGASSERT(we != NULL);
340 1.1 seb free(we->we_wordv);
341 1.1 seb free(we->we_strings);
342 1.1 seb we->we_wordv = NULL;
343 1.1 seb we->we_strings = NULL;
344 1.1 seb we->we_nbytes = 0;
345 1.1 seb we->we_wordc = 0;
346 1.1 seb }
347