diff --git a/src/block_group.rs b/src/block_group.rs new file mode 100644 index 0000000..739fa32 --- /dev/null +++ b/src/block_group.rs @@ -0,0 +1,29 @@ +/// The Block Group Descriptor Table contains a descriptor for each block group +/// within the file system. The number of block groups within the file system, +/// and correspondingly, the number of entries in the Block Group Descriptor +/// Table, is described above. Each descriptor contains information regarding +/// where important data structures for that group are located. +/// +/// The (`BlockGroupDescriptor`) table is located in the block immediately +/// following the Superblock. So if the block size (determined from a field in +/// the superblock) is 1024 bytes per block, the Block Group Descriptor Table +/// will begin at block 2. For any other block size, it will begin at block 1. +/// Remember that blocks are numbered starting at 0, and that block numbers +/// don't usually correspond to physical block addresses. +#[repr(C, packed)] +pub struct BlockGroupDescriptor { + /// Block address of block usage bitmap + block_usage_addr: u32, + /// Block address of inode usage bitmap + inode_usage_addr: u32, + /// Starting block address of inode table + inode_table_block: u32, + /// Number of unallocated blocks in group + free_blocks_count: u16, + /// Number of unallocated inodes in group + free_inodes_count: u16, + /// Number of directories in group + dirs_count: u16, + #[doc(hidden)] + _reserved: [u8; 14], +} diff --git a/src/inode.rs b/src/inode.rs new file mode 100644 index 0000000..072ad45 --- /dev/null +++ b/src/inode.rs @@ -0,0 +1,124 @@ +#[repr(C, packed)] +pub struct Inode { + /// Type and Permissions (see below) + type_perm: u16, + /// User ID + uid: u16, + /// Lower 32 bits of size in bytes + size_low: u32, + /// Last Access Time (in POSIX time) + atime: u32, + /// Creation Time (in POSIX time) + ctime: u32, + /// Last Modification time (in POSIX time) + mtime: u32, + /// Deletion time (in POSIX time) + dtime: u32, + /// Group ID + gid: u16, + /// Count of hard links (directory entries) to this inode. When this + /// reaches 0, the data blocks are marked as unallocated. + hard_links: u16, + /// Count of disk sectors (not Ext2 blocks) in use by this inode, not + /// counting the actual inode structure nor directory entries linking + /// to the inode. + sectors_count: u32, + /// Flags + flags: u32, + /// Operating System Specific value #1 + _os_specific_1: u32, + /// Direct block pointers + direct_pointer: [u32; 12], + /// Singly Indirect Block Pointer (Points to a block that is a list of + /// block pointers to data) + indirect_pointer: u32, + /// Doubly Indirect Block Pointer (Points to a block that is a list of + /// block pointers to Singly Indirect Blocks) + doubly_indirect: u32, + /// Triply Indirect Block Pointer (Points to a block that is a list of + /// block pointers to Doubly Indirect Blocks) + triply_indirect: u32, + /// Generation number (Primarily used for NFS) + gen_number: u32, + /// In Ext2 version 0, this field is reserved. In version >= 1, + /// Extended attribute block (File ACL). + ext_attribute_block: u32, + /// In Ext2 version 0, this field is reserved. In version >= 1, Upper + /// 32 bits of file size (if feature bit set) if it's a file, + /// Directory ACL if it's a directory + size_high: u32, + /// Block address of fragment + frag_block_addr: u32, + /// Operating System Specific Value #2 + _os_specific_2: [u8; 12], +} + +bitflags! { + pub struct TypePerm: u16 { + /// FIFO + const FIFO = 0x1000; + /// Character device + const CHAR_DEVICE = 0x2000; + /// Directory + const DIRECTORY = 0x4000; + /// Block device + const BLOCK_DEVICE = 0x6000; + /// Regular file + const FILE = 0x8000; + /// Symbolic link + const SYMLINK = 0xA000; + /// Unix socket + const SOCKET = 0xC000; + /// Other—execute permission + const O_EXEC = 0x001; + /// Other—write permission + const O_WRITE = 0x002; + /// Other—read permission + const O_READ = 0x004; + /// Group—execute permission + const G_EXEC = 0x008; + /// Group—write permission + const G_WRITE = 0x010; + /// Group—read permission + const G_READ = 0x020; + /// User—execute permission + const U_EXEC = 0x040; + /// User—write permission + const U_WRITE = 0x080; + /// User—read permission + const U_READ = 0x100; + /// Sticky Bit + const STICKY = 0x200; + /// Set group ID + const SET_GID = 0x400; + /// Set user ID + const SET_UID = 0x800; + } +} + +bitflags! { + pub struct InodeFlags: u32 { + /// Secure deletion (not used) + const SECURE_DEL = 0x00000001; + /// Keep a copy of data when deleted (not used) + const KEEP_COPY = 0x00000002; + /// File compression (not used) + const COMPRESSION = 0x00000004; + /// Synchronous updates—new data is written immediately to disk + const SYNC_UPDATE = 0x00000008; + /// Immutable file (content cannot be changed) + const IMMUTABLE = 0x00000010; + /// Append only + const APPEND_ONLY = 0x00000020; + /// File is not included in 'dump' command + const NODUMP = 0x00000040; + /// Last accessed time should not updated + const DONT_ATIME = 0x00000080; + /// Hash indexed directory + const HASH_DIR = 0x00010000; + /// AFS directory + const AFS_DIR = 0x00020000; + /// Journal file data + const JOURNAL_DATA = 0x00040000; + } +} diff --git a/src/lib.rs b/src/lib.rs index a6cc7cf..4bdee73 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,23 @@ #![cfg_attr(not(test), no_std)] +#[macro_use] +extern crate bitflags; + +pub mod superblock; +pub mod block_group; +pub mod inode; + #[cfg(test)] -mod tests {} +mod tests { + use super::superblock::*; + use super::block_group::*; + use super::inode::*; + + #[test] + fn sizes() { + use std::mem::size_of; + assert_eq!(size_of::(), 1024); + assert_eq!(size_of::(), 32); + assert_eq!(size_of::(), 128); + } +} diff --git a/src/superblock.rs b/src/superblock.rs new file mode 100644 index 0000000..258e8b2 --- /dev/null +++ b/src/superblock.rs @@ -0,0 +1,210 @@ +/// Ext2 signature (0xef53), used to help confirm the presence of Ext2 on a +/// volume +pub const EXT2_MAGIC: u16 = 0xef53; + +/// Filesystem is free of errors +pub const FS_CLEAN: u16 = 1; +/// Filesystem has errors +pub const FS_ERR: u16 = 2; + +/// Ignore errors +pub const ERR_IGNORE: u16 = 1; +/// Remount as read-only on error +pub const ERR_RONLY: u16 = 2; +/// Panic on error +pub const ERR_PANIC: u16 = 3; + +/// Creator OS is Linux +pub const OS_LINUX: u32 = 0; +/// Creator OS is Hurd +pub const OS_HURD: u32 = 1; +/// Creator OS is Masix +pub const OS_MASIX: u32 = 2; +/// Creator OS is FreeBSD +pub const OS_FREEBSD: u32 = 3; +/// Creator OS is a BSD4.4-Lite derivative +pub const OS_LITE: u32 = 4; + +/// The Superblock contains all information about the layout of the file system +/// and possibly contains other important information like what optional +/// features were used to create the file system. +/// +/// The Superblock is always located at byte 1024 from the beginning of the +/// volume and is exactly 1024 bytes in length. For example, if the disk uses +/// 512 byte sectors, the Superblock will begin at LBA 2 and will occupy all of +/// sector 2 and 3. +#[repr(C, packed)] +pub struct Superblock { + // taken from https://wiki.osdev.org/Ext2 + /// Total number of inodes in file system + inodes_count: u32, + /// Total number of blocks in file system + blocks_count: u32, + /// Number of blocks reserved for superuser (see offset 80) + r_blocks_count: u32, + /// Total number of unallocated blocks + free_blocks_count: u32, + /// Total number of unallocated inodes + free_inodes_count: u32, + /// Block number of the block containing the superblock + first_data_block: u32, + /// log2 (block size) - 10. (In other words, the number to shift 1,024 + /// to the left by to obtain the block size) + log_block_size: u32, + /// log2 (fragment size) - 10. (In other words, the number to shift + /// 1,024 to the left by to obtain the fragment size) + log_frag_size: i32, + /// Number of blocks in each block group + blocks_per_group: u32, + /// Number of fragments in each block group + frags_per_group: u32, + /// Number of inodes in each block group + inodes_per_group: u32, + /// Last mount time (in POSIX time) + mtime: u32, + /// Last written time (in POSIX time) + wtime: u32, + /// Number of times the volume has been mounted since its last + /// consistency check (fsck) + mnt_count: u16, + /// Number of mounts allowed before a consistency check (fsck) must be + /// done + max_mnt_count: i16, + /// Ext2 signature (0xef53), used to help confirm the presence of Ext2 + /// on a volume + magic: u16, + /// File system state (see `FS_CLEAN` and `FS_ERR`) + state: u16, + /// What to do when an error is detected (see `ERR_IGNORE`, `ERR_RONLY` and + /// `ERR_PANIC`) + errors: u16, + /// Minor portion of version (combine with Major portion below to + /// construct full version field) + rev_minor: u16, + /// POSIX time of last consistency check (fsck) + lastcheck: u32, + /// Interval (in POSIX time) between forced consistency checks (fsck) + checkinterval: u32, + /// Operating system ID from which the filesystem on this volume was + /// created + creator_os: u32, + /// Major portion of version (combine with Minor portion above to + /// construct full version field) + rev_major: u32, + /// User ID that can use reserved blocks + block_uid: u16, + /// Group ID that can use reserved blocks + block_gid: u16, + + /// First non-reserved inode in file system. + first_inode: u32, + /// Size of each inode structure in bytes. + inode_size: u16, + /// Block group that this superblock is part of (if backup copy) + block_group: u16, + /// Optional features present (features that are not required to read + /// or write, but usually result in a performance increase) + features_opt: FeaturesOptional, + /// Required features present (features that are required to be + /// supported to read or write) + features_req: FeaturesRequired, + /// Features that if not supported, the volume must be mounted + /// read-only) + features_ronly: FeaturesROnly, + /// File system ID (what is output by blkid) + fs_id: [u8; 16], + /// Volume name (C-style string: characters terminated by a 0 byte) + volume_name: [u8; 16], + /// Path volume was last mounted to (C-style string: characters + /// terminated by a 0 byte) + last_mnt_path: [u8; 64], + /// Compression algorithms used (see Required features above) + compression: u32, + /// Number of blocks to preallocate for files + prealloc_blocks_files: u8, + /// Number of blocks to preallocate for directories + prealloc_blocks_dirs: u8, + #[doc(hidden)] + _unused: [u8; 2], + /// Journal ID (same style as the File system ID above) + journal_id: [u8; 16], + /// Journal inode + journal_inode: u32, + /// Journal device + journal_dev: u32, + /// Head of orphan inode list + journal_orphan_head: u32, + #[doc(hidden)] + _reserved: [u8; 788], +} + +impl Superblock { + #[inline] + pub fn block_size(&self) -> usize { + 1024 << self.log_block_size + } + + #[inline] + pub fn frag_size(&self) -> usize { + 1024 << self.log_frag_size + } + + pub fn block_group_count(&self) -> Result { + let blocks_mod = self.blocks_count % self.blocks_per_group; + let inodes_mod = self.inodes_count % self.inodes_per_group; + let blocks_inc = if blocks_mod == 0 { 0 } else { 1 }; + let inodes_inc = if inodes_mod == 0 { 0 } else { 1 }; + let by_blocks = self.blocks_count / self.blocks_per_group + blocks_inc; + let by_inodes = self.inodes_count / self.inodes_per_group + inodes_inc; + if by_blocks == by_inodes { + Ok(by_blocks) + } else { + Err((by_blocks, by_inodes)) + } + } +} + +bitflags! { + /// Optional features + pub struct FeaturesOptional: u32 { + /// Preallocate some number of (contiguous?) blocks (see + /// `Superblock::prealloc_blocks_dirs`) to a directory when creating a new one + const PREALLOCATE = 0x0001; + /// AFS server inodes exist + const AFS = 0x0002; + /// File system has a journal (Ext3) + const JOURNAL = 0x0004; + /// Inodes have extended attributes + const EXTENDED_INODE = 0x0008; + /// File system can resize itself for larger partitions + const SELF_RESIZE = 0x0010; + /// Directories use hash index + const HASH_INDEX = 0x0020; + } +} + +bitflags! { + /// Required features. If these are not supported; can't mount + pub struct FeaturesRequired: u32 { + /// Compression is used + const REQ_COMPRESSION = 0x0001; + /// Directory entries contain a type field + const REQ_DIRECTORY_TYPE = 0x0002; + /// File system needs to replay its journal + const REQ_REPLAY_JOURNAL = 0x0004; + /// File system uses a journal device + const REQ_JOURNAL_DEVICE = 0x0008; + } +} + +bitflags! { + /// ROnly features. If these are not supported; remount as read-only + pub struct FeaturesROnly: u32 { + /// Sparse superblocks and group descriptor tables + const RONLY_SPARSE = 0x0001; + /// File system uses a 64-bit file size + const RONLY_FILE_SIZE_64 = 0x0002; + /// Directory contents are stored in the form of a Binary Tree + const RONLY_BTREE_DIRECTORY = 0x0004; + } +}