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
;
14 use anyhow
::{bail, format_err, Error}
;
16 use libc
::{c_char, c_int, c_void, size_t}
;
18 use crate::tools
::lru_cache
::{Cacher, LruCache}
;
19 use crate::tools
::acl
;
20 use super::binary_search_tree
::search_binary_tree_by
;
21 use super::decoder
::{Decoder, DirectoryEntry}
;
22 use super::format_definition
::PxarGoodbyeItem
;
24 /// Node ID of the root i-node
26 /// Offsets in the archive are used as i-node for the fuse implementation, as
27 /// they are unique and enough to reference each item in the pxar archive.
28 /// The only exception to this is the `FUSE_ROOT_ID`, which is defined as 1 by
30 /// This is okay since offset 1 is part of the root directory entry header and
31 /// will therefore not occur again, but remapping to the correct offset of 0 is
33 const FUSE_ROOT_ID
: u64 = 1;
35 /// FFI types for easier readability
36 type Request
= *mut c_void
;
37 type MutPtr
= *mut c_void
;
38 type ConstPtr
= *const c_void
;
39 type StrPtr
= *const c_char
;
40 type MutStrPtr
= *mut c_char
;
43 #[link(name = "fuse3")]
45 fn fuse_session_new(args
: Option
<&FuseArgs
>, oprs
: Option
<&Operations
>, size
: size_t
, op
: ConstPtr
) -> MutPtr
;
46 fn fuse_set_signal_handlers(session
: ConstPtr
) -> c_int
;
47 fn fuse_remove_signal_handlers(session
: ConstPtr
);
48 fn fuse_daemonize(foreground
: c_int
) -> c_int
;
49 fn fuse_session_mount(session
: ConstPtr
, mountpoint
: StrPtr
) -> c_int
;
50 fn fuse_session_unmount(session
: ConstPtr
);
51 fn fuse_session_loop(session
: ConstPtr
) -> c_int
;
52 fn fuse_session_loop_mt_31(session
: ConstPtr
, clone_fd
: c_int
) -> c_int
;
53 fn fuse_session_destroy(session
: ConstPtr
);
54 fn fuse_reply_attr(req
: Request
, attr
: Option
<&libc
::stat
>, timeout
: f64) -> c_int
;
55 fn fuse_reply_err(req
: Request
, errno
: c_int
) -> c_int
;
56 fn fuse_reply_buf(req
: Request
, buf
: MutStrPtr
, size
: size_t
) -> c_int
;
57 fn fuse_reply_entry(req
: Request
, entry
: Option
<&EntryParam
>) -> c_int
;
58 fn fuse_reply_xattr(req
: Request
, size
: size_t
) -> c_int
;
59 fn fuse_reply_readlink(req
: Request
, link
: StrPtr
) -> c_int
;
60 fn fuse_req_userdata(req
: Request
) -> MutPtr
;
61 fn fuse_add_direntry_plus(req
: Request
, buf
: MutStrPtr
, bufsize
: size_t
, name
: StrPtr
, stbuf
: Option
<&EntryParam
>, off
: c_int
) -> c_int
;
64 /// Command line arguments passed to fuse.
73 /// `Context` for callback functions providing the decoder, caches and the
74 /// offset within the archive for the i-node given by the caller.
77 /// The start of each DirectoryEntry is used as inode, used as key for this
80 /// This map stores the corresponding end offset, needed to read the
81 /// DirectoryEntry via the Decoder as well as the parent, in order
82 /// to be able to include the parent directory on readdirplus calls.
83 start_end_parent
: HashMap
<u64, (u64, u64)>,
84 gbt_cache
: LruCache
<u64, Vec
<(PxarGoodbyeItem
, u64, u64)>>,
85 entry_cache
: LruCache
<u64, DirectoryEntry
>,
88 /// Cacher for the goodbye table.
90 /// Provides the feching of the goodbye table via the decoder on cache misses.
91 struct GbtCacher
<'a
> {
92 decoder
: &'a
mut Decoder
,
93 map
: &'a HashMap
<u64, (u64, u64)>,
96 impl<'a
> Cacher
<u64, Vec
<(PxarGoodbyeItem
, u64, u64)>> for GbtCacher
<'a
> {
97 fn fetch(&mut self, key
: u64) -> Result
<Option
<Vec
<(PxarGoodbyeItem
, u64, u64)>>, Error
> {
98 let (end
, _
) = *self.map
.get(&key
).unwrap();
99 let gbt
= self.decoder
.goodbye_table(None
, end
)?
;
104 /// Cacher for the directory entries.
106 /// Provides the feching of directory entries via the decoder on cache misses.
107 struct EntryCacher
<'a
> {
108 decoder
: &'a
mut Decoder
,
109 map
: &'a HashMap
<u64, (u64, u64)>,
112 impl<'a
> Cacher
<u64, DirectoryEntry
> for EntryCacher
<'a
> {
113 fn fetch(&mut self, key
: u64) -> Result
<Option
<DirectoryEntry
>, Error
> {
114 let entry
= match key
{
115 0 => self.decoder
.root()?
,
117 let (end
, _
) = *self.map
.get(&key
).unwrap();
118 self.decoder
.read_directory_entry(key
, end
)?
126 /// Provides mutable references to the `Context` members.
127 /// This is needed to avoid borrow conflicts.
128 fn as_mut_refs(&mut self) -> (
130 &mut HashMap
<u64, (u64, u64)>,
131 &mut LruCache
<u64, Vec
<(PxarGoodbyeItem
, u64, u64)>>,
132 &mut LruCache
<u64, DirectoryEntry
>
134 ( &mut self.decoder
, &mut self.start_end_parent
, &mut self.gbt_cache
, &mut self.entry_cache
)
138 /// `Session` stores a pointer to the session context and is used to mount the
139 /// archive to the given mountpoint.
145 /// `Operations` defines the callback function table of supported operations.
150 // The order in which the functions are listed matters, as the offset in the
151 // struct defines what function the fuse driver uses.
152 // It should therefore not be altered!
153 init
: Option
<extern fn(userdata
: MutPtr
)>,
154 destroy
: Option
<extern fn(userdata
: MutPtr
)>,
155 lookup
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
)>,
156 forget
: Option
<extern fn(req
: Request
, inode
: u64, nlookup
: u64)>,
157 getattr
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
)>,
158 setattr
: Option
<extern fn(req
: Request
, inode
: u64, attr
: MutPtr
, to_set
: c_int
, fileinfo
: MutPtr
)>,
159 readlink
: Option
<extern fn(req
: Request
, inode
: u64)>,
160 mknod
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
, mode
: c_int
, rdev
: c_int
)>,
161 mkdir
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
, mode
: c_int
)>,
162 unlink
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
)>,
163 rmdir
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
)>,
164 symlink
: Option
<extern fn(req
: Request
, link
: StrPtr
, parent
: u64, name
: StrPtr
)>,
165 rename
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
, newparent
: u64, newname
: StrPtr
, flags
: c_int
)>,
166 link
: Option
<extern fn(req
: Request
, inode
: u64, newparent
: u64, newname
: StrPtr
)>,
167 open
: Option
<extern fn(req
: Request
, indoe
: u64, fileinfo
: MutPtr
)>,
168 read
: Option
<extern fn(req
: Request
, inode
: u64, size
: size_t
, offset
: c_int
, fileinfo
: MutPtr
)>,
169 write
: Option
<extern fn(req
: Request
, inode
: u64, buffer
: StrPtr
, size
: size_t
, offset
: c_void
, fileinfo
: MutPtr
)>,
170 flush
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
)>,
171 release
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
)>,
172 fsync
: Option
<extern fn(req
: Request
, inode
: u64, datasync
: c_int
, fileinfo
: MutPtr
)>,
173 opendir
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
)>,
174 readdir
: Option
<extern fn(req
: Request
, inode
: u64, size
: size_t
, offset
: c_int
, fileinfo
: MutPtr
)>,
175 releasedir
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
)>,
176 fsyncdir
: Option
<extern fn(req
: Request
, inode
: u64, datasync
: c_int
, fileinfo
: MutPtr
)>,
177 statfs
: Option
<extern fn(req
: Request
, inode
: u64)>,
178 setxattr
: Option
<extern fn(req
: Request
, inode
: u64, name
: StrPtr
, value
: StrPtr
, size
: size_t
, flags
: c_int
)>,
179 getxattr
: Option
<extern fn(req
: Request
, inode
: u64, name
: StrPtr
, size
: size_t
)>,
180 listxattr
: Option
<extern fn(req
: Request
, inode
: u64, size
: size_t
)>,
181 removexattr
: Option
<extern fn(req
: Request
, inode
: u64, name
: StrPtr
)>,
182 access
: Option
<extern fn(req
: Request
, inode
: u64, mask
: i32)>,
183 create
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
, mode
: c_int
, fileinfo
: MutPtr
)>,
184 getlk
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
, lock
: MutPtr
)>,
185 setlk
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
, lock
: MutPtr
, sleep
: c_int
)>,
186 bmap
: Option
<extern fn(req
: Request
, inode
: u64, blocksize
: size_t
, idx
: u64)>,
187 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
)>,
188 poll
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
, pollhandle
: MutPtr
)>,
189 write_buf
: Option
<extern fn(req
: Request
, inode
: u64, bufv
: MutPtr
, offset
: c_int
, fileinfo
: MutPtr
)>,
190 retrieve_reply
: Option
<extern fn(req
: Request
, cookie
: ConstPtr
, inode
: u64, offset
: c_int
, bufv
: MutPtr
)>,
191 forget_multi
: Option
<extern fn(req
: Request
, count
: size_t
, forgets
: MutPtr
)>,
192 flock
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
, op
: c_int
)>,
193 fallocate
: Option
<extern fn(req
: Request
, inode
: u64, mode
: c_int
, offset
: c_int
, length
: c_int
, fileinfo
: MutPtr
)>,
194 readdirplus
: Option
<extern fn(req
: Request
, inode
: u64, size
: size_t
, offset
: c_int
, fileinfo
: MutPtr
)>,
195 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
)>,
201 /// Create a new low level fuse session.
203 /// `Session` is created using the provided mount options and sets the
204 /// default signal handlers.
205 /// Options have to be provided as comma separated OsStr, e.g.
206 /// ("ro,default_permissions").
207 pub fn from_path(archive_path
: &Path
, options
: &OsStr
, verbose
: bool
) -> Result
<Self, Error
> {
208 let file
= File
::open(archive_path
)?
;
209 let reader
= BufReader
::new(file
);
210 let decoder
= Decoder
::new(reader
)?
;
211 Self::new(decoder
, options
, verbose
)
214 /// Create a new low level fuse session using the given `Decoder`.
216 /// `Session` is created using the provided mount options and sets the
217 /// default signal handlers.
218 /// Options have to be provided as comma separated OsStr, e.g.
219 /// ("ro,default_permissions").
220 pub fn new(decoder
: Decoder
, options
: &OsStr
, verbose
: bool
) -> Result
<Self, Error
> {
221 let args
= Self::setup_args(options
, verbose
)?
;
222 let oprs
= Self::setup_callbacks();
223 let mut map
= HashMap
::new();
224 // Insert entry for the root directory, with itself as parent.
225 map
.insert(0, (decoder
.root_end_offset(), 0));
229 start_end_parent
: map
,
230 entry_cache
: LruCache
::new(1024),
231 gbt_cache
: LruCache
::new(1024),
234 let session_ctx
= Box
::new(Mutex
::new(ctx
));
235 let arg_ptrs
: Vec
<_
> = args
.iter().map(|opt
| opt
.as_ptr()).collect();
236 let fuse_args
= FuseArgs
{
237 argc
: arg_ptrs
.len() as i32,
238 argv
: arg_ptrs
.as_ptr(),
241 let session_ptr
= unsafe {
245 std
::mem
::size_of
::<Operations
>(),
246 // Ownership of session_ctx is passed to the session here.
247 // It has to be reclaimed before dropping the session to free
248 // the `Context` and close the underlying file. This is done inside
249 // the destroy callback function.
250 Box
::into_raw(session_ctx
) as ConstPtr
,
254 if session_ptr
.is_null() {
255 bail
!("error while creating new fuse session");
258 if unsafe { fuse_set_signal_handlers(session_ptr) }
!= 0 {
259 bail
!("error while setting signal handlers");
262 Ok(Self { ptr: session_ptr, verbose }
)
265 fn setup_args(options
: &OsStr
, verbose
: bool
) -> Result
<Vec
<CString
>, Error
> {
266 // First argument should be the executable name
267 let mut arguments
= vec
![
268 CString
::new("pxar-mount").unwrap(),
269 CString
::new("-o").unwrap(),
270 CString
::new(options
.as_bytes())?
,
273 arguments
.push(CString
::new("--debug").unwrap());
279 fn setup_callbacks() -> Operations
{
280 // Register the callback functions for the session
281 let mut oprs
= Operations
::default();
282 oprs
.init
= Some(Self::init
);
283 oprs
.destroy
= Some(Self::destroy
);
284 oprs
.lookup
= Some(Self::lookup
);
285 oprs
.getattr
= Some(Self::getattr
);
286 oprs
.readlink
= Some(Self::readlink
);
287 oprs
.read
= Some(Self::read
);
288 oprs
.getxattr
= Some(Self::getxattr
);
289 oprs
.listxattr
= Some(Self::listxattr
);
290 oprs
.readdirplus
= Some(Self::readdirplus
);
294 /// Mount the filesystem on the given mountpoint.
296 /// Actually mount the filesystem for this session on the provided mountpoint
297 /// and daemonize process.
298 pub fn mount(&mut self, mountpoint
: &Path
, deamonize
: bool
) -> Result
<(), Error
> {
300 println
!("Mounting archive to {:#?}", mountpoint
);
302 let mountpoint
= mountpoint
.canonicalize()?
;
303 let path_cstr
= CString
::new(mountpoint
.as_os_str().as_bytes())
304 .map_err(|err
| format_err
!("invalid mountpoint - {}", err
))?
;
305 if unsafe { fuse_session_mount(self.ptr, path_cstr.as_ptr()) }
!= 0 {
306 bail
!("mounting on {:#?} failed", mountpoint
);
309 // Send process to background if deamonize is set
310 if deamonize
&& unsafe { fuse_daemonize(0) }
!= 0 {
311 bail
!("could not send process to background");
317 /// Execute session loop which handles requests from kernel.
319 /// The multi_threaded flag controls if the session loop runs in
320 /// single-threaded or multi-threaded mode.
321 /// Single-threaded mode is intended for debugging only.
322 pub fn run_loop(&mut self, multi_threaded
: bool
) -> Result
<(), Error
> {
324 println
!("Executing fuse session loop");
326 let result
= match multi_threaded
{
327 true => unsafe { fuse_session_loop_mt_31(self.ptr, 1) }
,
328 false => unsafe { fuse_session_loop(self.ptr) }
,
331 bail
!("fuse session loop exited with - {}", result
);
334 eprintln
!("fuse session loop received signal - {}", result
);
340 /// Creates a context providing exclusive mutable references to the members of
343 /// Same as run_in_context except it provides ref mut to the individual members
344 /// of `Context` in order to avoid borrow conflicts.
345 fn run_with_context_refs
<F
>(req
: Request
, inode
: u64, code
: F
)
349 &mut HashMap
<u64, (u64, u64)>,
350 &mut LruCache
<u64, Vec
<(PxarGoodbyeItem
, u64, u64)>>,
351 &mut LruCache
<u64, DirectoryEntry
>,
353 ) -> Result
<(), i32>,
355 let boxed_ctx
= unsafe {
356 let ptr
= fuse_req_userdata(req
) as *mut Mutex
<Context
>;
359 let result
= boxed_ctx
362 let ino_offset
= match inode
{
366 let (decoder
, map
, gbt_cache
, entry_cache
) = ctx
.as_mut_refs();
367 code(decoder
, map
, gbt_cache
, entry_cache
, ino_offset
)
369 .unwrap_or(Err(libc
::EIO
));
371 if let Err(err
) = result
{
373 let _res
= fuse_reply_err(req
, err
);
377 // Release ownership of boxed context, do not drop it.
378 let _
= Box
::into_raw(boxed_ctx
);
381 /// Callback functions for fuse kernel driver.
382 extern "C" fn init(_decoder
: MutPtr
) {
383 // Notting to do here for now
386 /// Cleanup the userdata created while creating the session, which is the `Context`
387 extern "C" fn destroy(ctx
: MutPtr
) {
388 // Get ownership of the `Context` and drop it when Box goes out of scope.
389 unsafe { Box::from_raw(ctx) }
;
392 /// Lookup `name` in the directory referenced by `parent` i-node.
394 /// Inserts also the child and parent file offset in the hashmap to quickly
395 /// obtain the parent offset based on the child offset.
396 /// Caches goodbye table of parent and attributes of child, if found.
397 extern "C" fn lookup(req
: Request
, parent
: u64, name
: StrPtr
) {
398 let filename
= unsafe { CStr::from_ptr(name) }
;
399 let hash
= super::format_definition
::compute_goodbye_hash(filename
.to_bytes());
401 Self::run_with_context_refs(req
, parent
, |decoder
, map
, gbt_cache
, entry_cache
, ino_offset
| {
402 let gbt
= gbt_cache
.access(ino_offset
, &mut GbtCacher { decoder, map }
)
403 .map_err(|_
| libc
::EIO
)?
404 .ok_or_else(|| libc
::EIO
)?
;
405 let mut start_idx
= 0;
406 let mut skip_multiple
= 0;
408 // Search for the next goodbye entry with matching hash.
409 let idx
= search_binary_tree_by(
413 |idx
| hash
.cmp(&gbt
[idx
].0.hash
),
414 ).ok_or_else(|| libc
::ENOENT
)?
;
416 let (_item
, start
, end
) = &gbt
[idx
];
417 map
.insert(*start
, (*end
, ino_offset
));
419 let entry
= entry_cache
.access(*start
, &mut EntryCacher { decoder, map }
)
420 .map_err(|_
| libc
::EIO
)?
421 .ok_or_else(|| libc
::ENOENT
)?
;
423 // Possible hash collision, need to check if the found entry is indeed
424 // the filename to lookup.
425 if entry
.filename
.as_bytes() == filename
.to_bytes() {
429 attr
: stat(*start
, &entry
)?
,
430 attr_timeout
: std
::f64::MAX
,
431 entry_timeout
: std
::f64::MAX
,
434 let _res
= unsafe { fuse_reply_entry(req, Some(&e)) }
;
437 // Hash collision, check the next entry in the goodbye table by starting
438 // from given index but skipping one more match (so hash at index itself).
445 extern "C" fn getattr(req
: Request
, inode
: u64, _fileinfo
: MutPtr
) {
446 Self::run_with_context_refs(req
, inode
, |decoder
, map
, _
, entry_cache
, ino_offset
| {
447 let entry
= entry_cache
.access(ino_offset
, &mut EntryCacher { decoder, map }
)
448 .map_err(|_
| libc
::EIO
)?
449 .ok_or_else(|| libc
::EIO
)?
;
450 let attr
= stat(inode
, &entry
)?
;
452 // Since fs is read-only, the timeout can be max.
453 let timeout
= std
::f64::MAX
;
454 fuse_reply_attr(req
, Some(&attr
), timeout
)
461 extern "C" fn readlink(req
: Request
, inode
: u64) {
462 Self::run_with_context_refs(req
, inode
, |decoder
, map
, _
, entry_cache
, ino_offset
| {
463 let entry
= entry_cache
464 .access(ino_offset
, &mut EntryCacher { decoder, map }
)
465 .map_err(|_
| libc
::EIO
)?
466 .ok_or_else(|| libc
::EIO
)?
;
467 let target
= entry
.target
.as_ref().ok_or_else(|| libc
::EIO
)?
;
468 let link
= CString
::new(target
.as_os_str().as_bytes()).map_err(|_
| libc
::EIO
)?
;
469 let _ret
= unsafe { fuse_reply_readlink(req, link.as_ptr()) }
;
475 extern "C" fn read(req
: Request
, inode
: u64, size
: size_t
, offset
: c_int
, _fileinfo
: MutPtr
) {
476 Self::run_with_context_refs(req
, inode
, |decoder
, map
, _gbt_cache
, entry_cache
, ino_offset
| {
477 let entry
= entry_cache
.access(ino_offset
, &mut EntryCacher { decoder, map }
)
478 .map_err(|_
| libc
::EIO
)?
479 .ok_or_else(|| libc
::EIO
)?
;
480 let mut data
= decoder
.read(&entry
, size
, offset
as u64).map_err(|_
| libc
::EIO
)?
;
483 let len
= data
.len();
484 let dptr
= data
.as_mut_ptr() as *mut c_char
;
485 fuse_reply_buf(req
, dptr
, len
)
492 /// Read and return the entries of the directory referenced by i-node.
494 /// Replies to the request with the entries fitting into a buffer of length
495 /// `size`, as requested by the caller.
496 /// `offset` identifies the start index of entries to return. This is used on
497 /// repeated calls, occurring if not all entries fitted into the buffer.
498 extern "C" fn readdirplus(req
: Request
, inode
: u64, size
: size_t
, offset
: c_int
, _fileinfo
: MutPtr
) {
499 let offset
= offset
as usize;
501 Self::run_with_context_refs(req
, inode
, |decoder
, map
, gbt_cache
, entry_cache
, ino_offset
| {
502 let gbt
= gbt_cache
.access(ino_offset
, &mut GbtCacher { decoder, map }
)
503 .map_err(|_
| libc
::EIO
)?
504 .ok_or_else(|| libc
::ENOENT
)?
;
505 let n_entries
= gbt
.len();
506 let mut buf
= ReplyBuf
::new(req
, size
, offset
);
508 if offset
< n_entries
{
509 for e
in gbt
[offset
..gbt
.len()].iter() {
510 map
.insert(e
.1, (e
.2, ino_offset
));
511 let entry
= entry_cache
.access(e
.1, &mut EntryCacher { decoder, map }
)
512 .map_err(|_
| libc
::EIO
)?
513 .ok_or_else(|| libc
::EIO
)?
;
514 let name
= CString
::new(entry
.filename
.as_bytes())
515 .map_err(|_
| libc
::EIO
)?
;
516 let attr
= EntryParam
{
519 attr
: stat(e
.1, &entry
).map_err(|_
| libc
::EIO
)?
,
520 attr_timeout
: std
::f64::MAX
,
521 entry_timeout
: std
::f64::MAX
,
523 match buf
.fill(&name
, &attr
) {
524 Ok(ReplyBufState
::Okay
) => {}
525 Ok(ReplyBufState
::Overfull
) => return buf
.reply_filled(),
526 Err(_
) => return Err(libc
::EIO
),
531 // Add current directory entry "."
532 if offset
<= n_entries
{
533 let entry
= entry_cache
.access(ino_offset
, &mut EntryCacher { decoder, map }
)
534 .map_err(|_
| libc
::EIO
)?
535 .ok_or_else(|| libc
::EIO
)?
;
536 let name
= CString
::new(".").unwrap();
537 let attr
= EntryParam
{
540 attr
: stat(inode
, &entry
).map_err(|_
| libc
::EIO
)?
,
541 attr_timeout
: std
::f64::MAX
,
542 entry_timeout
: std
::f64::MAX
,
544 match buf
.fill(&name
, &attr
) {
545 Ok(ReplyBufState
::Okay
) => {}
546 Ok(ReplyBufState
::Overfull
) => return buf
.reply_filled(),
547 Err(_
) => return Err(libc
::EIO
),
551 // Add parent directory entry ".."
552 if offset
<= n_entries
+ 1 {
553 let (_
, parent
) = *map
.get(&ino_offset
).unwrap();
554 let entry
= entry_cache
.access(parent
, &mut EntryCacher { decoder, map }
)
555 .map_err(|_
| libc
::EIO
)?
556 .ok_or_else(|| libc
::EIO
)?
;
557 let inode
= if parent
== 0 { FUSE_ROOT_ID }
else { parent }
;
558 let name
= CString
::new("..").unwrap();
559 let attr
= EntryParam
{
562 attr
: stat(inode
, &entry
).map_err(|_
| libc
::EIO
)?
,
563 attr_timeout
: std
::f64::MAX
,
564 entry_timeout
: std
::f64::MAX
,
566 match buf
.fill(&name
, &attr
) {
567 Ok(ReplyBufState
::Okay
) => {}
568 Ok(ReplyBufState
::Overfull
) => return buf
.reply_filled(),
569 Err(_
) => return Err(libc
::EIO
),
577 /// Get the value of the extended attribute of `inode` identified by `name`.
578 extern "C" fn getxattr(req
: Request
, inode
: u64, name
: StrPtr
, size
: size_t
) {
579 let name
= unsafe { CStr::from_ptr(name) }
;
581 Self::run_with_context_refs(req
, inode
, |decoder
, map
, _
, entry_cache
, ino_offset
| {
582 let entry
= entry_cache
.access(ino_offset
, &mut EntryCacher { decoder, map }
)
583 .map_err(|_
| libc
::EIO
)?
584 .ok_or_else(|| libc
::EIO
)?
;
586 // Some of the extended attributes are stored separately in the archive,
587 // so check if requested name matches one of those.
588 match name
.to_bytes() {
589 b
"security.capability" => {
590 match &mut entry
.xattr
.fcaps
{
591 None
=> return Err(libc
::ENODATA
),
592 Some(fcaps
) => return Self::xattr_reply_value(req
, &mut fcaps
.data
, size
),
595 b
"system.posix_acl_access" => {
596 // Make sure to return if there are no matching extended attributes in the archive
597 if entry
.xattr
.acl_group_obj
.is_none()
598 && entry
.xattr
.acl_user
.is_empty()
599 && entry
.xattr
.acl_group
.is_empty() {
600 return Err(libc
::ENODATA
);
602 let mut buffer
= acl
::ACLXAttrBuffer
::new(acl
::ACL_EA_VERSION
);
604 buffer
.add_entry(acl
::ACL_USER_OBJ
, None
, acl
::mode_user_to_acl_permissions(entry
.entry
.mode
));
605 match &entry
.xattr
.acl_group_obj
{
607 buffer
.add_entry(acl
::ACL_MASK
, None
, acl
::mode_group_to_acl_permissions(entry
.entry
.mode
));
608 buffer
.add_entry(acl
::ACL_GROUP_OBJ
, None
, group_obj
.permissions
);
611 buffer
.add_entry(acl
::ACL_GROUP_OBJ
, None
, acl
::mode_group_to_acl_permissions(entry
.entry
.mode
));
614 buffer
.add_entry(acl
::ACL_OTHER
, None
, acl
::mode_other_to_acl_permissions(entry
.entry
.mode
));
616 for user
in &mut entry
.xattr
.acl_user
{
617 buffer
.add_entry(acl
::ACL_USER
, Some(user
.uid
), user
.permissions
);
619 for group
in &mut entry
.xattr
.acl_group
{
620 buffer
.add_entry(acl
::ACL_GROUP
, Some(group
.gid
), group
.permissions
);
622 return Self::xattr_reply_value(req
, buffer
.as_mut_slice(), size
);
624 b
"system.posix_acl_default" => {
625 if let Some(default) = &entry
.xattr
.acl_default
{
626 let mut buffer
= acl
::ACLXAttrBuffer
::new(acl
::ACL_EA_VERSION
);
628 buffer
.add_entry(acl
::ACL_USER_OBJ
, None
, default.user_obj_permissions
);
629 buffer
.add_entry(acl
::ACL_GROUP_OBJ
, None
, default.group_obj_permissions
);
630 buffer
.add_entry(acl
::ACL_OTHER
, None
, default.other_permissions
);
632 if default.mask_permissions
!= std
::u64::MAX
{
633 buffer
.add_entry(acl
::ACL_MASK
, None
, default.mask_permissions
);
636 for user
in &mut entry
.xattr
.acl_default_user
{
637 buffer
.add_entry(acl
::ACL_USER
, Some(user
.uid
), user
.permissions
);
639 for group
in &mut entry
.xattr
.acl_default_group
{
640 buffer
.add_entry(acl
::ACL_GROUP
, Some(group
.gid
), group
.permissions
);
642 if buffer
.len() > 0 {
643 return Self::xattr_reply_value(req
, buffer
.as_mut_slice(), size
);
648 for xattr
in &mut entry
.xattr
.xattrs
{
649 if name
== xattr
.name
.as_slice() {
650 return Self::xattr_reply_value(req
, &mut xattr
.value
, size
);
661 /// Get a list of the extended attribute of `inode`.
662 extern "C" fn listxattr(req
: Request
, inode
: u64, size
: size_t
) {
663 Self::run_with_context_refs(req
, inode
, |decoder
, map
, _
, entry_cache
, ino_offset
| {
664 let entry
= entry_cache
.access(ino_offset
, &mut EntryCacher { decoder, map }
)
665 .map_err(|_
| libc
::EIO
)?
666 .ok_or_else(|| libc
::EIO
)?
;
667 let mut buffer
= Vec
::new();
668 if entry
.xattr
.fcaps
.is_some() {
669 buffer
.extend_from_slice(b
"security.capability\0");
671 if entry
.xattr
.acl_default
.is_some() {
672 buffer
.extend_from_slice(b
"system.posix_acl_default\0");
674 if entry
.xattr
.acl_group_obj
.is_some()
675 || !entry
.xattr
.acl_user
.is_empty()
676 || !entry
.xattr
.acl_group
.is_empty() {
677 buffer
.extend_from_slice(b
"system.posix_acl_user\0");
679 for xattr
in &mut entry
.xattr
.xattrs
{
680 buffer
.append(&mut xattr
.name
);
684 Self::xattr_reply_value(req
, &mut buffer
, size
)
688 /// Helper function used to respond to get- and listxattr calls in order to
689 /// de-duplicate code.
690 fn xattr_reply_value(req
: Request
, value
: &mut [u8], size
: size_t
) -> Result
<(), i32> {
691 let len
= value
.len();
694 // reply the needed buffer size to fit value
695 let _res
= unsafe { fuse_reply_xattr(req, len) }
;
696 } else if size
< len
{
697 // value does not fit into requested buffer size
698 return Err(libc
::ERANGE
);
700 // value fits into requested buffer size, send value
702 let vptr
= value
.as_mut_ptr() as *mut c_char
;
703 fuse_reply_buf(req
, vptr
, len
)
711 impl Drop
for Session
{
714 fuse_session_unmount(self.ptr
);
715 fuse_remove_signal_handlers(self.ptr
);
716 fuse_session_destroy(self.ptr
);
721 /// FUSE entry for fuse_reply_entry in lookup callback
731 /// Create a `libc::stat` with the provided i-node and entry
732 fn stat(inode
: u64, entry
: &DirectoryEntry
) -> Result
<libc
::stat
, i32> {
733 let nlink
= match (entry
.entry
.mode
as u32) & libc
::S_IFMT
{
737 let time
= i64::try_from(entry
.entry
.mtime
).map_err(|_
| libc
::EIO
)?
;
738 let sec
= time
/ 1_000_000_000;
739 let nsec
= time
% 1_000_000_000;
741 let mut attr
: libc
::stat
= unsafe { std::mem::zeroed() }
;
743 attr
.st_nlink
= nlink
;
744 attr
.st_mode
= u32::try_from(entry
.entry
.mode
).map_err(|_
| libc
::EIO
)?
;
745 attr
.st_size
= i64::try_from(entry
.size
).map_err(|_
| libc
::EIO
)?
;
746 attr
.st_uid
= entry
.entry
.uid
;
747 attr
.st_gid
= entry
.entry
.gid
;
749 attr
.st_atime_nsec
= nsec
;
751 attr
.st_mtime_nsec
= nsec
;
753 attr
.st_ctime_nsec
= nsec
;
758 /// State of ReplyBuf after last add_entry call
760 /// Entry was successfully added to ReplyBuf
762 /// Entry did not fit into ReplyBuf, was not added
766 /// Used to correctly fill and reply the buffer for the readdirplus callback
768 /// internal buffer holding the binary data
770 /// offset up to which the buffer is filled already
772 /// fuse request the buffer is used to reply to
774 /// index of the next item, telling from were to start on the next readdirplus
775 /// callback in case not everything fitted in the buffer on the first reply.
780 /// Create a new empty `ReplyBuf` of `size` with element counting index at `next`.
781 fn new(req
: Request
, size
: usize, next
: usize) -> Self {
783 buffer
: vec
![0; size
],
790 /// Reply to the `Request` with the filled buffer
791 fn reply_filled(&mut self) -> Result
<(), i32> {
793 let ptr
= self.buffer
.as_mut_ptr() as *mut c_char
;
794 fuse_reply_buf(self.req
, ptr
, self.filled
)
800 /// Fill the buffer for the fuse reply with the next dir entry by invoking the
801 /// fuse_add_direntry_plus helper function for the readdirplus callback.
802 /// The attr type T is has to be `libc::stat` or `EntryParam` accordingly.
803 fn fill(&mut self, name
: &CString
, attr
: &EntryParam
) -> Result
<ReplyBufState
, Error
> {
805 let size
= self.buffer
.len();
807 let bptr
= self.buffer
.as_mut_ptr() as *mut c_char
;
808 let nptr
= name
.as_ptr();
809 fuse_add_direntry_plus(
811 bptr
.offset(self.filled
as isize),
815 i32::try_from(self.next
)?
,
818 self.filled
+= bytes
;
819 // Never exceed the max size requested in the callback (=buffer.len())
820 if self.filled
> size
{
821 // Entry did not fit, so go back to previous state
822 self.filled
-= bytes
;
824 return Ok(ReplyBufState
::Overfull
);
827 Ok(ReplyBufState
::Okay
)