diff --git a/kernel/crates/another_ext4/src/ext4/alloc.rs b/kernel/crates/another_ext4/src/ext4/alloc.rs index 8156765a08..218b35e6eb 100644 --- a/kernel/crates/another_ext4/src/ext4/alloc.rs +++ b/kernel/crates/another_ext4/src/ext4/alloc.rs @@ -26,6 +26,42 @@ impl Ext4 { Ok(inode_ref) } + /// Create a device inode (character or block device). + /// + /// Unlike `create_inode()`, this function: + /// - Does NOT initialize the extent tree + /// - Stores the device number in i_block[0..1] (Linux ext4 standard) + #[inline(never)] + pub(super) fn create_device_inode( + &self, + mode: InodeMode, + major: u32, + minor: u32, + ) -> Result { + // Device nodes are never directories + let id = self.alloc_inode(false)?; + + // Initialize the inode + let mut inode = Box::new(Inode::default()); + inode.set_mode(mode); + + // Key difference: set device number instead of extent tree + inode.set_device(major, minor); + + let mut inode_ref = InodeRef::new(id, inode); + + // Sync the inode to disk + self.write_inode_with_csum(&mut inode_ref); + + trace!( + "Alloc device inode {} ({}:{}) ok", + inode_ref.id, + major, + minor + ); + Ok(inode_ref) + } + /// Create(initialize) the root inode of the file system #[inline(never)] pub(super) fn create_root_inode(&self) -> Result { diff --git a/kernel/crates/another_ext4/src/ext4/low_level.rs b/kernel/crates/another_ext4/src/ext4/low_level.rs index 43efd30934..4664d76aef 100644 --- a/kernel/crates/another_ext4/src/ext4/low_level.rs +++ b/kernel/crates/another_ext4/src/ext4/low_level.rs @@ -58,6 +58,14 @@ impl Ext4 { if inode.inode.link_count() == 0 { return_error!(ErrCode::EINVAL, "Invalid inode {}", id); } + + // Get device number for device nodes + let rdev = if inode.inode.is_device() { + inode.inode.device() + } else { + (0, 0) + }; + Ok(FileAttr { ino: id, size: inode.inode.size(), @@ -71,6 +79,7 @@ impl Ext4 { links: inode.inode.link_count(), uid: inode.inode.uid(), gid: inode.inode.gid(), + rdev, }) } @@ -122,6 +131,31 @@ impl Ext4 { Ok(()) } + /// Link a newly created inode into `parent`. + /// + /// If linking fails, this function frees the newly allocated inode to avoid leaks. + fn link_new_inode_or_free( + &self, + parent: &mut InodeRef, + child: &mut InodeRef, + name: &str, + ) -> Result<()> { + if let Err(link_err) = self.link_inode(parent, child, name) { + if let Err(cleanup_err) = self.free_inode(child) { + trace!( + "link failed for new inode {} (name {}), cleanup failed: {:?}; original link error: {:?}", + child.id, + name, + cleanup_err, + link_err + ); + return Err(cleanup_err); + } + return Err(link_err); + } + Ok(()) + } + /// Create a file. This function will not check the existence of /// the file, call `lookup` to check beforehand. /// @@ -148,11 +182,62 @@ impl Ext4 { } // Create child inode and link it to parent directory let mut child = self.create_inode(mode)?; - self.link_inode(&mut parent, &mut child, name)?; + self.link_new_inode_or_free(&mut parent, &mut child, name)?; // Create file handler Ok(child.id) } + /// Create a device node (character or block device). + /// + /// Unlike `create()`, this function: + /// - Does NOT initialize the extent tree + /// - Stores the device number in i_block[0..1] (Linux ext4 standard) + /// + /// # Params + /// + /// * `parent` - parent directory inode id + /// * `name` - device node name + /// * `mode` - file type (must include CHARDEV or BLOCKDEV) and permissions + /// * `major` - major device number + /// * `minor` - minor device number + /// + /// # Return + /// + /// `Ok(inode)` - Inode id of the new device node + /// + /// # Error + /// + /// * `ENOTDIR` - `parent` is not a directory + /// * `ENOSPC` - No space left on device + pub fn mknod( + &self, + parent: InodeId, + name: &str, + mode: InodeMode, + major: u32, + minor: u32, + ) -> Result { + let mut parent_ref = self.read_inode(parent); + + // Can only create in a directory + if !parent_ref.inode.is_dir() { + return_error!( + ErrCode::ENOTDIR, + "Inode {} is not a directory", + parent_ref.id + ); + } + + // Create device inode (uses create_device_inode which sets device number) + let mut child = self.create_device_inode(mode, major, minor)?; + + // Link to parent directory + self.link_new_inode_or_free(&mut parent_ref, &mut child, name)?; + + trace!("mknod {} ({}:{}) -> inode {}", name, major, minor, child.id); + Ok(child.id) + } + /// Read data from a file. This function will read exactly `buf.len()` /// bytes unless the end of the file is reached. /// diff --git a/kernel/crates/another_ext4/src/ext4_defs/inode.rs b/kernel/crates/another_ext4/src/ext4_defs/inode.rs index 9ce6adc8e4..f653fbab97 100644 --- a/kernel/crates/another_ext4/src/ext4_defs/inode.rs +++ b/kernel/crates/another_ext4/src/ext4_defs/inode.rs @@ -396,6 +396,55 @@ impl Inode { self.set_flags(Self::FLAG_EXTENTS); self.extent_root_mut().init(0, 0); } + + /// Check if this inode is a device node (character or block device). + pub fn is_device(&self) -> bool { + matches!( + self.file_type(), + FileType::CharacterDev | FileType::BlockDev + ) + } + + /// Get device number (major, minor) for character/block device nodes. + /// + /// Linux ext4 stores device numbers in i_block[0..1]: + /// - If i_block[0] != 0: old format, decode from i_block[0] + /// - If i_block[0] == 0: new format, decode from i_block[1] + pub fn device(&self) -> (u32, u32) { + let block0 = + u32::from_le_bytes([self.block[0], self.block[1], self.block[2], self.block[3]]); + let block1 = + u32::from_le_bytes([self.block[4], self.block[5], self.block[6], self.block[7]]); + + if block0 != 0 { + device::old_decode_dev(block0) + } else { + device::new_decode_dev(block1) + } + } + + /// Set device number (major, minor) for character/block device nodes. + /// + /// This stores the device number in i_block[0..1] using Linux ext4 format: + /// - Old format if major < 256 && minor < 256 + /// - New format otherwise + /// + /// Note: This should only be called for device inodes, and extent_init() + /// should NOT be called for device inodes. + pub fn set_device(&mut self, major: u32, minor: u32) { + // Clear i_block area + self.block.fill(0); + + if device::old_valid_dev(major, minor) { + // Old format: store in i_block[0] + let encoded = device::old_encode_dev(major, minor); + self.block[0..4].copy_from_slice(&encoded.to_le_bytes()); + } else { + // New format: i_block[0] = 0, store in i_block[1] + let encoded = device::new_encode_dev(major, minor); + self.block[4..8].copy_from_slice(&encoded.to_le_bytes()); + } + } } /// A combination of an `Inode` and its id @@ -438,4 +487,455 @@ pub struct FileAttr { pub links: u16, pub uid: u32, pub gid: u32, + /// Device number (major, minor) for character/block devices. + /// Only meaningful when ftype is CharacterDev or BlockDev. + pub rdev: (u32, u32), +} + +/// Device number encoding/decoding utilities compatible with Linux ext4. +/// +/// Linux ext4 stores device numbers in i_block[0..1]: +/// - Old format: i_block[0] contains encoded dev (major < 256 && minor < 256) +/// - New format: i_block[0] = 0, i_block[1] contains encoded dev +pub mod device { + /// Check if device number fits in old format (major < 256 && minor < 256). + #[inline] + pub const fn old_valid_dev(major: u32, minor: u32) -> bool { + major < 256 && minor < 256 + } + + /// Encode device number in old format (16-bit). + /// Layout: [major(8-bit)][minor(8-bit)] + #[inline] + pub const fn old_encode_dev(major: u32, minor: u32) -> u32 { + ((major & 0xff) << 8) | (minor & 0xff) + } + + /// Decode device number from old format. + #[inline] + pub const fn old_decode_dev(dev: u32) -> (u32, u32) { + let major = (dev >> 8) & 0xff; + let minor = dev & 0xff; + (major, minor) + } + + /// Encode device number in new format (32-bit). + /// Layout: [minor_lo(8-bit)][major(12-bit)][minor_hi(12-bit)] + #[inline] + pub const fn new_encode_dev(major: u32, minor: u32) -> u32 { + (minor & 0xff) | (major << 8) | ((minor & !0xff) << 12) + } + + /// Decode device number from new format. + #[inline] + pub const fn new_decode_dev(dev: u32) -> (u32, u32) { + let major = (dev & 0xfff00) >> 8; + let minor = (dev & 0xff) | ((dev >> 12) & 0xfff00); + (major, minor) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // ==================== device module tests ==================== + + mod device_encoding { + use super::device::*; + + #[test] + fn test_old_valid_dev_boundary() { + // Valid old format + assert!(old_valid_dev(0, 0)); + assert!(old_valid_dev(1, 3)); // /dev/null + assert!(old_valid_dev(255, 255)); // max old format + + // Invalid old format + assert!(!old_valid_dev(256, 0)); + assert!(!old_valid_dev(0, 256)); + assert!(!old_valid_dev(256, 256)); + assert!(!old_valid_dev(1000, 1000)); + } + + #[test] + fn test_old_encode_decode_roundtrip() { + let test_cases = [ + (0, 0), + (1, 3), // /dev/null + (1, 5), // /dev/zero + (4, 0), // /dev/tty0 + (8, 0), // /dev/sda + (8, 1), // /dev/sda1 + (255, 255), // max + ]; + + for (major, minor) in test_cases { + let encoded = old_encode_dev(major, minor); + let (dec_major, dec_minor) = old_decode_dev(encoded); + assert_eq!( + (dec_major, dec_minor), + (major, minor), + "old format roundtrip failed for ({}, {})", + major, + minor + ); + } + } + + #[test] + fn test_old_encode_known_values() { + // /dev/null: major=1, minor=3 -> 0x0103 + assert_eq!(old_encode_dev(1, 3), 0x0103); + // /dev/zero: major=1, minor=5 -> 0x0105 + assert_eq!(old_encode_dev(1, 5), 0x0105); + // /dev/sda: major=8, minor=0 -> 0x0800 + assert_eq!(old_encode_dev(8, 0), 0x0800); + } + + #[test] + fn test_new_encode_decode_roundtrip() { + let test_cases = [ + (0, 0), + (1, 256), // minor exceeds old format + (256, 0), // major exceeds old format + (256, 256), // both exceed + (8, 65536), // large minor + (259, 65536), // virtio-blk style + (4095, 1048575), // near max (12-bit major, 20-bit minor) + ]; + + for (major, minor) in test_cases { + let encoded = new_encode_dev(major, minor); + let (dec_major, dec_minor) = new_decode_dev(encoded); + assert_eq!( + (dec_major, dec_minor), + (major, minor), + "new format roundtrip failed for ({}, {})", + major, + minor + ); + } + } + + #[test] + fn test_new_format_also_works_for_small_values() { + // New format should also correctly handle small values + let test_cases = [(1, 3), (8, 0), (255, 255)]; + + for (major, minor) in test_cases { + let encoded = new_encode_dev(major, minor); + let (dec_major, dec_minor) = new_decode_dev(encoded); + assert_eq!((dec_major, dec_minor), (major, minor)); + } + } + + #[test] + fn test_format_discrimination() { + // When old_valid_dev returns true, old format should be used + // When old_valid_dev returns false, new format should be used + // This tests the format selection logic + + // Small device: should use old format + let (major, minor) = (1, 3); + assert!(old_valid_dev(major, minor)); + + // Large device: must use new format + let (major, minor) = (8, 256); + assert!(!old_valid_dev(major, minor)); + } + } + + // ==================== Inode device methods tests ==================== + + mod inode_device { + use super::*; + + fn create_test_inode() -> Inode { + Inode::default() + } + + #[test] + fn test_is_device() { + let mut inode = create_test_inode(); + + // Regular file + inode.set_mode(InodeMode::FILE | InodeMode::ALL_RW); + assert!(!inode.is_device()); + + // Directory + inode.set_mode(InodeMode::DIRECTORY | InodeMode::ALL_RWX); + assert!(!inode.is_device()); + + // Character device + inode.set_mode(InodeMode::CHARDEV | InodeMode::ALL_RW); + assert!(inode.is_device()); + + // Block device + inode.set_mode(InodeMode::BLOCKDEV | InodeMode::ALL_RW); + assert!(inode.is_device()); + + // FIFO + inode.set_mode(InodeMode::FIFO | InodeMode::ALL_RW); + assert!(!inode.is_device()); + + // Socket + inode.set_mode(InodeMode::SOCKET | InodeMode::ALL_RW); + assert!(!inode.is_device()); + + // Symlink + inode.set_mode(InodeMode::SOFTLINK | InodeMode::ALL_RWX); + assert!(!inode.is_device()); + } + + #[test] + fn test_set_device_old_format() { + let mut inode = create_test_inode(); + + // /dev/null: major=1, minor=3 + inode.set_device(1, 3); + + // Verify i_block[0] is non-zero (old format) + let block0 = u32::from_le_bytes([ + inode.block[0], + inode.block[1], + inode.block[2], + inode.block[3], + ]); + assert_ne!(block0, 0, "Old format should have non-zero i_block[0]"); + + // Verify i_block[1] is zero + let block1 = u32::from_le_bytes([ + inode.block[4], + inode.block[5], + inode.block[6], + inode.block[7], + ]); + assert_eq!(block1, 0, "Old format should have zero i_block[1]"); + + // Verify roundtrip + let (major, minor) = inode.device(); + assert_eq!((major, minor), (1, 3)); + } + + #[test] + fn test_set_device_new_format() { + let mut inode = create_test_inode(); + + // Device with minor=256 (exceeds old format) + inode.set_device(8, 256); + + // Verify i_block[0] is zero (new format marker) + let block0 = u32::from_le_bytes([ + inode.block[0], + inode.block[1], + inode.block[2], + inode.block[3], + ]); + assert_eq!(block0, 0, "New format should have zero i_block[0]"); + + // Verify i_block[1] is non-zero + let block1 = u32::from_le_bytes([ + inode.block[4], + inode.block[5], + inode.block[6], + inode.block[7], + ]); + assert_ne!(block1, 0, "New format should have non-zero i_block[1]"); + + // Verify roundtrip + let (major, minor) = inode.device(); + assert_eq!((major, minor), (8, 256)); + } + + #[test] + fn test_set_device_clears_block() { + let mut inode = create_test_inode(); + + // Fill block with garbage + inode.block.fill(0xff); + + // Set device + inode.set_device(1, 3); + + // Verify rest of block is cleared + for i in 8..60 { + assert_eq!( + inode.block[i], 0, + "block[{}] should be cleared after set_device", + i + ); + } + } + + #[test] + fn test_device_roundtrip_various() { + let mut inode = create_test_inode(); + + let test_cases = [ + (1, 3), // /dev/null (old format) + (1, 5), // /dev/zero (old format) + (4, 64), // /dev/ttyS0 (old format) + (8, 0), // /dev/sda (old format) + (8, 16), // /dev/sdb (old format) + (8, 256), // minor > 255 (new format) + (254, 0), // virtio-blk (old format) + (259, 0), // nvme (new format, major > 255) + (259, 65536), // nvme with large minor (new format) + ]; + + for (major, minor) in test_cases { + inode.set_device(major, minor); + let (got_major, got_minor) = inode.device(); + assert_eq!( + (got_major, got_minor), + (major, minor), + "Device roundtrip failed for ({}, {})", + major, + minor + ); + } + } + + #[test] + fn test_device_vs_extent_exclusivity() { + let mut inode = create_test_inode(); + + // Set as device + inode.set_device(1, 3); + let device_before = inode.device(); + + // Now initialize extent (this would corrupt device data!) + // This test documents the expected behavior: don't call extent_init on device inodes + inode.extent_init(); + + // After extent_init, device() will return garbage + // This is expected - device inodes should never have extent_init called + let device_after = inode.device(); + + // The values will differ, demonstrating mutual exclusivity + // In real usage, we must ensure device inodes never call extent_init + assert_ne!( + device_before, device_after, + "extent_init should corrupt device data (this is expected)" + ); + } + } + + // ==================== FileAttr tests ==================== + + mod file_attr { + use super::*; + + #[test] + fn test_file_attr_rdev_field() { + let attr = FileAttr { + ino: 123, + size: 0, + atime: 0, + mtime: 0, + ctime: 0, + crtime: 0, + blocks: 0, + ftype: FileType::CharacterDev, + perm: InodeMode::ALL_RW, + links: 1, + uid: 0, + gid: 0, + rdev: (1, 3), // /dev/null + }; + + assert_eq!(attr.rdev, (1, 3)); + assert_eq!(attr.ftype, FileType::CharacterDev); + } + + #[test] + fn test_file_attr_rdev_default_for_regular() { + let attr = FileAttr { + ino: 456, + size: 1024, + atime: 0, + mtime: 0, + ctime: 0, + crtime: 0, + blocks: 2, + ftype: FileType::RegularFile, + perm: InodeMode::ALL_RW, + links: 1, + uid: 0, + gid: 0, + rdev: (0, 0), // Not a device + }; + + assert_eq!(attr.rdev, (0, 0)); + assert_eq!(attr.ftype, FileType::RegularFile); + } + } + + // ==================== Linux compatibility tests ==================== + + mod linux_compat { + use super::device::*; + + /// Test values known from Linux systems + #[test] + fn test_linux_known_devices() { + // These are actual device numbers from a typical Linux system + let devices = [ + ("null", 1, 3), + ("zero", 1, 5), + ("full", 1, 7), + ("random", 1, 8), + ("urandom", 1, 9), + ("tty", 5, 0), + ("console", 5, 1), + ("sda", 8, 0), + ("sda1", 8, 1), + ("sdb", 8, 16), + ("loop0", 7, 0), + ("loop1", 7, 1), + ]; + + for (name, major, minor) in devices { + // All these fit in old format + assert!( + old_valid_dev(major, minor), + "{} should fit in old format", + name + ); + + // Roundtrip test + let encoded = old_encode_dev(major, minor); + let (dec_maj, dec_min) = old_decode_dev(encoded); + assert_eq!( + (dec_maj, dec_min), + (major, minor), + "Roundtrip failed for {}", + name + ); + } + } + + /// Test encoding matches Linux kernel's new_encode_dev + #[test] + fn test_new_encode_matches_linux() { + // Linux kernel: (minor & 0xff) | (major << 8) | ((minor & ~0xff) << 12) + // Test with known values + + // major=1, minor=256 + // Linux: (256 & 0xff) | (1 << 8) | ((256 & ~0xff) << 12) + // = 0 | 0x100 | (0x100 << 12) + // = 0x100 | 0x100000 + // = 0x100100 + let encoded = new_encode_dev(1, 256); + assert_eq!(encoded, 0x100100); + + // major=259, minor=0 + // Linux: (0 & 0xff) | (259 << 8) | ((0 & ~0xff) << 12) + // = 0 | 0x10300 | 0 + // = 0x10300 + let encoded = new_encode_dev(259, 0); + assert_eq!(encoded, 0x10300); + } + } } diff --git a/kernel/src/driver/base/device/device_number.rs b/kernel/src/driver/base/device/device_number.rs index 79f19d1cbf..b9a722da34 100644 --- a/kernel/src/driver/base/device/device_number.rs +++ b/kernel/src/driver/base/device/device_number.rs @@ -89,6 +89,19 @@ impl DeviceNumber { let minor = self.minor(); return (minor & 0xff) | (major << 8) | ((minor & !0xff) << 12); } + + /// Decode a Linux-encoded dev_t and create a DeviceNumber. + /// + /// Linux dev_t encoding (compatible with glibc makedev/major/minor): + /// - major = (dev & 0xfff00) >> 8 + /// - minor = (dev & 0xff) | ((dev >> 12) & 0xfff00) + /// + /// This works for both old format (major < 256 && minor < 256) and new format. + pub const fn from_linux_dev_t(dev: u32) -> Self { + let major = (dev & 0xfff00) >> 8; + let minor = (dev & 0xff) | ((dev >> 12) & 0xfff00); + Self::new(Major::new(major), minor) + } } impl Default for DeviceNumber { diff --git a/kernel/src/filesystem/ext4/filesystem.rs b/kernel/src/filesystem/ext4/filesystem.rs index 1bdad55cb5..f93ec4e615 100644 --- a/kernel/src/filesystem/ext4/filesystem.rs +++ b/kernel/src/filesystem/ext4/filesystem.rs @@ -90,6 +90,7 @@ impl Ext4FileSystem { vfs_inode_id: generate_inode_id(), parent: self_ref.clone(), self_ref: self_ref.clone(), + special_node: None, })) }); diff --git a/kernel/src/filesystem/ext4/inode.rs b/kernel/src/filesystem/ext4/inode.rs index 958ea4f39e..b3c3166bb7 100644 --- a/kernel/src/filesystem/ext4/inode.rs +++ b/kernel/src/filesystem/ext4/inode.rs @@ -1,11 +1,13 @@ use crate::{ + driver::base::device::device_number::DeviceNumber, filesystem::{ page_cache::{AsyncPageCacheBackend, PageCache}, vfs::{ self, syscall::RenameFlags, utils::DName, vcore::generate_inode_id, FilePrivateData, - IndexNode, InodeFlags, InodeId, InodeMode, + IndexNode, InodeFlags, InodeId, InodeMode, SpecialNodeData, }, }, + ipc::pipe::LockedPipeInode, libs::{ casting::DowncastArc, mutex::{Mutex, MutexGuard}, @@ -44,6 +46,9 @@ pub struct Ext4Inode { // 指向自身的Weak指针,用于获取Arc pub(super) self_ref: Weak, + + // 特殊节点数据(用于 FIFO 的 pipe inode) + pub(super) special_node: Option, } #[derive(Debug)] @@ -363,7 +368,21 @@ impl IndexNode for LockedExt4Inode { ) }; let attr = fs.fs.getattr(inode_num)?; - let raw_dev = fs.raw_dev; + + // dev_id: filesystem device number (st_dev) + let dev_id = fs.raw_dev.data() as usize; + + // raw_dev: device node's rdev (st_rdev), only for char/block devices + let raw_dev = if matches!(attr.ftype, FileType::CharacterDev | FileType::BlockDev) { + let (major, minor) = attr.rdev; + DeviceNumber::new( + crate::driver::base::device::device_number::Major::new(major), + minor, + ) + } else { + DeviceNumber::default() + }; + Ok(vfs::Metadata { inode_id: vfs_inode_id, size: attr.size as i64, @@ -379,7 +398,7 @@ impl IndexNode for LockedExt4Inode { nlinks: attr.links as usize, uid: attr.uid as usize, gid: attr.gid as usize, - dev_id: 0, + dev_id, raw_dev, }) } @@ -511,6 +530,60 @@ impl IndexNode for LockedExt4Inode { Ok(0) } + fn mknod( + &self, + filename: &str, + mode: InodeMode, + dev_t: DeviceNumber, + ) -> Result, SystemError> { + if mode.contains(InodeMode::S_IFREG) { + return self.create(filename, vfs::FileType::File, mode); + } + + let mut guard = self.0.lock(); + let ext4 = &guard.concret_fs().fs; + let inode_num = guard.inner_inode_num; + + if ext4.getattr(inode_num)?.ftype != FileType::Directory { + return Err(SystemError::ENOTDIR); + } + + // VFS InodeMode(u32) → another_ext4 InodeMode(u16) + let file_mode = another_ext4::InodeMode::from_bits_truncate(mode.bits() as u16); + + // Create inode based on file type + let id = if mode.contains(InodeMode::S_IFCHR) || mode.contains(InodeMode::S_IFBLK) { + // Character/block device: use mknod to store device number in i_block + ext4.mknod( + inode_num, + filename, + file_mode, + dev_t.major().data(), + dev_t.minor(), + )? + } else { + // FIFO, Socket, etc.: use regular create (no device number needed) + ext4.create(inode_num, filename, file_mode)? + }; + + // Wrap as VFS inode and cache + let dname = DName::from(filename); + let self_arc = guard.self_ref.upgrade().ok_or(SystemError::ENOENT)?; + let inode = LockedExt4Inode::new( + id, + guard.fs_ptr.clone(), + dname.clone(), + Some(Arc::downgrade(&self_arc)), + ); + guard.children.insert(dname, inode.clone()); + drop(guard); + Ok(inode as Arc) + } + + fn special_node(&self) -> Option { + self.0.lock().special_node.clone() + } + fn move_to( &self, old_name: &str, @@ -708,7 +781,12 @@ impl LockedExt4Inode { parent: Option>, ) -> Arc { let inode = Arc::new({ - LockedExt4Inode(Mutex::new(Ext4Inode::new(inode_num, fs_ptr, dname, parent))) + LockedExt4Inode(Mutex::new(Ext4Inode::new( + inode_num, + fs_ptr.clone(), + dname, + parent, + ))) }); let mut guard = inode.0.lock(); @@ -724,6 +802,17 @@ impl LockedExt4Inode { ); guard.page_cache = Some(page_cache); + // 对于 FIFO,创建 pipe inode + if let Some(fs) = fs_ptr.upgrade() { + if let Ok(attr) = fs.fs.getattr(inode_num) { + if attr.ftype == FileType::Fifo { + let pipe_inode = LockedPipeInode::new(); + pipe_inode.set_fifo(); + guard.special_node = Some(SpecialNodeData::Pipe(pipe_inode)); + } + } + } + drop(guard); return inode; } @@ -767,6 +856,7 @@ impl Ext4Inode { vfs_inode_id: generate_inode_id(), parent: parent.unwrap_or_default(), self_ref: Weak::new(), // 将在LockedExt4Inode::new()中设置 + special_node: None, } } } diff --git a/kernel/src/filesystem/vfs/syscall/sys_mknod.rs b/kernel/src/filesystem/vfs/syscall/sys_mknod.rs index 31a5769ab7..4f54a2b912 100644 --- a/kernel/src/filesystem/vfs/syscall/sys_mknod.rs +++ b/kernel/src/filesystem/vfs/syscall/sys_mknod.rs @@ -29,22 +29,42 @@ impl Syscall for SysMknodHandle { fn handle(&self, args: &[usize], _frame: &mut TrapFrame) -> Result { let path = Self::path(args); - let flags = Self::flags(args); - let dev_t = Self::dev_t(args); - let flags: InodeMode = InodeMode::from_bits_truncate(flags as u32); - let dev_t = DeviceNumber::from(dev_t as u32); + let mode_val = Self::mode(args) as u32; + // Decode Linux dev_t format (from userspace makedev) + let dev = DeviceNumber::from_linux_dev_t(Self::dev(args) as u32); let path = vfs_check_and_clone_cstr(path, Some(MAX_PATHLEN))? .into_string() .map_err(|_| SystemError::EINVAL)?; let path = path.as_str().trim(); + // 解析 mode:提取文件类型和权限位 + // "Zero file type is equivalent to type S_IFREG." - mknod(2) + let file_type_bits = mode_val & InodeMode::S_IFMT.bits(); + let perm_bits = mode_val & !InodeMode::S_IFMT.bits(); + + let file_type = if file_type_bits == 0 { + InodeMode::S_IFREG + } else { + InodeMode::from_bits(file_type_bits).ok_or(SystemError::EINVAL)? + }; + + // 应用 umask 到权限位 + // "In the absence of a default ACL, the permissions of the created node + // are (mode & ~umask)." - mknod(2) let pcb = ProcessManager::current_pcb(); + let umask = pcb.fs_struct().umask(); + let masked_perm = InodeMode::from_bits_truncate(perm_bits) & !umask; + + // 组合文件类型和 umask 后的权限 + let mode = file_type | masked_perm; + let (inode_begin, remain_path) = user_path_at(&pcb, AtFlags::AT_FDCWD.bits(), path)?; - let inode: Result, SystemError> = - inode_begin.lookup_follow_symlink(&remain_path, VFS_MAX_FOLLOW_SYMLINK_TIMES); - if inode.is_ok() { + if inode_begin + .lookup_follow_symlink(&remain_path, VFS_MAX_FOLLOW_SYMLINK_TIMES) + .is_ok() + { return Err(SystemError::EEXIST); } @@ -57,17 +77,18 @@ impl Syscall for SysMknodHandle { // 查找父目录 let parent_inode: Arc = resolve_parent_inode(inode_begin, parent_path)?; - // 创建 nod - parent_inode.mknod(filename, flags, dev_t)?; - return Ok(0); + // 创建节点 + parent_inode.mknod(filename, mode, dev)?; + + Ok(0) } fn entry_format(&self, args: &[usize]) -> Vec { vec![ FormattedSyscallParam::new("path", format!("{:#x}", Self::path(args) as usize)), - FormattedSyscallParam::new("flags", format!("{:#x}", Self::flags(args))), - FormattedSyscallParam::new("dev_t", format!("{:#x}", Self::dev_t(args))), + FormattedSyscallParam::new("mode", format!("{:#o}", Self::mode(args))), + FormattedSyscallParam::new("dev", format!("{:#x}", Self::dev(args))), ] } } @@ -77,11 +98,11 @@ impl SysMknodHandle { args[0] as *const u8 } - fn flags(args: &[usize]) -> usize { + fn mode(args: &[usize]) -> usize { args[1] } - fn dev_t(args: &[usize]) -> usize { + fn dev(args: &[usize]) -> usize { args[2] } } diff --git a/kernel/src/filesystem/vfs/syscall/sys_mknodat.rs b/kernel/src/filesystem/vfs/syscall/sys_mknodat.rs index 60897d1a29..2def6d2488 100644 --- a/kernel/src/filesystem/vfs/syscall/sys_mknodat.rs +++ b/kernel/src/filesystem/vfs/syscall/sys_mknodat.rs @@ -29,17 +29,33 @@ impl Syscall for SysMknodatHandle { let dirfd = Self::dirfd(args); let path = Self::path(args); let mode_val = Self::mode(args); - let dev = DeviceNumber::from(Self::dev(args)); + // Decode Linux dev_t format (from userspace makedev) + let dev = DeviceNumber::from_linux_dev_t(Self::dev(args)); let path = vfs_check_and_clone_cstr(path, Some(MAX_PATHLEN))? .into_string() .map_err(|_| SystemError::EINVAL)?; - let mode: InodeMode = if mode_val == 0 { + // 解析 mode:提取文件类型和权限位 + // "Zero file type is equivalent to type S_IFREG." - mknod(2) + let file_type_bits = mode_val & InodeMode::S_IFMT.bits(); + let perm_bits = mode_val & !InodeMode::S_IFMT.bits(); + + let file_type = if file_type_bits == 0 { InodeMode::S_IFREG } else { - InodeMode::from_bits(mode_val).ok_or(SystemError::EINVAL)? + InodeMode::from_bits(file_type_bits).ok_or(SystemError::EINVAL)? }; + + // 应用 umask 到权限位 + // "In the absence of a default ACL, the permissions of the created node + // are (mode & ~umask)." - mknod(2) let pcb = ProcessManager::current_pcb(); + let umask = pcb.fs_struct().umask(); + let masked_perm = InodeMode::from_bits_truncate(perm_bits) & !umask; + + // 组合文件类型和 umask 后的权限 + let mode = file_type | masked_perm; + let (mut current_inode, ret_path) = user_path_at(&pcb, dirfd, &path)?; let (name, parent) = rsplit_path(&ret_path); if let Some(parent) = parent { @@ -49,7 +65,15 @@ impl Syscall for SysMknodatHandle { if name.is_empty() && dirfd != AtFlags::AT_FDCWD.bits() { return Err(SystemError::ENOENT); } - // 在解析出的起始 inode 上进行 mknod(IndexNode::mknod 应负责对路径的进一步解析/校验) + + if current_inode + .lookup_follow_symlink(name, VFS_MAX_FOLLOW_SYMLINK_TIMES) + .is_ok() + { + return Err(SystemError::EEXIST); + } + + // 在解析出的父目录上进行 mknod current_inode.mknod(name, mode, dev)?; Ok(0) diff --git a/kernel/src/filesystem/vfs/syscall/sys_stat.rs b/kernel/src/filesystem/vfs/syscall/sys_stat.rs index 47c1d6de35..da0b516151 100644 --- a/kernel/src/filesystem/vfs/syscall/sys_stat.rs +++ b/kernel/src/filesystem/vfs/syscall/sys_stat.rs @@ -1,16 +1,15 @@ -//! System call handler for opening files. +//! System call handler for stat. use system_error::SystemError; use crate::arch::interrupt::TrapFrame; use crate::arch::syscall::nr::SYS_STAT; -use crate::filesystem::vfs::file::FileFlags; -use crate::filesystem::vfs::syscall::newfstat::do_newfstat; -use crate::filesystem::vfs::syscall::sys_close::do_close; -use crate::filesystem::vfs::InodeMode; +use crate::filesystem::vfs::fcntl::AtFlags; +use crate::filesystem::vfs::stat::do_newfstatat; +use crate::filesystem::vfs::MAX_PATHLEN; use crate::syscall::table::FormattedSyscallParam; use crate::syscall::table::Syscall; -use defer::defer; +use crate::syscall::user_access::vfs_check_and_clone_cstr; use alloc::vec::Vec; @@ -23,22 +22,21 @@ impl Syscall for SysStatHandle { } fn handle(&self, args: &[usize], _frame: &mut TrapFrame) -> Result { - let path = Self::path(args); + let path_ptr = Self::path(args); let usr_kstat = Self::usr_kstat(args); - let fd = super::open_utils::do_open( - path, - FileFlags::O_RDONLY.bits(), - InodeMode::empty().bits(), - )?; + if usr_kstat == 0 { + return Err(SystemError::EFAULT); + } - defer!({ - do_close(fd as i32).ok(); - }); + let path = vfs_check_and_clone_cstr(path_ptr, Some(MAX_PATHLEN))?; + let path_str = path.to_str().map_err(|_| SystemError::EINVAL)?; - do_newfstat(fd as i32, usr_kstat)?; + // stat(2) 等价于 newfstatat(AT_FDCWD, path, buf, 0) + // 与 lstat 不同,stat 会跟随符号链接,所以 flags = 0 + do_newfstatat(AtFlags::AT_FDCWD.bits(), path_str, usr_kstat, 0)?; - return Ok(0); + Ok(0) } /// Formats the syscall arguments for display/debugging purposes. diff --git a/user/apps/c_unitest/test_mknod.c b/user/apps/c_unitest/test_mknod.c new file mode 100644 index 0000000000..1602b81b1f --- /dev/null +++ b/user/apps/c_unitest/test_mknod.c @@ -0,0 +1,923 @@ +/** + * @file main.c + * @brief Comprehensive test suite for mknod system call + * + * Tests creation of: + * - Character devices (S_IFCHR) + * - Block devices (S_IFBLK) + * - Named pipes / FIFOs (S_IFIFO) + * - Unix domain sockets (S_IFSOCK) - note: typically created via socket()+bind() + * - Regular files (S_IFREG) + * + * Build: gcc -o test_mknod main.c + * Run: ./test_mknod (requires root for device nodes) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Test directory */ +#define TEST_DIR "/tmp/mknod_test" + +/* Colors for output */ +#define COLOR_GREEN "\033[0;32m" +#define COLOR_RED "\033[0;31m" +#define COLOR_YELLOW "\033[0;33m" +#define COLOR_RESET "\033[0m" + +/* Test counters */ +static int tests_passed = 0; +static int tests_failed = 0; +static int tests_skipped = 0; + +/* Helper macros */ +#define TEST_PASS(name) do { \ + printf(COLOR_GREEN "[PASS]" COLOR_RESET " %s\n", name); \ + tests_passed++; \ +} while(0) + +#define TEST_FAIL(name, reason) do { \ + printf(COLOR_RED "[FAIL]" COLOR_RESET " %s: %s\n", name, reason); \ + tests_failed++; \ +} while(0) + +#define TEST_SKIP(name, reason) do { \ + printf(COLOR_YELLOW "[SKIP]" COLOR_RESET " %s: %s\n", name, reason); \ + tests_skipped++; \ +} while(0) + +/** + * Setup test directory + */ +static int setup_test_dir(void) +{ + struct stat st; + + /* Remove existing test directory */ + if (stat(TEST_DIR, &st) == 0) { + /* Simple cleanup - remove known test files */ + char cmd[256]; + snprintf(cmd, sizeof(cmd), "rm -rf %s", TEST_DIR); + system(cmd); + } + + /* Create fresh test directory */ + if (mkdir(TEST_DIR, 0755) != 0) { + perror("Failed to create test directory"); + return -1; + } + + return 0; +} + +/** + * Cleanup test directory + */ +static void cleanup_test_dir(void) +{ + char cmd[256]; + snprintf(cmd, sizeof(cmd), "rm -rf %s", TEST_DIR); + system(cmd); +} + +/** + * Verify file type and device number + */ +static int verify_node(const char *path, mode_t expected_type, dev_t expected_dev) +{ + struct stat st; + + if (stat(path, &st) != 0) { + printf(" [DEBUG] stat failed: %s\n", strerror(errno)); + return -1; + } + + printf(" [DEBUG] stat: mode=0%o, type=0%o (expect 0%o), rdev=%u:%u (expect %u:%u)\n", + st.st_mode, st.st_mode & S_IFMT, expected_type, + major(st.st_rdev), minor(st.st_rdev), + major(expected_dev), minor(expected_dev)); + + if ((st.st_mode & S_IFMT) != expected_type) { + printf(" [DEBUG] type mismatch!\n"); + return -2; + } + + /* For device nodes, verify device number */ + if (expected_type == S_IFCHR || expected_type == S_IFBLK) { + if (st.st_rdev != expected_dev) { + printf(" [DEBUG] rdev mismatch!\n"); + return -3; + } + } + + return 0; +} + +/** + * Get file type string for display + */ +static const char *filetype_str(mode_t mode) +{ + switch (mode & S_IFMT) { + case S_IFREG: return "regular file"; + case S_IFDIR: return "directory"; + case S_IFCHR: return "character device"; + case S_IFBLK: return "block device"; + case S_IFIFO: return "FIFO"; + case S_IFSOCK: return "socket"; + case S_IFLNK: return "symlink"; + default: return "unknown"; + } +} + +/* ========== Character Device Tests ========== */ + +/** + * Test: Create /dev/null equivalent (major=1, minor=3) + */ +static void test_chardev_null(void) +{ + const char *path = TEST_DIR "/null"; + dev_t dev = makedev(1, 3); + int ret; + + printf(" [DEBUG] mknod(%s, S_IFCHR|0666, dev=%u:%u [raw=0x%lx])\n", + path, major(dev), minor(dev), (unsigned long)dev); + ret = mknod(path, S_IFCHR | 0666, dev); + if (ret != 0) { + if (errno == EPERM || errno == EACCES) { + TEST_SKIP("chardev_null", "requires root privileges"); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("chardev_null", buf); + } + return; + } + + ret = verify_node(path, S_IFCHR, dev); + if (ret == 0) { + TEST_PASS("chardev_null (major=1, minor=3)"); + } else { + TEST_FAIL("chardev_null", "verification failed"); + } +} + +/** + * Test: Create /dev/zero equivalent (major=1, minor=5) + */ +static void test_chardev_zero(void) +{ + const char *path = TEST_DIR "/zero"; + dev_t dev = makedev(1, 5); + int ret; + + printf(" [DEBUG] mknod(%s, S_IFCHR|0666, dev=%u:%u [raw=0x%lx])\n", + path, major(dev), minor(dev), (unsigned long)dev); + ret = mknod(path, S_IFCHR | 0666, dev); + if (ret != 0) { + if (errno == EPERM || errno == EACCES) { + TEST_SKIP("chardev_zero", "requires root privileges"); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("chardev_zero", buf); + } + return; + } + + ret = verify_node(path, S_IFCHR, dev); + if (ret == 0) { + TEST_PASS("chardev_zero (major=1, minor=5)"); + } else { + TEST_FAIL("chardev_zero", "verification failed"); + } +} + +/** + * Test: Create tty device (major=4, minor=0) + */ +static void test_chardev_tty(void) +{ + const char *path = TEST_DIR "/tty0"; + dev_t dev = makedev(4, 0); + int ret; + + ret = mknod(path, S_IFCHR | 0620, dev); + if (ret != 0) { + if (errno == EPERM || errno == EACCES) { + TEST_SKIP("chardev_tty", "requires root privileges"); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("chardev_tty", buf); + } + return; + } + + ret = verify_node(path, S_IFCHR, dev); + if (ret == 0) { + TEST_PASS("chardev_tty (major=4, minor=0)"); + } else { + TEST_FAIL("chardev_tty", "verification failed"); + } +} + +/** + * Test: Character device with minor > 255 (requires new format encoding) + */ +static void test_chardev_large_minor(void) +{ + const char *path = TEST_DIR "/chardev_large_minor"; + dev_t dev = makedev(10, 256); /* misc device with large minor */ + int ret; + + ret = mknod(path, S_IFCHR | 0666, dev); + if (ret != 0) { + if (errno == EPERM || errno == EACCES) { + TEST_SKIP("chardev_large_minor", "requires root privileges"); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("chardev_large_minor", buf); + } + return; + } + + ret = verify_node(path, S_IFCHR, dev); + if (ret == 0) { + TEST_PASS("chardev_large_minor (major=10, minor=256)"); + } else { + TEST_FAIL("chardev_large_minor", "verification failed"); + } +} + +/* ========== Block Device Tests ========== */ + +/** + * Test: Create sda equivalent (major=8, minor=0) + */ +static void test_blkdev_sda(void) +{ + const char *path = TEST_DIR "/sda"; + dev_t dev = makedev(8, 0); + int ret; + + printf(" [DEBUG] mknod(%s, S_IFBLK|0660, dev=%u:%u [raw=0x%lx])\n", + path, major(dev), minor(dev), (unsigned long)dev); + ret = mknod(path, S_IFBLK | 0660, dev); + if (ret != 0) { + if (errno == EPERM || errno == EACCES) { + TEST_SKIP("blkdev_sda", "requires root privileges"); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("blkdev_sda", buf); + } + return; + } + + ret = verify_node(path, S_IFBLK, dev); + if (ret == 0) { + TEST_PASS("blkdev_sda (major=8, minor=0)"); + } else { + TEST_FAIL("blkdev_sda", "verification failed"); + } +} + +/** + * Test: Create sda1 partition (major=8, minor=1) + */ +static void test_blkdev_sda1(void) +{ + const char *path = TEST_DIR "/sda1"; + dev_t dev = makedev(8, 1); + int ret; + + ret = mknod(path, S_IFBLK | 0660, dev); + if (ret != 0) { + if (errno == EPERM || errno == EACCES) { + TEST_SKIP("blkdev_sda1", "requires root privileges"); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("blkdev_sda1", buf); + } + return; + } + + ret = verify_node(path, S_IFBLK, dev); + if (ret == 0) { + TEST_PASS("blkdev_sda1 (major=8, minor=1)"); + } else { + TEST_FAIL("blkdev_sda1", "verification failed"); + } +} + +/** + * Test: Create loop device (major=7, minor=0) + */ +static void test_blkdev_loop(void) +{ + const char *path = TEST_DIR "/loop0"; + dev_t dev = makedev(7, 0); + int ret; + + ret = mknod(path, S_IFBLK | 0660, dev); + if (ret != 0) { + if (errno == EPERM || errno == EACCES) { + TEST_SKIP("blkdev_loop", "requires root privileges"); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("blkdev_loop", buf); + } + return; + } + + ret = verify_node(path, S_IFBLK, dev); + if (ret == 0) { + TEST_PASS("blkdev_loop (major=7, minor=0)"); + } else { + TEST_FAIL("blkdev_loop", "verification failed"); + } +} + +/** + * Test: Block device with large device numbers (NVMe style) + */ +static void test_blkdev_nvme(void) +{ + const char *path = TEST_DIR "/nvme0n1"; + dev_t dev = makedev(259, 0); /* NVMe major */ + int ret; + + ret = mknod(path, S_IFBLK | 0660, dev); + if (ret != 0) { + if (errno == EPERM || errno == EACCES) { + TEST_SKIP("blkdev_nvme", "requires root privileges"); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("blkdev_nvme", buf); + } + return; + } + + ret = verify_node(path, S_IFBLK, dev); + if (ret == 0) { + TEST_PASS("blkdev_nvme (major=259, minor=0)"); + } else { + TEST_FAIL("blkdev_nvme", "verification failed"); + } +} + +/** + * Test: Block device with very large minor number + */ +static void test_blkdev_large_minor(void) +{ + const char *path = TEST_DIR "/blkdev_large"; + dev_t dev = makedev(8, 65536); /* Large minor */ + int ret; + + ret = mknod(path, S_IFBLK | 0660, dev); + if (ret != 0) { + if (errno == EPERM || errno == EACCES) { + TEST_SKIP("blkdev_large_minor", "requires root privileges"); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("blkdev_large_minor", buf); + } + return; + } + + ret = verify_node(path, S_IFBLK, dev); + if (ret == 0) { + TEST_PASS("blkdev_large_minor (major=8, minor=65536)"); + } else { + TEST_FAIL("blkdev_large_minor", "verification failed"); + } +} + +/* ========== FIFO (Named Pipe) Tests ========== */ + +/** + * Test: Create basic FIFO + */ +static void test_fifo_basic(void) +{ + const char *path = TEST_DIR "/fifo_basic"; + int ret; + + printf(" [DEBUG] mknod(%s, S_IFIFO|0666, 0)\n", path); + ret = mknod(path, S_IFIFO | 0666, 0); + printf(" [DEBUG] mknod returned %d (errno=%d: %s)\n", ret, errno, strerror(errno)); + if (ret != 0) { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("fifo_basic", buf); + return; + } + + ret = verify_node(path, S_IFIFO, 0); + if (ret == 0) { + TEST_PASS("fifo_basic"); + } else { + TEST_FAIL("fifo_basic", "verification failed"); + } +} + +/** + * Test: Create FIFO using mkfifo (wrapper around mknod) + */ +static void test_fifo_mkfifo(void) +{ + const char *path = TEST_DIR "/fifo_mkfifo"; + int ret; + + ret = mkfifo(path, 0644); + if (ret != 0) { + char buf[128]; + snprintf(buf, sizeof(buf), "mkfifo failed: %s", strerror(errno)); + TEST_FAIL("fifo_mkfifo", buf); + return; + } + + ret = verify_node(path, S_IFIFO, 0); + if (ret == 0) { + TEST_PASS("fifo_mkfifo"); + } else { + TEST_FAIL("fifo_mkfifo", "verification failed"); + } +} + +/** + * Test: FIFO with restricted permissions + */ +static void test_fifo_permissions(void) +{ + const char *path = TEST_DIR "/fifo_perms"; + int ret; + struct stat st; + + ret = mknod(path, S_IFIFO | 0600, 0); + if (ret != 0) { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("fifo_permissions", buf); + return; + } + + if (stat(path, &st) != 0) { + TEST_FAIL("fifo_permissions", "stat failed"); + return; + } + + /* Check permissions (considering umask) */ + if ((st.st_mode & S_IFMT) == S_IFIFO) { + TEST_PASS("fifo_permissions (mode=0600)"); + } else { + TEST_FAIL("fifo_permissions", "wrong file type"); + } +} + +/** + * Test: FIFO read/write functionality + */ +static void test_fifo_io(void) +{ + const char *path = TEST_DIR "/fifo_io"; + int ret; + pid_t pid; + + printf(" [DEBUG] Creating FIFO for I/O test...\n"); + ret = mknod(path, S_IFIFO | 0666, 0); + if (ret != 0) { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("fifo_io", buf); + return; + } + printf(" [DEBUG] FIFO created, forking...\n"); + + pid = fork(); + if (pid < 0) { + TEST_FAIL("fifo_io", "fork failed"); + return; + } + + if (pid == 0) { + /* Child: write to FIFO */ + printf(" [DEBUG] Child: opening FIFO for writing...\n"); + int fd = open(path, O_WRONLY); + printf(" [DEBUG] Child: open returned fd=%d\n", fd); + if (fd >= 0) { + printf(" [DEBUG] Child: writing...\n"); + write(fd, "test", 4); + close(fd); + printf(" [DEBUG] Child: done\n"); + } + _exit(0); + } else { + /* Parent: read from FIFO */ + char buf[16] = {0}; + printf(" [DEBUG] Parent: opening FIFO for reading...\n"); + int fd = open(path, O_RDONLY); + printf(" [DEBUG] Parent: open returned fd=%d\n", fd); + if (fd >= 0) { + printf(" [DEBUG] Parent: reading...\n"); + ssize_t n = read(fd, buf, sizeof(buf) - 1); + printf(" [DEBUG] Parent: read returned %zd\n", n); + close(fd); + + int status; + waitpid(pid, &status, 0); + + if (n == 4 && strcmp(buf, "test") == 0) { + TEST_PASS("fifo_io (read/write)"); + } else { + TEST_FAIL("fifo_io", "data mismatch"); + } + } else { + TEST_FAIL("fifo_io", "open failed"); + waitpid(pid, NULL, 0); + } + } +} + +/* ========== Socket Tests ========== */ + +/** + * Test: Create socket node via mknod + * Note: This is not the standard way to create Unix sockets. + * Normally, sockets are created via socket() + bind(). + * Some systems may not support creating socket nodes via mknod. + */ +static void test_socket_mknod(void) +{ + const char *path = TEST_DIR "/socket_mknod"; + int ret; + + ret = mknod(path, S_IFSOCK | 0666, 0); + if (ret != 0) { + if (errno == EPERM || errno == EINVAL || errno == ENOSYS) { + TEST_SKIP("socket_mknod", "mknod for sockets not supported (use socket()+bind())"); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("socket_mknod", buf); + } + return; + } + + ret = verify_node(path, S_IFSOCK, 0); + if (ret == 0) { + TEST_PASS("socket_mknod"); + } else { + TEST_FAIL("socket_mknod", "verification failed"); + } +} + +/* ========== Regular File Tests ========== */ + +/** + * Test: Create regular file via mknod + */ +static void test_regular_file(void) +{ + const char *path = TEST_DIR "/regular"; + int ret; + + ret = mknod(path, S_IFREG | 0644, 0); + if (ret != 0) { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("regular_file", buf); + return; + } + + ret = verify_node(path, S_IFREG, 0); + if (ret == 0) { + TEST_PASS("regular_file"); + } else { + TEST_FAIL("regular_file", "verification failed"); + } +} + +/* ========== Error Handling Tests ========== */ + +/** + * Test: EEXIST - file already exists + */ +static void test_error_eexist(void) +{ + const char *path = TEST_DIR "/existing"; + int ret; + + /* Create file first */ + ret = mknod(path, S_IFREG | 0644, 0); + if (ret != 0) { + TEST_SKIP("error_eexist", "initial mknod failed"); + return; + } + + /* Try to create again */ + ret = mknod(path, S_IFREG | 0644, 0); + if (ret == -1 && errno == EEXIST) { + TEST_PASS("error_eexist"); + } else { + TEST_FAIL("error_eexist", "expected EEXIST"); + } +} + +/** + * Test: ENOENT - parent directory doesn't exist + */ +static void test_error_enoent(void) +{ + const char *path = TEST_DIR "/nonexistent/file"; + int ret; + + ret = mknod(path, S_IFREG | 0644, 0); + if (ret == -1 && errno == ENOENT) { + TEST_PASS("error_enoent"); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "expected ENOENT, got %s", strerror(errno)); + TEST_FAIL("error_enoent", buf); + } +} + +/** + * Test: ENOTDIR - parent is not a directory + */ +static void test_error_enotdir(void) +{ + const char *file_path = TEST_DIR "/notdir"; + const char *path = TEST_DIR "/notdir/child"; + int ret; + + /* Create regular file */ + ret = mknod(file_path, S_IFREG | 0644, 0); + if (ret != 0) { + TEST_SKIP("error_enotdir", "initial mknod failed"); + return; + } + + /* Try to create file under the regular file */ + ret = mknod(path, S_IFREG | 0644, 0); + if (ret == -1 && errno == ENOTDIR) { + TEST_PASS("error_enotdir"); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "expected ENOTDIR, got %s", strerror(errno)); + TEST_FAIL("error_enotdir", buf); + } +} + +/* ========== Edge Case Tests ========== */ + +/** + * Test: Device number boundary - max old format (255, 255) + */ +static void test_devnum_max_old(void) +{ + const char *path = TEST_DIR "/dev_max_old"; + dev_t dev = makedev(255, 255); + int ret; + + ret = mknod(path, S_IFCHR | 0666, dev); + if (ret != 0) { + if (errno == EPERM || errno == EACCES) { + TEST_SKIP("devnum_max_old", "requires root privileges"); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("devnum_max_old", buf); + } + return; + } + + ret = verify_node(path, S_IFCHR, dev); + if (ret == 0) { + TEST_PASS("devnum_max_old (major=255, minor=255)"); + } else { + TEST_FAIL("devnum_max_old", "verification failed"); + } +} + +/** + * Test: Device number boundary - first new format (256, 0) + */ +static void test_devnum_first_new(void) +{ + const char *path = TEST_DIR "/dev_first_new"; + dev_t dev = makedev(256, 0); + int ret; + + ret = mknod(path, S_IFCHR | 0666, dev); + if (ret != 0) { + if (errno == EPERM || errno == EACCES) { + TEST_SKIP("devnum_first_new", "requires root privileges"); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("devnum_first_new", buf); + } + return; + } + + ret = verify_node(path, S_IFCHR, dev); + if (ret == 0) { + TEST_PASS("devnum_first_new (major=256, minor=0)"); + } else { + TEST_FAIL("devnum_first_new", "verification failed"); + } +} + +/** + * Test: Zero device number + */ +static void test_devnum_zero(void) +{ + const char *path = TEST_DIR "/dev_zero_num"; + dev_t dev = makedev(0, 0); + int ret; + + ret = mknod(path, S_IFCHR | 0666, dev); + if (ret != 0) { + if (errno == EPERM || errno == EACCES) { + TEST_SKIP("devnum_zero", "requires root privileges"); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "mknod failed: %s", strerror(errno)); + TEST_FAIL("devnum_zero", buf); + } + return; + } + + ret = verify_node(path, S_IFCHR, dev); + if (ret == 0) { + TEST_PASS("devnum_zero (major=0, minor=0)"); + } else { + TEST_FAIL("devnum_zero", "verification failed"); + } +} + +/* ========== Print Test Summary ========== */ + +static void print_summary(void) +{ + printf("\n"); + printf("========================================\n"); + printf(" Test Summary\n"); + printf("========================================\n"); + printf(COLOR_GREEN " Passed: %d\n" COLOR_RESET, tests_passed); + printf(COLOR_RED " Failed: %d\n" COLOR_RESET, tests_failed); + printf(COLOR_YELLOW " Skipped: %d\n" COLOR_RESET, tests_skipped); + printf("----------------------------------------\n"); + printf(" Total: %d\n", tests_passed + tests_failed + tests_skipped); + printf("========================================\n"); + + if (tests_failed == 0) { + printf(COLOR_GREEN "\nAll tests passed!\n" COLOR_RESET); + } else { + printf(COLOR_RED "\nSome tests failed.\n" COLOR_RESET); + } +} + +/* ========== List Created Nodes ========== */ + +static void list_test_nodes(void) +{ + DIR *dir; + struct dirent *entry; + struct stat st; + char path[512]; + + printf("\n"); + printf("========================================\n"); + printf(" Created Test Nodes\n"); + printf("========================================\n"); + + dir = opendir(TEST_DIR); + if (dir == NULL) { + printf(" (test directory not found)\n"); + return; + } + + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') continue; + + snprintf(path, sizeof(path), "%s/%s", TEST_DIR, entry->d_name); + if (stat(path, &st) == 0) { + printf(" %-20s %-16s", entry->d_name, filetype_str(st.st_mode)); + if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) { + printf(" dev=%u:%u", major(st.st_rdev), minor(st.st_rdev)); + } + printf(" mode=%04o\n", st.st_mode & 07777); + } + } + + closedir(dir); + printf("========================================\n"); +} + +/* ========== Main ========== */ + +int main(int argc, char *argv[]) +{ + int keep_files = 0; + + /* Parse arguments */ + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--keep") == 0 || strcmp(argv[i], "-k") == 0) { + keep_files = 1; + } else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + printf("Usage: %s [OPTIONS]\n", argv[0]); + printf("Options:\n"); + printf(" -k, --keep Keep test files after completion\n"); + printf(" -h, --help Show this help message\n"); + return 0; + } + } + + printf("========================================\n"); + printf(" mknod System Call Test Suite\n"); + printf("========================================\n\n"); + + /* Check if running as root */ + if (geteuid() != 0) { + printf(COLOR_YELLOW "Warning: Not running as root. Device node tests will be skipped.\n" COLOR_RESET); + printf("Run with 'sudo' to test device node creation.\n\n"); + } + + /* Setup */ + if (setup_test_dir() != 0) { + fprintf(stderr, "Failed to setup test directory\n"); + return 1; + } + + /* Run tests */ + printf("--- Character Device Tests ---\n"); + test_chardev_null(); + test_chardev_zero(); + test_chardev_tty(); + test_chardev_large_minor(); + + printf("\n--- Block Device Tests ---\n"); + test_blkdev_sda(); + test_blkdev_sda1(); + test_blkdev_loop(); + test_blkdev_nvme(); + test_blkdev_large_minor(); + + printf("\n--- FIFO Tests ---\n"); + test_fifo_basic(); + test_fifo_mkfifo(); + test_fifo_permissions(); + test_fifo_io(); + + printf("\n--- Socket Tests ---\n"); + test_socket_mknod(); + + printf("\n--- Regular File Tests ---\n"); + test_regular_file(); + + printf("\n--- Error Handling Tests ---\n"); + test_error_eexist(); + test_error_enoent(); + test_error_enotdir(); + + printf("\n--- Device Number Edge Cases ---\n"); + test_devnum_max_old(); + test_devnum_first_new(); + test_devnum_zero(); + + /* List created nodes */ + list_test_nodes(); + + /* Print summary */ + print_summary(); + + /* Cleanup */ + if (!keep_files) { + cleanup_test_dir(); + printf("\nTest files cleaned up. Use --keep to preserve them.\n"); + } else { + printf("\nTest files preserved in %s\n", TEST_DIR); + } + + return tests_failed > 0 ? 1 : 0; +}