foyer_storage/io/device/
file.rs1use std::{
16 fs::{File, OpenOptions, create_dir_all},
17 path::{Path, PathBuf},
18 sync::{Arc, RwLock},
19};
20
21use foyer_common::error::{Error, Result};
22use fs4::free_space;
23
24use crate::{
25 RawFile,
26 io::{
27 PAGE,
28 device::{Device, DeviceBuilder, Partition, PartitionId, statistics::Statistics, throttle::Throttle},
29 },
30};
31
32#[derive(Debug)]
34pub struct FileDeviceBuilder {
35 path: PathBuf,
36 capacity: Option<usize>,
37 throttle: Throttle,
38 #[cfg(target_os = "linux")]
39 direct: bool,
40}
41
42impl FileDeviceBuilder {
43 pub fn new(path: impl AsRef<Path>) -> Self {
45 Self {
46 path: path.as_ref().into(),
47 capacity: None,
48 throttle: Throttle::default(),
49 #[cfg(target_os = "linux")]
50 direct: false,
51 }
52 }
53
54 pub fn with_capacity(mut self, capacity: usize) -> Self {
60 self.capacity = Some(capacity);
61 self
62 }
63
64 pub fn with_throttle(mut self, throttle: Throttle) -> Self {
66 self.throttle = throttle;
67 self
68 }
69
70 #[cfg(target_os = "linux")]
72 pub fn with_direct(mut self, direct: bool) -> Self {
73 self.direct = direct;
74 self
75 }
76}
77
78impl DeviceBuilder for FileDeviceBuilder {
79 fn build(self) -> Result<Arc<dyn Device>> {
80 let align_v = |value: usize, align: usize| value - (value % align);
83
84 let capacity = self.capacity.unwrap_or_else(|| {
85 #[cfg(unix)]
87 if let Ok(metadata) = std::fs::metadata(&self.path) {
88 let file_type = metadata.file_type();
89
90 use std::os::unix::fs::FileTypeExt;
91 if file_type.is_block_device() {
92 return super::utils::get_dev_capacity(&self.path).unwrap();
93 }
94 }
95
96 let dir = self.path.parent().expect("path must point to a file").to_path_buf();
98 create_dir_all(&dir).unwrap();
99 free_space(&dir).unwrap() as usize / 10 * 8
100 });
101 let capacity = align_v(capacity, PAGE);
102
103 println!("==========> {capacity}");
104
105 let mut opts = OpenOptions::new();
108 opts.create(true).write(true).read(true);
109 #[cfg(target_os = "linux")]
110 if self.direct {
111 use std::os::unix::fs::OpenOptionsExt;
112 opts.custom_flags(libc::O_DIRECT | libc::O_NOATIME);
113 }
114
115 let file = opts.open(&self.path).map_err(Error::io_error)?;
116
117 if file.metadata().unwrap().is_file() {
118 tracing::warn!(
119 "{} {} {}",
120 "It seems a `DirectFileDevice` is used within a normal file system, which is inefficient.",
121 "Please use `DirectFileDevice` directly on a raw block device.",
122 "Or use `DirectFsDevice` within a normal file system.",
123 );
124 file.set_len(capacity as _).map_err(Error::io_error)?;
125 }
126 let file = Arc::new(file);
127
128 let statistics = Arc::new(Statistics::new(self.throttle));
129
130 let device = FileDevice {
131 file,
132 capacity,
133 statistics,
134 partitions: RwLock::new(vec![]),
135 };
136 let device: Arc<dyn Device> = Arc::new(device);
137 Ok(device)
138 }
139}
140
141#[derive(Debug)]
143pub struct FileDevice {
144 file: Arc<File>,
145 capacity: usize,
146 partitions: RwLock<Vec<Arc<FilePartition>>>,
147 statistics: Arc<Statistics>,
148}
149
150impl Device for FileDevice {
151 fn capacity(&self) -> usize {
152 self.capacity
153 }
154
155 fn allocated(&self) -> usize {
156 self.partitions.read().unwrap().iter().map(|p| p.size).sum()
157 }
158
159 fn create_partition(&self, size: usize) -> Result<Arc<dyn Partition>> {
160 let mut partitions = self.partitions.write().unwrap();
161 let allocated = partitions.iter().map(|p| p.size).sum::<usize>();
162 if allocated + size > self.capacity {
163 return Err(Error::no_space(self.capacity, allocated, allocated + size));
164 }
165 let offset = partitions.last().map(|p| p.offset + p.size as u64).unwrap_or_default();
166 let id = partitions.len() as PartitionId;
167 let partition = Arc::new(FilePartition {
168 file: self.file.clone(),
169 id,
170 size,
171 offset,
172 statistics: self.statistics.clone(),
173 });
174 partitions.push(partition.clone());
175 Ok(partition)
176 }
177
178 fn partitions(&self) -> usize {
179 self.partitions.read().unwrap().len()
180 }
181
182 fn partition(&self, id: PartitionId) -> Arc<dyn Partition> {
183 self.partitions.read().unwrap()[id as usize].clone()
184 }
185
186 fn statistics(&self) -> &Arc<Statistics> {
187 &self.statistics
188 }
189}
190
191#[derive(Debug)]
192pub struct FilePartition {
193 file: Arc<File>,
194 id: PartitionId,
195 size: usize,
196 offset: u64,
197 statistics: Arc<Statistics>,
198}
199
200impl Partition for FilePartition {
201 fn id(&self) -> PartitionId {
202 self.id
203 }
204
205 fn size(&self) -> usize {
206 self.size
207 }
208
209 fn translate(&self, address: u64) -> (RawFile, u64) {
210 #[cfg(any(target_family = "unix", target_family = "wasm"))]
211 let raw = {
212 use std::os::fd::AsRawFd;
213 RawFile(self.file.as_raw_fd())
214 };
215
216 #[cfg(target_family = "windows")]
217 let raw = {
218 use std::os::windows::io::AsRawHandle;
219 RawFile(self.file.as_raw_handle())
220 };
221
222 let address = self.offset + address;
223 (raw, address)
224 }
225
226 fn statistics(&self) -> &Arc<Statistics> {
227 &self.statistics
228 }
229}