foyer_storage/io/device/
fs.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 FsDeviceBuilder {
35 dir: PathBuf,
36 capacity: Option<usize>,
37 throttle: Throttle,
38 #[cfg(target_os = "linux")]
39 direct: bool,
40}
41
42impl FsDeviceBuilder {
43 pub fn new(dir: impl AsRef<Path>) -> Self {
45 Self {
46 dir: dir.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 FsDeviceBuilder {
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 create_dir_all(&self.dir).unwrap();
87 free_space(&self.dir).unwrap() as usize / 10 * 8
88 });
89 let capacity = align_v(capacity, PAGE);
90
91 let statistics = Arc::new(Statistics::new(self.throttle));
92
93 if !self.dir.exists() {
96 create_dir_all(&self.dir).map_err(Error::io_error)?;
97 }
98
99 let device = FsDevice {
100 capacity,
101 statistics,
102 dir: self.dir,
103 #[cfg(target_os = "linux")]
104 direct: self.direct,
105 partitions: RwLock::new(vec![]),
106 };
107 let device: Arc<dyn Device> = Arc::new(device);
108 Ok(device)
109 }
110}
111
112#[derive(Debug)]
114pub struct FsDevice {
115 capacity: usize,
116 statistics: Arc<Statistics>,
117 dir: PathBuf,
118 #[cfg(target_os = "linux")]
119 direct: bool,
120 partitions: RwLock<Vec<Arc<FsPartition>>>,
121}
122
123impl FsDevice {
124 const PREFIX: &str = "foyer-storage-direct-fs-";
125 fn filename(partition: PartitionId) -> String {
126 format!("{prefix}{partition:08}", prefix = Self::PREFIX,)
127 }
128}
129
130impl Device for FsDevice {
131 fn capacity(&self) -> usize {
132 self.capacity
133 }
134
135 fn allocated(&self) -> usize {
136 self.partitions.read().unwrap().iter().map(|p| p.size).sum()
137 }
138
139 fn create_partition(&self, size: usize) -> Result<Arc<dyn Partition>> {
140 let mut partitions = self.partitions.write().unwrap();
141 let allocated = partitions.iter().map(|p| p.size).sum::<usize>();
142 if allocated + size > self.capacity {
143 return Err(Error::no_space(self.capacity, allocated, allocated + size));
144 }
145 let id = partitions.len() as PartitionId;
146 let path = self.dir.join(Self::filename(id));
147 let mut opts = OpenOptions::new();
148 opts.create(true).write(true).read(true);
149 #[cfg(target_os = "linux")]
150 if self.direct {
151 use std::os::unix::fs::OpenOptionsExt;
152 opts.custom_flags(libc::O_DIRECT | libc::O_NOATIME);
153 }
154 let file = opts.open(path).map_err(Error::io_error)?;
155 file.set_len(size as _).map_err(Error::io_error)?;
156
157 let partition = Arc::new(FsPartition {
158 id,
159 size,
160 file,
161 statistics: self.statistics.clone(),
162 });
163 partitions.push(partition.clone());
164 Ok(partition)
165 }
166
167 fn partitions(&self) -> usize {
168 self.partitions.read().unwrap().len()
169 }
170
171 fn partition(&self, id: PartitionId) -> Arc<dyn Partition> {
172 self.partitions.read().unwrap()[id as usize].clone()
173 }
174
175 fn statistics(&self) -> &Arc<Statistics> {
176 &self.statistics
177 }
178}
179
180#[derive(Debug)]
181pub struct FsPartition {
182 id: PartitionId,
183 size: usize,
184 statistics: Arc<Statistics>,
185 file: File,
186}
187
188impl Partition for FsPartition {
189 fn id(&self) -> PartitionId {
190 self.id
191 }
192
193 fn size(&self) -> usize {
194 self.size
195 }
196
197 fn translate(&self, address: u64) -> (RawFile, u64) {
198 #[cfg(any(target_family = "unix", target_family = "wasm"))]
199 let raw = {
200 use std::os::fd::AsRawFd;
201 RawFile(self.file.as_raw_fd())
202 };
203
204 #[cfg(target_family = "windows")]
205 let raw = {
206 use std::os::windows::io::AsRawHandle;
207 RawFile(self.file.as_raw_handle())
208 };
209
210 (raw, address)
211 }
212
213 fn statistics(&self) -> &Arc<Statistics> {
214 &self.statistics
215 }
216}