]>
Commit | Line | Data |
---|---|---|
c443f58b | 1 | //! Asynchronous fuse implementation. |
c50f8744 | 2 | |
c443f58b | 3 | use std::collections::BTreeMap; |
c443f58b WB |
4 | use std::ffi::{OsStr, OsString}; |
5 | use std::future::Future; | |
6 | use std::io; | |
7 | use std::mem; | |
8 | use std::ops::Range; | |
a8aff353 | 9 | use std::os::unix::ffi::OsStrExt; |
c50f8744 | 10 | use std::path::Path; |
c443f58b WB |
11 | use std::pin::Pin; |
12 | use std::sync::atomic::{AtomicUsize, Ordering}; | |
13 | use std::sync::{Arc, RwLock}; | |
14 | use std::task::{Context, Poll}; | |
c50f8744 | 15 | |
c443f58b WB |
16 | use anyhow::{format_err, Error}; |
17 | use futures::channel::mpsc::UnboundedSender; | |
18 | use futures::select; | |
19 | use futures::sink::SinkExt; | |
20 | use futures::stream::{StreamExt, TryStreamExt}; | |
c50f8744 | 21 | |
6ef1b649 | 22 | use proxmox_io::vec; |
c443f58b WB |
23 | use pxar::accessor::{self, EntryRangeInfo, ReadAt}; |
24 | ||
25 | use proxmox_fuse::requests::{self, FuseRequest}; | |
26 | use proxmox_fuse::{EntryParam, Fuse, ReplyBufState, Request, ROOT_ID}; | |
f26d7ca5 | 27 | use proxmox_lang::io_format_err; |
25877d05 | 28 | use proxmox_sys::fs::xattr; |
cf1bd081 | 29 | |
c443f58b WB |
30 | /// We mark inodes for regular files this way so we know how to access them. |
31 | const NON_DIRECTORY_INODE: u64 = 1u64 << 63; | |
32 | ||
33 | #[inline] | |
34 | fn is_dir_inode(inode: u64) -> bool { | |
35 | 0 == (inode & NON_DIRECTORY_INODE) | |
3e56c4ab CE |
36 | } |
37 | ||
c443f58b WB |
38 | /// Our reader type instance used for accessors. |
39 | pub type Reader = Arc<dyn ReadAt + Send + Sync + 'static>; | |
40 | ||
41 | /// Our Accessor type instance. | |
42 | pub type Accessor = accessor::aio::Accessor<Reader>; | |
43 | ||
44 | /// Our Directory type instance. | |
45 | pub type Directory = accessor::aio::Directory<Reader>; | |
46 | ||
47 | /// Our FileEntry type instance. | |
48 | pub type FileEntry = accessor::aio::FileEntry<Reader>; | |
49 | ||
50 | /// Our FileContents type instance. | |
51 | pub type FileContents = accessor::aio::FileContents<Reader>; | |
52 | ||
53 | pub struct Session { | |
54 | fut: Pin<Box<dyn Future<Output = Result<(), Error>> + Send + Sync + 'static>>, | |
3e56c4ab CE |
55 | } |
56 | ||
c443f58b WB |
57 | impl Session { |
58 | /// Create a fuse session for an archive. | |
59 | pub async fn mount_path( | |
60 | archive_path: &Path, | |
61 | options: &OsStr, | |
62 | verbose: bool, | |
63 | mountpoint: &Path, | |
64 | ) -> Result<Self, Error> { | |
26e78a2e | 65 | // TODO: Add a buffered/caching ReadAt layer? |
c443f58b WB |
66 | let file = std::fs::File::open(archive_path)?; |
67 | let file_size = file.metadata()?.len(); | |
68 | let reader: Reader = Arc::new(accessor::sync::FileReader::new(file)); | |
69 | let accessor = Accessor::new(reader, file_size).await?; | |
70 | Self::mount(accessor, options, verbose, mountpoint) | |
3e56c4ab | 71 | } |
3e56c4ab | 72 | |
c443f58b WB |
73 | /// Create a new fuse session for the given pxar `Accessor`. |
74 | pub fn mount( | |
75 | accessor: Accessor, | |
76 | options: &OsStr, | |
77 | verbose: bool, | |
78 | path: &Path, | |
79 | ) -> Result<Self, Error> { | |
80 | let fuse = Fuse::builder("pxar-mount")? | |
81 | .debug() | |
82 | .options_os(options)? | |
83 | .enable_readdirplus() | |
84 | .enable_read() | |
85 | .enable_readlink() | |
86 | .enable_read_xattr() | |
87 | .build()? | |
88 | .mount(path)?; | |
89 | ||
90 | let session = SessionImpl::new(accessor, verbose); | |
91 | ||
92 | Ok(Self { | |
93 | fut: Box::pin(session.main(fuse)), | |
94 | }) | |
95 | } | |
3e56c4ab CE |
96 | } |
97 | ||
c443f58b WB |
98 | impl Future for Session { |
99 | type Output = Result<(), Error>; | |
100 | ||
101 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { | |
102 | Pin::new(&mut self.fut).poll(cx) | |
3e56c4ab | 103 | } |
946b72a6 CE |
104 | } |
105 | ||
c443f58b WB |
106 | /// We use this to return an errno value back to the kernel. |
107 | macro_rules! io_return { | |
73fba2ed | 108 | ($errno:expr) => {{ |
c443f58b | 109 | return Err(::std::io::Error::from_raw_os_error($errno).into()); |
73fba2ed | 110 | }}; |
2f2b87e6 DM |
111 | } |
112 | ||
c443f58b WB |
113 | /// This is what we need to cache as a "lookup" entry. The kernel assumes that these are easily |
114 | /// accessed. | |
115 | struct Lookup { | |
116 | refs: AtomicUsize, | |
99b5b6cb | 117 | |
c443f58b WB |
118 | inode: u64, |
119 | parent: u64, | |
120 | entry_range_info: EntryRangeInfo, | |
121 | content_range: Option<Range<u64>>, | |
122 | } | |
99b5b6cb | 123 | |
c443f58b WB |
124 | impl Lookup { |
125 | fn new( | |
126 | inode: u64, | |
127 | parent: u64, | |
128 | entry_range_info: EntryRangeInfo, | |
129 | content_range: Option<Range<u64>>, | |
130 | ) -> Box<Lookup> { | |
131 | Box::new(Self { | |
132 | refs: AtomicUsize::new(1), | |
133 | inode, | |
134 | parent, | |
135 | entry_range_info, | |
136 | content_range, | |
137 | }) | |
f701d033 | 138 | } |
f701d033 | 139 | |
c443f58b WB |
140 | /// Decrease the reference count by `count`. Note that this must not include the reference held |
141 | /// by `self` itself, so this must not decrease the count below 2. | |
142 | fn forget(&self, count: usize) -> Result<(), Error> { | |
143 | loop { | |
144 | let old = self.refs.load(Ordering::Acquire); | |
145 | if count >= old { | |
f26d7ca5 DC |
146 | // We use this to bail out of a functionin an unexpected error case. This will cause the fuse |
147 | // request to be answered with a generic `EIO` error code. The error message contained in here | |
148 | // will be printed to stdout if the verbose flag is used, otherwise silently dropped. | |
149 | return Err(io_format_err!("reference count underflow").into()); | |
c443f58b WB |
150 | } |
151 | let new = old - count; | |
152 | match self | |
153 | .refs | |
154 | .compare_exchange(old, new, Ordering::SeqCst, Ordering::SeqCst) | |
155 | { | |
156 | Ok(_) => break Ok(()), | |
157 | Err(_) => continue, | |
158 | } | |
c50f8744 | 159 | } |
c443f58b | 160 | } |
c50f8744 | 161 | |
c443f58b WB |
162 | fn get_ref<'a>(&self, session: &'a SessionImpl) -> LookupRef<'a> { |
163 | if self.refs.fetch_add(1, Ordering::AcqRel) == 0 { | |
164 | panic!("atomic refcount increased from 0 to 1"); | |
c50f8744 CE |
165 | } |
166 | ||
c443f58b WB |
167 | LookupRef { |
168 | session, | |
169 | lookup: self as *const Lookup, | |
170 | } | |
970687c9 | 171 | } |
c443f58b | 172 | } |
970687c9 | 173 | |
c443f58b WB |
174 | struct LookupRef<'a> { |
175 | session: &'a SessionImpl, | |
176 | lookup: *const Lookup, | |
177 | } | |
178 | ||
179 | unsafe impl<'a> Send for LookupRef<'a> {} | |
180 | unsafe impl<'a> Sync for LookupRef<'a> {} | |
970687c9 | 181 | |
c443f58b WB |
182 | impl<'a> Clone for LookupRef<'a> { |
183 | fn clone(&self) -> Self { | |
184 | self.get_ref(self.session) | |
970687c9 | 185 | } |
c443f58b WB |
186 | } |
187 | ||
188 | impl<'a> std::ops::Deref for LookupRef<'a> { | |
189 | type Target = Lookup; | |
970687c9 | 190 | |
c443f58b WB |
191 | fn deref(&self) -> &Self::Target { |
192 | unsafe { &*self.lookup } | |
c50f8744 | 193 | } |
c443f58b | 194 | } |
c50f8744 | 195 | |
c443f58b WB |
196 | impl<'a> Drop for LookupRef<'a> { |
197 | fn drop(&mut self) { | |
198 | if self.lookup.is_null() { | |
199 | return; | |
c50f8744 CE |
200 | } |
201 | ||
c443f58b WB |
202 | if self.refs.fetch_sub(1, Ordering::AcqRel) == 1 { |
203 | let inode = self.inode; | |
204 | drop(self.session.lookups.write().unwrap().remove(&inode)); | |
c50f8744 | 205 | } |
c443f58b WB |
206 | } |
207 | } | |
c50f8744 | 208 | |
c443f58b WB |
209 | impl<'a> LookupRef<'a> { |
210 | fn leak(mut self) -> &'a Lookup { | |
211 | unsafe { &*mem::replace(&mut self.lookup, std::ptr::null()) } | |
c50f8744 | 212 | } |
c443f58b | 213 | } |
c50f8744 | 214 | |
c443f58b WB |
215 | struct SessionImpl { |
216 | accessor: Accessor, | |
217 | verbose: bool, | |
218 | lookups: RwLock<BTreeMap<u64, Box<Lookup>>>, | |
219 | } | |
c50f8744 | 220 | |
c443f58b WB |
221 | impl SessionImpl { |
222 | fn new(accessor: Accessor, verbose: bool) -> Self { | |
223 | let root = Lookup::new( | |
224 | ROOT_ID, | |
225 | ROOT_ID, | |
226 | EntryRangeInfo::toplevel(0..accessor.size()), | |
227 | None, | |
228 | ); | |
229 | ||
230 | let mut tree = BTreeMap::new(); | |
231 | tree.insert(ROOT_ID, root); | |
232 | ||
233 | Self { | |
234 | accessor, | |
235 | verbose, | |
236 | lookups: RwLock::new(tree), | |
237 | } | |
c50f8744 | 238 | } |
f701d033 | 239 | |
c443f58b | 240 | /// Here's how we deal with errors: |
3e56c4ab | 241 | /// |
e10fccf5 HL |
242 | /// Any error will be logged if a log level of at least 'debug' was set, otherwise the |
243 | /// message will be silently dropped. | |
c443f58b WB |
244 | /// |
245 | /// Opaque errors will cause the fuse main loop to bail out with that error. | |
246 | /// | |
247 | /// `io::Error`s will cause the fuse request to responded to with the given `io::Error`. An | |
248 | /// `io::ErrorKind::Other` translates to a generic `EIO`. | |
249 | async fn handle_err( | |
250 | &self, | |
251 | request: impl FuseRequest, | |
252 | err: Error, | |
253 | mut sender: UnboundedSender<Error>, | |
254 | ) { | |
255 | let final_result = match err.downcast::<io::Error>() { | |
256 | Ok(err) => { | |
e10fccf5 HL |
257 | if err.kind() == io::ErrorKind::Other { |
258 | log::error!("an IO error occurred: {}", err); | |
c443f58b | 259 | } |
3e56c4ab | 260 | |
c443f58b WB |
261 | // fail the request |
262 | request.io_fail(err).map_err(Error::from) | |
263 | } | |
264 | Err(err) => { | |
265 | // `bail` (non-`io::Error`) is used for fatal errors which should actually cancel: | |
e10fccf5 | 266 | log::error!("internal error: {}, bailing out", err); |
c443f58b | 267 | Err(err) |
3e56c4ab | 268 | } |
c443f58b WB |
269 | }; |
270 | if let Err(err) = final_result { | |
271 | // either we failed to send the error code to fuse, or the above was not an | |
272 | // `io::Error`, so in this case notify the main loop: | |
273 | sender | |
274 | .send(err) | |
275 | .await | |
276 | .expect("failed to propagate error to main loop"); | |
3e56c4ab | 277 | } |
3e56c4ab CE |
278 | } |
279 | ||
c443f58b WB |
280 | async fn main(self, fuse: Fuse) -> Result<(), Error> { |
281 | Arc::new(self).main_do(fuse).await | |
eedd1f95 DM |
282 | } |
283 | ||
c443f58b WB |
284 | async fn main_do(self: Arc<Self>, fuse: Fuse) -> Result<(), Error> { |
285 | let (err_send, mut err_recv) = futures::channel::mpsc::unbounded::<Error>(); | |
286 | let mut fuse = fuse.fuse(); // make this a futures::stream::FusedStream! | |
287 | loop { | |
288 | select! { | |
289 | request = fuse.try_next() => match request? { | |
290 | Some(request) => { | |
291 | tokio::spawn(Arc::clone(&self).handle_request(request, err_send.clone())); | |
292 | } | |
293 | None => break, | |
294 | }, | |
295 | err = err_recv.next() => match err { | |
296 | Some(err) => if self.verbose { | |
e10fccf5 | 297 | log::error!("cancelling fuse main loop due to error: {}", err); |
c443f58b WB |
298 | return Err(err); |
299 | }, | |
300 | None => panic!("error channel was closed unexpectedly"), | |
301 | }, | |
302 | } | |
303 | } | |
304 | Ok(()) | |
eedd1f95 DM |
305 | } |
306 | ||
c443f58b WB |
307 | async fn handle_request( |
308 | self: Arc<Self>, | |
309 | request: Request, | |
310 | mut err_sender: UnboundedSender<Error>, | |
311 | ) { | |
312 | let result: Result<(), Error> = match request { | |
313 | Request::Lookup(request) => { | |
314 | match self.lookup(request.parent, &request.file_name).await { | |
315 | Ok((entry, lookup)) => match request.reply(&entry) { | |
316 | Ok(()) => { | |
317 | lookup.leak(); | |
318 | Ok(()) | |
319 | } | |
320 | Err(err) => Err(Error::from(err)), | |
321 | }, | |
322 | Err(err) => return self.handle_err(request, err, err_sender).await, | |
323 | } | |
324 | } | |
325 | Request::Forget(request) => match self.forget(request.inode, request.count as usize) { | |
326 | Ok(()) => { | |
327 | request.reply(); | |
328 | Ok(()) | |
329 | } | |
330 | Err(err) => return self.handle_err(request, err, err_sender).await, | |
331 | }, | |
332 | Request::Getattr(request) => match self.getattr(request.inode).await { | |
347e0d4c | 333 | Ok(stat) => request.reply(&stat, f64::MAX).map_err(Error::from), |
c443f58b WB |
334 | Err(err) => return self.handle_err(request, err, err_sender).await, |
335 | }, | |
336 | Request::ReaddirPlus(mut request) => match self.readdirplus(&mut request).await { | |
337 | Ok(lookups) => match request.reply() { | |
338 | Ok(()) => { | |
339 | for i in lookups { | |
340 | i.leak(); | |
341 | } | |
342 | Ok(()) | |
343 | } | |
344 | Err(err) => Err(Error::from(err)), | |
345 | }, | |
346 | Err(err) => return self.handle_err(request, err, err_sender).await, | |
347 | }, | |
348 | Request::Read(request) => { | |
349 | match self.read(request.inode, request.size, request.offset).await { | |
350 | Ok(data) => request.reply(&data).map_err(Error::from), | |
351 | Err(err) => return self.handle_err(request, err, err_sender).await, | |
3e69abef | 352 | } |
3e69abef | 353 | } |
c443f58b WB |
354 | Request::Readlink(request) => match self.readlink(request.inode).await { |
355 | Ok(data) => request.reply(&data).map_err(Error::from), | |
356 | Err(err) => return self.handle_err(request, err, err_sender).await, | |
357 | }, | |
358 | Request::ListXAttrSize(request) => match self.listxattrs(request.inode).await { | |
359 | Ok(data) => request | |
360 | .reply( | |
361 | data.into_iter() | |
362 | .fold(0, |sum, i| sum + i.name().to_bytes_with_nul().len()), | |
363 | ) | |
364 | .map_err(Error::from), | |
365 | Err(err) => return self.handle_err(request, err, err_sender).await, | |
366 | }, | |
367 | Request::ListXAttr(mut request) => match self.listxattrs_into(&mut request).await { | |
368 | Ok(ReplyBufState::Ok) => request.reply().map_err(Error::from), | |
369 | Ok(ReplyBufState::Full) => request.fail_full().map_err(Error::from), | |
370 | Err(err) => return self.handle_err(request, err, err_sender).await, | |
371 | }, | |
372 | Request::GetXAttrSize(request) => { | |
373 | match self.getxattr(request.inode, &request.attr_name).await { | |
374 | Ok(xattr) => request.reply(xattr.value().len()).map_err(Error::from), | |
375 | Err(err) => return self.handle_err(request, err, err_sender).await, | |
376 | } | |
377 | } | |
378 | Request::GetXAttr(request) => { | |
379 | match self.getxattr(request.inode, &request.attr_name).await { | |
380 | Ok(xattr) => request.reply(xattr.value()).map_err(Error::from), | |
381 | Err(err) => return self.handle_err(request, err, err_sender).await, | |
382 | } | |
383 | } | |
384 | other => { | |
e10fccf5 | 385 | log::error!("Received unexpected fuse request"); |
c443f58b WB |
386 | other.fail(libc::ENOSYS).map_err(Error::from) |
387 | } | |
388 | }; | |
389 | ||
390 | if let Err(err) = result { | |
391 | err_sender | |
392 | .send(err) | |
393 | .await | |
394 | .expect("failed to propagate error to main loop"); | |
395 | } | |
f701d033 DM |
396 | } |
397 | ||
c443f58b WB |
398 | fn get_lookup(&self, inode: u64) -> Result<LookupRef, Error> { |
399 | let lookups = self.lookups.read().unwrap(); | |
400 | if let Some(lookup) = lookups.get(&inode) { | |
401 | return Ok(lookup.get_ref(self)); | |
402 | } | |
403 | io_return!(libc::ENOENT); | |
f701d033 DM |
404 | } |
405 | ||
c443f58b WB |
406 | async fn open_dir(&self, inode: u64) -> Result<Directory, Error> { |
407 | if inode == ROOT_ID { | |
408 | Ok(self.accessor.open_root().await?) | |
409 | } else if !is_dir_inode(inode) { | |
410 | io_return!(libc::ENOTDIR); | |
411 | } else { | |
412 | Ok(unsafe { self.accessor.open_dir_at_end(inode).await? }) | |
413 | } | |
f701d033 DM |
414 | } |
415 | ||
c443f58b WB |
416 | async fn open_entry(&self, lookup: &LookupRef<'_>) -> io::Result<FileEntry> { |
417 | unsafe { | |
418 | self.accessor | |
419 | .open_file_at_range(&lookup.entry_range_info) | |
420 | .await | |
421 | } | |
f701d033 DM |
422 | } |
423 | ||
c443f58b WB |
424 | fn open_content(&self, lookup: &LookupRef) -> Result<FileContents, Error> { |
425 | if is_dir_inode(lookup.inode) { | |
426 | io_return!(libc::EISDIR); | |
427 | } | |
6de36b94 | 428 | |
c443f58b WB |
429 | match lookup.content_range.clone() { |
430 | Some(range) => Ok(unsafe { self.accessor.open_contents_at_range(range) }), | |
431 | None => io_return!(libc::EBADF), | |
432 | } | |
433 | } | |
6de36b94 | 434 | |
c443f58b WB |
435 | fn make_lookup(&self, parent: u64, inode: u64, entry: &FileEntry) -> Result<LookupRef, Error> { |
436 | let lookups = self.lookups.read().unwrap(); | |
437 | if let Some(lookup) = lookups.get(&inode) { | |
438 | return Ok(lookup.get_ref(self)); | |
439 | } | |
440 | drop(lookups); | |
441 | ||
442 | let entry = Lookup::new( | |
443 | inode, | |
444 | parent, | |
445 | entry.entry_range_info().clone(), | |
446 | entry.content_range()?, | |
447 | ); | |
448 | let reference = entry.get_ref(self); | |
449 | entry.refs.store(1, Ordering::Release); | |
450 | ||
451 | let mut lookups = self.lookups.write().unwrap(); | |
452 | if let Some(lookup) = lookups.get(&inode) { | |
453 | return Ok(lookup.get_ref(self)); | |
454 | } | |
f701d033 | 455 | |
c443f58b WB |
456 | lookups.insert(inode, entry); |
457 | drop(lookups); | |
458 | Ok(reference) | |
f701d033 | 459 | } |
25ad4cbf | 460 | |
c443f58b WB |
461 | fn forget(&self, inode: u64, count: usize) -> Result<(), Error> { |
462 | let node = self.get_lookup(inode)?; | |
463 | node.forget(count)?; | |
464 | Ok(()) | |
465 | } | |
fa0b9500 | 466 | |
96b74831 FG |
467 | async fn lookup( |
468 | &'_ self, | |
c443f58b WB |
469 | parent: u64, |
470 | file_name: &OsStr, | |
96b74831 | 471 | ) -> Result<(EntryParam, LookupRef<'_>), Error> { |
c443f58b | 472 | let dir = self.open_dir(parent).await?; |
72677fb0 | 473 | |
c443f58b WB |
474 | let entry = match { dir }.lookup(file_name).await? { |
475 | Some(entry) => entry, | |
476 | None => io_return!(libc::ENOENT), | |
477 | }; | |
72677fb0 | 478 | |
c443f58b WB |
479 | let entry = if let pxar::EntryKind::Hardlink(_) = entry.kind() { |
480 | // we don't know the file's end-offset, so we'll just allow the decoder to decode the | |
481 | // entire rest of the archive until we figure out something better... | |
482 | let entry = self.accessor.follow_hardlink(&entry).await?; | |
72677fb0 | 483 | |
c443f58b WB |
484 | if let pxar::EntryKind::Hardlink(_) = entry.kind() { |
485 | // hardlinks must not point to other hardlinks... | |
486 | io_return!(libc::ELOOP); | |
fa0b9500 CE |
487 | } |
488 | ||
c443f58b WB |
489 | entry |
490 | } else { | |
491 | entry | |
492 | }; | |
72677fb0 | 493 | |
c443f58b WB |
494 | let response = to_entry(&entry)?; |
495 | let inode = response.inode; | |
496 | Ok((response, self.make_lookup(parent, inode, &entry)?)) | |
fa0b9500 CE |
497 | } |
498 | ||
c443f58b WB |
499 | async fn getattr(&self, inode: u64) -> Result<libc::stat, Error> { |
500 | let entry = unsafe { | |
bdfa6370 TL |
501 | self.accessor |
502 | .open_file_at_range(&self.get_lookup(inode)?.entry_range_info) | |
503 | .await? | |
c443f58b WB |
504 | }; |
505 | to_stat(inode, &entry) | |
506 | } | |
507 | ||
96b74831 FG |
508 | async fn readdirplus( |
509 | &'_ self, | |
c443f58b | 510 | request: &mut requests::ReaddirPlus, |
96b74831 | 511 | ) -> Result<Vec<LookupRef<'_>>, Error> { |
c443f58b WB |
512 | let mut lookups = Vec::new(); |
513 | let offset = usize::try_from(request.offset) | |
514 | .map_err(|_| io_format_err!("directory offset out of range"))?; | |
515 | ||
516 | let dir = self.open_dir(request.inode).await?; | |
517 | let dir_lookup = self.get_lookup(request.inode)?; | |
518 | ||
519 | let entry_count = dir.read_dir().count() as isize; | |
520 | ||
521 | let mut next = offset as isize; | |
522 | let mut iter = dir.read_dir().skip(offset); | |
523 | while let Some(file) = iter.next().await { | |
524 | next += 1; | |
525 | let file = file?.decode_entry().await?; | |
526 | let stat = to_stat(to_inode(&file), &file)?; | |
527 | let name = file.file_name(); | |
87b4b63e MS |
528 | if request |
529 | .add_entry(name, &stat, next, 1, f64::MAX, f64::MAX)? | |
530 | .is_full() | |
531 | { | |
532 | return Ok(lookups); | |
5d2158e8 | 533 | } |
c443f58b WB |
534 | lookups.push(self.make_lookup(request.inode, stat.st_ino, &file)?); |
535 | } | |
536 | ||
537 | if next == entry_count { | |
538 | next += 1; | |
539 | let file = dir.lookup_self().await?; | |
540 | let stat = to_stat(to_inode(&file), &file)?; | |
541 | let name = OsStr::new("."); | |
87b4b63e MS |
542 | if request |
543 | .add_entry(name, &stat, next, 1, f64::MAX, f64::MAX)? | |
544 | .is_full() | |
545 | { | |
546 | return Ok(lookups); | |
5d2158e8 | 547 | } |
c443f58b WB |
548 | lookups.push(LookupRef::clone(&dir_lookup)); |
549 | } | |
550 | ||
551 | if next == entry_count + 1 { | |
552 | next += 1; | |
553 | let lookup = self.get_lookup(dir_lookup.parent)?; | |
554 | let parent_dir = self.open_dir(lookup.inode).await?; | |
555 | let file = parent_dir.lookup_self().await?; | |
556 | let stat = to_stat(to_inode(&file), &file)?; | |
557 | let name = OsStr::new(".."); | |
87b4b63e MS |
558 | if request |
559 | .add_entry(name, &stat, next, 1, f64::MAX, f64::MAX)? | |
560 | .is_full() | |
561 | { | |
562 | return Ok(lookups); | |
fa0b9500 | 563 | } |
c443f58b WB |
564 | lookups.push(lookup); |
565 | } | |
72677fb0 | 566 | |
c443f58b | 567 | Ok(lookups) |
fa0b9500 CE |
568 | } |
569 | ||
c443f58b WB |
570 | async fn read(&self, inode: u64, len: usize, offset: u64) -> Result<Vec<u8>, Error> { |
571 | let file = self.get_lookup(inode)?; | |
572 | let content = self.open_content(&file)?; | |
573 | let mut buf = vec::undefined(len); | |
6171c727 DC |
574 | let mut pos = 0; |
575 | // fuse' read is different from normal read - no short reads allowed except for EOF! | |
576 | // the returned data will be 0-byte padded up to len by fuse | |
577 | loop { | |
578 | let got = content | |
579 | .read_at(&mut buf[pos..], offset + pos as u64) | |
580 | .await?; | |
581 | pos += got; | |
582 | if got == 0 || pos >= len { | |
583 | break; | |
584 | } | |
585 | } | |
586 | buf.truncate(pos); | |
c443f58b WB |
587 | Ok(buf) |
588 | } | |
589 | ||
590 | async fn readlink(&self, inode: u64) -> Result<OsString, Error> { | |
591 | let lookup = self.get_lookup(inode)?; | |
592 | let file = self.open_entry(&lookup).await?; | |
593 | match file.get_symlink() { | |
594 | None => io_return!(libc::EINVAL), | |
595 | Some(link) => Ok(link.to_owned()), | |
fa0b9500 | 596 | } |
c443f58b | 597 | } |
fa0b9500 | 598 | |
c443f58b | 599 | async fn listxattrs(&self, inode: u64) -> Result<Vec<pxar::format::XAttr>, Error> { |
c443f58b | 600 | let lookup = self.get_lookup(inode)?; |
bdfa6370 | 601 | let metadata = self.open_entry(&lookup).await?.into_entry().into_metadata(); |
cf1bd081 WB |
602 | |
603 | let mut xattrs = metadata.xattrs; | |
604 | ||
605 | use pxar::format::XAttr; | |
606 | ||
607 | if let Some(fcaps) = metadata.fcaps { | |
608 | xattrs.push(XAttr::new(xattr::xattr_name_fcaps().to_bytes(), fcaps.data)); | |
609 | } | |
610 | ||
611 | // TODO: Special cases: | |
612 | // b"system.posix_acl_access | |
613 | // b"system.posix_acl_default | |
614 | // | |
615 | // For these we need to be able to create posix acl format entries, at that point we could | |
616 | // just ditch libacl as well... | |
617 | ||
618 | Ok(xattrs) | |
fa0b9500 | 619 | } |
c50f8744 | 620 | |
c443f58b WB |
621 | async fn listxattrs_into( |
622 | &self, | |
623 | request: &mut requests::ListXAttr, | |
624 | ) -> Result<ReplyBufState, Error> { | |
625 | let xattrs = self.listxattrs(request.inode).await?; | |
626 | ||
627 | for entry in xattrs { | |
87b4b63e MS |
628 | if request.add_c_string(entry.name()).is_full() { |
629 | return Ok(ReplyBufState::Full); | |
c443f58b | 630 | } |
c50f8744 | 631 | } |
c443f58b WB |
632 | |
633 | Ok(ReplyBufState::Ok) | |
c50f8744 | 634 | } |
c50f8744 | 635 | |
c443f58b WB |
636 | async fn getxattr(&self, inode: u64, xattr: &OsStr) -> Result<pxar::format::XAttr, Error> { |
637 | // TODO: pxar::Accessor could probably get a more optimized method to fetch a specific | |
638 | // xattr for an entry... | |
c443f58b WB |
639 | let xattrs = self.listxattrs(inode).await?; |
640 | for entry in xattrs { | |
641 | if entry.name().to_bytes() == xattr.as_bytes() { | |
642 | return Ok(entry); | |
643 | } | |
644 | } | |
645 | io_return!(libc::ENODATA); | |
646 | } | |
d21ae955 CE |
647 | } |
648 | ||
c443f58b WB |
649 | #[inline] |
650 | fn to_entry(entry: &FileEntry) -> Result<EntryParam, Error> { | |
9a37bd6c | 651 | to_entry_param(to_inode(entry), entry) |
22eaa905 CE |
652 | } |
653 | ||
c443f58b WB |
654 | #[inline] |
655 | fn to_inode(entry: &FileEntry) -> u64 { | |
656 | if entry.is_dir() { | |
657 | entry.entry_range_info().entry_range.end | |
658 | } else { | |
659 | entry.entry_range_info().entry_range.start | NON_DIRECTORY_INODE | |
660 | } | |
22eaa905 CE |
661 | } |
662 | ||
c443f58b WB |
663 | fn to_entry_param(inode: u64, entry: &pxar::Entry) -> Result<EntryParam, Error> { |
664 | Ok(EntryParam::simple(inode, to_stat(inode, entry)?)) | |
665 | } | |
22eaa905 | 666 | |
c443f58b WB |
667 | fn to_stat(inode: u64, entry: &pxar::Entry) -> Result<libc::stat, Error> { |
668 | let nlink = if entry.is_dir() { 2 } else { 1 }; | |
22eaa905 | 669 | |
c443f58b | 670 | let metadata = entry.metadata(); |
22eaa905 | 671 | |
c443f58b WB |
672 | let mut stat: libc::stat = unsafe { mem::zeroed() }; |
673 | stat.st_ino = inode; | |
674 | stat.st_nlink = nlink; | |
675 | stat.st_mode = u32::try_from(metadata.stat.mode) | |
676 | .map_err(|err| format_err!("mode does not fit into st_mode field: {}", err))?; | |
677 | stat.st_size = i64::try_from(entry.file_size().unwrap_or(0)) | |
678 | .map_err(|err| format_err!("size does not fit into st_size field: {}", err))?; | |
679 | stat.st_uid = metadata.stat.uid; | |
680 | stat.st_gid = metadata.stat.gid; | |
f6c6e09a WB |
681 | stat.st_atime = metadata.stat.mtime.secs; |
682 | stat.st_atime_nsec = metadata.stat.mtime.nanos as _; | |
683 | stat.st_mtime = metadata.stat.mtime.secs; | |
684 | stat.st_mtime_nsec = metadata.stat.mtime.nanos as _; | |
685 | stat.st_ctime = metadata.stat.mtime.secs; | |
686 | stat.st_ctime_nsec = metadata.stat.mtime.nanos as _; | |
c443f58b | 687 | Ok(stat) |
22eaa905 | 688 | } |