xref: /illumos-gate/usr/src/lib/libc/port/regex/wordexp.c (revision a4955f4fa65e38d70c07d38e657a9aff43fa155f)
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 2009 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 #pragma	weak _wordexp = wordexp
46 #pragma	weak _wordfree = wordfree
47 
48 #include "lint.h"
49 #include <stdio.h>
50 #include <unistd.h>
51 #include <limits.h>
52 #include <fcntl.h>
53 #include <limits.h>
54 #include <stdlib.h>
55 #include <alloca.h>
56 #include <string.h>
57 #include <sys/wait.h>
58 #include <pthread.h>
59 #include <unistd.h>
60 #include <wordexp.h>
61 #include <stdio.h>
62 #include <spawn.h>
63 #include <errno.h>
64 
65 #define	INITIAL	8		/* initial pathv allocation */
66 #define	BUFSZ	256		/* allocation unit of the line buffer */
67 
68 /*
69  * Needs no locking if fetched only once.
70  * See getenv()/putenv()/setenv().
71  */
72 extern	const char **_environ;
73 
74 /* Local prototypes */
75 static int append(wordexp_t *, char *);
76 
77 /*
78  * |mystpcpy| - like |strcpy()| but returns the end of the buffer
79  * We'll add this later (and a matching multibyte/widechar version)
80  * as normal libc function.
81  *
82  * Copy string s2 to s1.  s1 must be large enough.
83  * return s1-1 (position of string terminator ('\0') in destination buffer).
84  */
85 static char *
86 mystpcpy(char *s1, const char *s2)
87 {
88 	while ((*s1++ = *s2++) != '\0')
89 		;
90 	return (s1-1);
91 }
92 
93 /*
94  * Do word expansion.
95  * We build a mini-script in |buff| which takes care of all details,
96  * including stdin/stdout/stderr redirection, WRDE_NOCMD mode and
97  * the word expansion itself.
98  */
99 int
100 wordexp(const char *word, wordexp_t *wp, int flags)
101 {
102 	const char *path = "/usr/bin/ksh93";
103 	wordexp_t wptmp;
104 	size_t si;
105 	pid_t pid;
106 	char *line, *eob, *cp;		/* word from shell */
107 	int rv = WRDE_ERRNO;
108 	int status;
109 	int pv[2];			/* pipe from shell stdout */
110 	FILE *fp;			/* pipe read stream */
111 	int tmpalloc;
112 	char *wd = NULL;
113 	const char **env = NULL;
114 	const char **envp;
115 	const char *ev;
116 	int n;
117 	posix_spawnattr_t attr;
118 	posix_spawn_file_actions_t fact;
119 	int error;
120 	int cancel_state;
121 	size_t bufflen; /* Length of |buff| */
122 	char *buff;
123 	char *currbuffp; /* Current position of '\0' in |buff| */
124 	char *args[10];
125 	int i;
126 
127 	/*
128 	 * Do absolute minimum necessary for the REUSE flag. Eventually
129 	 * want to be able to actually avoid excessive malloc calls.
130 	 */
131 	if (flags & WRDE_REUSE)
132 		wordfree(wp);
133 
134 	/*
135 	 * Initialize wordexp_t
136 	 *
137 	 * XPG requires that the struct pointed to by wp not be modified
138 	 * unless wordexp() either succeeds, or fails on WRDE_NOSPACE.
139 	 * So we work with wptmp, and only copy wptmp to wp if one of the
140 	 * previously mentioned conditions is satisfied.
141 	 */
142 	wptmp = *wp;
143 
144 	/*
145 	 * Man page says:
146 	 * 2. All of the calls must set WRDE_DOOFFS, or all must not
147 	 *    set it.
148 	 * Therefore, if it's not set, we_offs will always be reset.
149 	 */
150 	if ((flags & WRDE_DOOFFS) == 0)
151 		wptmp.we_offs = 0;
152 
153 	/*
154 	 * If we get APPEND|REUSE, how should we do?
155 	 * allocating buffer anyway to avoid segfault.
156 	 */
157 	tmpalloc = 0;
158 	if ((flags & WRDE_APPEND) == 0 || (flags & WRDE_REUSE)) {
159 		wptmp.we_wordc = 0;
160 		wptmp.we_wordn = wptmp.we_offs + INITIAL;
161 		wptmp.we_wordv = malloc(sizeof (char *) * wptmp.we_wordn);
162 		if (wptmp.we_wordv == NULL)
163 			return (WRDE_NOSPACE);
164 		wptmp.we_wordp = wptmp.we_wordv + wptmp.we_offs;
165 		for (si = 0; si < wptmp.we_offs; si++)
166 			wptmp.we_wordv[si] = NULL;
167 		tmpalloc = 1;
168 	}
169 
170 	/*
171 	 * The UNIX98 Posix conformance test suite requires
172 	 * |wordexp()| to not be a cancellation point.
173 	 */
174 	(void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state);
175 
176 	/*
177 	 * Make sure PWD is in the environment.
178 	 */
179 	if ((envp = _environ) == NULL) {
180 		/* can happen when processing a SunOS 4.x AOUT file */
181 		ev = NULL;
182 		n = 0;
183 	} else {
184 		for (n = 0; (ev = envp[n]) != NULL; n++) {
185 			if (*ev == 'P' && strncmp(ev, "PWD=", 4) == 0)
186 				break;
187 		}
188 	}
189 	if (ev == NULL) {	/* PWD missing from the environment */
190 		/* allocate a new environment */
191 		if ((env = malloc((n + 2) * sizeof (char *))) == NULL ||
192 		    (wd = malloc(PATH_MAX + 4)) == NULL)
193 			goto cleanup;
194 		for (i = 0; i < n; i++)
195 			env[i] = envp[i];
196 		(void) strcpy(wd, "PWD=");
197 		if (getcwd(&wd[4], PATH_MAX) == NULL)
198 			(void) strcpy(&wd[4], "/");
199 		env[i] = wd;
200 		env[i + 1] = NULL;
201 		envp = env;
202 	}
203 
204 	/*
205 	 * Calculate size of required buffer (which is size of the
206 	 * input string (|word|) plus all string literals below;
207 	 * this value MUST be adjusted each time the literals are
208 	 * changed!!).
209 	 */
210 	bufflen = 165 + strlen(word);
211 	buff = alloca(bufflen);
212 	i = 0;
213 
214 	/* Start filling the buffer */
215 	buff[0] = '\0';
216 	currbuffp = buff;
217 
218 	if (flags & WRDE_UNDEF)
219 		currbuffp = mystpcpy(currbuffp, "set -o nounset\n");
220 	if ((flags & WRDE_SHOWERR) == 0) {
221 		/*
222 		 * The newline ('\n') is neccesary to make sure that
223 		 * the redirection to /dev/null is already active in
224 		 * the case the printf below contains a syntax
225 		 * error...
226 		 */
227 		currbuffp = mystpcpy(currbuffp, "exec 2>/dev/null\n");
228 	}
229 	/* Squish stdin */
230 	currbuffp = mystpcpy(currbuffp, "exec 0</dev/null\n");
231 
232 	if (flags & WRDE_NOCMD) {
233 		/*
234 		 * Switch to restricted shell (rksh) mode here to
235 		 * put the word expansion into a "cage" which
236 		 * prevents users from executing external commands
237 		 * (outside those listed by ${PATH} (which we set
238 		 * explicitly to /usr/no/such/path/element/)).
239 		 */
240 		currbuffp = mystpcpy(currbuffp,
241 		    "export PATH=/usr/no/such/path/element/ ; "
242 		    "set -o restricted\n");
243 	}
244 
245 	(void) snprintf(currbuffp, bufflen,
246 	    "print -f '%%s\\000' -- %s", word);
247 
248 	args[i++] = strrchr(path, '/') + 1;
249 	args[i++] = "-c";
250 	args[i++] = buff;
251 	args[i++] = NULL;
252 
253 	if ((error = posix_spawnattr_init(&attr)) != 0) {
254 		errno = error;
255 		goto cleanup;
256 	}
257 	if ((error = posix_spawn_file_actions_init(&fact)) != 0) {
258 		(void) posix_spawnattr_destroy(&attr);
259 		errno = error;
260 		goto cleanup;
261 	}
262 
263 	/*
264 	 * Set up pipe from shell stdout to "fp" for us
265 	 */
266 	if (pipe(pv) < 0) {
267 		error = errno;
268 		(void) posix_spawnattr_destroy(&attr);
269 		(void) posix_spawn_file_actions_destroy(&fact);
270 		errno = error;
271 		goto cleanup;
272 	}
273 
274 	/*
275 	 * Spawn shell
276 	 */
277 	error = posix_spawnattr_setflags(&attr,
278 	    POSIX_SPAWN_NOSIGCHLD_NP | POSIX_SPAWN_WAITPID_NP);
279 	if (error == 0)
280 		error = posix_spawn_file_actions_adddup2(&fact, pv[1], 1);
281 	if (error == 0 && pv[0] != 1)
282 		error = posix_spawn_file_actions_addclose(&fact, pv[0]);
283 	if (error == 0 && pv[1] != 1)
284 		error = posix_spawn_file_actions_addclose(&fact, pv[1]);
285 	if (error == 0 && !(flags & WRDE_SHOWERR))
286 		error = posix_spawn_file_actions_addopen(&fact, 2,
287 		    "/dev/null", O_WRONLY, 0);
288 
289 	if (error == 0)
290 		error = posix_spawn(&pid, path, &fact, &attr,
291 		    (char *const *)args, (char *const *)envp);
292 	(void) posix_spawnattr_destroy(&attr);
293 	(void) posix_spawn_file_actions_destroy(&fact);
294 	(void) close(pv[1]);
295 	if (error) {
296 		(void) close(pv[0]);
297 		errno = error;
298 		goto cleanup;
299 	}
300 
301 	if ((fp = fdopen(pv[0], "rF")) == NULL) {
302 		error = errno;
303 		(void) close(pv[0]);
304 		errno = error;
305 		goto wait_cleanup;
306 	}
307 
308 	/*
309 	 * Read words from shell, separated with '\0'.
310 	 * Since there is no way to disable IFS splitting,
311 	 * it would be possible to separate the output with '\n'.
312 	 */
313 	cp = line = malloc(BUFSZ);
314 	if (line == NULL) {
315 		error = errno;
316 		(void) fclose(fp);
317 		errno = error;
318 		goto wait_cleanup;
319 	}
320 	eob = line + BUFSZ;
321 
322 	rv = 0;
323 	flockfile(fp);
324 	while ((i = getc_unlocked(fp)) != EOF) {
325 		*cp++ = (char)i;
326 		if (i == '\0') {
327 			cp = line;
328 			if ((rv = append(&wptmp, cp)) != 0) {
329 				break;
330 			}
331 		}
332 		if (cp == eob) {
333 			size_t bs = (eob - line);
334 			char *nl;
335 
336 			if ((nl = realloc(line, bs + BUFSZ)) == NULL) {
337 				rv = WRDE_NOSPACE;
338 				break;
339 			}
340 			line = nl;
341 			cp = line + bs;
342 			eob = cp + BUFSZ;
343 		}
344 	}
345 	funlockfile(fp);
346 
347 	wptmp.we_wordp[wptmp.we_wordc] = NULL;
348 
349 	free(line);
350 	(void) fclose(fp);	/* kill shell if still writing */
351 
352 wait_cleanup:
353 	while (waitpid(pid, &status, 0) == -1) {
354 		if (errno != EINTR) {
355 			if (rv == 0)
356 				rv = WRDE_ERRNO;
357 			break;
358 		}
359 	}
360 	if (rv == 0)
361 		rv = WEXITSTATUS(status); /* shell WRDE_* status */
362 
363 cleanup:
364 	if (rv == 0)
365 		*wp = wptmp;
366 	else if (tmpalloc)
367 		wordfree(&wptmp);
368 
369 	if (env)
370 		free(env);
371 	if (wd)
372 		free(wd);
373 	/*
374 	 * Map ksh93 errors to |wordexp()| errors
375 	 */
376 	switch (rv) {
377 		case 1:
378 			rv = WRDE_BADVAL;
379 			break;
380 		case 127:
381 			rv = WRDE_BADCHAR;
382 			break;
383 	}
384 
385 	(void) pthread_setcancelstate(cancel_state, NULL);
386 	return (rv);
387 }
388 
389 /*
390  * Append a word to the wordexp_t structure, growing it as necessary.
391  */
392 static int
393 append(wordexp_t *wp, char *str)
394 {
395 	char *cp;
396 	char **nwp;
397 
398 	/*
399 	 * We will be adding one entry and later adding
400 	 * one more NULL. So we need 2 more free slots.
401 	 */
402 	if ((wp->we_wordp + wp->we_wordc) ==
403 	    (wp->we_wordv + wp->we_wordn - 1)) {
404 		nwp = realloc(wp->we_wordv,
405 		    (wp->we_wordn + INITIAL) * sizeof (char *));
406 		if (nwp == NULL)
407 			return (WRDE_NOSPACE);
408 		wp->we_wordn += INITIAL;
409 		wp->we_wordv = nwp;
410 		wp->we_wordp = wp->we_wordv + wp->we_offs;
411 	}
412 	if ((cp = strdup(str)) == NULL)
413 		return (WRDE_NOSPACE);
414 	wp->we_wordp[wp->we_wordc++] = cp;
415 	return (0);
416 }
417 
418 /*
419  * Free all space owned by wordexp_t.
420  */
421 void
422 wordfree(wordexp_t *wp)
423 {
424 	size_t i;
425 
426 	if (wp->we_wordv == NULL)
427 		return;
428 	for (i = wp->we_offs; i < wp->we_offs + wp->we_wordc; i++)
429 		free(wp->we_wordv[i]);
430 	free((void *)wp->we_wordv);
431 	wp->we_wordc = 0;
432 	wp->we_wordv = NULL;
433 }
434