Răsfoiți Sursa

Implemented hard linking in Blocktree.

Matthew Carr 2 ani în urmă
părinte
comite
188a466136

+ 4 - 1
TODO.txt

@@ -98,4 +98,7 @@ Vec<u8> structs.
 Manually implement the Serialize trait for BlockMetaBody so that the secrets field can be lazily
 updated upon serialization if the secrets_struct field has been modified. In order to detect
 modifications, a new field with the serde(skip) attribute needs to be added to BlockMetaBody to
-store the hash of BlockMetaSecrets that was computed just after decryption. 
+store the hash of BlockMetaSecrets that was computed just after decryption. 
+
+- 24, 3, mdcarr941@gmail.com, 7dbb358,
+Move `BlockRecord.frags` into `BlockMetaSecrets`.

+ 38 - 1
crates/btfuse/src/main.rs

@@ -179,7 +179,7 @@ fn main() {
 mod test {
     use std::{
         ffi::{OsStr, OsString},
-        fs::{read, read_dir, remove_file, write, ReadDir},
+        fs::{hard_link, read, read_dir, remove_file, write, ReadDir},
         sync::mpsc::{channel, Receiver},
         thread::JoinHandle,
         time::Duration,
@@ -352,4 +352,41 @@ mod test {
         let expected: [&OsStr; 0] = [];
         assert!(file_names(read_dir(&mnt_path).expect("read_dir failed")).eq(expected))
     }
+
+    #[test]
+    fn hard_link_then_remove() {
+        const EXPECTED: &[u8] = b"And the lives we've reclaimed";
+        let name1 = OsStr::new("refugee_lyrics.txt");
+        let name2 = OsStr::new("rise_against_lyrics.txt");
+        let mut case = TestCase::new();
+        let mnt_path = case.mnt_path();
+        let path1 = mnt_path.join(name1);
+        let path2 = mnt_path.join(name2);
+        write(&path1, EXPECTED).expect("write failed");
+
+        hard_link(&path1, &path2).expect("hard_link failed");
+        remove_file(&path1).expect("remove_file failed");
+
+        let actual = read(&path2).expect("read failed");
+        assert_eq!(EXPECTED, actual);
+    }
+
+    #[test]
+    fn hard_link_then_remove_both() {
+        const EXPECTED: &[u8] = b"And the lives we've reclaimed";
+        let name1 = OsStr::new("refugee_lyrics.txt");
+        let name2 = OsStr::new("rise_against_lyrics.txt");
+        let mut case = TestCase::new();
+        let mnt_path = case.mnt_path();
+        let path1 = mnt_path.join(name1);
+        let path2 = mnt_path.join(name2);
+        write(&path1, EXPECTED).expect("write failed");
+
+        hard_link(&path1, &path2).expect("hard_link failed");
+        remove_file(&path1).expect("remove_file on path1 failed");
+        remove_file(&path2).expect("remove_file on path2 failed");
+
+        let expected: [&OsStr; 0] = [];
+        assert!(file_names(read_dir(&mnt_path).expect("read_dir failed")).eq(expected));
+    }
 }

+ 81 - 35
crates/btlib/src/blocktree.rs

@@ -24,8 +24,8 @@ mod private {
     };
 
     use crate::{
-        crypto::Creds, Block, BlockMeta, BlockOpenOptions, BlockPath, BoxInIoErr, DirEntry,
-        Directory, Epoch, Error, Result, ToStringInIoErr,
+        crypto::Creds, Block, BlockMeta, BlockOpenOptions, BlockPath, BlockRecord, BoxInIoErr,
+        DirEntry, DirEntryKind, Directory, Epoch, Error, Result, ToStringInIoErr,
     };
 
     type Inode = u64;
@@ -70,6 +70,13 @@ mod private {
             }
             Err(Error::custom(format!("unknown file type: 0o{value:0o}")))
         }
+
+        fn dir_entry_kind(self) -> DirEntryKind {
+            match self {
+                Self::Dir => DirEntryKind::Directory,
+                Self::Reg => DirEntryKind::File,
+            }
+        }
     }
 
     impl From<FileType> for libc::mode_t {
@@ -85,6 +92,12 @@ mod private {
         }
     }
 
