mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2024-09-13 02:24:55 +00:00
216 lines
8.5 KiB
Rust
216 lines
8.5 KiB
Rust
|
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style license that can be
|
||
|
// found in the LICENSE-BSD-3-Clause file.
|
||
|
//
|
||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
|
||
|
use std::fs::File;
|
||
|
use std::io::{Error, Result};
|
||
|
use std::os::unix::io::AsRawFd;
|
||
|
|
||
|
#[cfg(target_env = "musl")]
|
||
|
use libc::{c_int, lseek64, ENXIO};
|
||
|
|
||
|
#[cfg(target_env = "gnu")]
|
||
|
use libc::{lseek64, ENXIO, SEEK_DATA, SEEK_HOLE};
|
||
|
|
||
|
/// A trait for seeking to the next hole or non-hole position in a file.
|
||
|
pub trait SeekHole {
|
||
|
/// Seek to the first hole in a file at a position greater than or equal to `offset`.
|
||
|
/// If no holes exist after `offset`, the seek position will be set to the end of the file.
|
||
|
/// If `offset` is at or after the end of the file, the seek position is unchanged, and None is returned.
|
||
|
/// Returns the current seek position after the seek or an error.
|
||
|
fn seek_hole(&mut self, offset: u64) -> Result<Option<u64>>;
|
||
|
|
||
|
/// Seek to the first data in a file at a position greater than or equal to `offset`.
|
||
|
/// If no data exists after `offset`, the seek position is unchanged, and None is returned.
|
||
|
/// Returns the current offset after the seek or an error.
|
||
|
fn seek_data(&mut self, offset: u64) -> Result<Option<u64>>;
|
||
|
}
|
||
|
|
||
|
#[cfg(target_env = "musl")]
|
||
|
pub const SEEK_DATA: c_int = 3;
|
||
|
#[cfg(target_env = "musl")]
|
||
|
pub const SEEK_HOLE: c_int = 4;
|
||
|
|
||
|
/// Safe wrapper for `libc::lseek64()`
|
||
|
fn lseek(file: &mut File, offset: i64, whence: i32) -> Result<Option<u64>> {
|
||
|
// This is safe because we pass a known-good file descriptor.
|
||
|
let res = unsafe { lseek64(file.as_raw_fd(), offset, whence) };
|
||
|
|
||
|
if res < 0 {
|
||
|
// Convert ENXIO into None; pass any other error as-is.
|
||
|
let err = Error::last_os_error();
|
||
|
if let Some(errno) = Error::raw_os_error(&err) {
|
||
|
if errno == ENXIO {
|
||
|
return Ok(None);
|
||
|
}
|
||
|
}
|
||
|
Err(err)
|
||
|
} else {
|
||
|
Ok(Some(res as u64))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl SeekHole for File {
|
||
|
fn seek_hole(&mut self, offset: u64) -> Result<Option<u64>> {
|
||
|
lseek(self, offset as i64, SEEK_HOLE)
|
||
|
}
|
||
|
|
||
|
fn seek_data(&mut self, offset: u64) -> Result<Option<u64>> {
|
||
|
lseek(self, offset as i64, SEEK_DATA)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[cfg(test)]
|
||
|
mod tests {
|
||
|
use super::*;
|
||
|
use crate::TempDir;
|
||
|
use std::fs::File;
|
||
|
use std::io::{Seek, SeekFrom, Write};
|
||
|
use std::path::PathBuf;
|
||
|
|
||
|
fn seek_cur(file: &mut File) -> u64 {
|
||
|
file.seek(SeekFrom::Current(0)).unwrap()
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn seek_data() {
|
||
|
let tempdir = TempDir::new("/tmp/seek_data_test").unwrap();
|
||
|
let mut path = PathBuf::from(tempdir.as_path().unwrap());
|
||
|
path.push("test_file");
|
||
|
let mut file = File::create(&path).unwrap();
|
||
|
|
||
|
// Empty file
|
||
|
assert_eq!(file.seek_data(0).unwrap(), None);
|
||
|
assert_eq!(seek_cur(&mut file), 0);
|
||
|
|
||
|
// File with non-zero length consisting entirely of a hole
|
||
|
file.set_len(0x10000).unwrap();
|
||
|
assert_eq!(file.seek_data(0).unwrap(), None);
|
||
|
assert_eq!(seek_cur(&mut file), 0);
|
||
|
|
||
|
// seek_data at or after the end of the file should return None
|
||
|
assert_eq!(file.seek_data(0x10000).unwrap(), None);
|
||
|
assert_eq!(seek_cur(&mut file), 0);
|
||
|
assert_eq!(file.seek_data(0x10001).unwrap(), None);
|
||
|
assert_eq!(seek_cur(&mut file), 0);
|
||
|
|
||
|
// Write some data to [0x10000, 0x20000)
|
||
|
let b = [0x55u8; 0x10000];
|
||
|
file.seek(SeekFrom::Start(0x10000)).unwrap();
|
||
|
file.write_all(&b).unwrap();
|
||
|
assert_eq!(file.seek_data(0).unwrap(), Some(0x10000));
|
||
|
assert_eq!(seek_cur(&mut file), 0x10000);
|
||
|
|
||
|
// seek_data within data should return the same offset
|
||
|
assert_eq!(file.seek_data(0x10000).unwrap(), Some(0x10000));
|
||
|
assert_eq!(seek_cur(&mut file), 0x10000);
|
||
|
assert_eq!(file.seek_data(0x10001).unwrap(), Some(0x10001));
|
||
|
assert_eq!(seek_cur(&mut file), 0x10001);
|
||
|
assert_eq!(file.seek_data(0x1FFFF).unwrap(), Some(0x1FFFF));
|
||
|
assert_eq!(seek_cur(&mut file), 0x1FFFF);
|
||
|
|
||
|
// Extend the file to add another hole after the data
|
||
|
file.set_len(0x30000).unwrap();
|
||
|
assert_eq!(file.seek_data(0).unwrap(), Some(0x10000));
|
||
|
assert_eq!(seek_cur(&mut file), 0x10000);
|
||
|
assert_eq!(file.seek_data(0x1FFFF).unwrap(), Some(0x1FFFF));
|
||
|
assert_eq!(seek_cur(&mut file), 0x1FFFF);
|
||
|
assert_eq!(file.seek_data(0x20000).unwrap(), None);
|
||
|
assert_eq!(seek_cur(&mut file), 0x1FFFF);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
#[allow(clippy::cyclomatic_complexity)]
|
||
|
fn seek_hole() {
|
||
|
let tempdir = TempDir::new("/tmp/seek_hole_test").unwrap();
|
||
|
let mut path = PathBuf::from(tempdir.as_path().unwrap());
|
||
|
path.push("test_file");
|
||
|
let mut file = File::create(&path).unwrap();
|
||
|
|
||
|
// Empty file
|
||
|
assert_eq!(file.seek_hole(0).unwrap(), None);
|
||
|
assert_eq!(seek_cur(&mut file), 0);
|
||
|
|
||
|
// File with non-zero length consisting entirely of a hole
|
||
|
file.set_len(0x10000).unwrap();
|
||
|
assert_eq!(file.seek_hole(0).unwrap(), Some(0));
|
||
|
assert_eq!(seek_cur(&mut file), 0);
|
||
|
assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
|
||
|
assert_eq!(seek_cur(&mut file), 0xFFFF);
|
||
|
|
||
|
// seek_hole at or after the end of the file should return None
|
||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||
|
assert_eq!(file.seek_hole(0x10000).unwrap(), None);
|
||
|
assert_eq!(seek_cur(&mut file), 0);
|
||
|
assert_eq!(file.seek_hole(0x10001).unwrap(), None);
|
||
|
assert_eq!(seek_cur(&mut file), 0);
|
||
|
|
||
|
// Write some data to [0x10000, 0x20000)
|
||
|
let b = [0x55u8; 0x10000];
|
||
|
file.seek(SeekFrom::Start(0x10000)).unwrap();
|
||
|
file.write_all(&b).unwrap();
|
||
|
|
||
|
// seek_hole within a hole should return the same offset
|
||
|
assert_eq!(file.seek_hole(0).unwrap(), Some(0));
|
||
|
assert_eq!(seek_cur(&mut file), 0);
|
||
|
assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
|
||
|
assert_eq!(seek_cur(&mut file), 0xFFFF);
|
||
|
|
||
|
// seek_hole within data should return the next hole (EOF)
|
||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||
|
assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
|
||
|
assert_eq!(seek_cur(&mut file), 0x20000);
|
||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||
|
assert_eq!(file.seek_hole(0x10001).unwrap(), Some(0x20000));
|
||
|
assert_eq!(seek_cur(&mut file), 0x20000);
|
||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||
|
assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
|
||
|
assert_eq!(seek_cur(&mut file), 0x20000);
|
||
|
|
||
|
// seek_hole at EOF after data should return None
|
||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||
|
assert_eq!(file.seek_hole(0x20000).unwrap(), None);
|
||
|
assert_eq!(seek_cur(&mut file), 0);
|
||
|
|
||
|
// Extend the file to add another hole after the data
|
||
|
file.set_len(0x30000).unwrap();
|
||
|
assert_eq!(file.seek_hole(0).unwrap(), Some(0));
|
||
|
assert_eq!(seek_cur(&mut file), 0);
|
||
|
assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
|
||
|
assert_eq!(seek_cur(&mut file), 0xFFFF);
|
||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||
|
assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
|
||
|
assert_eq!(seek_cur(&mut file), 0x20000);
|
||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||
|
assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
|
||
|
assert_eq!(seek_cur(&mut file), 0x20000);
|
||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||
|
assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x20000));
|
||
|
assert_eq!(seek_cur(&mut file), 0x20000);
|
||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||
|
assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x20001));
|
||
|
assert_eq!(seek_cur(&mut file), 0x20001);
|
||
|
|
||
|
// seek_hole at EOF after a hole should return None
|
||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||
|
assert_eq!(file.seek_hole(0x30000).unwrap(), None);
|
||
|
assert_eq!(seek_cur(&mut file), 0);
|
||
|
|
||
|
// Write some data to [0x20000, 0x30000)
|
||
|
file.seek(SeekFrom::Start(0x20000)).unwrap();
|
||
|
file.write_all(&b).unwrap();
|
||
|
|
||
|
// seek_hole within [0x20000, 0x30000) should now find the hole at EOF
|
||
|
assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x30000));
|
||
|
assert_eq!(seek_cur(&mut file), 0x30000);
|
||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||
|
assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x30000));
|
||
|
assert_eq!(seek_cur(&mut file), 0x30000);
|
||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||
|
assert_eq!(file.seek_hole(0x30000).unwrap(), None);
|
||
|
assert_eq!(seek_cur(&mut file), 0);
|
||
|
}
|
||
|
}
|