Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/e2e_tests/tests/fs.rs
5394 views
1
// Copyright 2023 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 virtio-fs.
6
7
#![cfg(any(target_os = "android", target_os = "linux"))]
8
9
use std::path::Path;
10
11
use fixture::vhost_user::CmdType;
12
use fixture::vhost_user::Config as VuConfig;
13
use fixture::vhost_user::VhostUserBackend;
14
use fixture::vm::Config;
15
use fixture::vm::TestVm;
16
use tempfile::NamedTempFile;
17
use tempfile::TempDir;
18
19
/// Tests file copy
20
///
21
/// 1. Create `original.txt` on a temporal directory.
22
/// 2. Start a VM with a virtiofs device for the temporal directory.
23
/// 3. Copy `original.txt` to `new.txt` in the guest.
24
/// 4. Check that `new.txt` is created in the host.
25
fn copy_file(mut vm: TestVm, tag: &str, dir: TempDir) {
26
const ORIGINAL_FILE_NAME: &str = "original.txt";
27
const NEW_FILE_NAME: &str = "new.txt";
28
const TEST_DATA: &str = "virtiofs works!";
29
30
let orig_file = dir.path().join(ORIGINAL_FILE_NAME);
31
32
std::fs::write(orig_file, TEST_DATA).unwrap();
33
34
// TODO(b/269137600): Split this into multiple lines instead of connecting commands with `&&`.
35
vm.exec_in_guest(&format!(
36
"mount -t virtiofs {tag} /mnt && cp /mnt/{ORIGINAL_FILE_NAME} /mnt/{NEW_FILE_NAME} && sync",
37
))
38
.unwrap();
39
40
let new_file = dir.path().join(NEW_FILE_NAME);
41
let contents = std::fs::read(new_file).unwrap();
42
assert_eq!(TEST_DATA.as_bytes(), &contents);
43
}
44
45
/// Tests mount/read/create/write
46
/// 1. Create `read_file.txt` with test data in host's temporal directory.
47
/// 2. Start a VM with a virtiofs device for the temporal directory.
48
/// 3. Guest reads read_file.txt file & verify the content is test data
49
/// 4. Guest creates a write_file.txt file in shared directory
50
/// 5. Host reads file from host's temporal directory & verify content is test data
51
fn mount_rw(mut vm: TestVm, tag: &str, dir: TempDir) {
52
const READ_FILE_NAME: &str = "read_test.txt";
53
const WRITE_FILE_NAME: &str = "write_test.txt";
54
const TEST_DATA: &str = "hello world";
55
56
let read_test_file = dir.path().join(READ_FILE_NAME);
57
let write_test_file = dir.path().join(WRITE_FILE_NAME);
58
std::fs::write(read_test_file, TEST_DATA).unwrap();
59
60
assert_eq!(
61
vm.exec_in_guest(&format!(
62
"mount -t virtiofs {tag} /mnt && cat /mnt/read_test.txt"
63
))
64
.unwrap()
65
.stdout
66
.trim(),
67
TEST_DATA
68
);
69
70
const IN_FS_WRITE_FILE_PATH: &str = "/mnt/write_test.txt";
71
let _ = vm.exec_in_guest(&format!("echo -n {TEST_DATA} > {IN_FS_WRITE_FILE_PATH}"));
72
let read_contents = std::fs::read(write_test_file).unwrap();
73
assert_eq!(TEST_DATA.as_bytes(), &read_contents);
74
}
75
76
#[test]
77
fn fs_copy_file() {
78
let tag = "mtdtest";
79
let temp_dir = tempfile::tempdir().unwrap();
80
81
let config = Config::new().extra_args(vec![
82
"--shared-dir".to_string(),
83
format!(
84
"{}:{tag}:type=fs:cache=auto",
85
temp_dir.path().to_str().unwrap()
86
),
87
]);
88
89
let vm = TestVm::new(config).unwrap();
90
copy_file(vm, tag, temp_dir)
91
}
92
93
#[test]
94
fn fs_mount_rw() {
95
let tag = "mtdtest";
96
let temp_dir = tempfile::tempdir().unwrap();
97
98
let config = Config::new().extra_args(vec![
99
"--shared-dir".to_string(),
100
format!(
101
"{}:{tag}:type=fs:cache=auto",
102
temp_dir.path().to_str().unwrap()
103
),
104
]);
105
106
let vm = TestVm::new(config).unwrap();
107
mount_rw(vm, tag, temp_dir)
108
}
109
110
/// Tests file ownership seen by the VM.
111
///
112
/// 1. Create `user_file.txt` owned by the current user of the host on a temporal directory.
113
/// 2. Set virtiofs options: uidmap=<mapped-uid> <current-uid> 1, uid=<mapped-uid>.
114
/// 3. Start a VM with a virtiofs device for the temporal directory.
115
/// 4. Check that `user_file.txt`'s uid is <mapped-uid> in the VM.
116
/// 5. Verify gid similarly.
117
#[test]
118
fn file_ugid() {
119
const FILE_NAME: &str = "user_file.txt";
120
let uid = base::geteuid();
121
let gid = base::getegid();
122
let mapped_uid: u32 = rand::random();
123
let mapped_gid: u32 = rand::random();
124
let uid_map: String = format!("{mapped_uid} {uid} 1");
125
let gid_map = format!("{mapped_gid} {gid} 1");
126
127
let temp_dir = tempfile::tempdir().unwrap();
128
let orig_file = temp_dir.path().join(FILE_NAME);
129
130
std::fs::write(orig_file, "").unwrap();
131
132
let tag = "mtdtest";
133
134
let config = Config::new().extra_args(vec![
135
"--shared-dir".to_string(),
136
format!(
137
"{}:{tag}:type=fs:uidmap={}:gidmap={}:uid={}:gid={}",
138
temp_dir.path().to_str().unwrap(),
139
uid_map,
140
gid_map,
141
mapped_uid,
142
mapped_gid
143
),
144
]);
145
146
let mut vm = TestVm::new(config).unwrap();
147
vm.exec_in_guest(&format!("mount -t virtiofs {tag} /mnt"))
148
.unwrap();
149
let output = vm
150
.exec_in_guest(&format!("stat /mnt/{FILE_NAME}",))
151
.unwrap();
152
// stat output example:
153
// File: /mnt/user_file.txt
154
// Size: 0 Blocks: 0 IO Block: 4096 regular empty file
155
// Device: 0,11 Inode: 11666031 Links: 1
156
// Access: (0640/-rw-r-----) Uid: (2350626183/ UNKNOWN) Gid: (949179291/ UNKNOWN)
157
// Access: 2023-04-05 03:06:27.110144457 +0000
158
// Modify: 2023-04-05 03:06:27.110144457 +0000
159
// Change: 2023-04-05 03:06:27.110144457 +0000
160
assert!(output.stdout.contains(&format!("Uid: ({mapped_uid}/")));
161
assert!(output.stdout.contains(&format!("Gid: ({mapped_gid}/")));
162
}
163
164
pub fn create_vu_fs_config(socket: &Path, shared_dir: &Path, tag: &str) -> VuConfig {
165
let uid = base::geteuid();
166
let gid = base::getegid();
167
let socket_path = socket.to_str().unwrap();
168
let shared_dir_path = shared_dir.to_str().unwrap();
169
println!("socket={socket_path}, tag={tag}, shared_dir={shared_dir_path}");
170
VuConfig::new(CmdType::Device, "vhost-user-fs").extra_args(vec![
171
"fs".to_string(),
172
format!("--socket-path={socket_path}"),
173
format!("--shared-dir={shared_dir_path}"),
174
format!("--tag={tag}"),
175
format!("--uid-map=0 {uid} 1"),
176
format!("--gid-map=0 {gid} 1"),
177
])
178
}
179
180
/// Tests vhost-user fs device copy file.
181
#[test]
182
fn vhost_user_fs_copy_file() {
183
let socket = NamedTempFile::new().unwrap();
184
let temp_dir = tempfile::tempdir().unwrap();
185
186
let config = Config::new();
187
let tag = "mtdtest";
188
189
let vu_config = create_vu_fs_config(socket.path(), temp_dir.path(), tag);
190
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
191
192
let config = config.with_vhost_user("fs", socket.path());
193
let vm = TestVm::new(config).unwrap();
194
195
copy_file(vm, tag, temp_dir);
196
}
197
198
/// Tests vhost-user fs device mount and read write.
199
#[test]
200
fn vhost_user_fs_mount_rw() {
201
let socket = NamedTempFile::new().unwrap();
202
let temp_dir = tempfile::tempdir().unwrap();
203
204
let config = Config::new();
205
let tag = "mtdtest";
206
207
let vu_config = create_vu_fs_config(socket.path(), temp_dir.path(), tag);
208
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
209
210
let config = config.with_vhost_user("fs", socket.path());
211
let vm = TestVm::new(config).unwrap();
212
213
mount_rw(vm, tag, temp_dir);
214
}
215
216
fn copy_file_validate_ugid_mapping(
217
mut vm: TestVm,
218
tag: &str,
219
dir: TempDir,
220
mapped_uid: u32,
221
mapped_gid: u32,
222
) {
223
use std::os::linux::fs::MetadataExt;
224
const ORIGINAL_FILE_NAME: &str = "original.txt";
225
const NEW_FILE_NAME: &str = "new.txt";
226
const TEST_DATA: &str = "Hello world!";
227
228
let orig_file = dir.path().join(ORIGINAL_FILE_NAME);
229
230
std::fs::write(orig_file, TEST_DATA).unwrap();
231
232
vm.exec_in_guest(&format!(
233
"mount -t virtiofs {tag} /mnt && cp /mnt/{ORIGINAL_FILE_NAME} /mnt/{NEW_FILE_NAME} && sync",
234
))
235
.unwrap();
236
237
let output = vm
238
.exec_in_guest(&format!("stat /mnt/{ORIGINAL_FILE_NAME}",))
239
.unwrap();
240
241
assert!(output.stdout.contains(&format!("Uid: ({mapped_uid}/")));
242
assert!(output.stdout.contains(&format!("Gid: ({mapped_gid}/")));
243
244
let new_file = dir.path().join(NEW_FILE_NAME);
245
let output_stat = std::fs::metadata(new_file.clone());
246
247
assert_eq!(
248
output_stat
249
.as_ref()
250
.expect("stat of new_file failed")
251
.st_uid(),
252
base::geteuid()
253
);
254
assert_eq!(
255
output_stat
256
.as_ref()
257
.expect("stat of new_file failed")
258
.st_gid(),
259
base::getegid()
260
);
261
262
let contents = std::fs::read(new_file).unwrap();
263
assert_eq!(TEST_DATA.as_bytes(), &contents);
264
}
265
266
pub fn create_ugid_map_config(
267
socket: &Path,
268
shared_dir: &Path,
269
tag: &str,
270
mapped_uid: u32,
271
mapped_gid: u32,
272
) -> VuConfig {
273
let socket_path = socket.to_str().unwrap();
274
let shared_dir_path = shared_dir.to_str().unwrap();
275
276
let uid = base::geteuid();
277
let gid = base::getegid();
278
let ugid_map_value = format!("{mapped_uid} {mapped_gid} {uid} {gid} 7 /",);
279
280
let cfg_arg = format!("writeback=true,ugid_map='{ugid_map_value}'");
281
282
println!("socket={socket_path}, tag={tag}, shared_dir={shared_dir_path}");
283
284
VuConfig::new(CmdType::Device, "vhost-user-fs").extra_args(vec![
285
"fs".to_string(),
286
format!("--socket-path={socket_path}"),
287
format!("--shared-dir={shared_dir_path}"),
288
format!("--tag={tag}"),
289
format!("--cfg={cfg_arg}"),
290
format!("--disable-sandbox"),
291
format!("--skip-pivot-root=true"),
292
])
293
}
294
295
/// Tests file copy with disabled sandbox
296
///
297
/// 1. Create `original.txt` on a temporal directory.
298
/// 2. Setup ugid_map for vhost-user-fs backend
299
/// 3. Start a VM with a virtiofs device for the temporal directory.
300
/// 4. Copy `original.txt` to `new.txt` in the guest.
301
/// 5. Check that `new.txt` is created in the host.
302
/// 6. Verify the UID/GID of the files both in the guest and the host.
303
#[test]
304
fn vhost_user_fs_without_sandbox_and_pivot_root() {
305
let socket = NamedTempFile::new().unwrap();
306
let temp_dir = tempfile::tempdir().unwrap();
307
308
let config = Config::new();
309
let tag = "android";
310
311
let mapped_uid = 123456;
312
let mapped_gid = 12345;
313
let vu_config =
314
create_ugid_map_config(socket.path(), temp_dir.path(), tag, mapped_uid, mapped_gid);
315
316
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
317
318
let config = config.with_vhost_user("fs", socket.path());
319
let vm = TestVm::new(config).unwrap();
320
321
copy_file_validate_ugid_mapping(vm, tag, temp_dir, mapped_uid, mapped_gid);
322
}
323
324