1use super::{RawIoOsIntf, RawIoResult};
13use anyhow::{self as ah, Context as _};
14use libc::{POSIX_FADV_DONTNEED, c_int};
15use std::{
16 fs::{File, OpenOptions, metadata},
17 io::{Read as _, Seek as _, SeekFrom, Write as _},
18 os::unix::{fs::MetadataExt as _, io::AsRawFd as _},
19 path::{Path, PathBuf},
20};
21
22#[allow(clippy::unnecessary_cast)]
23const S_IFBLK: u32 = libc::S_IFBLK as u32;
24#[allow(clippy::unnecessary_cast)]
25const S_IFCHR: u32 = libc::S_IFCHR as u32;
26#[allow(clippy::unnecessary_cast)]
27const S_IFMT: u32 = libc::S_IFMT as u32;
28
29pub struct RawIoLinux {
31 path: PathBuf,
32 file: Option<File>,
33 read_mode: bool,
34 write_mode: bool,
35 is_blk: bool,
36 is_chr: bool,
37 sector_size: Option<u32>,
38}
39
40impl RawIoLinux {
41 fn read_disk_geometry(&mut self) -> ah::Result<()> {
42 if let Ok(meta) = metadata(&self.path) {
43 let mode_ifmt = meta.mode() & S_IFMT;
44 if mode_ifmt == S_IFBLK {
45 self.is_blk = true;
46 }
47 if mode_ifmt == S_IFCHR {
48 self.is_chr = true;
49 }
50 }
51
52 if self.is_blk {
53 let Some(file) = self.file.as_ref() else {
54 return Err(ah::format_err!("No file object"));
55 };
56
57 let mut sector_size: c_int = 0;
58 let res = unsafe {
63 libc::ioctl(
64 file.as_raw_fd(),
65 libc::BLKPBSZGET, (&raw mut sector_size).cast::<c_int>(),
67 )
68 };
69 if res < 0 {
70 return Err(ah::format_err!(
71 "Get device block size: ioctl(BLKPBSZGET) failed."
72 ));
73 }
74 if sector_size <= 0 {
75 return Err(ah::format_err!(
76 "Get device block size: ioctl(BLKPBSZGET) invalid size."
77 ));
78 }
79
80 self.sector_size = Some(sector_size.try_into().unwrap_or(0));
81 } else {
82 self.sector_size = None;
83 }
84 Ok(())
85 }
86}
87
88impl RawIoOsIntf for RawIoLinux {
89 fn new(path: &Path, mut create: bool, read: bool, write: bool) -> ah::Result<Self> {
90 if path.starts_with("/dev/") {
91 create = false;
95 }
96
97 let file = match OpenOptions::new()
98 .create(create)
99 .read(read)
100 .write(write)
101 .open(path)
102 {
103 Ok(f) => f,
104 Err(e) => {
105 return Err(ah::format_err!(
106 "Failed to open file {}: {e}",
107 path.display()
108 ));
109 }
110 };
111
112 let mut self_ = Self {
113 path: path.into(),
114 file: Some(file),
115 read_mode: read,
116 write_mode: write,
117 is_blk: false,
118 is_chr: false,
119 sector_size: None,
120 };
121
122 if let Err(e) = self_.read_disk_geometry() {
123 let _ = self_.close();
124 return Err(e);
125 }
126
127 Ok(self_)
128 }
129
130 fn get_sector_size(&self) -> Option<u32> {
131 self.sector_size
132 }
133
134 fn drop_file_caches(&mut self, offset: u64, size: u64) -> ah::Result<()> {
135 let Some(file) = self.file.take() else {
136 return Ok(());
137 };
138
139 if self.is_chr {
140 return Ok(());
143 }
144
145 if self.write_mode {
146 if let Err(e) = file.sync_all() {
148 return Err(ah::format_err!("Failed to flush: {e}"));
149 }
150 }
151
152 let ret = unsafe {
158 libc::posix_fadvise(
159 file.as_raw_fd(),
160 offset.try_into().context("File offset overflows off_t")?,
161 size.try_into().context("File size overflows off_t")?,
162 POSIX_FADV_DONTNEED,
163 )
164 };
165
166 if ret == 0 {
167 Ok(())
169 } else {
170 drop(file);
173
174 let proc_file = "/proc/sys/vm/drop_caches";
175 let proc_value = b"3\n";
176
177 match OpenOptions::new().write(true).open(proc_file) {
178 Ok(mut file) => match file.write_all(proc_value) {
179 Ok(()) => Ok(()),
180 Err(e) => Err(ah::format_err!("{e}")),
181 },
182 Err(e) => Err(ah::format_err!("{e}")),
183 }
184 }
185 }
186
187 fn close(&mut self) -> ah::Result<()> {
188 let Some(file) = self.file.take() else {
189 return Ok(());
190 };
191 if self.write_mode && !self.is_chr {
192 if let Err(e) = file.sync_all() {
193 return Err(ah::format_err!("Failed to flush: {e}"));
194 }
195 }
196 Ok(())
197 }
198
199 fn sync(&mut self) -> ah::Result<()> {
200 if self.write_mode && !self.is_chr {
201 let Some(file) = self.file.as_mut() else {
202 return Err(ah::format_err!("No file object"));
203 };
204 file.sync_all()?;
205 }
206 Ok(())
207 }
208
209 fn set_len(&mut self, size: u64) -> ah::Result<()> {
210 if !self.write_mode {
211 return Err(ah::format_err!("File is opened without write permission."));
212 }
213 if self.is_chr || self.is_blk {
214 return Err(ah::format_err!("Cannot set length of raw device."));
215 }
216 let Some(file) = self.file.as_mut() else {
217 return Err(ah::format_err!("No file object"));
218 };
219 Ok(file.set_len(size)?)
220 }
221
222 fn seek(&mut self, offset: u64) -> ah::Result<u64> {
223 let Some(file) = self.file.as_mut() else {
224 return Err(ah::format_err!("No file object"));
225 };
226 Ok(file.seek(SeekFrom::Start(offset))?)
227 }
228
229 fn read(&mut self, buffer: &mut [u8]) -> ah::Result<RawIoResult> {
230 if !self.read_mode {
231 return Err(ah::format_err!("File is opened without read permission."));
232 }
233 let Some(file) = self.file.as_mut() else {
234 return Err(ah::format_err!("No file object"));
235 };
236 match file.read(buffer) {
237 Ok(count) => Ok(RawIoResult::Ok(count)),
238 Err(e) => Err(ah::format_err!("Read error: {e}")),
239 }
240 }
241
242 fn write(&mut self, buffer: &[u8]) -> ah::Result<RawIoResult> {
243 if !self.write_mode {
244 return Err(ah::format_err!("File is opened without write permission."));
245 }
246 let Some(file) = self.file.as_mut() else {
247 return Err(ah::format_err!("No file object"));
248 };
249 if let Err(e) = file.write_all(buffer) {
250 if let Some(err_code) = e.raw_os_error() {
251 if err_code == libc::ENOSPC {
252 return Ok(RawIoResult::Enospc);
253 }
254 }
255 return Err(ah::format_err!("Write error: {e}"));
256 }
257 Ok(RawIoResult::Ok(buffer.len()))
258 }
259}
260
261impl Drop for RawIoLinux {
262 fn drop(&mut self) {
263 if let Err(e) = self.close() {
264 eprintln!("Warning: Failed to close device: {e}");
265 }
266 }
267}
268
269