foyer_storage/io/device/
file.rs1use std::{
16 fs::{create_dir_all, File, OpenOptions},
17 path::{Path, PathBuf},
18 sync::{Arc, RwLock},
19};
20
21use foyer_common::error::{Error, Result};
22use fs4::free_space;
23
24use crate::{
25 io::{
26 device::{statistics::Statistics, throttle::Throttle, Device, DeviceBuilder, Partition, PartitionId},
27 PAGE,
28 },
29 RawFile,
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({
85 let dir = self.path.parent().expect("path must point to a file").to_path_buf();
87 create_dir_all(&dir).unwrap();
88 free_space(&dir).unwrap() as usize / 10 * 8
89 });
90 let capacity = align_v(capacity, PAGE);
91
92 let mut opts = OpenOptions::new();
95 opts.create(true).write(true).read(true);
96 #[cfg(target_os = "linux")]
97 if self.direct {
98 use std::os::unix::fs::OpenOptionsExt;
99 opts.custom_flags(libc::O_DIRECT | libc::O_NOATIME);
100 }
101
102 let file = opts.open(&self.path).map_err(Error::io_error)?;
103
104 if file.metadata().unwrap().is_file() {
105 tracing::warn!(
106 "{} {} {}",
107 "It seems a `DirectFileDevice` is used within a normal file system, which is inefficient.",
108 "Please use `DirectFileDevice` directly on a raw block device.",
109 "Or use `DirectFsDevice` within a normal file system.",
110 );
111 file.set_len(capacity as _).map_err(Error::io_error)?;
112 }
113 let file = Arc::new(file);
114
115 let statistics = Arc::new(Statistics::new(self.throttle));
116
117 let device = FileDevice {
118 file,
119 capacity,
120 statistics,
121 partitions: RwLock::new(vec![]),
122 };
123 let device: Arc<dyn Device> = Arc::new(device);
124 Ok(device)
125 }
126}
127
128#[derive(Debug)]
130pub struct FileDevice {
131 file: Arc<File>,
132 capacity: usize,
133 partitions: RwLock<Vec<Arc<FilePartition>>>,
134 statistics: Arc<Statistics>,
135}
136
137impl Device for FileDevice {
138 fn capacity(&self) -> usize {
139 self.capacity
140 }
141
142 fn allocated(&self) -> usize {
143 self.partitions.read().unwrap().iter().map(|p| p.size).sum()
144 }
145
146 fn create_partition(&self, size: usize) -> Result<Arc<dyn Partition>> {
147 let mut partitions = self.partitions.write().unwrap();
148 let allocated = partitions.iter().map(|p| p.size).sum::<usize>();
149 if allocated + size > self.capacity {
150 return Err(Error::no_space(self.capacity, allocated, allocated + size));
151 }
152 let offset = partitions.last().map(|p| p.offset + p.size as u64).unwrap_or_default();
153 let id = partitions.len() as PartitionId;
154 let partition = Arc::new(FilePartition {
155 file: self.file.clone(),
156 id,
157 size,
158 offset,
159 statistics: self.statistics.clone(),
160 });
161 partitions.push(partition.clone());
162 Ok(partition)
163 }
164
165 fn partitions(&self) -> usize {
166 self.partitions.read().unwrap().len()
167 }
168
169 fn partition(&self, id: PartitionId) -> Arc<dyn Partition> {
170 self.partitions.read().unwrap()[id as usize].clone()
171 }
172
173 fn statistics(&self) -> &Arc<Statistics> {
174 &self.statistics
175 }
176}
177
178#[derive(Debug)]
179pub struct FilePartition {
180 file: Arc<File>,
181 id: PartitionId,
182 size: usize,
183 offset: u64,
184 statistics: Arc<Statistics>,
185}
186
187impl Partition for FilePartition {
188 fn id(&self) -> PartitionId {
189 self.id
190 }
191
192 fn size(&self) -> usize {
193 self.size
194 }
195
196 fn translate(&self, address: u64) -> (RawFile, u64) {
197 #[cfg(any(target_family = "unix", target_family = "wasm"))]
198 let raw = {
199 use std::os::fd::AsRawFd;
200 RawFile(self.file.as_raw_fd())
201 };
202
203 #[cfg(target_family = "windows")]
204 let raw = {
205 use std::os::windows::io::AsRawHandle;
206 RawFile(self.file.as_raw_handle())
207 };
208
209 let address = self.offset + address;
210 (raw, address)
211 }
212
213 fn statistics(&self) -> &Arc<Statistics> {
214 &self.statistics
215 }
216}