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