xref: /illumos-gate/usr/src/lib/libc/port/regex/wordexp.c (revision cb6207858a9fcc2feaee22e626912fba281ac969)
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 2006 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  *
45  */
46 
47 #pragma	weak wordexp = _wordexp
48 #pragma	weak wordfree = _wordfree
49 
50 #include "synonyms.h"
51 #include <stdio.h>
52 #include <unistd.h>
53 #include <limits.h>
54 #include <fcntl.h>
55 #include <limits.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <sys/wait.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 static int	append(wordexp_t *, char *);
69 
70 extern	int __xpg4;	/* defined in _xpg4.c; 0 if not xpg4-compiled program */
71 
72 /*
73  * Needs no locking if fetched only once.
74  * See getenv()/putenv()/setenv().
75  */
76 extern	const char **environ;
77 
78 /*
79  * Do word expansion.
80  * We just pass our arguments to shell with -E option.  Note that the
81  * underlying shell must recognize the -E option, and do the right thing
82  * with it.
83  */
84 int
85 wordexp(const char *word, wordexp_t *wp, int flags)
86 {
87 	char options[9];
88 	char *optendp = options;
89 	char *argv[4];
90 	const char *path;
91 	wordexp_t wptmp;
92 	size_t si;
93 	int i;
94 	pid_t pid;
95 	char *line, *eob, *cp;		/* word from shell */
96 	int rv = WRDE_ERRNO;
97 	int status;
98 	int pv[2];			/* pipe from shell stdout */
99 	FILE *fp;			/* pipe read stream */
100 	int tmpalloc;
101 	char *wd = NULL;
102 	const char **env = NULL;
103 	const char **envp;
104 	const char *ev;
105 	int n;
106 	posix_spawnattr_t attr;
107 	posix_spawn_file_actions_t fact;
108 	int error;
109 
110 	static const char *sun_path = "/bin/ksh";
111 	static const char *xpg4_path = "/usr/xpg4/bin/sh";
112 
113 	/*
114 	 * Do absolute minimum neccessary for the REUSE flag. Eventually
115 	 * want to be able to actually avoid excessive malloc calls.
116 	 */
117 	if (flags & WRDE_REUSE)
118 		wordfree(wp);
119 
120 	/*
121 	 * Initialize wordexp_t
122 	 *
123 	 * XPG requires that the struct pointed to by wp not be modified
124 	 * unless wordexp() either succeeds, or fails on WRDE_NOSPACE.
125 	 * So we work with wptmp, and only copy wptmp to wp if one of the
126 	 * previously mentioned conditions is satisfied.
127 	 */
128 	wptmp = *wp;
129 
130 	/*
131 	 * Man page says:
132 	 * 2. All of the calls must set WRDE_DOOFFS, or all must not
133 	 *    set it.
134 	 * Therefore, if it's not set, we_offs will always be reset.
135 	 */
136 	if ((flags & WRDE_DOOFFS) == 0)
137 		wptmp.we_offs = 0;
138 
139 	/*
140 	 * If we get APPEND|REUSE, how should we do?
141 	 * allocating buffer anyway to avoid segfault.
142 	 */
143 	tmpalloc = 0;
144 	if ((flags & WRDE_APPEND) == 0 || (flags & WRDE_REUSE)) {
145 		wptmp.we_wordc = 0;
146 		wptmp.we_wordn = wptmp.we_offs + INITIAL;
147 		wptmp.we_wordv = malloc(sizeof (char *) * wptmp.we_wordn);
148 		if (wptmp.we_wordv == NULL)
149 			return (WRDE_NOSPACE);
150 		wptmp.we_wordp = wptmp.we_wordv + wptmp.we_offs;
151 		for (si = 0; si < wptmp.we_offs; si++)
152 			wptmp.we_wordv[si] = NULL;
153 		tmpalloc = 1;
154 	}
155 
156 	/*
157 	 * Turn flags into shell options
158 	 */
159 	*optendp++ = '-';
160 	*optendp++ = (char)0x05;		/* ksh -^E */
161 	if (flags & WRDE_UNDEF)
162 		*optendp++ = 'u';
163 	if (flags & WRDE_NOCMD)
164 		*optendp++ = 'N';
165 	*optendp = '\0';
166 
167 	/*
168 	 * Make sure PWD is in the environment.
169 	 */
170 	if ((envp = environ) == NULL) {		/* can't happen? */
171 		ev = NULL;
172 		n = 0;
173 	} else {
174 		for (n = 0; (ev = envp[n]) != NULL; n++) {
175 			if (*ev == 'P' && strncmp(ev, "PWD=", 4) == 0)
176 				break;
177 		}
178 	}
179 	if (ev == NULL) {	/* PWD missing from the environment */
180 		/* allocate a new environment */
181 		if ((env = malloc((n + 2) * sizeof (char *))) == NULL ||
182 		    (wd = malloc(PATH_MAX + 4)) == NULL)
183 			goto cleanup;
184 		for (i = 0; i < n; i++)
185 			env[i] = envp[i];
186 		(void) strcpy(wd, "PWD=");
187 		if (getcwd(&wd[4], PATH_MAX) == NULL)
188 			(void) strcpy(&wd[4], "/");
189 		env[i] = wd;
190 		env[i + 1] = NULL;
191 		envp = env;
192 	}
193 
194 	if ((error = posix_spawnattr_init(&attr)) != 0) {
195 		errno = error;
196 		goto cleanup;
197 	}
198 	if ((error = posix_spawn_file_actions_init(&fact)) != 0) {
199 		(void) posix_spawnattr_destroy(&attr);
200 		errno = error;
201 		goto cleanup;
202 	}
203 
204 	/*
205 	 * Set up pipe from shell stdout to "fp" for us
206 	 */
207 	if (pipe(pv) < 0) {
208 		error = errno;
209 		(void) posix_spawnattr_destroy(&attr);
210 		(void) posix_spawn_file_actions_destroy(&fact);
211 		errno = error;
212 		goto cleanup;
213 	}
214 
215 	/*
216 	 * Spawn shell with -E word
217 	 */
218 	error = posix_spawnattr_setflags(&attr,
219 	    POSIX_SPAWN_NOSIGCHLD_NP | POSIX_SPAWN_WAITPID_NP);
220 	if (error == 0)
221 		error = posix_spawn_file_actions_adddup2(&fact, pv[1], 1);
222 	if (error == 0 && pv[0] != 1)
223 		error = posix_spawn_file_actions_addclose(&fact, pv[0]);
224 	if (error == 0 && pv[1] != 1)
225 		error = posix_spawn_file_actions_addclose(&fact, pv[1]);
226 	if (error == 0 && !(flags & WRDE_SHOWERR))
227 		error = posix_spawn_file_actions_addopen(&fact, 2,
228 		    "/dev/null", O_WRONLY, 0);
229 	path = __xpg4 ? xpg4_path : sun_path;
230 	argv[0] = strrchr(path, '/') + 1;
231 	argv[1] = options;
232 	argv[2] = (char *)word;
233 	argv[3] = NULL;
234 	if (error == 0)
235 		error = posix_spawn(&pid, path, &fact, &attr,
236 		    (char *const *)argv, (char *const *)envp);
237 	(void) posix_spawnattr_destroy(&attr);
238 	(void) posix_spawn_file_actions_destroy(&fact);
239 	(void) close(pv[1]);
240 	if (error) {
241 		(void) close(pv[0]);
242 		errno = error;
243 		goto cleanup;
244 	}
245 
246 	if ((fp = fdopen(pv[0], "rF")) == NULL) {
247 		error = errno;
248 		(void) close(pv[0]);
249 		errno = error;
250 		goto wait_cleanup;
251 	}
252 
253 	/*
254 	 * Read words from shell, separated with '\0'.
255 	 * Since there is no way to disable IFS splitting,
256 	 * it would be possible to separate the output with '\n'.
257 	 */
258 	cp = line = malloc(BUFSZ);
259 	if (line == NULL) {
260 		error = errno;
261 		(void) fclose(fp);
262 		errno = error;
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 	while (waitpid(pid, &status, 0) == -1) {
297 		if (errno != EINTR) {
298 			if (rv == 0)
299 				rv = WRDE_ERRNO;
300 			break;
301 		}
302 	}
303 	if (rv == 0)
304 		rv = WEXITSTATUS(status); /* shell WRDE_* status */
305 
306 cleanup:
307 	if (rv == 0)
308 		*wp = wptmp;
309 	else if (tmpalloc)
310 		wordfree(&wptmp);
311 
312 	if (env)
313 		free(env);
314 	if (wd)
315 		free(wd);
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 	return (rv);
326 }
327 
328 /*
329  * Append a word to the wordexp_t structure, growing it as neccessary.
330  */
331 static int
332 append(wordexp_t *wp, char *str)
333 {
334 	char *cp;
335 	char **nwp;
336 
337 	/*
338 	 * We will be adding one entry and later adding
339 	 * one more NULL. So we need 2 more free slots.
340 	 */
341 	if ((wp->we_wordp + wp->we_wordc) ==
342 		(wp->we_wordv + wp->we_wordn - 1)) {
343 		nwp = realloc(wp->we_wordv,
344 			(wp->we_wordn + INITIAL) * sizeof (char *));
345 		if (nwp == NULL)
346 			return (WRDE_NOSPACE);
347 		wp->we_wordn += INITIAL;
348 		wp->we_wordv = nwp;
349 		wp->we_wordp = wp->we_wordv + wp->we_offs;
350 	}
351 	if ((cp = strdup(str)) == NULL)
352 		return (WRDE_NOSPACE);
353 	wp->we_wordp[wp->we_wordc++] = cp;
354 	return (0);
355 }
356 
357 /*
358  * Free all space owned by wordexp_t.
359  */
360 void
361 wordfree(wordexp_t *wp)
362 {
363 	size_t i;
364 
365 	if (wp->we_wordv == NULL)
366 		return;
367 	for (i = wp->we_offs; i < wp->we_offs + wp->we_wordc; i++)
368 		free(wp->we_wordv[i]);
369 	free((void *)wp->we_wordv);
370 	wp->we_wordc = 0;
371 	wp->we_wordv = NULL;
372 }
373