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