1 //! Low level FUSE implementation for pxar.
3 //! Allows to mount the archive as read-only filesystem to inspect its contents.
5 use std
::collections
::HashMap
;
6 use std
::convert
::TryFrom
;
7 use std
::ffi
::{CStr, CString, OsStr}
;
9 use std
::io
::BufReader
;
10 use std
::os
::unix
::ffi
::{OsStrExt, OsStringExt}
;
14 use failure
::{bail, format_err, Error}
;
15 use lazy_static
::lazy_static
;
17 use libc
::{c_char, c_int, c_void, size_t}
;
19 use super::binary_search_tree
::search_binary_tree_by
;
20 use super::decoder
::Decoder
;
21 use super::format_definition
::{PxarAttributes, PxarEntry, PxarGoodbyeItem}
;
23 /// Node ID of the root i-node
25 /// Offsets in the archive are used as i-node for the fuse implementation, as
26 /// they are unique and enough to reference each item in the pxar archive.
27 /// The only exception to this is the `FUSE_ROOT_ID`, which is defined as 1 by
29 /// This is okay since offset 1 is part of the root directory entry header and
30 /// will therefore not occur again, but remapping to the correct offset of 0 is
32 const FUSE_ROOT_ID
: u64 = 1;
34 const GOODBYE_ITEM_SIZE
: u64 = std
::mem
::size_of
::<PxarGoodbyeItem
>() as u64;
36 /// HashMap holding the mapping from the child offsets to their parent
39 /// In order to include the parent directory entry '..' in the response for
40 /// readdir callback, this mapping is needed.
41 /// Calling the lookup callback will insert the offsets into the HashMap.
42 static ref CHILD_PARENT
: Mutex
<HashMap
<u64, u64>> = Mutex
::new(HashMap
::new());
47 /// FFI types for easier readability
48 type Request
= *mut c_void
;
49 type MutPtr
= *mut c_void
;
50 type ConstPtr
= *const c_void
;
51 type StrPtr
= *const c_char
;
52 type MutStrPtr
= *mut c_char
;
55 #[link(name = "fuse3")]
57 fn fuse_session_new(args
: Option
<&FuseArgs
>, oprs
: Option
<&Operations
>, size
: size_t
, op
: ConstPtr
) -> MutPtr
;
58 fn fuse_set_signal_handlers(session
: ConstPtr
) -> c_int
;
59 fn fuse_remove_signal_handlers(session
: ConstPtr
);
60 fn fuse_daemonize(foreground
: c_int
) -> c_int
;
61 fn fuse_session_mount(session
: ConstPtr
, mountpoint
: StrPtr
) -> c_int
;
62 fn fuse_session_unmount(session
: ConstPtr
);
63 fn fuse_session_loop(session
: ConstPtr
) -> c_int
;
64 fn fuse_session_loop_mt_31(session
: ConstPtr
, clone_fd
: c_int
) -> c_int
;
65 fn fuse_session_destroy(session
: ConstPtr
);
66 fn fuse_reply_attr(req
: Request
, attr
: Option
<&libc
::stat
>, timeout
: f64) -> c_int
;
67 fn fuse_reply_err(req
: Request
, errno
: c_int
) -> c_int
;
68 fn fuse_reply_open(req
: Request
, fileinfo
: ConstPtr
) -> c_int
;
69 fn fuse_reply_buf(req
: Request
, buf
: MutStrPtr
, size
: size_t
) -> c_int
;
70 fn fuse_reply_entry(req
: Request
, entry
: Option
<&EntryParam
>) -> c_int
;
71 fn fuse_reply_xattr(req
: Request
, size
: size_t
) -> c_int
;
72 fn fuse_reply_readlink(req
: Request
, link
: StrPtr
) -> c_int
;
73 fn fuse_req_userdata(req
: Request
) -> MutPtr
;
74 fn fuse_add_direntry(req
: Request
, buf
: MutStrPtr
, bufsize
: size_t
, name
: StrPtr
, stbuf
: Option
<&libc
::stat
>, off
: c_int
) -> c_int
;
77 /// Command line arguments passed to fuse.
86 /// `Context` for callback functions providing the decoder, caches and the
87 /// offset within the archive for the i-node given by the caller.
90 goodbye_cache
: HashMap
<Inode
, Vec
<(PxarGoodbyeItem
, Offset
, Offset
)>>,
91 attr_cache
: Option
<(Inode
, PxarAttributes
)>,
96 /// Lookup the goodbye item identified by `filename` and its corresponding `hash`
98 /// Updates the goodbye table cache to contain the table for the directory given
99 /// by the i-node in the provided `Context`.
100 /// Search the first matching `hash` in the goodbye table, allowing for a fast
101 /// comparison with the items.
102 /// As there could be a hash collision, the found items filename is then compared
103 /// by seek to the corresponding item in the archive and reading its attributes
104 /// (which the lookup callback needs to do anyway).
105 /// If the filename does not match, the function is called recursively with the
106 /// rest of the goodbye table to lookup the next match.
107 /// The matching items archive offset, entry and payload size are returned.
108 /// If there is no entry with matching `filename` and `hash` a `libc::ENOENT` is
110 fn find_goodbye_entry(
114 ) -> Result
<(u64, PxarEntry
, PxarAttributes
, u64), i32> {
115 let gbt
= self.goodbye_cache
.get(&self.ino_offset
)
116 .ok_or_else(|| libc
::EIO
)?
;
117 let mut start_idx
= 0;
118 let mut skip_multiple
= 0;
120 // Search for the next goodbye entry with matching hash.
121 let idx
= search_binary_tree_by(
125 |idx
| hash
.cmp(&gbt
[idx
].0.hash
),
126 ).ok_or(libc
::ENOENT
)?
;
128 let (_item
, start
, end
) = &gbt
[idx
];
130 // At this point it is not clear if the item is a directory or not, this
131 // has to be decided based on the entry mode.
132 // `Decoder`s attributes function accepts both, offsets pointing to
133 // the start of an item (PXAR_FILENAME) or the GOODBYE_TAIL_MARKER in case
134 // of directories, so the use of start offset is fine for both cases.
135 let (entry_name
, entry
, attr
, payload_size
) =
136 self.decoder
.attributes(*start
).map_err(|_
| libc
::EIO
)?
;
138 // Possible hash collision, need to check if the found entry is indeed
139 // the filename to lookup.
140 if entry_name
.as_bytes() == filename
.to_bytes() {
141 let child_offset
= find_offset(&entry
, *start
, *end
);
142 return Ok((child_offset
, entry
, attr
, payload_size
));
144 // Hash collision, check the next entry in the goodbye table by starting
145 // from given index but skipping one more match (so hash at index itself).
152 /// `Session` stores a pointer to the session context and is used to mount the
153 /// archive to the given mountpoint.
159 /// `Operations` defines the callback function table of supported operations.
164 // The order in which the functions are listed matters, as the offset in the
165 // struct defines what function the fuse driver uses.
166 // It should therefore not be altered!
167 init
: Option
<extern fn(userdata
: MutPtr
)>,
168 destroy
: Option
<extern fn(userdata
: MutPtr
)>,
169 lookup
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
)>,
170 forget
: Option
<extern fn(req
: Request
, inode
: u64, nlookup
: u64)>,
171 getattr
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
)>,
172 setattr
: Option
<extern fn(req
: Request
, inode
: u64, attr
: MutPtr
, to_set
: c_int
, fileinfo
: MutPtr
)>,
173 readlink
: Option
<extern fn(req
: Request
, inode
: u64)>,
174 mknod
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
, mode
: c_int
, rdev
: c_int
)>,
175 mkdir
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
, mode
: c_int
)>,
176 unlink
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
)>,
177 rmdir
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
)>,
178 symlink
: Option
<extern fn(req
: Request
, link
: StrPtr
, parent
: u64, name
: StrPtr
)>,
179 rename
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
, newparent
: u64, newname
: StrPtr
, flags
: c_int
)>,
180 link
: Option
<extern fn(req
: Request
, inode
: u64, newparent
: u64, newname
: StrPtr
)>,
181 open
: Option
<extern fn(req
: Request
, indoe
: u64, fileinfo
: MutPtr
)>,
182 read
: Option
<extern fn(req
: Request
, inode
: u64, size
: size_t
, offset
: c_int
, fileinfo
: MutPtr
)>,
183 write
: Option
<extern fn(req
: Request
, inode
: u64, buffer
: StrPtr
, size
: size_t
, offset
: c_void
, fileinfo
: MutPtr
)>,
184 flush
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
)>,
185 release
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
)>,
186 fsync
: Option
<extern fn(req
: Request
, inode
: u64, datasync
: c_int
, fileinfo
: MutPtr
)>,
187 opendir
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
)>,
188 readdir
: Option
<extern fn(req
: Request
, inode
: u64, size
: size_t
, offset
: c_int
, fileinfo
: MutPtr
)>,
189 releasedir
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
)>,
190 fsyncdir
: Option
<extern fn(req
: Request
, inode
: u64, datasync
: c_int
, fileinfo
: MutPtr
)>,
191 statfs
: Option
<extern fn(req
: Request
, inode
: u64)>,
192 setxattr
: Option
<extern fn(req
: Request
, inode
: u64, name
: StrPtr
, value
: StrPtr
, size
: size_t
, flags
: c_int
)>,
193 getxattr
: Option
<extern fn(req
: Request
, inode
: u64, name
: StrPtr
, size
: size_t
)>,
194 listxattr
: Option
<extern fn(req
: Request
, inode
: u64, size
: size_t
)>,
195 removexattr
: Option
<extern fn(req
: Request
, inode
: u64, name
: StrPtr
)>,
196 access
: Option
<extern fn(req
: Request
, inode
: u64, mask
: i32)>,
197 create
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
, mode
: c_int
, fileinfo
: MutPtr
)>,
198 getlk
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
, lock
: MutPtr
)>,
199 setlk
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
, lock
: MutPtr
, sleep
: c_int
)>,
200 bmap
: Option
<extern fn(req
: Request
, inode
: u64, blocksize
: size_t
, idx
: u64)>,
201 ioctl
: Option
<extern fn(req
: Request
, inode
: u64, cmd
: c_int
, arg
: MutPtr
, fileinfo
: MutPtr
, flags
: c_int
, in_buf
: ConstPtr
, in_bufsz
: size_t
, out_bufsz
: size_t
)>,
202 poll
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
, pollhandle
: MutPtr
)>,
203 write_buf
: Option
<extern fn(req
: Request
, inode
: u64, bufv
: MutPtr
, offset
: c_int
, fileinfo
: MutPtr
)>,
204 retrieve_reply
: Option
<extern fn(req
: Request
, cookie
: ConstPtr
, inode
: u64, offset
: c_int
, bufv
: MutPtr
)>,
205 forget_multi
: Option
<extern fn(req
: Request
, count
: size_t
, forgets
: MutPtr
)>,
206 flock
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
, op
: c_int
)>,
207 fallocate
: Option
<extern fn(req
: Request
, inode
: u64, mode
: c_int
, offset
: c_int
, length
: c_int
, fileinfo
: MutPtr
)>,
208 readdirplus
: Option
<extern fn(req
: Request
, inode
: u64, size
: size_t
, offset
: c_int
, fileinfo
: MutPtr
)>,
209 copy_file_range
: Option
<extern fn(req
: Request
, ino_in
: u64, off_in
: c_int
, fi_in
: MutPtr
, ino_out
: u64, off_out
: c_int
, fi_out
: MutPtr
, len
: size_t
, flags
: c_int
)>,
215 /// Create a new low level fuse session.
217 /// `Session` is created using the provided mount options and sets the
218 /// default signal handlers.
219 /// Options have to be provided as comma separated OsStr, e.g.
220 /// ("ro,default_permissions").
221 pub fn from_path(archive_path
: &Path
, options
: &OsStr
, verbose
: bool
) -> Result
<Self, Error
> {
222 let file
= File
::open(archive_path
)?
;
223 let reader
= BufReader
::new(file
);
224 let decoder
= Decoder
::new(reader
)?
;
225 Self::new(decoder
, options
, verbose
)
228 /// Create a new low level fuse session using the given `Decoder`.
230 /// `Session` is created using the provided mount options and sets the
231 /// default signal handlers.
232 /// Options have to be provided as comma separated OsStr, e.g.
233 /// ("ro,default_permissions").
235 mut decoder
: Decoder
,
238 ) -> Result
<Self, Error
> {
239 let args
= Self::setup_args(options
, verbose
)?
;
240 let oprs
= Self::setup_callbacks();
242 let root_ino_offset
= decoder
.root_end_offset() - GOODBYE_ITEM_SIZE
;
243 let root_goodbye_table
= decoder
.goodbye_table(None
, root_ino_offset
+ GOODBYE_ITEM_SIZE
)?
;
244 let mut goodbye_cache
= HashMap
::new();
245 goodbye_cache
.insert(root_ino_offset
, root_goodbye_table
);
254 let session_ctx
= Box
::new(Mutex
::new(ctx
));
255 let arg_ptrs
: Vec
<_
> = args
.iter().map(|opt
| opt
.as_ptr()).collect();
256 let fuse_args
= FuseArgs
{
257 argc
: arg_ptrs
.len() as i32,
258 argv
: arg_ptrs
.as_ptr(),
261 let session_ptr
= unsafe {
265 std
::mem
::size_of
::<Operations
>(),
266 // Ownership of session_ctx is passed to the session here.
267 // It has to be reclaimed before dropping the session to free
268 // the `Context` and close the underlying file. This is done inside
269 // the destroy callback function.
270 Box
::into_raw(session_ctx
) as ConstPtr
,
274 if session_ptr
.is_null() {
275 bail
!("error while creating new fuse session");
278 if unsafe { fuse_set_signal_handlers(session_ptr) }
!= 0 {
279 bail
!("error while setting signal handlers");
282 Ok(Self { ptr: session_ptr, verbose }
)
285 fn setup_args(options
: &OsStr
, verbose
: bool
) -> Result
<Vec
<CString
>, Error
> {
286 // First argument should be the executable name
287 let mut arguments
= vec
![
288 CString
::new("pxar-mount").unwrap(),
289 CString
::new("-o").unwrap(),
290 CString
::new(options
.as_bytes())?
,
293 arguments
.push(CString
::new("--debug").unwrap());
299 fn setup_callbacks() -> Operations
{
300 // Register the callback functions for the session
301 let mut oprs
= Operations
::default();
302 oprs
.init
= Some(Self::init
);
303 oprs
.destroy
= Some(Self::destroy
);
304 oprs
.lookup
= Some(Self::lookup
);
305 oprs
.getattr
= Some(Self::getattr
);
306 oprs
.readlink
= Some(Self::readlink
);
307 oprs
.open
= Some(Self::open
);
308 oprs
.read
= Some(Self::read
);
309 oprs
.opendir
= Some(Self::opendir
);
310 oprs
.readdir
= Some(Self::readdir
);
311 oprs
.releasedir
= Some(Self::releasedir
);
312 oprs
.getxattr
= Some(Self::getxattr
);
313 oprs
.listxattr
= Some(Self::listxattr
);
317 /// Mount the filesystem on the given mountpoint.
319 /// Actually mount the filesystem for this session on the provided mountpoint
320 /// and daemonize process.
321 pub fn mount(&mut self, mountpoint
: &Path
, deamonize
: bool
) -> Result
<(), Error
> {
323 println
!("Mounting archive to {:#?}", mountpoint
);
325 let mountpoint
= mountpoint
.canonicalize()?
;
326 let path_cstr
= CString
::new(mountpoint
.as_os_str().as_bytes())
327 .map_err(|err
| format_err
!("invalid mountpoint - {}", err
))?
;
328 if unsafe { fuse_session_mount(self.ptr, path_cstr.as_ptr()) }
!= 0 {
329 bail
!("mounting on {:#?} failed", mountpoint
);
332 // Send process to background if deamonize is set
333 if deamonize
&& unsafe { fuse_daemonize(0) }
!= 0 {
334 bail
!("could not send process to background");
340 /// Execute session loop which handles requests from kernel.
342 /// The multi_threaded flag controls if the session loop runs in
343 /// single-threaded or multi-threaded mode.
344 /// Single-threaded mode is intended for debugging only.
345 pub fn run_loop(&mut self, multi_threaded
: bool
) -> Result
<(), Error
> {
347 println
!("Executing fuse session loop");
349 let result
= match multi_threaded
{
350 true => unsafe { fuse_session_loop_mt_31(self.ptr, 1) }
,
351 false => unsafe { fuse_session_loop(self.ptr) }
,
354 bail
!("fuse session loop exited with - {}", result
);
357 eprintln
!("fuse session loop received signal - {}", result
);
363 /// Creates a context providing an exclusive mutable reference to the `Context`.
365 /// Each callback function needing access to the `Context` can easily get an
366 /// exclusive handle by running the code inside this context.
367 /// Responses with error code can easily be generated by returning with the
369 /// The error code will be used to reply to libfuse.
370 fn run_in_context
<F
>(req
: Request
, inode
: u64, code
: F
)
372 F
: FnOnce(&mut Context
) -> Result
<(), i32>,
374 let boxed_ctx
= unsafe {
375 let ptr
= fuse_req_userdata(req
) as *mut Mutex
<Context
>;
378 let result
= boxed_ctx
381 ctx
.ino_offset
= match inode
{
382 FUSE_ROOT_ID
=> ctx
.decoder
.root_end_offset() - GOODBYE_ITEM_SIZE
,
387 .unwrap_or(Err(libc
::EIO
));
389 if let Err(err
) = result
{
391 let _res
= fuse_reply_err(req
, err
);
395 // Release ownership of boxed context, do not drop it.
396 let _
= Box
::into_raw(boxed_ctx
);
399 /// Callback functions for fuse kernel driver.
400 extern "C" fn init(_decoder
: MutPtr
) {
401 // Notting to do here for now
404 /// Cleanup the userdata created while creating the session, which is the `Context`
405 extern "C" fn destroy(ctx
: MutPtr
) {
406 // Get ownership of the `Context` and drop it when Box goes out of scope.
407 unsafe { Box::from_raw(ctx) }
;
410 /// Lookup `name` in the directory referenced by `parent` i-node.
412 /// Inserts also the child and parent file offset in the hashmap to quickly
413 /// obtain the parent offset based on the child offset.
414 /// Caches goodbye table of parent and attributes of child, if found.
415 extern "C" fn lookup(req
: Request
, parent
: u64, name
: StrPtr
) {
416 let filename
= unsafe { CStr::from_ptr(name) }
;
417 let hash
= super::format_definition
::compute_goodbye_hash(filename
.to_bytes());
419 Self::run_in_context(req
, parent
, |mut ctx
| {
420 // find_ goodbye_entry() will also update the goodbye cache
421 let (child_offset
, entry
, attr
, payload_size
) =
422 ctx
.find_goodbye_entry(&filename
, hash
)?
;
423 ctx
.attr_cache
= Some((child_offset
, attr
));
424 let child_inode
= calculate_inode(child_offset
, ctx
.decoder
.root_end_offset());
429 attr
: stat(child_inode
, &entry
, payload_size
)?
,
430 attr_timeout
: std
::f64::MAX
,
431 entry_timeout
: std
::f64::MAX
,
434 // Update the parent for this child entry. Used to get parent offset if
435 // only child offset is known.
438 .map_err(|_
| libc
::EIO
)?
439 .insert(child_offset
, ctx
.ino_offset
);
440 let _res
= unsafe { fuse_reply_entry(req, Some(&e)) }
;
446 extern "C" fn getattr(req
: Request
, inode
: u64, _fileinfo
: MutPtr
) {
447 Self::run_in_context(req
, inode
, |ctx
| {
448 let (_
, entry
, attr
, payload_size
) = ctx
450 .attributes(ctx
.ino_offset
)
451 .map_err(|_
| libc
::EIO
)?
;
452 ctx
.attr_cache
= Some((ctx
.ino_offset
, attr
));
453 let attr
= stat(inode
, &entry
, payload_size
)?
;
455 // Since fs is read-only, the timeout can be max.
456 let timeout
= std
::f64::MAX
;
457 fuse_reply_attr(req
, Some(&attr
), timeout
)
464 extern "C" fn readlink(req
: Request
, inode
: u64) {
465 Self::run_in_context(req
, inode
, |ctx
| {
466 let (target
, _
) = ctx
468 .read_link(ctx
.ino_offset
)
469 .map_err(|_
| libc
::EIO
)?
;
470 let link
= CString
::new(target
.into_os_string().into_vec()).map_err(|_
| libc
::EIO
)?
;
471 let _ret
= unsafe { fuse_reply_readlink(req, link.as_ptr()) }
;
477 extern "C" fn open(req
: Request
, inode
: u64, fileinfo
: MutPtr
) {
478 Self::run_in_context(req
, inode
, |ctx
| {
479 ctx
.decoder
.open(ctx
.ino_offset
).map_err(|_
| libc
::ENOENT
)?
;
480 let _ret
= unsafe { fuse_reply_open(req, fileinfo) }
;
486 extern "C" fn read(req
: Request
, inode
: u64, size
: size_t
, offset
: c_int
, _fileinfo
: MutPtr
) {
487 Self::run_in_context(req
, inode
, |ctx
| {
490 .read(ctx
.ino_offset
, size
, offset
as u64)
491 .map_err(|_
| libc
::EIO
)?
;
494 let len
= data
.len();
495 let dptr
= data
.as_mut_ptr() as *mut c_char
;
496 fuse_reply_buf(req
, dptr
, len
)
503 /// Open the directory referenced by the given inode for reading.
505 /// This simply checks if the inode references a valid directory, no internal
506 /// state identifies the directory as opened.
507 extern "C" fn opendir(req
: Request
, inode
: u64, fileinfo
: MutPtr
) {
508 Self::run_in_context(req
, inode
, |ctx
| {
509 let gbt
= ctx
.decoder
510 .goodbye_table(None
, ctx
.ino_offset
+ GOODBYE_ITEM_SIZE
)
511 .map_err(|_
| libc
::EIO
)?
;
512 ctx
.goodbye_cache
.insert(ctx
.ino_offset
, gbt
);
514 let _ret
= unsafe { fuse_reply_open(req, fileinfo as MutPtr) }
;
520 /// Read and return the entries of the directory referenced by i-node.
522 /// Replies to the request with the entries fitting into a buffer of length
523 /// `size`, as requested by the caller.
524 /// `offset` identifies the start index of entries to return. This is used on
525 /// repeated calls, occurring if not all entries fitted into the buffer.
526 /// The goodbye table of the directory is cached in order to speedup repeated
527 /// calls occurring when not all entries fitted in the reply buffer.
528 extern "C" fn readdir(req
: Request
, inode
: u64, size
: size_t
, offset
: c_int
, _fileinfo
: MutPtr
) {
529 let offset
= offset
as usize;
531 Self::run_in_context(req
, inode
, |ctx
| {
532 let gb_table
= ctx
.goodbye_cache
.get(&ctx
.ino_offset
)
533 .ok_or_else(|| libc
::EIO
)?
;
534 let n_entries
= gb_table
.len();
535 let mut buf
= ReplyBuf
::new(req
, size
, offset
);
537 if offset
< n_entries
{
538 for e
in gb_table
[offset
..gb_table
.len()].iter() {
539 let (filename
, entry
, _
, payload_size
) =
540 ctx
.decoder
.attributes(e
.1).map_err(|_
| libc
::EIO
)?
;
541 let name
= CString
::new(filename
.as_bytes()).map_err(|_
| libc
::EIO
)?
;
542 let item_offset
= find_offset(&entry
, e
.1, e
.2);
543 let item_inode
= calculate_inode(item_offset
, ctx
.decoder
.root_end_offset());
544 let attr
= stat(item_inode
, &entry
, payload_size
).map_err(|_
| libc
::EIO
)?
;
545 match buf
.add_entry(&name
, &attr
) {
546 Ok(ReplyBufState
::Okay
) => {}
547 Ok(ReplyBufState
::Overfull
) => return buf
.reply_filled(),
548 Err(_
) => return Err(libc
::EIO
),
553 // Add current directory entry "."
554 if offset
<= n_entries
{
555 let (_
, entry
, _
, payload_size
) = ctx
557 .attributes(ctx
.ino_offset
)
558 .map_err(|_
| libc
::EIO
)?
;
559 // No need to calculate i-node for current dir, since it is given as parameter
560 let attr
= stat(inode
, &entry
, payload_size
).map_err(|_
| libc
::EIO
)?
;
561 let name
= CString
::new(".").unwrap();
562 match buf
.add_entry(&name
, &attr
) {
563 Ok(ReplyBufState
::Okay
) => {}
564 Ok(ReplyBufState
::Overfull
) => return buf
.reply_filled(),
565 Err(_
) => return Err(libc
::EIO
),
569 // Add parent directory entry ".."
570 if offset
<= n_entries
+ 1 {
571 let parent_off
= if inode
== FUSE_ROOT_ID
{
572 ctx
.decoder
.root_end_offset() - GOODBYE_ITEM_SIZE
574 let guard
= CHILD_PARENT
.lock().map_err(|_
| libc
::EIO
)?
;
575 *guard
.get(&ctx
.ino_offset
).ok_or_else(|| libc
::EIO
)?
577 let (_
, entry
, _
, payload_size
) =
578 ctx
.decoder
.attributes(parent_off
).map_err(|_
| libc
::EIO
)?
;
579 let item_inode
= calculate_inode(parent_off
, ctx
.decoder
.root_end_offset());
580 let attr
= stat(item_inode
, &entry
, payload_size
).map_err(|_
| libc
::EIO
)?
;
581 let name
= CString
::new("..").unwrap();
582 match buf
.add_entry(&name
, &attr
) {
583 Ok(ReplyBufState
::Okay
) => {}
584 Ok(ReplyBufState
::Overfull
) => return buf
.reply_filled(),
585 Err(_
) => return Err(libc
::EIO
),
593 extern "C" fn releasedir(req
: Request
, inode
: u64, _fileinfo
: MutPtr
) {
594 Self::run_in_context(req
, inode
, |ctx
| {
595 let _gbt
= ctx
.goodbye_cache
.remove(&ctx
.ino_offset
);
600 /// Get the value of the extended attribute of `inode` identified by `name`.
601 extern "C" fn getxattr(req
: Request
, inode
: u64, name
: StrPtr
, size
: size_t
) {
602 let name
= unsafe { CStr::from_ptr(name) }
;
604 Self::run_in_context(req
, inode
, |ctx
| {
605 let (_
, _
, xattrs
, _
) = ctx
607 .attributes(ctx
.ino_offset
)
608 .map_err(|_
| libc
::EIO
)?
;
609 // security.capability is stored separately, check it first
610 if name
.to_bytes() == b
"security.capability" {
612 None
=> return Err(libc
::ENODATA
),
613 Some(mut fcaps
) => return Self::xattr_reply_value(req
, &mut fcaps
.data
, size
),
617 for mut xattr
in xattrs
.xattrs
{
618 if name
.to_bytes() == xattr
.name
.as_slice() {
619 return Self::xattr_reply_value(req
, &mut xattr
.value
, size
);
627 /// Get a list of the extended attribute of `inode`.
628 extern "C" fn listxattr(req
: Request
, inode
: u64, size
: size_t
) {
629 Self::run_in_context(req
, inode
, |ctx
| {
630 let (_
, _
, xattrs
, _
) = ctx
632 .attributes(ctx
.ino_offset
)
633 .map_err(|_
| libc
::EIO
)?
;
634 let mut buffer
= Vec
::new();
635 if xattrs
.fcaps
.is_some() {
636 buffer
.extend_from_slice(b
"security.capability\0");
638 for mut xattr
in xattrs
.xattrs
{
639 buffer
.append(&mut xattr
.name
);
642 Self::xattr_reply_value(req
, &mut buffer
, size
)
646 /// Helper function used to respond to get- and listxattr calls in order to
647 /// de-duplicate code.
648 fn xattr_reply_value(req
: Request
, value
: &mut Vec
<u8>, size
: size_t
) -> Result
<(), i32> {
649 let len
= value
.len();
652 // reply the needed buffer size to fit value
653 let _res
= unsafe { fuse_reply_xattr(req, len) }
;
654 } else if size
< len
{
655 // value does not fit into requested buffer size
656 return Err(libc
::ERANGE
);
658 // value fits into requested buffer size, send value
660 let vptr
= value
.as_mut_ptr() as *mut c_char
;
661 fuse_reply_buf(req
, vptr
, len
)
669 impl Drop
for Session
{
672 fuse_session_unmount(self.ptr
);
673 fuse_remove_signal_handlers(self.ptr
);
674 fuse_session_destroy(self.ptr
);
679 /// Return the correct offset for the item based on its `PxarEntry` mode
681 /// For directories, the offset for the corresponding `GOODBYE_TAIL_MARKER`
683 /// If it is not a directory, the start offset is returned.
684 fn find_offset(entry
: &PxarEntry
, start
: u64, end
: u64) -> u64 {
685 if (entry
.mode
as u32 & libc
::S_IFMT
) == libc
::S_IFDIR
{
686 end
- GOODBYE_ITEM_SIZE
692 /// Calculate the i-node based on the given `offset`
694 /// This maps the `offset` to the correct i-node, which is simply the offset.
695 /// The root directory is an exception, as it has per definition `FUSE_ROOT_ID`.
696 /// `root_end` is the end offset of the root directory (archive end).
697 fn calculate_inode(offset
: u64, root_end
: u64) -> u64 {
698 // check for root offset which has to be mapped to `FUSE_ROOT_ID`
699 if offset
== root_end
- GOODBYE_ITEM_SIZE
{
706 /// FUSE entry for fuse_reply_entry in lookup callback
716 /// Create a `libc::stat` with the provided i-node, entry and payload size
717 fn stat(inode
: u64, entry
: &PxarEntry
, payload_size
: u64) -> Result
<libc
::stat
, i32> {
718 let nlink
= match (entry
.mode
as u32) & libc
::S_IFMT
{
722 let time
= i64::try_from(entry
.mtime
).map_err(|_
| libc
::EIO
)?
;
723 let sec
= time
/ 1_000_000_000;
724 let nsec
= time
% 1_000_000_000;
726 let mut attr
: libc
::stat
= unsafe { std::mem::zeroed() }
;
728 attr
.st_nlink
= nlink
;
729 attr
.st_mode
= u32::try_from(entry
.mode
).map_err(|_
| libc
::EIO
)?
;
730 attr
.st_size
= i64::try_from(payload_size
).map_err(|_
| libc
::EIO
)?
;
731 attr
.st_uid
= entry
.uid
;
732 attr
.st_gid
= entry
.gid
;
734 attr
.st_atime_nsec
= nsec
;
736 attr
.st_mtime_nsec
= nsec
;
738 attr
.st_ctime_nsec
= nsec
;
743 /// State of ReplyBuf after last add_entry call
745 /// Entry was successfully added to ReplyBuf
747 /// Entry did not fit into ReplyBuf, was not added
751 /// Used to correctly fill and reply the buffer for the readdir callback
753 /// internal buffer holding the binary data
755 /// offset up to which the buffer is filled already
757 /// fuse request the buffer is used to reply to
759 /// index of the next item, telling from were to start on the next readdir callback in
760 /// case not everything fitted in the buffer on the first reply.
765 /// Create a new empty `ReplyBuf` of `size` with element counting index at `next`.
766 fn new(req
: Request
, size
: usize, next
: usize) -> Self {
768 buffer
: vec
![0; size
],
775 /// Reply to the `Request` with the filled buffer
776 fn reply_filled(&mut self) -> Result
<(), i32> {
778 let ptr
= self.buffer
.as_mut_ptr() as *mut c_char
;
779 fuse_reply_buf(self.req
, ptr
, self.filled
)
785 /// Fill the buffer for the fuse reply with the next entry
786 fn add_entry(&mut self, name
: &CString
, attr
: &libc
::stat
) -> Result
<ReplyBufState
, Error
> {
788 let size
= self.buffer
.len();
790 let bptr
= self.buffer
.as_mut_ptr() as *mut c_char
;
791 let nptr
= name
.as_ptr();
794 bptr
.offset(self.filled
as isize),
798 i32::try_from(self.next
)?
,
801 self.filled
+= bytes
;
802 // Never exceed the max size requested in the callback (=buffer.len())
803 if self.filled
> size
{
804 // Entry did not fit, so go back to previous state
805 self.filled
-= bytes
;
807 return Ok(ReplyBufState
::Overfull
);
810 Ok(ReplyBufState
::Okay
)