]> git.proxmox.com Git - proxmox-backup.git/blob - src/pxar/fuse.rs
74108d97945fffbc75918d4ec441d6e9144c18d0
[proxmox-backup.git] / src / pxar / fuse.rs
1 //! Low level FUSE implementation for pxar.
2 //!
3 //! Allows to mount the archive as read-only filesystem to inspect its contents.
4
5 use std::collections::HashMap;
6 use std::convert::TryFrom;
7 use std::ffi::{CStr, CString, OsStr};
8 use std::fs::File;
9 use std::io::BufReader;
10 use std::os::unix::ffi::OsStrExt;
11 use std::path::Path;
12 use std::sync::Mutex;
13
14 use anyhow::{bail, format_err, Error};
15 use libc;
16 use libc::{c_char, c_int, c_void, size_t};
17
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;
23
24 /// Node ID of the root i-node
25 ///
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
29 /// the fuse library.
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
32 /// required.
33 const FUSE_ROOT_ID: u64 = 1;
34
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;
41
42 #[rustfmt::skip]
43 #[link(name = "fuse3")]
44 extern "C" {
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;
62 }
63
64 /// Command line arguments passed to fuse.
65 #[repr(C)]
66 #[derive(Debug)]
67 struct FuseArgs {
68 argc: c_int,
69 argv: *const StrPtr,
70 allocated: c_int,
71 }
72
73 /// `Context` for callback functions providing the decoder, caches and the
74 /// offset within the archive for the i-node given by the caller.
75 struct Context {
76 decoder: Decoder,
77 /// The start of each DirectoryEntry is used as inode, used as key for this
78 /// hashmap.
79 ///
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>,
86 }
87
88 /// Cacher for the goodbye table.
89 ///
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)>,
94 }
95
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)?;
100 Ok(Some(gbt))
101 }
102 }
103
104 /// Cacher for the directory entries.
105 ///
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)>,
110 }
111
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()?,
116 _ => {
117 let (end, _) = *self.map.get(&key).unwrap();
118 self.decoder.read_directory_entry(key, end)?
119 }
120 };
121 Ok(Some(entry))
122 }
123 }
124
125 impl Context {
126 /// Provides mutable references to the `Context` members.
127 /// This is needed to avoid borrow conflicts.
128 fn as_mut_refs(&mut self) -> (
129 &mut Decoder,
130 &mut HashMap<u64, (u64, u64)>,
131 &mut LruCache<u64, Vec<(PxarGoodbyeItem, u64, u64)>>,
132 &mut LruCache<u64, DirectoryEntry>
133 ) {
134 ( &mut self.decoder, &mut self.start_end_parent, &mut self.gbt_cache, &mut self.entry_cache )
135 }
136 }
137
138 /// `Session` stores a pointer to the session context and is used to mount the
139 /// archive to the given mountpoint.
140 pub struct Session {
141 ptr: MutPtr,
142 verbose: bool,
143 }
144
145 /// `Operations` defines the callback function table of supported operations.
146 #[repr(C)]
147 #[derive(Default)]
148 #[rustfmt::skip]
149 struct 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)>,
196 }
197
198
199 impl Session {
200
201 /// Create a new low level fuse session.
202 ///
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)
212 }
213
214 /// Create a new low level fuse session using the given `Decoder`.
215 ///
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));
226
227 let ctx = Context {
228 decoder,
229 start_end_parent: map,
230 entry_cache: LruCache::new(1024),
231 gbt_cache: LruCache::new(1024),
232 };
233
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(),
239 allocated: 0,
240 };
241 let session_ptr = unsafe {
242 fuse_session_new(
243 Some(&fuse_args),
244 Some(&oprs),
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,
251 )
252 };
253
254 if session_ptr.is_null() {
255 bail!("error while creating new fuse session");
256 }
257
258 if unsafe { fuse_set_signal_handlers(session_ptr) } != 0 {
259 bail!("error while setting signal handlers");
260 }
261
262 Ok(Self { ptr: session_ptr, verbose })
263 }
264
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())?,
271 ];
272 if verbose {
273 arguments.push(CString::new("--debug").unwrap());
274 }
275
276 Ok(arguments)
277 }
278
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);
291 oprs
292 }
293
294 /// Mount the filesystem on the given mountpoint.
295 ///
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> {
299 if self.verbose {
300 println!("Mounting archive to {:#?}", mountpoint);
301 }
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);
307 }
308
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");
312 }
313
314 Ok(())
315 }
316
317 /// Execute session loop which handles requests from kernel.
318 ///
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> {
323 if self.verbose {
324 println!("Executing fuse session loop");
325 }
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) },
329 };
330 if result < 0 {
331 bail!("fuse session loop exited with - {}", result);
332 }
333 if result > 0 {
334 eprintln!("fuse session loop received signal - {}", result);
335 }
336
337 Ok(())
338 }
339
340 /// Creates a context providing exclusive mutable references to the members of
341 /// `Context`.
342 ///
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)
346 where
347 F: FnOnce(
348 &mut Decoder,
349 &mut HashMap<u64, (u64, u64)>,
350 &mut LruCache<u64, Vec<(PxarGoodbyeItem, u64, u64)>>,
351 &mut LruCache<u64, DirectoryEntry>,
352 u64,
353 ) -> Result<(), i32>,
354 {
355 let boxed_ctx = unsafe {
356 let ptr = fuse_req_userdata(req) as *mut Mutex<Context>;
357 Box::from_raw(ptr)
358 };
359 let result = boxed_ctx
360 .lock()
361 .map(|mut ctx| {
362 let ino_offset = match inode {
363 FUSE_ROOT_ID => 0,
364 _ => inode,
365 };
366 let (decoder, map, gbt_cache, entry_cache) = ctx.as_mut_refs();
367 code(decoder, map, gbt_cache, entry_cache, ino_offset)
368 })
369 .unwrap_or(Err(libc::EIO));
370
371 if let Err(err) = result {
372 unsafe {
373 let _res = fuse_reply_err(req, err);
374 }
375 }
376
377 // Release ownership of boxed context, do not drop it.
378 let _ = Box::into_raw(boxed_ctx);
379 }
380
381 /// Callback functions for fuse kernel driver.
382 extern "C" fn init(_decoder: MutPtr) {
383 // Notting to do here for now
384 }
385
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) };
390 }
391
392 /// Lookup `name` in the directory referenced by `parent` i-node.
393 ///
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());
400
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;
407 loop {
408 // Search for the next goodbye entry with matching hash.
409 let idx = search_binary_tree_by(
410 start_idx,
411 gbt.len(),
412 skip_multiple,
413 |idx| hash.cmp(&gbt[idx].0.hash),
414 ).ok_or_else(|| libc::ENOENT)?;
415
416 let (_item, start, end) = &gbt[idx];
417 map.insert(*start, (*end, ino_offset));
418
419 let entry = entry_cache.access(*start, &mut EntryCacher { decoder, map })
420 .map_err(|_| libc::EIO)?
421 .ok_or_else(|| libc::ENOENT)?;
422
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() {
426 let e = EntryParam {
427 inode: *start,
428 generation: 1,
429 attr: stat(*start, &entry)?,
430 attr_timeout: std::f64::MAX,
431 entry_timeout: std::f64::MAX,
432 };
433
434 let _res = unsafe { fuse_reply_entry(req, Some(&e)) };
435 return Ok(())
436 }
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).
439 start_idx = idx;
440 skip_multiple = 1;
441 }
442 });
443 }
444
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)?;
451 let _res = unsafe {
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)
455 };
456
457 Ok(())
458 });
459 }
460
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()) };
470
471 Ok(())
472 });
473 }
474
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)?;
481
482 let _res = unsafe {
483 let len = data.len();
484 let dptr = data.as_mut_ptr() as *mut c_char;
485 fuse_reply_buf(req, dptr, len)
486 };
487
488 Ok(())
489 });
490 }
491
492 /// Read and return the entries of the directory referenced by i-node.
493 ///
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;
500
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);
507
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 {
517 inode: e.1,
518 generation: 1,
519 attr: stat(e.1, &entry).map_err(|_| libc::EIO)?,
520 attr_timeout: std::f64::MAX,
521 entry_timeout: std::f64::MAX,
522 };
523 match buf.fill(&name, &attr) {
524 Ok(ReplyBufState::Okay) => {}
525 Ok(ReplyBufState::Overfull) => return buf.reply_filled(),
526 Err(_) => return Err(libc::EIO),
527 }
528 }
529 }
530
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 {
538 inode: inode,
539 generation: 1,
540 attr: stat(inode, &entry).map_err(|_| libc::EIO)?,
541 attr_timeout: std::f64::MAX,
542 entry_timeout: std::f64::MAX,
543 };
544 match buf.fill(&name, &attr) {
545 Ok(ReplyBufState::Okay) => {}
546 Ok(ReplyBufState::Overfull) => return buf.reply_filled(),
547 Err(_) => return Err(libc::EIO),
548 }
549 }
550
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 {
560 inode: inode,
561 generation: 1,
562 attr: stat(inode, &entry).map_err(|_| libc::EIO)?,
563 attr_timeout: std::f64::MAX,
564 entry_timeout: std::f64::MAX,
565 };
566 match buf.fill(&name, &attr) {
567 Ok(ReplyBufState::Okay) => {}
568 Ok(ReplyBufState::Overfull) => return buf.reply_filled(),
569 Err(_) => return Err(libc::EIO),
570 }
571 }
572
573 buf.reply_filled()
574 });
575 }
576
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) };
580
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)?;
585
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),
593 }
594 }
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);
601 }
602 let mut buffer = acl::ACLXAttrBuffer::new(acl::ACL_EA_VERSION);
603
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 {
606 Some(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);
609 }
610 None => {
611 buffer.add_entry(acl::ACL_GROUP_OBJ, None, acl::mode_group_to_acl_permissions(entry.entry.mode));
612 }
613 }
614 buffer.add_entry(acl::ACL_OTHER, None, acl::mode_other_to_acl_permissions(entry.entry.mode));
615
616 for user in &mut entry.xattr.acl_user {
617 buffer.add_entry(acl::ACL_USER, Some(user.uid), user.permissions);
618 }
619 for group in &mut entry.xattr.acl_group {
620 buffer.add_entry(acl::ACL_GROUP, Some(group.gid), group.permissions);
621 }
622 return Self::xattr_reply_value(req, buffer.as_mut_slice(), size);
623 }
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);
627
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);
631
632 if default.mask_permissions != std::u64::MAX {
633 buffer.add_entry(acl::ACL_MASK, None, default.mask_permissions);
634 }
635
636 for user in &mut entry.xattr.acl_default_user {
637 buffer.add_entry(acl::ACL_USER, Some(user.uid), user.permissions);
638 }
639 for group in &mut entry.xattr.acl_default_group {
640 buffer.add_entry(acl::ACL_GROUP, Some(group.gid), group.permissions);
641 }
642 if buffer.len() > 0 {
643 return Self::xattr_reply_value(req, buffer.as_mut_slice(), size);
644 }
645 }
646 }
647 name => {
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);
651 }
652 }
653 }
654 }
655
656
657 Err(libc::ENODATA)
658 });
659 }
660
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");
670 }
671 if entry.xattr.acl_default.is_some() {
672 buffer.extend_from_slice(b"system.posix_acl_default\0");
673 }
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");
678 }
679 for xattr in &mut entry.xattr.xattrs {
680 buffer.append(&mut xattr.name);
681 buffer.push(b'\0');
682 }
683
684 Self::xattr_reply_value(req, &mut buffer, size)
685 });
686 }
687
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();
692
693 if size == 0 {
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);
699 } else {
700 // value fits into requested buffer size, send value
701 let _res = unsafe {
702 let vptr = value.as_mut_ptr() as *mut c_char;
703 fuse_reply_buf(req, vptr, len)
704 };
705 }
706
707 Ok(())
708 }
709 }
710
711 impl Drop for Session {
712 fn drop(&mut self) {
713 unsafe {
714 fuse_session_unmount(self.ptr);
715 fuse_remove_signal_handlers(self.ptr);
716 fuse_session_destroy(self.ptr);
717 }
718 }
719 }
720
721 /// FUSE entry for fuse_reply_entry in lookup callback
722 #[repr(C)]
723 struct EntryParam {
724 inode: u64,
725 generation: u64,
726 attr: libc::stat,
727 attr_timeout: f64,
728 entry_timeout: f64,
729 }
730
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 {
734 libc::S_IFDIR => 2,
735 _ => 1,
736 };
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;
740
741 let mut attr: libc::stat = unsafe { std::mem::zeroed() };
742 attr.st_ino = inode;
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;
748 attr.st_atime = sec;
749 attr.st_atime_nsec = nsec;
750 attr.st_mtime = sec;
751 attr.st_mtime_nsec = nsec;
752 attr.st_ctime = sec;
753 attr.st_ctime_nsec = nsec;
754
755 Ok(attr)
756 }
757
758 /// State of ReplyBuf after last add_entry call
759 enum ReplyBufState {
760 /// Entry was successfully added to ReplyBuf
761 Okay,
762 /// Entry did not fit into ReplyBuf, was not added
763 Overfull,
764 }
765
766 /// Used to correctly fill and reply the buffer for the readdirplus callback
767 struct ReplyBuf {
768 /// internal buffer holding the binary data
769 buffer: Vec<u8>,
770 /// offset up to which the buffer is filled already
771 filled: usize,
772 /// fuse request the buffer is used to reply to
773 req: Request,
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.
776 next: usize,
777 }
778
779 impl ReplyBuf {
780 /// Create a new empty `ReplyBuf` of `size` with element counting index at `next`.
781 fn new(req: Request, size: usize, next: usize) -> Self {
782 Self {
783 buffer: vec![0; size],
784 filled: 0,
785 req,
786 next,
787 }
788 }
789
790 /// Reply to the `Request` with the filled buffer
791 fn reply_filled(&mut self) -> Result<(), i32> {
792 let _res = unsafe {
793 let ptr = self.buffer.as_mut_ptr() as *mut c_char;
794 fuse_reply_buf(self.req, ptr, self.filled)
795 };
796
797 Ok(())
798 }
799
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> {
804 self.next += 1;
805 let size = self.buffer.len();
806 let bytes = unsafe {
807 let bptr = self.buffer.as_mut_ptr() as *mut c_char;
808 let nptr = name.as_ptr();
809 fuse_add_direntry_plus(
810 self.req,
811 bptr.offset(self.filled as isize),
812 size - self.filled,
813 nptr,
814 Some(&attr),
815 i32::try_from(self.next)?,
816 ) as usize
817 };
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;
823 self.next -= 1;
824 return Ok(ReplyBufState::Overfull);
825 }
826
827 Ok(ReplyBufState::Okay)
828 }
829 }