1use super::{RawIoOsIntf, RawIoResult};
13use anyhow as ah;
14use libc::{c_int, off_t, POSIX_FADV_DONTNEED};
15use std::{
16 fs::{metadata, File, OpenOptions},
17 io::{Read, Seek, SeekFrom, Write},
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, &mut sector_size as *mut 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 as u32);
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!("Failed to open file {:?}: {}", path, e));
106 }
107 };
108
109 let mut self_ = Self {
110 path: path.into(),
111 file: Some(file),
112 read_mode: read,
113 write_mode: write,
114 is_blk: false,
115 is_chr: false,
116 sector_size: None,
117 };
118
119 if let Err(e) = self_.read_disk_geometry() {
120 let _ = self_.close();
121 return Err(e);
122 }
123
124 Ok(self_)
125 }
126
127 fn get_sector_size(&self) -> Option<u32> {
128 self.sector_size
129 }
130
131 fn drop_file_caches(&mut self, offset: u64, size: u64) -> ah::Result<()> {
132 let Some(file) = self.file.take() else {
133 return Ok(());
134 };
135
136 if self.is_chr {
137 return Ok(());
140 }
141
142 if self.write_mode {
143 if let Err(e) = file.sync_all() {
145 return Err(ah::format_err!("Failed to flush: {}", e));
146 }
147 }
148
149 let ret = unsafe {
155 libc::posix_fadvise(
156 file.as_raw_fd(),
157 offset as off_t,
158 size as off_t,
159 POSIX_FADV_DONTNEED,
160 )
161 };
162
163 if ret == 0 {
164 Ok(())
166 } else {
167 drop(file);
170
171 let proc_file = "/proc/sys/vm/drop_caches";
172 let proc_value = b"3\n";
173
174 match OpenOptions::new().write(true).open(proc_file) {
175 Ok(mut file) => match file.write_all(proc_value) {
176 Ok(_) => Ok(()),
177 Err(e) => Err(ah::format_err!("{}", e)),
178 },
179 Err(e) => Err(ah::format_err!("{}", e)),
180 }
181 }
182 }
183
184 fn close(&mut self) -> ah::Result<()> {
185 let Some(file) = self.file.take() else {
186 return Ok(());
187 };
188 if self.write_mode && !self.is_chr {
189 if let Err(e) = file.sync_all() {
190 return Err(ah::format_err!("Failed to flush: {}", e));
191 }
192 }
193 Ok(())
194 }
195
196 fn sync(&mut self) -> ah::Result<()> {
197 if self.write_mode && !self.is_chr {
198 let Some(file) = self.file.as_mut() else {
199 return Err(ah::format_err!("No file object"));
200 };
201 file.sync_all()?;
202 }
203 Ok(())
204 }
205
206 fn set_len(&mut self, size: u64) -> ah::Result<()> {
207 if !self.write_mode {
208 return Err(ah::format_err!("File is opened without write permission."));
209 }
210 if self.is_chr || self.is_blk {
211 return Err(ah::format_err!("Cannot set length of raw device."));
212 }
213 let Some(file) = self.file.as_mut() else {
214 return Err(ah::format_err!("No file object"));
215 };
216 Ok(file.set_len(size)?)
217 }
218
219 fn seek(&mut self, offset: u64) -> ah::Result<u64> {
220 let Some(file) = self.file.as_mut() else {
221 return Err(ah::format_err!("No file object"));
222 };
223 Ok(file.seek(SeekFrom::Start(offset))?)
224 }
225
226 fn read(&mut self, buffer: &mut [u8]) -> ah::Result<RawIoResult> {
227 if !self.read_mode {
228 return Err(ah::format_err!("File is opened without read permission."));
229 }
230 let Some(file) = self.file.as_mut() else {
231 return Err(ah::format_err!("No file object"));
232 };
233 match file.read(buffer) {
234 Ok(count) => Ok(RawIoResult::Ok(count)),
235 Err(e) => Err(ah::format_err!("Read error: {}", e)),
236 }
237 }
238
239 fn write(&mut self, buffer: &[u8]) -> ah::Result<RawIoResult> {
240 if !self.write_mode {
241 return Err(ah::format_err!("File is opened without write permission."));
242 }
243 let Some(file) = self.file.as_mut() else {
244 return Err(ah::format_err!("No file object"));
245 };
246 if let Err(e) = file.write_all(buffer) {
247 if let Some(err_code) = e.raw_os_error() {
248 if err_code == libc::ENOSPC {
249 return Ok(RawIoResult::Enospc);
250 }
251 }
252 return Err(ah::format_err!("Write error: {}", e));
253 }
254 Ok(RawIoResult::Ok(buffer.len()))
255 }
256}
257
258impl Drop for RawIoLinux {
259 fn drop(&mut self) {
260 if let Err(e) = self.close() {
261 eprintln!("Warning: Failed to close device: {}", e);
262 }
263 }
264}
265
266