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