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_statfs(path: &str) -> Result<libc::statfs, io::Error> {
47 let c_path = CString::new(path)
48 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid path"))?;
49 unsafe {
50 let mut sfs: libc::statfs = std::mem::zeroed();
51 let rc = libc::statfs(c_path.as_ptr(), &mut sfs);
52 if rc != 0 {
53 Err(io::Error::last_os_error())
54 } else {
55 Ok(sfs)
56 }
57 }
58}
59
60pub fn stat_file(path: &str, config: &StatConfig) -> Result<String, io::Error> {
64 if config.filesystem {
65 stat_filesystem(path, config)
66 } else {
67 stat_regular(path, config)
68 }
69}
70
71fn stat_regular(path: &str, config: &StatConfig) -> Result<String, io::Error> {
76 let meta = if config.dereference {
77 std::fs::metadata(path)?
78 } else {
79 std::fs::symlink_metadata(path)?
80 };
81 let st = raw_stat(path, config.dereference)?;
82
83 if let Some(ref fmt) = config.printf_format {
84 let expanded = expand_backslash_escapes(fmt);
85 return Ok(format_file_specifiers(
86 &expanded,
87 path,
88 &meta,
89 &st,
90 config.dereference,
91 ));
92 }
93
94 if let Some(ref fmt) = config.format {
95 let result = format_file_specifiers(fmt, path, &meta, &st, config.dereference);
96 return Ok(result + "\n");
97 }
98
99 if config.terse {
100 return Ok(format_file_terse(path, &meta, &st, config.dereference));
101 }
102
103 Ok(format_file_default(path, &meta, &st, config.dereference))
104}
105
106fn stat_filesystem(path: &str, config: &StatConfig) -> Result<String, io::Error> {
111 let sfs = raw_statfs(path)?;
112
113 if let Some(ref fmt) = config.printf_format {
114 let expanded = expand_backslash_escapes(fmt);
115 return Ok(format_fs_specifiers(&expanded, path, &sfs));
116 }
117
118 if let Some(ref fmt) = config.format {
119 let result = format_fs_specifiers(fmt, path, &sfs);
120 return Ok(result + "\n");
121 }
122
123 if config.terse {
124 return Ok(format_fs_terse(path, &sfs));
125 }
126
127 Ok(format_fs_default(path, &sfs))
128}
129
130fn format_file_default(
135 path: &str,
136 meta: &std::fs::Metadata,
137 st: &libc::stat,
138 dereference: bool,
139) -> String {
140 let mode = meta.mode();
141 let file_type_str = file_type_label(mode);
142 let perms_str = mode_to_human(mode);
143 let uid = meta.uid();
144 let gid = meta.gid();
145 let uname = lookup_username(uid);
146 let gname = lookup_groupname(gid);
147 let dev = meta.dev();
148 let dev_major = major(dev);
149 let dev_minor = minor(dev);
150
151 let name_display = if meta.file_type().is_symlink() {
152 match std::fs::read_link(path) {
153 Ok(target) => format!("{} -> {}", path, target.display()),
154 Err(_) => path.to_string(),
155 }
156 } else {
157 path.to_string()
158 };
159
160 let size_line = if meta.file_type().is_block_device() || meta.file_type().is_char_device() {
161 let rdev = meta.rdev();
162 let rmaj = major(rdev);
163 let rmin = minor(rdev);
164 format!(
165 " Size: {:<10}\tBlocks: {:<10} IO Block: {:<6} {}",
166 format!("{}, {}", rmaj, rmin),
167 meta.blocks(),
168 meta.blksize(),
169 file_type_str
170 )
171 } else {
172 format!(
173 " Size: {:<10}\tBlocks: {:<10} IO Block: {:<6} {}",
174 meta.size(),
175 meta.blocks(),
176 meta.blksize(),
177 file_type_str
178 )
179 };
180
181 let device_line = format!(
182 "Device: {},{}\tInode: {:<11} Links: {}",
183 dev_major,
184 dev_minor,
185 meta.ino(),
186 meta.nlink()
187 );
188
189 let access_line = format!(
190 "Access: ({:04o}/{}) Uid: ({:5}/{:>8}) Gid: ({:5}/{:>8})",
191 mode & 0o7777,
192 perms_str,
193 uid,
194 uname,
195 gid,
196 gname
197 );
198
199 let atime = format_timestamp(st.st_atime, st.st_atime_nsec);
200 let mtime = format_timestamp(st.st_mtime, st.st_mtime_nsec);
201 let ctime = format_timestamp(st.st_ctime, st.st_ctime_nsec);
202 let birth = format_birth_time_for_path(path, dereference);
203
204 format!(
205 " File: {}\n{}\n{}\n{}\nAccess: {}\nModify: {}\nChange: {}\n Birth: {}\n",
206 name_display, size_line, device_line, access_line, atime, mtime, ctime, birth
207 )
208}
209
210fn format_file_terse(
215 path: &str,
216 meta: &std::fs::Metadata,
217 st: &libc::stat,
218 dereference: bool,
219) -> String {
220 let dev = meta.dev();
221 let birth_secs = get_birth_time(path, dereference)
222 .map(|(s, _)| s)
223 .unwrap_or(0);
224 format!(
225 "{} {} {} {:x} {} {} {:x} {} {} {:x} {:x} {} {} {} {} {}\n",
226 path,
227 meta.size(),
228 meta.blocks(),
229 meta.mode(),
230 meta.uid(),
231 meta.gid(),
232 dev,
233 meta.ino(),
234 meta.nlink(),
235 major(meta.rdev()),
236 minor(meta.rdev()),
237 st.st_atime,
238 st.st_mtime,
239 st.st_ctime,
240 birth_secs,
241 meta.blksize()
242 )
243}
244
245fn format_fs_default(path: &str, sfs: &libc::statfs) -> String {
250 #[cfg(target_os = "linux")]
251 let fs_type = sfs.f_type;
252 #[cfg(not(target_os = "linux"))]
253 let fs_type = 0u32;
254 let fs_type_name = fs_type_name(fs_type as u64);
255 let fsid = sfs.f_fsid;
256 let fsid_val = extract_fsid(&fsid);
257
258 #[cfg(target_os = "linux")]
259 let namelen = sfs.f_namelen;
260 #[cfg(not(target_os = "linux"))]
261 let namelen = 255i64; #[cfg(target_os = "linux")]
264 let frsize = sfs.f_frsize;
265 #[cfg(not(target_os = "linux"))]
266 let frsize = sfs.f_bsize as u64; format!(
269 " File: \"{}\"\n ID: {:016x} Namelen: {} Type: {}\nBlock size: {:<10} Fundamental block size: {}\nBlocks: Total: {:<10} Free: {:<10} Available: {}\nInodes: Total: {:<10} Free: {}\n",
270 path,
271 fsid_val,
272 namelen,
273 fs_type_name,
274 sfs.f_bsize,
275 frsize,
276 sfs.f_blocks,
277 sfs.f_bfree,
278 sfs.f_bavail,
279 sfs.f_files,
280 sfs.f_ffree
281 )
282}
283
284fn format_fs_terse(path: &str, sfs: &libc::statfs) -> String {
289 let fsid = sfs.f_fsid;
290 let fsid_val = extract_fsid(&fsid);
291
292 #[cfg(target_os = "linux")]
293 let namelen = sfs.f_namelen;
294 #[cfg(not(target_os = "linux"))]
295 let namelen = 255i64;
296
297 #[cfg(target_os = "linux")]
298 let fs_type = sfs.f_type;
299 #[cfg(not(target_os = "linux"))]
300 let fs_type = 0u32; #[cfg(target_os = "linux")]
303 let frsize = sfs.f_frsize;
304 #[cfg(not(target_os = "linux"))]
305 let frsize = sfs.f_bsize as u64;
306
307 format!(
308 "{} {} {} {} {} {} {} {} {} {} {} {}\n",
309 path,
310 fsid_val,
311 namelen,
312 fs_type,
313 sfs.f_bsize,
314 frsize,
315 sfs.f_blocks,
316 sfs.f_bfree,
317 sfs.f_bavail,
318 sfs.f_files,
319 sfs.f_ffree,
320 0 )
322}
323
324fn format_file_specifiers(
329 fmt: &str,
330 path: &str,
331 meta: &std::fs::Metadata,
332 st: &libc::stat,
333 dereference: bool,
334) -> String {
335 let mut result = String::new();
336 let chars: Vec<char> = fmt.chars().collect();
337 let mut i = 0;
338
339 while i < chars.len() {
340 if chars[i] == '%' && i + 1 < chars.len() {
341 i += 1;
342 match chars[i] {
343 'a' => {
344 result.push_str(&format!("{:o}", meta.mode() & 0o7777));
345 }
346 'A' => {
347 result.push_str(&mode_to_human(meta.mode()));
348 }
349 'b' => {
350 result.push_str(&meta.blocks().to_string());
351 }
352 'B' => {
353 result.push_str("512");
354 }
355 'd' => {
356 result.push_str(&meta.dev().to_string());
357 }
358 'D' => {
359 result.push_str(&format!("{:x}", meta.dev()));
360 }
361 'f' => {
362 result.push_str(&format!("{:x}", meta.mode()));
363 }
364 'F' => {
365 result.push_str(file_type_label(meta.mode()));
366 }
367 'g' => {
368 result.push_str(&meta.gid().to_string());
369 }
370 'G' => {
371 result.push_str(&lookup_groupname(meta.gid()));
372 }
373 'h' => {
374 result.push_str(&meta.nlink().to_string());
375 }
376 'i' => {
377 result.push_str(&meta.ino().to_string());
378 }
379 'm' => {
380 result.push_str(&find_mount_point(path));
381 }
382 'n' => {
383 result.push_str(path);
384 }
385 'N' => {
386 if meta.file_type().is_symlink() {
387 match std::fs::read_link(path) {
388 Ok(target) => {
389 result.push_str(&format!("'{}' -> '{}'", path, target.display()));
390 }
391 Err(_) => {
392 result.push_str(&format!("'{}'", path));
393 }
394 }
395 } else {
396 result.push_str(&format!("'{}'", path));
397 }
398 }
399 'o' => {
400 result.push_str(&meta.blksize().to_string());
401 }
402 's' => {
403 result.push_str(&meta.size().to_string());
404 }
405 't' => {
406 result.push_str(&format!("{:x}", major(meta.rdev())));
407 }
408 'T' => {
409 result.push_str(&format!("{:x}", minor(meta.rdev())));
410 }
411 'u' => {
412 result.push_str(&meta.uid().to_string());
413 }
414 'U' => {
415 result.push_str(&lookup_username(meta.uid()));
416 }
417 'w' => {
418 result.push_str(&format_birth_time_for_path(path, dereference));
419 }
420 'W' => {
421 result.push_str(&format_birth_seconds_for_path(path, dereference));
422 }
423 'x' => {
424 result.push_str(&format_timestamp(st.st_atime, st.st_atime_nsec));
425 }
426 'X' => {
427 result.push_str(&st.st_atime.to_string());
428 }
429 'y' => {
430 result.push_str(&format_timestamp(st.st_mtime, st.st_mtime_nsec));
431 }
432 'Y' => {
433 result.push_str(&st.st_mtime.to_string());
434 }
435 'z' => {
436 result.push_str(&format_timestamp(st.st_ctime, st.st_ctime_nsec));
437 }
438 'Z' => {
439 result.push_str(&st.st_ctime.to_string());
440 }
441 '%' => {
442 result.push('%');
443 }
444 other => {
445 result.push('%');
446 result.push(other);
447 }
448 }
449 } else {
450 result.push(chars[i]);
451 }
452 i += 1;
453 }
454
455 result
456}
457
458fn format_fs_specifiers(fmt: &str, path: &str, sfs: &libc::statfs) -> String {
463 let mut result = String::new();
464 let chars: Vec<char> = fmt.chars().collect();
465 let mut i = 0;
466 let fsid = sfs.f_fsid;
467 let fsid_val = extract_fsid(&fsid);
468
469 while i < chars.len() {
470 if chars[i] == '%' && i + 1 < chars.len() {
471 i += 1;
472 match chars[i] {
473 'a' => {
474 result.push_str(&sfs.f_bavail.to_string());
475 }
476 'b' => {
477 result.push_str(&sfs.f_blocks.to_string());
478 }
479 'c' => {
480 result.push_str(&sfs.f_files.to_string());
481 }
482 'd' => {
483 result.push_str(&sfs.f_ffree.to_string());
484 }
485 'f' => {
486 result.push_str(&sfs.f_bfree.to_string());
487 }
488 'i' => {
489 result.push_str(&format!("{:x}", fsid_val));
490 }
491 'l' => {
492 #[cfg(target_os = "linux")]
493 result.push_str(&sfs.f_namelen.to_string());
494 #[cfg(not(target_os = "linux"))]
495 result.push_str("255");
496 }
497 'n' => {
498 result.push_str(path);
499 }
500 's' => {
501 result.push_str(&sfs.f_bsize.to_string());
502 }
503 'S' => {
504 #[cfg(target_os = "linux")]
505 result.push_str(&sfs.f_frsize.to_string());
506 #[cfg(not(target_os = "linux"))]
507 result.push_str(&sfs.f_bsize.to_string());
508 }
509 't' => {
510 #[cfg(target_os = "linux")]
511 result.push_str(&format!("{:x}", sfs.f_type));
512 #[cfg(not(target_os = "linux"))]
513 result.push('0');
514 }
515 'T' => {
516 #[cfg(target_os = "linux")]
517 result.push_str(fs_type_name(sfs.f_type as u64));
518 #[cfg(not(target_os = "linux"))]
519 result.push_str("unknown");
520 }
521 '%' => {
522 result.push('%');
523 }
524 other => {
525 result.push('%');
526 result.push(other);
527 }
528 }
529 } else {
530 result.push(chars[i]);
531 }
532 i += 1;
533 }
534
535 result
536}
537
538pub fn mode_to_human(mode: u32) -> String {
544 let file_char = match mode & (libc::S_IFMT as u32) {
545 m if m == libc::S_IFREG as u32 => '-',
546 m if m == libc::S_IFDIR as u32 => 'd',
547 m if m == libc::S_IFLNK as u32 => 'l',
548 m if m == libc::S_IFBLK as u32 => 'b',
549 m if m == libc::S_IFCHR as u32 => 'c',
550 m if m == libc::S_IFIFO as u32 => 'p',
551 m if m == libc::S_IFSOCK as u32 => 's',
552 _ => '?',
553 };
554
555 let mut s = String::with_capacity(10);
556 s.push(file_char);
557
558 s.push(if mode & 0o400 != 0 { 'r' } else { '-' });
560 s.push(if mode & 0o200 != 0 { 'w' } else { '-' });
561 s.push(if mode & (libc::S_ISUID as u32) != 0 {
562 if mode & 0o100 != 0 { 's' } else { 'S' }
563 } else if mode & 0o100 != 0 {
564 'x'
565 } else {
566 '-'
567 });
568
569 s.push(if mode & 0o040 != 0 { 'r' } else { '-' });
571 s.push(if mode & 0o020 != 0 { 'w' } else { '-' });
572 s.push(if mode & (libc::S_ISGID as u32) != 0 {
573 if mode & 0o010 != 0 { 's' } else { 'S' }
574 } else if mode & 0o010 != 0 {
575 'x'
576 } else {
577 '-'
578 });
579
580 s.push(if mode & 0o004 != 0 { 'r' } else { '-' });
582 s.push(if mode & 0o002 != 0 { 'w' } else { '-' });
583 s.push(if mode & (libc::S_ISVTX as u32) != 0 {
584 if mode & 0o001 != 0 { 't' } else { 'T' }
585 } else if mode & 0o001 != 0 {
586 'x'
587 } else {
588 '-'
589 });
590
591 s
592}
593
594pub fn file_type_label(mode: u32) -> &'static str {
596 match mode & (libc::S_IFMT as u32) {
597 m if m == libc::S_IFREG as u32 => "regular file",
598 m if m == libc::S_IFDIR as u32 => "directory",
599 m if m == libc::S_IFLNK as u32 => "symbolic link",
600 m if m == libc::S_IFBLK as u32 => "block special file",
601 m if m == libc::S_IFCHR as u32 => "character special file",
602 m if m == libc::S_IFIFO as u32 => "fifo",
603 m if m == libc::S_IFSOCK as u32 => "socket",
604 _ => "unknown",
605 }
606}
607
608fn format_timestamp(secs: i64, nsec: i64) -> String {
610 let t = secs as libc::time_t;
612 let mut tm: libc::tm = unsafe { std::mem::zeroed() };
613 unsafe {
614 libc::localtime_r(&t, &mut tm);
615 }
616
617 let offset_secs = tm.tm_gmtoff;
618 let offset_sign = if offset_secs >= 0 { '+' } else { '-' };
619 let offset_abs = offset_secs.unsigned_abs();
620 let offset_hours = offset_abs / 3600;
621 let offset_mins = (offset_abs % 3600) / 60;
622
623 format!(
624 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:09} {}{:02}{:02}",
625 tm.tm_year + 1900,
626 tm.tm_mon + 1,
627 tm.tm_mday,
628 tm.tm_hour,
629 tm.tm_min,
630 tm.tm_sec,
631 nsec,
632 offset_sign,
633 offset_hours,
634 offset_mins
635 )
636}
637
638#[cfg(target_os = "linux")]
640fn get_birth_time(path: &str, dereference: bool) -> Option<(i64, i64)> {
641 use std::mem::MaybeUninit;
642
643 let c_path = CString::new(path).ok()?;
644 unsafe {
645 let mut statx_buf: libc::statx = MaybeUninit::zeroed().assume_init();
646 let flags = if dereference {
647 0
648 } else {
649 libc::AT_SYMLINK_NOFOLLOW
650 };
651 let rc = libc::statx(
652 libc::AT_FDCWD,
653 c_path.as_ptr(),
654 flags,
655 libc::STATX_BTIME,
656 &mut statx_buf,
657 );
658 if rc == 0 && (statx_buf.stx_mask & libc::STATX_BTIME) != 0 {
659 Some((
660 statx_buf.stx_btime.tv_sec,
661 statx_buf.stx_btime.tv_nsec as i64,
662 ))
663 } else {
664 None
665 }
666 }
667}
668
669#[cfg(not(target_os = "linux"))]
670fn get_birth_time(_path: &str, _dereference: bool) -> Option<(i64, i64)> {
671 None
672}
673
674fn format_birth_time_for_path(path: &str, dereference: bool) -> String {
676 if let Some((secs, nsec)) = get_birth_time(path, dereference) {
677 format_timestamp(secs, nsec)
678 } else {
679 "-".to_string()
680 }
681}
682
683fn format_birth_seconds_for_path(path: &str, dereference: bool) -> String {
685 if let Some((secs, _nsec)) = get_birth_time(path, dereference) {
686 secs.to_string()
687 } else {
688 "0".to_string()
689 }
690}
691
692fn major(dev: u64) -> u64 {
694 ((dev >> 8) & 0xff) | ((dev >> 32) & !0xffu64)
696}
697
698fn minor(dev: u64) -> u64 {
700 (dev & 0xff) | ((dev >> 12) & !0xffu64)
701}
702
703fn lookup_username(uid: u32) -> String {
705 unsafe {
706 let pw = libc::getpwuid(uid);
707 if pw.is_null() {
708 return uid.to_string();
709 }
710 let name = std::ffi::CStr::from_ptr((*pw).pw_name);
711 name.to_string_lossy().into_owned()
712 }
713}
714
715fn lookup_groupname(gid: u32) -> String {
717 unsafe {
718 let gr = libc::getgrgid(gid);
719 if gr.is_null() {
720 return gid.to_string();
721 }
722 let name = std::ffi::CStr::from_ptr((*gr).gr_name);
723 name.to_string_lossy().into_owned()
724 }
725}
726
727fn find_mount_point(path: &str) -> String {
729 use std::path::PathBuf;
730
731 let abs = match std::fs::canonicalize(path) {
732 Ok(p) => p,
733 Err(_) => PathBuf::from(path),
734 };
735
736 let mut current = abs.as_path();
737 let dev = match std::fs::metadata(current) {
738 Ok(m) => m.dev(),
739 Err(_) => return "/".to_string(),
740 };
741
742 loop {
743 match current.parent() {
744 Some(parent) => {
745 match std::fs::metadata(parent) {
746 Ok(pm) => {
747 if pm.dev() != dev {
748 return current.to_string_lossy().into_owned();
749 }
750 }
751 Err(_) => {
752 return current.to_string_lossy().into_owned();
753 }
754 }
755 current = parent;
756 }
757 None => {
758 return current.to_string_lossy().into_owned();
759 }
760 }
761 }
762}
763
764pub fn expand_backslash_escapes(s: &str) -> String {
766 let mut result = String::with_capacity(s.len());
767 let chars: Vec<char> = s.chars().collect();
768 let mut i = 0;
769
770 while i < chars.len() {
771 if chars[i] == '\\' && i + 1 < chars.len() {
772 i += 1;
773 match chars[i] {
774 'n' => result.push('\n'),
775 't' => result.push('\t'),
776 'r' => result.push('\r'),
777 'a' => result.push('\x07'),
778 'b' => result.push('\x08'),
779 'f' => result.push('\x0C'),
780 'v' => result.push('\x0B'),
781 '\\' => result.push('\\'),
782 '"' => result.push('"'),
783 '0' => {
784 let mut val: u32 = 0;
786 let mut count = 0;
787 while i + 1 < chars.len() && count < 3 {
788 let next = chars[i + 1];
789 if next >= '0' && next <= '7' {
790 val = val * 8 + (next as u32 - '0' as u32);
791 i += 1;
792 count += 1;
793 } else {
794 break;
795 }
796 }
797 if let Some(c) = char::from_u32(val) {
798 result.push(c);
799 }
800 }
801 other => {
802 result.push('\\');
803 result.push(other);
804 }
805 }
806 } else {
807 result.push(chars[i]);
808 }
809 i += 1;
810 }
811
812 result
813}
814
815fn fs_type_name(fs_type: u64) -> &'static str {
817 match fs_type {
819 0xEF53 => "ext2/ext3",
820 0x6969 => "nfs",
821 0x58465342 => "xfs",
822 0x2FC12FC1 => "zfs",
823 0x9123683E => "btrfs",
824 0x01021994 => "tmpfs",
825 0x28cd3d45 => "cramfs",
826 0x3153464a => "jfs",
827 0x52654973 => "reiserfs",
828 0x7275 => "romfs",
829 0x858458f6 => "ramfs",
830 0x73717368 => "squashfs",
831 0x62646576 => "devfs",
832 0x64626720 => "debugfs",
833 0x1cd1 => "devpts",
834 0xf15f => "ecryptfs",
835 0x794c7630 => "overlayfs",
836 0xFF534D42 => "cifs",
837 0xfe534d42 => "smb2",
838 0x137F => "minix",
839 0x4d44 => "msdos",
840 0x4006 => "fat",
841 0x65735546 => "fuse",
842 0x65735543 => "fusectl",
843 0x9fa0 => "proc",
844 0x62656572 => "sysfs",
845 0x27e0eb => "cgroup",
846 0x63677270 => "cgroup2",
847 0x19800202 => "mqueue",
848 0x50495045 => "pipefs",
849 0x74726163 => "tracefs",
850 0x68756773 => "hugetlbfs",
851 0xBAD1DEA => "futexfs",
852 0x5346544e => "ntfs",
853 0x00011954 => "ufs",
854 _ => "UNKNOWN",
855 }
856}