base: unix: find active pages in a file

lseek(2) + SEEK_DATA/SEEK_HOLE is used to find active data in a file.
The api is also effective for memfd. snapshot feature uses to traverse
active pages in the memfd backed guest memory.

BUG=b:215093219
TEST=cargo test -p base unix::file::tests

Change-Id: I9f0eb46a568dadc94231ac437dec25c3478a7d37
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3993126
Reviewed-by: David Stevens <stevensd@chromium.org>
Commit-Queue: Shin Kawamura <kawasin@google.com>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
This commit is contained in:
Shintaro Kawamura 2022-10-21 09:41:27 +09:00 committed by crosvm LUCI
parent 9406b788c3
commit 985e575e66
2 changed files with 111 additions and 0 deletions

109
base/src/sys/unix/file.rs Normal file
View file

@ -0,0 +1,109 @@
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#![deny(missing_docs)]
use std::ops::Range;
use crate::error;
use crate::AsRawDescriptor;
use crate::Error;
use crate::Result;
enum LseekOption {
Data,
Hole,
}
fn lseek(fd: &dyn AsRawDescriptor, offset: u64, option: LseekOption) -> Result<u64> {
let whence = match option {
LseekOption::Data => libc::SEEK_DATA,
LseekOption::Hole => libc::SEEK_HOLE,
};
// safe because this doesn't modify any memory.
let ret = unsafe { libc::lseek64(fd.as_raw_descriptor(), offset as i64, whence) };
if ret < 0 {
return Err(Error::last());
}
Ok(ret as u64)
}
/// Iterator returning the offset range of data in the file.
///
/// This uses `lseek(2)` internally, and thus it changes the file offset.
pub struct FileDataIterator<'a> {
fd: &'a dyn AsRawDescriptor,
offset: u64,
}
impl<'a> FileDataIterator<'a> {
/// Creates the [FileDataIterator]
///
/// # Arguments
///
/// * `fd` - the [AsRawDescriptor] of the file
/// * `offset` - the offset to start traversing from.
pub fn new(fd: &'a dyn AsRawDescriptor, offset: u64) -> Self {
Self { fd, offset }
}
fn find_next_data(&self) -> Result<Option<Range<u64>>> {
let offset_data = match lseek(self.fd, self.offset, LseekOption::Data) {
Ok(offset) => offset,
Err(e) => {
return match e.errno() {
libc::ENXIO => Ok(None),
_ => Err(e),
}
}
};
let offset_hole = lseek(self.fd, offset_data, LseekOption::Hole)?;
Ok(Some(offset_data..offset_hole))
}
}
impl<'a> Iterator for FileDataIterator<'a> {
type Item = Range<u64>;
fn next(&mut self) -> Option<Self::Item> {
match self.find_next_data() {
Ok(data_range) => {
if let Some(ref data_range) = data_range {
self.offset = data_range.end;
}
data_range
}
Err(e) => {
error!("failed to get data range: {:?}", e);
None
}
}
}
}
#[cfg(test)]
mod tests {
use std::os::unix::fs::FileExt;
use crate::pagesize;
use super::*;
#[test]
fn file_data_iterator() {
let file = tempfile::tempfile().unwrap();
file.write_at(&[1_u8], 10).unwrap();
file.write_at(&[1_u8], 2 * pagesize() as u64).unwrap();
file.write_at(&[1_u8], (4 * pagesize() - 1) as u64).unwrap();
let iter = FileDataIterator::new(&file, 0);
let result: Vec<Range<u64>> = iter.collect();
assert_eq!(result.len(), 2);
assert_eq!(result[0], 0..(pagesize() as u64));
assert_eq!(result[1], (2 * pagesize() as u64)..(4 * pagesize() as u64));
}
}

View file

@ -23,6 +23,7 @@ mod acpi_event;
mod capabilities;
mod descriptor;
mod event;
mod file;
mod file_flags;
pub mod file_traits;
mod get_filesystem_type;
@ -68,6 +69,7 @@ pub use capabilities::drop_capabilities;
pub use descriptor::*;
pub use event::EventExt;
pub(crate) use event::PlatformEvent;
pub use file::FileDataIterator;
pub use file_flags::*;
pub use file_traits::AsRawFds;
pub use file_traits::FileAllocate;