rencfs/
encryptedfs.rs

1use argon2::password_hash::rand_core::RngCore;
2use async_trait::async_trait;
3use futures_util::TryStreamExt;
4use lru::LruCache;
5use num_format::{Locale, ToFormattedString};
6use serde::{Deserialize, Serialize};
7use shush_rs::{ExposeSecret, SecretBox, SecretString, SecretVec};
8use std::backtrace::Backtrace;
9use std::collections::{HashMap, HashSet, VecDeque};
10use std::fmt::Debug;
11use std::fs::{DirEntry, File, OpenOptions, ReadDir};
12use std::io::{Read, Seek, SeekFrom, Write};
13use std::num::{NonZeroUsize, ParseIntError};
14use std::path::{Path, PathBuf};
15use std::str::FromStr;
16use std::sync::atomic::{AtomicU64, Ordering};
17use std::sync::{Arc, LazyLock, Weak};
18use std::time::{Duration, SystemTime};
19use std::{fs, io};
20use thiserror::Error;
21use tokio::runtime::Runtime;
22use tokio::sync::{Mutex, RwLock};
23use tokio::task::{JoinError, JoinSet};
24use tokio_stream::wrappers::ReadDirStream;
25use tracing::{debug, error, info, instrument, warn, Level};
26
27use crate::arc_hashmap::ArcHashMap;
28use crate::crypto::read::{CryptoRead, CryptoReadSeek};
29use crate::crypto::write::{CryptoInnerWriter, CryptoWrite, CryptoWriteSeek};
30use crate::crypto::Cipher;
31use crate::expire_value::{ExpireValue, ValueProvider};
32use crate::{crypto, fs_util, stream_util};
33use bon::bon;
34
35mod bench;
36#[cfg(test)]
37mod test;
38
39pub(crate) const INODES_DIR: &str = "inodes";
40pub(crate) const CONTENTS_DIR: &str = "contents";
41pub(crate) const SECURITY_DIR: &str = "security";
42pub(crate) const KEY_ENC_FILENAME: &str = "key.enc";
43pub(crate) const KEY_SALT_FILENAME: &str = "key.salt";
44
45pub(crate) const LS_DIR: &str = "ls";
46pub(crate) const HASH_DIR: &str = "hash";
47
48pub(crate) const ROOT_INODE: u64 = 1;
49
50fn spawn_runtime() -> Runtime {
51    let runtime = tokio::runtime::Builder::new_multi_thread()
52        .enable_all()
53        .build()
54        .unwrap();
55    runtime
56}
57
58static DIR_ENTRIES_RT: LazyLock<Runtime> = LazyLock::new(spawn_runtime);
59static NOD_RT: LazyLock<Runtime> = LazyLock::new(spawn_runtime);
60
61/// File attributes.
62#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
63pub struct FileAttr {
64    /// Inode number
65    pub ino: u64,
66    /// Size in bytes
67    pub size: u64,
68    /// Size in blocks
69    pub blocks: u64,
70    /// Time of last access
71    pub atime: SystemTime,
72    /// Time of last modification
73    pub mtime: SystemTime,
74    /// Time of last change
75    pub ctime: SystemTime,
76    /// Time of creation (macOS only)
77    pub crtime: SystemTime,
78    /// Kind of file (directory, file, pipe, etc.)
79    pub kind: FileType,
80    /// Permissions
81    pub perm: u16,
82    /// Number of hard links
83    pub nlink: u32,
84    /// User id
85    pub uid: u32,
86    /// Group id
87    pub gid: u32,
88    /// Rdev
89    pub rdev: u32,
90    /// Block size
91    pub blksize: u32,
92    /// Flags (macOS only, see chflags(2))
93    pub flags: u32,
94}
95
96/// File types.
97#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
98pub enum FileType {
99    // /// Named pipe (S_IFIFO)
100    // NamedPipe,
101    // /// Character device (S_IFCHR)
102    // CharDevice,
103    // /// Block device (S_IFBLK)
104    // BlockDevice,
105    /// Directory (`S_IFDIR`)
106    Directory,
107    /// Regular file (`S_IFREG`)
108    RegularFile,
109    // /// Symbolic link (S_IFLNK)
110    // Symlink,
111    // /// Unix domain socket (S_IFSOCK)
112    // Socket,
113}
114
115#[derive(Debug, Clone, Copy, Default)]
116pub struct SetFileAttr {
117    /// Size in bytes
118    pub size: Option<u64>,
119    /// Time of last access
120    pub atime: Option<SystemTime>,
121    /// Time of last modification
122    pub mtime: Option<SystemTime>,
123    /// Time of last change
124    pub ctime: Option<SystemTime>,
125    /// Time of creation (macOS only)
126    pub crtime: Option<SystemTime>,
127    /// Permissions
128    pub perm: Option<u16>,
129    /// User id
130    pub uid: Option<u32>,
131    /// Group id
132    pub gid: Option<u32>,
133    /// Rdev
134    pub rdev: Option<u32>,
135    /// Flags (macOS only, see chflags(2))
136    pub flags: Option<u32>,
137}
138
139impl SetFileAttr {
140    #[must_use]
141    pub const fn with_size(mut self, size: u64) -> Self {
142        self.size = Some(size);
143        self
144    }
145
146    #[must_use]
147    pub const fn with_atime(mut self, atime: SystemTime) -> Self {
148        self.atime = Some(atime);
149        self
150    }
151
152    #[must_use]
153    pub const fn with_mtime(mut self, mtime: SystemTime) -> Self {
154        self.mtime = Some(mtime);
155        self
156    }
157
158    #[must_use]
159    pub const fn with_ctime(mut self, ctime: SystemTime) -> Self {
160        self.ctime = Some(ctime);
161        self
162    }
163
164    #[must_use]
165    pub const fn with_crtime(mut self, crtime: SystemTime) -> Self {
166        self.crtime = Some(crtime);
167        self
168    }
169
170    #[must_use]
171    pub const fn with_perm(mut self, perm: u16) -> Self {
172        self.perm = Some(perm);
173        self
174    }
175
176    #[must_use]
177    pub const fn with_uid(mut self, uid: u32) -> Self {
178        self.uid = Some(uid);
179        self
180    }
181
182    #[must_use]
183    pub const fn with_gid(mut self, gid: u32) -> Self {
184        self.gid = Some(gid);
185        self
186    }
187
188    #[must_use]
189    pub const fn with_rdev(mut self, rdev: u32) -> Self {
190        self.rdev = Some(rdev);
191        self
192    }
193
194    #[must_use]
195    pub const fn with_flags(mut self, flags: u32) -> Self {
196        self.rdev = Some(flags);
197        self
198    }
199}
200
201#[derive(Debug, Clone)]
202pub struct CreateFileAttr {
203    /// Kind of file (directory, file, pipe, etc.)
204    pub kind: FileType,
205    /// Permissions
206    pub perm: u16,
207    /// User id
208    pub uid: u32,
209    /// Group id
210    pub gid: u32,
211    /// Rdev
212    pub rdev: u32,
213    /// Flags (macOS only, see chflags(2))
214    pub flags: u32,
215}
216
217impl From<CreateFileAttr> for FileAttr {
218    fn from(value: CreateFileAttr) -> Self {
219        let now = SystemTime::now();
220        Self {
221            ino: 0,
222            size: 0,
223            blocks: 0,
224            atime: now,
225            mtime: now,
226            ctime: now,
227            crtime: now,
228            kind: value.kind,
229            perm: value.perm,
230            nlink: if value.kind == FileType::Directory {
231                2
232            } else {
233                1
234            },
235            uid: value.uid,
236            gid: value.gid,
237            rdev: value.rdev,
238            blksize: 0,
239            flags: value.flags,
240        }
241    }
242}
243
244#[derive(Error, Debug)]
245pub enum FsError {
246    #[error("IO error: {source}")]
247    Io {
248        #[from]
249        source: io::Error,
250        backtrace: Backtrace,
251    },
252    #[error("serialize error: {source}")]
253    SerializeError {
254        #[from]
255        source: bincode::Error,
256        backtrace: Backtrace,
257    },
258    #[error("item not found: {0}")]
259    NotFound(&'static str),
260    #[error("inode not found")]
261    InodeNotFound,
262    #[error("invalid input")]
263    InvalidInput(&'static str),
264    #[error("invalid node type")]
265    InvalidInodeType,
266    #[error("invalid file handle")]
267    InvalidFileHandle,
268    #[error("already exists")]
269    AlreadyExists,
270    #[error("already open for write")]
271    AlreadyOpenForWrite,
272    #[error("not empty")]
273    NotEmpty,
274    #[error("other: {0}")]
275    Other(&'static str),
276    #[error("invalid password")]
277    InvalidPassword,
278    #[error("invalid structure of data directory")]
279    InvalidDataDirStructure,
280    #[error("crypto error: {source}")]
281    Crypto {
282        #[from]
283        source: crypto::Error,
284        backtrace: Backtrace,
285    },
286    #[error("keyring error: {source}")]
287    Keyring {
288        #[from]
289        source: keyring::Error,
290        backtrace: Backtrace,
291    },
292    #[error("parse int error: {source}")]
293    ParseIntError {
294        #[from]
295        source: ParseIntError,
296        backtrace: Backtrace,
297    },
298    #[error("tokio join error: {source}")]
299    JoinError {
300        #[from]
301        source: JoinError,
302        backtrace: Backtrace,
303    },
304    #[error("max filesize exceeded, max allowed {0}")]
305    MaxFilesizeExceeded(usize),
306    #[error("Read only mode is active.")]
307    ReadOnly,
308}
309
310#[derive(Debug, Clone)]
311struct TimesAndSizeFileAttr {
312    atime: SystemTime,
313    mtime: SystemTime,
314    ctime: SystemTime,
315    crtime: SystemTime,
316    size: u64,
317}
318
319impl TimesAndSizeFileAttr {
320    #[allow(dead_code)]
321    const fn new(
322        atime: SystemTime,
323        mtime: SystemTime,
324        ctime: SystemTime,
325        crtime: SystemTime,
326        size: u64,
327    ) -> Self {
328        Self {
329            atime,
330            mtime,
331            ctime,
332            crtime,
333            size,
334        }
335    }
336}
337
338impl From<FileAttr> for TimesAndSizeFileAttr {
339    fn from(value: FileAttr) -> Self {
340        Self {
341            atime: value.atime,
342            mtime: value.mtime,
343            ctime: value.ctime,
344            crtime: value.crtime,
345            size: value.size,
346        }
347    }
348}
349
350impl From<TimesAndSizeFileAttr> for SetFileAttr {
351    fn from(value: TimesAndSizeFileAttr) -> Self {
352        Self::default()
353            .with_atime(value.atime)
354            .with_mtime(value.mtime)
355            .with_ctime(value.ctime)
356            .with_crtime(value.crtime)
357            .with_size(value.size)
358    }
359}
360
361#[derive(Debug, Clone)]
362struct TimesFileAttr {
363    atime: SystemTime,
364    mtime: SystemTime,
365    ctime: SystemTime,
366    crtime: SystemTime,
367}
368
369impl TimesFileAttr {
370    #[allow(dead_code)]
371    const fn new(
372        atime: SystemTime,
373        mtime: SystemTime,
374        ctime: SystemTime,
375        crtime: SystemTime,
376    ) -> Self {
377        Self {
378            atime,
379            mtime,
380            ctime,
381            crtime,
382        }
383    }
384}
385
386impl From<FileAttr> for TimesFileAttr {
387    fn from(value: FileAttr) -> Self {
388        Self {
389            atime: value.atime,
390            mtime: value.mtime,
391            ctime: value.ctime,
392            crtime: value.crtime,
393        }
394    }
395}
396
397impl From<TimesFileAttr> for SetFileAttr {
398    fn from(value: TimesFileAttr) -> Self {
399        Self::default()
400            .with_atime(value.atime)
401            .with_mtime(value.mtime)
402            .with_ctime(value.ctime)
403            .with_crtime(value.crtime)
404    }
405}
406
407#[derive(Debug, Clone)]
408pub struct DirectoryEntry {
409    pub ino: u64,
410    pub name: SecretString,
411    pub kind: FileType,
412}
413
414impl PartialEq for DirectoryEntry {
415    fn eq(&self, other: &Self) -> bool {
416        self.ino == other.ino
417            && self.name.expose_secret() == other.name.expose_secret()
418            && self.kind == other.kind
419    }
420}
421
422/// Like [`DirectoryEntry`] but with [`FileAttr`].
423#[derive(Debug)]
424pub struct DirectoryEntryPlus {
425    pub ino: u64,
426    pub name: SecretString,
427    pub kind: FileType,
428    pub attr: FileAttr,
429}
430
431impl PartialEq for DirectoryEntryPlus {
432    fn eq(&self, other: &Self) -> bool {
433        self.ino == other.ino
434            && self.name.expose_secret() == other.name.expose_secret()
435            && self.kind == other.kind
436            && self.attr == other.attr
437    }
438}
439
440pub type FsResult<T> = Result<T, FsError>;
441
442pub struct DirectoryEntryIterator(VecDeque<FsResult<DirectoryEntry>>);
443
444impl Iterator for DirectoryEntryIterator {
445    type Item = FsResult<DirectoryEntry>;
446
447    fn next(&mut self) -> Option<Self::Item> {
448        self.0.pop_front()
449    }
450}
451
452pub struct DirectoryEntryPlusIterator(VecDeque<FsResult<DirectoryEntryPlus>>);
453
454impl Iterator for DirectoryEntryPlusIterator {
455    type Item = FsResult<DirectoryEntryPlus>;
456
457    #[instrument(name = "DirectoryEntryPlusIterator::next", skip(self))]
458    fn next(&mut self) -> Option<Self::Item> {
459        self.0.pop_front()
460    }
461}
462
463struct ReadHandleContext {
464    ino: u64,
465    attr: TimesFileAttr,
466    reader: Option<Box<dyn CryptoReadSeek<File>>>,
467}
468
469enum ReadHandleContextOperation {
470    Create { ino: u64 },
471}
472
473impl ReadHandleContextOperation {
474    const fn get_ino(&self) -> u64 {
475        match *self {
476            Self::Create { ino, .. } => ino,
477        }
478    }
479}
480
481enum WriteHandleContextOperation {
482    Create { ino: u64 },
483}
484
485impl WriteHandleContextOperation {
486    const fn get_ino(&self) -> u64 {
487        match *self {
488            Self::Create { ino, .. } => ino,
489        }
490    }
491}
492
493struct WriteHandleContext {
494    ino: u64,
495    attr: TimesAndSizeFileAttr,
496    writer: Option<Box<dyn CryptoWriteSeek<File>>>,
497}
498
499struct KeyProvider {
500    key_path: PathBuf,
501    salt_path: PathBuf,
502    password_provider: Box<dyn PasswordProvider>,
503    cipher: Cipher,
504}
505
506#[async_trait]
507impl ValueProvider<SecretVec<u8>, FsError> for KeyProvider {
508    async fn provide(&self) -> Result<SecretVec<u8>, FsError> {
509        let password = self
510            .password_provider
511            .get_password()
512            .ok_or(FsError::InvalidPassword)?;
513        read_or_create_key(&self.key_path, &self.salt_path, &password, self.cipher)
514    }
515}
516
517pub trait PasswordProvider: Send + Sync + 'static {
518    fn get_password(&self) -> Option<SecretString>;
519}
520
521struct DirEntryNameCacheProvider {}
522#[async_trait]
523impl ValueProvider<Mutex<LruCache<String, SecretString>>, FsError> for DirEntryNameCacheProvider {
524    async fn provide(&self) -> Result<Mutex<LruCache<String, SecretString>>, FsError> {
525        Ok(Mutex::new(LruCache::new(NonZeroUsize::new(2000).unwrap())))
526    }
527}
528
529struct DirEntryMetaCacheProvider {}
530#[async_trait]
531impl ValueProvider<Mutex<DirEntryMetaCache>, FsError> for DirEntryMetaCacheProvider {
532    async fn provide(&self) -> Result<Mutex<DirEntryMetaCache>, FsError> {
533        Ok(Mutex::new(LruCache::new(NonZeroUsize::new(2000).unwrap())))
534    }
535}
536
537struct AttrCacheProvider {}
538#[async_trait]
539impl ValueProvider<RwLock<LruCache<u64, FileAttr>>, FsError> for AttrCacheProvider {
540    async fn provide(&self) -> Result<RwLock<LruCache<u64, FileAttr>>, FsError> {
541        Ok(RwLock::new(LruCache::new(NonZeroUsize::new(2000).unwrap())))
542    }
543}
544
545type DirEntryMetaCache = LruCache<String, (u64, FileType)>;
546
547/// Encrypted FS that stores encrypted files in a dedicated directory with a specific structure based on `inode`.
548pub struct EncryptedFs {
549    pub(crate) data_dir: PathBuf,
550    write_handles: RwLock<HashMap<u64, Mutex<WriteHandleContext>>>,
551    read_handles: RwLock<HashMap<u64, Mutex<ReadHandleContext>>>,
552    current_handle: AtomicU64,
553    cipher: Cipher,
554    // (ino, fh)
555    opened_files_for_read: RwLock<HashMap<u64, HashSet<u64>>>,
556    opened_files_for_write: RwLock<HashMap<u64, u64>>,
557    // used for rw ops of actual serialization
558    // use std::sync::RwLock instead of tokio::sync::RwLock because we need to use it also in sync code in `DirectoryEntryIterator` and `DirectoryEntryPlusIterator`
559    serialize_inode_locks: Arc<ArcHashMap<u64, RwLock<bool>>>,
560    // used for the update op
561    serialize_update_inode_locks: ArcHashMap<u64, Mutex<bool>>,
562    // use std::sync::RwLock instead of tokio::sync::RwLock because we need to use it also in sync code in `DirectoryEntryIterator` and `DirectoryEntryPlusIterator`
563    serialize_dir_entries_ls_locks: Arc<ArcHashMap<String, RwLock<bool>>>,
564    serialize_dir_entries_hash_locks: Arc<ArcHashMap<String, RwLock<bool>>>,
565    read_write_locks: ArcHashMap<u64, RwLock<bool>>,
566    key: ExpireValue<SecretVec<u8>, FsError, KeyProvider>,
567    self_weak: std::sync::Mutex<Option<Weak<Self>>>,
568    attr_cache: ExpireValue<RwLock<LruCache<u64, FileAttr>>, FsError, AttrCacheProvider>,
569    dir_entries_name_cache:
570        ExpireValue<Mutex<LruCache<String, SecretString>>, FsError, DirEntryNameCacheProvider>,
571    dir_entries_meta_cache:
572        ExpireValue<Mutex<DirEntryMetaCache>, FsError, DirEntryMetaCacheProvider>,
573    sizes_write: Mutex<HashMap<u64, AtomicU64>>,
574    sizes_read: Mutex<HashMap<u64, AtomicU64>>,
575    requested_read: Mutex<HashMap<u64, AtomicU64>>,
576    read_only: bool,
577}
578
579impl EncryptedFs {
580    #[allow(clippy::missing_panics_doc)]
581    #[allow(clippy::missing_errors_doc)]
582    pub async fn new(
583        data_dir: PathBuf,
584        password_provider: Box<dyn PasswordProvider>,
585        cipher: Cipher,
586        read_only: bool,
587    ) -> FsResult<Arc<Self>> {
588        let key_provider = KeyProvider {
589            key_path: data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME),
590            salt_path: data_dir.join(SECURITY_DIR).join(KEY_SALT_FILENAME),
591            password_provider,
592            cipher,
593        };
594        let key = ExpireValue::new(key_provider, Duration::from_secs(10 * 60));
595
596        ensure_structure_created(&data_dir.clone()).await?;
597        key.get().await?; // this will check the password
598
599        let fs = Self {
600            data_dir,
601            write_handles: RwLock::new(HashMap::new()),
602            read_handles: RwLock::new(HashMap::new()),
603            current_handle: AtomicU64::new(1),
604            cipher,
605            opened_files_for_read: RwLock::new(HashMap::new()),
606            opened_files_for_write: RwLock::new(HashMap::new()),
607            serialize_inode_locks: Arc::new(ArcHashMap::default()),
608            serialize_update_inode_locks: ArcHashMap::default(),
609            serialize_dir_entries_ls_locks: Arc::new(ArcHashMap::default()),
610            serialize_dir_entries_hash_locks: Arc::new(ArcHashMap::default()),
611            key,
612            self_weak: std::sync::Mutex::new(None),
613            read_write_locks: ArcHashMap::default(),
614            // todo: take duration from param
615            attr_cache: ExpireValue::new(AttrCacheProvider {}, Duration::from_secs(10 * 60)),
616            // todo: take duration from param
617            dir_entries_name_cache: ExpireValue::new(
618                DirEntryNameCacheProvider {},
619                Duration::from_secs(10 * 60),
620            ),
621            // todo: take duration from param
622            dir_entries_meta_cache: ExpireValue::new(
623                DirEntryMetaCacheProvider {},
624                Duration::from_secs(10 * 60),
625            ),
626            sizes_write: Mutex::default(),
627            sizes_read: Mutex::default(),
628            requested_read: Mutex::default(),
629            read_only,
630        };
631
632        let arc = Arc::new(fs);
633        arc.self_weak
634            .lock()
635            .expect("cannot obtain lock")
636            .replace(Arc::downgrade(&arc));
637
638        arc.ensure_root_exists().await?;
639
640        Ok(arc)
641    }
642
643    pub fn exists(&self, ino: u64) -> bool {
644        self.ino_file(ino).is_file()
645    }
646
647    pub fn is_dir(&self, ino: u64) -> bool {
648        self.contents_path(ino).is_dir()
649    }
650
651    pub fn is_file(&self, ino: u64) -> bool {
652        self.contents_path(ino).is_file()
653    }
654
655    #[allow(dead_code)]
656    async fn is_read_only(&self) -> bool {
657        self.read_only
658    }
659
660    fn validate_filename(&self, secret_filename: &SecretBox<String>) -> FsResult<()> {
661        let filename = secret_filename.expose_secret().to_string();
662        if filename.contains('/') {
663            Err(FsError::InvalidInput("'/' not allowed in the filename"))
664        } else if filename.contains('\\') {
665            Err(FsError::InvalidInput("'\\' not allowed in the filename"))
666        } else {
667            Ok(())
668        }
669    }
670
671    /// Create a new node in the filesystem
672    #[allow(clippy::missing_panics_doc)]
673    #[allow(clippy::missing_errors_doc)]
674    #[allow(clippy::too_many_lines)]
675    pub async fn create(
676        &self,
677        parent: u64,
678        name: &SecretString,
679        create_attr: CreateFileAttr,
680        read: bool,
681        write: bool,
682    ) -> FsResult<(u64, FileAttr)> {
683        if self.read_only {
684            return Err(FsError::ReadOnly);
685        }
686        if *name.expose_secret() == "." || *name.expose_secret() == ".." {
687            return Err(FsError::InvalidInput("name cannot be '.' or '..'"));
688        }
689        if !self.exists(parent) {
690            return Err(FsError::InodeNotFound);
691        }
692        if self.exists_by_name(parent, name)? {
693            return Err(FsError::AlreadyExists);
694        }
695        self.validate_filename(name)?;
696
697        // spawn on a dedicated runtime to not interfere with other higher priority tasks
698        let self_clone = self
699            .self_weak
700            .lock()
701            .unwrap()
702            .as_ref()
703            .unwrap()
704            .upgrade()
705            .unwrap();
706        let name_clone = name.clone();
707        NOD_RT
708            .spawn(async move {
709                let mut attr: FileAttr = create_attr.into();
710                attr.ino = self_clone.generate_next_inode();
711
712                let fs = self_clone;
713                let mut join_set = JoinSet::new();
714
715                // write inode
716                let self_clone = fs.clone();
717                self_clone.write_inode_to_storage(&attr).await?;
718
719                match attr.kind {
720                    FileType::RegularFile => {
721                        let self_clone = fs.clone();
722                        join_set.spawn(async move {
723                            // create in contents directory
724                            let file = File::create(self_clone.contents_path(attr.ino))?;
725                            // sync_all file and parent
726                            // these operations are a bit slow, but are necessary to make sure the file is correctly created
727                            // i.e. creating 100 files takes 0.965 sec with sync_all and 0.130 sec without
728                            file.sync_all()?;
729                            File::open(
730                                self_clone
731                                    .contents_path(attr.ino)
732                                    .parent()
733                                    .expect("oops, we don't have a parent"),
734                            )?
735                            .sync_all()?;
736                            Ok::<(), FsError>(())
737                        });
738                    }
739                    FileType::Directory => {
740                        let self_clone = fs.clone();
741                        let attr_clone = attr;
742                        join_set.spawn(async move {
743                            // create in contents directory
744                            let contents_dir = self_clone.contents_path(attr.ino);
745                            fs::create_dir(contents_dir.clone())?;
746                            // used to keep encrypted file names used by [`read_dir`] and [`read_dir_plus`]
747                            fs::create_dir(contents_dir.join(LS_DIR))?;
748                            // used to keep hashes of encrypted file names used by [`exists_by_name`] and [`find_by_name`]
749                            // this optimizes the search process as we don't need to decrypt all file names and search
750                            fs::create_dir(contents_dir.join(HASH_DIR))?;
751
752                            // add "." and ".." entries
753                            self_clone
754                                .insert_directory_entry(
755                                    attr_clone.ino,
756                                    &DirectoryEntry {
757                                        ino: attr_clone.ino,
758                                        name: SecretString::new(Box::new("$.".into())),
759                                        kind: FileType::Directory,
760                                    },
761                                )
762                                .await?;
763                            self_clone
764                                .insert_directory_entry(
765                                    attr_clone.ino,
766                                    &DirectoryEntry {
767                                        ino: parent,
768                                        name: SecretString::new(Box::new("$..".into())),
769                                        kind: FileType::Directory,
770                                    },
771                                )
772                                .await?;
773                            Ok::<(), FsError>(())
774                        });
775                    }
776                }
777
778                // edd entry in parent directory, used for listing
779                let self_clone = fs.clone();
780                let attr_clone = attr;
781                join_set.spawn(async move {
782                    self_clone
783                        .insert_directory_entry(
784                            parent,
785                            &DirectoryEntry {
786                                ino: attr_clone.ino,
787                                name: name_clone,
788                                kind: attr_clone.kind,
789                            },
790                        )
791                        .await?;
792                    Ok::<(), FsError>(())
793                });
794
795                let self_clone = fs.clone();
796                join_set.spawn(async move {
797                    let now = SystemTime::now();
798                    self_clone
799                        .set_attr(
800                            parent,
801                            SetFileAttr::default()
802                                .with_mtime(now)
803                                .with_ctime(now)
804                                .with_atime(now),
805                        )
806                        .await?;
807                    Ok::<(), FsError>(())
808                });
809
810                // wait for all tasks to finish
811                while let Some(res) = join_set.join_next().await {
812                    res??;
813                }
814
815                let self_clone = fs.clone();
816                let handle = if attr.kind == FileType::RegularFile {
817                    if read || write {
818                        self_clone.open(attr.ino, read, write).await?
819                    } else {
820                        // we don't create a handle for files that are not opened
821                        0
822                    }
823                } else {
824                    // we don't use a handle for directories
825                    0
826                };
827
828                Ok((handle, attr))
829            })
830            .await?
831    }
832
833    #[allow(clippy::missing_panics_doc)]
834    #[allow(clippy::missing_errors_doc)]
835    pub async fn find_by_name(
836        &self,
837        parent: u64,
838        name: &SecretString,
839    ) -> FsResult<Option<FileAttr>> {
840        if !self.exists(parent) {
841            return Err(FsError::InodeNotFound);
842        }
843        if !self.is_dir(parent) {
844            return Err(FsError::InvalidInodeType);
845        }
846        let hash = crypto::hash_file_name(name);
847        let hash_path = self.contents_path(parent).join(HASH_DIR).join(hash);
848        if !hash_path.is_file() {
849            return Ok(None);
850        }
851        let lock = self
852            .serialize_dir_entries_hash_locks
853            .get_or_insert_with(hash_path.to_str().unwrap().to_owned(), || {
854                RwLock::new(false)
855            });
856        let guard = lock.read().await;
857        let (ino, _, _): (u64, FileType, String) = bincode::deserialize_from(crypto::create_read(
858            File::open(hash_path)?,
859            self.cipher,
860            &*self.key.get().await?,
861        ))?;
862        drop(guard);
863        self.get_inode_from_cache_or_storage(ino).await.map(Some)
864    }
865
866    /// Count children of a directory. This **EXCLUDES** "." and "..".
867    #[allow(clippy::missing_errors_doc)]
868    pub fn len(&self, ino: u64) -> FsResult<usize> {
869        if !self.is_dir(ino) {
870            return Err(FsError::InvalidInodeType);
871        }
872        let mut count = fs::read_dir(self.contents_path(ino).join(LS_DIR))?.count();
873        if ino == ROOT_INODE {
874            // we don't count "."
875            count -= 1;
876        } else {
877            // we don't count "." and ".."
878            count -= 2;
879        }
880        Ok(count)
881    }
882
883    /// Delete a directory
884    #[allow(clippy::missing_panics_doc)]
885    #[allow(clippy::missing_errors_doc)]
886    pub async fn remove_dir(&self, parent: u64, name: &SecretString) -> FsResult<()> {
887        if self.read_only {
888            return Err(FsError::ReadOnly);
889        }
890        if !self.is_dir(parent) {
891            return Err(FsError::InvalidInodeType);
892        }
893
894        if !self.exists_by_name(parent, name)? {
895            return Err(FsError::NotFound("name not found"));
896        }
897
898        let attr = self
899            .find_by_name(parent, name)
900            .await?
901            .ok_or(FsError::NotFound("name not found"))?;
902        if !matches!(attr.kind, FileType::Directory) {
903            return Err(FsError::InvalidInodeType);
904        }
905        // check if it's empty
906        if self.len(attr.ino)? > 0 {
907            return Err(FsError::NotEmpty);
908        }
909        let self_clone = self
910            .self_weak
911            .lock()
912            .unwrap()
913            .as_ref()
914            .unwrap()
915            .upgrade()
916            .unwrap();
917        let name_clone = name.clone();
918        NOD_RT
919            .spawn(async move {
920                // remove inode file
921                {
922                    let lock = self_clone
923                        .serialize_inode_locks
924                        .get_or_insert_with(attr.ino, || RwLock::new(false));
925                    let _guard = lock.write();
926                    fs::remove_file(self_clone.ino_file(attr.ino))?;
927                }
928
929                // remove contents directory
930                fs::remove_dir_all(self_clone.contents_path(attr.ino))?;
931                // remove from parent directory
932                self_clone
933                    .remove_directory_entry(parent, &name_clone)
934                    .await?;
935                // remove from cache
936                self_clone
937                    .attr_cache
938                    .get()
939                    .await?
940                    .write()
941                    .await
942                    .demote(&attr.ino);
943
944                let now = SystemTime::now();
945                self_clone
946                    .set_attr(
947                        parent,
948                        SetFileAttr::default()
949                            .with_mtime(now)
950                            .with_ctime(now)
951                            .with_atime(now),
952                    )
953                    .await?;
954
955                Ok(())
956            })
957            .await?
958    }
959
960    /// Delete a file
961    #[allow(clippy::missing_panics_doc)]
962    #[allow(clippy::missing_errors_doc)]
963    pub async fn remove_file(&self, parent: u64, name: &SecretString) -> FsResult<()> {
964        if self.read_only {
965            return Err(FsError::ReadOnly);
966        }
967        if !self.is_dir(parent) {
968            return Err(FsError::InvalidInodeType);
969        }
970        if !self.exists_by_name(parent, name)? {
971            return Err(FsError::NotFound("name not found"));
972        }
973
974        let attr = self
975            .find_by_name(parent, name)
976            .await?
977            .ok_or(FsError::NotFound("name not found"))?;
978        if !matches!(attr.kind, FileType::RegularFile) {
979            return Err(FsError::InvalidInodeType);
980        }
981        // todo move to method
982        let self_clone = self
983            .self_weak
984            .lock()
985            .unwrap()
986            .as_ref()
987            .unwrap()
988            .upgrade()
989            .unwrap();
990        let name_clone = name.clone();
991        NOD_RT
992            .spawn(async move {
993                // remove inode file
994                {
995                    let lock = self_clone
996                        .serialize_inode_locks
997                        .get_or_insert_with(attr.ino, || RwLock::new(false));
998                    let _guard = lock.write();
999                    fs::remove_file(self_clone.ino_file(attr.ino))?;
1000                }
1001
1002                // remove from contents directory
1003                fs::remove_file(self_clone.contents_path(attr.ino))?;
1004                // remove from parent directory
1005                self_clone
1006                    .remove_directory_entry(parent, &name_clone)
1007                    .await?;
1008                // remove from cache
1009                self_clone
1010                    .attr_cache
1011                    .get()
1012                    .await?
1013                    .write()
1014                    .await
1015                    .demote(&attr.ino);
1016
1017                let now = SystemTime::now();
1018                self_clone
1019                    .set_attr(
1020                        parent,
1021                        SetFileAttr::default()
1022                            .with_mtime(now)
1023                            .with_ctime(now)
1024                            .with_atime(now),
1025                    )
1026                    .await?;
1027
1028                Ok(())
1029            })
1030            .await?
1031    }
1032
1033    #[allow(clippy::missing_panics_doc)]
1034    #[allow(clippy::missing_errors_doc)]
1035    pub fn exists_by_name(&self, parent: u64, name: &SecretString) -> FsResult<bool> {
1036        if !self.exists(parent) {
1037            return Err(FsError::InodeNotFound);
1038        }
1039        if !self.is_dir(parent) {
1040            return Err(FsError::InvalidInodeType);
1041        }
1042        let hash = crypto::hash_file_name(name);
1043        let hash_path = self.contents_path(parent).join(HASH_DIR).join(hash);
1044        Ok(hash_path.is_file())
1045    }
1046
1047    #[allow(clippy::missing_errors_doc)]
1048    pub async fn read_dir(&self, ino: u64) -> FsResult<DirectoryEntryIterator> {
1049        if !self.is_dir(ino) {
1050            return Err(FsError::InvalidInodeType);
1051        }
1052        let ls_dir = self.contents_path(ino).join(LS_DIR);
1053        if !ls_dir.is_dir() {
1054            return Err(FsError::InvalidInodeType);
1055        }
1056
1057        let iter = fs::read_dir(ls_dir)?;
1058        let set_attr = SetFileAttr::default().with_atime(SystemTime::now());
1059        self.set_attr(ino, set_attr).await?;
1060        Ok(self.create_directory_entry_iterator(iter).await)
1061    }
1062
1063    /// Like [`EncryptedFs::read_dir`] but with [`FileAttr`] so we don't need to query again for those.
1064    pub async fn read_dir_plus(&self, ino: u64) -> FsResult<DirectoryEntryPlusIterator> {
1065        if !self.is_dir(ino) {
1066            return Err(FsError::InvalidInodeType);
1067        }
1068        let ls_dir = self.contents_path(ino).join(LS_DIR);
1069        if !ls_dir.is_dir() {
1070            return Err(FsError::InvalidInodeType);
1071        }
1072
1073        let iter = fs::read_dir(ls_dir)?;
1074        let set_attr = SetFileAttr::default().with_atime(SystemTime::now());
1075        self.set_attr(ino, set_attr).await?;
1076        Ok(self.create_directory_entry_plus_iterator(iter).await)
1077    }
1078
1079    async fn create_directory_entry_plus(
1080        &self,
1081        entry: io::Result<DirEntry>,
1082    ) -> FsResult<DirectoryEntryPlus> {
1083        let entry = self.create_directory_entry(entry).await?;
1084        let lock = self.serialize_inode_locks.clone();
1085        let lock_ino = lock.get_or_insert_with(entry.ino, || RwLock::new(false));
1086        let _ino_guard = lock_ino.read();
1087        let attr = self.get_inode_from_cache_or_storage(entry.ino).await?;
1088        Ok(DirectoryEntryPlus {
1089            ino: entry.ino,
1090            name: entry.name,
1091            kind: entry.kind,
1092            attr,
1093        })
1094    }
1095
1096    async fn create_directory_entry_plus_iterator(
1097        &self,
1098        read_dir: ReadDir,
1099    ) -> DirectoryEntryPlusIterator {
1100        #[allow(clippy::cast_possible_truncation)]
1101        let futures: Vec<_> = read_dir
1102            .into_iter()
1103            .map(|entry| {
1104                let fs = {
1105                    self.self_weak
1106                        .lock()
1107                        .unwrap()
1108                        .as_ref()
1109                        .unwrap()
1110                        .upgrade()
1111                        .unwrap()
1112                };
1113                DIR_ENTRIES_RT.spawn(async move { fs.create_directory_entry_plus(entry).await })
1114            })
1115            .collect();
1116
1117        // do these futures in parallel and return them
1118        let mut res = VecDeque::with_capacity(futures.len());
1119        for f in futures {
1120            res.push_back(f.await.unwrap());
1121        }
1122        DirectoryEntryPlusIterator(res)
1123    }
1124
1125    async fn create_directory_entry(
1126        &self,
1127        entry: io::Result<DirEntry>,
1128    ) -> FsResult<DirectoryEntry> {
1129        if entry.is_err() {
1130            return Err(entry.err().unwrap().into());
1131        }
1132        if let Err(e) = entry {
1133            error!(err = %e, "reading directory entry");
1134            return Err(e.into());
1135        }
1136        let entry = entry.unwrap();
1137        let name = entry.file_name().to_string_lossy().to_string();
1138        let name = {
1139            if name == "$." {
1140                SecretString::new(Box::new(".".into()))
1141            } else if name == "$.." {
1142                SecretString::from_str("..").unwrap()
1143            } else {
1144                // try from cache
1145                let lock = self.get_dir_entries_name_cache().await?;
1146                let mut cache = lock.lock().await;
1147                if let Some(name_cached) = cache.get(&name).cloned() {
1148                    name_cached
1149                } else {
1150                    drop(cache);
1151                    if let Ok(decrypted_name) =
1152                        crypto::decrypt_file_name(&name, self.cipher, &*self.key.get().await?)
1153                            .map_err(|err| {
1154                                error!(err = %err, "decrypting file name");
1155                                err
1156                            })
1157                    {
1158                        lock.lock().await.put(name.clone(), decrypted_name.clone());
1159                        decrypted_name
1160                    } else {
1161                        return Err(FsError::InvalidInput("invalid file name"));
1162                    }
1163                }
1164            }
1165        };
1166
1167        self.validate_filename(&name)?;
1168
1169        let file_path = entry.path().to_str().unwrap().to_owned();
1170        // try from cache
1171        let lock = self.dir_entries_meta_cache.get().await?;
1172        let mut cache = lock.lock().await;
1173        if let Some((ino, kind)) = cache.get(&file_path) {
1174            return Ok(DirectoryEntry {
1175                ino: *ino,
1176                name,
1177                kind: *kind,
1178            });
1179        }
1180        drop(cache);
1181        let lock = self
1182            .serialize_dir_entries_ls_locks
1183            .get_or_insert_with(file_path.clone(), || RwLock::new(false));
1184        let guard = lock.read().await;
1185        let file = File::open(entry.path())?;
1186        let res: bincode::Result<(u64, FileType)> = bincode::deserialize_from(crypto::create_read(
1187            file,
1188            self.cipher,
1189            &*self.key.get().await?,
1190        ));
1191        drop(guard);
1192        if let Err(e) = res {
1193            error!(err = %e, "deserializing directory entry");
1194            return Err(e.into());
1195        }
1196        let (ino, kind): (u64, FileType) = res.unwrap();
1197        // add to cache
1198        self.dir_entries_meta_cache
1199            .get()
1200            .await?
1201            .lock()
1202            .await
1203            .put(file_path, (ino, kind));
1204        Ok(DirectoryEntry { ino, name, kind })
1205    }
1206
1207    async fn get_dir_entries_name_cache(
1208        &self,
1209    ) -> FsResult<Arc<Mutex<LruCache<String, SecretString>>>> {
1210        self.dir_entries_name_cache.get().await
1211    }
1212
1213    async fn create_directory_entry_iterator(&self, read_dir: ReadDir) -> DirectoryEntryIterator {
1214        #[allow(clippy::cast_possible_truncation)]
1215        let futures: Vec<_> = read_dir
1216            .into_iter()
1217            .map(|entry| {
1218                let fs = {
1219                    self.self_weak
1220                        .lock()
1221                        .unwrap()
1222                        .as_ref()
1223                        .unwrap()
1224                        .upgrade()
1225                        .unwrap()
1226                };
1227                DIR_ENTRIES_RT.spawn(async move { fs.create_directory_entry(entry).await })
1228            })
1229            .collect();
1230
1231        // do these futures in parallel and return them
1232        let mut res = VecDeque::with_capacity(futures.len());
1233        for f in futures {
1234            res.push_back(f.await.unwrap());
1235        }
1236        DirectoryEntryIterator(res)
1237    }
1238
1239    #[allow(clippy::missing_errors_doc)]
1240    async fn get_inode_from_storage(&self, ino: u64) -> FsResult<FileAttr> {
1241        let lock = self
1242            .serialize_inode_locks
1243            .get_or_insert_with(ino, || RwLock::new(false));
1244        let _guard = lock.read();
1245
1246        let path = self.ino_file(ino);
1247        if !path.is_file() {
1248            return Err(FsError::InodeNotFound);
1249        }
1250        let file = OpenOptions::new().read(true).open(path).map_err(|err| {
1251            error!(err = %err, "opening file");
1252            FsError::InodeNotFound
1253        })?;
1254        Ok(bincode::deserialize_from(crypto::create_read(
1255            file,
1256            self.cipher,
1257            &*self.key.get().await?,
1258        ))?)
1259    }
1260
1261    async fn get_inode_from_cache_or_storage(&self, ino: u64) -> FsResult<FileAttr> {
1262        let lock = self.attr_cache.get().await?;
1263        let mut guard = lock.write().await;
1264        let attr = guard.get(&ino);
1265        if let Some(attr) = attr {
1266            Ok(*attr)
1267        } else {
1268            drop(guard);
1269            let attr = self.get_inode_from_storage(ino).await?;
1270            let mut guard = lock.write().await;
1271            guard.put(ino, attr);
1272            Ok(attr)
1273        }
1274    }
1275
1276    /// Get metadata
1277    #[allow(clippy::missing_errors_doc)]
1278    pub async fn get_attr(&self, ino: u64) -> FsResult<FileAttr> {
1279        let mut attr = self.get_inode_from_cache_or_storage(ino).await?;
1280
1281        // merge time info with any open read handles
1282        let open_reads = { self.opened_files_for_read.read().await.contains_key(&ino) };
1283        if open_reads {
1284            let fhs = self.opened_files_for_read.read().await.get(&ino).cloned();
1285            if let Some(fhs) = fhs {
1286                for fh in fhs {
1287                    let lock = self.read_handles.read().await;
1288                    if let Some(ctx) = lock.get(&fh) {
1289                        let set_atr: SetFileAttr = ctx.lock().await.attr.clone().into();
1290                        merge_attr(&mut attr, &set_atr, false);
1291                    }
1292                }
1293            }
1294        }
1295
1296        // merge time info and size with any open write handles
1297        let open_writes = { self.opened_files_for_write.read().await.contains_key(&ino) };
1298        if open_writes {
1299            let fh = self.opened_files_for_write.read().await.get(&ino).copied();
1300            if let Some(fh) = fh {
1301                let lock = self.write_handles.read().await;
1302                if let Some(ctx) = lock.get(&fh) {
1303                    let ctx = ctx.lock().await;
1304                    merge_attr(&mut attr, &ctx.attr.clone().into(), false);
1305                }
1306            }
1307        }
1308
1309        Ok(attr)
1310    }
1311
1312    /// Set metadata
1313    pub async fn set_attr(&self, ino: u64, set_attr: SetFileAttr) -> FsResult<()> {
1314        if self.read_only {
1315            return Err(FsError::ReadOnly);
1316        }
1317        self.set_attr2(ino, set_attr, false).await
1318    }
1319
1320    async fn set_attr2(
1321        &self,
1322        ino: u64,
1323        set_attr: SetFileAttr,
1324        overwrite_size: bool,
1325    ) -> FsResult<()> {
1326        let serialize_update_lock = self
1327            .serialize_update_inode_locks
1328            .get_or_insert_with(ino, || Mutex::new(false));
1329        let _serialize_update_guard = serialize_update_lock.lock().await;
1330
1331        let mut attr = self.get_attr(ino).await?;
1332        merge_attr(&mut attr, &set_attr, overwrite_size);
1333        let now = SystemTime::now();
1334        attr.ctime = now;
1335        attr.atime = now;
1336
1337        self.write_inode_to_storage(&attr).await?;
1338
1339        Ok(())
1340    }
1341
1342    async fn write_inode_to_storage(&self, attr: &FileAttr) -> Result<(), FsError> {
1343        let lock = self
1344            .serialize_inode_locks
1345            .get_or_insert_with(attr.ino, || RwLock::new(false));
1346        let guard = lock.write().await;
1347        crypto::atomic_serialize_encrypt_into(
1348            &self.ino_file(attr.ino),
1349            attr,
1350            self.cipher,
1351            &*self.key.get().await?,
1352        )?;
1353        drop(guard);
1354        // update cache also
1355        {
1356            let lock = self.attr_cache.get().await?;
1357            let mut guard = lock.write().await;
1358            guard.put(attr.ino, *attr);
1359        }
1360        Ok(())
1361    }
1362
1363    /// Read the contents from an `offset`.
1364    ///
1365    /// If we try to read outside of file size, we return zero bytes.
1366    /// If the file is not opened for read, it will return an error of type [FsError::InvalidFileHandle].
1367    #[instrument(skip(self, buf), fields(len = %buf.len()), ret(level = Level::DEBUG))]
1368    #[allow(clippy::missing_errors_doc)]
1369    #[allow(clippy::cast_possible_truncation)]
1370    pub async fn read(
1371        &self,
1372        ino: u64,
1373        offset: u64,
1374        buf: &mut [u8],
1375        handle: u64,
1376    ) -> FsResult<usize> {
1377        if !self.exists(ino) {
1378            return Err(FsError::InodeNotFound);
1379        }
1380        if !self.is_file(ino) {
1381            return Err(FsError::InvalidInodeType);
1382        }
1383        if !self.read_handles.read().await.contains_key(&handle) {
1384            return Err(FsError::InvalidFileHandle);
1385        }
1386
1387        let _size = self.get_attr(ino).await?.size;
1388
1389        let lock = self
1390            .read_write_locks
1391            .get_or_insert_with(ino, || RwLock::new(false));
1392        let _read_guard = lock.read().await;
1393
1394        let guard = self.read_handles.read().await;
1395        let mut ctx = guard.get(&handle).unwrap().lock().await;
1396
1397        if ctx.ino != ino {
1398            return Err(FsError::InvalidFileHandle);
1399        }
1400        if self.is_dir(ino) {
1401            return Err(FsError::InvalidInodeType);
1402        }
1403        if buf.is_empty() {
1404            // no-op
1405            return Ok(0);
1406        }
1407
1408        // read data
1409        let (_buf, len) = {
1410            let reader = ctx.reader.as_mut().unwrap();
1411
1412            reader.seek(SeekFrom::Start(offset)).map_err(|err| {
1413                error!(err = %err, "seeking");
1414                err
1415            })?;
1416            let pos = reader.stream_position().map_err(|err| {
1417                error!(err = %err, "getting position");
1418                err
1419            })?;
1420            if pos != offset {
1421                // we would need to seek after filesize
1422                return Ok(0);
1423            }
1424            // keep block size to max the cipher can handle
1425            #[allow(clippy::cast_possible_truncation)]
1426            let buf = if offset + buf.len() as u64 > self.cipher.max_plaintext_len() as u64 {
1427                warn!("reading more than max block size, truncating");
1428                buf.split_at_mut(self.cipher.max_plaintext_len() - offset as usize)
1429                    .0
1430            } else {
1431                buf
1432            };
1433            let len = stream_util::read(reader, buf).map_err(|err| {
1434                error!(err = %err, "reading");
1435                err
1436            })?;
1437            (buf, len)
1438        };
1439
1440        ctx.attr.atime = SystemTime::now();
1441        drop(ctx);
1442
1443        // self.sizes_read
1444        //     .lock()
1445        //     .await
1446        //     .get_mut(&ino)
1447        //     .unwrap()
1448        //     .fetch_add(len as u64, Ordering::SeqCst);
1449        // self.requested_read
1450        //     .lock()
1451        //     .await
1452        //     .get_mut(&ino)
1453        //     .unwrap()
1454        //     .fetch_add(buf.len() as u64, Ordering::SeqCst);
1455
1456        // if buf.len() != len {
1457        //     error!(
1458        //         "size mismatch in read(), size {size} offset {offset} buf_len {} len {len}",
1459        //         buf.len()
1460        //     );
1461        //     let lock = self.write_handles.read().await;
1462        //     lock.iter().for_each(|(_, lock2)| {
1463        //         call_async(async {
1464        //             let ctx = lock2.lock().await;
1465        //             if ctx.ino == ino && size != ctx.attr.size {
1466        //                 error!("trying to read from a file which is not commited yet, ctx size {} actual size {size}", ctx.attr.size);
1467        //             }
1468        //         });
1469        //     });
1470        // }
1471
1472        Ok(len)
1473    }
1474
1475    #[allow(clippy::missing_panics_doc)]
1476    #[allow(clippy::too_many_lines)]
1477    pub async fn release(&self, handle: u64) -> FsResult<()> {
1478        if handle == 0 {
1479            // in the case of directory or if the file was crated
1480            // without being opened we don't use a handle
1481            return Ok(());
1482        }
1483        let mut valid_fh = false;
1484
1485        // read
1486        let ctx = { self.read_handles.write().await.remove(&handle) };
1487        if let Some(ctx) = ctx {
1488            let ctx = ctx.lock().await;
1489
1490            {
1491                let mut opened_files_for_read = self.opened_files_for_read.write().await;
1492                opened_files_for_read
1493                    .get_mut(&ctx.ino)
1494                    .expect("handle is missing")
1495                    .remove(&handle);
1496                if opened_files_for_read
1497                    .get(&ctx.ino)
1498                    .expect("handle is missing")
1499                    .is_empty()
1500                {
1501                    opened_files_for_read.remove(&ctx.ino);
1502                }
1503            }
1504
1505            // write attr only here to avoid serializing it multiple times while reading
1506            // it will merge time fields with existing data because it might got change while we kept the handle
1507            let set_attr: SetFileAttr = ctx.attr.clone().into();
1508            let ino = ctx.ino;
1509            drop(ctx);
1510            self.set_attr(ino, set_attr).await?;
1511
1512            valid_fh = true;
1513        }
1514
1515        // write
1516        let ctx = { self.write_handles.write().await.remove(&handle) };
1517        if let Some(ctx) = ctx {
1518            if self.read_only {
1519                return Err(FsError::ReadOnly);
1520            }
1521            let mut ctx = ctx.lock().await;
1522
1523            let mut writer = ctx.writer.take().unwrap();
1524            let lock = self
1525                .read_write_locks
1526                .get_or_insert_with(ctx.ino, || RwLock::new(false));
1527            let write_guard = lock.write().await;
1528            let file = writer.finish()?;
1529            file.sync_all()?;
1530            File::open(self.contents_path(ctx.ino).parent().unwrap())?.sync_all()?;
1531            // write attr only here to avoid serializing it multiple times while writing
1532            // it will merge time fields with existing data because it might got change while we kept the handle
1533            let ino = ctx.ino;
1534            let attr = ctx.attr.clone();
1535            drop(ctx);
1536            self.set_attr(ino, attr.into()).await?;
1537            let attr = self.get_attr(ino).await?;
1538            {
1539                let write_size = self
1540                    .sizes_write
1541                    .lock()
1542                    .await
1543                    .get(&ino)
1544                    .unwrap()
1545                    .load(Ordering::SeqCst);
1546                info!("written for {ino} {write_size}");
1547                if attr.size != write_size {
1548                    // error!("size mismatch write {} {}", write_size, attr.size);
1549                }
1550                let requested_read = self
1551                    .requested_read
1552                    .lock()
1553                    .await
1554                    .get(&ino)
1555                    .unwrap()
1556                    .load(Ordering::SeqCst);
1557                let read = self
1558                    .sizes_read
1559                    .lock()
1560                    .await
1561                    .get(&ino)
1562                    .unwrap()
1563                    .load(Ordering::SeqCst);
1564                if requested_read != read {
1565                    error!(
1566                        "size mismatch read, size {} requested {} read {}",
1567                        attr.size, requested_read, read
1568                    );
1569                }
1570            }
1571            self.sizes_write.lock().await.remove(&ino);
1572            self.sizes_read.lock().await.remove(&ino);
1573            self.requested_read.lock().await.remove(&ino);
1574            drop(write_guard);
1575            self.opened_files_for_write.write().await.remove(&ino);
1576            self.reset_handles(ino, Some(handle), true).await?;
1577
1578            valid_fh = true;
1579        }
1580
1581        if !valid_fh {
1582            return Err(FsError::InvalidFileHandle);
1583        }
1584        Ok(())
1585    }
1586
1587    /// Check if a file is opened for reading with this handle.
1588    pub async fn is_read_handle(&self, fh: u64) -> bool {
1589        self.read_handles.read().await.contains_key(&fh)
1590    }
1591
1592    /// Check if a file is opened for writing with this handle.
1593    pub async fn is_write_handle(&self, fh: u64) -> bool {
1594        self.write_handles.read().await.contains_key(&fh)
1595    }
1596
1597    /// Writes the contents of `buf` to the file with `ino` starting at `offset`.
1598    ///
1599    /// If we write outside file size, we fill up with zeros until the `offset`.
1600    /// If the file is not opened for writing,
1601    /// it will return an error of type [FsError::InvalidFileHandle].
1602    #[instrument(skip(self, buf), fields(len = %buf.len()), ret(level = Level::DEBUG))]
1603    pub async fn write(&self, ino: u64, offset: u64, buf: &[u8], handle: u64) -> FsResult<usize> {
1604        if self.read_only {
1605            return Err(FsError::ReadOnly);
1606        }
1607        if !self.exists(ino) {
1608            return Err(FsError::InodeNotFound);
1609        }
1610        if !self.is_file(ino) {
1611            return Err(FsError::InvalidInodeType);
1612        }
1613        {
1614            if !self.write_handles.read().await.contains_key(&handle) {
1615                return Err(FsError::InvalidFileHandle);
1616            }
1617        }
1618        {
1619            let guard = self.write_handles.read().await;
1620            let ctx = guard.get(&handle).unwrap().lock().await;
1621            if ctx.ino != ino {
1622                return Err(FsError::InvalidFileHandle);
1623            }
1624        }
1625        if buf.is_empty() {
1626            // no-op
1627            return Ok(0);
1628        }
1629
1630        let lock = self
1631            .read_write_locks
1632            .get_or_insert_with(ino, || RwLock::new(false));
1633        let write_guard = lock.write().await;
1634
1635        let guard = self.write_handles.read().await;
1636        let mut ctx = guard.get(&handle).unwrap().lock().await;
1637
1638        // write new data
1639        let (pos, len) = {
1640            if offset > self.cipher.max_plaintext_len() as u64 {
1641                return Err(FsError::MaxFilesizeExceeded(
1642                    self.cipher.max_plaintext_len(),
1643                ));
1644            }
1645            let writer = ctx.writer.as_mut().unwrap();
1646            let pos = writer.seek(SeekFrom::Start(offset)).map_err(|err| {
1647                error!(err = %err, "seeking");
1648                err
1649            })?;
1650            if offset != pos {
1651                // we could not seek to the desired position
1652                return Ok(0);
1653            }
1654            // keep block size to max the cipher can handle
1655            #[allow(clippy::cast_possible_truncation)]
1656            let buf = if offset + buf.len() as u64 > self.cipher.max_plaintext_len() as u64 {
1657                warn!("writing more than max block size, truncating");
1658                &buf[..(self.cipher.max_plaintext_len() - offset as usize)]
1659            } else {
1660                buf
1661            };
1662            let len = writer.write(buf).map_err(|err| {
1663                error!(err = %err, "writing");
1664                err
1665            })?;
1666            (writer.stream_position()?, len)
1667        };
1668
1669        // let size = ctx.attr.size;
1670        if pos > ctx.attr.size {
1671            // if we write pass file size set the new size
1672            debug!("setting new file size {}", pos);
1673            ctx.attr.size = pos;
1674        }
1675        let now = SystemTime::now();
1676        ctx.attr.mtime = now;
1677        ctx.attr.ctime = now;
1678        ctx.attr.atime = now;
1679        drop(ctx);
1680
1681        drop(write_guard);
1682        self.reset_handles(ino, Some(handle), true).await?;
1683
1684        self.sizes_write
1685            .lock()
1686            .await
1687            .get_mut(&ino)
1688            .unwrap()
1689            .fetch_add(len as u64, Ordering::SeqCst);
1690        if buf.len() != len {
1691            // error!(
1692            //     "size mismatch in write(), size {size} offset {offset} buf_len {} len {len}",
1693            //     buf.len()
1694            // );
1695        }
1696        // warn!(
1697        //     "written uncommited for {ino} size {}",
1698        //     self.sizes_write
1699        //         .lock()
1700        //         .await
1701        //         .get(&ino)
1702        //         .unwrap()
1703        //         .load(Ordering::SeqCst)
1704        // );
1705
1706        Ok(len)
1707    }
1708
1709    /// Flush the data to the underlying storage.
1710    #[allow(clippy::missing_panics_doc)]
1711    pub async fn flush(&self, handle: u64) -> FsResult<()> {
1712        if self.read_only {
1713            return Err(FsError::ReadOnly);
1714        }
1715        if handle == 0 {
1716            // in the case of directory or if the file was crated without being opened we don't use a handle
1717            return Ok(());
1718        }
1719        let lock = self.read_handles.read().await;
1720        let mut valid_fh = lock.get(&handle).is_some();
1721        let lock = self.write_handles.read().await;
1722        if let Some(ctx) = lock.get(&handle) {
1723            let mut ctx = ctx.lock().await;
1724            let lock = self
1725                .read_write_locks
1726                .get_or_insert_with(ctx.ino, || RwLock::new(false));
1727            let write_guard = lock.write().await;
1728            ctx.writer.as_mut().expect("writer is missing").flush()?;
1729            File::open(self.contents_path(ctx.ino))?.sync_all()?;
1730            File::open(self.contents_path(ctx.ino).parent().unwrap())?.sync_all()?;
1731            drop(write_guard);
1732            let ino = ctx.ino;
1733            drop(ctx);
1734            self.reset_handles(ino, Some(handle), true).await?;
1735            valid_fh = true;
1736        }
1737
1738        if !valid_fh {
1739            return Err(FsError::InvalidFileHandle);
1740        }
1741
1742        Ok(())
1743    }
1744
1745    /// Helpful when we want to copy just some portions of the file.
1746    pub async fn copy_file_range(
1747        &self,
1748        file_range_req: &CopyFileRangeReq,
1749        size: usize,
1750    ) -> FsResult<usize> {
1751        if self.read_only {
1752            return Err(FsError::ReadOnly);
1753        }
1754        if self.is_dir(file_range_req.src_ino) || self.is_dir(file_range_req.dest_ino) {
1755            return Err(FsError::InvalidInodeType);
1756        }
1757
1758        let mut buf = vec![0; size];
1759        let len = self
1760            .read(
1761                file_range_req.src_ino,
1762                file_range_req.src_offset,
1763                &mut buf,
1764                file_range_req.src_fh,
1765            )
1766            .await?;
1767        if len == 0 {
1768            return Ok(0);
1769        }
1770        let mut copied = 0;
1771        while copied < size {
1772            let len = self
1773                .write(
1774                    file_range_req.dest_ino,
1775                    file_range_req.dest_offset,
1776                    &buf[copied..len],
1777                    file_range_req.dest_fh,
1778                )
1779                .await?;
1780            if len == 0 && copied < size {
1781                error!(len, "Failed to copy all read bytes");
1782                return Err(FsError::Other("Failed to copy all read bytes"));
1783            }
1784            copied += len;
1785        }
1786        Ok(len)
1787    }
1788
1789    /// Open a file. We can open multiple times for read but only one to write at a time.
1790    #[allow(clippy::missing_panics_doc)]
1791    pub async fn open(&self, ino: u64, read: bool, write: bool) -> FsResult<u64> {
1792        if write && self.read_only {
1793            return Err(FsError::ReadOnly);
1794        }
1795        if !read && !write {
1796            return Err(FsError::InvalidInput(
1797                "read and write cannot be false at the same time",
1798            ));
1799        }
1800        if self.is_dir(ino) {
1801            return Err(FsError::InvalidInodeType);
1802        }
1803
1804        let mut handle: Option<u64> = None;
1805        if read {
1806            handle = Some(self.next_handle());
1807            self.do_with_read_handle(
1808                *handle.as_ref().unwrap(),
1809                ReadHandleContextOperation::Create { ino },
1810            )
1811            .await?;
1812        }
1813        if write {
1814            if self.opened_files_for_write.read().await.contains_key(&ino) {
1815                return Err(FsError::AlreadyOpenForWrite);
1816            }
1817            if handle.is_none() {
1818                handle = Some(self.next_handle());
1819            }
1820            let res = self
1821                .do_with_write_handle(
1822                    *handle.as_ref().expect("handle is missing"),
1823                    WriteHandleContextOperation::Create { ino },
1824                )
1825                .await;
1826            if res.is_err() && read {
1827                // on error remove the read handle if it was added above,
1828                // remove the read handle if it was added above
1829                self.read_handles
1830                    .write()
1831                    .await
1832                    .remove(handle.as_ref().unwrap());
1833            }
1834            res?;
1835        }
1836        let fh = handle.unwrap();
1837        self.sizes_write
1838            .lock()
1839            .await
1840            .entry(ino)
1841            .or_insert(AtomicU64::new(0));
1842        self.sizes_read
1843            .lock()
1844            .await
1845            .entry(ino)
1846            .or_insert(AtomicU64::new(0));
1847        self.requested_read
1848            .lock()
1849            .await
1850            .entry(ino)
1851            .or_insert(AtomicU64::new(0));
1852        Ok(fh)
1853    }
1854
1855    /// Truncates or extends the underlying file, updating the size of this file to become size.
1856    #[allow(clippy::missing_panics_doc)]
1857    #[allow(clippy::too_many_lines)]
1858    pub async fn set_len(&self, ino: u64, size: u64) -> FsResult<()> {
1859        if self.read_only {
1860            return Err(FsError::ReadOnly);
1861        }
1862        info!("truncate {ino} to {size}");
1863        let attr = self.get_attr(ino).await?;
1864        if matches!(attr.kind, FileType::Directory) {
1865            return Err(FsError::InvalidInodeType);
1866        }
1867
1868        if size == attr.size {
1869            // no-op
1870            return Ok(());
1871        }
1872
1873        let lock = self
1874            .read_write_locks
1875            .get_or_insert_with(ino, || RwLock::new(false));
1876        let _write_guard = lock.write().await;
1877
1878        // flush writers
1879        self.flush_and_reset_writers(ino).await?;
1880
1881        let file_path = self.contents_path(ino);
1882        if size == 0 {
1883            debug!("truncate to zero");
1884            // truncate to zero
1885            let file = File::create(&file_path)?;
1886            file.set_len(0)?;
1887            file.sync_all()?;
1888        } else {
1889            debug!("truncate size to {}", size.to_formatted_string(&Locale::en));
1890
1891            let mut file = fs_util::open_atomic_write(&file_path)?;
1892            {
1893                // have a new scope, so we drop the reader before moving new content files
1894                let mut reader = self.create_read(File::open(file_path.as_path())?).await?;
1895
1896                let mut writer = self.create_write(file).await?;
1897
1898                let len = if size > attr.size {
1899                    // increase size, copy existing data until existing size
1900                    attr.size
1901                } else {
1902                    // decrease size, copy existing data until new size
1903                    size
1904                };
1905                stream_util::copy_exact(&mut reader, &mut writer, len)?;
1906                if size > attr.size {
1907                    // increase size, seek to new size will write zeros
1908                    stream_util::fill_zeros(&mut writer, size - attr.size)?;
1909                }
1910                file = writer.finish()?;
1911            }
1912            file.commit()?;
1913        }
1914        File::open(file_path.parent().unwrap())?.sync_all()?;
1915
1916        let now = SystemTime::now();
1917        let set_attr = SetFileAttr::default()
1918            .with_size(size)
1919            .with_mtime(now)
1920            .with_ctime(now)
1921            .with_atime(now);
1922        self.set_attr2(ino, set_attr, true).await?;
1923
1924        let attr = self.get_inode_from_storage(ino).await?;
1925        println!("attr 1: {:?}", attr.size);
1926        let attr = self.get_attr(ino).await?;
1927        println!("attr 1: {:?}", attr.size);
1928
1929        // reset handles because the file has changed
1930        self.reset_handles(attr.ino, None, false).await?;
1931
1932        let attr = self.get_inode_from_storage(ino).await?;
1933        println!("attr 2: {:?}", attr.size);
1934        let attr = self.get_attr(ino).await?;
1935        println!("attr 2: {:?}", attr.size);
1936
1937        if size != attr.size {
1938            error!("error truncating file expected {size} actual {}", attr.size);
1939        }
1940
1941        Ok(())
1942    }
1943
1944    /// This will write any dirty data to the file from all writers and reset them.
1945    /// Timestamps and size will be updated to the storage.
1946    /// > ⚠️ **Warning**
1947    /// > Need to be called in a context with write lock on `self.read_write_inode.lock().await.get(ino)`.
1948    /// > That is because we want to make sure caller is holding a lock while all writers flush and we can't
1949    /// > lock here also as we would end-up in a deadlock.
1950    async fn flush_and_reset_writers(&self, ino: u64) -> FsResult<()> {
1951        if self.read_only {
1952            return Err(FsError::ReadOnly);
1953        }
1954        let opened_files_for_write_guard = self.opened_files_for_write.read().await;
1955        let handle = opened_files_for_write_guard.get(&ino);
1956        if let Some(handle) = handle {
1957            let write_handles_guard = self.write_handles.write().await;
1958            let ctx = write_handles_guard.get(handle);
1959            if let Some(lock) = ctx {
1960                let mut ctx = lock.lock().await;
1961
1962                let mut writer = ctx.writer.take().unwrap();
1963                let file = writer.finish()?;
1964                file.sync_all()?;
1965                File::open(self.contents_path(ctx.ino).parent().unwrap())?.sync_all()?;
1966                let handle = *handle;
1967                let set_attr: SetFileAttr = ctx.attr.clone().into();
1968                drop(ctx);
1969                drop(opened_files_for_write_guard);
1970                drop(write_handles_guard);
1971                self.set_attr(ino, set_attr).await?;
1972                self.reset_handles(ino, Some(handle), true).await?;
1973                let write_handles_guard = self.write_handles.write().await;
1974                let mut ctx = write_handles_guard.get(&handle).unwrap().lock().await;
1975                let writer = self
1976                    .create_write_seek(
1977                        OpenOptions::new()
1978                            .read(true)
1979                            .write(true)
1980                            .open(self.contents_path(ino))?,
1981                    )
1982                    .await?;
1983                ctx.writer = Some(Box::new(writer));
1984                let attr = self.get_inode_from_storage(ino).await?;
1985                ctx.attr = attr.into();
1986            }
1987        }
1988        Ok(())
1989    }
1990
1991    #[allow(clippy::missing_panics_doc)]
1992    pub async fn rename(
1993        &self,
1994        parent: u64,
1995        name: &SecretString,
1996        new_parent: u64,
1997        new_name: &SecretString,
1998    ) -> FsResult<()> {
1999        if self.read_only {
2000            return Err(FsError::ReadOnly);
2001        }
2002        if !self.exists(parent) {
2003            return Err(FsError::InodeNotFound);
2004        }
2005        if !self.is_dir(parent) {
2006            return Err(FsError::InvalidInodeType);
2007        }
2008        if !self.exists(new_parent) {
2009            return Err(FsError::InodeNotFound);
2010        }
2011        if !self.is_dir(new_parent) {
2012            return Err(FsError::InvalidInodeType);
2013        }
2014        if !self.exists_by_name(parent, name)? {
2015            return Err(FsError::NotFound("name not found"));
2016        }
2017        self.validate_filename(new_name)?;
2018
2019        if parent == new_parent && name.expose_secret() == new_name.expose_secret() {
2020            // no-op
2021            return Ok(());
2022        }
2023
2024        // Only overwrite an existing directory if it's empty
2025        if let Ok(Some(new_attr)) = self.find_by_name(new_parent, new_name).await {
2026            if new_attr.kind == FileType::Directory && self.len(new_attr.ino)? > 0 {
2027                return Err(FsError::NotEmpty);
2028            }
2029        }
2030
2031        let attr = self
2032            .find_by_name(parent, name)
2033            .await?
2034            .ok_or(FsError::NotFound("name not found"))?;
2035        // remove from parent contents
2036        self.remove_directory_entry(parent, name).await?;
2037        // remove from new_parent contents, if exists
2038        if self.exists_by_name(new_parent, new_name)? {
2039            self.remove_directory_entry(new_parent, new_name).await?;
2040        }
2041        // add to new parent contents
2042        self.insert_directory_entry(
2043            new_parent,
2044            &DirectoryEntry {
2045                ino: attr.ino,
2046                name: new_name.clone(),
2047                kind: attr.kind,
2048            },
2049        )
2050        .await?;
2051
2052        if attr.kind == FileType::Directory {
2053            // add the parent link to the new directory
2054            self.insert_directory_entry(
2055                attr.ino,
2056                &DirectoryEntry {
2057                    ino: new_parent,
2058                    name: SecretBox::new(Box::new("$..".to_owned())),
2059                    kind: FileType::Directory,
2060                },
2061            )
2062            .await?;
2063        }
2064
2065        let now = SystemTime::now();
2066        let set_attr = SetFileAttr::default()
2067            .with_mtime(now)
2068            .with_ctime(now)
2069            .with_atime(now);
2070        self.set_attr(parent, set_attr).await?;
2071
2072        let set_attr = SetFileAttr::default()
2073            .with_mtime(now)
2074            .with_ctime(now)
2075            .with_atime(now);
2076        self.set_attr(new_parent, set_attr).await?;
2077
2078        let set_attr = SetFileAttr::default().with_ctime(now).with_atime(now);
2079        self.set_attr(attr.ino, set_attr).await?;
2080
2081        Ok(())
2082    }
2083
2084    /// Create a crypto writer using internal encryption info.
2085    pub async fn create_write<W: CryptoInnerWriter + Seek + Send + Sync + 'static>(
2086        &self,
2087        file: W,
2088    ) -> FsResult<impl CryptoWrite<W>> {
2089        Ok(crypto::create_write(
2090            file,
2091            self.cipher,
2092            &*self.key.get().await?,
2093        ))
2094    }
2095
2096    /// Create a crypto writer with seek using internal encryption info.
2097    pub async fn create_write_seek<W: Write + Seek + Read + Send + Sync + 'static>(
2098        &self,
2099        file: W,
2100    ) -> FsResult<impl CryptoWriteSeek<W>> {
2101        Ok(crypto::create_write_seek(
2102            file,
2103            self.cipher,
2104            &*self.key.get().await?,
2105        ))
2106    }
2107
2108    /// Create a crypto reader using internal encryption info.
2109    pub async fn create_read<R: Read + Send + Sync>(
2110        &self,
2111        reader: R,
2112    ) -> FsResult<impl CryptoRead<R>> {
2113        Ok(crypto::create_read(
2114            reader,
2115            self.cipher,
2116            &*self.key.get().await?,
2117        ))
2118    }
2119
2120    /// Create a crypto reader with seek using internal encryption info.
2121    pub async fn create_read_seek<R: Read + Seek + Send + Sync>(
2122        &self,
2123        reader: R,
2124    ) -> FsResult<impl CryptoReadSeek<R>> {
2125        Ok(crypto::create_read_seek(
2126            reader,
2127            self.cipher,
2128            &*self.key.get().await?,
2129        ))
2130    }
2131
2132    /// Change the password of the filesystem used to access the encryption key.
2133    pub async fn passwd(
2134        data_dir: &Path,
2135        old_password: SecretString,
2136        new_password: SecretString,
2137        cipher: Cipher,
2138    ) -> FsResult<()> {
2139        check_structure(data_dir, false).await?;
2140        // decrypt key
2141        let salt: Vec<u8> = bincode::deserialize_from(File::open(
2142            data_dir.join(SECURITY_DIR).join(KEY_SALT_FILENAME),
2143        )?)?;
2144        let initial_key = crypto::derive_key(&old_password, cipher, &salt)?;
2145        let enc_file = data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME);
2146        let reader = crypto::create_read(File::open(enc_file)?, cipher, &initial_key);
2147        let key: Vec<u8> =
2148            bincode::deserialize_from(reader).map_err(|_| FsError::InvalidPassword)?;
2149        let key = SecretBox::new(Box::new(key));
2150        // encrypt it with a new key derived from new password
2151        let new_key = crypto::derive_key(&new_password, cipher, &salt)?;
2152        crypto::atomic_serialize_encrypt_into(
2153            &data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME),
2154            &*key.expose_secret(),
2155            cipher,
2156            &new_key,
2157        )?;
2158        Ok(())
2159    }
2160
2161    fn next_handle(&self) -> u64 {
2162        self.current_handle
2163            .fetch_add(1, std::sync::atomic::Ordering::SeqCst)
2164    }
2165
2166    /// Reset all handles for a file.
2167    /// Read handles will be recreated.
2168    /// Write handles will be flushed and recreated.
2169    /// Timestamps and size will be updated to storage.
2170    /// > ⚠️ **Warning**
2171    /// > Need to be called in a context with write lock on `self.read_write_inode.lock().await.get(ino)`.
2172    /// > That is because we want to make sure caller is holding a lock while all writers flush, and we can't
2173    /// > lock here also as we would end-up in a deadlock.
2174    async fn reset_handles(
2175        &self,
2176        ino: u64,
2177        skip_write_fh: Option<u64>,
2178        save_attr: bool,
2179    ) -> FsResult<()> {
2180        let path = self.contents_path(ino);
2181
2182        // read
2183        let lock = self.opened_files_for_read.read().await;
2184        if let Some(set) = lock.get(&ino) {
2185            for handle in set.iter().filter(|h| skip_write_fh != Some(**h)) {
2186                let guard = self.read_handles.read().await;
2187                let ctx = guard.get(handle).unwrap().lock().await;
2188                let set_attr: SetFileAttr = ctx.attr.clone().into();
2189                drop(ctx);
2190                self.set_attr(ino, set_attr).await?;
2191                let attr = self.get_inode_from_storage(ino).await?;
2192                let mut ctx = guard.get(handle).unwrap().lock().await;
2193                let reader = self.create_read_seek(File::open(&path)?).await?;
2194                ctx.reader = Some(Box::new(reader));
2195                ctx.attr = attr.into();
2196            }
2197        }
2198
2199        // write
2200        let lock = self.opened_files_for_write.read().await;
2201        if let Some(fh) = lock.get(&ino) {
2202            if let Some(handle) = skip_write_fh {
2203                if *fh == handle {
2204                    return Ok(());
2205                }
2206            }
2207            let lock = self.write_handles.read().await;
2208            if let Some(lock) = lock.get(fh) {
2209                let mut ctx = lock.lock().await;
2210                let writer = ctx.writer.as_mut().unwrap();
2211                let file = writer.finish()?;
2212                file.sync_all()?;
2213                File::open(self.contents_path(ctx.ino).parent().unwrap())?.sync_all()?;
2214                let set_attr: Option<SetFileAttr> = if save_attr {
2215                    Some(ctx.attr.clone().into())
2216                } else {
2217                    None
2218                };
2219                drop(ctx);
2220                if let Some(set_attr) = set_attr {
2221                    self.set_attr(ino, set_attr).await?;
2222                }
2223                let writer = self
2224                    .create_write_seek(OpenOptions::new().read(true).write(true).open(&path)?)
2225                    .await?;
2226                let mut ctx = lock.lock().await;
2227                ctx.writer = Some(Box::new(writer));
2228                let attr = self.get_inode_from_storage(ino).await?;
2229                ctx.attr = attr.into();
2230            }
2231        }
2232
2233        Ok(())
2234    }
2235
2236    async fn do_with_read_handle(
2237        &self,
2238        handle: u64,
2239        op: ReadHandleContextOperation,
2240    ) -> FsResult<()> {
2241        let ino = op.get_ino();
2242        let path = self.contents_path(ino);
2243        let attr = self.get_inode_from_storage(ino).await?;
2244        match op {
2245            ReadHandleContextOperation::Create { ino } => {
2246                let attr: TimesFileAttr = attr.into();
2247                let reader = self.create_read_seek(File::open(&path)?).await?;
2248                let ctx = ReadHandleContext {
2249                    ino,
2250                    attr,
2251                    reader: Some(Box::new(reader)),
2252                };
2253                self.read_handles
2254                    .write()
2255                    .await
2256                    .insert(handle, Mutex::new(ctx));
2257                self.opened_files_for_read
2258                    .write()
2259                    .await
2260                    .entry(ino)
2261                    .or_insert_with(HashSet::new)
2262                    .insert(handle);
2263            }
2264        }
2265        Ok(())
2266    }
2267
2268    async fn do_with_write_handle(
2269        &self,
2270        handle: u64,
2271        op: WriteHandleContextOperation,
2272    ) -> FsResult<()> {
2273        let ino = op.get_ino();
2274        let path = self.contents_path(ino);
2275        match op {
2276            WriteHandleContextOperation::Create { ino } => {
2277                let attr = self.get_attr(ino).await?.into();
2278                let writer = self
2279                    .create_write_seek(OpenOptions::new().read(true).write(true).open(&path)?)
2280                    .await?;
2281                let ctx = WriteHandleContext {
2282                    ino,
2283                    attr,
2284                    writer: Some(Box::new(writer)),
2285                };
2286                self.write_handles
2287                    .write()
2288                    .await
2289                    .insert(handle, Mutex::new(ctx));
2290                self.opened_files_for_write
2291                    .write()
2292                    .await
2293                    .insert(ino, handle);
2294            }
2295        }
2296
2297        Ok(())
2298    }
2299
2300    async fn ensure_root_exists(&self) -> FsResult<()> {
2301        if !self.exists(ROOT_INODE) {
2302            let mut attr: FileAttr = CreateFileAttr {
2303                kind: FileType::Directory,
2304                perm: 0o755,
2305                uid: 0,
2306                gid: 0,
2307                rdev: 0,
2308                flags: 0,
2309            }
2310            .into();
2311            attr.ino = ROOT_INODE;
2312            #[cfg(any(target_os = "linux", target_os = "macos"))]
2313            unsafe {
2314                attr.uid = libc::getuid();
2315                attr.gid = libc::getgid();
2316            }
2317
2318            self.write_inode_to_storage(&attr).await?;
2319
2320            // create in contents directory
2321            fs::create_dir(self.contents_path(attr.ino))?;
2322            fs::create_dir(self.contents_path(attr.ino).join(LS_DIR))?;
2323            fs::create_dir(self.contents_path(attr.ino).join(HASH_DIR))?;
2324
2325            // add "." entry
2326            self.insert_directory_entry(
2327                attr.ino,
2328                &DirectoryEntry {
2329                    ino: attr.ino,
2330                    name: SecretString::from_str("$.").unwrap(),
2331                    kind: FileType::Directory,
2332                },
2333            )
2334            .await?;
2335        }
2336
2337        Ok(())
2338    }
2339
2340    async fn insert_directory_entry(
2341        &self,
2342        ino_contents_dir: u64,
2343        entry: &DirectoryEntry,
2344    ) -> FsResult<()> {
2345        let parent_path = self.contents_path(ino_contents_dir);
2346        let encrypted_name =
2347            crypto::encrypt_file_name(&entry.name, self.cipher, &*self.key.get().await?)?;
2348        // add to LS directory
2349        let self_clone = self
2350            .self_weak
2351            .lock()
2352            .unwrap()
2353            .as_ref()
2354            .unwrap()
2355            .upgrade()
2356            .unwrap();
2357        let parent_path_clone = parent_path.clone();
2358        let encrypted_name_clone = encrypted_name.clone();
2359        let entry_clone = entry.clone();
2360        // spawn a task to do concurrently with adding to HASH directory
2361        let h = tokio::spawn(async move {
2362            let file_path = parent_path_clone
2363                .join(LS_DIR)
2364                .join(encrypted_name_clone.clone());
2365            let lock = self_clone
2366                .serialize_dir_entries_ls_locks
2367                .get_or_insert_with(file_path.to_str().unwrap().to_owned(), || {
2368                    RwLock::new(false)
2369                });
2370            let _guard = lock.write().await;
2371            // write inode and file type
2372            let entry = (entry_clone.ino, entry_clone.kind);
2373            crypto::atomic_serialize_encrypt_into(
2374                &file_path,
2375                &entry,
2376                self_clone.cipher,
2377                &*self_clone.key.get().await?,
2378            )?;
2379            Ok::<(), FsError>(())
2380        });
2381        // add to HASH directory
2382        let self_clone = self
2383            .self_weak
2384            .lock()
2385            .unwrap()
2386            .as_ref()
2387            .unwrap()
2388            .upgrade()
2389            .unwrap();
2390        let entry_hash = entry.clone();
2391        tokio::spawn(async move {
2392            let name = crypto::hash_file_name(&entry_hash.name);
2393            let file_path = parent_path.join(HASH_DIR).join(name);
2394            let lock = self_clone
2395                .serialize_dir_entries_hash_locks
2396                .get_or_insert_with(file_path.to_str().unwrap().to_owned(), || {
2397                    RwLock::new(false)
2398                });
2399            let _guard = lock.write().await;
2400            // write inode and file type
2401            // we save the encrypted name also because we need it to remove the entry on [`remove_directory_entry`]
2402            let entry = (entry_hash.ino, entry_hash.kind, encrypted_name);
2403            crypto::atomic_serialize_encrypt_into(
2404                &file_path,
2405                &entry,
2406                self_clone.cipher,
2407                &*self_clone.key.get().await?,
2408            )?;
2409            Ok::<(), FsError>(())
2410        })
2411        .await??;
2412        h.await??;
2413        Ok(())
2414    }
2415
2416    fn ino_file(&self, ino: u64) -> PathBuf {
2417        self.data_dir.join(INODES_DIR).join(ino.to_string())
2418    }
2419
2420    fn contents_path(&self, ino: u64) -> PathBuf {
2421        self.data_dir.join(CONTENTS_DIR).join(ino.to_string())
2422    }
2423
2424    async fn remove_directory_entry(&self, parent: u64, name: &SecretString) -> FsResult<()> {
2425        let parent_path = self.contents_path(parent);
2426        // remove from HASH
2427        let name = crypto::hash_file_name(name);
2428        let path = parent_path.join(HASH_DIR).join(name);
2429        let lock = self
2430            .serialize_dir_entries_hash_locks
2431            .get_or_insert_with(path.to_str().unwrap().to_owned(), || RwLock::new(false));
2432        let guard = lock.write().await;
2433        let (_, _, name): (u64, FileType, String) =
2434            bincode::deserialize_from(crypto::create_read(
2435                File::open(path.clone())?,
2436                self.cipher,
2437                &*self.key.get().await?,
2438            ))?;
2439        fs::remove_file(path)?;
2440        drop(guard);
2441        // remove from LS
2442        let path = parent_path.join(LS_DIR).join(name);
2443        let lock = self
2444            .serialize_dir_entries_ls_locks
2445            .get_or_insert_with(path.to_str().unwrap().to_owned(), || RwLock::new(false));
2446        let _guard = lock.write().await;
2447        fs::remove_file(path)?;
2448        Ok(())
2449    }
2450
2451    fn generate_next_inode(&self) -> u64 {
2452        loop {
2453            let ino = crypto::create_rng().next_u64();
2454
2455            if ino <= ROOT_INODE {
2456                continue;
2457            }
2458            if self.exists(ino) {
2459                continue;
2460            }
2461
2462            return ino;
2463        }
2464    }
2465}
2466pub struct CopyFileRangeReq {
2467    src_ino: u64,
2468    src_offset: u64,
2469    dest_ino: u64,
2470    dest_offset: u64,
2471    src_fh: u64,
2472    dest_fh: u64,
2473}
2474
2475#[bon]
2476impl CopyFileRangeReq {
2477    #[builder]
2478    pub fn new(
2479        src_ino: u64,
2480        src_offset: u64,
2481        dest_ino: u64,
2482        dest_offset: u64,
2483        src_fh: u64,
2484        dest_fh: u64,
2485    ) -> Self {
2486        Self {
2487            src_ino,
2488            src_offset,
2489            dest_ino,
2490            dest_offset,
2491            src_fh,
2492            dest_fh,
2493        }
2494    }
2495}
2496
2497fn read_or_create_key(
2498    key_path: &PathBuf,
2499    salt_path: &PathBuf,
2500    password: &SecretString,
2501    cipher: Cipher,
2502) -> FsResult<SecretVec<u8>> {
2503    let salt = if salt_path.exists() {
2504        bincode::deserialize_from(File::open(salt_path)?).map_err(|_| FsError::InvalidPassword)?
2505    } else {
2506        let mut salt = vec![0; 16];
2507        crypto::create_rng().fill_bytes(&mut salt);
2508        let mut file = OpenOptions::new()
2509            .read(true)
2510            .write(true)
2511            .create(true)
2512            .truncate(true)
2513            .open(salt_path)?;
2514        bincode::serialize_into(&mut file, &salt)?;
2515        file.flush()?;
2516        file.sync_all()?;
2517        File::open(salt_path.parent().expect("oops, we don't have a parent"))?.sync_all()?;
2518        salt
2519    };
2520    // derive key from password
2521    let derived_key = crypto::derive_key(password, cipher, &salt)?;
2522    if key_path.exists() {
2523        // read key
2524        let reader = crypto::create_read(File::open(key_path)?, cipher, &derived_key);
2525        let key: Vec<u8> =
2526            bincode::deserialize_from(reader).map_err(|_| FsError::InvalidPassword)?;
2527        Ok(SecretBox::new(Box::new(key)))
2528    } else {
2529        // first time, create a random key and encrypt it with the derived key from password
2530        let mut key: Vec<u8> = vec![];
2531        let key_len = cipher.key_len();
2532        key.resize(key_len, 0);
2533        crypto::create_rng().fill_bytes(&mut key);
2534        let mut writer = crypto::create_write(
2535            OpenOptions::new()
2536                .read(true)
2537                .write(true)
2538                .create(true)
2539                .truncate(true)
2540                .open(key_path)?,
2541            cipher,
2542            &derived_key,
2543        );
2544        bincode::serialize_into(&mut writer, &key)?;
2545        let file = writer.finish()?;
2546        file.sync_all()?;
2547        File::open(key_path.parent().unwrap())?.sync_all()?;
2548        Ok(SecretBox::new(Box::new(key)))
2549    }
2550}
2551
2552async fn ensure_structure_created(data_dir: &PathBuf) -> FsResult<()> {
2553    if data_dir.exists() {
2554        check_structure(data_dir, true).await?;
2555    } else {
2556        fs::create_dir_all(data_dir)?;
2557    }
2558
2559    // create directories
2560    let dirs = vec![INODES_DIR, CONTENTS_DIR, SECURITY_DIR];
2561    for dir in dirs {
2562        let path = data_dir.join(dir);
2563        if !path.exists() {
2564            fs::create_dir_all(path)?;
2565        }
2566    }
2567
2568    Ok(())
2569}
2570
2571async fn check_structure(data_dir: &Path, ignore_empty: bool) -> FsResult<()> {
2572    if !data_dir.exists() || !data_dir.is_dir() {
2573        return Err(FsError::InvalidDataDirStructure);
2574    }
2575    let mut vec = ReadDirStream::new(tokio::fs::read_dir(data_dir).await?)
2576        .try_collect::<Vec<_>>()
2577        .await?
2578        .iter()
2579        .map(|dir| dir.file_name().to_string_lossy().to_string())
2580        .collect::<Vec<String>>();
2581    if vec.is_empty() && ignore_empty {
2582        return Ok(());
2583    }
2584    if vec.len() != 3 {
2585        return Err(FsError::InvalidDataDirStructure);
2586    }
2587    // make sure existing structure is ok
2588    vec.sort_unstable();
2589    let mut vec2 = vec![INODES_DIR, CONTENTS_DIR, SECURITY_DIR];
2590    vec2.sort_unstable();
2591    if vec != vec2
2592        || !data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME).is_file()
2593        || !data_dir
2594            .join(SECURITY_DIR)
2595            .join(KEY_SALT_FILENAME)
2596            .is_file()
2597    {
2598        return Err(FsError::InvalidDataDirStructure);
2599    }
2600
2601    Ok(())
2602}
2603
2604fn merge_attr(attr: &mut FileAttr, set_attr: &SetFileAttr, overwrite_size: bool) {
2605    if let Some(size) = set_attr.size {
2606        if overwrite_size {
2607            attr.size = size;
2608        } else {
2609            attr.size = attr.size.max(size);
2610        }
2611    }
2612    if let Some(atime) = set_attr.atime {
2613        attr.atime = attr.atime.max(atime);
2614    }
2615    if let Some(mtime) = set_attr.mtime {
2616        attr.mtime = attr.mtime.max(mtime);
2617    }
2618    if let Some(ctime) = set_attr.ctime {
2619        attr.ctime = attr.ctime.max(ctime);
2620    }
2621    if let Some(crtime) = set_attr.crtime {
2622        attr.crtime = attr.crtime.max(crtime);
2623    }
2624    if let Some(perm) = set_attr.perm {
2625        attr.perm = perm;
2626    }
2627    if let Some(uid) = set_attr.uid {
2628        attr.uid = uid;
2629    }
2630    if let Some(gid) = set_attr.gid {
2631        attr.gid = gid;
2632    }
2633    if let Some(flags) = set_attr.flags {
2634        attr.flags = flags;
2635    }
2636}
2637
2638pub async fn write_all_string_to_fs(
2639    fs: &EncryptedFs,
2640    ino: u64,
2641    offset: u64,
2642    s: &str,
2643    fh: u64,
2644) -> FsResult<()> {
2645    write_all_bytes_to_fs(fs, ino, offset, s.as_bytes(), fh).await
2646}
2647
2648#[allow(clippy::missing_panics_doc)]
2649pub async fn write_all_bytes_to_fs(
2650    fs: &EncryptedFs,
2651    ino: u64,
2652    offset: u64,
2653    buf: &[u8],
2654    fh: u64,
2655) -> FsResult<()> {
2656    let mut pos = 0_usize;
2657    loop {
2658        let len = fs.write(ino, offset, &buf[pos..], fh).await?;
2659        pos += len;
2660        if pos == buf.len() {
2661            break;
2662        } else if len == 0 {
2663            return Err(FsError::Other("Failed to write all bytes"));
2664        }
2665    }
2666    fs.flush(fh).await?;
2667    Ok(())
2668}