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 //! Testing pmem-ext2 device.
6
7 #![cfg(any(target_os = "android", target_os = "linux"))]
8
9 use std::os::unix::fs::symlink;
10
11 use fixture::vm::Config;
12 use fixture::vm::TestVm;
13
14 /// Check file contents on pmem-ext2
15 #[test]
pmem_ext2() -> anyhow::Result<()>16 fn pmem_ext2() -> anyhow::Result<()> {
17 // /temp_dir/
18 // ├── a.txt
19 // └── dir
20 // ├── b.txt
21 // └── symlink_a -> ../a.txt
22
23 const A_TXT_NAME: &str = "a.txt";
24 const A_TXT_DATA: &str = "Hello!";
25 const DIR_NAME: &str = "dir";
26 const B_TXT_NAME: &str = "b.txt";
27 const B_TXT_DATA: &str = "test test test\ntest test test";
28 const SYMLINK_A_NAME: &str = "symlink_a";
29 const SYMLINK_A_DEST: &str = "../a.txt";
30
31 let temp_dir = tempfile::tempdir()?;
32 let a_txt = temp_dir.path().join(A_TXT_NAME);
33 std::fs::write(a_txt, A_TXT_DATA)?;
34 let dir = temp_dir.path().join(DIR_NAME);
35 std::fs::create_dir(&dir)?;
36 let b_txt = dir.join(B_TXT_NAME);
37 std::fs::write(b_txt, B_TXT_DATA)?;
38 let symlink_a = dir.join(SYMLINK_A_NAME);
39 symlink(SYMLINK_A_DEST, symlink_a)?;
40
41 let config = Config::new().extra_args(vec![
42 "--pmem-ext2".to_string(),
43 temp_dir.path().to_str().unwrap().to_string(),
44 ]);
45
46 let mut vm = TestVm::new(config)?;
47 vm.exec_in_guest("mount -t ext2 /dev/pmem0 /mnt/")?;
48
49 // List all files
50 let find_result = vm
51 .exec_in_guest_async("find /mnt/ | sort")?
52 .with_timeout(std::time::Duration::from_secs(1))
53 .wait_ok(&mut vm)?;
54 assert_eq!(
55 find_result.stdout.trim(),
56 r"/mnt/
57 /mnt/a.txt
58 /mnt/dir
59 /mnt/dir/b.txt
60 /mnt/dir/symlink_a
61 /mnt/lost+found"
62 );
63
64 let a_result = vm
65 .exec_in_guest_async(&format!("cat /mnt/{A_TXT_NAME}"))?
66 .with_timeout(std::time::Duration::from_secs(1))
67 .wait_ok(&mut vm)?;
68 assert_eq!(a_result.stdout.trim(), A_TXT_DATA);
69 let b_result = vm
70 .exec_in_guest_async(&format!("cat /mnt/{DIR_NAME}/{B_TXT_NAME}"))?
71 .with_timeout(std::time::Duration::from_secs(1))
72 .wait_ok(&mut vm)?;
73 assert_eq!(b_result.stdout.trim(), B_TXT_DATA);
74
75 // Trying to read a non-existent file should return an error
76 let non_existent_result = vm
77 .exec_in_guest_async(&format!("cat /mnt/{DIR_NAME}/non-existent"))?
78 .with_timeout(std::time::Duration::from_secs(1))
79 .wait_ok(&mut vm);
80 assert!(non_existent_result.is_err());
81
82 let readlink_result = vm
83 .exec_in_guest_async(&format!("readlink /mnt/{DIR_NAME}/{SYMLINK_A_NAME}"))?
84 .with_timeout(std::time::Duration::from_secs(1))
85 .wait_ok(&mut vm)?;
86 assert_eq!(readlink_result.stdout.trim(), SYMLINK_A_DEST);
87
88 let symlink_a_result = vm
89 .exec_in_guest_async(&format!("cat /mnt/{DIR_NAME}/{SYMLINK_A_NAME}"))?
90 .with_timeout(std::time::Duration::from_secs(1))
91 .wait_ok(&mut vm)?;
92 assert_eq!(symlink_a_result.stdout.trim(), A_TXT_DATA);
93
94 Ok(())
95 }
96
97 /// Check a case with 1000 files in a directory.
98 #[test]
pmem_ext2_manyfiles() -> anyhow::Result<()>99 fn pmem_ext2_manyfiles() -> anyhow::Result<()> {
100 // /temp_dir/
101 // ├── 0.txt
102 // ...
103 // └── 999.txt
104
105 let temp_dir = tempfile::tempdir()?;
106 for i in 0..1000 {
107 let f = temp_dir.path().join(format!("{i}.txt"));
108 std::fs::write(f, format!("{i}"))?;
109 }
110
111 let config = Config::new().extra_args(vec![
112 "--pmem-ext2".to_string(),
113 temp_dir.path().to_str().unwrap().to_string(),
114 ]);
115
116 let mut vm = TestVm::new(config)?;
117 vm.exec_in_guest("mount -t ext2 /dev/pmem0 /mnt/")?;
118
119 // `ls -l` returns 1002 lines because 1000 files + 'lost+found' and the total line.
120 let ls_result = vm
121 .exec_in_guest_async("ls -l /mnt/ | wc -l")?
122 .with_timeout(std::time::Duration::from_secs(1))
123 .wait_ok(&mut vm)?;
124 assert_eq!(ls_result.stdout.trim(), "1002");
125
126 Ok(())
127 }
128
129 /// Starts pmem-ext2 device with the given uid/gid setting and share a file created by the current
130 /// user with the guest. Returns (uid, gid) in the guest.
start_with_ugid_map( uid: u32, uid_map: &str, gid: u32, gid_map: &str, ) -> anyhow::Result<(u32, u32)>131 fn start_with_ugid_map(
132 uid: u32,
133 uid_map: &str,
134 gid: u32,
135 gid_map: &str,
136 ) -> anyhow::Result<(u32, u32)> {
137 let temp_dir = tempfile::tempdir()?;
138 let a = temp_dir.path().join("a.txt");
139 std::fs::write(a, "A")?;
140
141 let dir_path = temp_dir.path().to_str().unwrap().to_string();
142 let config = Config::new().extra_args(vec![
143 "--pmem-ext2".to_string(),
144 format!("{dir_path}:uidmap={uid_map}:gidmap={gid_map}:uid={uid}:gid={gid}"),
145 ]);
146
147 let mut vm = TestVm::new(config)?;
148 vm.exec_in_guest("mount -t ext2 /dev/pmem0 /mnt/")?;
149
150 let result = vm
151 .exec_in_guest_async("stat --printf '%u %g' /mnt/a.txt")?
152 .with_timeout(std::time::Duration::from_secs(1))
153 .wait_ok(&mut vm)?;
154 let out = result.stdout.trim();
155 println!("guest ugid: {out}");
156 let ids = out
157 .split(" ")
158 .map(|s| s.parse::<u32>())
159 .collect::<Result<Vec<u32>, _>>()
160 .unwrap();
161 assert_eq!(ids.len(), 2);
162 Ok((ids[0], ids[1])) // (uid, gid)
163 }
164
geteugid() -> (u32, u32)165 fn geteugid() -> (u32, u32) {
166 // SAFETY: geteuid never fails.
167 let euid = unsafe { libc::geteuid() };
168 // SAFETY: getegid never fails.
169 let egid = unsafe { libc::getegid() };
170 (euid, egid)
171 }
172
173 /// Maps to the same id in the guest.
174 #[test]
pmem_ext2_ugid_map_identical()175 fn pmem_ext2_ugid_map_identical() {
176 let (host_uid, host_gid) = geteugid();
177
178 let uid_map = format!("{host_uid} {host_uid} 1");
179 let gid_map = format!("{host_gid} {host_gid} 1");
180 let (guest_uid, guest_gid) =
181 start_with_ugid_map(host_uid, &uid_map, host_gid, &gid_map).unwrap();
182 assert_eq!(host_uid, guest_uid);
183 assert_eq!(host_gid, guest_gid);
184 }
185
186 /// Maps to the root in the guest.
187 #[test]
pmem_ext2_ugid_map_to_root()188 fn pmem_ext2_ugid_map_to_root() {
189 let (host_uid, host_gid) = geteugid();
190
191 let uid_map = format!("0 {host_uid} 1");
192 let gid_map = format!("0 {host_gid} 1");
193 let (guest_uid, guest_gid) = start_with_ugid_map(0, &uid_map, 0, &gid_map).unwrap();
194 assert_eq!(guest_uid, 0);
195 assert_eq!(guest_gid, 0);
196 }
197
198 /// Maps to fake ids in the guest.
199 #[test]
pmem_ext2_ugid_map_fake_ids()200 fn pmem_ext2_ugid_map_fake_ids() {
201 let (host_uid, host_gid) = geteugid();
202
203 let fake_uid = 1234;
204 let fake_gid = 5678;
205
206 let uid_map = format!("{fake_uid} {host_uid} 1");
207 let gid_map = format!("{fake_gid} {host_gid} 1");
208 let (guest_uid, guest_gid) =
209 start_with_ugid_map(fake_uid, &uid_map, fake_gid, &gid_map).unwrap();
210 assert_eq!(guest_uid, fake_uid);
211 assert_eq!(guest_gid, fake_gid);
212 }
213