Ver código fonte

feat: xattr get set remove

liujingx 10 meses atrás
pai
commit
b529146def
4 arquivos alterados com 390 adições e 3 exclusões
  1. 14 3
      src/ext4/alloc.rs
  2. 94 0
      src/ext4/low_level.rs
  3. 2 0
      src/ext4_defs/mod.rs
  4. 280 0
      src/ext4_defs/xattr.rs

+ 14 - 3
src/ext4/alloc.rs

@@ -61,12 +61,19 @@ impl Ext4 {
         for pblock in pblocks {
             // Deallocate the block
             self.dealloc_block(inode, pblock)?;
-            // Tree blocks are not counted in `inode.block_count`
             // Clear the block content
             self.write_block(&Block::new(pblock, [0; BLOCK_SIZE]));
         }
+        // Free xattr block
+        let xattr_block = inode.inode.xattr_block();
+        if xattr_block != 0 {
+            // Deallocate the block
+            self.dealloc_block(inode, xattr_block)?;
+            // Clear the block content
+            self.write_block(&Block::new(xattr_block, [0; BLOCK_SIZE]));
+        }
         // Deallocate the inode
-        self.dealloc_inode(&inode)?;
+        self.dealloc_inode(inode)?;
         Ok(())
     }
 
@@ -234,7 +241,7 @@ impl Ext4 {
     }
 
     /// Free an inode
-    fn dealloc_inode(&self, inode_ref: &InodeRef) -> Result<()> {
+    fn dealloc_inode(&self, inode_ref: &mut InodeRef) -> Result<()> {
         let mut sb = self.read_super_block();
 
         // Calc block group id and index in block group
@@ -276,6 +283,10 @@ impl Ext4 {
         sb.set_free_inodes_count(sb.free_inodes_count() - 1);
         self.write_super_block(&sb);
 
+        // Clear inode content
+        inode_ref.inode = unsafe { mem::zeroed() };
+        self.write_inode_without_csum(inode_ref);
+
         Ok(())
     }
 }

+ 94 - 0
src/ext4/low_level.rs

@@ -6,6 +6,7 @@
 use super::Ext4;
 use crate::constants::*;
 use crate::ext4_defs::*;
+use crate::format_error;
 use crate::prelude::*;
 use crate::return_error;
 use core::cmp::min;
@@ -448,4 +449,97 @@ impl Ext4 {
         // Remove directory entry
         self.unlink_inode(&mut parent, &mut child, name)
     }
+
+    /// Get extended attribute of a file.
+    ///
+    /// # Params
+    ///
+    /// * `inode` - the inode of the file
+    /// * `name` - the name of the attribute
+    ///
+    /// # Return
+    ///
+    /// `Ok(value)` - the value of the attribute
+    ///
+    /// # Error
+    ///
+    /// `ENODATA` - the attribute does not exist
+    pub fn getxattr(&self, inode: InodeId, name: &str) -> Result<Vec<u8>> {
+        let inode_ref = self.read_inode(inode);
+        let xattr_block_id = inode_ref.inode.xattr_block();
+        if xattr_block_id == 0 {
+            return_error!(ErrCode::ENODATA, "Xattr {} does not exist", name);
+        }
+        let xattr_block = XattrBlock::new(self.read_block(xattr_block_id));
+        match xattr_block.get(name) {
+            Some(value) => Ok(value.to_owned()),
+            None => Err(format_error!(
+                ErrCode::ENODATA,
+                "Xattr {} does not exist",
+                name
+            )),
+        }
+    }
+
+    /// Set extended attribute of a file. This function will not check name conflict.
+    /// Call `getxattr` to check beforehand.
+    ///
+    /// # Params
+    ///
+    /// * `inode` - the inode of the file
+    /// * `name` - the name of the attribute
+    /// * `value` - the value of the attribute
+    ///
+    /// # Error
+    ///
+    /// `ENOSPC` - xattr block does not have enough space
+    pub fn setxattr(&self, inode: InodeId, name: &str, value: &[u8]) -> Result<()> {
+        let mut inode_ref = self.read_inode(inode);
+        let xattr_block_id = inode_ref.inode.xattr_block();
+        if xattr_block_id == 0 {
+            // lazy allocate xattr block
+            let pblock = self.alloc_block(&mut inode_ref)?;
+            inode_ref.inode.set_xattr_block(pblock);
+            self.write_inode_with_csum(&mut inode_ref);
+        }
+        let mut xattr_block = XattrBlock::new(self.read_block(inode_ref.inode.xattr_block()));
+        if xattr_block_id == 0 {
+            xattr_block.init();
+        }
+        if xattr_block.insert(name, value) {
+            self.write_block(&xattr_block.block());
+            Ok(())
+        } else {
+            return_error!(
+                ErrCode::ENOSPC,
+                "Xattr block of Inode {} does not have enough space",
+                inode
+            );
+        }
+    }
+
+    /// Remove extended attribute of a file.
+    ///
+    /// # Params
+    ///
+    /// * `inode` - the inode of the file
+    /// * `name` - the name of the attribute
+    ///
+    /// # Error
+    ///
+    /// `ENODATA` - the attribute does not exist
+    pub fn removexattr(&self, inode: InodeId, name: &str) -> Result<()> {
+        let inode_ref = self.read_inode(inode);
+        let xattr_block_id = inode_ref.inode.xattr_block();
+        if xattr_block_id == 0 {
+            return_error!(ErrCode::ENODATA, "Xattr {} does not exist", name);
+        }
+        let mut xattr_block = XattrBlock::new(self.read_block(xattr_block_id));
+        if xattr_block.remove(name) {
+            self.write_block(&xattr_block.block());
+            Ok(())
+        } else {
+            return_error!(ErrCode::ENODATA, "Xattr {} does not exist", name);
+        }
+    }
 }

