]> git.proxmox.com Git - proxmox-backup.git/blame - pbs-pxar-fuse/src/lib.rs
pxar-fuse: use ReplyBufState::is_full() when possible
[proxmox-backup.git] / pbs-pxar-fuse / src / lib.rs
CommitLineData
c443f58b 1//! Asynchronous fuse implementation.
c50f8744 2
c443f58b 3use std::collections::BTreeMap;
c443f58b
WB
4use std::ffi::{OsStr, OsString};
5use std::future::Future;
6use std::io;
7use std::mem;
8use std::ops::Range;
a8aff353 9use std::os::unix::ffi::OsStrExt;
c50f8744 10use std::path::Path;
c443f58b
WB
11use std::pin::Pin;
12use std::sync::atomic::{AtomicUsize, Ordering};
13use std::sync::{Arc, RwLock};
14use std::task::{Context, Poll};
c50f8744 15
c443f58b
WB
16use anyhow::{format_err, Error};
17use futures::channel::mpsc::UnboundedSender;
18use futures::select;
19use futures::sink::SinkExt;
20use futures::stream::{StreamExt, TryStreamExt};
c50f8744 21
6ef1b649 22use proxmox_io::vec;
c443f58b
WB
23use pxar::accessor::{self, EntryRangeInfo, ReadAt};
24
25use proxmox_fuse::requests::{self, FuseRequest};
26use proxmox_fuse::{EntryParam, Fuse, ReplyBufState, Request, ROOT_ID};
f26d7ca5 27use proxmox_lang::io_format_err;
25877d05 28use proxmox_sys::fs::xattr;
cf1bd081 29
c443f58b
WB
30/// We mark inodes for regular files this way so we know how to access them.
31const NON_DIRECTORY_INODE: u64 = 1u64 << 63;
32
33#[inline]
34fn 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.
39pub type Reader = Arc<dyn ReadAt + Send + Sync + 'static>;
40
41/// Our Accessor type instance.
42pub type Accessor = accessor::aio::Accessor<Reader>;
43
44/// Our Directory type instance.
45pub type Directory = accessor::aio::Directory<Reader>;
46
47/// Our FileEntry type instance.
48pub type FileEntry = accessor::aio::FileEntry<Reader>;
49
50/// Our FileContents type instance.
51pub type FileContents = accessor::aio::FileContents<Reader>;
52
53pub struct Session {
54 fut: Pin<Box<dyn Future<Output = Result<(), Error>> + Send + Sync + 'static>>,
3e56c4ab
CE
55}
56
c443f58b
WB
57impl 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
98impl 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.
107macro_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.
115struct 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
124impl 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
174struct LookupRef<'a> {
175 session: &'a SessionImpl,
176 lookup: *const Lookup,
177}
178
179unsafe impl<'a> Send for LookupRef<'a> {}
180unsafe impl<'a> Sync for LookupRef<'a> {}
970687c9 181
c443f58b
WB
182impl<'a> Clone for LookupRef<'a> {
183 fn clone(&self) -> Self {
184 self.get_ref(self.session)
970687c9 185 }
c443f58b
WB
186}
187
188impl<'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
196impl<'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
209impl<'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
215struct SessionImpl {
216 accessor: Accessor,
217 verbose: bool,
218 lookups: RwLock<BTreeMap<u64, Box<Lookup>>>,
219}
c50f8744 220
c443f58b
WB
221impl 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]
650fn to_entry(entry: &FileEntry) -> Result<EntryParam, Error> {
9a37bd6c 651 to_entry_param(to_inode(entry), entry)
22eaa905
CE
652}
653
c443f58b
WB
654#[inline]
655fn 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
663fn 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
667fn 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}