diff --git a/kernel/src/device/mod.rs b/kernel/src/device/mod.rs index 1cb82cb14..66b600892 100644 --- a/kernel/src/device/mod.rs +++ b/kernel/src/device/mod.rs @@ -64,12 +64,7 @@ pub fn init() -> Result<()> { // Instead of hardcoding every device numbers in this function, // a registration mechanism should be used to allow each driver to // allocate device IDs either statically or dynamically. -pub fn get_device(dev: usize) -> Result> { - if dev == 0 { - return_errno_with_message!(Errno::EPERM, "whiteout device") - } - - let devid = DeviceId::from(dev as u64); +pub fn get_device(devid: DeviceId) -> Result> { let major = devid.major(); let minor = devid.minor(); @@ -79,6 +74,6 @@ pub fn get_device(dev: usize) -> Result> { (5, 0) => Ok(Arc::new(tty::TtyDevice)), (1, 8) => Ok(Arc::new(random::Random)), (1, 9) => Ok(Arc::new(urandom::Urandom)), - _ => return_errno_with_message!(Errno::EINVAL, "unsupported device"), + _ => return_errno_with_message!(Errno::EINVAL, "the device ID is invalid or unsupported"), } } diff --git a/kernel/src/fs/device.rs b/kernel/src/fs/device.rs index 80e05599c..93a3bba97 100644 --- a/kernel/src/fs/device.rs +++ b/kernel/src/fs/device.rs @@ -63,17 +63,32 @@ impl DeviceId { pub fn minor(&self) -> u32 { self.minor } +} - /// Encodes the device ID as a `u32` value. +impl DeviceId { + /// Creates a device ID from the encoded `u64` value. /// - /// The encoding strategy here is the same as in Linux. See the Linux implementation at: - /// - pub fn as_encoded_u32(&self) -> u32 { - self.as_encoded_u64() as u32 + /// See [`as_encoded_u64`] for details about how to encode a device ID to a `u64` value. + /// + /// [`as_encoded_u64`]: Self::as_encoded_u64 + pub fn from_encoded_u64(raw: u64) -> Self { + let major = ((raw >> 32) & 0xffff_f000 | (raw >> 8) & 0x0000_0fff) as u32; + let minor = ((raw >> 12) & 0xffff_ff00 | raw & 0x0000_00ff) as u32; + Self::new(major, minor) } /// Encodes the device ID as a `u64` value. - fn as_encoded_u64(&self) -> u64 { + /// + /// The lower 32 bits use the same encoding strategy as Linux. See the Linux implementation at: + /// . + /// + /// If the major or minor device number is too large, the additional bits will be recorded + /// using the higher 32 bits. Note that as of 2025, the Linux kernel still has no support for + /// 64-bit device IDs: + /// . + /// So this encoding follows the implementation in glibc: + /// . + pub fn as_encoded_u64(&self) -> u64 { let major = self.major() as u64; let minor = self.minor() as u64; ((major & 0xffff_f000) << 32) @@ -81,25 +96,6 @@ impl DeviceId { | ((minor & 0xffff_ff00) << 12) | (minor & 0x0000_00ff) } - - /// Decodes the device ID from a `u64` value. - fn decode_from_u64(raw: u64) -> Self { - let major = ((raw >> 32) & 0xffff_f000 | (raw >> 8) & 0x0000_0fff) as u32; - let minor = ((raw >> 12) & 0xffff_ff00 | raw & 0x0000_00ff) as u32; - Self::new(major, minor) - } -} - -impl From for u64 { - fn from(value: DeviceId) -> Self { - value.as_encoded_u64() - } -} - -impl From for DeviceId { - fn from(raw: u64) -> Self { - Self::decode_from_u64(raw) - } } /// Add a device node to FS for the device. diff --git a/kernel/src/fs/ext2/impl_for_vfs/inode.rs b/kernel/src/fs/ext2/impl_for_vfs/inode.rs index dc68a3e0e..0238571dd 100644 --- a/kernel/src/fs/ext2/impl_for_vfs/inode.rs +++ b/kernel/src/fs/ext2/impl_for_vfs/inode.rs @@ -120,7 +120,7 @@ impl Inode for Ext2Inode { let inode = match type_ { MknodType::CharDeviceNode(dev) | MknodType::BlockDeviceNode(dev) => { let inode = self.create(name, inode_type, mode.into())?; - inode.set_device_id(dev.id().into()).unwrap(); + inode.set_device_id(dev.id().as_encoded_u64()).unwrap(); inode } _ => todo!(), diff --git a/kernel/src/fs/procfs/pid/stat.rs b/kernel/src/fs/procfs/pid/stat.rs index 6d72b48b3..537e0f6eb 100644 --- a/kernel/src/fs/procfs/pid/stat.rs +++ b/kernel/src/fs/procfs/pid/stat.rs @@ -125,7 +125,7 @@ impl FileOps for StatFileOps { let (tty_nr, tpgid) = if let Some(terminal) = process.terminal() { ( - terminal.id().as_encoded_u32(), + terminal.id().as_encoded_u64(), terminal .job_control() .foreground() diff --git a/kernel/src/fs/ramfs/fs.rs b/kernel/src/fs/ramfs/fs.rs index 7921e6653..bdfc0a41c 100644 --- a/kernel/src/fs/ramfs/fs.rs +++ b/kernel/src/fs/ramfs/fs.rs @@ -1109,7 +1109,7 @@ impl Inode for RamInode { let rdev = self .inner .as_device() - .map(|device| device.id().into()) + .map(|device| device.id().as_encoded_u64()) .unwrap_or(0); let inode_metadata = self.metadata.lock(); Metadata { diff --git a/kernel/src/fs/utils/inode.rs b/kernel/src/fs/utils/inode.rs index c276f285f..eaba08626 100644 --- a/kernel/src/fs/utils/inode.rs +++ b/kernel/src/fs/utils/inode.rs @@ -294,7 +294,7 @@ impl Metadata { nlinks: 1, uid: Uid::new_root(), gid: Gid::new_root(), - rdev: device.id().into(), + rdev: device.id().as_encoded_u64(), } } diff --git a/kernel/src/syscall/mknod.rs b/kernel/src/syscall/mknod.rs index 05683f8b5..efbbc7822 100644 --- a/kernel/src/syscall/mknod.rs +++ b/kernel/src/syscall/mknod.rs @@ -4,6 +4,7 @@ use super::SyscallReturn; use crate::{ device::get_device, fs::{ + device::DeviceId, file_table::FileDesc, fs_resolver::{FsPath, AT_FDCWD}, utils::{InodeMode, InodeType, MknodType}, @@ -49,7 +50,7 @@ pub fn sys_mknodat( let _ = dir_dentry.new_fs_child(&name, InodeType::File, inode_mode)?; } InodeType::CharDevice | InodeType::BlockDevice => { - let device_inode = get_device(dev)?; + let device_inode = get_device(DeviceId::from_encoded_u64(dev as u64))?; let _ = dir_dentry.mknod(&name, inode_mode, device_inode.into())?; } InodeType::NamedPipe => { diff --git a/kernel/src/syscall/statx.rs b/kernel/src/syscall/statx.rs index 4c1d01d99..d53aadef7 100644 --- a/kernel/src/syscall/statx.rs +++ b/kernel/src/syscall/statx.rs @@ -118,8 +118,8 @@ pub struct Statx { impl From for Statx { fn from(info: Metadata) -> Self { - let devid = DeviceId::from(info.dev); - let rdevid = DeviceId::from(info.rdev); + let devid = DeviceId::from_encoded_u64(info.dev); + let rdevid = DeviceId::from_encoded_u64(info.rdev); // FIXME: We assume it is always not mount_root. let stx_attributes = 0; diff --git a/test/syscall_test/gvisor/blocklists/mknod_test b/test/syscall_test/gvisor/blocklists/mknod_test index f1d108a5d..b747c61eb 100644 --- a/test/syscall_test/gvisor/blocklists/mknod_test +++ b/test/syscall_test/gvisor/blocklists/mknod_test @@ -1,6 +1,13 @@ -MknodTest.MknodAtFIFO -MknodTest.MknodOnExistingPathFails +# Broken support or no support at all. MknodTest.Socket MknodTest.Fifo MknodTest.FifoOtrunc -MknodTest.FifoTruncNoOp \ No newline at end of file +MknodTest.FifoTruncNoOp + +# Buggy or unimplemented in exfat and ext2. +MknodTest.MknodAtFIFO +MknodTest.MknodOnExistingPathFails + +# This test cannot pass in Linux when run as root, and +# it has been removed from the latest version of gVisor. +MknodTest.UnimplementedTypesReturnError