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