+ 2 - 0
src/ext4_defs/mod.rs

@@ -24,6 +24,7 @@ mod file;
 mod inode;
 mod mount_point;
 mod super_block;
+mod xattr;
 
 pub use bitmap::*;
 pub use block_device::*;
@@ -33,6 +34,7 @@ pub use extent::*;
 pub use file::*;
 pub use inode::*;
 pub use super_block::*;
+pub use xattr::*;
 
 /// All file types. Also matches the defination in directory entries.
 #[derive(PartialEq, Eq, Clone, Copy, Debug)]

+ 280 - 0
src/ext4_defs/xattr.rs

@@ -0,0 +1,280 @@
+//! Extended attributes (xattrs) are typically stored in a separate data block
+//! on the disk and referenced from inodes via `inode.file_acl*`.
+//!
+//! There are two places where extended attributes can be found. The first place
+//! is between the end of each inode entry and the beginning of the next inode
+//! entry. The second place where extended attributes can be found is in the block
+//! pointed to by `inode.file_acl`.
+//!
+//! We only implement the seperate data block storage of extended attributes.
+
+use super::{AsBytes, Block};
+use crate::constants::*;
+use crate::prelude::*;
+
+/// The beginning of an extended attribute block.
+#[repr(C)]
+#[derive(Debug)]
+pub struct XattrHeader {
+    /// Magic number for identification, 0xEA020000.
+    magic: u32,
+    /// Reference count.
+    refcount: u32,
+    /// Number of disk blocks used.
+    blocks: u32,
+    /// Hash value of all attributes. (UNUSED by now)
+    hash: u32,
+    /// Checksum of the extended attribute block.
+    checksum: u32,
+    /// Reserved for future use.
+    reserved: [u32; 3],
+}
+
+unsafe impl AsBytes for XattrHeader {}
+
+impl XattrHeader {
+    const XATTR_MAGIC: u32 = 0xEA020000;
+
+    pub fn new() -> Self {
+        XattrHeader {
+            magic: Self::XATTR_MAGIC,
+            refcount: 1,
+            blocks: 1,
+            hash: 0,
+            checksum: 0,
+            reserved: [0; 3],
+        }
+    }
+}
+
+/// Following the struct `XattrHeader` is an array of `XattrEntry`.
+#[repr(C)]
+#[derive(Debug)]
+pub struct XattrEntry {
+    /// Length of name.
+    name_len: u8,
+    /// Attribute name index (UNUSED by now)
+    name_index: u8,
+    /// Location of this attribute's value on the disk block where
+    /// it is stored. For a block this value is relative to the start
+    /// of the block (i.e. the header).
+    /// value = `block[value_offset..value_offset + value_size]`
+    value_offset: u16,
+    /// The inode where the value is stored. Zero indicates the value
+    /// is in the same block as this entry (FIXED 0 by now)
+    value_inum: u32,
+    /// Length of attribute value.
+    value_size: u32,
+    /// Hash value of attribute name and attribute value (UNUSED by now)
+    hash: u32,
+    /// Attribute name, max 255 bytes.
+    name: [u8; 255],
+}
+
+/// Fake xattr entry. A normal entry without `name` field.
+#[repr(C)]
+pub struct FakeXattrEntry {
+    name_len: u8,
+    name_index: u8,
+    value_offset: u16,
+    value_inum: u32,
+    value_size: u32,
+    hash: u32,
+}
+unsafe impl AsBytes for FakeXattrEntry {}
+
+/// The actual size of the extended attribute entry is determined by `name_len`.
+/// So we need to implement `AsBytes` methods specifically for `XattrEntry`.
+unsafe impl AsBytes for XattrEntry {
+    fn from_bytes(bytes: &[u8]) -> Self {
+        let fake_entry = FakeXattrEntry::from_bytes(bytes);
+        let mut entry = XattrEntry {
+            name_len: fake_entry.name_len,
+            name_index: fake_entry.name_index,
+            value_offset: fake_entry.value_offset,
+            value_inum: fake_entry.value_inum,
+            value_size: fake_entry.value_size,
+            hash: fake_entry.hash,
+            name: [0; 255],
+        };
+        let name_len = entry.name_len as usize;
+        let name_offset = size_of::<FakeXattrEntry>();
+        entry.name[..name_len].copy_from_slice(&bytes[name_offset..name_offset + name_len]);
+        entry
+    }
+    fn to_bytes(&self) -> &[u8] {
+        let name_len = self.name_len as usize;
+        unsafe {
+            core::slice::from_raw_parts(
+                self as *const Self as *const u8,
+                size_of::<FakeXattrEntry>() + name_len,
+            )
+        }
+    }
+}
+
+impl XattrEntry {
+    /// Create a new xattr entry.
+    pub fn new(name: &str, value_size: usize, value_offset: usize) -> Self {
+        let mut name_bytes = [0u8; 255];
+        let name_len = name.as_bytes().len();
+        name_bytes[..name_len].copy_from_slice(name.as_bytes());
+        Self {
+            name_len: name.len() as u8,
+            name_index: 0,
+            value_offset: value_offset as u16,
+            value_inum: 0,
+            value_size: value_size as u32,
+            hash: 0,
+            name: name_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
+        (core::mem::size_of::<FakeXattrEntry>() + name_len + 3) / 4 * 4
+    }
+
+    /// Get the used size of this xattr entry, 4-bytes alighed
+    pub fn used_size(&self) -> usize {
+        Self::required_size(self.name_len as usize)
+    }
+}
+
+/// The block that stores extended attributes for an inode. The block is
+/// pointed to `by inode.file_acl`.
+///
+/// `XattrHeader` is the beginning of an extended attribute block. Following
+/// the struct `XattrHeader` is an array of `XattrEntry`. Attribute values
+/// follow the end of the entry table. The values are stored starting at the
+/// end of the block and grow towards the xattr_header/xattr_entry table. When
+/// the two collide, the disk block fills up, and the filesystem returns `ENOSPC`.
+pub struct XattrBlock(Block);
+
+impl XattrBlock {
+    pub fn new(block: Block) -> Self {
+        XattrBlock(block)
+    }
+
+    pub fn init(&mut self) {
+        let header = XattrHeader::new();
+        self.0.write_offset_as(0, &header);
+    }
+
+    pub fn block(self) -> Block {
+        self.0
+    }
+
+    /// Get a xattr by name, return the value.
+    pub fn get(&self, name: &str) -> Option<&[u8]> {
+        let mut entry_start = size_of::<XattrHeader>();
+        // 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 {
+                // Target xattr not found
+                break;
+            }
+            let entry: XattrEntry = self.0.read_offset_as(entry_start);
+            // Compare name
+            if name.as_bytes() == &entry.name[..entry.name_len as usize] {
+                return Some(
+                    &self
+                        .0
+                        .read_offset(entry.value_offset as usize, entry.value_size as usize),
+                );
+            }
+            entry_start += entry.used_size();
+        }
+        None
+    }
+
+    /// 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>();
+        let mut value_end = BLOCK_SIZE;
+        // Iterate over entry table, find the position to insert entry
+        while entry_start < BLOCK_SIZE {
+            // Check `name_len`, 0 indicates the end of the entry table.
+            if self.0.data[entry_start] == 0 {
+                // Insert to the end of table
+                break;
+            }
+            let entry: XattrEntry = self.0.read_offset_as(entry_start);
+            entry_start += entry.used_size();
+            value_end = entry.value_offset as usize;
+        }
+        // `[entry_start, value_end)` is the empty space
+        // Check space
+        let required_size = XattrEntry::required_size(name.len()) + value.len() + 1;
+        if value_end - entry_start < required_size {
+            return false;
+        }
+        // Insert entry
+        let value_offset = value_end - value.len();
+        let entry = XattrEntry::new(name, value.len(), value_offset);
+        self.0.write_offset_as(entry_start, &entry);
+        // Insert value
+        self.0.write_offset(value_offset, value);
+        true
+    }
+
+    /// Remove a xattr entry from the block. Return true if success.
+    pub fn remove(&mut self, name: &str) -> bool {
+        let mut entry_start = size_of::<XattrHeader>();
+        // Iterate over entry table, find the position to remove entry
+        while entry_start < BLOCK_SIZE {
+            // Check `name_len`, 0 indicates the end of the entry table.
+            if self.0.data[entry_start] == 0 {
+                // Target xattr not found
+                return false;
+            }
+            let entry: XattrEntry = self.0.read_offset_as(entry_start);
+            // Compare name
+            if name.as_bytes() == &entry.name[..entry.name_len as usize] {
+                break;
+            }
+            entry_start += entry.used_size();
+        }
+        // `entry_start` now points to the removed entry.
+        let removed_entry: XattrEntry = self.0.read_offset_as(entry_start);
+        let removed_entry_size = removed_entry.used_size();
+        // `value_end` points to the end of removed value
+        let mut value_end = removed_entry.value_offset as usize + removed_entry.value_size as usize;
+
+        // Move the following entries and values
+        while entry_start + removed_entry_size < BLOCK_SIZE {
+            let next_entry_start = entry_start + removed_entry_size;
+            // Check `name_len`, 0 indicates the end of the entry table.
+            if self.0.data[next_entry_start] == 0 {
+                break;
+            }
+            // Get the entry to move
+            let mut next_entry: XattrEntry = self.0.read_offset_as(next_entry_start);
+            // Get its value
+            let next_value = self
+                .0
+                .read_offset(
+                    next_entry.value_offset as usize,
+                    next_entry.value_size as usize,
+                )
+                .to_owned();
+            // Move the value
+            let value_offset = value_end - next_value.len();
+            self.0.write_offset(value_offset, &next_value);
+            // Update entry
+            next_entry.value_offset = value_offset as u16;
+            // Write the entry to block
+            self.0.write_offset_as(entry_start, &next_entry);
+            // Update offset
+            value_end -= next_value.len();
+            entry_start += next_entry.used_size();
+        }
+        // Clear [entry_offset, value_offset)
+        trace!("Clearing [{}, {})", entry_start, value_end);
+        assert!(entry_start < value_end);
+        self.0.data[entry_start..value_end].fill(0);
+        true
+    }
+}