Skip to content

Commit 69b92cc

Browse files
fogtijounathaen
authored andcommitted
feat(isolation/fd): implement virtual file descriptors
1 parent c1b5916 commit 69b92cc

File tree

5 files changed

+372
-108
lines changed

5 files changed

+372
-108
lines changed

Cargo.lock

Lines changed: 39 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,14 @@ rand = { version = "0.9", optional = true }
6666
shell-words = "1"
6767
sysinfo = { version = "0.37.2", default-features = false, features = ["system"] }
6868
vm-fdt = "0.3"
69-
tempfile = "3.23.0"
69+
tempfile = "3.23"
7070
uuid = { version = "1.18.1", features = ["fast-rng", "v4"]}
7171
toml = "0.9.8"
7272
serde = { version = "1.0", features = ["derive"] }
7373
merge = { version = "0.2" }
74+
yoke = "0.8"
75+
slab = { version = "0.4", optional = true }
76+
nohash = "0.2.0"
7477

7578
[target.'cfg(target_os = "linux")'.dependencies]
7679
kvm-bindings = "0.14"

src/hypercall.rs

Lines changed: 128 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use core::cmp;
12
use std::{
23
ffi::{CStr, CString, OsStr},
34
io::{self, Error, ErrorKind},
@@ -7,7 +8,10 @@ use std::{
78
use uhyve_interface::{GuestPhysAddr, Hypercall, HypercallAddress, MAX_ARGC_ENVC, parameters::*};
89

910
use crate::{
10-
isolation::filemap::UhyveFileMap,
11+
isolation::{
12+
fd::{FdData, GuestFd},
13+
filemap::UhyveFileMap,
14+
},
1115
mem::{MemoryError, MmapMemory},
1216
params::EnvVars,
1317
virt_to_phys,
@@ -109,16 +113,21 @@ pub fn open(mem: &MmapMemory, sysopen: &mut OpenParams, file_map: &mut UhyveFile
109113
return;
110114
}
111115

112-
if let Some(host_path) = file_map.get_host_path(guest_path) {
116+
sysopen.ret = if let Some(host_path) = file_map.get_host_path(guest_path) {
113117
debug!("{guest_path:#?} found in file map.");
114118
// We can safely unwrap here, as host_path.as_bytes will never contain internal \0 bytes
115119
// As host_path_c_string is a valid CString, this implementation is presumed to be safe.
116120
let host_path_c_string = CString::new(host_path.as_bytes()).unwrap();
117121

118-
sysopen.ret =
122+
let host_fd =
119123
unsafe { libc::open(host_path_c_string.as_c_str().as_ptr(), flags, sysopen.mode) };
120-
121-
file_map.fdmap.insert_fd(sysopen.ret);
124+
if let Some(guest_fd) = file_map.fdmap.insert(FdData::Raw(host_fd)) {
125+
guest_fd.0
126+
} else if host_fd < 0 {
127+
host_fd
128+
} else {
129+
-ENOENT
130+
}
122131
} else {
123132
debug!("{guest_path:#?} not found in file map.");
124133
if (flags & O_CREAT) == O_CREAT {
@@ -130,29 +139,35 @@ pub fn open(mem: &MmapMemory, sysopen: &mut OpenParams, file_map: &mut UhyveFile
130139

131140
let host_path_c_string = file_map.create_temporary_file(guest_path);
132141
let new_host_path = host_path_c_string.as_c_str().as_ptr();
133-
sysopen.ret = unsafe { libc::open(new_host_path, flags, sysopen.mode) };
134-
file_map.fdmap.insert_fd(sysopen.ret.into_raw_fd());
142+
let host_fd = unsafe { libc::open(new_host_path, flags, sysopen.mode) };
143+
if let Some(guest_fd) = file_map.fdmap.insert(FdData::Raw(host_fd)) {
144+
guest_fd.0
145+
} else if host_fd < 0 {
146+
host_fd
147+
} else {
148+
-ENOENT
149+
}
135150
} else {
136151
debug!("Returning -ENOENT for {guest_path:#?}");
137-
sysopen.ret = -ENOENT;
152+
-ENOENT
138153
}
139154
}
140155
}
141156

142157
/// Handles an close syscall by closing the file on the host.
143158
pub fn close(sysclose: &mut CloseParams, file_map: &mut UhyveFileMap) {
144-
if file_map.fdmap.is_fd_present(sysclose.fd.into_raw_fd()) {
145-
if sysclose.fd > 2 {
146-
unsafe { sysclose.ret = libc::close(sysclose.fd) }
147-
file_map.fdmap.remove_fd(sysclose.fd)
148-
} else {
149-
// Ignore closes of stdin, stdout and stderr that would
150-
// otherwise affect Uhyve
151-
sysclose.ret = 0
159+
sysclose.ret = if file_map
160+
.fdmap
161+
.is_fd_present(GuestFd(sysclose.fd.into_raw_fd()))
162+
{
163+
match file_map.fdmap.remove(GuestFd(sysclose.fd)) {
164+
Some(FdData::Raw(fd)) => unsafe { libc::close(fd) },
165+
// ignore other closures (fdmap's remove already handles stdio)
166+
_ => 0,
152167
}
153168
} else {
154-
sysclose.ret = -EBADF
155-
}
169+
-EBADF
170+
};
156171
}
157172

158173
/// Handles a read syscall on the host.
@@ -162,25 +177,45 @@ pub fn read(
162177
root_pt: GuestPhysAddr,
163178
file_map: &mut UhyveFileMap,
164179
) {
165-
if file_map.fdmap.is_fd_present(sysread.fd.into_raw_fd()) {
180+
sysread.ret = if let Some(fdata) = file_map.fdmap.get_mut(GuestFd(sysread.fd.into_raw_fd())) {
166181
let guest_phys_addr = virt_to_phys(sysread.buf, mem, root_pt);
167182
if let Ok(guest_phys_addr) = guest_phys_addr
168183
&& let Ok(host_address) = mem.host_address(guest_phys_addr)
169184
{
170-
let bytes_read =
171-
unsafe { libc::read(sysread.fd, host_address as *mut libc::c_void, sysread.len) };
172-
if bytes_read >= 0 {
173-
sysread.ret = bytes_read;
174-
} else {
175-
sysread.ret = -1
185+
match fdata {
186+
FdData::Raw(rfd) => {
187+
let bytes_read =
188+
unsafe { libc::read(*rfd, host_address as *mut libc::c_void, sysread.len) };
189+
if bytes_read >= 0 { bytes_read } else { -1 }
190+
}
191+
FdData::Virtual { data, offset } => {
192+
let data: &[u8] = data.get();
193+
let remaining = {
194+
let pos = cmp::min(*offset, data.len() as u64);
195+
&data[pos as usize..]
196+
};
197+
let amt = cmp::min(remaining.len() as u64, sysread.len as u64) as usize;
198+
assert!(amt <= isize::MAX as usize);
199+
200+
// SAFETY: the input slices can't overlap, as `host_address` is owned by the guest
201+
// and `data` is owned by the host.
202+
unsafe {
203+
core::ptr::copy_nonoverlapping(
204+
remaining.as_ptr(),
205+
host_address as *mut u8,
206+
amt,
207+
)
208+
};
209+
amt as isize
210+
}
176211
}
177212
} else {
178213
warn!("Unable to get host address for read buffer");
179-
sysread.ret = -EFAULT as isize;
214+
-EFAULT as isize
180215
}
181216
} else {
182-
sysread.ret = -EBADF as isize;
183-
}
217+
-EBADF as isize
218+
};
184219
}
185220

186221
/// Handles an write syscall on the host.
@@ -191,12 +226,21 @@ pub fn write(
191226
file_map: &mut UhyveFileMap,
192227
) -> io::Result<()> {
193228
let mut bytes_written: usize = 0;
229+
let gfd = GuestFd(syswrite.fd.into_raw_fd());
230+
if !file_map.fdmap.is_fd_present(gfd) {
231+
// We don't write anything if the file descriptor is not available,
232+
// but this is OK for now, as we have no means of returning an error code
233+
// and writes are not necessarily guaranteed to write anything.
234+
return Ok(());
235+
}
236+
194237
while bytes_written != syswrite.len {
195238
let guest_phys_addr = virt_to_phys(
196239
syswrite.buf + bytes_written as u64,
197240
&peripherals.mem,
198241
root_pt,
199242
);
243+
let guest_phys_len = syswrite.len - bytes_written;
200244

201245
if let Ok(guest_phys_addr) = guest_phys_addr {
202246
if syswrite.fd == 1 || syswrite.fd == 2 {
@@ -212,36 +256,38 @@ pub fn write(
212256
})?
213257
};
214258
return peripherals.serial.output(bytes);
215-
} else if !file_map.fdmap.is_fd_present(syswrite.fd.into_raw_fd()) {
216-
// We don't write anything if the file descriptor is not available,
217-
// but this is OK for now, as we have no means of returning an error code
218-
// and writes are not necessarily guaranteed to write anything.
219-
return Ok(());
220259
}
221260
} else {
222261
return Ok(());
223262
}
224263

225-
unsafe {
226-
let step = libc::write(
227-
syswrite.fd,
228-
peripherals
229-
.mem
230-
.host_address(guest_phys_addr.unwrap())
231-
.map_err(|e| match e {
232-
MemoryError::BoundsViolation => {
233-
unreachable!("Bounds violation after host_address function")
234-
}
235-
MemoryError::WrongMemoryError => {
236-
Error::new(ErrorKind::AddrNotAvailable, e.to_string())
237-
}
238-
})? as *const libc::c_void,
239-
syswrite.len - bytes_written,
240-
);
241-
if step >= 0 {
242-
bytes_written += step as usize;
243-
} else {
244-
return Err(io::Error::last_os_error());
264+
let host_address = peripherals
265+
.mem
266+
.host_address(guest_phys_addr.unwrap())
267+
.map_err(|e| match e {
268+
MemoryError::BoundsViolation => {
269+
unreachable!("Bounds violation after host_address function")
270+
}
271+
MemoryError::WrongMemoryError => {
272+
Error::new(ErrorKind::AddrNotAvailable, e.to_string())
273+
}
274+
})?;
275+
276+
match file_map.fdmap.get_mut(gfd).unwrap() {
277+
FdData::Raw(r) => unsafe {
278+
let step = libc::write(*r, host_address as *const libc::c_void, guest_phys_len);
279+
if step >= 0 {
280+
bytes_written += step as usize;
281+
} else {
282+
return Err(io::Error::last_os_error());
283+
}
284+
},
285+
FdData::Virtual { .. } => {
286+
// virtual fds are read-only
287+
return Err(io::Error::new(
288+
io::ErrorKind::ReadOnlyFilesystem,
289+
format!("Unable to write to virtual file {gfd}"),
290+
));
245291
}
246292
}
247293
}
@@ -251,16 +297,36 @@ pub fn write(
251297

252298
/// Handles an lseek syscall on the host.
253299
pub fn lseek(syslseek: &mut LseekParams, file_map: &mut UhyveFileMap) {
254-
if file_map.fdmap.is_fd_present(syslseek.fd.into_raw_fd()) {
255-
unsafe {
256-
syslseek.offset =
257-
libc::lseek(syslseek.fd, syslseek.offset as i64, syslseek.whence) as isize;
300+
syslseek.offset = match file_map.fdmap.get_mut(GuestFd(syslseek.fd.into_raw_fd())) {
301+
Some(FdData::Raw(r)) => unsafe {
302+
libc::lseek(*r, syslseek.offset as i64, syslseek.whence) as isize
303+
},
304+
Some(FdData::Virtual { data, offset }) => {
305+
let tmp = match syslseek.whence {
306+
SEEK_SET => 0,
307+
SEEK_CUR => *offset as isize,
308+
SEEK_END => data.get().len() as isize,
309+
_ => -1,
310+
};
311+
if tmp >= 0 {
312+
let tmp2 = (tmp as i64) + (syslseek.offset as i64);
313+
match tmp2.try_into() {
314+
Ok(tmp3) => {
315+
*offset = tmp3;
316+
tmp2 as isize
317+
}
318+
_ => -1,
319+
}
320+
} else {
321+
tmp
322+
}
258323
}
259-
} else {
260-
// TODO: Return -EBADF to the ret field, as soon as it is implemented for LseekParams
261-
warn!("lseek attempted to use an unknown file descriptor");
262-
syslseek.offset = -1
263-
}
324+
None => {
325+
// TODO: Return -EBADF to the ret field, as soon as it is implemented for LseekParams
326+
warn!("lseek attempted to use an unknown file descriptor");
327+
-1
328+
}
329+
};
264330
}
265331

266332
/// Copies the arguments of the application into the VM's memory to the destinations specified in `syscmdval`.

0 commit comments

Comments
 (0)