xref: /aosp_15_r20/external/crosvm/ext2/tests/tests.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2024 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #![cfg(target_os = "linux")]
6 
7 use std::collections::BTreeMap;
8 use std::collections::BTreeSet;
9 use std::fs;
10 use std::fs::create_dir;
11 use std::fs::read_link;
12 use std::fs::symlink_metadata;
13 use std::fs::File;
14 use std::fs::OpenOptions;
15 use std::io::BufWriter;
16 use std::io::Seek;
17 use std::io::SeekFrom;
18 use std::io::Write;
19 use std::os::unix::fs::symlink;
20 use std::path::Path;
21 use std::path::PathBuf;
22 use std::process::Command;
23 
24 use base::MappedRegion;
25 use ext2::Builder;
26 use tempfile::tempdir;
27 use tempfile::tempdir_in;
28 use tempfile::TempDir;
29 use walkdir::WalkDir;
30 
31 const FSCK_PATH: &str = "/usr/sbin/e2fsck";
32 const DEBUGFS_PATH: &str = "/usr/sbin/debugfs";
33 
34 const BLOCK_SIZE: u32 = 4096;
35 
run_fsck(path: &PathBuf)36 fn run_fsck(path: &PathBuf) {
37     // Run fsck and scheck its exit code is 0.
38     // Passing 'y' to stop attempting interactive repair.
39     let output = Command::new(FSCK_PATH)
40         .arg("-fvy")
41         .arg(path)
42         .output()
43         .unwrap();
44     println!("status: {}", output.status);
45     println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
46     println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
47     assert!(output.status.success());
48 }
49 
run_debugfs_cmd(args: &[&str], disk: &PathBuf) -> String50 fn run_debugfs_cmd(args: &[&str], disk: &PathBuf) -> String {
51     let output = Command::new(DEBUGFS_PATH)
52         .arg("-R")
53         .args(args)
54         .arg(disk)
55         .output()
56         .unwrap();
57 
58     let stdout = String::from_utf8_lossy(&output.stdout);
59     let stderr = String::from_utf8_lossy(&output.stderr);
60     println!("status: {}", output.status);
61     println!("stdout: {stdout}");
62     println!("stderr: {stderr}");
63     assert!(output.status.success());
64 
65     stdout.trim_start().trim_end().to_string()
66 }
67 
mkfs(td: &TempDir, builder: Builder) -> PathBuf68 fn mkfs(td: &TempDir, builder: Builder) -> PathBuf {
69     let path = td.path().join("empty.ext2");
70     let mem = builder
71         .allocate_memory()
72         .unwrap()
73         .build_mmap_info()
74         .unwrap()
75         .do_mmap()
76         .unwrap();
77     // SAFETY: `mem` has a valid pointer and its size.
78     let buf = unsafe { std::slice::from_raw_parts(mem.as_ptr(), mem.size()) };
79     let mut file = OpenOptions::new()
80         .write(true)
81         .create(true)
82         .truncate(true)
83         .open(&path)
84         .unwrap();
85     file.write_all(buf).unwrap();
86 
87     run_fsck(&path);
88 
89     path
90 }
91 
92 #[test]
test_mkfs_empty()93 fn test_mkfs_empty() {
94     let td = tempdir().unwrap();
95     let disk = mkfs(
96         &td,
97         Builder {
98             blocks_per_group: 1024,
99             inodes_per_group: 1024,
100             ..Default::default()
101         },
102     );
103 
104     // Ensure the content of the generated disk image with `debugfs`.
105     // It contains the following entries:
106     // - `.`: the rootdir whose inode is 2 and rec_len is 12.
107     // - `..`: this is also the rootdir with same inode and the same rec_len.
108     // - `lost+found`: inode is 11 and rec_len is 4072 (= block_size - 2*12).
109     assert_eq!(
110         run_debugfs_cmd(&["ls"], &disk),
111         "2  (12) .    2  (12) ..    11  (4072) lost+found"
112     );
113 }
114 
115 #[test]
test_mkfs_empty_multi_block_groups()116 fn test_mkfs_empty_multi_block_groups() {
117     let td = tempdir().unwrap();
118     let blocks_per_group = 2048;
119     let num_groups = 2;
120     let disk = mkfs(
121         &td,
122         Builder {
123             blocks_per_group,
124             inodes_per_group: 4096,
125             size: 4096 * blocks_per_group * num_groups,
126             ..Default::default()
127         },
128     );
129     assert_eq!(
130         run_debugfs_cmd(&["ls"], &disk),
131         "2  (12) .    2  (12) ..    11  (4072) lost+found"
132     );
133 }
134 
collect_paths(dir: &Path, skip_lost_found: bool) -> BTreeSet<(String, PathBuf)>135 fn collect_paths(dir: &Path, skip_lost_found: bool) -> BTreeSet<(String, PathBuf)> {
136     WalkDir::new(dir)
137         .into_iter()
138         .filter_map(|entry| {
139             entry.ok().and_then(|e| {
140                 let name = e
141                     .path()
142                     .strip_prefix(dir)
143                     .unwrap()
144                     .to_string_lossy()
145                     .into_owned();
146                 let path = e.path().to_path_buf();
147                 if name.is_empty() {
148                     return None;
149                 }
150                 if skip_lost_found && name == "lost+found" {
151                     return None;
152                 }
153 
154                 Some((name, path))
155             })
156         })
157         .collect()
158 }
159 
assert_eq_dirs( td: &TempDir, dir: &Path, disk: &PathBuf, xattr_map: Option<BTreeMap<String, Vec<(&str, &str)>>>, )160 fn assert_eq_dirs(
161     td: &TempDir,
162     dir: &Path,
163     disk: &PathBuf,
164     // Check the correct xattr is set and any unexpected one isn't set.
165     // Pass None to skip this check for test cases where many files are created.
166     xattr_map: Option<BTreeMap<String, Vec<(&str, &str)>>>,
167 ) {
168     // dump the disk contents to `dump_dir`.
169     let dump_dir = td.path().join("dump");
170     std::fs::create_dir(&dump_dir).unwrap();
171     run_debugfs_cmd(
172         &[&format!(
173             "rdump / {}",
174             dump_dir.as_os_str().to_str().unwrap()
175         )],
176         disk,
177     );
178 
179     let paths1 = collect_paths(dir, true);
180     let paths2 = collect_paths(&dump_dir, true);
181     if paths1.len() != paths2.len() {
182         panic!(
183             "number of entries mismatch: {:?}={:?}, {:?}={:?}",
184             dir,
185             paths1.len(),
186             dump_dir,
187             paths2.len()
188         );
189     }
190 
191     for ((name1, path1), (name2, path2)) in paths1.iter().zip(paths2.iter()) {
192         assert_eq!(name1, name2);
193         let m1 = symlink_metadata(path1).unwrap();
194         let m2 = symlink_metadata(path2).unwrap();
195         assert_eq!(
196             m1.file_type(),
197             m2.file_type(),
198             "file type mismatch ({name1})"
199         );
200 
201         if m1.file_type().is_symlink() {
202             let dst1 = read_link(path1).unwrap();
203             let dst2 = read_link(path2).unwrap();
204             assert_eq!(
205                 dst1, dst2,
206                 "symlink mismatch ({name1}): {:?}->{:?} vs {:?}->{:?}",
207                 path1, dst1, path2, dst2
208             );
209         } else {
210             assert_eq!(m1.len(), m2.len(), "length mismatch ({name1})");
211         }
212 
213         assert_eq!(
214             m1.permissions(),
215             m2.permissions(),
216             "permissions mismatch ({name1})"
217         );
218 
219         if m1.file_type().is_file() {
220             // Check contents
221             let c1 = std::fs::read_to_string(path1).unwrap();
222             let c2 = std::fs::read_to_string(path2).unwrap();
223             assert_eq!(c1, c2, "content mismatch: ({name1})");
224         }
225 
226         // Check xattr
227         if let Some(mp) = &xattr_map {
228             match mp.get(name1) {
229                 Some(expected_xattrs) if !expected_xattrs.is_empty() => {
230                     for (key, value) in expected_xattrs {
231                         let s = run_debugfs_cmd(&[&format!("ea_get -V {name1} {key}",)], disk);
232                         assert_eq!(&s, value);
233                     }
234                 }
235                 // If no xattr is specified, any value must not be set.
236                 _ => {
237                     let s = run_debugfs_cmd(&[&format!("ea_list {}", name1,)], disk);
238                     assert_eq!(s, "");
239                 }
240             }
241         }
242     }
243 }
244 
245 #[test]
test_simple_dir()246 fn test_simple_dir() {
247     // testdata
248     // ├── a.txt
249     // ├── b.txt
250     // └── dir
251     //     └── c.txt
252     let td = tempdir().unwrap();
253     let dir = td.path().join("testdata");
254     create_dir(&dir).unwrap();
255     File::create(dir.join("a.txt")).unwrap();
256     File::create(dir.join("b.txt")).unwrap();
257     create_dir(dir.join("dir")).unwrap();
258     File::create(dir.join("dir/c.txt")).unwrap();
259     let disk = mkfs(
260         &td,
261         Builder {
262             blocks_per_group: 2048,
263             inodes_per_group: 4096,
264             root_dir: Some(dir.clone()),
265             ..Default::default()
266         },
267     );
268 
269     assert_eq_dirs(&td, &dir, &disk, Some(Default::default()));
270 
271     td.close().unwrap(); // make sure that tempdir is properly deleted.
272 }
273 
274 #[test]
test_nested_dirs()275 fn test_nested_dirs() {
276     // testdata
277     // └── dir1
278     //     ├── a.txt
279     //     └── dir2
280     //         ├── b.txt
281     //         └── dir3
282     let td = tempdir().unwrap();
283     let dir = td.path().join("testdata");
284     create_dir(&dir).unwrap();
285     let dir1 = &dir.join("dir1");
286     create_dir(dir1).unwrap();
287     File::create(dir1.join("a.txt")).unwrap();
288     let dir2 = dir1.join("dir2");
289     create_dir(&dir2).unwrap();
290     File::create(dir2.join("b.txt")).unwrap();
291     let dir3 = dir2.join("dir3");
292     create_dir(dir3).unwrap();
293     let disk = mkfs(
294         &td,
295         Builder {
296             blocks_per_group: 2048,
297             inodes_per_group: 4096,
298             root_dir: Some(dir.clone()),
299             ..Default::default()
300         },
301     );
302 
303     assert_eq_dirs(&td, &dir, &disk, Some(Default::default()));
304 }
305 
306 #[test]
test_file_contents()307 fn test_file_contents() {
308     // testdata
309     // ├── hello.txt (content: "Hello!\n")
310     // └── big.txt (content: 10KB of data, which doesn't fit in one block)
311     let td = tempdir().unwrap();
312     let dir = td.path().join("testdata");
313     create_dir(&dir).unwrap();
314     let mut hello = File::create(dir.join("hello.txt")).unwrap();
315     hello.write_all(b"Hello!\n").unwrap();
316     let mut big = BufWriter::new(File::create(dir.join("big.txt")).unwrap());
317     let data = b"123456789\n";
318     for _ in 0..1024 {
319         big.write_all(data).unwrap();
320     }
321 
322     let disk = mkfs(
323         &td,
324         Builder {
325             blocks_per_group: 2048,
326             inodes_per_group: 4096,
327             root_dir: Some(dir.clone()),
328             ..Default::default()
329         },
330     );
331 
332     assert_eq_dirs(&td, &dir, &disk, Some(Default::default()));
333 }
334 
335 #[test]
test_max_file_name()336 fn test_max_file_name() {
337     // testdata
338     // └── aa..aa (whose file name length is 255, which is the ext2/3/4's maximum file name length)
339     let td = tempdir().unwrap();
340     let dir = td.path().join("testdata");
341     create_dir(&dir).unwrap();
342     let long_name = "a".repeat(255);
343     File::create(dir.join(long_name)).unwrap();
344 
345     let disk = mkfs(
346         &td,
347         Builder {
348             blocks_per_group: 2048,
349             inodes_per_group: 4096,
350             root_dir: Some(dir.clone()),
351             ..Default::default()
352         },
353     );
354 
355     assert_eq_dirs(&td, &dir, &disk, Some(Default::default()));
356 }
357 
358 #[test]
test_mkfs_indirect_block()359 fn test_mkfs_indirect_block() {
360     // testdata
361     // ├── big.txt (80KiB), which requires indirect blocks
362     // └── huge.txt (8MiB), which requires doubly indirect blocks
363     let td = tempdir().unwrap();
364     let dir = td.path().join("testdata");
365     std::fs::create_dir(&dir).unwrap();
366     let mut big = std::fs::File::create(dir.join("big.txt")).unwrap();
367     big.seek(SeekFrom::Start(80 * 1024)).unwrap();
368     big.write_all(&[0]).unwrap();
369 
370     let mut huge = std::fs::File::create(dir.join("huge.txt")).unwrap();
371     huge.seek(SeekFrom::Start(8 * 1024 * 1024)).unwrap();
372     huge.write_all(&[0]).unwrap();
373 
374     let disk = mkfs(
375         &td,
376         Builder {
377             blocks_per_group: 4096,
378             inodes_per_group: 4096,
379             root_dir: Some(dir.clone()),
380             ..Default::default()
381         },
382     );
383 
384     assert_eq_dirs(&td, &dir, &disk, Some(Default::default()));
385 }
386 
387 #[test]
test_mkfs_symlink()388 fn test_mkfs_symlink() {
389     // testdata
390     // ├── a.txt
391     // ├── self -> ./self
392     // ├── symlink0 -> ./a.txt
393     // ├── symlink1 -> ./symlink0
394     // └── dir
395     //     └── upper-a -> ../a.txt
396     let td = tempdir().unwrap();
397     let dir = td.path().join("testdata");
398     create_dir(&dir).unwrap();
399 
400     let mut f = File::create(dir.join("a.txt")).unwrap();
401     f.write_all("Hello".as_bytes()).unwrap();
402 
403     symlink("./self", dir.join("self")).unwrap();
404 
405     symlink("./a.txt", dir.join("symlink0")).unwrap();
406     symlink("./symlink0", dir.join("symlink1")).unwrap();
407 
408     create_dir(dir.join("dir")).unwrap();
409     symlink("../a.txt", dir.join("dir/upper-a")).unwrap();
410 
411     let disk = mkfs(
412         &td,
413         Builder {
414             blocks_per_group: 2048,
415             inodes_per_group: 4096,
416             root_dir: Some(dir.clone()),
417             ..Default::default()
418         },
419     );
420 
421     assert_eq_dirs(&td, &dir, &disk, Some(Default::default()));
422 }
423 
424 #[test]
test_mkfs_abs_symlink()425 fn test_mkfs_abs_symlink() {
426     // testdata
427     // ├── a.txt
428     // ├── a -> /testdata/a
429     // ├── self -> /testdata/self
430     // ├── tmp -> /tmp
431     // └── abc -> /a/b/c
432     let td = tempdir().unwrap();
433     let dir = td.path().join("testdata");
434 
435     std::fs::create_dir(&dir).unwrap();
436     File::create(dir.join("a.txt")).unwrap();
437     symlink(dir.join("a.txt"), dir.join("a")).unwrap();
438     symlink(dir.join("self"), dir.join("self")).unwrap();
439     symlink("/tmp/", dir.join("tmp")).unwrap();
440     symlink("/a/b/c", dir.join("abc")).unwrap();
441 
442     let disk = mkfs(
443         &td,
444         Builder {
445             blocks_per_group: 2048,
446             inodes_per_group: 4096,
447             root_dir: Some(dir.clone()),
448             ..Default::default()
449         },
450     );
451 
452     assert_eq_dirs(&td, &dir, &disk, Some(Default::default()));
453 }
454 
455 #[test]
test_mkfs_symlink_to_deleted()456 fn test_mkfs_symlink_to_deleted() {
457     // testdata
458     // ├── (deleted)
459     // └── symlink_to_deleted -> (deleted)
460     let td = tempdir().unwrap();
461     let dir = td.path().join("testdata");
462 
463     std::fs::create_dir(&dir).unwrap();
464     File::create(dir.join("deleted")).unwrap();
465     symlink("./deleted", dir.join("symlink_to_deleted")).unwrap();
466     fs::remove_file(dir.join("deleted")).unwrap();
467 
468     let disk = mkfs(
469         &td,
470         Builder {
471             blocks_per_group: 2048,
472             inodes_per_group: 4096,
473             root_dir: Some(dir.clone()),
474             ..Default::default()
475         },
476     );
477 
478     assert_eq_dirs(&td, &dir, &disk, Some(Default::default()));
479 }
480 
481 #[test]
test_mkfs_long_symlink()482 fn test_mkfs_long_symlink() {
483     // testdata
484     // ├── /(long name directory)/a.txt
485     // └── symlink -> /(long name directory)/a.txt
486     // ├── (60-byte filename)
487     // └── symlink60 -> (60-byte filename)
488 
489     let td = tempdir().unwrap();
490     let dir = td.path().join("testdata");
491 
492     create_dir(&dir).unwrap();
493 
494     const LONG_DIR_NAME: &str =
495         "this_is_a_very_long_directory_name_so_that_name_cannoot_fit_in_60_characters_in_inode";
496     assert!(LONG_DIR_NAME.len() > 60);
497 
498     let long_dir = dir.join(LONG_DIR_NAME);
499     create_dir(&long_dir).unwrap();
500     File::create(long_dir.join("a.txt")).unwrap();
501     symlink(long_dir.join("a.txt"), dir.join("symlink")).unwrap();
502 
503     const SIXTY_CHAR_DIR_NAME: &str =
504         "./this_is_just_60_byte_long_so_it_can_work_as_a_corner_case.";
505     assert_eq!(SIXTY_CHAR_DIR_NAME.len(), 60);
506     File::create(dir.join(SIXTY_CHAR_DIR_NAME)).unwrap();
507     symlink(SIXTY_CHAR_DIR_NAME, dir.join("symlink60")).unwrap();
508 
509     let disk = mkfs(
510         &td,
511         Builder {
512             blocks_per_group: 2048,
513             inodes_per_group: 4096,
514             root_dir: Some(dir.clone()),
515             ..Default::default()
516         },
517     );
518 
519     assert_eq_dirs(&td, &dir, &disk, Some(Default::default()));
520 }
521 
522 #[test]
test_ignore_lost_found()523 fn test_ignore_lost_found() {
524     // Ignore /lost+found/ directory in source to avoid conflict.
525     //
526     // testdata
527     // ├── lost+found (ignored and recreated as an empty dir)
528     // │   └── should_be_ignored.txt
529     // └── sub
530     //     └── lost+found (not ignored)
531     //         └── a.txt
532 
533     let td = tempdir().unwrap();
534     let dir = td.path().join("testdata");
535 
536     create_dir(&dir).unwrap();
537     create_dir(dir.join("lost+found")).unwrap();
538     File::create(dir.join("lost+found").join("should_be_ignored.txt")).unwrap();
539     create_dir(dir.join("sub")).unwrap();
540     create_dir(dir.join("sub").join("lost+found")).unwrap();
541     File::create(dir.join("sub").join("lost+found").join("a.txt")).unwrap();
542 
543     let disk = mkfs(
544         &td,
545         Builder {
546             blocks_per_group: 2048,
547             inodes_per_group: 4096,
548             root_dir: Some(dir.clone()),
549             ..Default::default()
550         },
551     );
552 
553     // dump the disk contents to `dump_dir`.
554     let dump_dir = td.path().join("dump");
555     std::fs::create_dir(&dump_dir).unwrap();
556     run_debugfs_cmd(
557         &[&format!(
558             "rdump / {}",
559             dump_dir.as_os_str().to_str().unwrap()
560         )],
561         &disk,
562     );
563 
564     let paths = collect_paths(&dump_dir, false /* skip_lost_found */)
565         .into_iter()
566         .map(|(path, _)| path)
567         .collect::<BTreeSet<_>>();
568     assert_eq!(
569         paths,
570         BTreeSet::from([
571             "lost+found".to_string(),
572             // 'lost+found/should_be_ignored.txt' must not in the result.
573             "sub".to_string(),
574             "sub/lost+found".to_string(),
575             "sub/lost+found/a.txt".to_string()
576         ])
577     );
578 }
579 
580 #[test]
test_multiple_block_directory_entry()581 fn test_multiple_block_directory_entry() {
582     // Creates a many files in a directory.
583     // So the sum of the sizes of directory entries exceeds 4KB and they need to be stored in
584     // multiple blocks.
585     //
586     // testdata
587     // ├─  0.txt
588     // ├─  1.txt
589     // ...
590     // └── 999.txt
591     let td = tempdir().unwrap();
592     let dir = td.path().join("testdata");
593 
594     std::fs::create_dir(&dir).unwrap();
595 
596     for i in 0..1000 {
597         let path = dir.join(format!("{i}.txt"));
598         File::create(&path).unwrap();
599     }
600 
601     let disk = mkfs(
602         &td,
603         Builder {
604             blocks_per_group: 2048,
605             inodes_per_group: 4096,
606             root_dir: Some(dir.clone()),
607             ..Default::default()
608         },
609     );
610 
611     assert_eq_dirs(&td, &dir, &disk, None); // skip xattr check
612 }
613 
614 // Test a case where the inode tables spans multiple block groups.
615 #[test]
test_multiple_bg_multi_inode_bitmap()616 fn test_multiple_bg_multi_inode_bitmap() {
617     // testdata
618     // ├─  0.txt
619     // ├─  1.txt
620     // ...
621     // └── 999.txt
622     let td = tempdir().unwrap();
623     let dir = td.path().join("testdata");
624 
625     std::fs::create_dir(&dir).unwrap();
626 
627     for i in 0..1000 {
628         let fname = format!("{i}.txt");
629         let path = dir.join(&fname);
630         let mut f = File::create(&path).unwrap();
631         // Write a file name to the file.
632         f.write_all(fname.as_bytes()).unwrap();
633     }
634 
635     let blocks_per_group = 1024;
636     // Set `inodes_per_group` to a smaller value than the number of files.
637     // So, the inode table in the 2nd block group will be used.
638     let inodes_per_group = 512;
639     let num_groups = 2;
640     let disk = mkfs(
641         &td,
642         Builder {
643             blocks_per_group,
644             inodes_per_group,
645             size: BLOCK_SIZE * blocks_per_group * num_groups,
646             root_dir: Some(dir.clone()),
647         },
648     );
649 
650     assert_eq_dirs(&td, &dir, &disk, None);
651 }
652 
653 /// Test a case where the block tables spans multiple block groups.
654 #[test]
test_multiple_bg_multi_block_bitmap()655 fn test_multiple_bg_multi_block_bitmap() {
656     // testdata
657     // ├─  0.txt
658     // ├─  1.txt
659     // ...
660     // └── 999.txt
661     let td = tempdir().unwrap();
662     let dir = td.path().join("testdata");
663 
664     std::fs::create_dir(&dir).unwrap();
665 
666     for i in 0..1000 {
667         let fname = format!("{i}.txt");
668         let path = dir.join(&fname);
669         let mut f = File::create(&path).unwrap();
670         // Write a file name to the file.
671         f.write_all(fname.as_bytes()).unwrap();
672     }
673 
674     // Set `blocks_per_group` to a smaller value than the number of files.
675     // So, the block table in the 2nd block group will be used.
676     let blocks_per_group = 512;
677     let inodes_per_group = 2048;
678     let num_groups = 4;
679     let disk = mkfs(
680         &td,
681         Builder {
682             blocks_per_group,
683             inodes_per_group,
684             size: BLOCK_SIZE * blocks_per_group * num_groups,
685             root_dir: Some(dir.clone()),
686         },
687     );
688 
689     assert_eq_dirs(&td, &dir, &disk, None);
690 }
691 
692 // Test a case where a file spans multiple block groups.
693 #[test]
test_multiple_bg_big_files()694 fn test_multiple_bg_big_files() {
695     // testdata
696     // ├─  0.txt (200 * 5000 bytes)
697     // ├─  1.txt (200 * 5000 bytes)
698     // ...
699     // └── 9.txt (200 * 5000 bytes)
700     let td = tempdir().unwrap();
701     let dir = td.path().join("testdata");
702 
703     std::fs::create_dir(&dir).unwrap();
704 
705     // Prepare a large data.
706     let data = vec!["0123456789"; 5000 * 20].concat();
707     for i in 0..10 {
708         let path = dir.join(format!("{i}.txt"));
709         let mut f = File::create(&path).unwrap();
710         f.write_all(data.as_bytes()).unwrap();
711     }
712 
713     // Set `blocks_per_group` to a value smaller than |size of a file| / 4K.
714     // So, each file spans multiple block groups.
715     let blocks_per_group = 128;
716     let num_groups = 50;
717     let disk = mkfs(
718         &td,
719         Builder {
720             blocks_per_group,
721             inodes_per_group: 1024,
722             size: BLOCK_SIZE * blocks_per_group * num_groups,
723             root_dir: Some(dir.clone()),
724         },
725     );
726 
727     assert_eq_dirs(&td, &dir, &disk, Some(Default::default()));
728 }
729 
730 #[test]
test_mkfs_xattr()731 fn test_mkfs_xattr() {
732     // Since tmpfs doesn't support xattr, use the current directory.
733     let td = tempdir_in(".").unwrap();
734     let dir = td.path().join("testdata");
735     // testdata
736     // ├── a.txt ("user.foo"="a", "user.bar"="0123456789")
737     // ├── b.txt ("security.selinux"="unconfined_u:object_r:user_home_t:s0")
738     // ├── c.txt (no xattr)
739     // └── dir/ ("user.foo"="directory")
740     //     └─ d.txt ("user.foo"="in_directory")
741     std::fs::create_dir(&dir).unwrap();
742 
743     let dir_xattrs = vec![("dir".to_string(), vec![("user.foo", "directory")])];
744     let file_xattrs = vec![
745         (
746             "a.txt".to_string(),
747             vec![("user.foo", "a"), ("user.number", "0123456789")],
748         ),
749         (
750             "b.txt".to_string(),
751             vec![("security.selinux", "unconfined_u:object_r:user_home_t:s0")],
752         ),
753         ("c.txt".to_string(), vec![]),
754         ("dir/d.txt".to_string(), vec![("user.foo", "in_directory")]),
755     ];
756 
757     // Create dirs
758     for (fname, xattrs) in &dir_xattrs {
759         let f_path = dir.join(fname);
760         std::fs::create_dir(&f_path).unwrap();
761         for (key, value) in xattrs {
762             ext2::set_xattr(&f_path, key, value).unwrap();
763         }
764     }
765     // Create files
766     for (fname, xattrs) in &file_xattrs {
767         let f_path = dir.join(fname);
768         File::create(&f_path).unwrap();
769         for (key, value) in xattrs {
770             ext2::set_xattr(&f_path, key, value).unwrap();
771         }
772     }
773 
774     let xattr_map: BTreeMap<String, Vec<(&str, &str)>> =
775         file_xattrs.into_iter().chain(dir_xattrs).collect();
776 
777     let builder = Builder {
778         root_dir: Some(dir.clone()),
779         ..Default::default()
780     };
781     let disk = mkfs(&td, builder);
782 
783     assert_eq_dirs(&td, &dir, &disk, Some(xattr_map));
784 }
785