Skip to main content

c/
wordexp.rs

1// A real wordexp(3): quoting, parameter expansion, command substitution and
2// pathname expansion.
3
4#[repr(C)]
5pub struct WordexpT {
6    pub we_wordc: usize,
7    pub we_wordv: *mut *mut c_char,
8    pub we_offs: usize,
9}
10
11const WRDE_APPEND_V: c_int = 1;
12const WRDE_DOOFFS_V: c_int = 2;
13const WRDE_NOCMD_V: c_int = 4;
14const WRDE_REUSE_V: c_int = 8;
15const WRDE_UNDEF_V: c_int = 32;
16
17const WRDE_NOSPACE_V: c_int = 1;
18const WRDE_BADCHAR_V: c_int = 2;
19const WRDE_BADVAL_V: c_int = 3;
20const WRDE_CMDSUB_V: c_int = 4;
21const WRDE_SYNTAX_V: c_int = 5;
22
23const WORDEXP_CAP: usize = 4096;
24
25struct WordList {
26    items: *mut *mut c_char,
27    n: usize,
28    cap: usize,
29}
30
31unsafe fn wl_push(list: *mut WordList, s: *const u8, len: usize) {
32    if (*list).n == (*list).cap {
33        let ncap = if (*list).cap == 0 { 16 } else { (*list).cap * 2 };
34        let na = crate::realloc(
35            (*list).items as *mut c_void,
36            ncap * core::mem::size_of::<*mut c_char>(),
37        ) as *mut *mut c_char;
38        if na.is_null() { return; }
39        (*list).items = na;
40        (*list).cap = ncap;
41    }
42    let p = crate::malloc(len + 1) as *mut c_char;
43    if p.is_null() { return; }
44    core::ptr::copy_nonoverlapping(s, p as *mut u8, len);
45    *p.add(len) = 0;
46    *(*list).items.add((*list).n) = p;
47    (*list).n += 1;
48}
49
50unsafe fn wl_free(list: *mut WordList) {
51    for i in 0..(*list).n {
52        crate::free(*(*list).items.add(i) as *mut c_void);
53    }
54    crate::free((*list).items as *mut c_void);
55    (*list).items = core::ptr::null_mut();
56    (*list).n = 0;
57    (*list).cap = 0;
58}
59
60struct WordBuf {
61    buf: [u8; WORDEXP_CAP],
62    len: usize,
63    has_glob: bool,
64    quoted: bool,
65}
66
67unsafe fn wb_push(w: *mut WordBuf, c: u8, quoted: bool) -> c_int {
68    if (*w).len + 1 >= WORDEXP_CAP { return WRDE_NOSPACE_V; }
69    (*w).buf[(*w).len] = c;
70    (*w).len += 1;
71    if quoted {
72        (*w).quoted = true;
73    } else if c == b'*' || c == b'?' || c == b'[' {
74        (*w).has_glob = true;
75    }
76    0
77}
78
79unsafe fn wb_push_str(w: *mut WordBuf, s: *const u8, len: usize, quoted: bool) -> c_int {
80    if (*w).len + len >= WORDEXP_CAP { return WRDE_NOSPACE_V; }
81    core::ptr::copy_nonoverlapping(s, (*w).buf.as_mut_ptr().add((*w).len), len);
82    (*w).len += len;
83    if !quoted {
84        for i in 0..len {
85            let c = *s.add(i);
86            if c == b'*' || c == b'?' || c == b'[' { (*w).has_glob = true; break; }
87        }
88    }
89    (*w).quoted = true;
90    0
91}
92
93unsafe fn env_value(name: *const u8, len: usize) -> *mut c_char {
94    let mut buf = [0 as c_char; 256];
95    if len >= buf.len() { return core::ptr::null_mut(); }
96    for i in 0..len { buf[i] = *name.add(i) as c_char; }
97    buf[len] = 0;
98    crate::getenv(buf.as_ptr())
99}
100
101/// Expand `$name` or `${name}`; returns the new index or an error code.
102unsafe fn expand_var(
103    s: &[u8],
104    i_start: usize,
105    w: *mut WordBuf,
106    flags: c_int,
107) -> Result<usize, c_int> {
108    let mut i = i_start + 1;
109    let mut name = [0u8; 256];
110    let mut nl = 0usize;
111    if s[i] == b'{' {
112        i += 1;
113        while s[i] != 0 && s[i] != b'}' {
114            if nl + 1 >= name.len() { return Err(WRDE_NOSPACE_V); }
115            name[nl] = s[i];
116            nl += 1;
117            i += 1;
118        }
119        if s[i] != b'}' { return Err(WRDE_SYNTAX_V); }
120        i += 1;
121    } else {
122        while nl + 1 < name.len() {
123            let c = s[i];
124            if (c >= b'A' && c <= b'Z') || (c >= b'a' && c <= b'z') || (c >= b'0' && c <= b'9') || c == b'_' {
125                name[nl] = c;
126                nl += 1;
127                i += 1;
128            } else {
129                break;
130            }
131        }
132        if nl == 0 {
133            // A lone '$' is literal.
134            let r = wb_push(w, b'$', false);
135            if r != 0 { return Err(r); }
136            return Ok(i_start + 1);
137        }
138    }
139    if nl == 1 && name[0] == b'$' {
140        let mut pidbuf = [0 as c_char; 16];
141        let pid = crate::getpid();
142        let mut n = 0usize;
143        let mut v = pid as u32;
144        let mut digits = [0u8; 10];
145        if v == 0 { digits[0] = b'0'; n = 1; }
146        while v > 0 { digits[n] = b'0' + (v % 10) as u8; v /= 10; n += 1; }
147        let mut o = 0usize;
148        while n > 0 { n -= 1; pidbuf[o] = digits[n] as c_char; o += 1; }
149        pidbuf[o] = 0;
150        let r = wb_push_str(w, pidbuf.as_ptr() as *const u8, o, false);
151        if r != 0 { return Err(r); }
152        return Ok(i);
153    }
154    let val = env_value(name.as_ptr(), nl);
155    if val.is_null() {
156        if flags & WRDE_UNDEF_V != 0 { return Err(WRDE_BADVAL_V); }
157        return Ok(i);
158    }
159    let vl = crate::strlen(val);
160    let r = wb_push_str(w, val as *const u8, vl, false);
161    if r != 0 { return Err(r); }
162    Ok(i)
163}
164
165unsafe fn command_sub(
166    s: &[u8],
167    i_start: usize,
168    w: *mut WordBuf,
169    flags: c_int,
170) -> Result<usize, c_int> {
171    if flags & WRDE_NOCMD_V != 0 { return Err(WRDE_CMDSUB_V); }
172    let mut i = i_start;
173    let mut cmd = [0u8; 2048];
174    let mut cl = 0usize;
175    if s[i] == b'`' {
176        i += 1;
177        while s[i] != 0 && s[i] != b'`' {
178            if cl + 1 >= cmd.len() { return Err(WRDE_NOSPACE_V); }
179            cmd[cl] = s[i];
180            cl += 1;
181            i += 1;
182        }
183        if s[i] != b'`' { return Err(WRDE_SYNTAX_V); }
184        i += 1;
185    } else {
186        // $( ... ) with nesting.
187        i += 2;
188        let mut depth = 1;
189        while s[i] != 0 {
190            let c = s[i];
191            if c == b'\\' && s[i + 1] != 0 {
192                if cl + 1 >= cmd.len() { return Err(WRDE_NOSPACE_V); }
193                cmd[cl] = c;
194                cl += 1;
195                i += 1;
196                if cl + 1 >= cmd.len() { return Err(WRDE_NOSPACE_V); }
197                cmd[cl] = s[i];
198                cl += 1;
199                i += 1;
200                continue;
201            }
202            if c == b'\'' {
203                // Copy the quoted section verbatim.
204                if cl + 1 >= cmd.len() { return Err(WRDE_NOSPACE_V); }
205                cmd[cl] = c;
206                cl += 1;
207                i += 1;
208                while s[i] != 0 && s[i] != b'\'' {
209                    if cl + 1 >= cmd.len() { return Err(WRDE_NOSPACE_V); }
210                    cmd[cl] = s[i];
211                    cl += 1;
212                    i += 1;
213                }
214                if s[i] == b'\'' {
215                    if cl + 1 >= cmd.len() { return Err(WRDE_NOSPACE_V); }
216                    cmd[cl] = s[i];
217                    cl += 1;
218                    i += 1;
219                }
220                continue;
221            }
222            if c == b'(' { depth += 1; }
223            if c == b')' {
224                depth -= 1;
225                if depth == 0 { i += 1; break; }
226            }
227            if cl + 1 >= cmd.len() { return Err(WRDE_NOSPACE_V); }
228            cmd[cl] = c;
229            cl += 1;
230            i += 1;
231        }
232        if depth != 0 { return Err(WRDE_SYNTAX_V); }
233    }
234    cmd[cl] = 0;
235    let f = crate::popen(cmd.as_ptr() as *const c_char, b"r\0".as_ptr() as *const c_char);
236    if f.is_null() { return Ok(i); }
237    let mut out = [0u8; 4096];
238    let n = crate::fread(out.as_mut_ptr() as *mut c_void, 1, out.len() - 1, f);
239    crate::pclose(f);
240    let mut l = n;
241    while l > 0 && (out[l - 1] == b'\n' || out[l - 1] == b'\r') { l -= 1; }
242    // Word splitting on the command output.
243    let mut p = 0usize;
244    let mut first = true;
245    while p < l {
246        while p < l && (out[p] == b' ' || out[p] == b'\t' || out[p] == b'\n') { p += 1; }
247        if p >= l { break; }
248        let st = p;
249        while p < l && out[p] != b' ' && out[p] != b'\t' && out[p] != b'\n' { p += 1; }
250        if !first {
251            // Caller appends to the current word; insert a separator by
252            // returning a space marker is not possible here, so concatenate.
253        }
254        let r = wb_push_str(w, out.as_ptr().add(st), p - st, false);
255        if r != 0 { return Err(r); }
256        first = false;
257    }
258    Ok(i)
259}
260
261#[no_mangle]
262pub unsafe extern "C" fn wordexp(s: *const c_char, p: *mut WordexpT, flags: c_int) -> c_int {
263    if s.is_null() || p.is_null() { return WRDE_SYNTAX_V; }
264    if flags & WRDE_REUSE_V != 0 { wordfree(p); }
265    let mut list = WordList { items: core::ptr::null_mut(), n: 0, cap: 0 };
266    let src = core::slice::from_raw_parts(s as *const u8, crate::strlen(s) + 1);
267    let mut i = 0usize;
268    loop {
269        while src[i] == b' ' || src[i] == b'\t' || src[i] == b'\n' { i += 1; }
270        if src[i] == 0 { break; }
271        let mut w = WordBuf { buf: [0u8; WORDEXP_CAP], len: 0, has_glob: false, quoted: false };
272        let mut done = false;
273        while !done {
274            let c = src[i];
275            match c {
276                0 => { done = true; }
277                b' ' | b'\t' | b'\n' => { i += 1; done = true; }
278                b'\'' => {
279                    i += 1;
280                    loop {
281                        if src[i] == 0 {
282                            wl_free(&mut list);
283                            return WRDE_SYNTAX_V;
284                        }
285                        if src[i] == b'\'' { i += 1; break; }
286                        let r = wb_push(&mut w, src[i], true);
287                        if r != 0 { wl_free(&mut list); return r; }
288                        i += 1;
289                    }
290                    w.quoted = true;
291                }
292                b'"' => {
293                    i += 1;
294                    loop {
295                        if src[i] == 0 {
296                            wl_free(&mut list);
297                            return WRDE_SYNTAX_V;
298                        }
299                        if src[i] == b'"' { i += 1; break; }
300                        if src[i] == b'\\' {
301                            let n = src[i + 1];
302                            if n == b'$' || n == b'`' || n == b'"' || n == b'\\' {
303                                let r = wb_push(&mut w, n, true);
304                                if r != 0 { wl_free(&mut list); return r; }
305                                i += 2;
306                                continue;
307                            }
308                            let r = wb_push(&mut w, src[i], true);
309                            if r != 0 { wl_free(&mut list); return r; }
310                            i += 1;
311                            continue;
312                        }
313                        if src[i] == b'`' || (src[i] == b'$' && src[i + 1] == b'(') {
314                            match command_sub(src, i, &mut w, flags) {
315                                Ok(ni) => { i = ni; continue; }
316                                Err(e) => { wl_free(&mut list); return e; }
317                            }
318                        }
319                        if src[i] == b'$' {
320                            match expand_var(src, i, &mut w, flags) {
321                                Ok(ni) => { i = ni; continue; }
322                                Err(e) => { wl_free(&mut list); return e; }
323                            }
324                        }
325                        let r = wb_push(&mut w, src[i], true);
326                        if r != 0 { wl_free(&mut list); return r; }
327                        i += 1;
328                    }
329                    w.quoted = true;
330                }
331                b'\\' => {
332                    i += 1;
333                    if src[i] == 0 {
334                        wl_free(&mut list);
335                        return WRDE_SYNTAX_V;
336                    }
337                    let r = wb_push(&mut w, src[i], true);
338                    if r != 0 { wl_free(&mut list); return r; }
339                    i += 1;
340                }
341                b'$' => {
342                    if src[i + 1] == b'(' {
343                        match command_sub(src, i, &mut w, flags) {
344                            Ok(ni) => { i = ni; }
345                            Err(e) => { wl_free(&mut list); return e; }
346                        }
347                    } else {
348                        match expand_var(src, i, &mut w, flags) {
349                            Ok(ni) => { i = ni; }
350                            Err(e) => { wl_free(&mut list); return e; }
351                        }
352                    }
353                }
354                b'`' => {
355                    match command_sub(src, i, &mut w, flags) {
356                        Ok(ni) => { i = ni; }
357                        Err(e) => { wl_free(&mut list); return e; }
358                    }
359                }
360                b'*' | b'?' | b'[' => {
361                    w.has_glob = true;
362                    let r = wb_push(&mut w, c, false);
363                    if r != 0 { wl_free(&mut list); return r; }
364                    i += 1;
365                }
366                _ => {
367                    let r = wb_push(&mut w, c, false);
368                    if r != 0 { wl_free(&mut list); return r; }
369                    i += 1;
370                }
371            }
372        }
373        if w.len == 0 && !w.quoted { continue; }
374        w.buf[w.len] = 0;
375        if w.has_glob && !w.quoted {
376            let mut g: GlobT = core::mem::zeroed();
377            let r = crate::glob(w.buf.as_ptr() as *const c_char, 0x10 | 0x04, None, &mut g); // NOCHECK|NOSORT
378            if r == 0 {
379                for k in 0..g.gl_pathc {
380                    let gp = *g.gl_pathv.add(k);
381                    let l = crate::strlen(gp);
382                    wl_push(&mut list, gp as *const u8, l);
383                }
384                crate::globfree(&mut g);
385            } else {
386                wl_push(&mut list, w.buf.as_ptr(), w.len);
387            }
388        } else {
389            wl_push(&mut list, w.buf.as_ptr(), w.len);
390        }
391    }
392
393    let old = if flags & WRDE_APPEND_V != 0 { (*p).we_wordc } else { 0 };
394    let offs = if flags & WRDE_DOOFFS_V != 0 { (*p).we_offs } else { 0 };
395    let total = offs + old + list.n + 1;
396    let pathv = crate::calloc(total, core::mem::size_of::<*mut c_char>()) as *mut *mut c_char;
397    if pathv.is_null() {
398        wl_free(&mut list);
399        return WRDE_NOSPACE_V;
400    }
401    for k in 0..offs { *pathv.add(k) = core::ptr::null_mut(); }
402    if flags & WRDE_APPEND_V != 0 && old != 0 && !(*p).we_wordv.is_null() {
403        for k in 0..old {
404            *pathv.add(offs + k) = *(*p).we_wordv.add(offs + k);
405        }
406        crate::free((*p).we_wordv as *mut c_void);
407    }
408    for k in 0..list.n {
409        *pathv.add(offs + old + k) = *list.items.add(k);
410    }
411    *pathv.add(offs + old + list.n) = core::ptr::null_mut();
412    crate::free(list.items as *mut c_void);
413    (*p).we_wordc = old + list.n;
414    (*p).we_wordv = pathv;
415    (*p).we_offs = offs;
416    0
417}
418
419#[no_mangle]
420pub unsafe extern "C" fn wordfree(p: *mut WordexpT) {
421    if p.is_null() || (*p).we_wordv.is_null() { return; }
422    let offs = (*p).we_offs;
423    for i in 0..(*p).we_wordc {
424        let q = *(*p).we_wordv.add(offs + i);
425        if !q.is_null() { crate::free(q as *mut c_void); }
426    }
427    crate::free((*p).we_wordv as *mut c_void);
428    (*p).we_wordv = core::ptr::null_mut();
429    (*p).we_wordc = 0;
430}