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 *
mystpcpy(char * s1,const char * s2)86 mystpcpy(char *s1, const char *s2)
87 {
88 while (*s1++ = *s2++)
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
wordexp(const char * word,wordexp_t * wp,int flags)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
append(wordexp_t * wp,char * str)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
wordfree(wordexp_t * wp)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