1use std::ffi::CString;
2use std::fmt::Write as FmtWrite;
3use std::io;
4use std::os::unix::fs::{FileTypeExt, MetadataExt};
5
6pub struct StatConfig {
8 pub dereference: bool,
9 pub filesystem: bool,
10 pub format: Option<String>,
11 pub printf_format: Option<String>,
12 pub terse: bool,
13}
14
15fn extract_fsid(fsid: &libc::fsid_t) -> u64 {
17 let bytes: [u8; std::mem::size_of::<libc::fsid_t>()] =
19 unsafe { std::mem::transmute_copy(fsid) };
20 let val0 = u32::from_ne_bytes(bytes[0..4].try_into().unwrap()) as u64;
22 let val1 = u32::from_ne_bytes(bytes[4..8].try_into().unwrap()) as u64;
23 (val0 << 32) | val1
25}
26
27fn raw_stat(path: &str, dereference: bool) -> Result<libc::stat, io::Error> {
29 let c_path = CString::new(path)
30 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid path"))?;
31 unsafe {
32 let mut st: libc::stat = std::mem::zeroed();
33 let rc = if dereference {
34 libc::stat(c_path.as_ptr(), &mut st)
35 } else {
36 libc::lstat(c_path.as_ptr(), &mut st)
37 };
38 if rc != 0 {
39 Err(io::Error::last_os_error())
40 } else {
41 Ok(st)
42 }
43 }
44}
45
46fn raw_fstat(fd: i32) -> Result<libc::stat, io::Error> {
48 unsafe {
49 let mut st: libc::stat = std::mem::zeroed();
50 let rc = libc::fstat(fd, &mut st);
51 if rc != 0 {
52 Err(io::Error::last_os_error())
53 } else {
54 Ok(st)
55 }
56 }
57}
58
59fn raw_statfs(path: &str) -> Result<libc::statfs, io::Error> {
61 let c_path = CString::new(path)
62 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid path"))?;
63 unsafe {
64 let mut sfs: libc::statfs = std::mem::zeroed();
65 let rc = libc::statfs(c_path.as_ptr(), &mut sfs);
66 if rc != 0 {
67 Err(io::Error::last_os_error())
68 } else {
69 Ok(sfs)
70 }
71 }
72}
73
74pub fn stat_file(path: &str, config: &StatConfig) -> Result<String, io::Error> {
78 if path == "-" {
79 if config.filesystem {
80 return Err(io::Error::new(
81 io::ErrorKind::InvalidInput,
82 "using '-' to denote standard input does not work in file system mode",
83 ));
84 }
85 return stat_stdin(config);
86 }
87 if config.filesystem {
88 stat_filesystem(path, config)
89 } else {
90 stat_regular(path, config)
91 }
92}
93
94fn stat_stdin(config: &StatConfig) -> Result<String, io::Error> {
99 let st = raw_fstat(0)?;
101
102 let f = std::mem::ManuallyDrop::new(unsafe {
104 <std::fs::File as std::os::unix::io::FromRawFd>::from_raw_fd(0)
105 });
106 let meta = f.metadata()?;
107
108 let display_path = "-";
109
110 if let Some(ref fmt) = config.printf_format {
111 let expanded = expand_backslash_escapes(fmt);
112 return Ok(format_file_specifiers(
113 &expanded,
114 display_path,
115 &meta,
116 &st,
117 config.dereference,
118 ));
119 }
120
121 if let Some(ref fmt) = config.format {
122 let result = format_file_specifiers(fmt, display_path, &meta, &st, config.dereference);
123 return Ok(result + "\n");
124 }
125
126 if config.terse {
127 return Ok(format_file_terse(
128 display_path,
129 &meta,
130 &st,
131 config.dereference,
132 ));
133 }
134
135 Ok(format_file_default(
136 display_path,
137 &meta,
138 &st,
139 config.dereference,
140 ))
141}
142
143fn stat_regular(path: &str, config: &StatConfig) -> Result<String, io::Error> {
148 let meta = if config.dereference {
149 std::fs::metadata(path)?
150 } else {
151 std::fs::symlink_metadata(path)?
152 };
153 let st = raw_stat(path, config.dereference)?;
154
155 if let Some(ref fmt) = config.printf_format {
156 let expanded = expand_backslash_escapes(fmt);
157 return Ok(format_file_specifiers(
158 &expanded,
159 path,
160 &meta,
161 &st,
162 config.dereference,
163 ));
164 }
165
166 if let Some(ref fmt) = config.format {
167 let result = format_file_specifiers(fmt, path, &meta, &st, config.dereference);
168 return Ok(result + "\n");
169 }
170
171 if config.terse {
172 return Ok(format_file_terse(path, &meta, &st, config.dereference));
173 }
174
175 Ok(format_file_default(path, &meta, &st, config.dereference))
176}
177
178fn stat_filesystem(path: &str, config: &StatConfig) -> Result<String, io::Error> {
183 let sfs = raw_statfs(path)?;
184
185 if let Some(ref fmt) = config.printf_format {
186 let expanded = expand_backslash_escapes(fmt);
187 return Ok(format_fs_specifiers(&expanded, path, &sfs));
188 }
189
190 if let Some(ref fmt) = config.format {
191 let result = format_fs_specifiers(fmt, path, &sfs);
192 return Ok(result + "\n");
193 }
194
195 if config.terse {
196 return Ok(format_fs_terse(path, &sfs));
197 }
198
199 Ok(format_fs_default(path, &sfs))
200}
201
202fn format_file_default(
207 path: &str,
208 meta: &std::fs::Metadata,
209 st: &libc::stat,
210 dereference: bool,
211) -> String {
212 let mode = meta.mode();
213 let file_type_str = file_type_label(mode);
214 let perms_str = mode_to_human(mode);
215 let uid = meta.uid();
216 let gid = meta.gid();
217 let uname = lookup_username(uid);
218 let gname = lookup_groupname(gid);
219 let dev = meta.dev();
220 let dev_major = major(dev);
221 let dev_minor = minor(dev);
222
223 let name_display = if meta.file_type().is_symlink() {
224 match std::fs::read_link(path) {
225 Ok(target) => format!("{} -> {}", path, target.display()),
226 Err(_) => path.to_string(),
227 }
228 } else {
229 path.to_string()
230 };
231
232 let size_line = if meta.file_type().is_block_device() || meta.file_type().is_char_device() {
233 let rdev = meta.rdev();
234 let rmaj = major(rdev);
235 let rmin = minor(rdev);
236 format!(
237 " Size: {:<10}\tBlocks: {:<10} IO Block: {:<6} {}",
238 format!("{}, {}", rmaj, rmin),
239 meta.blocks(),
240 meta.blksize(),
241 file_type_str
242 )
243 } else {
244 format!(
245 " Size: {:<10}\tBlocks: {:<10} IO Block: {:<6} {}",
246 meta.size(),
247 meta.blocks(),
248 meta.blksize(),
249 file_type_str
250 )
251 };
252
253 let device_line = format!(
254 "Device: {},{}\tInode: {:<11} Links: {}",
255 dev_major,
256 dev_minor,
257 meta.ino(),
258 meta.nlink()
259 );
260
261 let access_line = format!(
262 "Access: ({:04o}/{}) Uid: ({:5}/{:>8}) Gid: ({:5}/{:>8})",
263 mode & 0o7777,
264 perms_str,
265 uid,
266 uname,
267 gid,
268 gname
269 );
270
271 let atime = format_timestamp(st.st_atime, st.st_atime_nsec);
272 let mtime = format_timestamp(st.st_mtime, st.st_mtime_nsec);
273 let ctime = format_timestamp(st.st_ctime, st.st_ctime_nsec);
274 let birth = format_birth_time_for_path(path, dereference);
275
276 format!(
277 " File: {}\n{}\n{}\n{}\nAccess: {}\nModify: {}\nChange: {}\n Birth: {}\n",
278 name_display, size_line, device_line, access_line, atime, mtime, ctime, birth
279 )
280}
281
282fn format_file_terse(
287 path: &str,
288 meta: &std::fs::Metadata,
289 st: &libc::stat,
290 dereference: bool,
291) -> String {
292 let dev = meta.dev();
293 let birth_secs = get_birth_time(path, dereference)
294 .map(|(s, _)| s)
295 .unwrap_or(0);
296 format!(
297 "{} {} {} {:x} {} {} {:x} {} {} {:x} {:x} {} {} {} {} {}\n",
298 path,
299 meta.size(),
300 meta.blocks(),
301 meta.mode(),
302 meta.uid(),
303 meta.gid(),
304 dev,
305 meta.ino(),
306 meta.nlink(),
307 major(meta.rdev()),
308 minor(meta.rdev()),
309 st.st_atime,
310 st.st_mtime,
311 st.st_ctime,
312 birth_secs,
313 meta.blksize()
314 )
315}
316
317fn format_fs_default(path: &str, sfs: &libc::statfs) -> String {
322 #[cfg(target_os = "linux")]
323 let fs_type = sfs.f_type;
324 #[cfg(not(target_os = "linux"))]
325 let fs_type = 0u32;
326 let fs_type_name = fs_type_name(fs_type as u64);
327 let fsid = sfs.f_fsid;
328 let fsid_val = extract_fsid(&fsid);
329
330 #[cfg(target_os = "linux")]
331 let namelen = sfs.f_namelen;
332 #[cfg(not(target_os = "linux"))]
333 let namelen = 255i64; #[cfg(target_os = "linux")]
336 let frsize = sfs.f_frsize;
337 #[cfg(not(target_os = "linux"))]
338 let frsize = sfs.f_bsize as u64; format!(
341 " File: \"{}\"\n ID: {:x} Namelen: {} Type: {}\nBlock size: {:<10} Fundamental block size: {}\nBlocks: Total: {:<10} Free: {:<10} Available: {}\nInodes: Total: {:<10} Free: {}\n",
342 path,
343 fsid_val,
344 namelen,
345 fs_type_name,
346 sfs.f_bsize,
347 frsize,
348 sfs.f_blocks,
349 sfs.f_bfree,
350 sfs.f_bavail,
351 sfs.f_files,
352 sfs.f_ffree
353 )
354}
355
356fn format_fs_terse(path: &str, sfs: &libc::statfs) -> String {
361 let fsid = sfs.f_fsid;
362 let fsid_val = extract_fsid(&fsid);
363
364 #[cfg(target_os = "linux")]
365 let namelen = sfs.f_namelen;
366 #[cfg(not(target_os = "linux"))]
367 let namelen = 255i64;
368
369 #[cfg(target_os = "linux")]
370 let fs_type = sfs.f_type;
371 #[cfg(not(target_os = "linux"))]
372 let fs_type = 0u32; #[cfg(target_os = "linux")]
375 let frsize = sfs.f_frsize;
376 #[cfg(not(target_os = "linux"))]
377 let frsize = sfs.f_bsize as u64;
378
379 format!(
380 "{} {} {} {} {} {} {} {} {} {} {} {}\n",
381 path,
382 fsid_val,
383 namelen,
384 fs_type,
385 sfs.f_bsize,
386 frsize,
387 sfs.f_blocks,
388 sfs.f_bfree,
389 sfs.f_bavail,
390 sfs.f_files,
391 sfs.f_ffree,
392 0 )
394}
395
396fn format_file_specifiers(
401 fmt: &str,
402 path: &str,
403 meta: &std::fs::Metadata,
404 st: &libc::stat,
405 dereference: bool,
406) -> String {
407 let mut result = String::new();
408 let bytes = fmt.as_bytes();
409 let len = bytes.len();
410 let mut i = 0;
411
412 while i < len {
413 if bytes[i] == b'%' && i + 1 < len {
414 i += 1;
415
416 if bytes[i] == b'H' || bytes[i] == b'L' {
419 if i + 1 >= len {
420 result.push('?');
421 i += 1;
422 continue;
423 }
424 let modifier = bytes[i];
425 let spec = bytes[i + 1];
426 match (modifier, spec) {
427 (b'H', b'd') => {
428 let _ = write!(result, "{}", major(meta.dev()));
429 i += 2;
430 continue;
431 }
432 (b'L', b'd') => {
433 let _ = write!(result, "{}", minor(meta.dev()));
434 i += 2;
435 continue;
436 }
437 (b'H', b'r') => {
438 let _ = write!(result, "{}", major(meta.rdev()));
439 i += 2;
440 continue;
441 }
442 (b'L', b'r') => {
443 let _ = write!(result, "{}", minor(meta.rdev()));
444 i += 2;
445 continue;
446 }
447 (_, _) => {
448 result.push('?');
449 result.push(spec as char);
450 i += 2;
451 continue;
452 }
453 }
454 }
455
456 match bytes[i] {
457 b'a' => {
458 let _ = write!(result, "{:o}", meta.mode() & 0o7777);
459 }
460 b'A' => {
461 result.push_str(&mode_to_human(meta.mode()));
462 }
463 b'b' => {
464 let _ = write!(result, "{}", meta.blocks());
465 }
466 b'B' => {
467 result.push_str("512");
468 }
469 b'd' => {
470 let _ = write!(result, "{}", meta.dev());
471 }
472 b'D' => {
473 let _ = write!(result, "{:x}", meta.dev());
474 }
475 b'f' => {
476 let _ = write!(result, "{:x}", meta.mode());
477 }
478 b'F' => {
479 result.push_str(file_type_label(meta.mode()));
480 }
481 b'g' => {
482 let _ = write!(result, "{}", meta.gid());
483 }
484 b'G' => {
485 result.push_str(&lookup_groupname(meta.gid()));
486 }
487 b'h' => {
488 let _ = write!(result, "{}", meta.nlink());
489 }
490 b'i' => {
491 let _ = write!(result, "{}", meta.ino());
492 }
493 b'm' => {
494 result.push_str(&find_mount_point(path));
495 }
496 b'n' => {
497 result.push_str(path);
498 }
499 b'N' => {
500 if meta.file_type().is_symlink() {
501 match std::fs::read_link(path) {
502 Ok(target) => {
503 result.push('\'');
504 result.push_str(path);
505 result.push_str("' -> '");
506 let _ = write!(result, "{}", target.display());
507 result.push('\'');
508 }
509 Err(_) => {
510 result.push('\'');
511 result.push_str(path);
512 result.push('\'');
513 }
514 }
515 } else {
516 result.push('\'');
517 result.push_str(path);
518 result.push('\'');
519 }
520 }
521 b'o' => {
522 let _ = write!(result, "{}", meta.blksize());
523 }
524 b's' => {
525 let _ = write!(result, "{}", meta.size());
526 }
527 b't' => {
528 let _ = write!(result, "{:x}", major(meta.rdev()));
529 }
530 b'T' => {
531 let _ = write!(result, "{:x}", minor(meta.rdev()));
532 }
533 b'u' => {
534 let _ = write!(result, "{}", meta.uid());
535 }
536 b'U' => {
537 result.push_str(&lookup_username(meta.uid()));
538 }
539 b'w' => {
540 result.push_str(&format_birth_time_for_path(path, dereference));
541 }
542 b'W' => {
543 result.push_str(&format_birth_seconds_for_path(path, dereference));
544 }
545 b'x' => {
546 result.push_str(&format_timestamp(st.st_atime, st.st_atime_nsec));
547 }
548 b'X' => {
549 let _ = write!(result, "{}", st.st_atime);
550 }
551 b'y' => {
552 result.push_str(&format_timestamp(st.st_mtime, st.st_mtime_nsec));
553 }
554 b'Y' => {
555 let _ = write!(result, "{}", st.st_mtime);
556 }
557 b'z' => {
558 result.push_str(&format_timestamp(st.st_ctime, st.st_ctime_nsec));
559 }
560 b'Z' => {
561 let _ = write!(result, "{}", st.st_ctime);
562 }
563 b'%' => {
564 result.push('%');
565 }
566 other => {
567 result.push('%');
568 result.push(other as char);
569 }
570 }
571 } else {
572 let b = bytes[i];
574 if b < 0x80 {
575 result.push(b as char);
576 } else {
577 let char_len = if b < 0xE0 {
578 2
579 } else if b < 0xF0 {
580 3
581 } else {
582 4
583 };
584 let end = (i + char_len).min(len);
585 if let Ok(s) = std::str::from_utf8(&bytes[i..end]) {
586 result.push_str(s);
587 i = end;
588 continue;
589 } else {
590 result.push(b as char);
591 }
592 }
593 }
594 i += 1;
595 }
596
597 result
598}
599
600fn format_fs_specifiers(fmt: &str, path: &str, sfs: &libc::statfs) -> String {
605 let mut result = String::new();
606 let bytes = fmt.as_bytes();
607 let len = bytes.len();
608 let mut i = 0;
609 let fsid_val = extract_fsid(&sfs.f_fsid);
610
611 while i < len {
612 if bytes[i] == b'%' && i + 1 < len {
613 i += 1;
614 match bytes[i] {
615 b'a' => {
616 let _ = write!(result, "{}", sfs.f_bavail);
617 }
618 b'b' => {
619 let _ = write!(result, "{}", sfs.f_blocks);
620 }
621 b'c' => {
622 let _ = write!(result, "{}", sfs.f_files);
623 }
624 b'd' => {
625 let _ = write!(result, "{}", sfs.f_ffree);
626 }
627 b'f' => {
628 let _ = write!(result, "{}", sfs.f_bfree);
629 }
630 b'i' => {
631 let _ = write!(result, "{:x}", fsid_val);
632 }
633 b'l' => {
634 #[cfg(target_os = "linux")]
635 let _ = write!(result, "{}", sfs.f_namelen);
636 #[cfg(not(target_os = "linux"))]
637 result.push_str("255");
638 }
639 b'n' => {
640 result.push_str(path);
641 }
642 b's' => {
643 let _ = write!(result, "{}", sfs.f_bsize);
644 }
645 b'S' => {
646 #[cfg(target_os = "linux")]
647 let _ = write!(result, "{}", sfs.f_frsize);
648 #[cfg(not(target_os = "linux"))]
649 let _ = write!(result, "{}", sfs.f_bsize);
650 }
651 b't' => {
652 #[cfg(target_os = "linux")]
653 let _ = write!(result, "{:x}", sfs.f_type);
654 #[cfg(not(target_os = "linux"))]
655 result.push('0');
656 }
657 b'T' => {
658 #[cfg(target_os = "linux")]
659 result.push_str(fs_type_name(sfs.f_type as u64));
660 #[cfg(not(target_os = "linux"))]
661 result.push_str("unknown");
662 }
663 b'%' => {
664 result.push('%');
665 }
666 other => {
667 result.push('%');
668 result.push(other as char);
669 }
670 }
671 } else {
672 let b = bytes[i];
674 if b < 0x80 {
675 result.push(b as char);
676 } else {
677 let char_len = if b < 0xE0 {
678 2
679 } else if b < 0xF0 {
680 3
681 } else {
682 4
683 };
684 let end = (i + char_len).min(len);
685 if let Ok(s) = std::str::from_utf8(&bytes[i..end]) {
686 result.push_str(s);
687 i = end;
688 continue;
689 } else {
690 result.push(b as char);
691 }
692 }
693 }
694 i += 1;
695 }
696
697 result
698}
699
700pub fn mode_to_human(mode: u32) -> String {
706 let file_char = match mode & (libc::S_IFMT as u32) {
707 m if m == libc::S_IFREG as u32 => '-',
708 m if m == libc::S_IFDIR as u32 => 'd',
709 m if m == libc::S_IFLNK as u32 => 'l',
710 m if m == libc::S_IFBLK as u32 => 'b',
711 m if m == libc::S_IFCHR as u32 => 'c',
712 m if m == libc::S_IFIFO as u32 => 'p',
713 m if m == libc::S_IFSOCK as u32 => 's',
714 _ => '?',
715 };
716
717 let mut s = String::with_capacity(10);
718 s.push(file_char);
719
720 s.push(if mode & 0o400 != 0 { 'r' } else { '-' });
722 s.push(if mode & 0o200 != 0 { 'w' } else { '-' });
723 s.push(if mode & (libc::S_ISUID as u32) != 0 {
724 if mode & 0o100 != 0 { 's' } else { 'S' }
725 } else if mode & 0o100 != 0 {
726 'x'
727 } else {
728 '-'
729 });
730
731 s.push(if mode & 0o040 != 0 { 'r' } else { '-' });
733 s.push(if mode & 0o020 != 0 { 'w' } else { '-' });
734 s.push(if mode & (libc::S_ISGID as u32) != 0 {
735 if mode & 0o010 != 0 { 's' } else { 'S' }
736 } else if mode & 0o010 != 0 {
737 'x'
738 } else {
739 '-'
740 });
741
742 s.push(if mode & 0o004 != 0 { 'r' } else { '-' });
744 s.push(if mode & 0o002 != 0 { 'w' } else { '-' });
745 s.push(if mode & (libc::S_ISVTX as u32) != 0 {
746 if mode & 0o001 != 0 { 't' } else { 'T' }
747 } else if mode & 0o001 != 0 {
748 'x'
749 } else {
750 '-'
751 });
752
753 s
754}
755
756pub fn file_type_label(mode: u32) -> &'static str {
758 match mode & (libc::S_IFMT as u32) {
759 m if m == libc::S_IFREG as u32 => "regular file",
760 m if m == libc::S_IFDIR as u32 => "directory",
761 m if m == libc::S_IFLNK as u32 => "symbolic link",
762 m if m == libc::S_IFBLK as u32 => "block special file",
763 m if m == libc::S_IFCHR as u32 => "character special file",
764 m if m == libc::S_IFIFO as u32 => "fifo",
765 m if m == libc::S_IFSOCK as u32 => "socket",
766 _ => "unknown",
767 }
768}
769
770fn format_timestamp(secs: i64, nsec: i64) -> String {
772 let t = secs as libc::time_t;
774 let mut tm: libc::tm = unsafe { std::mem::zeroed() };
775 unsafe {
776 libc::localtime_r(&t, &mut tm);
777 }
778
779 let offset_secs = tm.tm_gmtoff;
780 let offset_sign = if offset_secs >= 0 { '+' } else { '-' };
781 let offset_abs = offset_secs.unsigned_abs();
782 let offset_hours = offset_abs / 3600;
783 let offset_mins = (offset_abs % 3600) / 60;
784
785 let mut s = String::with_capacity(36);
787 let _ = write!(
788 s,
789 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:09} {}{:02}{:02}",
790 tm.tm_year + 1900,
791 tm.tm_mon + 1,
792 tm.tm_mday,
793 tm.tm_hour,
794 tm.tm_min,
795 tm.tm_sec,
796 nsec,
797 offset_sign,
798 offset_hours,
799 offset_mins
800 );
801 s
802}
803
804#[cfg(target_os = "linux")]
806fn get_birth_time(path: &str, dereference: bool) -> Option<(i64, i64)> {
807 use std::mem::MaybeUninit;
808
809 let c_path = CString::new(path).ok()?;
810 unsafe {
811 let mut statx_buf: libc::statx = MaybeUninit::zeroed().assume_init();
812 let flags = if dereference {
813 0
814 } else {
815 libc::AT_SYMLINK_NOFOLLOW
816 };
817 let rc = libc::statx(
818 libc::AT_FDCWD,
819 c_path.as_ptr(),
820 flags,
821 libc::STATX_BTIME,
822 &mut statx_buf,
823 );
824 if rc == 0 && (statx_buf.stx_mask & libc::STATX_BTIME) != 0 {
825 Some((
826 statx_buf.stx_btime.tv_sec,
827 statx_buf.stx_btime.tv_nsec as i64,
828 ))
829 } else {
830 None
831 }
832 }
833}
834
835#[cfg(not(target_os = "linux"))]
836fn get_birth_time(_path: &str, _dereference: bool) -> Option<(i64, i64)> {
837 None
838}
839
840fn format_birth_time_for_path(path: &str, dereference: bool) -> String {
842 if let Some((secs, nsec)) = get_birth_time(path, dereference) {
843 format_timestamp(secs, nsec)
844 } else {
845 "-".to_string()
846 }
847}
848
849fn format_birth_seconds_for_path(path: &str, dereference: bool) -> String {
851 if let Some((secs, _nsec)) = get_birth_time(path, dereference) {
852 secs.to_string()
853 } else {
854 "0".to_string()
855 }
856}
857
858fn major(dev: u64) -> u64 {
860 ((dev >> 8) & 0xff) | ((dev >> 32) & !0xffu64)
862}
863
864fn minor(dev: u64) -> u64 {
866 (dev & 0xff) | ((dev >> 12) & !0xffu64)
867}
868
869fn lookup_username(uid: u32) -> String {
871 unsafe {
872 let pw = libc::getpwuid(uid);
873 if pw.is_null() {
874 return uid.to_string();
875 }
876 let name = std::ffi::CStr::from_ptr((*pw).pw_name);
877 name.to_string_lossy().into_owned()
878 }
879}
880
881fn lookup_groupname(gid: u32) -> String {
883 unsafe {
884 let gr = libc::getgrgid(gid);
885 if gr.is_null() {
886 return gid.to_string();
887 }
888 let name = std::ffi::CStr::from_ptr((*gr).gr_name);
889 name.to_string_lossy().into_owned()
890 }
891}
892
893fn find_mount_point(path: &str) -> String {
895 use std::path::PathBuf;
896
897 let abs = match std::fs::canonicalize(path) {
898 Ok(p) => p,
899 Err(_) => PathBuf::from(path),
900 };
901
902 let mut current = abs.as_path();
903 let dev = match std::fs::metadata(current) {
904 Ok(m) => m.dev(),
905 Err(_) => return "/".to_string(),
906 };
907
908 loop {
909 match current.parent() {
910 Some(parent) => {
911 match std::fs::metadata(parent) {
912 Ok(pm) => {
913 if pm.dev() != dev {
914 return current.to_string_lossy().into_owned();
915 }
916 }
917 Err(_) => {
918 return current.to_string_lossy().into_owned();
919 }
920 }
921 current = parent;
922 }
923 None => {
924 return current.to_string_lossy().into_owned();
925 }
926 }
927 }
928}
929
930pub fn expand_backslash_escapes(s: &str) -> String {
932 let mut result = String::with_capacity(s.len());
933 let bytes = s.as_bytes();
934 let len = bytes.len();
935 let mut i = 0;
936
937 while i < len {
938 if bytes[i] == b'\\' && i + 1 < len {
939 i += 1;
940 match bytes[i] {
941 b'n' => result.push('\n'),
942 b't' => result.push('\t'),
943 b'r' => result.push('\r'),
944 b'a' => result.push('\x07'),
945 b'b' => result.push('\x08'),
946 b'f' => result.push('\x0C'),
947 b'v' => result.push('\x0B'),
948 b'\\' => result.push('\\'),
949 b'"' => result.push('"'),
950 b'0' => {
951 let mut val: u32 = 0;
953 let mut count = 0;
954 while i + 1 < len && count < 3 {
955 let next = bytes[i + 1];
956 if next >= b'0' && next <= b'7' {
957 val = val * 8 + (next - b'0') as u32;
958 i += 1;
959 count += 1;
960 } else {
961 break;
962 }
963 }
964 if let Some(c) = char::from_u32(val) {
965 result.push(c);
966 }
967 }
968 other => {
969 result.push('\\');
970 result.push(other as char);
971 }
972 }
973 } else {
974 let b = bytes[i];
976 if b < 0x80 {
977 result.push(b as char);
978 } else {
979 let char_len = if b < 0xE0 {
980 2
981 } else if b < 0xF0 {
982 3
983 } else {
984 4
985 };
986 let end = (i + char_len).min(len);
987 if let Ok(s) = std::str::from_utf8(&bytes[i..end]) {
988 result.push_str(s);
989 i = end;
990 continue;
991 } else {
992 result.push(b as char);
993 }
994 }
995 }
996 i += 1;
997 }
998
999 result
1000}
1001
1002fn fs_type_name(fs_type: u64) -> &'static str {
1004 match fs_type {
1006 0xEF53 => "ext2/ext3",
1007 0x6969 => "nfs",
1008 0x58465342 => "xfs",
1009 0x2FC12FC1 => "zfs",
1010 0x9123683E => "btrfs",
1011 0x01021994 => "tmpfs",
1012 0x28cd3d45 => "cramfs",
1013 0x3153464a => "jfs",
1014 0x52654973 => "reiserfs",
1015 0x7275 => "romfs",
1016 0x858458f6 => "ramfs",
1017 0x73717368 => "squashfs",
1018 0x62646576 => "devfs",
1019 0x64626720 => "debugfs",
1020 0x1cd1 => "devpts",
1021 0xf15f => "ecryptfs",
1022 0x794c7630 => "overlayfs",
1023 0xFF534D42 => "cifs",
1024 0xfe534d42 => "smb2",
1025 0x137F => "minix",
1026 0x4d44 => "msdos",
1027 0x4006 => "fat",
1028 0x65735546 => "fuse",
1029 0x65735543 => "fusectl",
1030 0x9fa0 => "proc",
1031 0x62656572 => "sysfs",
1032 0x27e0eb => "cgroup",
1033 0x63677270 => "cgroup2",
1034 0x19800202 => "mqueue",
1035 0x50495045 => "pipefs",
1036 0x74726163 => "tracefs",
1037 0x68756773 => "hugetlbfs",
1038 0xBAD1DEA => "futexfs",
1039 0x5346544e => "ntfs",
1040 0x00011954 => "ufs",
1041 _ => "UNKNOWN",
1042 }
1043}