Clarify the `DeviceId` encoding

This commit is contained in:
Ruihan Li 2025-07-02 18:40:36 +08:00 committed by Jianfeng Jiang
parent a13297ae4c
commit d73cbb5008
9 changed files with 41 additions and 42 deletions

View File

@ -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<Arc<dyn Device>> {
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<Arc<dyn Device>> {
let major = devid.major();
let minor = devid.minor();
@ -79,6 +74,6 @@ pub fn get_device(dev: usize) -> Result<Arc<dyn Device>> {
(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"),
}
}

View File

@ -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:
/// <https://github.com/torvalds/linux/blob/0ff41df1cb268fc69e703a08a57ee14ae967d0ca/include/linux/kdev_t.h#L39-L44>
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:
/// <https://github.com/torvalds/linux/blob/0ff41df1cb268fc69e703a08a57ee14ae967d0ca/include/linux/kdev_t.h#L39-L44>.
///
/// 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:
/// <https://github.com/torvalds/linux/blob/0ff41df1cb268fc69e703a08a57ee14ae967d0ca/include/linux/types.h#L18>.
/// So this encoding follows the implementation in glibc:
/// <https://github.com/bminor/glibc/blob/632d895f3e5d98162f77b9c3c1da4ec19968b671/bits/sysmacros.h#L26-L34>.
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<DeviceId> for u64 {
fn from(value: DeviceId) -> Self {
value.as_encoded_u64()
}
}
impl From<u64> for DeviceId {
fn from(raw: u64) -> Self {
Self::decode_from_u64(raw)
}
}
/// Add a device node to FS for the device.

View File

@ -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!(),

View File

@ -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()

View File

@ -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 {

View File

@ -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(),
}
}

View File

@ -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 => {

View File

@ -118,8 +118,8 @@ pub struct Statx {
impl From<Metadata> 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;

View File

@ -1,6 +1,13 @@
MknodTest.MknodAtFIFO
MknodTest.MknodOnExistingPathFails
# Broken support or no support at all.
MknodTest.Socket
MknodTest.Fifo
MknodTest.FifoOtrunc
MknodTest.FifoTruncNoOp
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