metrale_storage/tier/direct_swap/
unix.rs1use std::fs::OpenOptions;
11use std::os::fd::{AsRawFd, OwnedFd};
12use std::os::unix::fs::OpenOptionsExt;
13use std::path::Path;
14
15use anyhow::{Result, bail};
16
17use crate::tier::direct_swap::validate_record_bytes;
18use crate::tier::traits::SwapStore;
19
20#[cfg(target_os = "linux")]
22const DIRECT_FLAGS: i32 = libc::O_DIRECT;
23#[cfg(not(target_os = "linux"))]
24const DIRECT_FLAGS: i32 = 0;
25
26pub struct DirectSwapFile {
37 fd: OwnedFd,
38 record_bytes: usize,
39 bounce: AlignedBuf,
41}
42
43impl DirectSwapFile {
44 pub fn create(path: &Path, record_bytes: usize) -> Result<Self> {
45 validate_record_bytes(record_bytes)?;
46 let f = OpenOptions::new()
47 .read(true)
48 .write(true)
49 .create(true)
50 .truncate(true)
51 .custom_flags(DIRECT_FLAGS)
52 .open(path)
53 .map_err(|e| anyhow::anyhow!("open O_DIRECT {}: {e}", path.display()))?;
54 Ok(Self {
55 fd: OwnedFd::from(f),
56 record_bytes,
57 bounce: AlignedBuf::new(record_bytes),
58 })
59 }
60
61 fn offset(&self, disk_slot: usize) -> libc::off_t {
62 (disk_slot as u64 * self.record_bytes as u64) as libc::off_t
63 }
64}
65
66impl SwapStore for DirectSwapFile {
67 fn record_bytes(&self) -> usize {
68 self.record_bytes
69 }
70
71 fn write_record(&mut self, disk_slot: usize, bytes: &[u8]) -> Result<()> {
72 if bytes.len() != self.record_bytes {
73 bail!(
74 "write_record: {} bytes, expected {}",
75 bytes.len(),
76 self.record_bytes
77 );
78 }
79 let off = self.offset(disk_slot);
80 let src = if is_aligned(bytes.as_ptr()) {
81 bytes.as_ptr()
82 } else {
83 self.bounce.as_mut_slice().copy_from_slice(bytes);
84 self.bounce.ptr()
85 };
86 let n = unsafe {
87 libc::pwrite(
88 self.fd.as_raw_fd(),
89 src as *const libc::c_void,
90 self.record_bytes,
91 off,
92 )
93 };
94 if n != self.record_bytes as isize {
95 bail!("pwrite record {disk_slot} returned {n}, errno {}", errno());
96 }
97 Ok(())
98 }
99
100 fn read_record(&self, disk_slot: usize, out: &mut [u8]) -> Result<()> {
101 if out.len() != self.record_bytes {
102 bail!(
103 "read_record: {} bytes, expected {}",
104 out.len(),
105 self.record_bytes
106 );
107 }
108 let off = self.offset(disk_slot);
109 if is_aligned(out.as_ptr()) {
110 let n = unsafe {
111 libc::pread(
112 self.fd.as_raw_fd(),
113 out.as_mut_ptr() as *mut libc::c_void,
114 self.record_bytes,
115 off,
116 )
117 };
118 if n != self.record_bytes as isize {
119 bail!("pread record {disk_slot} returned {n}, errno {}", errno());
120 }
121 } else {
122 let bp = self.bounce.ptr();
126 let n = unsafe {
127 libc::pread(
128 self.fd.as_raw_fd(),
129 bp as *mut libc::c_void,
130 self.record_bytes,
131 off,
132 )
133 };
134 if n != self.record_bytes as isize {
135 bail!(
136 "pread(bounce) record {disk_slot} returned {n}, errno {}",
137 errno()
138 );
139 }
140 unsafe {
141 std::ptr::copy_nonoverlapping(bp, out.as_mut_ptr(), self.record_bytes);
142 }
143 }
144 Ok(())
145 }
146}
147
148fn errno() -> i32 {
149 std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
150}
151
152fn is_aligned(p: *const u8) -> bool {
153 (p as usize) & 0xfff == 0
154}
155
156struct AlignedBuf {
159 ptr: *mut u8,
160 len: usize,
161}
162unsafe impl Send for AlignedBuf {}
163impl AlignedBuf {
164 fn new(len: usize) -> Self {
165 let mut p: *mut libc::c_void = std::ptr::null_mut();
166 let rc = unsafe { libc::posix_memalign(&mut p, 4096, len) };
167 assert!(
168 rc == 0 && !p.is_null(),
169 "posix_memalign({len}) failed rc={rc}"
170 );
171 Self {
172 ptr: p as *mut u8,
173 len,
174 }
175 }
176 fn ptr(&self) -> *mut u8 {
177 self.ptr
178 }
179 fn as_mut_slice(&mut self) -> &mut [u8] {
180 unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
181 }
182}
183impl Drop for AlignedBuf {
184 fn drop(&mut self) {
185 unsafe { libc::free(self.ptr as *mut libc::c_void) }
186 }
187}