Explorar o código

feat: list xattr

liujingx hai 10 meses
pai
achega
21008d0c6a
Modificáronse 6 ficheiros con 108 adicións e 32 borrados
  1. 30 4
      ext4_fuse/src/fuse_fs.rs
  2. 1 1
      ext4_fuse/src/main.rs
  3. 22 13
      ext4_test/src/main.rs
  4. 20 1
      src/ext4/low_level.rs
  5. 5 10
      src/ext4_defs/dir.rs
  6. 30 3
      src/ext4_defs/xattr.rs

+ 30 - 4
ext4_fuse/src/fuse_fs.rs

@@ -280,7 +280,7 @@ impl<T: 'static> Filesystem for StateExt4FuseFs<T> {
             if let Ok(des) = self.fs.lookup(newparent as u32, newname.to_str().unwrap()) {
                 if self.fs.getattr(src).unwrap().ftype == Ext4FileType::Directory
                     && self.fs.getattr(des).unwrap().ftype == Ext4FileType::Directory
-                    && self.fs.list(des).unwrap().len() <= 2
+                    && self.fs.listdir(des).unwrap().len() <= 2
                 {
                     // Overwrite empty directory
                     if let Err(e) = self.fs.rmdir(newparent as u32, newname.to_str().unwrap()) {
@@ -348,7 +348,7 @@ impl<T: 'static> Filesystem for StateExt4FuseFs<T> {
         offset: i64,
         mut reply: ReplyDirectory,
     ) {
-        let entries = self.fs.list(ino as u32);
+        let entries = self.fs.listdir(ino as u32);
         match entries {
             Ok(entries) => {
                 let mut i = offset as usize;
@@ -358,7 +358,7 @@ impl<T: 'static> Filesystem for StateExt4FuseFs<T> {
                         ino,
                         i as i64 + 1,
                         translate_ftype(self.fs.getattr(entry.inode()).unwrap().ftype),
-                        entry.name().unwrap(),
+                        entry.name(),
                     ) {
                         break;
                     }
@@ -458,9 +458,15 @@ impl<T: 'static> Filesystem for StateExt4FuseFs<T> {
         let name = name.to_str().unwrap();
         match self.fs.getxattr(ino as u32, name) {
             Ok(value) => {
+                log::trace!(
+                    "Get xattr {} of inode {}: {:?}",
+                    name,
+                    ino,
+                    String::from_utf8_lossy(&value)
+                );
                 if size == 0 {
                     reply.size(value.len() as u32);
-                } else if value.len() == size as usize {
+                } else if value.len() <= size as usize {
                     reply.data(&value);
                 } else {
                     reply.error(ErrCode::ERANGE as i32);
@@ -498,6 +504,26 @@ impl<T: 'static> Filesystem for StateExt4FuseFs<T> {
             Err(e) => reply.error(e.code() as i32),
         }
     }
+
+    fn listxattr(&mut self, _req: &Request<'_>, ino: u64, size: u32, reply: fuser::ReplyXattr) {
+        match self.fs.listxattr(ino as u32) {
+            Ok(names) => {
+                let mut buffer = Vec::new();
+                for name in names {
+                    buffer.extend_from_slice(name.as_bytes());
+                    buffer.push(0);
+                }
+                if size == 0 {
+                    reply.size(buffer.len() as u32);
+                } else if buffer.len() <= size as usize {
+                    reply.data(&buffer);
+                } else {
+                    reply.error(ErrCode::ERANGE as i32);
+                }
+            }
+            Err(e) => reply.error(e.code() as i32),
+        }
+    }
 }
 
 fn get_ttl() -> Duration {

+ 1 - 1
ext4_fuse/src/main.rs

@@ -27,7 +27,7 @@ fn main() {
     let args = Args::parse();
 
     SimpleLogger::new().init().unwrap();
-    log::set_max_level(log::LevelFilter::Debug);
+    log::set_max_level(log::LevelFilter::Trace);
     info!("Use mountpoint \"{}\"", args.mountpoint);
 
     // Initialize block device and filesystem

+ 22 - 13
ext4_test/src/main.rs

@@ -112,17 +112,26 @@ fn remove_file_test(ext4: &mut Ext4) {
         .expect_err("remove file failed");
 }
 
-fn remove_dir_test(ext4: &mut Ext4) {
-    ext4.generic_remove(ROOT_INO, "d2")
-        .expect_err("remove unempty dir");
-    ext4.generic_create(ROOT_INO, "dtmp", InodeMode::DIRECTORY | InodeMode::ALL_RWX)
-        .expect("mkdir failed");
-    ext4.generic_lookup(ROOT_INO, "dtmp")
-        .expect("dir not created");
-    ext4.generic_remove(ROOT_INO, "dtmp")
-        .expect("remove file failed");
-    ext4.generic_lookup(ROOT_INO, "dtmp")
-        .expect_err("dir not removed");
+fn xattr_test(ext4: &mut Ext4) {
+    let file_mode: InodeMode = InodeMode::FILE | InodeMode::ALL_RWX;
+    let file = ext4
+        .generic_create(ROOT_INO, "f2", file_mode)
+        .expect("Create failed");
+    ext4.setxattr(file, "user.test1", "hello world".as_bytes())
+        .expect("setxattr failed");
+    ext4.setxattr(file, "user.test2", "world hello".as_bytes())
+        .expect("setxattr failed");
+    let value = ext4.getxattr(file, "user.test1").expect("getxattr failed");
+    assert_eq!(value, "hello world".as_bytes());
+    let value = ext4.getxattr(file, "user.test2").expect("getxattr failed");
+    assert_eq!(value, "world hello".as_bytes());
+    let names = ext4.listxattr(file).expect("listxattr failed");
+    assert_eq!(names, vec!["user.test1", "user.test2"]);
+    ext4.removexattr(file, "user.test1").expect("removexattr failed");
+    ext4.getxattr(file, "user.test1")
+        .expect_err("getxattr failed");
+    let names = ext4.listxattr(file).expect("listxattr failed");
+    assert_eq!(names, vec!["user.test2"]);
 }
 
 fn main() {
@@ -142,6 +151,6 @@ fn main() {
     println!("large read write test done");
     remove_file_test(&mut ext4);
     println!("remove file test done");
-    remove_dir_test(&mut ext4);
-    println!("remove dir test done");
+    xattr_test(&mut ext4);
+    println!("xattr test done");
 }

+ 20 - 1
src/ext4/low_level.rs

@@ -428,7 +428,7 @@ impl Ext4 {
     /// # Error
     ///
     /// `ENOTDIR` - `inode` is not a directory
-    pub fn list(&self, inode: InodeId) -> Result<Vec<DirEntry>> {
+    pub fn listdir(&self, inode: InodeId) -> Result<Vec<DirEntry>> {
         let inode_ref = self.read_inode(inode);
         // Can only list a directory
         if inode_ref.inode.file_type() != FileType::Directory {
@@ -560,4 +560,23 @@ impl Ext4 {
             return_error!(ErrCode::ENODATA, "Xattr {} does not exist", name);
         }
     }
+
+    /// List extended attributes of a file.
+    /// 
+    /// # Params
+    /// 
+    /// * `inode` - the inode of the file
+    /// 
+    /// # Returns
+    /// 
+    /// A list of extended attributes of the file.
+    pub fn listxattr(&self, inode: InodeId) -> Result<Vec<String>> {
+        let inode_ref = self.read_inode(inode);
+        let xattr_block_id = inode_ref.inode.xattr_block();
+        if xattr_block_id == 0 {
+            return Ok(Vec::new());
+        }
+        let xattr_block = XattrBlock::new(self.read_block(xattr_block_id));
+        Ok(xattr_block.list())
+    }
 }

+ 5 - 10
src/ext4_defs/dir.rs

@@ -5,7 +5,6 @@ use super::crc::*;
 use super::AsBytes;
 use super::FileType;
 use crate::constants::*;
-use crate::format_error;
 use crate::prelude::*;
 use crate::Block;
 
@@ -84,15 +83,11 @@ impl DirEntry {
     }
 
     /// Get the name of the directory entry
-    pub fn name(&self) -> Result<String> {
-        let name_len = self.name_len as usize;
-        let name = &self.name[..name_len];
-        String::from_utf8(name.to_vec()).map_err(|_| {
-            format_error!(
-                ErrCode::EINVAL,
-                "Invalid UTF-8 sequence in directory entry name"
-            )
-        })
+    pub fn name(&self) -> String {
+        let name = &self.name[..self.name_len as usize];
+        unsafe {
+            String::from_utf8_unchecked(name.to_vec())
+        }
     }
 
     /// Compare the name of the directory entry with a given name

+ 30 - 3
src/ext4_defs/xattr.rs

@@ -130,6 +130,17 @@ impl XattrEntry {
         }
     }
 
+    /// Get the name of the xattr entry
+    pub fn name(&self) -> String {
+        let name = &self.name[..self.name_len as usize];
+        unsafe { String::from_utf8_unchecked(name.to_vec()) }
+    }
+
+    /// Compare the name of the xattr entry with a given name
+    pub fn compare_name(&self, name: &str) -> bool {
+        &self.name[..name.len()] == name.as_bytes()
+    }
+
     /// Get the required size to save a xattr entry, 4-byte aligned
     pub fn required_size(name_len: usize) -> usize {
         // u32 + u16 + u8 + Ext4DirEnInner + name -> align to 4
@@ -181,8 +192,7 @@ impl XattrBlock {
                 break;
             }
             let entry: XattrEntry = self.0.read_offset_as(entry_start);
-            // Compare name
-            if name.as_bytes() == &entry.name[..entry.name_len as usize] {
+            if entry.compare_name(name) {
                 return Some(
                     &self
                         .0
@@ -194,6 +204,23 @@ impl XattrBlock {
         None
     }
 
+    /// List all xattr names
+    pub fn list(&self) -> Vec<String> {
+        let mut entry_start = size_of::<XattrHeader>();
+        let mut names = Vec::new();
+        // Iterate over entry table
+        while entry_start < BLOCK_SIZE {
+            // Check `name_len`, 0 indicates the end of the entry table.
+            if self.0.data[entry_start] == 0 {
+                break;
+            }
+            let entry: XattrEntry = self.0.read_offset_as(entry_start);
+            names.push(entry.name());
+            entry_start += entry.used_size();
+        }
+        names
+    }
+
     /// Insert a xattr entry into the block. Return true if success.
     pub fn insert(&mut self, name: &str, value: &[u8]) -> bool {
         let mut entry_start = size_of::<XattrHeader>();
@@ -236,7 +263,7 @@ impl XattrBlock {
             }
             let entry: XattrEntry = self.0.read_offset_as(entry_start);
             // Compare name
-            if name.as_bytes() == &entry.name[..entry.name_len as usize] {
+            if entry.compare_name(name) {
                 break;
             }
             entry_start += entry.used_size();