Skip to content

Commit 367ae98

Browse files
committed
virtio/fs/macos: fix fallocate semantics
The translation of Linux's fallocate semantics to macOS was badly broken. While we can't fully preserve the original semantics, since macOS doesn't have posix_fallocate and F_PREALLOCATE doesn't allow to allocate arbitrary ranges, we can do something close enough to work fine for the vast majority of guest applications. Signed-off-by: Sergio Lopez <[email protected]>
1 parent ab8946a commit 367ae98

2 files changed

Lines changed: 74 additions & 27 deletions

File tree

src/devices/src/virtio/bindings.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ pub const LINUX_RENAME_WHITEOUT: libc::c_int = 1 << 2;
2929
pub const LINUX_XATTR_CREATE: libc::c_int = 1;
3030
pub const LINUX_XATTR_REPLACE: libc::c_int = 2;
3131

32+
pub const LINUX_FALLOC_FL_ALLOCATE_RANGE: libc::c_int = 0;
33+
pub const LINUX_FALLOC_FL_KEEP_SIZE: libc::c_int = 1;
34+
pub const LINUX_FALLOC_FL_PUNCH_HOLE: libc::c_int = 2;
35+
3236
#[cfg(target_os = "macos")]
3337
pub type stat64 = libc::stat;
3438
#[cfg(target_os = "linux")]

src/devices/src/virtio/fs/macos/passthrough.rs

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2209,7 +2209,7 @@ impl FileSystem for PassthroughFs {
22092209
_ctx: Context,
22102210
inode: Inode,
22112211
handle: Handle,
2212-
_mode: u32,
2212+
mode: u32,
22132213
offset: u64,
22142214
length: u64,
22152215
) -> io::Result<()> {
@@ -2224,36 +2224,79 @@ impl FileSystem for PassthroughFs {
22242224

22252225
let fd = data.file.write().unwrap().as_raw_fd();
22262226

2227-
let proposed_length = (offset + length) as i64;
2228-
let mut fs = libc::fstore_t {
2229-
fst_flags: libc::F_ALLOCATECONTIG,
2230-
fst_posmode: libc::F_PEOFPOSMODE,
2231-
fst_offset: 0,
2232-
fst_length: proposed_length,
2233-
fst_bytesalloc: 0,
2234-
};
2235-
2236-
let res = unsafe { libc::fcntl(fd, libc::F_PREALLOCATE, &mut fs as *mut _) };
2237-
if res < 0 {
2238-
fs.fst_flags = libc::F_ALLOCATEALL;
2239-
let res = unsafe { libc::fcntl(fd, libc::F_PREALLOCATE, &mut fs as &mut _) };
2240-
if res < 0 {
2241-
return Err(linux_error(io::Error::last_os_error()));
2227+
const SUPPORTED_FLAGS: i32 = bindings::LINUX_FALLOC_FL_ALLOCATE_RANGE
2228+
| bindings::LINUX_FALLOC_FL_KEEP_SIZE
2229+
| bindings::LINUX_FALLOC_FL_PUNCH_HOLE;
2230+
2231+
if mode as i32 & !SUPPORTED_FLAGS != 0 {
2232+
return Err(linux_error(io::Error::from_raw_os_error(libc::EOPNOTSUPP)));
2233+
}
2234+
2235+
let keep_size = mode & bindings::LINUX_FALLOC_FL_KEEP_SIZE as u32 != 0;
2236+
let mode = mode & !bindings::LINUX_FALLOC_FL_KEEP_SIZE as u32;
2237+
2238+
match mode as i32 {
2239+
bindings::LINUX_FALLOC_FL_ALLOCATE_RANGE => {
2240+
// The closest thing we have on macOS to posix_fallocate is F_PREALLOCATE,
2241+
// but this one doesn't allow us to allocate arbitrary ranges, only allocate
2242+
// blocks to the file's end.
2243+
//
2244+
// The best thing we can do here is extend the file to (offset + length).
2245+
// This doesn't adhere to the same semantics, but should work fine (albeit
2246+
// less performant) for most guest applications.
2247+
let st = fstat(fd, true)?;
2248+
let new_length = (offset + length) as i64;
2249+
2250+
if keep_size {
2251+
// Check the number of allocated blocks instead of the file size.
2252+
let disk_size = st.st_blocks * 512_i64;
2253+
if disk_size >= new_length {
2254+
return Ok(());
2255+
}
2256+
let mut fs = libc::fstore_t {
2257+
fst_flags: libc::F_ALLOCATEALL,
2258+
fst_posmode: libc::F_PEOFPOSMODE,
2259+
fst_offset: 0,
2260+
fst_length: new_length - disk_size,
2261+
fst_bytesalloc: 0,
2262+
};
2263+
2264+
let res = unsafe { libc::fcntl(fd, libc::F_PREALLOCATE, &mut fs as *mut _) };
2265+
if res < 0 {
2266+
return Err(linux_error(io::Error::last_os_error()));
2267+
}
2268+
} else {
2269+
if st.st_size >= new_length {
2270+
return Ok(());
2271+
}
2272+
let res = unsafe { libc::ftruncate(fd, new_length) };
2273+
if res < 0 {
2274+
return Err(linux_error(io::Error::last_os_error()));
2275+
}
2276+
}
22422277
}
2243-
}
2278+
bindings::LINUX_FALLOC_FL_PUNCH_HOLE => {
2279+
if !keep_size {
2280+
// Linux forbids the use of PUNCH_HOLE without KEEP_SIZE.
2281+
return Err(linux_error(io::Error::from_raw_os_error(libc::EINVAL)));
2282+
}
22442283

2245-
let st = fstat(fd, true)?;
2246-
if st.st_size >= proposed_length {
2247-
// fallocate should not shrink the file. The file is already larger than needed.
2248-
return Ok(());
2249-
}
2250-
let res = unsafe { libc::ftruncate(fd, proposed_length) };
2284+
let mut hole = libc::fpunchhole_t {
2285+
fp_offset: offset as i64,
2286+
fp_flags: 0,
2287+
reserved: 0,
2288+
fp_length: length as i64,
2289+
};
22512290

2252-
if res == 0 {
2253-
Ok(())
2254-
} else {
2255-
Err(linux_error(io::Error::last_os_error()))
2291+
let res = unsafe { libc::fcntl(fd, libc::F_PUNCHHOLE, &mut hole as *mut _) };
2292+
if res < 0 {
2293+
return Err(linux_error(io::Error::last_os_error()));
2294+
}
2295+
}
2296+
_ => unreachable!(),
22562297
}
2298+
2299+
Ok(())
22572300
}
22582301

22592302
fn lseek(

0 commit comments

Comments
 (0)