xref: /aosp_15_r20/external/crosvm/e2e_tests/tests/pmem_ext2.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 //! 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