Skip to main content

arcbox_virtio_blk/
direct_io.rs

1//! Direct I/O block backend (Linux, `O_DIRECT`).
2
3#![cfg(target_os = "linux")]
4
5use std::os::unix::io::RawFd;
6
7/// Direct I/O block backend using `O_DIRECT`.
8pub struct DirectIoBackend {
9    /// File descriptor.
10    fd: RawFd,
11    /// Capacity in sectors.
12    capacity: u64,
13    /// Block size.
14    block_size: u32,
15    /// Read-only mode.
16    read_only: bool,
17}
18
19impl DirectIoBackend {
20    /// Creates a new direct I/O backend.
21    ///
22    /// # Errors
23    ///
24    /// Returns an error if the file cannot be opened with `O_DIRECT`.
25    pub fn new(path: &std::path::Path, read_only: bool) -> std::io::Result<Self> {
26        let flags = if read_only {
27            libc::O_RDONLY | libc::O_DIRECT | libc::O_CLOEXEC
28        } else {
29            libc::O_RDWR | libc::O_DIRECT | libc::O_CLOEXEC
30        };
31
32        let path_cstr = std::ffi::CString::new(path.to_string_lossy().as_bytes())
33            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
34
35        // SAFETY: open() reads the C-string we just constructed; on success it
36        // returns a fresh fd we own.
37        let fd = unsafe { libc::open(path_cstr.as_ptr(), flags, 0o644) };
38
39        if fd < 0 {
40            return Err(std::io::Error::last_os_error());
41        }
42
43        // SAFETY: `stat` is zero-initialised plain-old-data; fstat fills it
44        // in over the borrow we hand it. On error we close the fd we opened.
45        let mut stat: libc::stat = unsafe { std::mem::zeroed() };
46        let ret = unsafe { libc::fstat(fd, &mut stat) };
47        if ret < 0 {
48            unsafe { libc::close(fd) };
49            return Err(std::io::Error::last_os_error());
50        }
51
52        let capacity = stat.st_size as u64 / 512;
53
54        tracing::info!(
55            "Opened {} with O_DIRECT, capacity={} sectors",
56            path.display(),
57            capacity
58        );
59
60        Ok(Self {
61            fd,
62            capacity,
63            block_size: 512,
64            read_only,
65        })
66    }
67
68    /// Reads data at the given offset using pread.
69    pub fn pread(&self, offset: u64, buf: &mut [u8]) -> std::io::Result<usize> {
70        // SAFETY: pread writes at most buf.len() bytes into our borrowed buffer.
71        let ret = unsafe {
72            libc::pread(
73                self.fd,
74                buf.as_mut_ptr() as *mut libc::c_void,
75                buf.len(),
76                offset as libc::off_t,
77            )
78        };
79
80        if ret < 0 {
81            Err(std::io::Error::last_os_error())
82        } else {
83            Ok(ret as usize)
84        }
85    }
86
87    /// Writes data at the given offset using pwrite.
88    pub fn pwrite(&self, offset: u64, buf: &[u8]) -> std::io::Result<usize> {
89        if self.read_only {
90            return Err(std::io::Error::new(
91                std::io::ErrorKind::PermissionDenied,
92                "Device is read-only",
93            ));
94        }
95
96        // SAFETY: pwrite reads buf.len() bytes from our borrowed buffer.
97        let ret = unsafe {
98            libc::pwrite(
99                self.fd,
100                buf.as_ptr() as *const libc::c_void,
101                buf.len(),
102                offset as libc::off_t,
103            )
104        };
105
106        if ret < 0 {
107            Err(std::io::Error::last_os_error())
108        } else {
109            Ok(ret as usize)
110        }
111    }
112
113    /// Syncs the file to disk.
114    pub fn sync(&self) -> std::io::Result<()> {
115        // SAFETY: fdatasync on an fd we own.
116        let ret = unsafe { libc::fdatasync(self.fd) };
117        if ret < 0 {
118            Err(std::io::Error::last_os_error())
119        } else {
120            Ok(())
121        }
122    }
123}
124
125impl Drop for DirectIoBackend {
126    fn drop(&mut self) {
127        if self.fd >= 0 {
128            // SAFETY: closing an fd we exclusively own.
129            unsafe { libc::close(self.fd) };
130        }
131    }
132}