Skip to main content

mtp_mount/
fs.rs

1use std::collections::HashMap;
2use std::ffi::OsStr;
3use std::io;
4use std::io::{Seek, SeekFrom};
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::sync::{Arc, Mutex};
7use std::time::{Duration, SystemTime, UNIX_EPOCH};
8
9use bytes::Bytes;
10use fuser::{
11    BsdFileFlags, Errno, FileAttr, FileHandle, FileType, Filesystem, FopenFlags, Generation,
12    INodeNo, KernelConfig, LockOwner, MountOption, OpenFlags, RenameFlags, ReplyAttr, ReplyCreate,
13    ReplyData, ReplyDirectory, ReplyEmpty, ReplyEntry, ReplyOpen, ReplyStatfs, ReplyWrite, Request,
14    TimeOrNow, WriteFlags,
15};
16use log::{debug, error, warn};
17use mtp_rs::mtp::{DeviceEvent, MtpDevice};
18use mtp_rs::{NewObjectInfo, ObjectHandle, Storage};
19
20use crate::buffer::WriteBuffer;
21use crate::inode::{InodeEntry, InodeKind, InodeTable, FUSE_ROOT_INODE};
22use crate::sparse_cache::SparseCache;
23
24const TTL: Duration = Duration::from_secs(1);
25
26fn mtp_datetime_to_system_time(dt: &mtp_rs::DateTime) -> SystemTime {
27    fn days_from_civil(y: i64, m: i64, d: i64) -> i64 {
28        let y = if m <= 2 { y - 1 } else { y };
29        let era = if y >= 0 { y } else { y - 399 } / 400;
30        let yoe = (y - era * 400) as u64;
31        let m_adj = if m > 2 { m - 3 } else { m + 9 } as u64;
32        let doy = (153 * m_adj + 2) / 5 + d as u64 - 1;
33        let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
34        era * 146097 + doe as i64 - 719468
35    }
36
37    let days = days_from_civil(dt.year as i64, dt.month as i64, dt.day as i64);
38    let secs = days * 86400 + dt.hour as i64 * 3600 + dt.minute as i64 * 60 + dt.second as i64;
39    if secs >= 0 {
40        UNIX_EPOCH + Duration::from_secs(secs as u64)
41    } else {
42        UNIX_EPOCH
43    }
44}
45
46fn inode_to_file_attr(entry: &InodeEntry) -> FileAttr {
47    let uid = unsafe { libc::getuid() };
48    let gid = unsafe { libc::getgid() };
49    FileAttr {
50        ino: INodeNo(entry.inode),
51        size: entry.size,
52        blocks: entry.size.div_ceil(512),
53        atime: entry.atime,
54        mtime: entry.mtime,
55        ctime: entry.mtime,
56        crtime: entry.mtime,
57        kind: if entry.is_dir() {
58            FileType::Directory
59        } else {
60            FileType::RegularFile
61        },
62        perm: if entry.is_dir() { 0o755 } else { 0o644 },
63        nlink: if entry.is_dir() { 2 } else { 1 },
64        uid,
65        gid,
66        rdev: 0,
67        blksize: 4096,
68        flags: 0,
69    }
70}
71
72/// Helper to create an `Unpin` stream from a `Vec<u8>`.
73fn bytes_stream(
74    data: Vec<u8>,
75) -> futures::stream::Iter<std::vec::IntoIter<Result<Bytes, io::Error>>> {
76    let chunks = if data.is_empty() {
77        vec![Ok(Bytes::new())]
78    } else {
79        vec![Ok(Bytes::from(data))]
80    };
81    futures::stream::iter(chunks)
82}
83
84/// Read a file in 64KB chunks and return as a stream.
85fn file_stream(
86    mut file: std::fs::File,
87) -> futures::stream::Iter<std::vec::IntoIter<Result<Bytes, io::Error>>> {
88    use std::io::Read as _;
89    let mut chunks = Vec::new();
90    loop {
91        let mut buf = vec![0u8; 65536];
92        match file.read(&mut buf) {
93            Ok(0) => break,
94            Ok(n) => {
95                buf.truncate(n);
96                chunks.push(Ok(Bytes::from(buf)));
97            }
98            Err(e) => {
99                chunks.push(Err(e));
100                break;
101            }
102        }
103    }
104    futures::stream::iter(chunks)
105}
106
107/// Mutable state protected by `RefCell` so fuser's `&self` callbacks can mutate it.
108struct Inner {
109    storages: Vec<Storage>,
110    inodes: InodeTable,
111    write_buf: WriteBuffer,
112    read_cache: HashMap<u64, SparseCache>,
113    dirs_loaded: HashMap<u64, bool>,
114    fh_to_inode: HashMap<u64, u64>,
115}
116
117/// FUSE filesystem backed by an MTP device.
118pub struct MtpFs {
119    rt: tokio::runtime::Handle,
120    device: Mutex<MtpDevice>,
121    /// Clone of the device for event polling (avoids holding the device lock).
122    event_device: MtpDevice,
123    inner: Arc<Mutex<Inner>>,
124    next_fh: AtomicU64,
125    read_only: bool,
126    /// Counter incremented on every MTP partial-read fetch. Used by integration
127    /// tests to verify that the sparse cache prevents redundant fetches.
128    fetch_counter: Arc<AtomicU64>,
129}
130
131impl MtpFs {
132    pub fn new(device: MtpDevice, read_only: bool, rt: tokio::runtime::Handle) -> Self {
133        let event_device = device.clone();
134        Self {
135            rt,
136            device: Mutex::new(device),
137            event_device,
138            inner: Arc::new(Mutex::new(Inner {
139                storages: Vec::new(),
140                inodes: InodeTable::new(),
141                write_buf: WriteBuffer::new(),
142                read_cache: HashMap::new(),
143                dirs_loaded: HashMap::new(),
144                fh_to_inode: HashMap::new(),
145            })),
146            next_fh: AtomicU64::new(1),
147            read_only,
148            fetch_counter: Arc::new(AtomicU64::new(0)),
149        }
150    }
151
152    /// Returns a shared handle to the MTP fetch counter.
153    ///
154    /// The counter increments each time a partial-read operation is issued to
155    /// the device. Primarily used by integration tests to verify cache behavior.
156    #[allow(dead_code)] // used by integration tests via lib.rs, not by the bin
157    pub fn fetch_counter(&self) -> Arc<AtomicU64> {
158        Arc::clone(&self.fetch_counter)
159    }
160
161    fn alloc_fh(&self) -> u64 {
162        self.next_fh.fetch_add(1, Ordering::Relaxed)
163    }
164
165    /// Find the storage index that owns a given inode by walking up the tree.
166    fn find_storage_index(inner: &Inner, inode: u64) -> Option<usize> {
167        let mut current = inode;
168        loop {
169            let entry = inner.inodes.get(current)?;
170            if let InodeKind::Storage { storage_id } = &entry.kind {
171                return inner
172                    .storages
173                    .iter()
174                    .position(|s: &Storage| s.id() == *storage_id);
175            }
176            if current == entry.parent {
177                return None;
178            }
179            current = entry.parent;
180        }
181    }
182
183    /// Get the MTP parent handle for a given directory inode.
184    fn mtp_parent_handle(inner: &Inner, inode: u64) -> Option<Option<ObjectHandle>> {
185        let entry = inner.inodes.get(inode)?;
186        match &entry.kind {
187            InodeKind::Storage { .. } => Some(None),
188            InodeKind::Directory { handle } => Some(Some(*handle)),
189            _ => None,
190        }
191    }
192
193    /// Load children of a directory from MTP into the inode table.
194    fn load_dir(&self, inner: &mut Inner, parent_inode: u64) {
195        if inner.dirs_loaded.get(&parent_inode) == Some(&true) {
196            return;
197        }
198
199        if parent_inode == FUSE_ROOT_INODE {
200            inner.dirs_loaded.insert(parent_inode, true);
201            return;
202        }
203
204        let mtp_parent = match Self::mtp_parent_handle(inner, parent_inode) {
205            Some(p) => p,
206            None => return,
207        };
208
209        let storage_idx = match Self::find_storage_index(inner, parent_inode) {
210            Some(i) => i,
211            None => return,
212        };
213
214        let objects = match self
215            .rt
216            .block_on(inner.storages[storage_idx].list_objects(mtp_parent))
217        {
218            Ok(objs) => objs,
219            Err(e) => {
220                error!("Failed to list MTP objects: {e}");
221                return;
222            }
223        };
224
225        inner.inodes.clear_children(parent_inode);
226
227        for obj in objects {
228            let mtime = obj
229                .modified
230                .as_ref()
231                .map(mtp_datetime_to_system_time)
232                .unwrap_or(UNIX_EPOCH);
233            let is_folder = obj.is_folder();
234            inner.inodes.add_object(
235                parent_inode,
236                obj.handle,
237                obj.filename,
238                is_folder,
239                obj.size,
240                mtime,
241            );
242        }
243
244        inner.dirs_loaded.insert(parent_inode, true);
245    }
246
247    /// Flush a dirty write buffer to MTP.
248    ///
249    /// When the device supports rename, uses a safe upload-then-delete-then-rename
250    /// sequence to avoid data loss if the upload fails. Falls back to
251    /// delete-then-upload on devices without rename support.
252    fn flush_to_mtp(&self, inner: &mut Inner, fh: u64) {
253        let buf = match inner.write_buf.close(fh) {
254            Some(b) => b,
255            None => return,
256        };
257
258        if !buf.is_dirty() {
259            return;
260        }
261
262        let inode = buf.inode;
263        let mut file = buf.into_file();
264        if let Err(e) = file.seek(SeekFrom::Start(0)) {
265            error!("Flush: failed to rewind temp file: {e}");
266            return;
267        }
268        let file_len = file.seek(SeekFrom::End(0)).unwrap_or(0);
269        if let Err(e) = file.seek(SeekFrom::Start(0)) {
270            error!("Flush: failed to rewind temp file: {e}");
271            return;
272        }
273        let entry = match inner.inodes.get(inode) {
274            Some(e) => e.clone(),
275            None => {
276                error!("Flush: inode {inode} not found");
277                return;
278            }
279        };
280
281        let handle = match &entry.kind {
282            InodeKind::File { handle } => *handle,
283            _ => {
284                error!("Flush: inode {inode} is not a file");
285                return;
286            }
287        };
288
289        let storage_idx = match Self::find_storage_index(inner, inode) {
290            Some(i) => i,
291            None => {
292                error!("Flush: no storage for inode {inode}");
293                return;
294            }
295        };
296
297        let parent_handle = inner.inodes.get(entry.parent).and_then(|p| match &p.kind {
298            InodeKind::Storage { .. } => None,
299            InodeKind::Directory { handle } => Some(*handle),
300            _ => None,
301        });
302
303        let supports_rename = self.device.lock().unwrap().supports_rename();
304
305        if supports_rename {
306            self.flush_safe(
307                inner,
308                inode,
309                handle,
310                storage_idx,
311                parent_handle,
312                &entry,
313                file_len,
314                file,
315            );
316        } else {
317            warn!(
318                "Flush: device does not support rename, using delete-then-upload \
319                 (data loss possible if upload fails)"
320            );
321            self.flush_unsafe(
322                inner,
323                inode,
324                handle,
325                storage_idx,
326                parent_handle,
327                &entry,
328                file_len,
329                file,
330            );
331        }
332    }
333
334    /// Safe flush: upload with temp name, delete old, rename new.
335    #[allow(clippy::too_many_arguments)]
336    fn flush_safe(
337        &self,
338        inner: &mut Inner,
339        inode: u64,
340        old_handle: ObjectHandle,
341        storage_idx: usize,
342        parent_handle: Option<ObjectHandle>,
343        entry: &InodeEntry,
344        size: u64,
345        file: std::fs::File,
346    ) {
347        let storage = &inner.storages[storage_idx];
348        let temp_name = format!(".~tmp~{}", entry.name);
349
350        // Step 1: Upload new data with a temp name.
351        let info = NewObjectInfo::file(&temp_name, size);
352        let stream = file_stream(file);
353        let new_handle = match self
354            .rt
355            .block_on(storage.upload(parent_handle, info, stream))
356        {
357            Ok(h) => h,
358            Err(e) => {
359                error!("Flush: upload failed (original file untouched): {e}");
360                return;
361            }
362        };
363
364        // Step 2: Delete old object.
365        if let Err(e) = self.rt.block_on(storage.delete(old_handle)) {
366            error!("Flush: failed to delete old object (new data saved as '{temp_name}'): {e}");
367            if let Some(e) = inner.inodes.get_mut(inode) {
368                e.kind = InodeKind::File { handle: new_handle };
369                e.name = temp_name;
370                e.size = size;
371                e.mtime = SystemTime::now();
372            }
373            return;
374        }
375
376        // Step 3: Rename temp to original name.
377        if let Err(e) = self.rt.block_on(storage.rename(new_handle, &entry.name)) {
378            warn!(
379                "Flush: rename from '{temp_name}' to '{}' failed: {e}",
380                entry.name
381            );
382            if let Some(e) = inner.inodes.get_mut(inode) {
383                e.kind = InodeKind::File { handle: new_handle };
384                e.name = temp_name;
385                e.size = size;
386                e.mtime = SystemTime::now();
387            }
388            return;
389        }
390
391        if let Some(e) = inner.inodes.get_mut(inode) {
392            e.kind = InodeKind::File { handle: new_handle };
393            e.size = size;
394            e.mtime = SystemTime::now();
395        }
396    }
397
398    /// Unsafe flush: delete old object, then upload. Data is lost if upload fails.
399    #[allow(clippy::too_many_arguments)]
400    fn flush_unsafe(
401        &self,
402        inner: &mut Inner,
403        inode: u64,
404        old_handle: ObjectHandle,
405        storage_idx: usize,
406        parent_handle: Option<ObjectHandle>,
407        entry: &InodeEntry,
408        size: u64,
409        file: std::fs::File,
410    ) {
411        let storage = &inner.storages[storage_idx];
412
413        if let Err(e) = self.rt.block_on(storage.delete(old_handle)) {
414            error!("Flush: failed to delete old object: {e}");
415            return;
416        }
417
418        let info = NewObjectInfo::file(&entry.name, size);
419        let stream = file_stream(file);
420
421        match self
422            .rt
423            .block_on(storage.upload(parent_handle, info, stream))
424        {
425            Ok(new_handle) => {
426                if let Some(e) = inner.inodes.get_mut(inode) {
427                    e.kind = InodeKind::File { handle: new_handle };
428                    e.size = size;
429                    e.mtime = SystemTime::now();
430                }
431            }
432            Err(e) => {
433                error!("Flush: upload failed after delete (data lost): {e}");
434            }
435        }
436    }
437
438    pub fn mount_options(&self) -> Vec<MountOption> {
439        let mut opts = vec![
440            MountOption::FSName("mtp-mount".to_string()),
441            MountOption::Subtype("mtp".to_string()),
442            MountOption::DefaultPermissions,
443            MountOption::NoDev,
444            MountOption::NoSuid,
445        ];
446        if self.read_only {
447            opts.push(MountOption::RO);
448        } else {
449            opts.push(MountOption::RW);
450        }
451        opts
452    }
453
454    /// Background event loop that polls the device for MTP events and invalidates
455    /// cached directory listings when objects change on the device side.
456    async fn event_loop(device: MtpDevice, inner: Arc<Mutex<Inner>>) {
457        loop {
458            match tokio::time::timeout(Duration::from_millis(200), device.next_event()).await {
459                Ok(Ok(event)) => {
460                    Self::handle_event(&inner, &event);
461                }
462                Ok(Err(mtp_rs::Error::Disconnected)) => {
463                    debug!("Event loop: device disconnected");
464                    break;
465                }
466                Ok(Err(mtp_rs::Error::Timeout)) => continue,
467                Ok(Err(e)) => {
468                    warn!("Event loop error: {e}");
469                    break;
470                }
471                Err(_) => continue, // tokio timeout elapsed, loop again
472            }
473        }
474    }
475
476    /// Process a single device event by invalidating the relevant cache entries.
477    fn handle_event(inner: &Mutex<Inner>, event: &DeviceEvent) {
478        match event {
479            DeviceEvent::ObjectAdded { handle } => {
480                debug!("Event: object added {:?}", handle);
481                let mut inner = inner.lock().unwrap();
482                // The new object might be in any directory. If we can find its parent
483                // in the inode table (the parent dir was already cached), invalidate
484                // just that directory. Otherwise, invalidate all directories.
485                if let Some(parent_ino) = inner.inodes.find_parent_by_handle(*handle) {
486                    inner.dirs_loaded.remove(&parent_ino);
487                } else {
488                    Self::invalidate_all_dirs(&mut inner);
489                }
490            }
491            DeviceEvent::ObjectRemoved { handle } => {
492                debug!("Event: object removed {:?}", handle);
493                let mut inner = inner.lock().unwrap();
494                if let Some(parent_ino) = inner.inodes.find_parent_by_handle(*handle) {
495                    inner.dirs_loaded.remove(&parent_ino);
496                } else {
497                    Self::invalidate_all_dirs(&mut inner);
498                }
499            }
500            DeviceEvent::ObjectInfoChanged { handle } => {
501                debug!("Event: object info changed {:?}", handle);
502                let mut inner = inner.lock().unwrap();
503                // Invalidate the parent directory and clear any read cache for this file.
504                if let Some(parent_ino) = inner.inodes.find_parent_by_handle(*handle) {
505                    inner.dirs_loaded.remove(&parent_ino);
506                }
507                // Clear read cache entries for file handles pointing to this object.
508                let fhs_to_clear: Vec<u64> = inner
509                    .fh_to_inode
510                    .iter()
511                    .filter_map(|(&fh, &ino)| {
512                        inner.inodes.get(ino).and_then(|e| match &e.kind {
513                            InodeKind::File { handle: h } if *h == *handle => Some(fh),
514                            _ => None,
515                        })
516                    })
517                    .collect();
518                for fh in fhs_to_clear {
519                    inner.read_cache.remove(&fh);
520                }
521            }
522            DeviceEvent::StoreAdded { .. }
523            | DeviceEvent::StoreRemoved { .. }
524            | DeviceEvent::StorageInfoChanged { .. } => {
525                debug!("Event: storage change {:?}", event);
526                // Storage-level changes: invalidate everything.
527                let mut inner = inner.lock().unwrap();
528                Self::invalidate_all_dirs(&mut inner);
529            }
530            _ => {
531                debug!("Event: unhandled {:?}", event);
532            }
533        }
534    }
535
536    /// Mark all cached directories as stale so they're re-fetched on next access.
537    fn invalidate_all_dirs(inner: &mut Inner) {
538        inner.dirs_loaded.retain(|&k, _| k == FUSE_ROOT_INODE);
539    }
540}
541
542impl Filesystem for MtpFs {
543    fn init(&mut self, _req: &Request, _config: &mut KernelConfig) -> io::Result<()> {
544        let storages = self
545            .rt
546            .block_on(self.device.lock().unwrap().storages())
547            .map_err(|e: mtp_rs::Error| io::Error::other(e.to_string()))?;
548
549        let mut inner = self.inner.lock().unwrap();
550        for storage in &storages {
551            let storage: &Storage = storage;
552            let name = if storage.info().description.is_empty() {
553                format!("Storage_{}", storage.id().0)
554            } else {
555                storage.info().description.clone()
556            };
557            inner.inodes.add_storage(storage.id(), name);
558        }
559        inner.dirs_loaded.insert(FUSE_ROOT_INODE, true);
560        inner.storages = storages;
561        drop(inner);
562
563        // Spawn a background task that monitors device events and invalidates
564        // cached directory listings when objects are added, removed, or changed.
565        let event_device = self.event_device.clone();
566        let event_inner = Arc::clone(&self.inner);
567        self.rt.spawn(async move {
568            Self::event_loop(event_device, event_inner).await;
569        });
570
571        debug!(
572            "MtpFs initialized with {} storages + event monitor",
573            self.inner.lock().unwrap().storages.len()
574        );
575        Ok(())
576    }
577
578    fn lookup(&self, _req: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEntry) {
579        let parent_ino = parent.0;
580        let name_str = match name.to_str() {
581            Some(s) => s,
582            None => {
583                reply.error(Errno::ENOENT);
584                return;
585            }
586        };
587
588        let mut inner = self.inner.lock().unwrap();
589        self.load_dir(&mut inner, parent_ino);
590
591        match inner.inodes.lookup(parent_ino, name_str) {
592            Some(ino) => {
593                let entry = inner.inodes.get(ino).unwrap();
594                let attr = inode_to_file_attr(entry);
595                reply.entry(&TTL, &attr, Generation(0));
596            }
597            None => {
598                reply.error(Errno::ENOENT);
599            }
600        }
601    }
602
603    fn getattr(&self, _req: &Request, ino: INodeNo, _fh: Option<FileHandle>, reply: ReplyAttr) {
604        let inner = self.inner.lock().unwrap();
605        match inner.inodes.get(ino.0) {
606            Some(entry) => {
607                let mut attr = inode_to_file_attr(entry);
608                for (&fh, &inode) in &inner.fh_to_inode {
609                    if inode == ino.0 {
610                        if let Some(size) = inner.write_buf.size(fh) {
611                            attr.size = size;
612                            attr.blocks = size.div_ceil(512);
613                        }
614                        break;
615                    }
616                }
617                reply.attr(&TTL, &attr);
618            }
619            None => {
620                reply.error(Errno::ENOENT);
621            }
622        }
623    }
624
625    fn readdir(
626        &self,
627        _req: &Request,
628        ino: INodeNo,
629        _fh: FileHandle,
630        offset: u64,
631        mut reply: ReplyDirectory,
632    ) {
633        let ino_val = ino.0;
634
635        let mut inner = self.inner.lock().unwrap();
636        self.load_dir(&mut inner, ino_val);
637
638        let parent_ino = inner
639            .inodes
640            .get(ino_val)
641            .map(|e| e.parent)
642            .unwrap_or(FUSE_ROOT_INODE);
643
644        let mut entries: Vec<(u64, INodeNo, FileType, String)> = vec![
645            (1, INodeNo(ino_val), FileType::Directory, ".".to_string()),
646            (
647                2,
648                INodeNo(parent_ino),
649                FileType::Directory,
650                "..".to_string(),
651            ),
652        ];
653
654        let children = inner.inodes.children(ino_val);
655        for (i, child_ino) in children.iter().enumerate() {
656            if let Some(child) = inner.inodes.get(*child_ino) {
657                let kind = if child.is_dir() {
658                    FileType::Directory
659                } else {
660                    FileType::RegularFile
661                };
662                entries.push((i as u64 + 3, INodeNo(*child_ino), kind, child.name.clone()));
663            }
664        }
665
666        for (i, (off, ino, kind, name)) in entries.iter().enumerate() {
667            if i as u64 >= offset && reply.add(*ino, *off, *kind, name) {
668                break;
669            }
670        }
671        reply.ok();
672    }
673
674    fn open(&self, _req: &Request, ino: INodeNo, _flags: OpenFlags, reply: ReplyOpen) {
675        let mut inner = self.inner.lock().unwrap();
676        match inner.inodes.get(ino.0) {
677            Some(entry) if !entry.is_dir() => {
678                let fh = self.alloc_fh();
679                inner.fh_to_inode.insert(fh, ino.0);
680                reply.opened(FileHandle(fh), FopenFlags::empty());
681            }
682            Some(_) => {
683                reply.error(Errno::EISDIR);
684            }
685            None => {
686                reply.error(Errno::ENOENT);
687            }
688        }
689    }
690
691    fn read(
692        &self,
693        _req: &Request,
694        ino: INodeNo,
695        fh: FileHandle,
696        offset: u64,
697        size: u32,
698        _flags: OpenFlags,
699        _lock_owner: Option<LockOwner>,
700        reply: ReplyData,
701    ) {
702        let fh_val = fh.0;
703        let mut inner = self.inner.lock().unwrap();
704
705        // If there's a write buffer open for this fh, read from it.
706        if inner.write_buf.is_open(fh_val) {
707            match inner.write_buf.read(fh_val, offset as i64, size) {
708                Ok(data) => reply.data(&data),
709                Err(e) => {
710                    error!("Read from write buffer failed: {e}");
711                    reply.error(Errno::EIO);
712                }
713            }
714            return;
715        }
716
717        // Resolve the MTP object and its storage.
718        let entry = match inner.inodes.get(ino.0) {
719            Some(e) => e.clone(),
720            None => {
721                reply.error(Errno::ENOENT);
722                return;
723            }
724        };
725
726        let handle = match &entry.kind {
727            InodeKind::File { handle } => *handle,
728            _ => {
729                reply.error(Errno::EISDIR);
730                return;
731            }
732        };
733
734        let storage_idx = match Self::find_storage_index(&inner, ino.0) {
735            Some(i) => i,
736            None => {
737                reply.error(Errno::EIO);
738                return;
739            }
740        };
741
742        // Lazily create a sparse cache for this file handle.
743        use std::collections::hash_map::Entry;
744        if let Entry::Vacant(slot) = inner.read_cache.entry(fh_val) {
745            let cache = match SparseCache::new(entry.size) {
746                Ok(c) => c,
747                Err(e) => {
748                    error!("Failed to create sparse cache: {e}");
749                    reply.error(Errno::EIO);
750                    return;
751                }
752            };
753            slot.insert(cache);
754        }
755
756        // Figure out which byte ranges still need to be fetched from MTP.
757        let missing = {
758            let cache = inner.read_cache.get(&fh_val).unwrap();
759            cache.missing_ranges(offset, size as u64)
760        };
761
762        // Fetch missing ranges. Uses the 64-bit partial-read op to support
763        // offsets beyond 4 GB. Each USB transfer is capped at 1 MB to keep
764        // latency reasonable.
765        const CHUNK: u64 = 1024 * 1024;
766        for range in missing {
767            let mut cursor = range.start;
768            while cursor < range.end {
769                let chunk_size = (range.end - cursor).min(CHUNK) as u32;
770                self.fetch_counter.fetch_add(1, Ordering::Relaxed);
771                let bytes = match self.rt.block_on(
772                    inner.storages[storage_idx].download_partial_64(handle, cursor, chunk_size),
773                ) {
774                    Ok(b) => b,
775                    Err(e) => {
776                        error!("MTP download_partial_64 failed at offset {cursor}: {e}");
777                        reply.error(Errno::EIO);
778                        return;
779                    }
780                };
781                let bytes_len = bytes.len() as u64;
782                let cache = inner.read_cache.get_mut(&fh_val).unwrap();
783                if let Err(e) = cache.write_at(cursor, &bytes) {
784                    error!("Sparse cache write failed: {e}");
785                    reply.error(Errno::EIO);
786                    return;
787                }
788                // Short read from device — the object is smaller than reported;
789                // stop fetching to avoid an infinite loop.
790                if bytes_len == 0 {
791                    break;
792                }
793                cursor += bytes_len;
794            }
795        }
796
797        // Serve the requested slice from the cache.
798        let cache = inner.read_cache.get_mut(&fh_val).unwrap();
799        match cache.read_at(offset, size as u64) {
800            Ok(buf) => reply.data(&buf),
801            Err(e) => {
802                error!("Sparse cache read failed: {e}");
803                reply.error(Errno::EIO);
804            }
805        }
806    }
807
808    fn release(
809        &self,
810        _req: &Request,
811        _ino: INodeNo,
812        fh: FileHandle,
813        _flags: OpenFlags,
814        _lock_owner: Option<LockOwner>,
815        _flush: bool,
816        reply: ReplyEmpty,
817    ) {
818        let fh_val = fh.0;
819        let mut inner = self.inner.lock().unwrap();
820
821        if inner.write_buf.is_open(fh_val) {
822            self.flush_to_mtp(&mut inner, fh_val);
823        }
824
825        inner.read_cache.remove(&fh_val);
826        inner.fh_to_inode.remove(&fh_val);
827        reply.ok();
828    }
829
830    fn write(
831        &self,
832        _req: &Request,
833        ino: INodeNo,
834        fh: FileHandle,
835        offset: u64,
836        data: &[u8],
837        _write_flags: WriteFlags,
838        _flags: OpenFlags,
839        _lock_owner: Option<LockOwner>,
840        reply: ReplyWrite,
841    ) {
842        if self.read_only {
843            reply.error(Errno::EROFS);
844            return;
845        }
846
847        let fh_val = fh.0;
848        let mut inner = self.inner.lock().unwrap();
849
850        if !inner.write_buf.is_open(fh_val) {
851            let original_size = inner.inodes.get(ino.0).map(|e| e.size).unwrap_or(0);
852            if let Err(e) = inner.write_buf.open(fh_val, ino.0, original_size) {
853                error!("Failed to open write buffer: {e}");
854                reply.error(Errno::EIO);
855                return;
856            }
857        }
858
859        match inner.write_buf.write(fh_val, offset as i64, data) {
860            Ok(written) => reply.written(written),
861            Err(e) => {
862                error!("Write failed: {e}");
863                reply.error(Errno::EIO);
864            }
865        }
866    }
867
868    fn create(
869        &self,
870        _req: &Request,
871        parent: INodeNo,
872        name: &OsStr,
873        _mode: u32,
874        _umask: u32,
875        _flags: i32,
876        reply: ReplyCreate,
877    ) {
878        if self.read_only {
879            reply.error(Errno::EROFS);
880            return;
881        }
882
883        let name_str = match name.to_str() {
884            Some(s) => s,
885            None => {
886                reply.error(Errno::EINVAL);
887                return;
888            }
889        };
890
891        let parent_ino = parent.0;
892        let mut inner = self.inner.lock().unwrap();
893
894        let storage_idx = match Self::find_storage_index(&inner, parent_ino) {
895            Some(i) => i,
896            None => {
897                reply.error(Errno::EIO);
898                return;
899            }
900        };
901
902        let mtp_parent = match Self::mtp_parent_handle(&inner, parent_ino) {
903            Some(p) => p,
904            None => {
905                reply.error(Errno::ENOTDIR);
906                return;
907            }
908        };
909
910        let info = NewObjectInfo::file(name_str, 0);
911        let stream = bytes_stream(Vec::new());
912
913        let handle = match self
914            .rt
915            .block_on(inner.storages[storage_idx].upload(mtp_parent, info, stream))
916        {
917            Ok(h) => h,
918            Err(e) => {
919                error!("MTP create failed: {e}");
920                reply.error(Errno::EIO);
921                return;
922            }
923        };
924
925        let now = SystemTime::now();
926        let ino = inner
927            .inodes
928            .add_object(parent_ino, handle, name_str.to_string(), false, 0, now);
929
930        let fh = self.alloc_fh();
931        inner.fh_to_inode.insert(fh, ino);
932        if let Err(e) = inner.write_buf.open(fh, ino, 0) {
933            error!("Failed to open write buffer: {e}");
934            reply.error(Errno::EIO);
935            return;
936        }
937
938        let entry = inner.inodes.get(ino).unwrap();
939        let attr = inode_to_file_attr(entry);
940        reply.created(
941            &TTL,
942            &attr,
943            Generation(0),
944            FileHandle(fh),
945            FopenFlags::empty(),
946        );
947    }
948
949    fn mkdir(
950        &self,
951        _req: &Request,
952        parent: INodeNo,
953        name: &OsStr,
954        _mode: u32,
955        _umask: u32,
956        reply: ReplyEntry,
957    ) {
958        if self.read_only {
959            reply.error(Errno::EROFS);
960            return;
961        }
962
963        let name_str = match name.to_str() {
964            Some(s) => s,
965            None => {
966                reply.error(Errno::EINVAL);
967                return;
968            }
969        };
970
971        let parent_ino = parent.0;
972        let mut inner = self.inner.lock().unwrap();
973
974        let storage_idx = match Self::find_storage_index(&inner, parent_ino) {
975            Some(i) => i,
976            None => {
977                reply.error(Errno::EIO);
978                return;
979            }
980        };
981
982        let mtp_parent = match Self::mtp_parent_handle(&inner, parent_ino) {
983            Some(p) => p,
984            None => {
985                reply.error(Errno::ENOTDIR);
986                return;
987            }
988        };
989
990        let handle = match self
991            .rt
992            .block_on(inner.storages[storage_idx].create_folder(mtp_parent, name_str))
993        {
994            Ok(h) => h,
995            Err(e) => {
996                error!("MTP mkdir failed: {e}");
997                reply.error(Errno::EIO);
998                return;
999            }
1000        };
1001
1002        let now = SystemTime::now();
1003        let ino = inner
1004            .inodes
1005            .add_object(parent_ino, handle, name_str.to_string(), true, 0, now);
1006
1007        let entry = inner.inodes.get(ino).unwrap();
1008        let attr = inode_to_file_attr(entry);
1009        reply.entry(&TTL, &attr, Generation(0));
1010    }
1011
1012    fn unlink(&self, _req: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEmpty) {
1013        if self.read_only {
1014            reply.error(Errno::EROFS);
1015            return;
1016        }
1017
1018        let name_str = match name.to_str() {
1019            Some(s) => s,
1020            None => {
1021                reply.error(Errno::ENOENT);
1022                return;
1023            }
1024        };
1025
1026        let parent_ino = parent.0;
1027        let mut inner = self.inner.lock().unwrap();
1028
1029        let child_ino = match inner.inodes.lookup(parent_ino, name_str) {
1030            Some(i) => i,
1031            None => {
1032                reply.error(Errno::ENOENT);
1033                return;
1034            }
1035        };
1036
1037        let handle = match inner.inodes.get(child_ino).and_then(|e| match &e.kind {
1038            InodeKind::File { handle } => Some(*handle),
1039            _ => None,
1040        }) {
1041            Some(h) => h,
1042            None => {
1043                reply.error(Errno::EISDIR);
1044                return;
1045            }
1046        };
1047
1048        let storage_idx = match Self::find_storage_index(&inner, child_ino) {
1049            Some(i) => i,
1050            None => {
1051                reply.error(Errno::EIO);
1052                return;
1053            }
1054        };
1055
1056        if let Err(e) = self.rt.block_on(inner.storages[storage_idx].delete(handle)) {
1057            error!("MTP delete failed: {e}");
1058            reply.error(Errno::EIO);
1059            return;
1060        }
1061
1062        inner.inodes.remove(child_ino);
1063        reply.ok();
1064    }
1065
1066    fn rmdir(&self, _req: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEmpty) {
1067        if self.read_only {
1068            reply.error(Errno::EROFS);
1069            return;
1070        }
1071
1072        let name_str = match name.to_str() {
1073            Some(s) => s,
1074            None => {
1075                reply.error(Errno::ENOENT);
1076                return;
1077            }
1078        };
1079
1080        let parent_ino = parent.0;
1081        let mut inner = self.inner.lock().unwrap();
1082
1083        let child_ino = match inner.inodes.lookup(parent_ino, name_str) {
1084            Some(i) => i,
1085            None => {
1086                reply.error(Errno::ENOENT);
1087                return;
1088            }
1089        };
1090
1091        let handle = match inner.inodes.get(child_ino).and_then(|e| match &e.kind {
1092            InodeKind::Directory { handle } => Some(*handle),
1093            _ => None,
1094        }) {
1095            Some(h) => h,
1096            None => {
1097                reply.error(Errno::ENOTDIR);
1098                return;
1099            }
1100        };
1101
1102        let storage_idx = match Self::find_storage_index(&inner, child_ino) {
1103            Some(i) => i,
1104            None => {
1105                reply.error(Errno::EIO);
1106                return;
1107            }
1108        };
1109
1110        if let Err(e) = self.rt.block_on(inner.storages[storage_idx].delete(handle)) {
1111            error!("MTP rmdir failed: {e}");
1112            reply.error(Errno::EIO);
1113            return;
1114        }
1115
1116        inner.inodes.remove(child_ino);
1117        reply.ok();
1118    }
1119
1120    fn rename(
1121        &self,
1122        _req: &Request,
1123        parent: INodeNo,
1124        name: &OsStr,
1125        newparent: INodeNo,
1126        newname: &OsStr,
1127        _flags: RenameFlags,
1128        reply: ReplyEmpty,
1129    ) {
1130        if self.read_only {
1131            reply.error(Errno::EROFS);
1132            return;
1133        }
1134
1135        let name_str = match name.to_str() {
1136            Some(s) => s,
1137            None => {
1138                reply.error(Errno::ENOENT);
1139                return;
1140            }
1141        };
1142        let newname_str = match newname.to_str() {
1143            Some(s) => s,
1144            None => {
1145                reply.error(Errno::EINVAL);
1146                return;
1147            }
1148        };
1149
1150        let parent_ino = parent.0;
1151        let newparent_ino = newparent.0;
1152        let mut inner = self.inner.lock().unwrap();
1153
1154        let child_ino = match inner.inodes.lookup(parent_ino, name_str) {
1155            Some(i) => i,
1156            None => {
1157                reply.error(Errno::ENOENT);
1158                return;
1159            }
1160        };
1161
1162        let handle = match inner.inodes.get(child_ino).and_then(|e| match &e.kind {
1163            InodeKind::File { handle } | InodeKind::Directory { handle } => Some(*handle),
1164            _ => None,
1165        }) {
1166            Some(h) => h,
1167            None => {
1168                reply.error(Errno::EINVAL);
1169                return;
1170            }
1171        };
1172
1173        let storage_idx = match Self::find_storage_index(&inner, child_ino) {
1174            Some(i) => i,
1175            None => {
1176                reply.error(Errno::EIO);
1177                return;
1178            }
1179        };
1180
1181        if name_str != newname_str {
1182            if let Err(e) = self
1183                .rt
1184                .block_on(inner.storages[storage_idx].rename(handle, newname_str))
1185            {
1186                error!("MTP rename failed: {e}");
1187                reply.error(Errno::EIO);
1188                return;
1189            }
1190        }
1191
1192        if parent_ino != newparent_ino {
1193            let new_mtp_parent = match Self::mtp_parent_handle(&inner, newparent_ino) {
1194                Some(Some(h)) => h,
1195                Some(None) => ObjectHandle::ROOT,
1196                None => {
1197                    reply.error(Errno::ENOTDIR);
1198                    return;
1199                }
1200            };
1201
1202            if let Err(e) = self.rt.block_on(inner.storages[storage_idx].move_object(
1203                handle,
1204                new_mtp_parent,
1205                None,
1206            )) {
1207                error!("MTP move failed: {e}");
1208                reply.error(Errno::EIO);
1209                return;
1210            }
1211        }
1212
1213        inner
1214            .inodes
1215            .rename(child_ino, newparent_ino, newname_str.to_string());
1216        reply.ok();
1217    }
1218
1219    fn setattr(
1220        &self,
1221        _req: &Request,
1222        ino: INodeNo,
1223        _mode: Option<u32>,
1224        _uid: Option<u32>,
1225        _gid: Option<u32>,
1226        size: Option<u64>,
1227        _atime: Option<TimeOrNow>,
1228        _mtime: Option<TimeOrNow>,
1229        _ctime: Option<SystemTime>,
1230        fh: Option<FileHandle>,
1231        _crtime: Option<SystemTime>,
1232        _chgtime: Option<SystemTime>,
1233        _bkuptime: Option<SystemTime>,
1234        _flags: Option<BsdFileFlags>,
1235        reply: ReplyAttr,
1236    ) {
1237        if let Some(new_size) = size {
1238            if self.read_only {
1239                reply.error(Errno::EROFS);
1240                return;
1241            }
1242
1243            if let Some(fh) = fh {
1244                let fh_val = fh.0;
1245                let mut inner = self.inner.lock().unwrap();
1246
1247                if !inner.write_buf.is_open(fh_val) {
1248                    let original_size = inner.inodes.get(ino.0).map(|e| e.size).unwrap_or(0);
1249                    if let Err(e) = inner.write_buf.open(fh_val, ino.0, original_size) {
1250                        error!("Failed to open write buffer: {e}");
1251                        reply.error(Errno::EIO);
1252                        return;
1253                    }
1254                }
1255
1256                if new_size == 0 {
1257                    inner.write_buf.close(fh_val);
1258                    if let Err(e) = inner.write_buf.open(fh_val, ino.0, 0) {
1259                        error!("Failed to open write buffer: {e}");
1260                        reply.error(Errno::EIO);
1261                        return;
1262                    }
1263                }
1264            }
1265        }
1266
1267        let inner = self.inner.lock().unwrap();
1268        match inner.inodes.get(ino.0) {
1269            Some(entry) => {
1270                let mut attr = inode_to_file_attr(entry);
1271                if let Some(new_size) = size {
1272                    attr.size = new_size;
1273                    attr.blocks = new_size.div_ceil(512);
1274                }
1275                reply.attr(&TTL, &attr);
1276            }
1277            None => {
1278                reply.error(Errno::ENOENT);
1279            }
1280        }
1281    }
1282
1283    fn statfs(&self, _req: &Request, _ino: INodeNo, reply: ReplyStatfs) {
1284        let inner = self.inner.lock().unwrap();
1285        let block_size: u64 = 4096;
1286
1287        let mut total_bytes: u64 = 0;
1288        let mut free_bytes: u64 = 0;
1289        for storage in &inner.storages {
1290            total_bytes = total_bytes.saturating_add(storage.info().max_capacity);
1291            free_bytes = free_bytes.saturating_add(storage.info().free_space_bytes);
1292        }
1293
1294        let blocks = total_bytes / block_size;
1295        let bfree = free_bytes / block_size;
1296
1297        reply.statfs(blocks, bfree, bfree, 0, 0, block_size as u32, 255, 0);
1298    }
1299
1300    fn opendir(&self, _req: &Request, ino: INodeNo, _flags: OpenFlags, reply: ReplyOpen) {
1301        let mut inner = self.inner.lock().unwrap();
1302        match inner.inodes.get(ino.0) {
1303            Some(entry) if entry.is_dir() => {
1304                let fh = self.alloc_fh();
1305                inner.dirs_loaded.remove(&ino.0);
1306                reply.opened(FileHandle(fh), FopenFlags::empty());
1307            }
1308            Some(_) => {
1309                reply.error(Errno::ENOTDIR);
1310            }
1311            None => {
1312                reply.error(Errno::ENOENT);
1313            }
1314        }
1315    }
1316
1317    fn releasedir(
1318        &self,
1319        _req: &Request,
1320        _ino: INodeNo,
1321        _fh: FileHandle,
1322        _flags: OpenFlags,
1323        reply: ReplyEmpty,
1324    ) {
1325        reply.ok();
1326    }
1327}