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
::os
::unix
::ffi
::OsStrExt
;
10 use std
::io
::{BufReader, Read, Seek}
;
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::decoder
::Decoder
;
20 use super::format_definition
::{PxarAttributes, PxarGoodbyeItem}
;
22 /// Node ID of the root i-node
24 /// Offsets in the archive are used as i-node for the fuse implementation, as
25 /// they are unique and enough to reference each item in the pxar archive.
26 /// The only exception to this is the `FUSE_ROOT_ID`, which is defined as 1 by
28 /// This is okay since offset 1 is part of the root directory entry header and
29 /// will therefore not occur again, but remapping to the correct offset of 0 is
31 const FUSE_ROOT_ID
: u64 = 1;
33 const GOODBYE_ITEM_SIZE
: u64 = std
::mem
::size_of
::<PxarGoodbyeItem
>() as u64;
35 /// HashMap holding the mapping from the child offsets to their parent
38 /// In order to include the parent directory entry '..' in the response for
39 /// readdir callback, this mapping is needed.
40 /// Calling the lookup callback will insert the offsets into the HashMap.
41 static ref CHILD_PARENT
: Mutex
<HashMap
<u64, u64>> = Mutex
::new(HashMap
::new());
44 /// Callback function for `super::decoder::Decoder`.
46 /// At the moment, this is only needed to satisfy the `SequentialDecoder`.
47 fn decoder_callback(_path
: &Path
) -> Result
<(), Error
> {
51 /// FFI types for easier readability
52 type Request
= *mut c_void
;
53 type MutPtr
= *mut c_void
;
54 type ConstPtr
= *const c_void
;
55 type StrPtr
= *const c_char
;
56 type MutStrPtr
= *mut c_char
;
59 #[link(name = "fuse3")]
61 fn fuse_session_new(args
: Option
<&FuseArgs
>, oprs
: Option
<&Operations
>, size
: size_t
, op
: ConstPtr
) -> MutPtr
;
62 fn fuse_set_signal_handlers(session
: ConstPtr
) -> c_int
;
63 fn fuse_remove_signal_handlers(session
: ConstPtr
);
64 fn fuse_daemonize(foreground
: c_int
) -> c_int
;
65 fn fuse_session_mount(session
: ConstPtr
, mountpoint
: StrPtr
) -> c_int
;
66 fn fuse_session_unmount(session
: ConstPtr
);
67 fn fuse_session_loop(session
: ConstPtr
) -> c_int
;
68 fn fuse_session_loop_mt_31(session
: ConstPtr
, clone_fd
: c_int
) -> c_int
;
69 fn fuse_session_destroy(session
: ConstPtr
);
70 fn fuse_reply_attr(req
: Request
, attr
: Option
<&libc
::stat
>, timeout
: f64) -> c_int
;
71 fn fuse_reply_err(req
: Request
, errno
: c_int
) -> c_int
;
72 fn fuse_reply_open(req
: Request
, fileinfo
: ConstPtr
) -> c_int
;
73 fn fuse_reply_buf(req
: Request
, buf
: MutStrPtr
, size
: size_t
) -> c_int
;
74 fn fuse_reply_entry(req
: Request
, entry
: Option
<&EntryParam
>) -> c_int
;
75 fn fuse_req_userdata(req
: Request
) -> MutPtr
;
78 /// Command line arguments passed to fuse.
87 /// `Session` stores a pointer to the session context and is used to mount the
88 /// archive to the given mountpoint.
94 /// `Operations` defines the callback function table of supported operations.
99 // The order in which the functions are listed matters, as the offset in the
100 // struct defines what function the fuse driver uses.
101 // It should therefore not be altered!
102 init
: Option
<extern fn(userdata
: MutPtr
)>,
103 destroy
: Option
<extern fn(userdata
: MutPtr
)>,
104 lookup
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
)>,
105 forget
: Option
<extern fn(req
: Request
, inode
: u64, nlookup
: u64)>,
106 getattr
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
)>,
107 setattr
: Option
<extern fn(req
: Request
, inode
: u64, attr
: MutPtr
, to_set
: c_int
, fileinfo
: MutPtr
)>,
108 readlink
: Option
<extern fn(req
: Request
, inode
: u64)>,
109 mknod
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
, mode
: c_int
, rdev
: c_int
)>,
110 mkdir
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
, mode
: c_int
)>,
111 unlink
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
)>,
112 rmdir
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
)>,
113 symlink
: Option
<extern fn(req
: Request
, link
: StrPtr
, parent
: u64, name
: StrPtr
)>,
114 rename
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
, newparent
: u64, newname
: StrPtr
, flags
: c_int
)>,
115 link
: Option
<extern fn(req
: Request
, inode
: u64, newparent
: u64, newname
: StrPtr
)>,
116 open
: Option
<extern fn(req
: Request
, indoe
: u64, fileinfo
: MutPtr
)>,
117 read
: Option
<extern fn(req
: Request
, inode
: u64, size
: size_t
, offset
: c_int
, fileinfo
: MutPtr
)>,
118 write
: Option
<extern fn(req
: Request
, inode
: u64, buffer
: StrPtr
, size
: size_t
, offset
: c_void
, fileinfo
: MutPtr
)>,
119 flush
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
)>,
120 release
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
)>,
121 fsync
: Option
<extern fn(req
: Request
, inode
: u64, datasync
: c_int
, fileinfo
: MutPtr
)>,
122 opendir
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
)>,
123 readdir
: Option
<extern fn(req
: Request
, inode
: u64, size
: size_t
, offset
: c_int
, fileinfo
: MutPtr
)>,
124 releasedir
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
)>,
125 fsyncdir
: Option
<extern fn(req
: Request
, inode
: u64, datasync
: c_int
, fileinfo
: MutPtr
)>,
126 statfs
: Option
<extern fn(req
: Request
, inode
: u64)>,
127 setxattr
: Option
<extern fn(req
: Request
, inode
: u64, name
: StrPtr
, value
: StrPtr
, size
: size_t
, flags
: c_int
)>,
128 getxattr
: Option
<extern fn(req
: Request
, inode
: u64, name
: StrPtr
, size
: size_t
)>,
129 listxattr
: Option
<extern fn(req
: Request
, inode
: u64, size
: size_t
)>,
130 removexattr
: Option
<extern fn(req
: Request
, inode
: u64, name
: StrPtr
)>,
131 access
: Option
<extern fn(req
: Request
, inode
: u64, mask
: i32)>,
132 create
: Option
<extern fn(req
: Request
, parent
: u64, name
: StrPtr
, mode
: c_int
, fileinfo
: MutPtr
)>,
133 getlk
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
, lock
: MutPtr
)>,
134 setlk
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
, lock
: MutPtr
, sleep
: c_int
)>,
135 bmap
: Option
<extern fn(req
: Request
, inode
: u64, blocksize
: size_t
, idx
: u64)>,
136 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
)>,
137 poll
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
, pollhandle
: MutPtr
)>,
138 write_buf
: Option
<extern fn(req
: Request
, inode
: u64, bufv
: MutPtr
, offset
: c_int
, fileinfo
: MutPtr
)>,
139 retrieve_reply
: Option
<extern fn(req
: Request
, cookie
: ConstPtr
, inode
: u64, offset
: c_int
, bufv
: MutPtr
)>,
140 forget_multi
: Option
<extern fn(req
: Request
, count
: size_t
, forgets
: MutPtr
)>,
141 flock
: Option
<extern fn(req
: Request
, inode
: u64, fileinfo
: MutPtr
, op
: c_int
)>,
142 fallocate
: Option
<extern fn(req
: Request
, inode
: u64, mode
: c_int
, offset
: c_int
, length
: c_int
, fileinfo
: MutPtr
)>,
143 readdirplus
: Option
<extern fn(req
: Request
, inode
: u64, size
: size_t
, offset
: c_int
, fileinfo
: MutPtr
)>,
144 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
)>,
148 /// Create a new low level fuse session.
150 /// `Session` is created using the provided mount options and sets the
151 /// default signal handlers.
152 /// Options have to be provided as comma separated OsStr, e.g.
153 /// ("ro,default_permissions").
154 pub fn new(archive_path
: &Path
, options
: &OsStr
, verbose
: bool
) -> Result
<Self, Error
> {
155 let file
= File
::open(archive_path
)?
;
156 // First argument should be the executable name
157 let mut arguments
= vec
![
158 CString
::new("pxar-mount").unwrap(),
159 CString
::new("-o").unwrap(),
160 CString
::new(options
.as_bytes())?
,
163 arguments
.push(CString
::new("--debug").unwrap());
166 let arg_ptrs
: Vec
<_
> = arguments
.iter().map(|opt
| opt
.as_ptr()).collect();
167 let args
= FuseArgs
{
168 argc
: arg_ptrs
.len() as i32,
169 argv
: arg_ptrs
.as_ptr(),
173 // Register the callback funcitons for the session
174 let mut oprs
= Operations
::default();
175 oprs
.init
= Some(init
);
176 oprs
.destroy
= Some(destroy
);
177 oprs
.lookup
= Some(lookup
);
178 oprs
.getattr
= Some(getattr
);
179 oprs
.open
= Some(open
);
180 oprs
.read
= Some(read
);
181 oprs
.opendir
= Some(opendir
);
182 oprs
.readdir
= Some(readdir
);
184 // By storing the decoder as userdata of the session, each request may
186 let reader
= BufReader
::new(file
);
187 let decoder
= Decoder
::new(reader
, decoder_callback
as fn(&Path
) -> Result
<(), Error
>)?
;
188 let session_decoder
= Box
::new(Mutex
::new(decoder
));
189 let session_ptr
= unsafe {
193 std
::mem
::size_of
::<Operations
>(),
194 // Ownership of session_decoder is passed to the session here.
195 // It has to be reclaimed before dropping the session to free
196 // the decoder and close the underlying file. This is done inside
197 // the destroy callback function.
198 Box
::into_raw(session_decoder
) as ConstPtr
,
202 if session_ptr
.is_null() {
203 bail
!("error while creating new fuse session");
206 if unsafe { fuse_set_signal_handlers(session_ptr) }
!= 0 {
207 bail
!("error while setting signal handlers");
216 /// Mount the filesystem on the given mountpoint.
218 /// Actually mount the filesystem for this session on the provided mountpoint
219 /// and daemonize process.
220 pub fn mount(&mut self, mountpoint
: &Path
) -> Result
<(), Error
> {
222 println
!("Mounting archive to {:#?}", mountpoint
);
224 let mountpoint
= mountpoint
.canonicalize()?
;
225 let path_cstr
= CString
::new(mountpoint
.as_os_str().as_bytes())
226 .map_err(|err
| format_err
!("invalid mountpoint - {}", err
))?
;
227 if unsafe { fuse_session_mount(self.ptr, path_cstr.as_ptr()) }
!= 0 {
228 bail
!("mounting on {:#?} failed", mountpoint
);
231 // Do not send process to background if verbose flag is set
232 if !self.verbose
&& unsafe { fuse_daemonize(0) }
!= 0 {
233 bail
!("could not send process to background");
239 /// Execute session loop which handles requests from kernel.
241 /// The multi_threaded flag controls if the session loop runs in
242 /// single-threaded or multi-threaded mode.
243 /// Single-threaded mode is intended for debugging only.
244 pub fn run_loop(&mut self, multi_threaded
: bool
) -> Result
<(), Error
> {
246 println
!("Executing fuse session loop");
248 let result
= match multi_threaded
{
249 true => unsafe { fuse_session_loop_mt_31(self.ptr, 1) }
,
250 false => unsafe { fuse_session_loop(self.ptr) }
,
253 bail
!("fuse session loop exited with - {}", result
);
256 eprintln
!("fuse session loop received signal - {}", result
);
263 impl Drop
for Session
{
266 fuse_session_unmount(self.ptr
);
267 fuse_remove_signal_handlers(self.ptr
);
268 fuse_session_destroy(self.ptr
);
273 /// Creates a context providing an exclusive mutable reference to the decoder.
275 /// Each callback function needing access to the decoder can easily get an
276 /// exclusive handle by running the code inside this context.
277 /// Responses with error code can easily be generated by returning with the
279 /// The error code will be used to reply to libfuse.
280 fn run_in_context
<F
>(req
: Request
, inode
: u64, code
: F
)
283 &mut Decoder
<BufReader
<File
>, fn(&Path
) -> Result
<(), Error
>>,
285 ) -> Result
<(), i32>,
288 fuse_req_userdata(req
)
289 as *mut Mutex
<Decoder
<BufReader
<File
>, fn(&Path
) -> Result
<(), Error
>>>
291 let boxed_decoder
= unsafe { Box::from_raw(ptr) }
;
292 let result
= boxed_decoder
295 let ino_offset
= match inode
{
296 FUSE_ROOT_ID
=> decoder
.root_end_offset() - GOODBYE_ITEM_SIZE
,
299 code(&mut decoder
, ino_offset
)
301 .unwrap_or(Err(libc
::EIO
));
303 if let Err(err
) = result
{
305 let _res
= fuse_reply_err(req
, err
);
309 // Release ownership of boxed decoder, do not drop it.
310 let _
= Box
::into_raw(boxed_decoder
);
313 /// Callback functions for fuse kernel driver.
314 extern "C" fn init(_decoder
: MutPtr
) {
315 // Notting to do here for now
318 /// Cleanup the userdata created while creating the session, which is the decoder
319 extern "C" fn destroy(decoder
: MutPtr
) {
320 // Get ownership of the decoder and drop it when Box goes out of scope.
322 Box
::from_raw(decoder
);
326 /// FUSE entry for fuse_reply_entry in lookup callback
336 /// Lookup `name` in the directory referenced by `parent` inode.
338 /// Inserts also the child and parent file offset in the hashmap to quickly
339 /// obtain the parent offset based on the child offset.
340 extern "C" fn lookup(req
: Request
, parent
: u64, name
: StrPtr
) {
341 let filename
= unsafe { CStr::from_ptr(name) }
;
342 let hash
= super::format_definition
::compute_goodbye_hash(filename
.to_bytes());
344 run_in_context(req
, parent
, |mut decoder
, ino_offset
| {
345 let goodbye_table
= decoder
.goodbye_table(None
, ino_offset
+ GOODBYE_ITEM_SIZE
).map_err(|_
| libc
::EIO
)?
;
347 let (_item
, start
, end
) = goodbye_table
349 .find(|(e
, _
, _
)| e
.hash
== hash
)
350 .ok_or(libc
::ENOENT
)?
;
352 let (mut attr
, _
) = stat(&mut decoder
, *start
)?
;
353 let offset
= if attr
.st_mode
& libc
::S_IFMT
== libc
::S_IFDIR
{
354 *end
- GOODBYE_ITEM_SIZE
358 let inode
= if offset
== decoder
.root_end_offset() - GOODBYE_ITEM_SIZE
{
369 attr_timeout
: std
::f64::MAX
,
370 entry_timeout
: std
::f64::MAX
,
373 // Update the parent for this child entry. Used to get parent offset if
374 // only child offset is known.
377 .map_err(|_
| libc
::EIO
)?
378 .insert(offset
, ino_offset
);
379 let _res
= unsafe { fuse_reply_entry(req, Some(&e)) }
;
385 /// Get attr and xattr from the decoder and update stat according to the fuse
386 /// implementation before returning
387 fn stat
<R
, F
>(decoder
: &mut Decoder
<R
, F
>, offset
: u64) -> Result
<(libc
::stat
, PxarAttributes
), i32>
390 F
: Fn(&Path
) -> Result
<(), Error
>,
392 let (entry
, xattr
, payload_size
) = decoder
.attributes(offset
).map_err(|_
| libc
::EIO
)?
;
393 let inode
= if offset
== decoder
.root_end_offset() - GOODBYE_ITEM_SIZE
{
398 let nlink
= match (entry
.mode
as u32) & libc
::S_IFMT
{
402 let time
= i64::try_from(entry
.mtime
).map_err(|_
| libc
::EIO
)?
/ 1_000_000_000;
404 let mut attr
: libc
::stat
= unsafe { std::mem::zeroed() }
;
406 attr
.st_nlink
= nlink
;
407 attr
.st_mode
= u32::try_from(entry
.mode
).map_err(|_
| libc
::EIO
)?
;
408 attr
.st_size
= i64::try_from(payload_size
).map_err(|_
| libc
::EIO
)?
;
409 attr
.st_uid
= entry
.uid
;
410 attr
.st_gid
= entry
.gid
;
411 attr
.st_atime
= time
;
412 attr
.st_mtime
= time
;
413 attr
.st_ctime
= time
;
418 extern "C" fn getattr(req
: Request
, inode
: u64, _fileinfo
: MutPtr
) {
419 run_in_context(req
, inode
, |mut decoder
, ino_offset
| {
420 let (attr
, _
) = stat(&mut decoder
, ino_offset
)?
;
422 // Since fs is read-only, the timeout can be max.
423 let timeout
= std
::f64::MAX
;
424 fuse_reply_attr(req
, Some(&attr
), timeout
)
431 extern "C" fn open(req
: Request
, inode
: u64, fileinfo
: MutPtr
) {
432 run_in_context(req
, inode
, |decoder
, ino_offset
| {
433 decoder
.open(ino_offset
).map_err(|_
| libc
::ENOENT
)?
;
434 let _ret
= unsafe { fuse_reply_open(req, fileinfo) }
;
440 extern "C" fn read(req
: Request
, inode
: u64, size
: size_t
, offset
: c_int
, _fileinfo
: MutPtr
) {
441 run_in_context(req
, inode
, |decoder
, ino_offset
| {
442 let mut data
= decoder
443 .read(ino_offset
, size
, offset
as u64)
444 .map_err(|_
| libc
::EIO
)?
;
447 let len
= data
.len();
448 let dptr
= data
.as_mut_ptr() as *mut c_char
;
449 fuse_reply_buf(req
, dptr
, len
)
456 /// Open the directory referenced by the given inode for reading.
458 /// This simply checks if the inode references a valid directory, no internal
459 /// state identifies the directory as opened.
460 extern "C" fn opendir(req
: Request
, inode
: u64, fileinfo
: MutPtr
) {
461 run_in_context(req
, inode
, |mut decoder
, ino_offset
| {
462 let (attr
, _
) = stat(&mut decoder
, ino_offset
).map_err(|_
| libc
::ENOENT
)?
;
463 if attr
.st_mode
& libc
::S_IFMT
!= libc
::S_IFDIR
{
464 return Err(libc
::ENOENT
);
466 let _ret
= unsafe { fuse_reply_open(req, fileinfo) }
;
472 extern "C" fn readdir(req
: Request
, inode
: u64, _size
: size_t
, _offset
: c_int
, _fileinfo
: MutPtr
) {
473 run_in_context(req
, inode
, |_decoder
, _ino_offset
| {