Skip to main content

mtp_mount/
fs.rs

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