xref: /illumos-gate/usr/src/lib/libc/port/regex/wordexp.c (revision 9ec394dbf343c1f23c6e13c39df427f238e5a369)
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 /*
28  * This code is MKS code ported to Solaris originally with minimum
29  * modifications so that upgrades from MKS would readily integrate.
30  * The MKS basis for this modification was:
31  *
32  *	$Id: wordexp.c 1.22 1994/11/21 18:24:50 miked
33  *
34  * Additional modifications have been made to this code to make it
35  * 64-bit clean.
36  */
37 
38 /*
39  * wordexp, wordfree -- POSIX.2 D11.2 word expansion routines.
40  *
41  * Copyright 1985, 1992 by Mortice Kern Systems Inc.  All rights reserved.
42  * Modified by Roland Mainz <roland.mainz@nrubsig.org> to support ksh93.
43  *
44  */
45 
46 #pragma ident	"%Z%%M%	%I%	%E% SMI"
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 "lint.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) {
440 		/* can happen when processing a SunOS 4.x AOUT file */
441 		ev = NULL;
442 		n = 0;
443 	} else {
444 		for (n = 0; (ev = envp[n]) != NULL; n++) {
445 			if (*ev == 'P' && strncmp(ev, "PWD=", 4) == 0)
446 				break;
447 		}
448 	}
449 	if (ev == NULL) {	/* PWD missing from the environment */
450 		/* allocate a new environment */
451 		if ((env = malloc((n + 2) * sizeof (char *))) == NULL ||
452 		    (wd = malloc(PATH_MAX + 4)) == NULL)
453 			goto cleanup;
454 		for (i = 0; i < n; i++)
455 			env[i] = envp[i];
456 		(void) strcpy(wd, "PWD=");
457 		if (getcwd(&wd[4], PATH_MAX) == NULL)
458 			(void) strcpy(&wd[4], "/");
459 		env[i] = wd;
460 		env[i + 1] = NULL;
461 		envp = env;
462 	}
463 
464 	if ((error = posix_spawnattr_init(&attr)) != 0) {
465 		errno = error;
466 		goto cleanup;
467 	}
468 	if ((error = posix_spawn_file_actions_init(&fact)) != 0) {
469 		(void) posix_spawnattr_destroy(&attr);
470 		errno = error;
471 		goto cleanup;
472 	}
473 
474 	/*
475 	 * Set up pipe from shell stdout to "fp" for us
476 	 */
477 	if (pipe(pv) < 0) {
478 		error = errno;
479 		(void) posix_spawnattr_destroy(&attr);
480 		(void) posix_spawn_file_actions_destroy(&fact);
481 		errno = error;
482 		goto cleanup;
483 	}
484 
485 	/*
486 	 * Spawn shell with -E word
487 	 */
488 	error = posix_spawnattr_setflags(&attr,
489 	    POSIX_SPAWN_NOSIGCHLD_NP | POSIX_SPAWN_WAITPID_NP);
490 	if (error == 0)
491 		error = posix_spawn_file_actions_adddup2(&fact, pv[1], 1);
492 	if (error == 0 && pv[0] != 1)
493 		error = posix_spawn_file_actions_addclose(&fact, pv[0]);
494 	if (error == 0 && pv[1] != 1)
495 		error = posix_spawn_file_actions_addclose(&fact, pv[1]);
496 	if (error == 0 && !(flags & WRDE_SHOWERR))
497 		error = posix_spawn_file_actions_addopen(&fact, 2,
498 		    "/dev/null", O_WRONLY, 0);
499 	path = __xpg4 ? xpg4_path : sun_path;
500 	argv[0] = strrchr(path, '/') + 1;
501 	argv[1] = options;
502 	argv[2] = (char *)word;
503 	argv[3] = NULL;
504 	if (error == 0)
505 		error = posix_spawn(&pid, path, &fact, &attr,
506 		    (char *const *)argv, (char *const *)envp);
507 	(void) posix_spawnattr_destroy(&attr);
508 	(void) posix_spawn_file_actions_destroy(&fact);
509 	(void) close(pv[1]);
510 	if (error) {
511 		(void) close(pv[0]);
512 		errno = error;
513 		goto cleanup;
514 	}
515 
516 	if ((fp = fdopen(pv[0], "rF")) == NULL) {
517 		error = errno;
518 		(void) close(pv[0]);
519 		errno = error;
520 		goto wait_cleanup;
521 	}
522 
523 	/*
524 	 * Read words from shell, separated with '\0'.
525 	 * Since there is no way to disable IFS splitting,
526 	 * it would be possible to separate the output with '\n'.
527 	 */
528 	cp = line = malloc(BUFSZ);
529 	if (line == NULL) {
530 		error = errno;
531 		(void) fclose(fp);
532 		errno = error;
533 		goto wait_cleanup;
534 	}
535 	eob = line + BUFSZ;
536 
537 	rv = 0;
538 	while ((i = getc(fp)) != EOF) {
539 		*cp++ = (char)i;
540 		if (i == '\0') {
541 			cp = line;
542 			if ((rv = append(&wptmp, cp)) != 0) {
543 				break;
544 			}
545 		}
546 		if (cp == eob) {
547 			size_t bs = (eob - line);
548 			char *nl;
549 
550 			if ((nl = realloc(line, bs + BUFSZ)) == NULL) {
551 				rv = WRDE_NOSPACE;
552 				break;
553 			}
554 			line = nl;
555 			cp = line + bs;
556 			eob = cp + BUFSZ;
557 		}
558 	}
559 
560 	wptmp.we_wordp[wptmp.we_wordc] = NULL;
561 
562 	free(line);
563 	(void) fclose(fp);	/* kill shell if still writing */
564 
565 wait_cleanup:
566 	while (waitpid(pid, &status, 0) == -1) {
567 		if (errno != EINTR) {
568 			if (rv == 0)
569 				rv = WRDE_ERRNO;
570 			break;
571 		}
572 	}
573 	if (rv == 0)
574 		rv = WEXITSTATUS(status); /* shell WRDE_* status */
575 
576 cleanup:
577 	if (rv == 0)
578 		*wp = wptmp;
579 	else if (tmpalloc)
580 		wordfree(&wptmp);
581 
582 	if (env)
583 		free(env);
584 	if (wd)
585 		free(wd);
586 	/*
587 	 * Map ksh errors to wordexp() errors
588 	 */
589 	if (rv == 4)
590 		rv = WRDE_CMDSUB;
591 	else if (rv == 5)
592 		rv = WRDE_BADVAL;
593 	else if (rv == 6)
594 		rv = WRDE_SYNTAX;
595 
596 	(void) pthread_setcancelstate(cancel_state, NULL);
597 	return (rv);
598 }
599 
600 #endif /* WORDEXP_KSH93 */
601 
602 /*
603  * Append a word to the wordexp_t structure, growing it as necessary.
604  */
605 static int
606 append(wordexp_t *wp, char *str)
607 {
608 	char *cp;
609 	char **nwp;
610 
611 	/*
612 	 * We will be adding one entry and later adding
613 	 * one more NULL. So we need 2 more free slots.
614 	 */
615 	if ((wp->we_wordp + wp->we_wordc) ==
616 	    (wp->we_wordv + wp->we_wordn - 1)) {
617 		nwp = realloc(wp->we_wordv,
618 		    (wp->we_wordn + INITIAL) * sizeof (char *));
619 		if (nwp == NULL)
620 			return (WRDE_NOSPACE);
621 		wp->we_wordn += INITIAL;
622 		wp->we_wordv = nwp;
623 		wp->we_wordp = wp->we_wordv + wp->we_offs;
624 	}
625 	if ((cp = strdup(str)) == NULL)
626 		return (WRDE_NOSPACE);
627 	wp->we_wordp[wp->we_wordc++] = cp;
628 	return (0);
629 }
630 
631 /*
632  * Free all space owned by wordexp_t.
633  */
634 void
635 wordfree(wordexp_t *wp)
636 {
637 	size_t i;
638 
639 	if (wp->we_wordv == NULL)
640 		return;
641 	for (i = wp->we_offs; i < wp->we_offs + wp->we_wordc; i++)
642 		free(wp->we_wordv[i]);
643 	free((void *)wp->we_wordv);
644 	wp->we_wordc = 0;
645 	wp->we_wordv = NULL;
646 }
647