Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/ext2/tests/tests.rs
5394 views
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
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
51
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
69
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]
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]
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
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
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}): {path1:?}->{dst1:?} vs {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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
731
#[ignore = "Called by test_mkfs_xattr"]
732
fn test_mkfs_xattr_impl() {
733
// Since tmpfs doesn't support xattr, use the current directory.
734
let td = tempdir_in(".").unwrap();
735
let dir = td.path().join("testdata");
736
// testdata
737
// ├── a.txt ("user.foo"="a", "user.bar"="0123456789")
738
// ├── b.txt ("security.selinux"="unconfined_u:object_r:user_home_t:s0")
739
// ├── c.txt (no xattr)
740
// └── dir/ ("user.foo"="directory")
741
// └─ d.txt ("user.foo"="in_directory")
742
std::fs::create_dir(&dir).unwrap();
743
744
let dir_xattrs = vec![("dir".to_string(), vec![("user.foo", "directory")])];
745
let file_xattrs = vec![
746
(
747
"a.txt".to_string(),
748
vec![("user.foo", "a"), ("user.number", "0123456789")],
749
),
750
(
751
"b.txt".to_string(),
752
vec![("security.selinux", "unconfined_u:object_r:user_home_t:s0")],
753
),
754
("c.txt".to_string(), vec![]),
755
("dir/d.txt".to_string(), vec![("user.foo", "in_directory")]),
756
];
757
758
// Create dirs
759
for (fname, xattrs) in &dir_xattrs {
760
let f_path = dir.join(fname);
761
std::fs::create_dir(&f_path).unwrap();
762
for (key, value) in xattrs {
763
ext2::set_xattr(&f_path, key, value).unwrap();
764
}
765
}
766
// Create files
767
for (fname, xattrs) in &file_xattrs {
768
let f_path = dir.join(fname);
769
File::create(&f_path).unwrap();
770
for (key, value) in xattrs {
771
ext2::set_xattr(&f_path, key, value).unwrap();
772
}
773
}
774
775
let xattr_map: BTreeMap<String, Vec<(&str, &str)>> =
776
file_xattrs.into_iter().chain(dir_xattrs).collect();
777
778
let builder = Builder {
779
root_dir: Some(dir.clone()),
780
..Default::default()
781
};
782
let disk = mkfs(&td, builder);
783
784
assert_eq_dirs(&td, &dir, &disk, Some(xattr_map));
785
}
786
787
#[test]
788
fn test_mkfs_xattr() {
789
call_test_with_sudo("test_mkfs_xattr_impl")
790
}
791
792