xref: /illumos-gate/usr/src/lib/libc/port/regex/wordexp.c (revision dd4eeefd)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * This code is MKS code ported to Solaris originally with minimum
30  * modifications so that upgrades from MKS would readily integrate.
31  * The MKS basis for this modification was:
32  *
33  *	$Id: wordexp.c 1.22 1994/11/21 18:24:50 miked
34  *
35  * Additional modifications have been made to this code to make it
36  * 64-bit clean.
37  */
38 
39 /*
40  * wordexp, wordfree -- POSIX.2 D11.2 word expansion routines.
41  *
42  * Copyright 1985, 1992 by Mortice Kern Systems Inc.  All rights reserved.
43  * Modified by Roland Mainz <roland.mainz@nrubsig.org> to support ksh93.
44  *
45  */
46 
47 #pragma	weak wordexp = _wordexp
48 #pragma	weak wordfree = _wordfree
49 
50 /* Safeguard against mistakes in the Makefiles */
51 #ifndef WORDEXP_KSH93
52 #error "WORDEXP_KSH93 not set. Please check the Makefile flags."
53 #endif
54 
55 #include "synonyms.h"
56 #include <stdio.h>
57 #include <unistd.h>
58 #include <limits.h>
59 #include <fcntl.h>
60 #include <limits.h>
61 #include <stdlib.h>
62 #if WORDEXP_KSH93
63 #include <alloca.h>
64 #endif /* WORDEXP_KSH93 */
65 #include <string.h>
66 #include <sys/wait.h>
67 #include <unistd.h>
68 #include <wordexp.h>
69 #include <stdio.h>
70 #include <spawn.h>
71 #include <errno.h>
72 
73 #define	INITIAL	8		/* initial pathv allocation */
74 #define	BUFSZ	256		/* allocation unit of the line buffer */
75 
76 /* Local prototypes */
77 static int append(wordexp_t *, char *);
78 
79 #if WORDEXP_KSH93
80 /*
81  * |mystpcpy| - like |strcpy()| but returns the end of the buffer
82  * We'll add this later (and a matching multibyte/widechar version)
83  * as normal libc function.
84  *
85  * Copy string s2 to s1.  s1 must be large enough.
86  * return s1-1 (position of string terminator ('\0') in destination buffer).
87  */
88 static char *
89 mystpcpy(char *s1, const char *s2)
90 {
91 	while (*s1++ = *s2++)
92 		;
93 	return (s1-1);
94 }
95 
96 /*
97  * Do word expansion.
98  * We built a mini-script in |buff| which takes care of all details,
99  * including stdin/stdout/stderr redirection, WRDE_NOCMD mode and
100  * the word expansion itself.
101  */
102 int
103 wordexp(const char *word, wordexp_t *wp, int flags)
104 {
105 	char *args[10];
106 	wordexp_t wptmp;
107 	size_t si;
108 	int i;
109 	pid_t pid;
110 	char *line, *eob, *cp;	/* word from shell */
111 	int rv = WRDE_ERRNO;
112 	int status;
113 	int pv[2];		/* pipe from shell stdout */
114 	FILE *fp;		/* pipe read stream */
115 	int serrno, tmpalloc;
116 
117 	/*
118 	 * Do absolute minimum necessary for the REUSE flag. Eventually
119 	 * want to be able to actually avoid excessive malloc calls.
120 	 */
121 	if (flags & WRDE_REUSE)
122 		wordfree(wp);
123 
124 	/*
125 	 * Initialize wordexp_t
126 	 *
127 	 * XPG requires that the struct pointed to by wp not be modified
128 	 * unless wordexp() either succeeds, or fails on WRDE_NOSPACE.
129 	 * So we work with wptmp, and only copy wptmp to wp if one of the
130 	 * previously mentioned conditions is satisfied.
131 	 */
132 	wptmp = *wp;
133 
134 	/*
135 	 * Man page says:
136 	 * 2. All of the calls must set WRDE_DOOFFS, or all must not
137 	 * set it.
138 	 * Therefore, if it's not set, we_offs will always be reset.
139 	 */
140 	if ((flags & WRDE_DOOFFS) == 0)
141 		wptmp.we_offs = 0;
142 
143 	/*
144 	 * If we get APPEND|REUSE, how should we do?
145 	 * We allocate the buffer anyway to avoid segfault.
146 	 */
147 	tmpalloc = 0;
148 	if ((flags & WRDE_APPEND) == 0 || (flags & WRDE_REUSE)) {
149 		wptmp.we_wordc = 0;
150 		wptmp.we_wordn = wptmp.we_offs + INITIAL;
151 		wptmp.we_wordv = (char **)malloc(
152 		    sizeof (char *) * wptmp.we_wordn);
153 		if (wptmp.we_wordv == NULL)
154 			return (WRDE_NOSPACE);
155 		wptmp.we_wordp = wptmp.we_wordv + wptmp.we_offs;
156 		for (si = 0; si < wptmp.we_offs; si++)
157 			wptmp.we_wordv[si] = NULL;
158 		tmpalloc = 1;
159 	}
160 
161 	/*
162 	 * Set up pipe from shell stdout to "fp" for us
163 	 */
164 	if (pipe(pv) < 0)
165 		goto cleanup;
166 
167 	/*
168 	 * Fork/exec shell
169 	 */
170 
171 	if ((pid = fork()) == -1) {
172 		serrno = errno;
173 		(void) close(pv[0]);
174 		(void) close(pv[1]);
175 		errno = serrno;
176 		goto cleanup;
177 	}
178 
179 	if (pid == 0) {	 /* child */
180 		/*
181 		 * Calculate size of required buffer (which is size of the
182 		 * input string (|word|) plus all string literals below;
183 		 * this value MUST be adjusted each time the literals are
184 		 * changed!!!!).
185 		 */
186 		size_t bufflen = 124+strlen(word); /* Length of |buff| */
187 		char *buff = alloca(bufflen);
188 		char *currbuffp; /* Current position of '\0' in |buff| */
189 		int i;
190 		const char *path;
191 
192 		(void) dup2(pv[1], 1);
193 		(void) close(pv[0]);
194 		(void) close(pv[1]);
195 
196 		path = "/usr/bin/ksh93";
197 		i = 0;
198 
199 		/* Start filling the buffer */
200 		buff[0] = '\0';
201 		currbuffp = buff;
202 
203 		if (flags & WRDE_UNDEF)
204 			currbuffp = mystpcpy(currbuffp, "set -o nounset ; ");
205 		if ((flags & WRDE_SHOWERR) == 0) {
206 			/*
207 			 * The newline ('\n') is neccesary to make sure that
208 			 * the redirection to /dev/null is already active in
209 			 * the case the printf below contains a syntax
210 			 * error...
211 			 */
212 			currbuffp = mystpcpy(currbuffp, "exec 2>/dev/null\n");
213 		}
214 		/* Squish stdin */
215 		currbuffp = mystpcpy(currbuffp, "exec 0</dev/null\n");
216 
217 		if (flags & WRDE_NOCMD) {
218 			/*
219 			 * Switch to restricted shell (rksh) mode here to
220 			 * put the word expansion into a "cage" which
221 			 * prevents users from executing external commands
222 			 * (outside those listed by ${PATH} (which we set
223 			 * explicitly to /usr/no/such/path/element/)).
224 			 */
225 			currbuffp = mystpcpy(currbuffp, "set -o restricted\n");
226 
227 			(void) putenv("PATH=/usr/no/such/path/element/");
228 
229 		}
230 
231 		(void) snprintf(currbuffp, bufflen,
232 		    "print -f \"%%s\\000\" %s", word);
233 
234 		args[i++] = strrchr(path, '/') + 1;
235 		args[i++] = "-c";
236 		args[i++] = buff;
237 		args[i++] = NULL;
238 
239 		(void) execv(path, args);
240 		_exit(127);
241 	}
242 
243 	(void) close(pv[1]);
244 
245 	if ((fp = fdopen(pv[0], "rF")) == NULL) {
246 		serrno = errno;
247 		(void) close(pv[0]);
248 		errno = serrno;
249 		goto wait_cleanup;
250 	}
251 
252 	/*
253 	 * Read words from shell, separated with '\0'.
254 	 * Since there is no way to disable IFS splitting,
255 	 * it would be possible to separate the output with '\n'.
256 	 */
257 	cp = line = malloc(BUFSZ);
258 	if (line == NULL) {
259 		(void) fclose(fp);
260 		rv = WRDE_NOSPACE;
261 		goto wait_cleanup;
262 	}
263 	eob = line + BUFSZ;
264 
265 	rv = 0;
266 	while ((i = getc(fp)) != EOF) {
267 		*cp++ = (char)i;
268 		if (i == '\0') {
269 			cp = line;
270 			if ((rv = append(&wptmp, cp)) != 0) {
271 				break;
272 			}
273 		}
274 		if (cp == eob) {
275 			size_t bs = (eob - line);
276 			char *nl;
277 
278 			if ((nl = realloc(line, bs + BUFSZ)) == NULL) {
279 				rv = WRDE_NOSPACE;
280 				break;
281 			}
282 			line = nl;
283 			cp = line + bs;
284 			eob = cp + BUFSZ;
285 		}
286 	}
287 
288 	wptmp.we_wordp[wptmp.we_wordc] = NULL;
289 
290 	free(line);
291 	(void) fclose(fp);	/* kill shell if still writing */
292 
293 wait_cleanup:
294 	if (waitpid(pid, &status, 0) == -1)
295 		rv = WRDE_ERRNO;
296 	else if (rv == 0)
297 		rv = WEXITSTATUS(status); /* shell WRDE_* status */
298 
299 cleanup:
300 	if (rv == 0)
301 		*wp = wptmp;
302 	else {
303 		if (tmpalloc)
304 			wordfree(&wptmp);
305 	}
306 
307 	/*
308 	 * Map ksh errors to wordexp() errors
309 	 */
310 	if (rv == 4)
311 		rv = WRDE_CMDSUB;
312 	else if (rv == 5)
313 		rv = WRDE_BADVAL;
314 	else if (rv == 6)
315 		rv = WRDE_SYNTAX;
316 	return (rv);
317 }
318 
319 #else /* WORDEXP_KSH93 */
320 
321 extern	int __xpg4;	/* defined in _xpg4.c; 0 if not xpg4-compiled program */
322 
323 /*
324  * Needs no locking if fetched only once.
325  * See getenv()/putenv()/setenv().
326  */
327 extern	const char **environ;
328 
329 /*
330  * Do word expansion.
331  * We just pass our arguments to shell with -E option.  Note that the
332  * underlying shell must recognize the -E option, and do the right thing
333  * with it.
334  */
335 int
336 wordexp(const char *word, wordexp_t *wp, int flags)
337 {
338 	char options[9];
339 	char *optendp = options;
340 	char *argv[4];
341 	const char *path;
342 	wordexp_t wptmp;
343 	size_t si;
344 	int i;
345 	pid_t pid;
346 	char *line, *eob, *cp;		/* word from shell */
347 	int rv = WRDE_ERRNO;
348 	int status;
349 	int pv[2];			/* pipe from shell stdout */
350 	FILE *fp;			/* pipe read stream */
351 	int tmpalloc;
352 	char *wd = NULL;
353 	const char **env = NULL;
354 	const char **envp;
355 	const char *ev;
356 	int n;
357 	posix_spawnattr_t attr;
358 	posix_spawn_file_actions_t fact;
359 	int error;
360 
361 	static const char *sun_path = "/bin/ksh";
362 	static const char *xpg4_path = "/usr/xpg4/bin/sh";
363 
364 	/*
365 	 * Do absolute minimum neccessary for the REUSE flag. Eventually
366 	 * want to be able to actually avoid excessive malloc calls.
367 	 */
368 	if (flags & WRDE_REUSE)
369 		wordfree(wp);
370 
371 	/*
372 	 * Initialize wordexp_t
373 	 *
374 	 * XPG requires that the struct pointed to by wp not be modified
375 	 * unless wordexp() either succeeds, or fails on WRDE_NOSPACE.
376 	 * So we work with wptmp, and only copy wptmp to wp if one of the
377 	 * previously mentioned conditions is satisfied.
378 	 */
379 	wptmp = *wp;
380 
381 	/*
382 	 * Man page says:
383 	 * 2. All of the calls must set WRDE_DOOFFS, or all must not
384 	 *    set it.
385 	 * Therefore, if it's not set, we_offs will always be reset.
386 	 */
387 	if ((flags & WRDE_DOOFFS) == 0)
388 		wptmp.we_offs = 0;
389 
390 	/*
391 	 * If we get APPEND|REUSE, how should we do?
392 	 * allocating buffer anyway to avoid segfault.
393 	 */
394 	tmpalloc = 0;
395 	if ((flags & WRDE_APPEND) == 0 || (flags & WRDE_REUSE)) {
396 		wptmp.we_wordc = 0;
397 		wptmp.we_wordn = wptmp.we_offs + INITIAL;
398 		wptmp.we_wordv = malloc(sizeof (char *) * wptmp.we_wordn);
399 		if (wptmp.we_wordv == NULL)
400 			return (WRDE_NOSPACE);
401 		wptmp.we_wordp = wptmp.we_wordv + wptmp.we_offs;
402 		for (si = 0; si < wptmp.we_offs; si++)
403 			wptmp.we_wordv[si] = NULL;
404 		tmpalloc = 1;
405 	}
406 
407 	/*
408 	 * Turn flags into shell options
409 	 */
410 	*optendp++ = '-';
411 	*optendp++ = (char)0x05;		/* ksh -^E */
412 	if (flags & WRDE_UNDEF)
413 		*optendp++ = 'u';
414 	if (flags & WRDE_NOCMD)
415 		*optendp++ = 'N';
416 	*optendp = '\0';
417 
418 	/*
419 	 * Make sure PWD is in the environment.
420 	 */
421 	if ((envp = environ) == NULL) {		/* can't happen? */
422 		ev = NULL;
423 		n = 0;
424 	} else {
425 		for (n = 0; (ev = envp[n]) != NULL; n++) {
426 			if (*ev == 'P' && strncmp(ev, "PWD=", 4) == 0)
427 				break;
428 		}
429 	}
430 	if (ev == NULL) {	/* PWD missing from the environment */
431 		/* allocate a new environment */
432 		if ((env = malloc((n + 2) * sizeof (char *))) == NULL ||
433 		    (wd = malloc(PATH_MAX + 4)) == NULL)
434 			goto cleanup;
435 		for (i = 0; i < n; i++)
436 			env[i] = envp[i];
437 		(void) strcpy(wd, "PWD=");
438 		if (getcwd(&wd[4], PATH_MAX) == NULL)
439 			(void) strcpy(&wd[4], "/");
440 		env[i] = wd;
441 		env[i + 1] = NULL;
442 		envp = env;
443 	}
444 
445 	if ((error = posix_spawnattr_init(&attr)) != 0) {
446 		errno = error;
447 		goto cleanup;
448 	}
449 	if ((error = posix_spawn_file_actions_init(&fact)) != 0) {
450 		(void) posix_spawnattr_destroy(&attr);
451 		errno = error;
452 		goto cleanup;
453 	}
454 
455 	/*
456 	 * Set up pipe from shell stdout to "fp" for us
457 	 */
458 	if (pipe(pv) < 0) {
459 		error = errno;
460 		(void) posix_spawnattr_destroy(&attr);
461 		(void) posix_spawn_file_actions_destroy(&fact);
462 		errno = error;
463 		goto cleanup;
464 	}
465 
466 	/*
467 	 * Spawn shell with -E word
468 	 */
469 	error = posix_spawnattr_setflags(&attr,
470 	    POSIX_SPAWN_NOSIGCHLD_NP | POSIX_SPAWN_WAITPID_NP);
471 	if (error == 0)
472 		error = posix_spawn_file_actions_adddup2(&fact, pv[1], 1);
473 	if (error == 0 && pv[0] != 1)
474 		error = posix_spawn_file_actions_addclose(&fact, pv[0]);
475 	if (error == 0 && pv[1] != 1)
476 		error = posix_spawn_file_actions_addclose(&fact, pv[1]);
477 	if (error == 0 && !(flags & WRDE_SHOWERR))
478 		error = posix_spawn_file_actions_addopen(&fact, 2,
479 		    "/dev/null", O_WRONLY, 0);
480 	path = __xpg4 ? xpg4_path : sun_path;
481 	argv[0] = strrchr(path, '/') + 1;
482 	argv[1] = options;
483 	argv[2] = (char *)word;
484 	argv[3] = NULL;
485 	if (error == 0)
486 		error = posix_spawn(&pid, path, &fact, &attr,
487 		    (char *const *)argv, (char *const *)envp);
488 	(void) posix_spawnattr_destroy(&attr);
489 	(void) posix_spawn_file_actions_destroy(&fact);
490 	(void) close(pv[1]);
491 	if (error) {
492 		(void) close(pv[0]);
493 		errno = error;
494 		goto cleanup;
495 	}
496 
497 	if ((fp = fdopen(pv[0], "rF")) == NULL) {
498 		error = errno;
499 		(void) close(pv[0]);
500 		errno = error;
501 		goto wait_cleanup;
502 	}
503 
504 	/*
505 	 * Read words from shell, separated with '\0'.
506 	 * Since there is no way to disable IFS splitting,
507 	 * it would be possible to separate the output with '\n'.
508 	 */
509 	cp = line = malloc(BUFSZ);
510 	if (line == NULL) {
511 		error = errno;
512 		(void) fclose(fp);
513 		errno = error;
514 		goto wait_cleanup;
515 	}
516 	eob = line + BUFSZ;
517 
518 	rv = 0;
519 	while ((i = getc(fp)) != EOF) {
520 		*cp++ = (char)i;
521 		if (i == '\0') {
522 			cp = line;
523 			if ((rv = append(&wptmp, cp)) != 0) {
524 				break;
525 			}
526 		}
527 		if (cp == eob) {
528 			size_t bs = (eob - line);
529 			char *nl;
530 
531 			if ((nl = realloc(line, bs + BUFSZ)) == NULL) {
532 				rv = WRDE_NOSPACE;
533 				break;
534 			}
535 			line = nl;
536 			cp = line + bs;
537 			eob = cp + BUFSZ;
538 		}
539 	}
540 
541 	wptmp.we_wordp[wptmp.we_wordc] = NULL;
542 
543 	free(line);
544 	(void) fclose(fp);	/* kill shell if still writing */
545 
546 wait_cleanup:
547 	while (waitpid(pid, &status, 0) == -1) {
548 		if (errno != EINTR) {
549 			if (rv == 0)
550 				rv = WRDE_ERRNO;
551 			break;
552 		}
553 	}
554 	if (rv == 0)
555 		rv = WEXITSTATUS(status); /* shell WRDE_* status */
556 
557 cleanup:
558 	if (rv == 0)
559 		*wp = wptmp;
560 	else if (tmpalloc)
561 		wordfree(&wptmp);
562 
563 	if (env)
564 		free(env);
565 	if (wd)
566 		free(wd);
567 	/*
568 	 * Map ksh errors to wordexp() errors
569 	 */
570 	if (rv == 4)
571 		rv = WRDE_CMDSUB;
572 	else if (rv == 5)
573 		rv = WRDE_BADVAL;
574 	else if (rv == 6)
575 		rv = WRDE_SYNTAX;
576 	return (rv);
577 }
578 
579 #endif /* WORDEXP_KSH93 */
580 
581 /*
582  * Append a word to the wordexp_t structure, growing it as necessary.
583  */
584 static int
585 append(wordexp_t *wp, char *str)
586 {
587 	char *cp;
588 	char **nwp;
589 
590 	/*
591 	 * We will be adding one entry and later adding
592 	 * one more NULL. So we need 2 more free slots.
593 	 */
594 	if ((wp->we_wordp + wp->we_wordc) ==
595 	    (wp->we_wordv + wp->we_wordn - 1)) {
596 		nwp = realloc(wp->we_wordv,
597 		    (wp->we_wordn + INITIAL) * sizeof (char *));
598 		if (nwp == NULL)
599 			return (WRDE_NOSPACE);
600 		wp->we_wordn += INITIAL;
601 		wp->we_wordv = nwp;
602 		wp->we_wordp = wp->we_wordv + wp->we_offs;
603 	}
604 	if ((cp = strdup(str)) == NULL)
605 		return (WRDE_NOSPACE);
606 	wp->we_wordp[wp->we_wordc++] = cp;
607 	return (0);
608 }
609 
610 /*
611  * Free all space owned by wordexp_t.
612  */
613 void
614 wordfree(wordexp_t *wp)
615 {
616 	size_t i;
617 
618 	if (wp->we_wordv == NULL)
619 		return;
620 	for (i = wp->we_offs; i < wp->we_offs + wp->we_wordc; i++)
621 		free(wp->we_wordv[i]);
622 	free((void *)wp->we_wordv);
623 	wp->we_wordc = 0;
624 	wp->we_wordv = NULL;
625 }
626