1use futures::executor::block_on;
4use mountpoint_s3_client::ObjectClient;
5use std::ffi::OsStr;
6use std::path::Path;
7use std::time::SystemTime;
8use time::OffsetDateTime;
9use tracing::{Instrument, field, instrument};
10
11use crate::fs::{
12 DirectoryEntry, DirectoryReplier, InodeNo, S3Filesystem, ToErrno, error_metadata::MOUNTPOINT_EVENT_READY,
13};
14use crate::metrics::defs::{ATTR_FUSE_REQUEST, FUSE_IO_SIZE, FUSE_REQUEST_ERRORS};
15#[cfg(target_os = "macos")]
16use fuser::ReplyXTimes;
17use fuser::{
18 Filesystem, KernelConfig, ReplyAttr, ReplyBmap, ReplyCreate, ReplyData, ReplyEmpty, ReplyEntry, ReplyIoctl,
19 ReplyLock, ReplyLseek, ReplyOpen, ReplyStatfs, ReplyWrite, ReplyXattr, Request, TimeOrNow,
20};
21
22pub mod config;
23pub mod session;
24
25pub trait ErrorLogger: std::fmt::Debug {
27 fn error(&self, err: &crate::fs::Error, fuse_operation: &str, fuse_request_id: u64);
32
33 fn event(&self, operation: &str, event_code: &str);
35}
36
37macro_rules! event {
40 ($level:expr, $($args:tt)*) => {
41 match $level {
42 ::tracing::Level::ERROR => ::tracing::event!(::tracing::Level::ERROR, $($args)*),
43 ::tracing::Level::WARN => ::tracing::event!(::tracing::Level::WARN, $($args)*),
44 ::tracing::Level::INFO => ::tracing::event!(::tracing::Level::INFO, $($args)*),
45 ::tracing::Level::DEBUG => ::tracing::event!(::tracing::Level::DEBUG, $($args)*),
46 ::tracing::Level::TRACE => ::tracing::event!(::tracing::Level::TRACE, $($args)*),
47 }
48 };
49}
50
51macro_rules! fuse_error {
54 ($name:literal, $reply:expr, $err:expr, $fs:expr, $request:expr) => {{
55 let err = $err;
56 event!(err.level, "{} failed with errno {}: {:#}", $name, err.to_errno(), err);
57 ::metrics::counter!(FUSE_REQUEST_ERRORS, ATTR_FUSE_REQUEST => $name).increment(1);
58 if let Some(error_logger) = $fs.error_logger.as_ref() {
59 error_logger.error(&err, $name, $request.unique());
60 }
61 $reply.error(err.to_errno());
62 }};
63}
64
65macro_rules! fuse_unsupported {
67 ($name:literal, $reply:expr, $err:expr, $level:expr) => {{
68 event!($level, "{} failed: operation not supported by Mountpoint", $name);
69 ::metrics::counter!(FUSE_REQUEST_ERRORS, ATTR_FUSE_REQUEST => $name).increment(1);
70 ::metrics::counter!("fuse.op_unimplemented","op" => $name).increment(1);
71 $reply.error($err);
72 }};
73 ($name:literal, $reply:expr) => {
74 fuse_unsupported!($name, $reply, libc::ENOSYS, tracing::Level::WARN)
75 };
76 ($name:literal, $reply:expr, $err:expr) => {
77 fuse_unsupported!($name, $reply, $err, tracing::Level::WARN)
78 };
79}
80
81pub struct S3FuseFilesystem<Client>
84where
85 Client: ObjectClient + Clone + Send + Sync + 'static,
86{
87 fs: S3Filesystem<Client>,
88 error_logger: Option<Box<dyn ErrorLogger + Send + Sync>>,
89}
90
91impl<Client> S3FuseFilesystem<Client>
92where
93 Client: ObjectClient + Clone + Send + Sync + 'static,
94{
95 pub fn new(fs: S3Filesystem<Client>, error_logger: Option<Box<dyn ErrorLogger + Send + Sync>>) -> Self {
96 Self { fs, error_logger }
97 }
98}
99
100impl<Client> Filesystem for S3FuseFilesystem<Client>
101where
102 Client: ObjectClient + Clone + Send + Sync + 'static,
103{
104 #[instrument(level="warn", skip_all, fields(req=req.unique()))]
105 fn init(&self, req: &Request<'_>, config: &mut KernelConfig) -> Result<(), libc::c_int> {
106 if let Some(error_logger) = self.error_logger.as_ref() {
107 error_logger.event("mount", MOUNTPOINT_EVENT_READY);
108 }
109 block_on(self.fs.init(config).in_current_span())
110 }
111
112 #[instrument(level="warn", skip_all, fields(req=req.unique(), ino=parent, name=?name))]
113 fn lookup(&self, req: &Request<'_>, parent: InodeNo, name: &OsStr, reply: ReplyEntry) {
114 match block_on(self.fs.lookup(parent, name).in_current_span()) {
115 Ok(entry) => reply.entry(&entry.ttl, &entry.attr, entry.generation),
116 Err(e) => fuse_error!("lookup", reply, e, self, req),
117 }
118 }
119
120 #[instrument(level="warn", skip_all, fields(req=req.unique(), ino=ino, name=field::Empty))]
121 fn getattr(&self, req: &Request<'_>, ino: InodeNo, _fh: Option<u64>, reply: ReplyAttr) {
122 match block_on(self.fs.getattr(ino).in_current_span()) {
123 Ok(attr) => reply.attr(&attr.ttl, &attr.attr),
124 Err(e) => fuse_error!("getattr", reply, e, self, req),
125 }
126 }
127
128 #[instrument(level="warn", skip_all, fields(req=req.unique(), ino, nlookup, name=field::Empty))]
129 fn forget(&self, req: &Request<'_>, ino: u64, nlookup: u64) {
130 block_on(self.fs.forget(ino, nlookup));
131 }
132
133 #[instrument(level="warn", skip_all, fields(req=req.unique(), ino=ino, pid=req.pid(), name=field::Empty))]
134 fn open(&self, req: &Request<'_>, ino: InodeNo, flags: i32, reply: ReplyOpen) {
135 match block_on(self.fs.open(ino, flags.into(), req.pid()).in_current_span()) {
136 Ok(opened) => reply.opened(opened.fh, opened.flags),
137 Err(e) => fuse_error!("open", reply, e, self, req),
138 }
139 }
140
141 #[instrument(level="warn", skip_all, fields(req=req.unique(), ino=ino, fh=fh, offset=offset, size=size, name=field::Empty))]
142 fn read(
143 &self,
144 req: &Request<'_>,
145 ino: InodeNo,
146 fh: u64,
147 offset: i64,
148 size: u32,
149 flags: i32,
150 lock: Option<u64>,
151 reply: ReplyData,
152 ) {
153 let mut bytes_sent = 0;
154
155 match block_on(self.fs.read(ino, fh, offset, size, flags, lock).in_current_span()) {
156 Ok(data) => {
157 bytes_sent = data.len();
158 reply.data(&data);
159 }
160 Err(err) => fuse_error!("read", reply, err, self, req),
161 }
162
163 metrics::counter!("fuse.total_bytes", "type" => "read").increment(bytes_sent as u64);
164 metrics::histogram!(FUSE_IO_SIZE, ATTR_FUSE_REQUEST => "read").record(bytes_sent as f64);
165 }
166
167 #[instrument(level="warn", skip_all, fields(req=req.unique(), ino=parent, name=field::Empty))]
168 fn opendir(&self, req: &Request<'_>, parent: InodeNo, flags: i32, reply: ReplyOpen) {
169 match block_on(self.fs.opendir(parent, flags).in_current_span()) {
170 Ok(opened) => reply.opened(opened.fh, opened.flags),
171 Err(e) => fuse_error!("opendir", reply, e, self, req),
172 }
173 }
174
175 #[instrument(level="warn", skip_all, fields(req=req.unique(), ino=parent, fh=fh, offset=offset))]
176 fn readdir(&self, req: &Request<'_>, parent: InodeNo, fh: u64, offset: i64, mut reply: fuser::ReplyDirectory) {
177 struct ReplyDirectory<'a> {
178 inner: &'a mut fuser::ReplyDirectory,
179 count: &'a mut usize,
180 }
181
182 impl DirectoryReplier for ReplyDirectory<'_> {
183 fn add(&mut self, entry: DirectoryEntry) -> bool {
184 let result = self.inner.add(entry.ino, entry.offset, entry.attr.kind, entry.name);
185 if !result {
186 *self.count += 1;
187 }
188 result
189 }
190 }
191
192 let mut count = 0;
193 let replier = ReplyDirectory {
194 inner: &mut reply,
195 count: &mut count,
196 };
197
198 match block_on(self.fs.readdir(parent, fh, offset, replier).in_current_span()) {
199 Ok(_) => {
200 reply.ok();
201 metrics::histogram!("fuse.readdir.entries").record(count as f64);
202 }
203 Err(e) => fuse_error!("readdir", reply, e, self, req),
204 }
205 }
206
207 #[instrument(level="warn", skip_all, fields(req=req.unique(), ino=parent, fh=fh, offset=offset))]
208 fn readdirplus(
209 &self,
210 req: &Request<'_>,
211 parent: InodeNo,
212 fh: u64,
213 offset: i64,
214 mut reply: fuser::ReplyDirectoryPlus,
215 ) {
216 struct ReplyDirectoryPlus<'a> {
217 inner: &'a mut fuser::ReplyDirectoryPlus,
218 count: &'a mut usize,
219 }
220
221 impl DirectoryReplier for ReplyDirectoryPlus<'_> {
222 fn add(&mut self, entry: DirectoryEntry) -> bool {
223 let result = self.inner.add(
224 entry.ino,
225 entry.offset,
226 entry.name,
227 &entry.ttl,
228 &entry.attr,
229 entry.generation,
230 );
231 if !result {
232 *self.count += 1;
233 }
234 result
235 }
236 }
237
238 let mut count = 0;
239 let replier = ReplyDirectoryPlus {
240 inner: &mut reply,
241 count: &mut count,
242 };
243
244 match block_on(self.fs.readdirplus(parent, fh, offset, replier).in_current_span()) {
245 Ok(_) => {
246 reply.ok();
247 metrics::histogram!("fuse.readdirplus.entries").record(count as f64);
248 }
249 Err(e) => fuse_error!("readdirplus", reply, e, self, req),
250 }
251 }
252
253 #[instrument(level="warn", skip_all, fields(req=req.unique(), ino=ino, fh=fh, datasync=datasync, name=field::Empty))]
254 fn fsync(&self, req: &Request<'_>, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty) {
255 match block_on(self.fs.fsync(ino, fh, datasync).in_current_span()) {
256 Ok(()) => reply.ok(),
257 Err(e) => fuse_error!("fsync", reply, e, self, req),
258 }
259 }
260
261 #[instrument(level="warn", skip_all, fields(req=req.unique(), ino=ino, fh=fh, pid=req.pid(), name=field::Empty))]
262 fn flush(&self, req: &Request<'_>, ino: u64, fh: u64, lock_owner: u64, reply: ReplyEmpty) {
263 match block_on(self.fs.flush(ino, fh, lock_owner, req.pid()).in_current_span()) {
264 Ok(()) => reply.ok(),
265 Err(e) => fuse_error!("flush", reply, e, self, req),
266 }
267 }
268
269 #[instrument(level="warn", skip_all, fields(req=req.unique(), ino=ino, fh=fh, name=field::Empty))]
270 fn release(
271 &self,
272 req: &Request<'_>,
273 ino: InodeNo,
274 fh: u64,
275 flags: i32,
276 lock_owner: Option<u64>,
277 flush: bool,
278 reply: ReplyEmpty,
279 ) {
280 match block_on(self.fs.release(ino, fh, flags, lock_owner, flush).in_current_span()) {
281 Ok(()) => reply.ok(),
282 Err(e) => fuse_error!("release", reply, e, self, req),
283 }
284 }
285
286 #[instrument(level="warn", skip_all, fields(req=req.unique(), ino=ino, fh=fh))]
287 fn releasedir(&self, req: &Request<'_>, ino: u64, fh: u64, flags: i32, reply: ReplyEmpty) {
288 match block_on(self.fs.releasedir(ino, fh, flags).in_current_span()) {
289 Ok(()) => reply.ok(),
290 Err(e) => fuse_error!("releasedir", reply, e, self, req),
291 }
292 }
293
294 #[instrument(level="warn", skip_all, fields(req=req.unique(), parent=parent, name=?name))]
295 fn mknod(
296 &self,
297 req: &Request<'_>,
298 parent: InodeNo,
299 name: &OsStr,
300 mode: u32,
301 umask: u32,
302 rdev: u32,
303 reply: ReplyEntry,
304 ) {
305 let mode = mode as libc::mode_t;
307
308 match block_on(self.fs.mknod(parent, name, mode, umask, rdev).in_current_span()) {
309 Ok(entry) => reply.entry(&entry.ttl, &entry.attr, entry.generation),
310 Err(e) => fuse_error!("mknod", reply, e, self, req),
311 }
312 }
313
314 #[instrument(level="warn", skip_all, fields(req=req.unique(), parent=parent, name=?name))]
315 fn mkdir(&self, req: &Request<'_>, parent: u64, name: &OsStr, mode: u32, umask: u32, reply: ReplyEntry) {
316 let mode = mode as libc::mode_t;
318
319 match block_on(self.fs.mkdir(parent, name, mode, umask).in_current_span()) {
320 Ok(entry) => reply.entry(&entry.ttl, &entry.attr, entry.generation),
321 Err(e) => fuse_error!("mkdir", reply, e, self, req),
322 }
323 }
324
325 #[instrument(level="warn", skip_all, fields(req=req.unique(), ino=ino, fh=fh, offset=offset, length=data.len(), pid=req.pid(), name=field::Empty))]
326 fn write(
327 &self,
328 req: &Request<'_>,
329 ino: InodeNo,
330 fh: u64,
331 offset: i64,
332 data: &[u8],
333 write_flags: u32,
334 flags: i32,
335 lock_owner: Option<u64>,
336 reply: ReplyWrite,
337 ) {
338 match block_on(
339 self.fs
340 .write(ino, fh, offset, data, write_flags, flags, lock_owner)
341 .in_current_span(),
342 ) {
343 Ok(bytes_written) => {
344 reply.written(bytes_written);
345 metrics::counter!("fuse.total_bytes", "type" => "write").increment(bytes_written as u64);
346 metrics::histogram!(FUSE_IO_SIZE, ATTR_FUSE_REQUEST => "write").record(bytes_written as f64);
347 }
348 Err(e) => fuse_error!("write", reply, e, self, req),
349 }
350 }
351
352 #[instrument(level="warn", skip_all, fields(req=req.unique(), parent=parent, name=?name))]
353 fn rmdir(&self, req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
354 match block_on(self.fs.rmdir(parent, name).in_current_span()) {
355 Ok(()) => reply.ok(),
356 Err(e) => fuse_error!("rmdir", reply, e, self, req),
357 }
358 }
359
360 #[instrument(level="warn", skip_all, fields(req=req.unique(), parent=parent, name=?name))]
361 fn unlink(&self, req: &Request<'_>, parent: InodeNo, name: &OsStr, reply: ReplyEmpty) {
362 match block_on(self.fs.unlink(parent, name).in_current_span()) {
363 Ok(()) => reply.ok(),
364 Err(e) => fuse_error!("unlink", reply, e, self, req),
365 }
366 }
367
368 #[instrument(level="warn", skip_all, fields(req=req.unique(), ino=ino, name=field::Empty))]
369 fn setattr(
370 &self,
371 req: &Request<'_>,
372 ino: u64,
373 _mode: Option<u32>,
374 _uid: Option<u32>,
375 _gid: Option<u32>,
376 size: Option<u64>,
377 atime: Option<TimeOrNow>,
378 mtime: Option<TimeOrNow>,
379 _ctime: Option<SystemTime>,
380 _fh: Option<u64>,
381 _crtime: Option<SystemTime>,
382 _chgtime: Option<SystemTime>,
383 _bkuptime: Option<SystemTime>,
384 flags: Option<u32>,
385 reply: ReplyAttr,
386 ) {
387 let atime = atime.map(|t| match t {
388 TimeOrNow::SpecificTime(st) => OffsetDateTime::from(st),
389 TimeOrNow::Now => OffsetDateTime::now_utc(),
390 });
391 let mtime = mtime.map(|t| match t {
392 TimeOrNow::SpecificTime(st) => OffsetDateTime::from(st),
393 TimeOrNow::Now => OffsetDateTime::now_utc(),
394 });
395 match block_on(self.fs.setattr(ino, atime, mtime, size, flags).in_current_span()) {
396 Ok(attr) => reply.attr(&attr.ttl, &attr.attr),
397 Err(e) => fuse_error!("setattr", reply, e, self, req),
398 }
399 }
400
401 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino=ino))]
404 fn readlink(&self, _req: &Request<'_>, ino: u64, reply: ReplyData) {
405 fuse_unsupported!("readlink", reply);
406 }
407
408 #[instrument(level="warn", skip_all, fields(req=_req.unique(), parent=parent, name=?name, link=?link))]
409 fn symlink(&self, _req: &Request<'_>, parent: u64, name: &OsStr, link: &Path, reply: ReplyEntry) {
410 fuse_unsupported!("symlink", reply, libc::EPERM);
412 }
413
414 #[instrument(level="warn", skip_all, fields(req=req.unique(), parent=parent, name=?name, newparent=newparent, newname=?newname))]
415 fn rename(
416 &self,
417 req: &Request<'_>,
418 parent: u64,
419 name: &OsStr,
420 newparent: u64,
421 newname: &OsStr,
422 flags: u32,
423 reply: ReplyEmpty,
424 ) {
425 match block_on(
426 self.fs
427 .rename(parent, name, newparent, newname, flags.into())
428 .in_current_span(),
429 ) {
430 Ok(()) => reply.ok(),
431 Err(e) => fuse_error!("rename", reply, e, self, req),
432 }
433 }
434
435 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino=ino, newparent=newparent, newname=?newname))]
436 fn link(&self, _req: &Request<'_>, ino: u64, newparent: u64, newname: &OsStr, reply: ReplyEntry) {
437 fuse_unsupported!("link", reply, libc::EPERM);
439 }
440
441 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino=ino, fh=fh, datasync=datasync))]
442 fn fsyncdir(&self, _req: &Request<'_>, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty) {
443 fuse_unsupported!("fsyncdir", reply);
444 }
445
446 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino=ino, name=?name))]
447 fn setxattr(
448 &self,
449 _req: &Request<'_>,
450 ino: u64,
451 name: &OsStr,
452 _value: &[u8],
453 _flags: i32,
454 _position: u32,
455 reply: ReplyEmpty,
456 ) {
457 fuse_unsupported!("setxattr", reply);
458 }
459
460 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino=ino, name=?name))]
461 fn getxattr(&self, _req: &Request<'_>, ino: u64, name: &OsStr, _size: u32, reply: ReplyXattr) {
462 fuse_unsupported!("getxattr", reply);
463 }
464
465 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino=ino))]
466 fn listxattr(&self, _req: &Request<'_>, ino: u64, _size: u32, reply: ReplyXattr) {
467 fuse_unsupported!("listxattr", reply);
468 }
469
470 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino=ino, name=?name))]
471 fn removexattr(&self, _req: &Request<'_>, ino: u64, name: &OsStr, reply: ReplyEmpty) {
472 fuse_unsupported!("removexattr", reply);
473 }
474
475 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino=ino, mask=mask))]
476 fn access(&self, _req: &Request<'_>, ino: u64, mask: i32, reply: ReplyEmpty) {
477 fuse_unsupported!("access", reply);
478 }
479
480 #[instrument(level="warn", skip_all, fields(req=_req.unique(), parent=parent, name=?name))]
481 fn create(
482 &self,
483 _req: &Request<'_>,
484 parent: u64,
485 name: &OsStr,
486 _mode: u32,
487 _umask: u32,
488 _flags: i32,
489 reply: ReplyCreate,
490 ) {
491 fuse_unsupported!("create", reply, libc::ENOSYS, tracing::Level::DEBUG);
492 }
493
494 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino=ino, fh=fh, pid=pid))]
495 fn getlk(
496 &self,
497 _req: &Request<'_>,
498 ino: u64,
499 fh: u64,
500 _lock_owner: u64,
501 _start: u64,
502 _end: u64,
503 _typ: i32,
504 pid: u32,
505 reply: ReplyLock,
506 ) {
507 fuse_unsupported!("getlk", reply);
508 }
509
510 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino=ino, fh=fh, pid=pid))]
511 fn setlk(
512 &self,
513 _req: &Request<'_>,
514 ino: u64,
515 fh: u64,
516 _lock_owner: u64,
517 _start: u64,
518 _end: u64,
519 _typ: i32,
520 pid: u32,
521 _sleep: bool,
522 reply: ReplyEmpty,
523 ) {
524 fuse_unsupported!("setlk", reply);
525 }
526
527 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino=ino))]
528 fn bmap(&self, _req: &Request<'_>, ino: u64, _blocksize: u32, _idx: u64, reply: ReplyBmap) {
529 fuse_unsupported!("bmap", reply);
530 }
531
532 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino=ino, fh=fh, cmd=cmd))]
533 fn ioctl(
534 &self,
535 _req: &Request<'_>,
536 ino: u64,
537 fh: u64,
538 _flags: u32,
539 cmd: u32,
540 _in_data: &[u8],
541 _out_size: u32,
542 reply: ReplyIoctl,
543 ) {
544 fuse_unsupported!("ioctl", reply, libc::ENOSYS, tracing::Level::DEBUG);
545 }
546
547 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino=ino, fh=fh, offset=offset, length=length))]
548 fn fallocate(
549 &self,
550 _req: &Request<'_>,
551 ino: u64,
552 fh: u64,
553 offset: i64,
554 length: i64,
555 _mode: i32,
556 reply: ReplyEmpty,
557 ) {
558 fuse_unsupported!("fallocate", reply);
559 }
560
561 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino=ino, fh=fh, offset=offset, whence=whence))]
562 fn lseek(&self, _req: &Request<'_>, ino: u64, fh: u64, offset: i64, whence: i32, reply: ReplyLseek) {
563 fuse_unsupported!("lseek", reply);
564 }
565
566 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino_in=ino_in, fh_in=fh_in, offset_in=offset_in, ino_out=ino_out, fh_out=fh_out, offset_out=offset_out, len=len))]
567 fn copy_file_range(
568 &self,
569 _req: &Request<'_>,
570 ino_in: u64,
571 fh_in: u64,
572 offset_in: i64,
573 ino_out: u64,
574 fh_out: u64,
575 offset_out: i64,
576 len: u64,
577 _flags: u32,
578 reply: ReplyWrite,
579 ) {
580 fuse_unsupported!("copy_file_range", reply);
581 }
582
583 #[cfg(target_os = "macos")]
584 #[instrument(level="warn", skip_all, fields(req=_req.unique(), name=?name))]
585 fn setvolname(&self, _req: &Request<'_>, name: &OsStr, reply: ReplyEmpty) {
586 fuse_unsupported!("setvolname", reply);
587 }
588
589 #[cfg(target_os = "macos")]
590 #[instrument(level="warn", skip_all, fields(req=_req.unique(), parent=parent, name=?name, newparent=newparent, newname=?newname))]
591 fn exchange(
592 &self,
593 _req: &Request<'_>,
594 parent: u64,
595 name: &OsStr,
596 newparent: u64,
597 newname: &OsStr,
598 _options: u64,
599 reply: ReplyEmpty,
600 ) {
601 fuse_unsupported!("exchange", reply);
602 }
603
604 #[cfg(target_os = "macos")]
605 #[instrument(level="warn", skip_all, fields(req=_req.unique(), ino=ino))]
606 fn getxtimes(&self, _req: &Request<'_>, ino: u64, reply: ReplyXTimes) {
607 fuse_unsupported!("getxtimes", reply);
608 }
609
610 #[instrument(level="warn", skip_all, fields(req=req.unique(), ino=ino))]
611 fn statfs(&self, req: &Request<'_>, ino: u64, reply: ReplyStatfs) {
612 match block_on(self.fs.statfs(ino).in_current_span()) {
613 Ok(statfs) => reply.statfs(
614 statfs.total_blocks,
615 statfs.free_blocks,
616 statfs.available_blocks,
617 statfs.total_inodes,
618 statfs.free_inodes,
619 statfs.block_size,
620 statfs.maximum_name_length,
621 statfs.fragment_size,
622 ),
623 Err(e) => fuse_error!("statfs", reply, e, self, req),
624 }
625 }
626}