Skip to main content

c/
path_extra.rs

1// realpath, glob/globfree and nftw/ftw.
2
3const PATH_MAX_V: usize = 4096;
4
5// ============================================================
6// realpath
7// ============================================================
8
9static mut REALPATH_BUF: [c_char; PATH_MAX_V] = [0; PATH_MAX_V];
10
11unsafe fn rp_is_dir(path: *const c_char) -> bool {
12    let mut st: Stat = core::mem::zeroed();
13    if crate::lstat(path, &mut st as *mut Stat as *mut c_void) != 0 { return false; }
14    st.st_mode & 0o170000 == 0o040000
15}
16
17#[no_mangle]
18pub unsafe extern "C" fn realpath(
19    filename: *const c_char,
20    resolved: *mut c_char,
21) -> *mut c_char {
22    if filename.is_null() { ERRNO!() = EINVAL; return core::ptr::null_mut(); }
23    let flen = crate::strlen(filename);
24    if flen == 0 { ERRNO!() = 2; return core::ptr::null_mut(); }
25    if flen >= PATH_MAX_V { ERRNO!() = 36; return core::ptr::null_mut(); }
26
27    let mut out = [0u8; PATH_MAX_V + 1];
28    let mut olen;
29    let mut pend = [0u8; 2 * PATH_MAX_V + 1];
30    core::ptr::copy_nonoverlapping(filename as *const u8, pend.as_mut_ptr(), flen);
31    pend[flen] = 0;
32    let mut pi = 0usize;
33    let absolute = pend[0] == b'/';
34    if absolute {
35        pi = 1;
36        out[0] = b'/';
37        olen = 1;
38    } else {
39        let r = crate::getcwd(out.as_mut_ptr() as *mut c_char, PATH_MAX_V);
40        if r.is_null() { return core::ptr::null_mut(); }
41        olen = crate::strlen(out.as_ptr() as *const c_char);
42        if olen == 0 { out[0] = b'/'; olen = 1; }
43    }
44    let mut symlinks = 0;
45
46    loop {
47        while pend[pi] == b'/' { pi += 1; }
48        if pend[pi] == 0 { break; }
49        let start = pi;
50        while pend[pi] != 0 && pend[pi] != b'/' { pi += 1; }
51        let comp_len = pi - start;
52        let mut q = pi;
53        while pend[q] == b'/' { q += 1; }
54        let is_last = pend[q] == 0;
55        let followed_by_slash = pend[pi] == b'/';
56        let needs_dir = !is_last || followed_by_slash;
57
58        if comp_len == 1 && pend[start] == b'.' {
59            if needs_dir && is_last { /* "." is a dir */ }
60            continue;
61        }
62        if comp_len == 2 && pend[start] == b'.' && pend[start + 1] == b'.' {
63            while olen > 0 && out[olen - 1] != b'/' { olen -= 1; }
64            if olen > 0 { olen -= 1; }
65            continue;
66        }
67
68        if olen == 0 {
69            out[0] = b'/';
70            olen = 1;
71        }
72        if olen > 0 && out[olen - 1] != b'/' {
73            if olen + 1 >= PATH_MAX_V { ERRNO!() = 36; return core::ptr::null_mut(); }
74            out[olen] = b'/';
75            olen += 1;
76        }
77        if olen + comp_len >= PATH_MAX_V { ERRNO!() = 36; return core::ptr::null_mut(); }
78        core::ptr::copy_nonoverlapping(pend.as_ptr().add(start), out.as_mut_ptr().add(olen), comp_len);
79        olen += comp_len;
80        out[olen] = 0;
81
82        let mut st: Stat = core::mem::zeroed();
83        if crate::lstat(out.as_ptr() as *const c_char, &mut st as *mut Stat as *mut c_void) != 0 {
84            if ERRNO!() == 2 { ERRNO!() = 2; }
85            return core::ptr::null_mut();
86        }
87        let mode = st.st_mode;
88        if mode & 0o170000 == 0o120000 {
89            symlinks += 1;
90            if symlinks > 40 { ERRNO!() = 40; return core::ptr::null_mut(); }
91            let mut link = [0u8; PATH_MAX_V + 1];
92            let n = crate::readlink(out.as_ptr() as *const c_char, link.as_mut_ptr() as *mut c_char, PATH_MAX_V);
93            if n < 0 { return core::ptr::null_mut(); }
94            let n = n as usize;
95            if n == 0 { ERRNO!() = 2; return core::ptr::null_mut(); }
96            link[n] = 0;
97            // Remove the component we just appended.
98            while olen > 0 && out[olen - 1] != b'/' { olen -= 1; }
99            if olen > 0 { olen -= 1; }
100            let mut next = [0u8; 2 * PATH_MAX_V + 1];
101            let link_abs = link[0] == b'/';
102            let mut nl = n;
103            core::ptr::copy_nonoverlapping(link.as_ptr(), next.as_mut_ptr(), n);
104            let rest = &pend[pi..];
105            let rest_len = crate::strlen(rest.as_ptr() as *const c_char);
106            if rest_len > 0 {
107                if nl > 0 && next[nl - 1] != b'/' {
108                    next[nl] = b'/';
109                    nl += 1;
110                }
111                if nl + rest_len >= next.len() { ERRNO!() = 36; return core::ptr::null_mut(); }
112                core::ptr::copy_nonoverlapping(rest.as_ptr(), next.as_mut_ptr().add(nl), rest_len + 1);
113                nl += rest_len;
114            }
115            if link_abs { olen = 0; }
116            pend = next;
117            pi = 0;
118            let _ = nl;
119            continue;
120        }
121        if mode & 0o170000 != 0o040000 && needs_dir {
122            ERRNO!() = 20; // ENOTDIR
123            return core::ptr::null_mut();
124        }
125    }
126
127    if olen == 0 {
128        out[0] = b'/';
129        olen = 1;
130    }
131    // Collapse a trailing slash except for the root.
132    while olen > 1 && out[olen - 1] == b'/' { olen -= 1; }
133    out[olen] = 0;
134    let dst = if resolved.is_null() { REALPATH_BUF.as_mut_ptr() } else { resolved };
135    core::ptr::copy_nonoverlapping(out.as_ptr(), dst as *mut u8, olen + 1);
136    dst
137}
138
139// ============================================================
140// glob
141// ============================================================
142
143#[repr(C)]
144pub struct GlobT {
145    pub gl_pathc: usize,
146    pub gl_pathv: *mut *mut c_char,
147    pub gl_offs: usize,
148    __dummy1: c_int,
149    __dummy2: [*mut c_void; 5],
150}
151
152const GLOB_ERR_V: c_int = 0x01;
153const GLOB_MARK_V: c_int = 0x02;
154const GLOB_NOSORT_V: c_int = 0x04;
155const GLOB_DOOFFS_V: c_int = 0x08;
156const GLOB_NOCHECK_V: c_int = 0x10;
157const GLOB_APPEND_V: c_int = 0x20;
158const GLOB_NOESCAPE_V: c_int = 0x40;
159const GLOB_PERIOD_V: c_int = 0x80;
160const GLOB_TILDE_V: c_int = 0x1000;
161const GLOB_TILDE_CHECK_V: c_int = 0x4000;
162
163const GLOB_NOSPACE_V: c_int = 1;
164const GLOB_ABORTED_V: c_int = 2;
165const GLOB_NOMATCH_V: c_int = 3;
166
167struct GlobList {
168    items: *mut *mut c_char,
169    n: usize,
170    cap: usize,
171}
172
173unsafe fn glob_list_push(list: *mut GlobList, s: *const c_char) {
174    if (*list).n == (*list).cap {
175        let ncap = if (*list).cap == 0 { 32 } else { (*list).cap * 2 };
176        let na = crate::realloc(
177            (*list).items as *mut c_void,
178            ncap * core::mem::size_of::<*mut c_char>(),
179        ) as *mut *mut c_char;
180        if na.is_null() { return; }
181        (*list).items = na;
182        (*list).cap = ncap;
183    }
184    let l = crate::strlen(s);
185    let copy = crate::malloc(l + 1) as *mut c_char;
186    if copy.is_null() { return; }
187    core::ptr::copy_nonoverlapping(s as *const u8, copy as *mut u8, l + 1);
188    *(*list).items.add((*list).n) = copy;
189    (*list).n += 1;
190}
191
192unsafe fn glob_list_push_n(list: *mut GlobList, s: *const u8, len: usize) {
193    if (*list).n == (*list).cap {
194        let ncap = if (*list).cap == 0 { 32 } else { (*list).cap * 2 };
195        let na = crate::realloc(
196            (*list).items as *mut c_void,
197            ncap * core::mem::size_of::<*mut c_char>(),
198        ) as *mut *mut c_char;
199        if na.is_null() { return; }
200        (*list).items = na;
201        (*list).cap = ncap;
202    }
203    let copy = crate::malloc(len + 1) as *mut c_char;
204    if copy.is_null() { return; }
205    core::ptr::copy_nonoverlapping(s, copy as *mut u8, len);
206    *copy.add(len) = 0;
207    *(*list).items.add((*list).n) = copy;
208    (*list).n += 1;
209}
210
211unsafe fn list_dir_names(path: *const c_char) -> (*mut *mut c_char, usize) {
212    let fd = crate::open(path, crate::O_DIRECTORY, 0);
213    if fd < 0 { return (core::ptr::null_mut(), 0); }
214    let mut cap = 32usize;
215    let mut count = 0usize;
216    let mut arr = crate::malloc(cap * core::mem::size_of::<*mut c_char>()) as *mut *mut c_char;
217    if arr.is_null() { crate::close(fd); return (core::ptr::null_mut(), 0); }
218    #[repr(align(8))]
219    struct Buf([u8; 8192]);
220    let mut buf = Buf([0u8; 8192]);
221    let name_off = core::mem::offset_of!(linux_dirent64, d_name);
222    loop {
223        let n = <Arch as Syscalls>::syscall3(
224            SYS_GETDENTS64,
225            fd as i64,
226            buf.0.as_mut_ptr() as i64,
227            buf.0.len() as i64,
228        ) as isize;
229        if n <= 0 { break; }
230        let mut off = 0usize;
231        while off + name_off < n as usize {
232            let ent = buf.0.as_ptr().add(off) as *const linux_dirent64;
233            let reclen = (*ent).d_reclen as usize;
234            if reclen == 0 { break; }
235            let name = buf.0.as_ptr().add(off + name_off) as *const c_char;
236            let nl = crate::strlen(name);
237            if count + 2 >= cap {
238                cap *= 2;
239                let na = crate::realloc(arr as *mut c_void, cap * core::mem::size_of::<*mut c_char>()) as *mut *mut c_char;
240                if na.is_null() { break; }
241                arr = na;
242            }
243            let copy = crate::malloc(nl + 1) as *mut c_char;
244            if copy.is_null() { break; }
245            core::ptr::copy_nonoverlapping(name as *const u8, copy as *mut u8, nl + 1);
246            *arr.add(count) = copy;
247            count += 1;
248            off += reclen;
249        }
250    }
251    crate::close(fd);
252    (arr, count)
253}
254
255unsafe fn free_names(arr: *mut *mut c_char, n: usize) {
256    if arr.is_null() { return; }
257    for i in 0..n {
258        if !(*arr.add(i)).is_null() { crate::free(*arr.add(i) as *mut c_void); }
259    }
260    crate::free(arr as *mut c_void);
261}
262
263fn glob_has_magic(comp: &[u8], noescape: bool) -> bool {
264    let mut i = 0;
265    while i < comp.len() {
266        match comp[i] {
267            b'\\' if !noescape => i += 1,
268            b'*' | b'?' | b'[' => return true,
269            _ => {}
270        }
271        i += 1;
272    }
273    false
274}
275
276unsafe fn glob_unescape(dst: &mut [u8], comp: &[u8], noescape: bool) -> usize {
277    let mut o = 0usize;
278    let mut i = 0usize;
279    while i < comp.len() {
280        if comp[i] == b'\\' && !noescape && i + 1 < comp.len() {
281            i += 1;
282        }
283        if o < dst.len() { dst[o] = comp[i]; o += 1; }
284        i += 1;
285    }
286    o
287}
288
289#[derive(Copy, Clone)]
290struct Comp {
291    start: usize,
292    end: usize,
293}
294
295unsafe fn glob_split(pat: &[u8], noescape: bool, comps: &mut [Comp; 128]) -> (usize, bool) {
296    let mut n = 0usize;
297    let mut i = 0usize;
298    let absolute = !pat.is_empty() && pat[0] == b'/';
299    let mut trailing = false;
300    while i < pat.len() {
301        while i < pat.len() && pat[i] == b'/' { i += 1; }
302        if i >= pat.len() { if i > 0 { trailing = pat[i - 1] == b'/'; } break; }
303        let start = i;
304        while i < pat.len() {
305            if pat[i] == b'\\' && !noescape && i + 1 < pat.len() { i += 2; continue; }
306            if pat[i] == b'/' { break; }
307            i += 1;
308        }
309        if n < comps.len() {
310            comps[n] = Comp { start, end: i };
311            n += 1;
312        }
313    }
314    let _ = trailing;
315    (n, absolute)
316}
317
318unsafe fn glob_walk(
319    base: &mut [u8; PATH_MAX_V + 1],
320    blen: &mut usize,
321    pat: &[u8],
322    comps: &[Comp],
323    ci: usize,
324    trailing: bool,
325    flags: c_int,
326    errfunc: Option<unsafe extern "C" fn(*const c_char, c_int) -> c_int>,
327    list: *mut GlobList,
328) -> c_int {
329    if ci >= comps.len() {
330        glob_list_push_n(list, base.as_ptr(), *blen);
331        return 0;
332    }
333    let comp = &pat[comps[ci].start..comps[ci].end];
334    let last = ci + 1 == comps.len();
335    let noescape = flags & GLOB_NOESCAPE_V != 0;
336
337    if !glob_has_magic(comp, noescape) {
338        let mut name = [0u8; PATH_MAX_V + 1];
339        let nlen = glob_unescape(&mut name, comp, noescape);
340        let mut newlen = *blen;
341        if newlen > 0 && base[newlen - 1] != b'/' {
342            if newlen + 1 >= base.len() { return GLOB_NOSPACE_V; }
343            base[newlen] = b'/';
344            newlen += 1;
345        }
346        if newlen + nlen >= base.len() { return GLOB_NOSPACE_V; }
347        core::ptr::copy_nonoverlapping(name.as_ptr(), base.as_mut_ptr().add(newlen), nlen);
348        newlen += nlen;
349        base[newlen] = 0;
350        let mut st: Stat = core::mem::zeroed();
351        let exists = crate::lstat(base.as_ptr() as *const c_char, &mut st as *mut Stat as *mut c_void) == 0;
352        let is_dir = exists && st.st_mode & 0o170000 == 0o040000;
353        if last {
354            if exists && (!trailing || is_dir) {
355                if flags & GLOB_ONLYDIR_V != 0 && !is_dir {
356                    *blen = newlen;
357                    return 0;
358                }
359                let mut end = newlen;
360                if (flags & GLOB_MARK_V != 0) && is_dir {
361                    if end + 1 < base.len() {
362                        base[end] = b'/';
363                        end += 1;
364                    }
365                }
366                glob_list_push_n(list, base.as_ptr(), end);
367            }
368        } else {
369            if !is_dir {
370                if flags & GLOB_ERR_V != 0 {
371                    *blen = newlen;
372                    return GLOB_ABORTED_V;
373                }
374            } else {
375                let old = *blen;
376                *blen = newlen;
377                let r = glob_walk(base, blen, pat, comps, ci + 1, trailing, flags, errfunc, list);
378                *blen = old;
379                if r != 0 { return r; }
380            }
381        }
382        *blen = newlen;
383        return 0;
384    }
385
386    // Wildcard component: enumerate the base directory.
387    let mut dirc = [0 as c_char; PATH_MAX_V + 1];
388    let dp: *const c_char;
389    if *blen == 0 {
390        dirc[0] = b'.' as c_char;
391        dirc[1] = 0;
392        dp = dirc.as_ptr();
393    } else {
394        core::ptr::copy_nonoverlapping(base.as_ptr(), dirc.as_mut_ptr() as *mut u8, *blen);
395        dirc[*blen] = 0;
396        dp = dirc.as_ptr();
397    }
398    let (names, count) = list_dir_names(dp);
399    if names.is_null() {
400        if flags & GLOB_ERR_V != 0 {
401            if let Some(f) = errfunc { let _ = f(dp, ERRNO!()); }
402            return GLOB_ABORTED_V;
403        }
404        return 0;
405    }
406    let mut patc = [0 as c_char; PATH_MAX_V + 1];
407    core::ptr::copy_nonoverlapping(comp.as_ptr(), patc.as_mut_ptr() as *mut u8, comp.len());
408    patc[comp.len()] = 0;
409    let mut fnm_flags = 0;
410    if noescape { fnm_flags |= FNM_NOESCAPE; }
411    if flags & GLOB_PERIOD_V == 0 { fnm_flags |= FNM_PERIOD; }
412    let old = *blen;
413    for i in 0..count {
414        let name = *names.add(i);
415        if flags & GLOB_PERIOD_V == 0 {
416            // FNM_PERIOD already covers this, but skip "." / ".." here.
417            if *name == b'.' as c_char {
418                let second = *name.add(1);
419                if second == 0 || (second == b'.' as c_char && *name.add(2) == 0) { continue; }
420            }
421        }
422        if crate::fnmatch(patc.as_ptr(), name, fnm_flags) != 0 { continue; }
423        let nlen = crate::strlen(name);
424        let mut newlen = old;
425        if newlen > 0 && base[newlen - 1] != b'/' {
426            if newlen + 1 >= base.len() { free_names(names, count); return GLOB_NOSPACE_V; }
427            base[newlen] = b'/';
428            newlen += 1;
429        }
430        if newlen + nlen >= base.len() { free_names(names, count); return GLOB_NOSPACE_V; }
431        core::ptr::copy_nonoverlapping(name as *const u8, base.as_mut_ptr().add(newlen), nlen);
432        newlen += nlen;
433        base[newlen] = 0;
434        let mut st: Stat = core::mem::zeroed();
435        let exists = crate::lstat(base.as_ptr() as *const c_char, &mut st as *mut Stat as *mut c_void) == 0;
436        let is_dir = exists && st.st_mode & 0o170000 == 0o040000;
437        if last {
438            if flags & GLOB_ONLYDIR_V != 0 && !is_dir { continue; }
439            let mut end = newlen;
440            if (flags & GLOB_MARK_V != 0) && is_dir && end + 1 < base.len() {
441                base[end] = b'/';
442                end += 1;
443            }
444            glob_list_push_n(list, base.as_ptr(), end);
445        } else if is_dir {
446            *blen = newlen;
447            let r = glob_walk(base, blen, pat, comps, ci + 1, trailing, flags, errfunc, list);
448            *blen = old;
449            if r != 0 { free_names(names, count); return r; }
450        }
451    }
452    free_names(names, count);
453    *blen = old;
454    0
455}
456
457const GLOB_ONLYDIR_V: c_int = 0x200; // same value as musl/glibc
458
459unsafe fn glob_expand_tilde(pat: *const c_char, buf: *mut u8, cap: usize) -> Option<usize> {
460    let s = pat as *const u8;
461    if *s != b'~' { return None; }
462    let mut i = 1usize;
463    while *s.add(i) != 0 && *s.add(i) != b'/' { i += 1; }
464    let mut name = [0 as c_char; 256];
465    let nlen = i - 1;
466    if nlen >= name.len() { return None; }
467    core::ptr::copy_nonoverlapping(s.add(1), name.as_mut_ptr() as *mut u8, nlen);
468    name[nlen] = 0;
469    let pw = if nlen == 0 {
470        crate::getpwuid(crate::getuid())
471    } else {
472        crate::getpwnam(name.as_ptr())
473    };
474    if pw.is_null() { return None; }
475    let home = (*pw).pw_dir;
476    let hl = crate::strlen(home);
477    let rest = crate::strlen(s.add(i) as *const c_char);
478    if hl + rest + 1 > cap { return None; }
479    core::ptr::copy_nonoverlapping(home as *const u8, buf, hl);
480    core::ptr::copy_nonoverlapping(s.add(i), buf.add(hl), rest + 1);
481    Some(hl + rest)
482}
483
484#[no_mangle]
485pub unsafe extern "C" fn glob(
486    pattern: *const c_char,
487    flags: c_int,
488    errfunc: Option<unsafe extern "C" fn(*const c_char, c_int) -> c_int>,
489    pglob: *mut GlobT,
490) -> c_int {
491    if pattern.is_null() || pglob.is_null() { ERRNO!() = EINVAL; return GLOB_ABORTED_V; }
492
493    let mut expanded = [0u8; PATH_MAX_V + 1];
494    let mut pat_ptr = pattern as *const u8;
495    let mut pat_len = crate::strlen(pattern);
496    if flags & (GLOB_TILDE_V | GLOB_TILDE_CHECK_V) != 0 {
497        if let Some(n) = glob_expand_tilde(pattern, expanded.as_mut_ptr(), PATH_MAX_V) {
498            pat_ptr = expanded.as_ptr();
499            pat_len = n;
500        } else if flags & GLOB_TILDE_CHECK_V != 0 {
501            return GLOB_NOMATCH_V;
502        }
503    }
504    let pat = &core::slice::from_raw_parts(pat_ptr, pat_len)[..];
505
506    let mut comps = [Comp { start: 0, end: 0 }; 128];
507    let (ncomp, absolute) = glob_split(pat, flags & GLOB_NOESCAPE_V != 0, &mut comps);
508
509    let mut list = GlobList { items: core::ptr::null_mut(), n: 0, cap: 0 };
510    let mut base = [0u8; PATH_MAX_V + 1];
511    let mut blen = 0usize;
512    if absolute {
513        base[0] = b'/';
514        blen = 1;
515    }
516    let trailing = !pat.is_empty() && pat[pat.len() - 1] == b'/';
517    let r = if ncomp == 0 {
518        if absolute {
519            glob_list_push_n(&mut list, b"/".as_ptr(), 1);
520            0
521        } else {
522            GLOB_NOMATCH_V
523        }
524    } else {
525        glob_walk(&mut base, &mut blen, pat, &comps[..ncomp], 0, trailing, flags, errfunc, &mut list)
526    };
527    if r == GLOB_ABORTED_V {
528        for i in 0..list.n { crate::free(*list.items.add(i) as *mut c_void); }
529        crate::free(list.items as *mut c_void);
530        return r;
531    }
532    let mut matches = list.n;
533    if matches == 0 && flags & GLOB_NOCHECK_V != 0 {
534        glob_list_push(&mut list, pattern);
535        matches = list.n;
536    }
537    if matches == 0 && flags & GLOB_APPEND_V == 0 {
538        crate::free(list.items as *mut c_void);
539        return GLOB_NOMATCH_V;
540    }
541    if flags & GLOB_NOSORT_V == 0 && matches > 1 {
542        crate::qsort(
543            list.items as *mut c_void,
544            matches,
545            core::mem::size_of::<*mut c_char>(),
546            glob_cmp,
547        );
548    }
549
550    let old_n = if flags & GLOB_APPEND_V != 0 { (*pglob).gl_pathc } else { 0 };
551    let offs = if flags & GLOB_DOOFFS_V != 0 { (*pglob).gl_offs } else { 0 };
552    if old_n != 0 && !(*pglob).gl_pathv.is_null() && flags & GLOB_APPEND_V == 0 {
553        // Nothing to free; append is off.
554    }
555    let total = offs + old_n + matches + 1;
556    let pathv = crate::calloc(total, core::mem::size_of::<*mut c_char>()) as *mut *mut c_char;
557    if pathv.is_null() {
558        for i in 0..list.n { crate::free(*list.items.add(i) as *mut c_void); }
559        crate::free(list.items as *mut c_void);
560        return GLOB_NOSPACE_V;
561    }
562    for i in 0..offs { *pathv.add(i) = core::ptr::null_mut(); }
563    if flags & GLOB_APPEND_V != 0 && old_n != 0 && !(*pglob).gl_pathv.is_null() {
564        for i in 0..old_n {
565            *pathv.add(offs + i) = *(*pglob).gl_pathv.add(offs + i);
566        }
567        crate::free((*pglob).gl_pathv as *mut c_void);
568    }
569    for i in 0..list.n {
570        *pathv.add(offs + old_n + i) = *list.items.add(i);
571    }
572    *pathv.add(offs + old_n + matches) = core::ptr::null_mut();
573    crate::free(list.items as *mut c_void);
574
575    (*pglob).gl_pathc = old_n + matches;
576    (*pglob).gl_pathv = pathv;
577    (*pglob).gl_offs = offs;
578    (*pglob).__dummy1 = 0;
579    0
580}
581
582unsafe extern "C" fn glob_cmp(a: *const c_void, b: *const c_void) -> c_int {
583    let sa = *(a as *const *const c_char);
584    let sb = *(b as *const *const c_char);
585    crate::strcmp(sa as *const u8, sb as *const u8)
586}
587
588#[no_mangle]
589pub unsafe extern "C" fn globfree(pglob: *mut GlobT) {
590    if pglob.is_null() || (*pglob).gl_pathv.is_null() { return; }
591    let offs = (*pglob).gl_offs;
592    for i in 0..(*pglob).gl_pathc {
593        let p = *(*pglob).gl_pathv.add(offs + i);
594        if !p.is_null() { crate::free(p as *mut c_void); }
595    }
596    crate::free((*pglob).gl_pathv as *mut c_void);
597    (*pglob).gl_pathv = core::ptr::null_mut();
598    (*pglob).gl_pathc = 0;
599}
600
601// ============================================================
602// nftw / ftw
603// ============================================================
604
605#[repr(C)]
606pub struct Ftw {
607    pub base: c_int,
608    pub level: c_int,
609}
610
611const FTW_F_V: c_int = 1;
612const FTW_D_V: c_int = 2;
613const FTW_DNR_V: c_int = 3;
614const FTW_NS_V: c_int = 4;
615const FTW_SL_V: c_int = 5;
616const FTW_DP_V: c_int = 6;
617const FTW_SLN_V: c_int = 7;
618
619const FTW_PHYS_V: c_int = 1;
620const FTW_MOUNT_V: c_int = 2;
621const FTW_DEPTH_V: c_int = 8;
622
623struct History {
624    chain: *mut History,
625    dev: u64,
626    ino: u64,
627    level: c_int,
628    base: c_int,
629}
630
631#[derive(Copy, Clone)]
632enum WalkCb {
633    N(Option<unsafe extern "C" fn(*const c_char, *const Stat, c_int, *mut Ftw) -> c_int>),
634    F(Option<unsafe extern "C" fn(*const c_char, *const Stat, c_int) -> c_int>),
635}
636
637unsafe fn walk_call(cb: WalkCb, path: *const c_char, st: *const Stat, type_: c_int, ftw: *mut Ftw) -> c_int {
638    match cb {
639        WalkCb::N(Some(f)) => f(path, st, type_, ftw),
640        WalkCb::F(Some(f)) => f(path, st, type_),
641        _ => 0,
642    }
643}
644
645unsafe fn nftw_do(
646    buf: *mut u8,
647    cap: usize,
648    len: usize,
649    cb: WalkCb,
650    fd_limit: c_int,
651    flags: c_int,
652    h: *mut History,
653    _base: c_int,
654    level: c_int,
655) -> c_int {
656    let path = buf as *const c_char;
657    let mut st: Stat = core::mem::zeroed();
658    let phys = flags & FTW_PHYS_V != 0;
659    let mut type_: c_int;
660    let mut stat_ok = true;
661    let rc = if phys { crate::lstat(path, &mut st as *mut Stat as *mut c_void) } else { crate::stat(path, &mut st) };
662    if rc != 0 {
663        if !phys && ERRNO!() == 2 && crate::lstat(path, &mut st as *mut Stat as *mut c_void) == 0 {
664            type_ = FTW_SLN_V;
665        } else if ERRNO!() != 13 {
666            return -1;
667        } else {
668            type_ = FTW_NS_V;
669            st = core::mem::zeroed();
670            stat_ok = false;
671        }
672    } else {
673        let mode = st.st_mode;
674        if mode & 0o170000 == 0o040000 {
675            type_ = if flags & FTW_DEPTH_V != 0 { FTW_DP_V } else { FTW_D_V };
676        } else if mode & 0o170000 == 0o120000 {
677            type_ = if phys { FTW_SL_V } else { FTW_SLN_V };
678        } else {
679            type_ = FTW_F_V;
680        }
681    }
682    if flags & FTW_MOUNT_V != 0 && !h.is_null() && stat_ok && type_ != FTW_NS_V && st.st_dev != (*h).dev {
683        return 0;
684    }
685    let parent_base = if h.is_null() {
686        let mut k = len;
687        while k > 0 && *buf.add(k - 1) == b'/' { k -= 1; }
688        while k > 0 && *buf.add(k - 1) != b'/' { k -= 1; }
689        k as c_int
690    } else {
691        (*h).base
692    };
693    let mut new = History {
694        chain: h,
695        dev: st.st_dev,
696        ino: st.st_ino,
697        level: level + 1,
698        base: parent_base,
699    };
700    let mut ftw = Ftw { base: parent_base, level: level };
701
702    let mut dfd: c_int = -1;
703    let mut open_err: c_int = 0;
704    if type_ == FTW_D_V || type_ == FTW_DP_V {
705        dfd = crate::open(path, 0, 0);
706        open_err = ERRNO!();
707        if dfd < 0 && open_err == 13 { type_ = FTW_DNR_V; }
708        if fd_limit == 0 && dfd >= 0 { crate::close(dfd); dfd = -1; }
709    }
710
711    if flags & FTW_DEPTH_V == 0 {
712        let r = walk_call(cb, path, &st, type_, &mut ftw);
713        if r != 0 { return r; }
714    }
715
716    let mut p = h;
717    while !p.is_null() {
718        if (*p).dev == st.st_dev && (*p).ino == st.st_ino { return 0; }
719        p = (*p).chain;
720    }
721
722    if (type_ == FTW_D_V || type_ == FTW_DP_V) && fd_limit > 0 {
723        if dfd < 0 {
724            ERRNO!() = open_err;
725            return -1;
726        }
727        // Enumerate with getdents64 so nested walks are independent.
728        #[repr(align(8))]
729        struct Buf([u8; 8192]);
730        let mut dbuf = Buf([0u8; 8192]);
731        let name_off = core::mem::offset_of!(linux_dirent64, d_name);
732        loop {
733            let n = <Arch as Syscalls>::syscall3(
734                SYS_GETDENTS64,
735                dfd as i64,
736                dbuf.0.as_mut_ptr() as i64,
737                dbuf.0.len() as i64,
738            ) as isize;
739            if n <= 0 { break; }
740            let mut off = 0usize;
741            while off + name_off < n as usize {
742                let ent = dbuf.0.as_ptr().add(off) as *const linux_dirent64;
743                let reclen = (*ent).d_reclen as usize;
744                if reclen == 0 { break; }
745                let name = dbuf.0.as_ptr().add(off + name_off) as *const c_char;
746                off += reclen;
747                if *name == b'.' as c_char {
748                    let s = *name.add(1);
749                    if s == 0 || (s == b'.' as c_char && *name.add(2) == 0) { continue; }
750                }
751                let nl = crate::strlen(name);
752                if len + 1 + nl >= cap {
753                    ERRNO!() = 36;
754                    crate::close(dfd);
755                    return -1;
756                }
757                *buf.add(len) = b'/';
758                core::ptr::copy_nonoverlapping(name as *const u8, buf.add(len + 1), nl + 1);
759                let r = nftw_do(buf, cap, len + 1 + nl, cb, fd_limit - 1, flags, &mut new, parent_base, level + 1);
760                if r != 0 { crate::close(dfd); return r; }
761            }
762        }
763        crate::close(dfd);
764    } else if dfd >= 0 {
765        crate::close(dfd);
766    }
767    *buf.add(len) = 0;
768
769    if flags & FTW_DEPTH_V != 0 {
770        let mut f2 = Ftw { base: parent_base, level: level };
771        let r = walk_call(cb, path, &st, type_, &mut f2);
772        if r != 0 { return r; }
773    }
774    let _ = &mut new;
775    0
776}
777
778#[no_mangle]
779pub unsafe extern "C" fn nftw(
780    path: *const c_char,
781    f: Option<unsafe extern "C" fn(*const c_char, *const Stat, c_int, *mut Ftw) -> c_int>,
782    fd_limit: c_int,
783    flags: c_int,
784) -> c_int {
785    if path.is_null() || f.is_none() { ERRNO!() = EINVAL; return -1; }
786    if fd_limit <= 0 { return 0; }
787    let l = crate::strlen(path);
788    if l >= PATH_MAX_V { ERRNO!() = 36; return -1; }
789    let mut buf = [0u8; PATH_MAX_V + 1];
790    core::ptr::copy_nonoverlapping(path as *const u8, buf.as_mut_ptr(), l + 1);
791    nftw_do(buf.as_mut_ptr(), buf.len(), l, WalkCb::N(f), fd_limit, flags, core::ptr::null_mut(), 0, 0)
792}
793
794#[no_mangle]
795pub unsafe extern "C" fn ftw(
796    path: *const c_char,
797    f: Option<unsafe extern "C" fn(*const c_char, *const Stat, c_int) -> c_int>,
798    fd_limit: c_int,
799) -> c_int {
800    if path.is_null() || f.is_none() { ERRNO!() = EINVAL; return -1; }
801    if fd_limit <= 0 { return 0; }
802    let l = crate::strlen(path);
803    if l >= PATH_MAX_V { ERRNO!() = 36; return -1; }
804    let mut buf = [0u8; PATH_MAX_V + 1];
805    core::ptr::copy_nonoverlapping(path as *const u8, buf.as_mut_ptr(), l + 1);
806    nftw_do(buf.as_mut_ptr(), buf.len(), l, WalkCb::F(f), fd_limit, FTW_PHYS_V, core::ptr::null_mut(), 0, 0)
807}