+    impl From<FileType> for DirEntryKind {
+        fn from(value: FileType) -> Self {
+            value.dir_entry_kind()
+        }
+    }
+
     trait SeekFromExt {
         /// Converts a C-style `(whence, offset)` pair into a [SeekFrom] enum value.
         /// See the POSIX man page of `lseek` for more details.
@@ -318,7 +331,7 @@ mod private {
             self.value(handle)?.access_block_mut(cb)
         }
 
-        fn try_borrow_block<T, F: FnOnce(&mut Box<dyn Block>) -> io::Result<T>>(
+        fn borrow_block<T, F: FnOnce(&mut Box<dyn Block>) -> io::Result<T>>(
             &mut self,
             cb: F,
         ) -> io::Result<T> {
@@ -816,19 +829,11 @@ mod private {
                 .entries
                 .get(name)
                 .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
-            let inode = match entry {
-                DirEntry::File(entry) => entry.inode,
-                DirEntry::Directory(entry) => entry.inode,
-                DirEntry::Server(_) => {
-                    return Err(io::Error::new(
-                        io::ErrorKind::Unsupported,
-                        "can't lookup server entry",
-                    ))
-                }
-            };
+            let inode = entry.inode().ok_or_else(|| {
+                io::Error::new(io::ErrorKind::Unsupported, "can't lookup server entry")
+            })?;
             let stat = self.open_value(inode, block_path, |value| {
-                let stat =
-                    value.try_borrow_block(|block| Ok(block.meta_body().secrets()?.stat()))?;
+                let stat = value.borrow_block(|block| Ok(block.meta_body().secrets()?.stat()))?;
                 value.incr_lookup_count();
                 Ok(stat)
             })?;
@@ -901,7 +906,7 @@ mod private {
         ) -> io::Result<(Option<Self::Handle>, OpenOptions)> {
             debug!("Blocktree::opendir called on inode {inode}");
             let handle = self.access_value_mut(inode, |value| {
-                value.try_borrow_block(|block| {
+                value.borrow_block(|block| {
                     let ctx = AuthzContext::new(ctx, block.meta());
                     self.authorizer.can_exec(&ctx)?;
                     Ok(())
@@ -1192,15 +1197,16 @@ mod private {
 
                 block.seek(SeekFrom::Start(0))?;
                 let mut dir: Directory = read_from(block)?;
-                let inode = match dir.entries.remove(name) {
+                let entry = match dir.entries.remove(name) {
                     None => return Err(io::Error::from_raw_os_error(libc::ENOENT)),
-                    Some(entry) => entry.inode().ok_or_else(|| {
-                        io::Error::new(
-                            io::ErrorKind::InvalidInput,
-                            format!("name {name} does not refer to a file or directory"),
-                        )
-                    })?,
+                    Some(entry) => entry,
                 };
+                let inode = entry.inode().ok_or_else(|| {
+                    io::Error::new(
+                        io::ErrorKind::Other,
+                        "no inode associated with the given name",
+                    )
+                })?;
                 block.seek(SeekFrom::Start(0))?;
                 write_to(&dir, block)?;
 
@@ -1210,7 +1216,7 @@ mod private {
             })?;
             self.open_value(inode, block_path, |value| {
                 // We mark the block for deletion if `nlink` drops to zero.
-                value.delete = value.try_borrow_block(|block| {
+                value.delete = value.borrow_block(|block| {
                     let nlink = block.mut_meta_body().access_secrets(|secrets| {
                         secrets.nlink -= 1;
                         Ok(secrets.nlink)
@@ -1222,6 +1228,57 @@ mod private {
             })
         }
 
+        fn link(
+            &self,
+            ctx: &Context,
+            inode: Self::Inode,
+            newparent: Self::Inode,
+            newname: &CStr,
+        ) -> io::Result<Entry> {
+            debug!("Blocktree::link called for inode {inode}");
+            let newname = newname.to_str().box_err()?;
+            self.borrow_block(newparent, |block| {
+                let ctx = AuthzContext::new(ctx, block.meta());
+                self.authorizer.can_write(&ctx)?;
+
+                block.seek(SeekFrom::Start(0))?;
+                let mut dir: Directory = read_from(block)?;
+                if dir.entries.contains_key(newname) {
+                    return Err(io::Error::from_raw_os_error(libc::EEXIST));
+                }
+
+                let (file_type, attr) = self.access_value_mut(inode, |value| {
+                    let (file_type, attr) = value.borrow_block(|block| {
+                        let meta = block.mut_meta_body();
+                        let (mode, attr) = meta.access_secrets(|secrets| {
+                            secrets.nlink += 1;
+                            Ok((secrets.mode, secrets.attr()))
+                        })?;
+                        let file_type = FileType::from_value(mode)?;
+                        block.flush_meta()?;
+                        Ok((file_type, attr))
+                    })?;
+                    value.incr_lookup_count();
+                    Ok((file_type, attr))
+                })?;
+                let entry = match file_type {
+                    FileType::Reg => DirEntry::File(BlockRecord::new(inode)),
+                    FileType::Dir => DirEntry::Directory(BlockRecord::new(inode)),
+                };
+                dir.entries.insert(newname.to_owned(), entry);
+                block.seek(SeekFrom::Start(0))?;
+                write_to(&dir, block)?;
+                Ok(Entry {
+                    inode,
+                    generation: self.generation,
+                    attr: attr.into(),
+                    attr_flags: 0,
+                    attr_timeout: self.attr_timeout(),
+                    entry_timeout: self.entry_timeout(),
+                })
+            })
+        }
+
         //////////////////////////////////
         // METHODS WHICH ARE NOT SUPPORTED
         //////////////////////////////////
@@ -1320,17 +1377,6 @@ mod private {
             Self::not_supported()
         }
 
-        fn link(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _newparent: Self::Inode,
-            _newname: &CStr,
-        ) -> io::Result<Entry> {
-            debug!("Blocktree::link called for inode {inode}");
-            Self::not_supported()
-        }
-
         fn listxattr(
             &self,
             _ctx: &Context,

+ 3 - 2
crates/btlib/src/crypto/mod.rs

@@ -5,8 +5,8 @@ pub mod secret_stream;
 pub use secret_stream::SecretStream;
 
 use crate::{
-    fmt, io, BigArray, BlockMeta, BlockPath, Deserialize, Display, Epoch, Formatter, Hashable,
-    Principal, Principaled, Serialize, Writecap, WritecapBody,
+    fmt, io, BigArray, BlockMeta, BlockPath, Deserialize, Epoch, Formatter, Hashable, Principal,
+    Principaled, Serialize, Writecap, WritecapBody,
 };
 
 use btserde::{self, from_vec, to_vec, write_to};
@@ -28,6 +28,7 @@ use serde::{
     ser::{SerializeStruct, Serializer},
 };
 use std::{
+    fmt::Display,
     io::{Read, Write},
     marker::PhantomData,
     num::TryFromIntError,

+ 16 - 3
crates/btlib/src/lib.rs

@@ -18,13 +18,14 @@ extern crate static_assertions;
 #[cfg(test)]
 extern crate lazy_static;
 
-use brotli::{CompressorWriter, Decompressor};
 use crypto::{
     AsymKeyPub, Ciphertext, Creds, Decrypter, DecrypterExt, Encrypter, EncrypterExt, HashKind,
     MerkleStream, PublicCreds, SecretStream, Sign, Signature, Signer, SymKey, SymKeyKind, VarHash,
 };
+use trailered::Trailered;
 
 use ::log::error;
+use brotli::{CompressorWriter, Decompressor};
 use fuse_backend_rs::abi::fuse_abi::{stat64, Attr};
 use sectored_buf::SectoredBuf;
 use serde::{Deserialize, Serialize};
@@ -40,7 +41,7 @@ use std::{
     sync::PoisonError,
     time::{Duration, SystemTime},
 };
-use trailered::Trailered;
+use strum_macros::{Display, EnumDiscriminants, FromRepr};
 
 #[derive(Debug)]
 pub enum Error {
@@ -882,9 +883,19 @@ pub struct BlockRecord {
     pub inode: u64,
     /// A mapping from fragment serial numbers to fragment records.
     /// TODO: Does this need to have a consistent order?
+    /// TODO: Move this into the block metadata.
     pub frags: HashMap<FragmentSerial, FragmentRecord>,
 }
 
+impl BlockRecord {
+    pub fn new(inode: u64) -> BlockRecord {
+        BlockRecord {
+            inode,
+            frags: HashMap::new(),
+        }
+    }
+}
+
 #[derive(Debug, PartialEq, Serialize, Deserialize)]
 /// Structure for keeping track of server information in a directory.
 pub struct ServerRecord {
@@ -894,7 +905,9 @@ pub struct ServerRecord {
     pub_creds: PublicCreds,
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Serialize, Deserialize, EnumDiscriminants)]
+#[strum_discriminants(derive(FromRepr, Display, Serialize, Deserialize))]
+#[strum_discriminants(name(DirEntryKind))]
 pub enum DirEntry {
     Directory(BlockRecord),
     File(BlockRecord),