1pub const FTS_COMFOLLOW_V: c_int = 0x001;
8pub const FTS_LOGICAL_V: c_int = 0x002;
9pub const FTS_NOCHDIR_V: c_int = 0x004;
10pub const FTS_NOSTAT_V: c_int = 0x008;
11pub const FTS_PHYSICAL_V: c_int = 0x010;
12pub const FTS_SEEDOT_V: c_int = 0x020;
13pub const FTS_XDEV_V: c_int = 0x040;
14
15pub const FTS_D_V: u16 = 1;
16pub const FTS_DC_V: u16 = 2;
17pub const FTS_DEFAULT_V: u16 = 3;
18pub const FTS_DNR_V: u16 = 4;
19pub const FTS_DOT_V: u16 = 5;
20pub const FTS_DP_V: u16 = 6;
21pub const FTS_ERR_V: u16 = 7;
22pub const FTS_F_V: u16 = 8;
23pub const FTS_INIT_V: u16 = 9;
24pub const FTS_NS_V: u16 = 10;
25pub const FTS_NSOK_V: u16 = 11;
26pub const FTS_SL_V: u16 = 12;
27pub const FTS_SLNONE_V: u16 = 13;
28pub const FTS_W_V: u16 = 14;
29
30pub const FTS_SKIP_V: c_int = 1;
31pub const FTS_FOLLOW_V: c_int = 2;
32pub const FTS_NOINSTR_V: c_int = 3;
33
34const FTS_NAME_OFF: usize = 112;
35
36#[repr(C)]
37pub struct FtsEnt {
38 pub fts_cycle: *mut FtsEnt,
39 pub fts_parent: *mut FtsEnt,
40 pub fts_link: *mut FtsEnt,
41 pub fts_number: c_long,
42 pub fts_pointer: *mut c_void,
43 pub fts_accpath: *mut c_char,
44 pub fts_path: *mut c_char,
45 pub fts_errno: c_int,
46 pub fts_symfd: c_int,
47 pub fts_pathlen: u16,
48 pub fts_namelen: u16,
49 pub fts_ino: u64,
50 pub fts_dev: u64,
51 pub fts_nlink: u64,
52 pub fts_level: u16,
53 pub fts_info: u16,
54 pub fts_flags: u16,
55 pub fts_instr: u16,
56 pub fts_statp: *mut Stat,
57 pub fts_name: [c_char; 1],
58}
59
60const _: [(); 120] = [(); core::mem::size_of::<FtsEnt>()];
61
62pub struct Fts {
63 roots: *mut *mut c_char,
64 nroots: usize,
65 options: c_int,
66 compar: Option<unsafe extern "C" fn(*const *const FtsEnt, *const *const FtsEnt) -> c_int>,
67 ents: *mut *mut FtsEnt,
68 nents: usize,
69 ents_cap: usize,
70 idx: usize,
71 built: bool,
72}
73
74unsafe fn fts_name_ptr(ent: *mut FtsEnt) -> *mut c_char {
75 (ent as *mut u8).add(FTS_NAME_OFF) as *mut c_char
76}
77
78unsafe fn fts_ent_alloc(
79 path: *const c_char,
80 name: *const c_char,
81 level: u16,
82 parent: *mut FtsEnt,
83) -> *mut FtsEnt {
84 let plen = crate::strlen(path);
85 let nlen = crate::strlen(name);
86 let namelen = nlen + 1;
87 let stat_off = (core::mem::size_of::<FtsEnt>() + namelen + 7) & !7usize;
88 let total = stat_off + core::mem::size_of::<Stat>();
89 let raw = crate::calloc(1, total) as *mut u8;
90 if raw.is_null() { return core::ptr::null_mut(); }
91 let ent = raw as *mut FtsEnt;
92 (*ent).fts_parent = parent;
93 (*ent).fts_level = level;
94 (*ent).fts_pathlen = plen as u16;
95 (*ent).fts_namelen = nlen as u16;
96 let pathbuf = crate::malloc(plen + 1) as *mut c_char;
97 if pathbuf.is_null() { crate::free(raw as *mut c_void); return core::ptr::null_mut(); }
98 core::ptr::copy_nonoverlapping(path, pathbuf, plen + 1);
99 (*ent).fts_path = pathbuf;
100 (*ent).fts_accpath = pathbuf;
101 core::ptr::copy_nonoverlapping(name, fts_name_ptr(ent), namelen);
102 (*ent).fts_statp = raw.add(stat_off) as *mut Stat;
103 ent
104}
105
106unsafe fn fts_push(fts: *mut Fts, ent: *mut FtsEnt) {
107 if ent.is_null() { return; }
108 if (*fts).nents == (*fts).ents_cap {
109 let ncap = if (*fts).ents_cap == 0 { 64 } else { (*fts).ents_cap * 2 };
110 let na = crate::realloc(
111 (*fts).ents as *mut c_void,
112 ncap * core::mem::size_of::<*mut FtsEnt>(),
113 ) as *mut *mut FtsEnt;
114 if na.is_null() { return; }
115 (*fts).ents = na;
116 (*fts).ents_cap = ncap;
117 }
118 *(*fts).ents.add((*fts).nents) = ent;
119 (*fts).nents += 1;
120 (*ent).fts_link = core::ptr::null_mut();
121}
122
123unsafe fn fts_free_ent(ent: *mut FtsEnt) {
124 if ent.is_null() { return; }
125 if !(*ent).fts_path.is_null() { crate::free((*ent).fts_path as *mut c_void); }
126 crate::free(ent as *mut c_void);
127}
128
129unsafe fn fts_name_of(path: *const c_char) -> *const c_char {
130 let n = crate::strlen(path);
131 let mut i = n;
132 while i > 0 {
133 if *path.add(i - 1) == b'/' as c_char { break; }
134 i -= 1;
135 }
136 path.add(i)
137}
138
139unsafe fn fts_join(dir: *const c_char, name: *const c_char) -> *mut c_char {
140 let dl = crate::strlen(dir);
141 let nl = crate::strlen(name);
142 let need_sep = dl > 0 && *dir.add(dl - 1) != b'/' as c_char;
143 let total = dl + if need_sep { 1 } else { 0 } + nl + 1;
144 let p = crate::malloc(total) as *mut c_char;
145 if p.is_null() { return core::ptr::null_mut(); }
146 core::ptr::copy_nonoverlapping(dir, p, dl);
147 let mut o = dl;
148 if need_sep { *p.add(o) = b'/' as c_char; o += 1; }
149 core::ptr::copy_nonoverlapping(name, p.add(o), nl + 1);
150 p
151}
152
153unsafe fn fts_is_dir(mode: u32) -> bool { mode & 0o170000 == 0o040000 }
154unsafe fn fts_is_link(mode: u32) -> bool { mode & 0o170000 == 0o120000 }
155
156unsafe fn fts_stat_path(path: *const c_char, follow: bool, st: *mut Stat) -> c_int {
157 if follow { crate::stat(path, st) } else { crate::lstat(path, st as *mut c_void) }
158}
159
160unsafe fn fts_collect_names(path: *const c_char) -> (*mut *mut c_char, usize) {
161 let fd = crate::open(path, crate::O_DIRECTORY, 0); if fd < 0 { return (core::ptr::null_mut(), 0); }
163 let mut cap = 16usize;
164 let mut count = 0usize;
165 let mut arr = crate::malloc(cap * core::mem::size_of::<*mut c_char>()) as *mut *mut c_char;
166 if arr.is_null() { crate::close(fd); return (core::ptr::null_mut(), 0); }
167 let mut buf = [0u8; 4096];
168 loop {
169 let n = <Arch as Syscalls>::syscall3(
170 SYS_GETDENTS64,
171 fd as i64,
172 buf.as_mut_ptr() as i64,
173 buf.len() as i64,
174 ) as isize;
175 if n <= 0 { break; }
176 let mut pos = 0usize;
177 while pos < n as usize {
178 let ent = buf.as_ptr().add(pos) as *const linux_dirent64;
179 let reclen = (*ent).d_reclen as usize;
180 if reclen == 0 { break; }
181 let namep = (ent as *const u8).add(core::mem::offset_of!(linux_dirent64, d_name)) as *const c_char;
182 let nl = crate::strlen(namep);
183 if count + 2 >= cap {
184 cap *= 2;
185 let na = crate::realloc(arr as *mut c_void, cap * core::mem::size_of::<*mut c_char>()) as *mut *mut c_char;
186 if na.is_null() { break; }
187 arr = na;
188 }
189 let copy = crate::malloc(nl + 1) as *mut c_char;
190 if copy.is_null() { break; }
191 core::ptr::copy_nonoverlapping(namep, copy, nl + 1);
192 *arr.add(count) = copy;
193 count += 1;
194 pos += reclen;
195 }
196 }
197 crate::close(fd);
198 (arr, count)
199}
200
201unsafe fn fts_free_names(arr: *mut *mut c_char, count: usize) {
202 if arr.is_null() { return; }
203 for i in 0..count {
204 if !(*arr.add(i)).is_null() { crate::free(*arr.add(i) as *mut c_void); }
205 }
206 crate::free(arr as *mut c_void);
207}
208
209unsafe fn fts_sort_entries(fts: *mut Fts, arr: *mut *mut FtsEnt, count: usize) {
210 let cmp = match (*fts).compar { Some(c) => c, None => return };
211 for i in 1..count {
212 let v = *arr.add(i);
213 let mut j = i;
214 while j > 0 {
215 let prev = *arr.add(j - 1);
216 let a = prev as *const FtsEnt;
217 let b = v as *const FtsEnt;
218 if cmp(&a, &b) <= 0 { break; }
219 *arr.add(j) = prev;
220 j -= 1;
221 }
222 *arr.add(j) = v;
223 }
224}
225
226unsafe fn fts_fill_info(ent: *mut FtsEnt, st: *mut Stat) -> u16 {
227 if (*ent).fts_errno != 0 { return FTS_NS_V; }
228 let mode = (*st).st_mode;
229 if fts_is_dir(mode) { FTS_D_V }
230 else if fts_is_link(mode) { FTS_SL_V }
231 else if mode & 0o170000 == 0o100000 { FTS_F_V }
232 else { FTS_DEFAULT_V }
233}
234
235unsafe fn fts_walk(fts: *mut Fts, ent: *mut FtsEnt) {
236 fts_push(fts, ent);
237 if (*ent).fts_level >= 64 || (*ent).fts_info != FTS_D_V { return; }
238 let mut p = (*ent).fts_parent;
239 while !p.is_null() {
240 if (*p).fts_info == FTS_D_V && (*p).fts_dev == (*ent).fts_dev && (*p).fts_ino == (*ent).fts_ino {
241 (*ent).fts_info = FTS_DC_V;
242 return;
243 }
244 p = (*p).fts_parent;
245 }
246
247 let (names, count) = fts_collect_names((*ent).fts_path);
248 if !names.is_null() {
249 let ptrsz = core::mem::size_of::<*mut c_char>();
250 let kids = crate::malloc((count + 1) * ptrsz) as *mut *mut FtsEnt;
251 if !kids.is_null() {
252 let mut nkids = 0usize;
253 for i in 0..count {
254 let name = *names.add(i);
255 if ((*fts).options & FTS_SEEDOT_V) == 0 {
256 if crate::strcmp(name as *const u8, b".\0".as_ptr()) == 0 { continue; }
257 if crate::strcmp(name as *const u8, b"..\0".as_ptr()) == 0 { continue; }
258 }
259 let full = fts_join((*ent).fts_path, name);
260 if full.is_null() { continue; }
261 let child = fts_ent_alloc(full, name, (*ent).fts_level + 1, ent);
262 crate::free(full as *mut c_void);
263 if child.is_null() { continue; }
264 let mut st: Stat = core::mem::zeroed();
265 let follow = (*fts).options & FTS_LOGICAL_V != 0;
266 let mut rc = fts_stat_path((*child).fts_path, follow, &mut st);
267 if rc != 0 && !follow {
268 rc = fts_stat_path((*child).fts_path, true, &mut st);
269 }
270 if rc != 0 {
271 (*child).fts_errno = ERRNO!();
272 (*child).fts_info = FTS_NS_V;
273 } else {
274 core::ptr::copy_nonoverlapping(&st as *const Stat as *const u8, (*child).fts_statp as *mut u8, core::mem::size_of::<Stat>());
275 (*child).fts_ino = st.st_ino;
276 (*child).fts_dev = st.st_dev;
277 (*child).fts_nlink = st.st_nlink as u64;
278 (*child).fts_info = fts_fill_info(child, &mut st);
279 }
280 *kids.add(nkids) = child;
281 nkids += 1;
282 }
283 fts_sort_entries(fts, kids, nkids);
284 for i in 0..nkids { fts_walk(fts, *kids.add(i)); }
285 crate::free(kids as *mut c_void);
286 }
287 fts_free_names(names, count);
288 }
289
290 let dp = fts_ent_alloc((*ent).fts_path, fts_name_ptr(ent), (*ent).fts_level, (*ent).fts_parent);
291 if !dp.is_null() {
292 (*dp).fts_info = FTS_DP_V;
293 (*dp).fts_dev = (*ent).fts_dev;
294 (*dp).fts_ino = (*ent).fts_ino;
295 fts_push(fts, dp);
296 }
297}
298
299unsafe fn fts_build(fts: *mut Fts) {
300 (*fts).built = true;
301 for i in 0..(*fts).nroots {
302 let root = *(*fts).roots.add(i);
303 if root.is_null() { continue; }
304 let mut st: Stat = core::mem::zeroed();
305 let follow = (*fts).options & (FTS_LOGICAL_V | FTS_COMFOLLOW_V) != 0;
306 let rc = fts_stat_path(root, follow, &mut st);
307 let ent = fts_ent_alloc(root, fts_name_of(root), 0, core::ptr::null_mut());
308 if ent.is_null() { continue; }
309 if rc != 0 {
310 (*ent).fts_errno = ERRNO!();
311 (*ent).fts_info = FTS_NS_V;
312 fts_push(fts, ent);
313 continue;
314 }
315 core::ptr::copy_nonoverlapping(&st as *const Stat as *const u8, (*ent).fts_statp as *mut u8, core::mem::size_of::<Stat>());
316 (*ent).fts_ino = st.st_ino;
317 (*ent).fts_dev = st.st_dev;
318 (*ent).fts_nlink = st.st_nlink as u64;
319 (*ent).fts_info = fts_fill_info(ent, &mut st);
320 fts_walk(fts, ent);
321 }
322}
323
324#[no_mangle]
325pub unsafe extern "C" fn fts_open(
326 path_argv: *mut *mut c_char,
327 options: c_int,
328 compar: Option<unsafe extern "C" fn(*const *const FtsEnt, *const *const FtsEnt) -> c_int>,
329) -> *mut Fts {
330 if path_argv.is_null() || (*path_argv).is_null() {
331 ERRNO!() = EINVAL;
332 return core::ptr::null_mut();
333 }
334 let fts = crate::calloc(1, core::mem::size_of::<Fts>()) as *mut Fts;
335 if fts.is_null() { ERRNO!() = ENOMEM; return core::ptr::null_mut(); }
336 let mut n = 0usize;
337 while !(*path_argv.add(n)).is_null() { n += 1; }
338 let roots = crate::malloc((n + 1) * core::mem::size_of::<*mut c_char>()) as *mut *mut c_char;
339 if roots.is_null() { crate::free(fts as *mut c_void); ERRNO!() = ENOMEM; return core::ptr::null_mut(); }
340 for i in 0..n {
341 let s = *path_argv.add(i);
342 let l = crate::strlen(s);
343 let copy = crate::malloc(l + 1) as *mut c_char;
344 if copy.is_null() { ERRNO!() = ENOMEM; crate::free(roots as *mut c_void); crate::free(fts as *mut c_void); return core::ptr::null_mut(); }
345 core::ptr::copy_nonoverlapping(s, copy, l + 1);
346 *roots.add(i) = copy;
347 }
348 *roots.add(n) = core::ptr::null_mut();
349 (*fts).roots = roots;
350 (*fts).nroots = n;
351 (*fts).options = options;
352 (*fts).compar = compar;
353 (*fts).ents = core::ptr::null_mut();
354 (*fts).nents = 0;
355 (*fts).ents_cap = 0;
356 (*fts).idx = 0;
357 (*fts).built = false;
358 fts
359}
360
361#[no_mangle]
362pub unsafe extern "C" fn fts_read(fts: *mut Fts) -> *mut FtsEnt {
363 if fts.is_null() { ERRNO!() = EINVAL; return core::ptr::null_mut(); }
364 if !(*fts).built { fts_build(fts); }
365 if (*fts).idx < (*fts).nents {
366 let e = *(*fts).ents.add((*fts).idx);
367 (*fts).idx += 1;
368 return e;
369 }
370 core::ptr::null_mut()
371}
372
373#[no_mangle]
374pub unsafe extern "C" fn fts_set(fts: *mut Fts, ent: *mut FtsEnt, instr: c_int) -> c_int {
375 if fts.is_null() || ent.is_null() { ERRNO!() = EINVAL; return -1; }
376 match instr {
377 FTS_SKIP_V => {
378 (*ent).fts_instr = FTS_SKIP_V as u16;
379 if (*ent).fts_info == FTS_D_V {
380 let lvl = (*ent).fts_level;
381 while (*fts).idx < (*fts).nents {
382 let e = *(*fts).ents.add((*fts).idx);
383 if (*e).fts_level < lvl { break; }
384 (*fts).idx += 1;
385 if (*e).fts_level == lvl && (*e).fts_info == FTS_DP_V { break; }
386 }
387 (*ent).fts_instr = FTS_SKIP_V as u16;
388 }
389 }
390 FTS_FOLLOW_V => {
391 (*ent).fts_instr = FTS_FOLLOW_V as u16;
392 if (*ent).fts_info == FTS_SL_V || (*ent).fts_info == FTS_SLNONE_V {
393 let mut st: Stat = core::mem::zeroed();
394 if crate::stat((*ent).fts_path, &mut st) == 0 {
395 core::ptr::copy_nonoverlapping(&st as *const Stat as *const u8, (*ent).fts_statp as *mut u8, core::mem::size_of::<Stat>());
396 (*ent).fts_ino = st.st_ino;
397 (*ent).fts_dev = st.st_dev;
398 (*ent).fts_nlink = st.st_nlink as u64;
399 (*ent).fts_info = fts_fill_info(ent, &mut st);
400 }
401 }
402 }
403 FTS_NOINSTR_V => { (*ent).fts_instr = FTS_NOINSTR_V as u16; }
404 _ => { ERRNO!() = EINVAL; return -1; }
405 }
406 0
407}
408
409#[no_mangle]
410pub unsafe extern "C" fn fts_children(fts: *mut Fts, _instr: c_int) -> *mut FtsEnt {
411 if fts.is_null() { ERRNO!() = EINVAL; return core::ptr::null_mut(); }
412 if !(*fts).built { fts_build(fts); }
413 if (*fts).idx == 0 { return core::ptr::null_mut(); }
414 let parent = *(*fts).ents.add((*fts).idx - 1);
415 if (*parent).fts_info != FTS_D_V { return core::ptr::null_mut(); }
416 let level = (*parent).fts_level + 1;
417 let mut head: *mut FtsEnt = core::ptr::null_mut();
418 let mut tail: *mut FtsEnt = core::ptr::null_mut();
419 let mut i = (*fts).idx;
420 while i < (*fts).nents {
421 let q = *(*fts).ents.add(i);
422 if (*q).fts_level < level { break; }
423 if (*q).fts_level == level && (*q).fts_info != FTS_DP_V {
424 (*q).fts_link = core::ptr::null_mut();
425 if head.is_null() { head = q; } else { (*tail).fts_link = q; }
426 tail = q;
427 }
428 i += 1;
429 }
430 head
431}
432
433#[no_mangle]
434pub unsafe extern "C" fn fts_close(fts: *mut Fts) -> c_int {
435 if fts.is_null() { ERRNO!() = EINVAL; return -1; }
436 for i in 0..(*fts).nents {
437 fts_free_ent(*(*fts).ents.add(i));
438 }
439 if !(*fts).ents.is_null() { crate::free((*fts).ents as *mut c_void); }
440 for i in 0..(*fts).nroots {
441 if !(*(*fts).roots.add(i)).is_null() { crate::free(*(*fts).roots.add(i) as *mut c_void); }
442 }
443 crate::free((*fts).roots as *mut c_void);
444 crate::free(fts as *mut c_void);
445 0
446}