1use std::{
4 ffi::OsStr,
5 io,
6 path::Path,
7 time::{Duration, SystemTime},
8};
9
10use aster_forge_cloud_files_core::CloudFilesBackend;
11use fuser::{
12 BsdFileFlags, Config, Errno, FileAttr, FileHandle, FileType, Filesystem, FopenFlags,
13 Generation, INodeNo, LockOwner, MountOption, OpenAccMode, OpenFlags, RenameFlags, ReplyAttr,
14 ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty, ReplyEntry, ReplyOpen, ReplyWrite, Request,
15 TimeOrNow, WriteFlags,
16};
17use tokio::runtime::Handle;
18
19use crate::{
20 LinuxDirectoryHandle, LinuxDispatchRejection, LinuxErrorCode, LinuxFileAccess,
21 LinuxFileAttributes, LinuxFileHandle, LinuxInode, LinuxInvalidation, LinuxMountConfig,
22 LinuxNode, LinuxNodeKind, LinuxReadOnlyEngine, LinuxRequestDispatcher, LinuxWritableEngine,
23 LinuxWritebackStore, Result,
24};
25
26#[derive(Debug, Clone)]
28pub struct LinuxKernelNotifier {
29 inner: fuser::Notifier,
30}
31
32impl LinuxKernelNotifier {
33 fn new(inner: fuser::Notifier) -> Self {
34 Self { inner }
35 }
36
37 pub fn apply(&self, invalidation: &LinuxInvalidation) -> io::Result<()> {
44 match invalidation {
45 LinuxInvalidation::Entry { parent, name } => self
46 .inner
47 .inval_entry(INodeNo(parent.get()), OsStr::new(name)),
48 LinuxInvalidation::Inode { inode } => {
49 self.inner.inval_inode(INodeNo(inode.get()), 0, 0)
50 }
51 LinuxInvalidation::Delete {
52 parent,
53 child,
54 name,
55 } => self.inner.delete(
56 INodeNo(parent.get()),
57 INodeNo(child.get()),
58 OsStr::new(name),
59 ),
60 }
61 }
62
63 pub fn apply_all<'a>(
69 &self,
70 invalidations: impl IntoIterator<Item = &'a LinuxInvalidation>,
71 ) -> io::Result<()> {
72 for invalidation in invalidations {
73 self.apply(invalidation)?;
74 }
75 Ok(())
76 }
77}
78
79#[derive(Debug)]
81pub struct LinuxBackgroundSession {
82 inner: fuser::BackgroundSession,
83}
84
85impl LinuxBackgroundSession {
86 #[must_use]
88 pub fn notifier(&self) -> LinuxKernelNotifier {
89 LinuxKernelNotifier::new(self.inner.notifier())
90 }
91
92 pub fn join(self) -> io::Result<()> {
98 self.inner.join()
99 }
100
101 pub fn unmount_and_join(self) -> io::Result<()> {
107 self.inner.umount_and_join()
108 }
109}
110
111fn errno(code: LinuxErrorCode) -> Errno {
112 match code {
113 LinuxErrorCode::NotFound => Errno::ENOENT,
114 LinuxErrorCode::AlreadyExists => Errno::EEXIST,
115 LinuxErrorCode::DirectoryNotEmpty => Errno::ENOTEMPTY,
116 LinuxErrorCode::IsDirectory => Errno::EISDIR,
117 LinuxErrorCode::NotDirectory => Errno::ENOTDIR,
118 LinuxErrorCode::AccessDenied => Errno::EACCES,
119 LinuxErrorCode::ReadOnlyFilesystem => Errno::EROFS,
120 LinuxErrorCode::TryAgain => Errno::EAGAIN,
121 LinuxErrorCode::Stale => Errno::ESTALE,
122 LinuxErrorCode::NotSupported => Errno::ENOSYS,
123 LinuxErrorCode::InvalidArgument => Errno::EINVAL,
124 LinuxErrorCode::Io => Errno::EIO,
125 }
126}
127
128fn file_access(mode: OpenAccMode) -> LinuxFileAccess {
129 match mode {
130 OpenAccMode::O_RDONLY => LinuxFileAccess::Read,
131 OpenAccMode::O_WRONLY => LinuxFileAccess::Write,
132 OpenAccMode::O_RDWR => LinuxFileAccess::ReadWrite,
133 }
134}
135
136fn request_inode(inode: INodeNo) -> Result<LinuxInode> {
137 LinuxInode::new(u64::from(inode))
138}
139
140fn file_type(kind: LinuxNodeKind) -> FileType {
141 match kind {
142 LinuxNodeKind::File => FileType::RegularFile,
143 LinuxNodeKind::Directory => FileType::Directory,
144 }
145}
146
147fn file_attr(attributes: &LinuxFileAttributes) -> FileAttr {
148 FileAttr {
149 ino: INodeNo(attributes.inode().get()),
150 size: attributes.size(),
151 blocks: attributes.blocks(),
152 atime: attributes.time(),
153 mtime: attributes.time(),
154 ctime: attributes.time(),
155 crtime: attributes.time(),
156 kind: file_type(attributes.kind()),
157 perm: attributes.permissions(),
158 nlink: attributes.links(),
159 uid: attributes.uid(),
160 gid: attributes.gid(),
161 rdev: 0,
162 blksize: attributes.block_size(),
163 flags: 0,
164 }
165}
166
167fn reply_entry(reply: ReplyEntry, ttl: Duration, node: &LinuxNode) {
168 reply.entry(
169 &ttl,
170 &file_attr(node.attributes()),
171 Generation(node.generation().get()),
172 );
173}
174
175fn rejection_errno(rejection: LinuxDispatchRejection) -> Errno {
176 errno(rejection.error_code())
177}
178
179pub struct LinuxReadOnlyFilesystem<B> {
181 engine: LinuxReadOnlyEngine<B>,
182 dispatcher: LinuxRequestDispatcher,
183}
184
185pub struct LinuxWritableFilesystem<B, S> {
187 engine: LinuxWritableEngine<B, S>,
188 dispatcher: LinuxRequestDispatcher,
189}
190
191impl<B, S> LinuxWritableFilesystem<B, S>
192where
193 B: CloudFilesBackend + 'static,
194 S: LinuxWritebackStore + 'static,
195{
196 pub fn new(
203 engine: LinuxWritableEngine<B, S>,
204 runtime: Handle,
205 max_in_flight: usize,
206 ) -> Result<Self> {
207 Ok(Self {
208 engine,
209 dispatcher: LinuxRequestDispatcher::new(runtime, max_in_flight)?,
210 })
211 }
212
213 #[must_use]
215 pub const fn dispatcher(&self) -> &LinuxRequestDispatcher {
216 &self.dispatcher
217 }
218}
219
220impl<B> LinuxReadOnlyFilesystem<B>
221where
222 B: CloudFilesBackend + 'static,
223{
224 pub fn new(
231 engine: LinuxReadOnlyEngine<B>,
232 runtime: Handle,
233 max_in_flight: usize,
234 ) -> Result<Self> {
235 Ok(Self {
236 engine,
237 dispatcher: LinuxRequestDispatcher::new(runtime, max_in_flight)?,
238 })
239 }
240
241 #[must_use]
243 pub const fn dispatcher(&self) -> &LinuxRequestDispatcher {
244 &self.dispatcher
245 }
246}
247
248impl<B> Filesystem for LinuxReadOnlyFilesystem<B>
249where
250 B: CloudFilesBackend + 'static,
251{
252 fn destroy(&mut self) {
253 self.dispatcher.close();
254 }
255
256 fn lookup(&self, _request: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEntry) {
257 let parent = match request_inode(parent) {
258 Ok(parent) => parent,
259 Err(error) => {
260 reply.error(errno(error.error_code()));
261 return;
262 }
263 };
264 let Some(name) = name.to_str().map(str::to_owned) else {
265 reply.error(Errno::EINVAL);
266 return;
267 };
268 let reservation = match self.dispatcher.reserve() {
269 Ok(reservation) => reservation,
270 Err(rejection) => {
271 reply.error(rejection_errno(rejection));
272 return;
273 }
274 };
275 let engine = self.engine.clone();
276 let ttl = engine.attribute_policy().cache_ttl();
277 reservation.spawn(async move {
278 match engine.lookup(parent, &name).await {
279 Ok(node) => reply_entry(reply, ttl, &node),
280 Err(error) => reply.error(errno(error.error_code())),
281 }
282 });
283 }
284
285 fn getattr(
286 &self,
287 _request: &Request,
288 inode: INodeNo,
289 _handle: Option<FileHandle>,
290 reply: ReplyAttr,
291 ) {
292 let inode = match request_inode(inode) {
293 Ok(inode) => inode,
294 Err(error) => {
295 reply.error(errno(error.error_code()));
296 return;
297 }
298 };
299 let reservation = match self.dispatcher.reserve() {
300 Ok(reservation) => reservation,
301 Err(rejection) => {
302 reply.error(rejection_errno(rejection));
303 return;
304 }
305 };
306 let engine = self.engine.clone();
307 let ttl = engine.attribute_policy().cache_ttl();
308 reservation.spawn(async move {
309 match engine.getattr(inode).await {
310 Ok(node) => reply.attr(&ttl, &file_attr(node.attributes())),
311 Err(error) => reply.error(errno(error.error_code())),
312 }
313 });
314 }
315
316 fn open(&self, _request: &Request, inode: INodeNo, flags: OpenFlags, reply: ReplyOpen) {
317 if flags.acc_mode() != OpenAccMode::O_RDONLY {
318 reply.error(Errno::EROFS);
319 return;
320 }
321 let inode = match request_inode(inode) {
322 Ok(inode) => inode,
323 Err(error) => {
324 reply.error(errno(error.error_code()));
325 return;
326 }
327 };
328 let reservation = match self.dispatcher.reserve() {
329 Ok(reservation) => reservation,
330 Err(rejection) => {
331 reply.error(rejection_errno(rejection));
332 return;
333 }
334 };
335 let engine = self.engine.clone();
336 reservation.spawn(async move {
337 match engine.open_file(inode).await {
338 Ok(handle) => reply.opened(
339 FileHandle(handle.get()),
340 FopenFlags::FOPEN_DIRECT_IO | FopenFlags::FOPEN_NOFLUSH,
341 ),
342 Err(error) => reply.error(errno(error.error_code())),
343 }
344 });
345 }
346
347 fn read(
348 &self,
349 _request: &Request,
350 inode: INodeNo,
351 handle: FileHandle,
352 offset: u64,
353 size: u32,
354 _flags: OpenFlags,
355 _lock_owner: Option<LockOwner>,
356 reply: ReplyData,
357 ) {
358 let inode = match request_inode(inode) {
359 Ok(inode) => inode,
360 Err(error) => {
361 reply.error(errno(error.error_code()));
362 return;
363 }
364 };
365 let handle = match LinuxFileHandle::new(u64::from(handle)) {
366 Ok(handle) => handle,
367 Err(error) => {
368 reply.error(errno(error.error_code()));
369 return;
370 }
371 };
372 let reservation = match self.dispatcher.reserve() {
373 Ok(reservation) => reservation,
374 Err(rejection) => {
375 reply.error(rejection_errno(rejection));
376 return;
377 }
378 };
379 let engine = self.engine.clone();
380 reservation.spawn(async move {
381 match engine.read_file(inode, handle, offset, size).await {
382 Ok(bytes) => reply.data(&bytes),
383 Err(error) => reply.error(errno(error.error_code())),
384 }
385 });
386 }
387
388 fn write(
389 &self,
390 _request: &Request,
391 _inode: INodeNo,
392 _handle: FileHandle,
393 _offset: u64,
394 _data: &[u8],
395 _write_flags: WriteFlags,
396 _flags: OpenFlags,
397 _lock_owner: Option<LockOwner>,
398 reply: ReplyWrite,
399 ) {
400 reply.error(Errno::EROFS);
401 }
402
403 fn release(
404 &self,
405 _request: &Request,
406 _inode: INodeNo,
407 handle: FileHandle,
408 _flags: OpenFlags,
409 _lock_owner: Option<LockOwner>,
410 _flush: bool,
411 reply: ReplyEmpty,
412 ) {
413 if let Ok(handle) = LinuxFileHandle::new(u64::from(handle)) {
414 let _result = self.engine.release_file(handle);
415 }
416 reply.ok();
417 }
418
419 fn opendir(&self, _request: &Request, inode: INodeNo, _flags: OpenFlags, reply: ReplyOpen) {
420 let inode = match request_inode(inode) {
421 Ok(inode) => inode,
422 Err(error) => {
423 reply.error(errno(error.error_code()));
424 return;
425 }
426 };
427 let reservation = match self.dispatcher.reserve() {
428 Ok(reservation) => reservation,
429 Err(rejection) => {
430 reply.error(rejection_errno(rejection));
431 return;
432 }
433 };
434 let engine = self.engine.clone();
435 reservation.spawn(async move {
436 match engine.open_directory(inode).await {
437 Ok(handle) => reply.opened(FileHandle(handle.get()), FopenFlags::FOPEN_CACHE_DIR),
438 Err(error) => reply.error(errno(error.error_code())),
439 }
440 });
441 }
442
443 fn readdir(
444 &self,
445 _request: &Request,
446 inode: INodeNo,
447 handle: FileHandle,
448 offset: u64,
449 mut reply: ReplyDirectory,
450 ) {
451 let result = request_inode(inode).and_then(|inode| {
452 let handle = LinuxDirectoryHandle::new(u64::from(handle))?;
453 self.engine
454 .directory_snapshot(inode, handle)
455 .map(|snapshot| (inode, snapshot))
456 });
457 let (inode, snapshot) = match result {
458 Ok(result) => result,
459 Err(error) => {
460 reply.error(errno(error.error_code()));
461 return;
462 }
463 };
464 let Ok(start) = usize::try_from(offset) else {
465 reply.ok();
466 return;
467 };
468 let mut index = 0usize;
469 let mut full = false;
470 if index >= start {
471 full = reply.add(INodeNo(inode.get()), 1, FileType::Directory, ".");
472 }
473 index += 1;
474 if !full && index >= start {
475 full = reply.add(
476 INodeNo(snapshot.parent().get()),
477 2,
478 FileType::Directory,
479 "..",
480 );
481 }
482 index += 1;
483 for entry in snapshot.entries() {
484 if full {
485 break;
486 }
487 let Ok(next) = u64::try_from(index + 1) else {
488 break;
489 };
490 if index >= start {
491 full = reply.add(
492 INodeNo(entry.inode().get()),
493 next,
494 file_type(entry.kind()),
495 entry.name(),
496 );
497 }
498 index += 1;
499 }
500 reply.ok();
501 }
502
503 fn releasedir(
504 &self,
505 _request: &Request,
506 _inode: INodeNo,
507 handle: FileHandle,
508 _flags: OpenFlags,
509 reply: ReplyEmpty,
510 ) {
511 if let Ok(handle) = LinuxDirectoryHandle::new(u64::from(handle)) {
512 let _result = self.engine.release_directory(handle);
513 }
514 reply.ok();
515 }
516}
517
518impl<B, S> Filesystem for LinuxWritableFilesystem<B, S>
519where
520 B: CloudFilesBackend + 'static,
521 S: LinuxWritebackStore + 'static,
522{
523 fn destroy(&mut self) {
524 self.dispatcher.close();
525 }
526
527 fn lookup(&self, _request: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEntry) {
528 let parent = match request_inode(parent) {
529 Ok(parent) => parent,
530 Err(error) => {
531 reply.error(errno(error.error_code()));
532 return;
533 }
534 };
535 let Some(name) = name.to_str().map(str::to_owned) else {
536 reply.error(Errno::EINVAL);
537 return;
538 };
539 let reservation = match self.dispatcher.reserve() {
540 Ok(reservation) => reservation,
541 Err(rejection) => {
542 reply.error(rejection_errno(rejection));
543 return;
544 }
545 };
546 let engine = self.engine.clone();
547 let ttl = engine.readonly().attribute_policy().cache_ttl();
548 reservation.spawn(async move {
549 match engine.lookup(parent, &name).await {
550 Ok(node) => reply_entry(reply, ttl, &node),
551 Err(error) => reply.error(errno(error.error_code())),
552 }
553 });
554 }
555
556 fn getattr(
557 &self,
558 _request: &Request,
559 inode: INodeNo,
560 handle: Option<FileHandle>,
561 reply: ReplyAttr,
562 ) {
563 let inode = match request_inode(inode) {
564 Ok(inode) => inode,
565 Err(error) => {
566 reply.error(errno(error.error_code()));
567 return;
568 }
569 };
570 let handle = match handle
571 .map(|handle| LinuxFileHandle::new(u64::from(handle)))
572 .transpose()
573 {
574 Ok(handle) => handle,
575 Err(error) => {
576 reply.error(errno(error.error_code()));
577 return;
578 }
579 };
580 let reservation = match self.dispatcher.reserve() {
581 Ok(reservation) => reservation,
582 Err(rejection) => {
583 reply.error(rejection_errno(rejection));
584 return;
585 }
586 };
587 let engine = self.engine.clone();
588 let ttl = engine.readonly().attribute_policy().cache_ttl();
589 reservation.spawn(async move {
590 match engine.getattr(inode, handle).await {
591 Ok(node) => reply.attr(&ttl, &file_attr(node.attributes())),
592 Err(error) => reply.error(errno(error.error_code())),
593 }
594 });
595 }
596
597 fn setattr(
598 &self,
599 _request: &Request,
600 inode: INodeNo,
601 mode: Option<u32>,
602 uid: Option<u32>,
603 gid: Option<u32>,
604 size: Option<u64>,
605 atime: Option<TimeOrNow>,
606 mtime: Option<TimeOrNow>,
607 status_change_time: Option<SystemTime>,
608 handle: Option<FileHandle>,
609 creation_time: Option<SystemTime>,
610 attribute_change_time: Option<SystemTime>,
611 backup_time: Option<SystemTime>,
612 flags: Option<BsdFileFlags>,
613 reply: ReplyAttr,
614 ) {
615 if mode.is_some()
616 || uid.is_some()
617 || gid.is_some()
618 || atime.is_some()
619 || mtime.is_some()
620 || status_change_time.is_some()
621 || creation_time.is_some()
622 || attribute_change_time.is_some()
623 || backup_time.is_some()
624 || flags.is_some()
625 {
626 reply.error(Errno::ENOSYS);
627 return;
628 }
629 let Some(size) = size else {
630 reply.error(Errno::EINVAL);
631 return;
632 };
633 let inode = match request_inode(inode) {
634 Ok(inode) => inode,
635 Err(error) => {
636 reply.error(errno(error.error_code()));
637 return;
638 }
639 };
640 let handle = match handle
641 .map(|handle| LinuxFileHandle::new(u64::from(handle)))
642 .transpose()
643 {
644 Ok(handle) => handle,
645 Err(error) => {
646 reply.error(errno(error.error_code()));
647 return;
648 }
649 };
650 let reservation = match self.dispatcher.reserve() {
651 Ok(reservation) => reservation,
652 Err(rejection) => {
653 reply.error(rejection_errno(rejection));
654 return;
655 }
656 };
657 let engine = self.engine.clone();
658 let ttl = engine.readonly().attribute_policy().cache_ttl();
659 reservation.spawn(async move {
660 let result = match handle {
661 Some(handle) => engine.truncate_file(inode, handle, size).await,
662 None => engine.truncate_once(inode, size).await,
663 };
664 match result {
665 Ok(node) => reply.attr(&ttl, &file_attr(node.attributes())),
666 Err(error) => reply.error(errno(error.error_code())),
667 }
668 });
669 }
670
671 fn open(&self, _request: &Request, inode: INodeNo, flags: OpenFlags, reply: ReplyOpen) {
672 let inode = match request_inode(inode) {
673 Ok(inode) => inode,
674 Err(error) => {
675 reply.error(errno(error.error_code()));
676 return;
677 }
678 };
679 let reservation = match self.dispatcher.reserve() {
680 Ok(reservation) => reservation,
681 Err(rejection) => {
682 reply.error(rejection_errno(rejection));
683 return;
684 }
685 };
686 let engine = self.engine.clone();
687 let access = file_access(flags.acc_mode());
688 reservation.spawn(async move {
689 match engine.open_file(inode, access).await {
690 Ok(handle) => reply.opened(FileHandle(handle.get()), FopenFlags::FOPEN_DIRECT_IO),
691 Err(error) => reply.error(errno(error.error_code())),
692 }
693 });
694 }
695
696 fn create(
697 &self,
698 _request: &Request,
699 parent: INodeNo,
700 name: &OsStr,
701 mode: u32,
702 umask: u32,
703 flags: i32,
704 reply: ReplyCreate,
705 ) {
706 let parent = match request_inode(parent) {
707 Ok(parent) => parent,
708 Err(error) => {
709 reply.error(errno(error.error_code()));
710 return;
711 }
712 };
713 let Some(name) = name.to_str().map(str::to_owned) else {
714 reply.error(Errno::EINVAL);
715 return;
716 };
717 let reservation = match self.dispatcher.reserve() {
718 Ok(reservation) => reservation,
719 Err(rejection) => {
720 reply.error(rejection_errno(rejection));
721 return;
722 }
723 };
724 let engine = self.engine.clone();
725 let access = file_access(OpenFlags(flags).acc_mode());
726 let ttl = engine.readonly().attribute_policy().cache_ttl();
727 reservation.spawn(async move {
728 match engine.create_file(parent, &name, mode, umask, access).await {
729 Ok((node, handle)) => reply.created(
730 &ttl,
731 &file_attr(node.attributes()),
732 Generation(node.generation().get()),
733 FileHandle(handle.get()),
734 FopenFlags::FOPEN_DIRECT_IO,
735 ),
736 Err(error) => reply.error(errno(error.error_code())),
737 }
738 });
739 }
740
741 fn mkdir(
742 &self,
743 _request: &Request,
744 parent: INodeNo,
745 name: &OsStr,
746 mode: u32,
747 umask: u32,
748 reply: ReplyEntry,
749 ) {
750 let parent = match request_inode(parent) {
751 Ok(parent) => parent,
752 Err(error) => {
753 reply.error(errno(error.error_code()));
754 return;
755 }
756 };
757 let Some(name) = name.to_str().map(str::to_owned) else {
758 reply.error(Errno::EINVAL);
759 return;
760 };
761 let reservation = match self.dispatcher.reserve() {
762 Ok(reservation) => reservation,
763 Err(rejection) => {
764 reply.error(rejection_errno(rejection));
765 return;
766 }
767 };
768 let engine = self.engine.clone();
769 let ttl = engine.readonly().attribute_policy().cache_ttl();
770 reservation.spawn(async move {
771 match engine.create_directory(parent, &name, mode, umask).await {
772 Ok(node) => reply_entry(reply, ttl, &node),
773 Err(error) => reply.error(errno(error.error_code())),
774 }
775 });
776 }
777
778 fn unlink(&self, _request: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEmpty) {
779 self.remove_request(parent, name, LinuxNodeKind::File, reply);
780 }
781
782 fn rmdir(&self, _request: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEmpty) {
783 self.remove_request(parent, name, LinuxNodeKind::Directory, reply);
784 }
785
786 fn rename(
787 &self,
788 _request: &Request,
789 parent: INodeNo,
790 name: &OsStr,
791 new_parent: INodeNo,
792 new_name: &OsStr,
793 flags: RenameFlags,
794 reply: ReplyEmpty,
795 ) {
796 if flags.intersects(RenameFlags::RENAME_EXCHANGE | RenameFlags::RENAME_WHITEOUT) {
797 reply.error(Errno::ENOSYS);
798 return;
799 }
800 let result = request_inode(parent)
801 .and_then(|parent| request_inode(new_parent).map(|new_parent| (parent, new_parent)));
802 let (parent, new_parent) = match result {
803 Ok(result) => result,
804 Err(error) => {
805 reply.error(errno(error.error_code()));
806 return;
807 }
808 };
809 let (Some(name), Some(new_name)) = (
810 name.to_str().map(str::to_owned),
811 new_name.to_str().map(str::to_owned),
812 ) else {
813 reply.error(Errno::EINVAL);
814 return;
815 };
816 let reservation = match self.dispatcher.reserve() {
817 Ok(reservation) => reservation,
818 Err(rejection) => {
819 reply.error(rejection_errno(rejection));
820 return;
821 }
822 };
823 let engine = self.engine.clone();
824 let no_replace = flags.contains(RenameFlags::RENAME_NOREPLACE);
825 reservation.spawn(async move {
826 match engine
827 .rename(parent, &name, new_parent, &new_name, no_replace)
828 .await
829 {
830 Ok(()) => reply.ok(),
831 Err(error) => reply.error(errno(error.error_code())),
832 }
833 });
834 }
835
836 fn read(
837 &self,
838 _request: &Request,
839 inode: INodeNo,
840 handle: FileHandle,
841 offset: u64,
842 size: u32,
843 _flags: OpenFlags,
844 _lock_owner: Option<LockOwner>,
845 reply: ReplyData,
846 ) {
847 let result = request_inode(inode).and_then(|inode| {
848 LinuxFileHandle::new(u64::from(handle)).map(|handle| (inode, handle))
849 });
850 let (inode, handle) = match result {
851 Ok(result) => result,
852 Err(error) => {
853 reply.error(errno(error.error_code()));
854 return;
855 }
856 };
857 let reservation = match self.dispatcher.reserve() {
858 Ok(reservation) => reservation,
859 Err(rejection) => {
860 reply.error(rejection_errno(rejection));
861 return;
862 }
863 };
864 let engine = self.engine.clone();
865 reservation.spawn(async move {
866 match engine.read_file(inode, handle, offset, size).await {
867 Ok(bytes) => reply.data(&bytes),
868 Err(error) => reply.error(errno(error.error_code())),
869 }
870 });
871 }
872
873 fn write(
874 &self,
875 _request: &Request,
876 inode: INodeNo,
877 handle: FileHandle,
878 offset: u64,
879 data: &[u8],
880 _write_flags: WriteFlags,
881 _flags: OpenFlags,
882 _lock_owner: Option<LockOwner>,
883 reply: ReplyWrite,
884 ) {
885 let result = request_inode(inode).and_then(|inode| {
886 LinuxFileHandle::new(u64::from(handle)).map(|handle| (inode, handle))
887 });
888 let (inode, handle) = match result {
889 Ok(result) => result,
890 Err(error) => {
891 reply.error(errno(error.error_code()));
892 return;
893 }
894 };
895 let reservation = match self.dispatcher.reserve() {
896 Ok(reservation) => reservation,
897 Err(rejection) => {
898 reply.error(rejection_errno(rejection));
899 return;
900 }
901 };
902 let engine = self.engine.clone();
903 let bytes = bytes::Bytes::copy_from_slice(data);
904 reservation.spawn(async move {
905 match engine.write_file(inode, handle, offset, bytes).await {
906 Ok(written) => reply.written(written),
907 Err(error) => reply.error(errno(error.error_code())),
908 }
909 });
910 }
911
912 fn flush(
913 &self,
914 _request: &Request,
915 inode: INodeNo,
916 handle: FileHandle,
917 _lock_owner: LockOwner,
918 reply: ReplyEmpty,
919 ) {
920 self.sync_request(inode, handle, false, reply);
921 }
922
923 fn fsync(
924 &self,
925 _request: &Request,
926 inode: INodeNo,
927 handle: FileHandle,
928 data_only: bool,
929 reply: ReplyEmpty,
930 ) {
931 self.sync_request(inode, handle, data_only, reply);
932 }
933
934 fn release(
935 &self,
936 _request: &Request,
937 _inode: INodeNo,
938 handle: FileHandle,
939 _flags: OpenFlags,
940 _lock_owner: Option<LockOwner>,
941 _flush: bool,
942 reply: ReplyEmpty,
943 ) {
944 let Ok(handle) = LinuxFileHandle::new(u64::from(handle)) else {
945 reply.ok();
946 return;
947 };
948 let engine = self.engine.clone();
949 self.dispatcher.spawn_cleanup(async move {
950 let _result = engine.release_file(handle).await;
951 reply.ok();
952 });
953 }
954
955 fn opendir(&self, _request: &Request, inode: INodeNo, _flags: OpenFlags, reply: ReplyOpen) {
956 let inode = match request_inode(inode) {
957 Ok(inode) => inode,
958 Err(error) => {
959 reply.error(errno(error.error_code()));
960 return;
961 }
962 };
963 let reservation = match self.dispatcher.reserve() {
964 Ok(reservation) => reservation,
965 Err(rejection) => {
966 reply.error(rejection_errno(rejection));
967 return;
968 }
969 };
970 let engine = self.engine.clone();
971 reservation.spawn(async move {
972 match engine.open_directory(inode).await {
973 Ok(handle) => reply.opened(FileHandle(handle.get()), FopenFlags::FOPEN_CACHE_DIR),
974 Err(error) => reply.error(errno(error.error_code())),
975 }
976 });
977 }
978
979 fn readdir(
980 &self,
981 _request: &Request,
982 inode: INodeNo,
983 handle: FileHandle,
984 offset: u64,
985 mut reply: ReplyDirectory,
986 ) {
987 let result = request_inode(inode).and_then(|inode| {
988 let handle = LinuxDirectoryHandle::new(u64::from(handle))?;
989 self.engine
990 .directory_snapshot(inode, handle)
991 .map(|snapshot| (inode, snapshot))
992 });
993 let (inode, snapshot) = match result {
994 Ok(result) => result,
995 Err(error) => {
996 reply.error(errno(error.error_code()));
997 return;
998 }
999 };
1000 let Ok(start) = usize::try_from(offset) else {
1001 reply.ok();
1002 return;
1003 };
1004 let mut index = 0usize;
1005 let mut full = false;
1006 if index >= start {
1007 full = reply.add(INodeNo(inode.get()), 1, FileType::Directory, ".");
1008 }
1009 index += 1;
1010 if !full && index >= start {
1011 full = reply.add(
1012 INodeNo(snapshot.parent().get()),
1013 2,
1014 FileType::Directory,
1015 "..",
1016 );
1017 }
1018 index += 1;
1019 for entry in snapshot.entries() {
1020 if full {
1021 break;
1022 }
1023 let Ok(next) = u64::try_from(index + 1) else {
1024 break;
1025 };
1026 if index >= start {
1027 full = reply.add(
1028 INodeNo(entry.inode().get()),
1029 next,
1030 file_type(entry.kind()),
1031 entry.name(),
1032 );
1033 }
1034 index += 1;
1035 }
1036 reply.ok();
1037 }
1038
1039 fn releasedir(
1040 &self,
1041 _request: &Request,
1042 _inode: INodeNo,
1043 handle: FileHandle,
1044 _flags: OpenFlags,
1045 reply: ReplyEmpty,
1046 ) {
1047 if let Ok(handle) = LinuxDirectoryHandle::new(u64::from(handle)) {
1048 let _result = self.engine.release_directory(handle);
1049 }
1050 reply.ok();
1051 }
1052}
1053
1054impl<B, S> LinuxWritableFilesystem<B, S>
1055where
1056 B: CloudFilesBackend + 'static,
1057 S: LinuxWritebackStore + 'static,
1058{
1059 fn sync_request(&self, inode: INodeNo, handle: FileHandle, data_only: bool, reply: ReplyEmpty) {
1060 let result = request_inode(inode).and_then(|inode| {
1061 LinuxFileHandle::new(u64::from(handle)).map(|handle| (inode, handle))
1062 });
1063 let (inode, handle) = match result {
1064 Ok(result) => result,
1065 Err(error) => {
1066 reply.error(errno(error.error_code()));
1067 return;
1068 }
1069 };
1070 let reservation = match self.dispatcher.reserve() {
1071 Ok(reservation) => reservation,
1072 Err(rejection) => {
1073 reply.error(rejection_errno(rejection));
1074 return;
1075 }
1076 };
1077 let engine = self.engine.clone();
1078 reservation.spawn(async move {
1079 match engine.sync_file(inode, handle, data_only).await {
1080 Ok(()) => reply.ok(),
1081 Err(error) => reply.error(errno(error.error_code())),
1082 }
1083 });
1084 }
1085
1086 fn remove_request(
1087 &self,
1088 parent: INodeNo,
1089 name: &OsStr,
1090 kind: LinuxNodeKind,
1091 reply: ReplyEmpty,
1092 ) {
1093 let parent = match request_inode(parent) {
1094 Ok(parent) => parent,
1095 Err(error) => {
1096 reply.error(errno(error.error_code()));
1097 return;
1098 }
1099 };
1100 let Some(name) = name.to_str().map(str::to_owned) else {
1101 reply.error(Errno::EINVAL);
1102 return;
1103 };
1104 let reservation = match self.dispatcher.reserve() {
1105 Ok(reservation) => reservation,
1106 Err(rejection) => {
1107 reply.error(rejection_errno(rejection));
1108 return;
1109 }
1110 };
1111 let engine = self.engine.clone();
1112 reservation.spawn(async move {
1113 match engine.remove(parent, &name, kind).await {
1114 Ok(()) => reply.ok(),
1115 Err(error) => reply.error(errno(error.error_code())),
1116 }
1117 });
1118 }
1119}
1120
1121pub fn mount_read_only<B>(
1127 filesystem: LinuxReadOnlyFilesystem<B>,
1128 mountpoint: impl AsRef<Path>,
1129 config: &LinuxMountConfig,
1130) -> io::Result<()>
1131where
1132 B: CloudFilesBackend + 'static,
1133{
1134 let mut options = Config::default();
1135 options.n_threads = config.kernel_threads();
1136 options.clone_fd = config.clone_fd();
1137 options.mount_options.extend([
1138 MountOption::RO,
1139 MountOption::DefaultPermissions,
1140 MountOption::NoDev,
1141 MountOption::NoSuid,
1142 MountOption::NoExec,
1143 MountOption::FSName(config.filesystem_name().to_owned()),
1144 ]);
1145 if let Some(subtype) = config.subtype() {
1146 options
1147 .mount_options
1148 .push(MountOption::Subtype(subtype.to_owned()));
1149 }
1150 fuser::mount(filesystem, mountpoint, &options)
1151}
1152
1153pub fn spawn_mount_read_only<B>(
1160 filesystem: LinuxReadOnlyFilesystem<B>,
1161 mountpoint: impl AsRef<Path>,
1162 config: &LinuxMountConfig,
1163) -> io::Result<LinuxBackgroundSession>
1164where
1165 B: CloudFilesBackend + 'static,
1166{
1167 let mut options = Config::default();
1168 options.n_threads = config.kernel_threads();
1169 options.clone_fd = config.clone_fd();
1170 options.mount_options.extend([
1171 MountOption::RO,
1172 MountOption::DefaultPermissions,
1173 MountOption::NoDev,
1174 MountOption::NoSuid,
1175 MountOption::NoExec,
1176 MountOption::FSName(config.filesystem_name().to_owned()),
1177 ]);
1178 if let Some(subtype) = config.subtype() {
1179 options
1180 .mount_options
1181 .push(MountOption::Subtype(subtype.to_owned()));
1182 }
1183 fuser::spawn_mount(filesystem, mountpoint, &options)
1184 .map(|inner| LinuxBackgroundSession { inner })
1185}
1186
1187pub fn mount_writable<B, S>(
1193 filesystem: LinuxWritableFilesystem<B, S>,
1194 mountpoint: impl AsRef<Path>,
1195 config: &LinuxMountConfig,
1196) -> io::Result<()>
1197where
1198 B: CloudFilesBackend + 'static,
1199 S: LinuxWritebackStore + 'static,
1200{
1201 let mut options = Config::default();
1202 options.n_threads = config.kernel_threads();
1203 options.clone_fd = config.clone_fd();
1204 options.mount_options.extend([
1205 MountOption::DefaultPermissions,
1206 MountOption::NoDev,
1207 MountOption::NoSuid,
1208 MountOption::NoExec,
1209 MountOption::FSName(config.filesystem_name().to_owned()),
1210 ]);
1211 if let Some(subtype) = config.subtype() {
1212 options
1213 .mount_options
1214 .push(MountOption::Subtype(subtype.to_owned()));
1215 }
1216 fuser::mount(filesystem, mountpoint, &options)
1217}
1218
1219pub fn spawn_mount_writable<B, S>(
1226 filesystem: LinuxWritableFilesystem<B, S>,
1227 mountpoint: impl AsRef<Path>,
1228 config: &LinuxMountConfig,
1229) -> io::Result<LinuxBackgroundSession>
1230where
1231 B: CloudFilesBackend + 'static,
1232 S: LinuxWritebackStore + 'static,
1233{
1234 let mut options = Config::default();
1235 options.n_threads = config.kernel_threads();
1236 options.clone_fd = config.clone_fd();
1237 options.mount_options.extend([
1238 MountOption::DefaultPermissions,
1239 MountOption::NoDev,
1240 MountOption::NoSuid,
1241 MountOption::NoExec,
1242 MountOption::FSName(config.filesystem_name().to_owned()),
1243 ]);
1244 if let Some(subtype) = config.subtype() {
1245 options
1246 .mount_options
1247 .push(MountOption::Subtype(subtype.to_owned()));
1248 }
1249 fuser::spawn_mount(filesystem, mountpoint, &options)
1250 .map(|inner| LinuxBackgroundSession { inner })
1251}