bijou/bijou/
mod.rs

1// Copyright 2023 Mivik
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15
16mod file;
17mod fs;
18
19pub use file::File;
20pub use fs::BijouFs;
21
22#[cfg(feature = "fuse")]
23mod fuse;
24#[cfg(feature = "fuse")]
25pub use fuse::BijouFuse;
26
27use crate::{
28    algo::Algorithm,
29    anyhow, bail,
30    crypto::{cast_key, crypto_error, split_nonce_tag, xchacha20_siv},
31    db::{consts, Database, DatabaseKey, RawKeyType},
32    error::ResultExt,
33    fs::{
34        config::Config, obtain_metadata, path::Component, DirItem, FileKind, Inode, LowLevelFile,
35        RawFileMeta, RawFileSystem, UnixPerms,
36    },
37    id_lock::IdLock,
38    path::Path,
39    serde_ext,
40    sodium::{
41        aead::XCHACHA20_POLY1305_IETF as AEAD,
42        kdf::BLAKE2B as KDF,
43        pwhash::{Limit, ARGON2_ID13 as PWHASH},
44        utils,
45    },
46    Context, ErrorKind, FileId, FileMeta, OpenOptions, Result, SecretBytes,
47};
48use bijou_rocksdb::{
49    DBIteratorWithThreadMode, DBPinnableSlice, DBWithThreadMode, Direction, IteratorMode,
50    ReadOptions, SingleThreaded, WriteBatch,
51};
52use chrono::{DateTime, Utc};
53use dashmap::DashMap;
54use ring::{
55    error::Unspecified,
56    hkdf::{self, KeyType, Prk},
57};
58use serde::{Deserialize, Serialize};
59use std::{
60    path::{Path as StdPath, PathBuf as StdPathBuf},
61    sync::{atomic::AtomicU32, Arc},
62};
63use tracing::{info, trace};
64
65pub const SYMBOLIC_MAX_DEPTH: u32 = 40;
66
67#[derive(Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69struct KeyStore {
70    version: u32,
71
72    #[serde(with = "serde_ext::base64")]
73    salt: [u8; PWHASH.salt_len],
74    #[serde(with = "serde_ext::base64")]
75    nonce: [u8; AEAD.nonce_len],
76    #[serde(with = "serde_ext::base64")]
77    tag: [u8; AEAD.tag_len],
78
79    ops_limit: usize,
80    mem_limit: usize,
81
82    #[serde(with = "serde_ext::base64")]
83    master_key: [u8; KDF.key_len],
84}
85
86/// The main Bijou interface providing low level APIs.
87///
88/// For high level usage, see [`BijouFs`] and [`BijouFuse`].
89pub struct Bijou {
90    path: StdPathBuf,
91
92    db: Arc<Database>,
93    raw_fs: Arc<dyn RawFileSystem + Send + Sync>,
94    algo: Arc<dyn Algorithm + Send + Sync>,
95
96    config: Config,
97
98    content_key: hkdf::Prk,
99    file_name_key: Option<SecretBytes>,
100
101    /// For files, this is acquired whenever the file is being
102    /// read/written. Note that this is not necessarily acquired
103    /// when the file is being opened. This conforms to the typical
104    /// Unix semantics.
105    ///
106    /// For directories, this is acquired when its children are
107    /// being modified (add, unlink, etc.).
108    file_lock: Arc<IdLock<RawFileMeta>>,
109
110    /// The currently opened file handles count for each file.
111    ///
112    /// The GC thread will periodically check files in the GC pool.
113    /// If the file doesn't have opened handles anymore, the GC thread
114    /// will remove it.
115    file_open_counts: Arc<DashMap<FileId, Arc<AtomicU32>>>,
116}
117
118impl Bijou {
119    const KDF_CTX: [u8; 8] = *b"@bijoufs";
120
121    /// Create a new Bijou.
122    ///
123    /// The `path` should either be an empty directory or non-existent.
124    ///
125    /// `password` should be convertible to [`SecretBytes`] (e.g.
126    /// [`Vec<u8>`]). Otherwise, you may use [`SecretBytes::move_from`]
127    /// to create a [`SecretBytes`] from a mutable byte slice. This
128    /// is to prevent the password from being copied around in memory.
129    /// For more details, see [`SecretBytes`].
130    pub fn create(
131        path: impl AsRef<StdPath>,
132        password: impl Into<SecretBytes>,
133        config: Config,
134        ops_limit: Limit,
135        mem_limit: Limit,
136    ) -> Result<()> {
137        info!("creating Bijou");
138
139        let password = password.into();
140
141        let path = path.as_ref();
142        if path.exists() {
143            if !path.is_dir() || path.read_dir().wrap()?.next().is_some() {
144                bail!(@AlreadyExists "not an empty directory: {}", path.display());
145            }
146        } else {
147            std::fs::create_dir(path)
148                .context("failed to create directory")
149                .kind(ErrorKind::AlreadyExists)?;
150        }
151
152        // This is not made into SecretBytes because we'll encrypt it inplace later.
153        let master_key = KDF.gen_key();
154        let prk = KDF.prk(master_key.clone(), Self::KDF_CTX.as_slice());
155        let config_key = prk.derive(0, AEAD.key_len)?;
156
157        let salt = utils::gen_rand_bytes::<{ PWHASH.salt_len }>();
158
159        let mut key = [0; AEAD.key_len];
160        PWHASH.derive_key(&mut key, &password, &salt, ops_limit, mem_limit)?;
161        drop(password);
162        let nonce = utils::gen_rand_bytes::<{ AEAD.nonce_len }>();
163        let mut tag = [0; AEAD.tag_len];
164
165        let mut encrypted_master_key = [0; KDF.key_len];
166        AEAD.encrypt(
167            &mut encrypted_master_key,
168            &mut tag,
169            &master_key,
170            Some(b"bijou"),
171            &nonce,
172            &key,
173        )?;
174        drop(master_key);
175
176        let keystore = KeyStore {
177            version: 0,
178
179            salt,
180            nonce,
181            tag,
182
183            ops_limit: ops_limit.eval(PWHASH.ops_limits),
184            mem_limit: mem_limit.eval(PWHASH.mem_limits),
185
186            master_key: encrypted_master_key,
187        };
188        (|| {
189            serde_json::to_writer_pretty(
190                std::fs::File::create(path.join("keystore.json")).wrap()?,
191                &keystore,
192            )
193            .wrap()
194        })()
195        .context("failed to save keystore.json")?;
196
197        let mut bytes = serde_json::to_vec(&config).wrap()?;
198        let nonce = utils::gen_rand_bytes::<{ AEAD.nonce_len }>();
199        let mut tag = [0; AEAD.tag_len];
200        AEAD.encrypt_inplace(&mut bytes, &mut tag, &nonce, None, &config_key)?;
201        drop(config_key);
202        bytes = nonce
203            .into_iter()
204            .chain(bytes.into_iter())
205            .chain(tag.into_iter())
206            .collect::<Vec<_>>();
207        std::fs::write(path.join("config.json"), bytes).context("failed to save config.json")?;
208
209        Ok(())
210    }
211
212    /// Open an existing Bijou.
213    ///
214    /// `password` should be convertible to [`SecretBytes`] (e.g.
215    /// [`Vec<u8>`]). Otherwise, you may use [`SecretBytes::move_from`]
216    /// to create a [`SecretBytes`] from a mutable byte slice. This
217    /// is to prevent the password from being copied around in memory.
218    /// For more details, see [`SecretBytes`].
219    pub fn open(path: impl Into<StdPathBuf>, password: impl Into<SecretBytes>) -> Result<Self> {
220        let password = password.into();
221
222        let path = path.into();
223        if !path.is_dir() {
224            bail!(@NotFound "directory not found: {}", path.display());
225        }
226
227        let file_lock = Arc::default();
228
229        let mut keystore: KeyStore = (|| {
230            serde_json::from_reader(std::fs::File::open(path.join("keystore.json")).wrap()?).wrap()
231        })()
232        .context("failed to read keystore.json")?;
233        if keystore.version > 0 {
234            bail!(@IncompatibleVersion "keystore version {} is not supported", keystore.version);
235        }
236
237        let mut key = [0; AEAD.key_len];
238        PWHASH.derive_key(
239            &mut key,
240            &password,
241            &keystore.salt,
242            Limit::Custom(keystore.ops_limit),
243            Limit::Custom(keystore.mem_limit),
244        )?;
245
246        let mut master_key: SecretBytes = SecretBytes::move_from(&mut keystore.master_key);
247        AEAD.decrypt_inplace(
248            &mut master_key,
249            &keystore.tag,
250            Some(b"bijou"),
251            &keystore.nonce,
252            &key,
253        )
254        .context("incorrect password")?;
255        let mk = KDF.prk(master_key, Self::KDF_CTX.as_slice());
256
257        let config_key = mk.derive(0, AEAD.key_len)?;
258        let content_key_bytes = mk.derive(1, hkdf::KeyType::len(&hkdf::HKDF_SHA256))?;
259
260        let content_key = Prk::new_less_safe(hkdf::HKDF_SHA256, &content_key_bytes);
261        drop(content_key_bytes);
262
263        let mut config =
264            std::fs::read(path.join("config.json")).context("failed to read config.json")?;
265        // Safety
266        //
267        // libsodium uses char* under the hood, which
268        // does not require any alignment guarantees.
269        let (nonce, config, tag) = split_nonce_tag(&mut config, AEAD.nonce_len, AEAD.tag_len);
270        AEAD.decrypt_inplace(config, tag, None, nonce, &config_key)?;
271        drop(config_key);
272        let config: Config = serde_json::from_slice(config).context("failed to parse config")?;
273
274        info!("config: {config:?}");
275
276        let file_name_key = if config.encrypt_file_name {
277            Some(mk.derive(2, hkdf::KeyType::len(&hkdf::HKDF_SHA256))?)
278        } else {
279            None
280        };
281
282        let db_key = if config.encrypt_db {
283            Some(mk.derive(3, Database::KEYBYTES)?)
284        } else {
285            None
286        };
287
288        let data_dir = path.join("data");
289        if !data_dir.is_dir() {
290            std::fs::create_dir_all(&data_dir).context("failed to create data directory")?;
291        }
292
293        let db = Arc::new(Database::open(path.join("db"), db_key)?);
294        let raw_fs = config
295            .storage
296            .build(&db, &data_dir)
297            .context("failed to build storage")?;
298
299        info!("launching Bijou");
300
301        let file_open_counts = Arc::new(DashMap::<FileId, Arc<AtomicU32>>::new());
302
303        let mut result = Self {
304            path,
305
306            db,
307            raw_fs,
308            algo: config.to_algorithm()?,
309
310            config,
311
312            content_key,
313            file_name_key,
314
315            file_lock,
316            file_open_counts,
317        };
318        result.init()?;
319        Ok(result)
320    }
321
322    /// Returns the local path of this Bijou.
323    pub fn path(&self) -> &StdPath {
324        &self.path
325    }
326
327    fn child_key<T>(&self, key: DatabaseKey<T>, name: &str) -> Result<DatabaseKey<DirItem>> {
328        if let Some(file_name_key) = &self.file_name_key {
329            if name != "." && name != ".." {
330                // TODO cache
331                let mut name = name.as_bytes().to_vec();
332                let tag = xchacha20_siv::encrypt_detached(
333                    &mut name,
334                    key.key.as_slice(),
335                    cast_key(file_name_key),
336                )
337                .map_err(crypto_error)?;
338                name.extend(tag.0);
339                return Ok(key.derive(consts::DIR_DERIVE).derive(&name).typed());
340            }
341        }
342
343        Ok(key
344            .derive(consts::DIR_DERIVE)
345            .derive(name.as_bytes())
346            .typed())
347    }
348
349    fn init(&mut self) -> Result<()> {
350        let root_id = FileId::ROOT;
351        let root_key = self.get_key(root_id);
352        if !root_key.exists()? {
353            let now = Utc::now();
354            let attrs = FileMeta {
355                id: root_id,
356                kind: FileKind::Directory,
357
358                size: 0,
359
360                accessed: now,
361                modified: now,
362
363                nlinks: 2,
364
365                perms: if self.config.unix_perms {
366                    Some(UnixPerms {
367                        mode: 0o755,
368                        uid: 0,
369                        gid: 0,
370                    })
371                } else {
372                    None
373                },
374            };
375
376            let mut batch = self.db.batch();
377            root_key.put_batch(&mut batch, &attrs)?;
378            self.child_key(root_key.clone(), ".")?.put_batch(
379                &mut batch,
380                &DirItem {
381                    id: root_id,
382                    kind: FileKind::Directory,
383                },
384            )?;
385            self.child_key(root_key, "..")?.put_batch(
386                &mut batch,
387                &DirItem {
388                    id: root_id,
389                    kind: FileKind::Directory,
390                },
391            )?;
392
393            batch.commit()?;
394        }
395
396        Ok(())
397    }
398
399    /// Returns the root inode.
400    pub fn root(&self) -> Inode {
401        Inode::ROOT
402    }
403
404    fn allocate_id(&self) -> Result<FileId> {
405        let mut id = FileId::gen();
406
407        while self.get_key(id).exists()? {
408            // Unlikely
409            id = FileId::gen();
410        }
411        Ok(id)
412    }
413
414    /// Looks up a file by name.
415    ///
416    /// Returns the inode and its generation.
417    pub fn lookup(&self, parent: FileId, name: &str) -> Result<FileId> {
418        Ok(self
419            .child_key(self.get_key(parent), name)?
420            .get()?
421            .kind(ErrorKind::NotFound)?
422            .id)
423    }
424
425    fn get_key(&self, file: FileId) -> DatabaseKey<FileMeta> {
426        self.db.key(consts::FILE_ROOT).derive(file).typed()
427    }
428
429    fn get_raw_meta(&self, key: &DatabaseKey<FileMeta>) -> Result<FileMeta> {
430        key.get()?.kind(ErrorKind::NotFound)
431    }
432
433    /// Returns the metadata of the given file.
434    pub fn get_meta(&self, file: FileId) -> Result<FileMeta> {
435        obtain_metadata(&self.get_key(file), self.algo.as_ref(), || {
436            self.raw_fs.stat(file)
437        })
438    }
439
440    /// Creates a new file (or directory, symlink, etc.).
441    ///
442    /// `symlink` must not be `None` if `kind` is `FileKind::Symlink`.
443    pub fn make_node(
444        &self,
445        parent: FileId,
446        name: &str,
447        kind: FileKind,
448        symlink: Option<String>,
449        perms: Option<UnixPerms>,
450    ) -> Result<FileMeta> {
451        trace!(%parent, name, ?kind, "make node");
452        let lock = self.file_lock.get(parent);
453        let _guard = lock.write().unwrap();
454
455        let mut batch = self.db.batch();
456
457        let parent_key = self.get_key(parent);
458        let child_key = self.child_key(parent_key.clone(), name)?;
459        if child_key.exists()? {
460            bail!(@AlreadyExists? "file already exists: {name}");
461        }
462
463        let now = Utc::now();
464
465        let mut parent_meta = self.get_raw_meta(&parent_key)?;
466        parent_meta.modified = now;
467        parent_meta.nlinks += (kind == FileKind::Directory) as u32;
468        parent_key.put_batch(&mut batch, &parent_meta)?;
469
470        let id = self.allocate_id()?;
471        let key = self.get_key(id);
472        let meta = FileMeta {
473            id,
474            kind,
475
476            size: 0,
477
478            accessed: now,
479            modified: now,
480
481            nlinks: if kind == FileKind::Directory { 2 } else { 1 },
482
483            perms: perms.filter(|_| self.config.unix_perms),
484        };
485        key.put_batch(&mut batch, &meta)?;
486
487        match kind {
488            FileKind::Directory => {
489                self.child_key(key.clone(), ".")?.put_batch(
490                    &mut batch,
491                    &DirItem {
492                        id,
493                        kind: FileKind::Directory,
494                    },
495                )?;
496                self.child_key(key, "..")?.put_batch(
497                    &mut batch,
498                    &DirItem {
499                        id: parent,
500                        kind: FileKind::Directory,
501                    },
502                )?;
503            }
504            FileKind::Symlink => {
505                let Some(target) = symlink else {
506                    bail!(@InvalidInput "symlink target must not be None");
507                };
508                key.derive(consts::SYMLINK_DERIVE)
509                    .typed::<String>()
510                    .put_batch(&mut batch, &target)?;
511            }
512            _ => {}
513        }
514
515        child_key.put_batch(
516            &mut batch,
517            &DirItem {
518                id,
519                kind: meta.kind,
520            },
521        )?;
522
523        batch.commit()?;
524
525        if kind == FileKind::File {
526            self.raw_fs.create(id)?;
527        }
528
529        Ok(meta)
530    }
531
532    /// Creates a hard link for the given file.
533    pub fn link(&self, file: FileId, parent: FileId, name: &str) -> Result<FileMeta> {
534        trace!(%parent, name, "link");
535
536        let lock = self.file_lock.get(parent);
537        let _guard = lock.write().unwrap();
538
539        let mut batch = self.db.batch();
540
541        let key = self.get_key(file);
542        let mut meta = self.get_raw_meta(&key)?;
543        if meta.kind == FileKind::Directory {
544            bail!(@InvalidInput? "creating hard link to directory");
545        }
546        meta.nlinks += 1;
547        key.put_batch(&mut batch, &meta)?;
548
549        let parent_key = self.get_key(parent);
550        let child_key = self.child_key(parent_key, name)?;
551        if child_key.exists()? {
552            bail!(@AlreadyExists? "file already exists: {name}");
553        }
554        child_key.put_batch(
555            &mut batch,
556            &DirItem {
557                id: file,
558                kind: meta.kind,
559            },
560        )?;
561
562        batch.commit()?;
563
564        Ok(meta)
565    }
566
567    fn derive_key(&self, file: FileId) -> Result<SecretBytes> {
568        let mut bytes = SecretBytes::allocate(self.algo.key_size());
569        struct DummyKey(usize);
570        impl KeyType for DummyKey {
571            fn len(&self) -> usize {
572                self.0
573            }
574        }
575        (|| -> Result<(), Unspecified> {
576            self.content_key
577                .expand(&[file.as_ref()], DummyKey(self.algo.key_size()))?
578                .fill(&mut bytes)
579        })()
580        .map_err(|_| anyhow!(@CryptoError "failed to derive key"))?;
581
582        Ok(bytes)
583    }
584
585    fn open_inner(&self, meta: FileMeta, options: &OpenOptions) -> Result<LowLevelFile> {
586        let flags = options.to_flags();
587        let raw_file = self
588            .raw_fs
589            .open(meta.id, options.clone().read(true).to_flags())?;
590        let key = self.get_key(meta.id);
591
592        Ok(LowLevelFile::new(
593            raw_file,
594            Arc::clone(&self.algo),
595            self.algo.key(self.derive_key(meta.id)?)?,
596            key,
597            flags,
598            self.file_lock
599                .get_or_try_insert(meta.id, || self.raw_fs.stat(meta.id))?,
600            Arc::clone(&self.file_open_counts.entry(meta.id).or_default()),
601        ))
602    }
603
604    /// Opens a file directly.
605    ///
606    /// As its parameters imply, this method **does not support
607    /// creating the file**.
608    ///
609    /// See also [`open_file`].
610    ///
611    /// [`open_file`]: Bijou::open_file
612    pub fn open_file_direct(&self, file: FileId, options: &OpenOptions) -> Result<LowLevelFile> {
613        let meta = self.get_raw_meta(&self.get_key(file))?;
614        self.open_inner(meta, options)
615    }
616
617    /// Opens a file, and creates it if necessary.
618    ///
619    /// See also [`open_file_direct`].
620    ///
621    /// [`open_file_direct`]: Bijou::open_file_direct
622    pub fn open_file(
623        &self,
624        parent: FileId,
625        name: &str,
626        options: &OpenOptions,
627        perms: Option<UnixPerms>,
628    ) -> Result<LowLevelFile> {
629        if options.truncate && !options.write {
630            bail!(@InvalidInput? "cannot specify truncate without write")
631        }
632        match self.child_key(self.get_key(parent), name)?.get()? {
633            Some(item) => {
634                if options.create_new {
635                    bail!(@AlreadyExists? "requiring create_new but file already exists: {name}");
636                }
637                self.open_file_direct(item.id, options)
638            }
639            None => {
640                if options.create || options.create_new {
641                    let meta = self.make_node(parent, name, FileKind::File, None, perms)?;
642                    self.open_inner(meta, options)
643                } else {
644                    bail!(@NotFound? "file not found: {name}");
645                }
646            }
647        }
648    }
649
650    pub(crate) fn resolve_inner(
651        &self,
652        mut stack: Vec<FileId>,
653        path: &Path,
654        depth: &mut u32,
655    ) -> Result<FileId> {
656        for comp in path.components() {
657            match comp {
658                Component::RootDir => {
659                    stack.truncate(1);
660                }
661                Component::CurDir => {}
662                Component::ParentDir => {
663                    if stack.len() > 1 {
664                        stack.pop();
665                    }
666                }
667                other => {
668                    let id = self.lookup(*stack.last().unwrap(), other.as_str())?;
669                    let id = match self.read_link(id) {
670                        Ok(path) => {
671                            *depth += 1;
672                            if *depth > SYMBOLIC_MAX_DEPTH {
673                                bail!(@FilesystemLoop? "too many levels of symbolic links");
674                            }
675                            // symlink
676                            self.resolve_inner(stack.clone(), Path::new(&path), depth)?
677                        }
678                        Err(err) if err.kind() == ErrorKind::InvalidInput => {
679                            // not a symlink
680                            id
681                        }
682                        Err(err) => return Err(err),
683                    };
684                    stack.push(id);
685                }
686            }
687        }
688
689        Ok(stack.into_iter().rev().next().unwrap())
690    }
691
692    /// Resolves a path to a file.
693    pub fn resolve(&self, path: impl AsRef<Path>) -> Result<FileId> {
694        self.resolve_inner(vec![FileId::ROOT], path.as_ref(), &mut 0)
695    }
696
697    /// Resolves a path, returning its parent and its name.
698    ///
699    /// If the path is `/`, returns `(FileId::ROOT, None)`.
700    ///
701    /// Different from [`resolve`], this method does not require
702    /// the path to exist.
703    ///
704    /// [`resolve`]: Bijou::resolve
705    pub fn resolve_parent<'a>(&self, path: &'a Path) -> Result<(FileId, Option<&'a str>)> {
706        let mut stack = vec![(FileId::ROOT, "")];
707        let mut current_name = None;
708        let mut symlink_depth = 0;
709        for comp in path.components() {
710            match comp {
711                Component::RootDir => {
712                    stack.truncate(1);
713                    current_name = None;
714                }
715                Component::CurDir => {}
716                Component::ParentDir => {
717                    if stack.len() == 1 {
718                        current_name = None;
719                    } else {
720                        current_name = Some(stack.pop().unwrap().1);
721                    }
722                }
723                Component::Normal(name) => {
724                    if let Some(parent_name) = current_name {
725                        let parent = self.resolve_inner(
726                            stack.iter().map(|it| it.0).collect(),
727                            Path::new(parent_name),
728                            &mut symlink_depth,
729                        )?;
730                        stack.push((parent, parent_name));
731                    }
732                    current_name = Some(name);
733                }
734            }
735        }
736        Ok((stack.into_iter().rev().next().unwrap().0, current_name))
737    }
738
739    /// Resolves a path, returning its parent and its name.
740    ///
741    /// Different from [`resolve_parent`], this will throw an error
742    /// if the path is `/`.
743    ///
744    /// [`resolve_parent`]: Bijou::resolve_parent
745    pub fn resolve_parent_nonroot<'a>(&self, path: &'a Path) -> Result<(FileId, &'a str)> {
746        let (parent, name) = self.resolve_parent(path)?;
747        let Some(name) = name else {
748            bail!(@InvalidInput "expected non-root path, got `{path}`");
749        };
750        Ok((parent, name))
751    }
752
753    /// Returns an iterator of the entries of the given directory.
754    ///
755    /// Note that [`DirIterator::reset`] must be called before
756    /// the iterator is used.
757    ///
758    /// The content will only be updated when the iterator is reset.
759    /// Before that, the content is a snapshot of the directory
760    /// at the time of the last call to [`DirIterator::reset`].
761    ///
762    /// The results include `.` and `..`.
763    pub fn read_dir(&self, id: FileId) -> Result<DirIterator> {
764        let key = self.get_key(id);
765        if key.get()?.kind(ErrorKind::NotFound)?.kind != FileKind::Directory {
766            bail!(@NotADirectory "not a directory");
767        }
768        let mut opts = ReadOptions::default();
769        opts.set_iterate_upper_bound(key.clone().derive(consts::DIR_DERIVE_UPPER).key.to_vec());
770        Ok(DirIterator {
771            key: key.derive(consts::DIR_DERIVE).key,
772            inner: self.db.0.iterator_opt(IteratorMode::Start, opts),
773            // inner: self.db.0.prefix_iterator(&key.derive(consts::DIR_DERIVE).key),
774            decrypt: self.file_name_key.as_ref().map(|key| (id, cast_key(key))),
775        })
776    }
777
778    fn unlink_inner(
779        &self,
780        batch: &mut WriteBatch,
781        parent: FileId,
782        name: &str,
783    ) -> Result<Option<FileId>> {
784        trace!(%parent, name, "unlink");
785
786        let child = self.lookup(parent, name)?;
787
788        let key = self.get_key(child);
789        let mut meta = self.get_raw_meta(&key)?;
790        let is_dir = meta.kind == FileKind::Directory;
791
792        if is_dir && self.read_dir(child)?.reset().nth(2).is_some() {
793            bail!(@NotEmpty? "trying to unlink non-empty directory: {name}");
794        }
795
796        let parent_key = self.get_key(parent);
797        let mut parent_meta = self.get_raw_meta(&parent_key)?;
798
799        parent_meta.modified = Utc::now();
800        parent_meta.nlinks -= is_dir as u32;
801        parent_key.put_batch(batch, &parent_meta)?;
802
803        self.child_key(parent_key, name)?.delete_batch(batch);
804
805        if meta.kind == FileKind::Directory {
806            meta.nlinks = 0;
807
808            self.child_key(key.clone(), ".")?.delete_batch(batch);
809            self.child_key(key.clone(), "..")?.delete_batch(batch);
810
811            // Directory can always be deleted directly
812            // since they don't have hardlinks.
813            key.delete_batch(batch);
814        } else {
815            // TODO can symlinks have hardlink?
816
817            // For files, we reduce its nlinks by 1.
818            // If it reaches zero, we put it into the GC pool.
819            assert!(meta.nlinks > 0);
820            meta.nlinks -= 1;
821
822            if meta.nlinks == 0 {
823                key.delete_batch(batch);
824                // batch.delete_range(
825                // key.clone().derive(consts::XATTR_DERIVE).key,
826                // key.clone().derive(consts::XATTR_DERIVE_UPPER).key,
827                // );
828                for item in key.range_iter(consts::XATTR_DERIVE, consts::XATTR_DERIVE_UPPER) {
829                    let item = item.wrap()?;
830                    batch.delete(&item.0);
831                }
832                if meta.kind == FileKind::Symlink {
833                    key.derive(consts::SYMLINK_DERIVE).delete_batch(batch);
834                } else {
835                    self.raw_fs.unlink(child)?;
836                }
837            } else {
838                key.put_batch(batch, &meta)?;
839            }
840        }
841
842        Ok(if meta.nlinks == 0 { Some(child) } else { None })
843    }
844
845    /// Unlinks a file.
846    ///
847    /// Returns the removed file if it is a file and has no more
848    /// hardlinks. Otherwise, returns `None`.
849    pub fn unlink(&self, parent: FileId, name: &str) -> Result<Option<FileId>> {
850        let parent_lock = self.file_lock.get(parent);
851        let _guard = parent_lock.write().unwrap();
852
853        let mut batch = self.db.batch();
854        let removed = self.unlink_inner(&mut batch, parent, name)?;
855        batch.commit()?;
856
857        Ok(removed)
858    }
859
860    /// Renames a file.
861    ///
862    /// Returns the removed file if it is a file and has no more
863    /// hardlinks. Otherwise, returns `None`.
864    pub fn rename(
865        &self,
866        parent: FileId,
867        name: &str,
868        new_parent: FileId,
869        new_name: &str,
870    ) -> Result<Option<FileId>> {
871        trace!(%parent, name, %new_parent, new_name, "rename");
872
873        if parent == new_parent && name == new_name {
874            return Ok(None);
875        }
876
877        let parent_key = self.get_key(parent);
878        let new_parent_key = self.get_key(new_parent);
879
880        let parent_lock = self.file_lock.get(parent);
881        let new_parent_lock = self.file_lock.get(new_parent);
882        let _guard = parent_lock.write().unwrap();
883        let _guard2 = if parent == new_parent {
884            None
885        } else {
886            Some(new_parent_lock.write().unwrap())
887        };
888
889        let mut batch = self.db.batch();
890
891        let old_child_dir_key = self.child_key(parent_key.clone(), name)?;
892        let new_child_dir_key = self.child_key(new_parent_key.clone(), new_name)?;
893
894        let dir_item = old_child_dir_key.get()?.kind(ErrorKind::NotFound)?;
895        let child = self.get_key(dir_item.id);
896        let meta = self.get_raw_meta(&child)?;
897
898        let mut removed = None;
899
900        if new_child_dir_key.exists()? {
901            removed = self.unlink_inner(&mut batch, new_parent, new_name)?;
902        }
903
904        old_child_dir_key.delete_batch(&mut batch);
905        new_child_dir_key.put_batch(&mut batch, &dir_item)?;
906
907        let now = Utc::now();
908
909        if meta.kind == FileKind::Directory {
910            self.child_key(child, "..")?.put_batch(
911                &mut batch,
912                &DirItem {
913                    id: new_parent,
914                    kind: FileKind::Directory,
915                },
916            )?;
917        }
918
919        let mut parent_meta = self.get_raw_meta(&parent_key)?;
920        parent_meta.nlinks -= (meta.kind == FileKind::Directory) as u32;
921        parent_meta.modified = now;
922        parent_key.put_batch(&mut batch, &parent_meta)?;
923
924        let mut new_parent_meta = self.get_raw_meta(&new_parent_key)?;
925        new_parent_meta.nlinks += (meta.kind == FileKind::Directory) as u32;
926        new_parent_meta.modified = now;
927        new_parent_key.put_batch(&mut batch, &new_parent_meta)?;
928
929        batch.commit()?;
930
931        Ok(removed)
932    }
933
934    /// Sets the size of a file.
935    ///
936    /// If `len` is larger than the current size, the file will be
937    /// extended with zeros. Otherwise, the file will be truncated.
938    pub fn set_len(&self, file: FileId, len: u64) -> Result<()> {
939        trace!(%file, len, "set length");
940        self.open_file_direct(file, OpenOptions::new().write(true))?
941            .set_len(len)
942    }
943
944    /// Reads the target of a symlink.
945    pub fn read_link(&self, file: FileId) -> Result<String> {
946        trace!(%file, "read link");
947        let key = self.get_key(file);
948        let meta = self.get_raw_meta(&key)?;
949        if meta.kind != FileKind::Symlink {
950            bail!(@InvalidInput? "not a symlink");
951        }
952
953        key.derive(consts::SYMLINK_DERIVE)
954            .typed::<String>()
955            .get()?
956            .kind(ErrorKind::NotFound)
957    }
958
959    /// Sets atime and mtime of a file.
960    pub fn set_times(
961        &self,
962        file: FileId,
963        accessed: DateTime<Utc>,
964        modified: DateTime<Utc>,
965    ) -> Result<()> {
966        let key = self.get_key(file);
967        let mut meta = self.get_raw_meta(&key)?;
968        meta.accessed = accessed;
969        meta.modified = modified;
970        key.put(&meta)?;
971
972        Ok(())
973    }
974
975    /// Sets the permissions of a file.
976    pub fn set_perms(
977        &self,
978        id: FileId,
979        mode: Option<u16>,
980        uid: Option<u32>,
981        gid: Option<u32>,
982    ) -> Result<()> {
983        let key = self.get_key(id);
984        let mut meta = self.get_raw_meta(&key)?;
985        meta.perms = Some(UnixPerms {
986            mode: mode
987                .or_else(|| meta.perms.as_ref().map(|it| it.mode))
988                .unwrap_or(0o640),
989            uid: uid
990                .or_else(|| meta.perms.as_ref().map(|it| it.uid))
991                .unwrap_or(0),
992            gid: gid
993                .or_else(|| meta.perms.as_ref().map(|it| it.gid))
994                .unwrap_or(0),
995        });
996        key.put(&meta)?;
997
998        Ok(())
999    }
1000
1001    /// Sets extended attribute (xattr) of a file.
1002    pub fn set_xattr(&self, id: FileId, name: &str, value: &[u8]) -> Result<()> {
1003        self.get_key(id)
1004            .derive(consts::XATTR_DERIVE)
1005            .derive(name)
1006            .write(value)
1007    }
1008
1009    /// Returns extended attribute (xattr) of a file.
1010    pub fn get_xattr<R>(
1011        &self,
1012        id: FileId,
1013        name: &str,
1014        cb: impl FnOnce(Result<Option<DBPinnableSlice>>) -> R,
1015    ) -> R {
1016        if self.config.disable_xattr_gets {
1017            return cb(Err(anyhow!(@Unsupported "xattr gets are disabled")));
1018        }
1019        cb(self
1020            .get_key(id)
1021            .derive(consts::XATTR_DERIVE)
1022            .derive(name)
1023            .read())
1024    }
1025
1026    /// Removes extended attribute (xattr) of a file.
1027    pub fn remove_xattr(&self, id: FileId, name: &str) -> Result<()> {
1028        self.get_key(id)
1029            .derive(consts::XATTR_DERIVE)
1030            .derive(name)
1031            .delete()
1032    }
1033
1034    // TODO cache
1035    /// Returns all extended attributes (xattr) of a file.
1036    pub fn xattrs(&self, id: FileId) -> Result<Vec<String>> {
1037        let mut result = Vec::new();
1038        let key = self.get_key(id);
1039        let iter = key.range_iter(consts::XATTR_DERIVE, consts::XATTR_DERIVE_UPPER);
1040        let len =
1041            consts::FILE_ROOT.len() + std::mem::size_of::<FileId>() + consts::XATTR_DERIVE.len();
1042        for entry in iter {
1043            let (key, _value) = entry.wrap()?;
1044            let name = &key[len..];
1045            result.push(String::from_utf8(name.to_vec()).unwrap());
1046        }
1047
1048        Ok(result)
1049    }
1050}
1051
1052/// Iterator of directory entries, created by [`Bijou::read_dir`].
1053pub struct DirIterator<'db> {
1054    key: RawKeyType,
1055    inner: DBIteratorWithThreadMode<'db, DBWithThreadMode<SingleThreaded>>,
1056    decrypt: Option<(FileId, &'db xchacha20_siv::Key)>,
1057}
1058impl DirIterator<'_> {
1059    pub fn reset(&mut self) -> &mut Self {
1060        self.inner
1061            .set_mode(IteratorMode::From(&self.key, Direction::Forward));
1062        self
1063    }
1064}
1065impl Iterator for DirIterator<'_> {
1066    type Item = Result<(String, DirItem)>;
1067
1068    fn next(&mut self) -> Option<Self::Item> {
1069        self.inner.next().map(|result| {
1070            let (mut key, value) = result.wrap()?;
1071            let name = &mut key[consts::FILE_ROOT.len()
1072                + std::mem::size_of::<FileId>()
1073                + consts::DIR_DERIVE.len()..];
1074            if let Some((id, key)) = &self.decrypt {
1075                if name != b"." && name != b".." {
1076                    assert!(name.len() > xchacha20_siv::ABYTES);
1077                    let (name, tag) = name.split_at_mut(name.len() - xchacha20_siv::ABYTES);
1078                    xchacha20_siv::decrypt_inplace(name, cast_key(tag), id.as_ref(), key)
1079                        .map_err(|_| anyhow!(@CryptoError "failed to decrypt filename"))?;
1080                    return Ok((
1081                        String::from_utf8(name.to_vec()).unwrap(),
1082                        postcard::from_bytes(&value).wrap()?,
1083                    ));
1084                }
1085            }
1086            Ok((
1087                String::from_utf8(name.to_vec()).unwrap(),
1088                postcard::from_bytes(&value).wrap()?,
1089            ))
1090        })
1091    }
1092}