1use std::collections::HashMap;
8use std::fmt;
9use std::fs::{File, OpenOptions};
10use std::io::Result;
11use std::os::unix::io::AsRawFd;
12use std::path::{Path, PathBuf};
13use std::sync::{Arc, RwLock};
14
15use fuse_backend_rs::file_buf::FileVolatileSlice;
16use nix::sys::uio;
17
18use nydus_api::LocalFsConfig;
19use nydus_utils::metrics::BackendMetrics;
20
21use crate::backend::{BackendError, BackendResult, BlobBackend, BlobReader};
22use crate::utils::{readv, MemSliceCursor};
23
24type LocalFsResult<T> = std::result::Result<T, LocalFsError>;
25
26#[derive(Debug)]
28pub enum LocalFsError {
29 BlobFile(String),
30 ReadBlob(String),
31}
32
33impl fmt::Display for LocalFsError {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 LocalFsError::BlobFile(s) => write!(f, "{}", s),
37 LocalFsError::ReadBlob(s) => write!(f, "{}", s),
38 }
39 }
40}
41
42impl From<LocalFsError> for BackendError {
43 fn from(error: LocalFsError) -> Self {
44 BackendError::LocalFs(error)
45 }
46}
47
48struct LocalFsEntry {
49 id: String,
50 file: File,
51 metrics: Arc<BackendMetrics>,
52}
53
54impl BlobReader for LocalFsEntry {
55 fn blob_size(&self) -> BackendResult<u64> {
56 self.file.metadata().map(|v| v.len()).map_err(|e| {
57 let msg = format!("failed to get size of localfs blob {}, {}", self.id, e);
58 LocalFsError::BlobFile(msg).into()
59 })
60 }
61
62 fn try_read(&self, buf: &mut [u8], offset: u64) -> BackendResult<usize> {
63 uio::pread(self.file.as_raw_fd(), buf, offset as i64).map_err(|e| {
64 let msg = format!("failed to read data from blob {}, {}", self.id, e);
65 LocalFsError::ReadBlob(msg).into()
66 })
67 }
68
69 fn readv(
70 &self,
71 bufs: &[FileVolatileSlice],
72 offset: u64,
73 max_size: usize,
74 ) -> BackendResult<usize> {
75 let mut c = MemSliceCursor::new(bufs);
76 let mut iovec = c.consume(max_size);
77
78 readv(self.file.as_raw_fd(), &mut iovec, offset).map_err(|e| {
79 let msg = format!("failed to read data from blob {}, {}", self.id, e);
80 LocalFsError::ReadBlob(msg).into()
81 })
82 }
83
84 fn metrics(&self) -> &BackendMetrics {
85 &self.metrics
86 }
87}
88
89#[derive(Default)]
91pub struct LocalFs {
92 blob_file: String,
94 dir: String,
97 alt_dirs: Vec<String>,
99 metrics: Arc<BackendMetrics>,
101 entries: RwLock<HashMap<String, Arc<LocalFsEntry>>>,
103}
104
105impl LocalFs {
106 pub fn new(config: &LocalFsConfig, id: Option<&str>) -> Result<LocalFs> {
107 let id = id.ok_or_else(|| einval!("LocalFs requires blob_id"))?;
108
109 if config.blob_file.is_empty() && config.dir.is_empty() {
110 return Err(einval!("blob file or dir is required"));
111 }
112
113 Ok(LocalFs {
114 blob_file: config.blob_file.clone(),
115 dir: config.dir.clone(),
116 alt_dirs: config.alt_dirs.clone(),
117 metrics: BackendMetrics::new(id, "localfs"),
118 entries: RwLock::new(HashMap::new()),
119 })
120 }
121
122 fn get_blob_path(&self, blob_id: &str) -> LocalFsResult<PathBuf> {
125 let path = if !self.blob_file.is_empty() {
126 Path::new(&self.blob_file).to_path_buf()
127 } else {
128 let is_valid = |dir: &PathBuf| -> bool {
130 let blob = Path::new(&dir).join(blob_id);
131 if let Ok(meta) = std::fs::metadata(blob) {
132 meta.len() != 0
133 } else {
134 false
135 }
136 };
137
138 let blob = Path::new(&self.dir).join(blob_id);
139 if is_valid(&blob) || self.alt_dirs.is_empty() {
140 blob
141 } else {
142 let mut file = PathBuf::new();
143 for dir in &self.alt_dirs {
144 file = Path::new(dir).join(blob_id);
145 if is_valid(&file) {
146 break;
147 }
148 }
149 file
150 }
151 };
152
153 path.canonicalize().map_err(|e| {
154 LocalFsError::BlobFile(format!("invalid file path {}, {}", path.display(), e))
155 })
156 }
157
158 #[allow(clippy::mutex_atomic)]
159 fn get_blob(&self, blob_id: &str) -> LocalFsResult<Arc<dyn BlobReader>> {
160 if let Some(entry) = self.entries.read().unwrap().get(blob_id) {
162 return Ok(entry.clone());
163 }
164
165 let blob_file_path = self.get_blob_path(blob_id)?;
166 let file = OpenOptions::new()
167 .read(true)
168 .open(&blob_file_path)
169 .map_err(|e| {
170 let msg = format!(
171 "failed to open blob file {}, {}",
172 blob_file_path.display(),
173 e
174 );
175 LocalFsError::BlobFile(msg)
176 })?;
177 let mut table_guard = self.entries.write().unwrap();
179 if let Some(entry) = table_guard.get(blob_id) {
180 Ok(entry.clone())
181 } else {
182 let entry = Arc::new(LocalFsEntry {
183 id: blob_id.to_owned(),
184 file,
185 metrics: self.metrics.clone(),
186 });
187 table_guard.insert(blob_id.to_string(), entry.clone());
188 Ok(entry)
189 }
190 }
191}
192
193impl BlobBackend for LocalFs {
194 fn shutdown(&self) {}
195
196 fn metrics(&self) -> &BackendMetrics {
197 &self.metrics
198 }
199
200 fn get_reader(&self, blob_id: &str) -> BackendResult<Arc<dyn BlobReader>> {
201 self.get_blob(blob_id).map_err(|e| e.into())
202 }
203}
204
205impl Drop for LocalFs {
206 fn drop(&mut self) {
207 self.metrics.release().unwrap_or_else(|e| error!("{:?}", e));
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use std::io::Write;
215 use std::os::unix::io::{FromRawFd, IntoRawFd};
216 use vmm_sys_util::tempfile::TempFile;
217
218 #[test]
219 fn test_invalid_localfs_new() {
220 let config = LocalFsConfig {
221 blob_file: "".to_string(),
222 dir: "".to_string(),
223 alt_dirs: Vec::new(),
224 };
225 assert!(LocalFs::new(&config, Some("test")).is_err());
226
227 let config = LocalFsConfig {
228 blob_file: "/a/b/c".to_string(),
229 dir: "/a/b".to_string(),
230 alt_dirs: Vec::new(),
231 };
232 assert!(LocalFs::new(&config, None).is_err());
233 }
234
235 #[test]
236 fn test_localfs_get_blob_path() {
237 let config = LocalFsConfig {
238 blob_file: "/a/b/cxxxxxxxxxxxxxxxxxxxxxxx".to_string(),
239 dir: "/a/b".to_string(),
240 alt_dirs: Vec::new(),
241 };
242 let fs = LocalFs::new(&config, Some("test")).unwrap();
243 assert!(fs.get_blob_path("test").is_err());
244
245 let tempfile = TempFile::new().unwrap();
246 let path = tempfile.as_path();
247 let filename = path.file_name().unwrap().to_str().unwrap();
248
249 let config = LocalFsConfig {
250 blob_file: path.to_str().unwrap().to_owned(),
251 dir: path.parent().unwrap().to_str().unwrap().to_owned(),
252 alt_dirs: Vec::new(),
253 };
254 let fs = LocalFs::new(&config, Some("test")).unwrap();
255 assert_eq!(fs.get_blob_path("test").unwrap().to_str(), path.to_str());
256
257 let config = LocalFsConfig {
258 blob_file: "".to_string(),
259 dir: path.parent().unwrap().to_str().unwrap().to_owned(),
260 alt_dirs: Vec::new(),
261 };
262 let fs = LocalFs::new(&config, Some(filename)).unwrap();
263 assert_eq!(fs.get_blob_path(filename).unwrap().to_str(), path.to_str());
264
265 let config = LocalFsConfig {
266 blob_file: "".to_string(),
267 dir: "/a/b".to_string(),
268 alt_dirs: vec![
269 "/test".to_string(),
270 path.parent().unwrap().to_str().unwrap().to_owned(),
271 ],
272 };
273 let fs = LocalFs::new(&config, Some(filename)).unwrap();
274 assert_eq!(fs.get_blob_path(filename).unwrap().to_str(), path.to_str());
275 }
276
277 #[test]
278 fn test_localfs_get_blob() {
279 let tempfile = TempFile::new().unwrap();
280 let path = tempfile.as_path();
281 let filename = path.file_name().unwrap().to_str().unwrap();
282 let config = LocalFsConfig {
283 blob_file: "".to_string(),
284 dir: path.parent().unwrap().to_str().unwrap().to_owned(),
285 alt_dirs: Vec::new(),
286 };
287 let fs = LocalFs::new(&config, Some(filename)).unwrap();
288 let blob1 = fs.get_blob(filename).unwrap();
289 let blob2 = fs.get_blob(filename).unwrap();
290 assert_eq!(Arc::strong_count(&blob1), 3);
291 assert_eq!(Arc::strong_count(&blob2), 3);
292 }
293
294 #[test]
295 fn test_localfs_get_reader() {
296 let tempfile = TempFile::new().unwrap();
297 let path = tempfile.as_path();
298 let filename = path.file_name().unwrap().to_str().unwrap();
299
300 {
301 let mut file = unsafe { File::from_raw_fd(tempfile.as_file().as_raw_fd()) };
302 file.write_all(&[0x1u8, 0x2, 0x3, 0x4]).unwrap();
303 let _ = file.into_raw_fd();
304 }
305
306 let config = LocalFsConfig {
307 blob_file: "".to_string(),
308 dir: path.parent().unwrap().to_str().unwrap().to_owned(),
309 alt_dirs: Vec::new(),
310 };
311 let fs = LocalFs::new(&config, Some(filename)).unwrap();
312 let blob1 = fs.get_reader(filename).unwrap();
313 let blob2 = fs.get_reader(filename).unwrap();
314 assert_eq!(Arc::strong_count(&blob1), 3);
315
316 let mut buf1 = [0x0u8];
317 blob1.read(&mut buf1, 0x0).unwrap();
318 assert_eq!(buf1[0], 0x1);
319
320 let mut buf2 = [0x0u8];
321 let mut buf3 = [0x0u8];
322 let bufs = [
323 unsafe { FileVolatileSlice::from_raw_ptr(buf2.as_mut_ptr(), buf2.len()) },
324 unsafe { FileVolatileSlice::from_raw_ptr(buf3.as_mut_ptr(), buf3.len()) },
325 ];
326
327 assert_eq!(blob2.readv(&bufs, 0x1, 2).unwrap(), 2);
328 assert_eq!(buf2[0], 0x2);
329 assert_eq!(buf3[0], 0x3);
330
331 assert_eq!(blob2.readv(&bufs, 0x3, 3).unwrap(), 1);
332 assert_eq!(buf2[0], 0x4);
333 assert_eq!(buf3[0], 0x3);
334
335 assert_eq!(blob2.blob_size().unwrap(), 4);
336 let blob4 = fs.get_blob(filename).unwrap();
337 assert_eq!(blob4.blob_size().unwrap(), 4);
338 }
339}