improve robustness of wordexp and fix handling of 0-word case
[musl] / src / misc / wordexp.c
1 #include <wordexp.h>
2 #include <unistd.h>
3 #include <stdio.h>
4 #include <string.h>
5 #include <limits.h>
6 #include <stdint.h>
7 #include <stdlib.h>
8 #include <sys/wait.h>
9 #include <signal.h>
10 #include <errno.h>
11 #include "pthread_impl.h"
12
13 static char *getword(FILE *f)
14 {
15         char *s = 0;
16         return getdelim(&s, (size_t [1]){0}, 0, f) < 0 ? 0 : s;
17 }
18
19 static int do_wordexp(const char *s, wordexp_t *we, int flags)
20 {
21         size_t i, l;
22         int sq=0, dq=0;
23         size_t np=0;
24         char *w, **tmp;
25         char *redir = (flags & WRDE_SHOWERR) ? "" : "2>/dev/null";
26         int err = 0, status;
27         FILE *f;
28         size_t wc = 0;
29         char **wv = 0;
30         int p[2];
31         pid_t pid;
32         sigset_t set;
33
34         if (flags & WRDE_REUSE) wordfree(we);
35
36         if (flags & WRDE_NOCMD) for (i=0; s[i]; i++) switch (s[i]) {
37         case '\\':
38                 if (!sq) i++;
39                 break;
40         case '\'':
41                 if (!dq) sq^=1;
42                 break;
43         case '"':
44                 if (!sq) dq^=1;
45                 break;
46         case '(':
47                 if (np) {
48                         np++;
49                         break;
50                 }
51         case ')':
52                 if (np) {
53                         np--;
54                         break;
55                 }
56         case '\n':
57         case '|':
58         case '&':
59         case ';':
60         case '<':
61         case '>':
62         case '{':
63         case '}':
64                 if (!(sq|dq|np)) return WRDE_BADCHAR;
65                 break;
66         case '$':
67                 if (s[i+1]=='(' && s[i+2]=='(') {
68                         i += 2;
69                         np += 2;
70                         break;
71                 } else if (s[i+1] != '(') break;
72         case '`':
73                 if (sq) break;
74                 return WRDE_CMDSUB;
75         }
76
77         if (flags & WRDE_APPEND) {
78                 wc = we->we_wordc;
79                 wv = we->we_wordv;
80         }
81
82         i = wc;
83         if (flags & WRDE_DOOFFS) {
84                 if (we->we_offs > SIZE_MAX/sizeof(void *)/4)
85                         return WRDE_NOSPACE;
86                 i += we->we_offs;
87         } else {
88                 we->we_offs = 0;
89         }
90
91         if (pipe(p) < 0) return WRDE_NOSPACE;
92         __block_all_sigs(&set);
93         pid = fork();
94         __restore_sigs(&set);
95         if (pid < 0) {
96                 close(p[0]);
97                 close(p[1]);
98                 return WRDE_NOSPACE;
99         }
100         if (!pid) {
101                 dup2(p[1], 1);
102                 close(p[0]);
103                 close(p[1]);
104                 execl("/bin/sh", "sh", "-c",
105                         "eval \"printf %s\\\\\\\\0 x $1 $2\"",
106                         "sh", s, redir, (char *)0);
107                 _exit(1);
108         }
109         close(p[1]);
110         
111         f = fdopen(p[0], "r");
112         if (!f) {
113                 close(p[0]);
114                 kill(pid, SIGKILL);
115                 waitpid(pid, &status, 0);
116                 return WRDE_NOSPACE;
117         }
118
119         l = wv ? i+1 : 0;
120
121         free(getword(f));
122         if (feof(f)) {
123                 fclose(f);
124                 while ((waitpid(pid, &status, 0) < 0 && errno == EINTR)
125                         || !WIFEXITED(status));
126                 return WRDE_SYNTAX;
127         }
128
129         while ((w = getword(f))) {
130                 if (i+1 >= l) {
131                         l += l/2+10;
132                         tmp = realloc(wv, l*sizeof(char *));
133                         if (!tmp) break;
134                         wv = tmp;
135                 }
136                 wv[i++] = w;
137                 wv[i] = 0;
138         }
139         if (!feof(f)) err = WRDE_NOSPACE;
140
141         fclose(f);
142         while ((waitpid(pid, &status, 0) < 0 && errno == EINTR)
143                 || !WIFEXITED(status));
144
145         we->we_wordv = wv;
146         we->we_wordc = i;
147
148         for (i=we->we_offs; i; i--)
149                 we->we_wordv[i-1] = 0;
150
151         if (flags & WRDE_DOOFFS) we->we_wordc -= we->we_offs;
152         return err;
153 }
154
155 int wordexp(const char *restrict s, wordexp_t *restrict we, int flags)
156 {
157         int r, cs;
158         pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
159         r = do_wordexp(s, we, flags);
160         pthread_setcancelstate(cs, 0);
161         return r;
162 }
163
164 void wordfree(wordexp_t *we)
165 {
166         size_t i;
167         if (!we->we_wordv) return;
168         for (i=0; i<we->we_wordc; i++) free(we->we_wordv[we->we_offs+i]);
169         free(we->we_wordv);
170         we->we_wordv = 0;
171         we->we_wordc = 0;
172 }