relibc/header/glob/
mod.rs1use core::ptr;
6
7use alloc::{boxed::Box, vec::Vec};
8
9use crate::{
10 c_str::{CStr, CString},
11 header::{
12 dirent::{closedir, opendir, readdir},
13 errno::*,
14 fnmatch::{FNM_NOESCAPE, FNM_PERIOD, fnmatch},
15 sys_stat::{S_IFDIR, S_IFMT, stat},
16 },
17 platform::{
18 self,
19 types::{c_char, c_int, c_uchar, c_void, size_t},
20 },
21};
22
23pub const GLOB_ERR: c_int = 0x0001;
25pub const GLOB_MARK: c_int = 0x0002;
27pub const GLOB_NOSORT: c_int = 0x0004;
29pub const GLOB_DOOFFS: c_int = 0x0008;
31pub const GLOB_NOCHECK: c_int = 0x0010;
33pub const GLOB_APPEND: c_int = 0x0020;
35pub const GLOB_NOESCAPE: c_int = 0x0040;
37pub const GLOB_PERIOD: c_int = 0x0080;
39
40pub const GLOB_NOSPACE: c_int = 1;
42pub const GLOB_ABORTED: c_int = 2;
44pub const GLOB_NOMATCH: c_int = 3;
46
47#[derive(Debug)]
49#[repr(C)]
50pub struct glob_t {
51 pub gl_pathc: size_t, pub gl_offs: size_t, pub gl_pathv: *mut *mut c_char, __opaque: *mut c_void, }
58
59#[linkage = "weak"] #[unsafe(no_mangle)]
62pub unsafe extern "C" fn glob(
63 pattern: *const c_char,
64 flags: c_int,
65 errfunc: Option<unsafe extern "C" fn(epath: *const c_char, eerrno: c_int) -> c_int>,
66 pglob: *mut glob_t,
67) -> c_int {
68 if flags & GLOB_APPEND != GLOB_APPEND {
69 unsafe {
70 (*pglob).gl_pathc = 0;
71 (*pglob).gl_pathv = ptr::null_mut();
72 (*pglob).__opaque = ptr::null_mut();
73 }
74 }
75
76 let glob_expr = unsafe { CStr::from_ptr(pattern) };
77
78 if glob_expr.to_bytes() == b"" {
79 return GLOB_NOMATCH;
80 }
81
82 let base_path = unsafe {
83 CStr::from_bytes_with_nul_unchecked(if glob_expr.to_bytes().first() == Some(&b'/') {
84 b"/\0"
85 } else {
86 b"\0"
87 })
88 };
89
90 let mut pathv: Box<Vec<*mut c_char>>;
91 if flags & GLOB_APPEND == GLOB_APPEND {
92 pathv = unsafe { Box::from_raw((*pglob).__opaque.cast()) };
93 pathv.pop(); } else {
95 pathv = Box::new(Vec::new());
96 if flags & GLOB_DOOFFS == GLOB_DOOFFS {
97 let gl_offs = unsafe { (*pglob).gl_offs };
98 pathv.reserve(gl_offs);
99 for _ in 0..gl_offs {
100 pathv.push(ptr::null_mut());
101 }
102 }
103 }
104
105 let errfunc = match errfunc {
106 Some(f) => f,
107 None => default_errfunc,
108 };
109
110 let return_value = match inner_glob(&base_path, &glob_expr, flags, errfunc) {
112 Ok(mut results) => {
113 if results.is_empty() && flags & GLOB_NOCHECK != GLOB_NOCHECK {
115 GLOB_NOMATCH
116 } else {
117 if results.is_empty() {
118 results.push(glob_expr.to_owned_cstring());
119 }
120
121 if flags & GLOB_NOSORT != GLOB_NOSORT {
123 results.sort();
124 }
125
126 unsafe {
127 (*pglob).gl_pathc += results.len();
128 }
129
130 pathv.reserve_exact(results.len());
131 pathv.extend(results.into_iter().map(|s| s.into_raw()));
132
133 0
134 }
135 }
136 Err(e) => e,
137 };
138
139 pathv.reserve_exact(1);
141 pathv.push(ptr::null_mut());
142 unsafe {
143 (*pglob).gl_pathv = pathv.as_ptr().cast_mut();
144 (*pglob).__opaque = Box::into_raw(pathv).cast();
145 }
146
147 return_value
148}
149
150#[linkage = "weak"] #[unsafe(no_mangle)]
153pub unsafe extern "C" fn globfree(pglob: *mut glob_t) {
154 if unsafe { !(*pglob).__opaque.is_null() } {
156 let pathv: Box<Vec<*mut c_char>> = unsafe { Box::from_raw((*pglob).__opaque.cast()) };
157 for (idx, path) in pathv.into_iter().enumerate() {
158 if unsafe { idx < (*pglob).gl_offs } {
159 continue;
160 }
161 if !path.is_null() {
162 unsafe {
163 drop(CString::from_raw(path));
164 }
165 }
166 }
167 unsafe {
168 (*pglob).gl_pathv = ptr::null_mut();
169 }
170 }
171}
172
173type GlobErrorFunc = unsafe extern "C" fn(epath: *const c_char, eerrno: c_int) -> c_int;
174
175struct DirEntry {
176 name: CString,
177 is_dir: bool,
178}
179
180unsafe extern "C" fn default_errfunc(epath: *const c_char, eerrno: c_int) -> c_int {
181 0
182}
183
184fn list_dir(
185 path: &CStr,
186 errfunc: GlobErrorFunc,
187 abort_on_error: bool,
188) -> Result<Vec<DirEntry>, c_int> {
189 const DT_DIR: c_uchar = 4; const DT_LNK: c_uchar = 10; let old_errno = platform::ERRNO.get();
193 let mut results: Vec<DirEntry> = Vec::new();
194 let open_path = if path.to_bytes().is_empty() {
195 unsafe { &CStr::from_bytes_with_nul_unchecked(b".\0") }
196 } else {
197 path
198 };
199 let dir = unsafe { opendir(open_path.as_ptr()) };
200
201 if dir.is_null() {
202 let new_errno = platform::ERRNO.get();
203 platform::ERRNO.set(old_errno);
204
205 if unsafe { errfunc(path.as_ptr(), new_errno) } != 0 || abort_on_error {
206 return Err(GLOB_ABORTED);
207 }
208
209 return Ok(results);
210 }
211
212 platform::ERRNO.set(0);
213
214 loop {
215 let entry = unsafe { readdir(&mut *dir) };
216 if entry.is_null() {
217 break;
218 }
219
220 let name = unsafe { CStr::from_ptr((*entry).d_name.as_ptr()).to_owned_cstring() };
221
222 if name.as_bytes() == b"." || name.as_bytes() == b".." {
223 continue;
224 }
225
226 let is_dir: bool = unsafe {
227 if (*entry).d_type == DT_DIR {
228 true
229 } else if (*entry).d_type == DT_LNK {
230 let mut full_path = path.to_bytes().to_vec();
232 if !full_path.ends_with(b"/") {
233 full_path.push(b'/');
234 }
235 full_path.extend_from_slice(name.as_bytes());
236 let full_path = CString::new(full_path).unwrap();
237
238 let mut link_info = stat::default();
239 if stat(full_path.as_ptr(), ptr::from_mut(&mut link_info)) != 0 {
240 let errno = platform::ERRNO.get();
241 platform::ERRNO.set(old_errno);
242 if errfunc(full_path.as_ptr(), errno) != 0 || abort_on_error {
243 return Err(GLOB_ABORTED);
244 }
245 }
246 link_info.st_mode & S_IFMT == S_IFDIR
247 } else {
248 false
249 }
250 };
251
252 results.push(DirEntry { name, is_dir });
253 }
254
255 let errno = platform::ERRNO.get();
257
258 unsafe { closedir(Box::from_raw(dir)) };
259
260 platform::ERRNO.set(old_errno);
262
263 if errno != 0 && (unsafe { errfunc(path.as_ptr(), errno) } != 0 || abort_on_error) {
264 return Err(GLOB_ABORTED);
265 }
266
267 Ok(results)
268}
269
270fn inner_glob(
271 current_dir: &CStr,
272 glob_expr: &CStr,
273 flags: c_int,
274 errfunc: GlobErrorFunc,
275) -> Result<Vec<CString>, c_int> {
276 let mut pattern: Vec<u8> = Vec::new();
277
278 let glob_expr = {
280 let mut expr = glob_expr.to_bytes_with_nul();
281 while expr.first() == Some(&b'/') {
282 expr = &expr[1..];
283 }
284 unsafe { CStr::from_bytes_with_nul_unchecked(expr) }
285 };
286
287 let glob_iter = glob_expr.to_bytes();
289 let mut in_bracket = false;
290 let mut escaped = false;
291 let mut glob_consumed = 0;
292
293 for ch in glob_iter {
294 if ch == &b'\0' {
296 break;
297 }
298
299 glob_consumed += 1;
300
301 if ch == &b'/' && !escaped {
302 break;
303 }
304
305 if ch == &b'[' && !escaped {
306 in_bracket = true;
307 } else if ch == &b']' {
308 in_bracket = false;
310 }
311
312 escaped =
313 ch == &b'\\' && !in_bracket && !escaped && (flags & GLOB_NOESCAPE != GLOB_NOESCAPE);
314
315 pattern.push(*ch);
316 }
317
318 pattern.push(b'\0');
320
321 let new_glob_expr = unsafe {
322 CStr::from_bytes_with_nul_unchecked(&glob_expr.to_bytes_with_nul()[glob_consumed..])
323 };
324
325 if pattern == b".\0" || pattern == b"..\0" {
327 let mut new_dir: Vec<u8> = Vec::new();
328 new_dir.extend_from_slice(current_dir.to_bytes());
329 new_dir.extend_from_slice(&pattern);
330 let new_dir_c = unsafe { CStr::from_bytes_with_nul_unchecked(&new_dir) };
331 return inner_glob(&new_dir_c, &new_glob_expr, flags, errfunc);
332 }
333
334 let mut fnmatch_flags = 0;
335 if flags & GLOB_NOESCAPE == GLOB_NOESCAPE {
336 fnmatch_flags |= FNM_NOESCAPE;
337 }
338 if flags & GLOB_PERIOD == GLOB_PERIOD {
339 fnmatch_flags |= FNM_PERIOD;
340 }
341
342 let mut matches: Vec<CString> = Vec::new();
343
344 for entry in list_dir(current_dir, errfunc, flags & GLOB_ERR == GLOB_ERR)? {
345 if !new_glob_expr.to_bytes().is_empty() && !entry.is_dir {
347 continue;
348 }
349
350 let mut path = current_dir.to_bytes().to_vec();
351
352 if path != b"" && !path.ends_with(b"/") {
353 path.push(b'/');
354 }
355 path.extend_from_slice(entry.name.as_bytes());
356
357 if flags & GLOB_MARK == GLOB_MARK && new_glob_expr.to_bytes() == b"" && entry.is_dir {
358 path.push(b'/');
359 }
360
361 let path = CString::new(path).unwrap();
363
364 if unsafe {
365 fnmatch(
366 pattern.as_ptr().cast::<c_char>(),
367 entry.name.as_ptr(),
368 fnmatch_flags,
369 )
370 } == 0
371 {
372 if entry.is_dir && new_glob_expr.to_bytes() != b"" {
373 let new_matches = inner_glob(&CStr::borrow(&path), &new_glob_expr, flags, errfunc)?;
374 matches.extend(new_matches);
375 } else {
376 matches.push(path);
377 }
378 }
379 }
380
381 if matches.is_empty() && !new_glob_expr.to_bytes().is_empty() {
383 let mut path = current_dir.to_bytes().to_vec();
384 path.extend_from_slice(&pattern);
385 if unsafe { errfunc(path.as_ptr().cast::<c_char>(), ENOENT) } != 0
386 || flags & GLOB_ERR == GLOB_ERR
387 {
388 return Err(GLOB_ABORTED);
389 }
390 }
391
392 Ok(matches)
393}