diff --git a/os/StarryOS/kernel/src/file/mod.rs b/os/StarryOS/kernel/src/file/mod.rs index ca298330f1..d4bb973b96 100644 --- a/os/StarryOS/kernel/src/file/mod.rs +++ b/os/StarryOS/kernel/src/file/mod.rs @@ -38,8 +38,8 @@ use axpoll::Pollable; use downcast_rs::{DowncastSync, impl_downcast}; use flatten_objects::FlattenObjects; use linux_raw_sys::general::{ - O_ACCMODE, O_PATH, O_RDONLY, O_RDWR, O_WRONLY, RLIMIT_NOFILE, STATX_BASIC_STATS, stat, statx, - statx_timestamp, + O_ACCMODE, O_PATH, O_RDONLY, O_RDWR, O_WRONLY, RLIMIT_NOFILE, STATX_ATTR_MOUNT_ROOT, + STATX_BASIC_STATS, stat, statx, statx_timestamp, }; use starry_process::Pid; @@ -157,9 +157,11 @@ impl From for statx { // SAFETY: valid for statx let mut statx: statx = unsafe { core::mem::zeroed() }; // We always populate the basic stats; Linux returns the same mask. - // `stx_attributes` is left zero — it reports FS-specific flags we do - // not track. + // Mount-root state is a VFS attribute, so every statx result advertises + // support for it. The syscall layer sets the value when it has a + // resolved filesystem location. statx.stx_mask = STATX_BASIC_STATS; + statx.stx_attributes_mask = STATX_ATTR_MOUNT_ROOT as u64; statx.stx_blksize = value.blksize as _; statx.stx_nlink = value.nlink as _; statx.stx_uid = value.uid as _; diff --git a/os/StarryOS/kernel/src/syscall/fs/stat.rs b/os/StarryOS/kernel/src/syscall/fs/stat.rs index 1e28f063a8..8d8a920719 100644 --- a/os/StarryOS/kernel/src/syscall/fs/stat.rs +++ b/os/StarryOS/kernel/src/syscall/fs/stat.rs @@ -13,7 +13,7 @@ use linux_raw_sys::general::{ use starry_vm::{VmMutPtr, VmPtr}; use crate::{ - file::{Directory, File, get_file_like, memfd::Memfd, resolve_at}, + file::{Directory, File, ResolveAtResult, get_file_like, memfd::Memfd, resolve_at}, mm::{UserPtr, vm_load_path_string}, task::AsThread, }; @@ -134,7 +134,16 @@ pub fn sys_statx( let path = path.nullable().map(vm_load_path_string).transpose()?; debug!("sys_statx <= dirfd: {dirfd}, path: {path:?}, flags: {flags}"); - statxbuf.vm_write(resolve_at(dirfd, path.as_deref(), flags)?.stat()?.into())?; + let resolved = resolve_at(dirfd, path.as_deref(), flags)?; + let mut status: statx = resolved.stat()?.into(); + if let ResolveAtResult::File(location) = &resolved { + status.stx_mask |= linux_raw_sys::general::STATX_MNT_ID; + status.stx_mnt_id = location.mountpoint().mount_id(); + if location.is_root_of_mount() { + status.stx_attributes |= linux_raw_sys::general::STATX_ATTR_MOUNT_ROOT as u64; + } + } + statxbuf.vm_write(status)?; Ok(0) } @@ -245,6 +254,7 @@ fn statfs_mount_flags(loc: &Location) -> u32 { } statfs_flags } + pub fn sys_statfs(path: *const c_char, buf: *mut statfs) -> AxResult { let path = vm_load_path_string(path)?; debug!("sys_statfs <= path: {path:?}"); @@ -319,7 +329,9 @@ pub fn sys_name_to_handle_at( .get_as_mut_slice(FILE_HANDLE_BYTES)? .copy_from_slice(&bytes); - (mount_id as *mut c_int).vm_write(loc.mountpoint().device() as c_int)?; + let resolved_mount_id = c_int::try_from(loc.mountpoint().mount_id()) + .map_err(|_| AxError::from(LinuxError::EOVERFLOW))?; + (mount_id as *mut c_int).vm_write(resolved_mount_id)?; Ok(0) } diff --git a/test-suit/starryos/qemu/system/bugfix-statx-mount-root/CMakeLists.txt b/test-suit/starryos/qemu/system/bugfix-statx-mount-root/CMakeLists.txt new file mode 100644 index 0000000000..87983e6ba4 --- /dev/null +++ b/test-suit/starryos/qemu/system/bugfix-statx-mount-root/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.20) +project(bugfix-statx-mount-root C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) + +add_executable(bugfix-statx-mount-root src/main.c) +target_compile_options(bugfix-statx-mount-root PRIVATE -Wall -Wextra -Werror) +install(TARGETS bugfix-statx-mount-root RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/qemu/system/bugfix-statx-mount-root/src/main.c b/test-suit/starryos/qemu/system/bugfix-statx-mount-root/src/main.c new file mode 100644 index 0000000000..9ac7eeef21 --- /dev/null +++ b/test-suit/starryos/qemu/system/bugfix-statx-mount-root/src/main.c @@ -0,0 +1,173 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#ifndef AT_NO_AUTOMOUNT +#define AT_NO_AUTOMOUNT 0x800 +#endif +#ifndef AT_STATX_DONT_SYNC +#define AT_STATX_DONT_SYNC 0x4000 +#endif +#ifndef STATX_TYPE +#define STATX_TYPE 0x00000001U +#endif +#ifndef STATX_INO +#define STATX_INO 0x00000100U +#endif +#ifndef STATX_MNT_ID +#define STATX_MNT_ID 0x00001000U +#endif +#ifndef STATX_ATTR_MOUNT_ROOT +#define STATX_ATTR_MOUNT_ROOT 0x00002000ULL +#endif + +struct statx_timestamp_abi { + int64_t tv_sec; + uint32_t tv_nsec; + int32_t reserved; +}; + +struct statx_abi { + uint32_t stx_mask; + uint32_t stx_blksize; + uint64_t stx_attributes; + uint32_t stx_nlink; + uint32_t stx_uid; + uint32_t stx_gid; + uint16_t stx_mode; + uint16_t pad0; + uint64_t stx_ino; + uint64_t stx_size; + uint64_t stx_blocks; + uint64_t stx_attributes_mask; + struct statx_timestamp_abi stx_atime; + struct statx_timestamp_abi stx_btime; + struct statx_timestamp_abi stx_ctime; + struct statx_timestamp_abi stx_mtime; + uint32_t stx_rdev_major; + uint32_t stx_rdev_minor; + uint32_t stx_dev_major; + uint32_t stx_dev_minor; + uint64_t stx_mnt_id; + uint64_t spare[13]; +}; + +struct file_handle_abi { + unsigned int handle_bytes; + int handle_type; + unsigned char bytes[128]; +}; + +static int failures; + +static void check(int condition, const char *message) +{ + if (condition) { + printf("PASS: %s\n", message); + return; + } + + fprintf(stderr, "FAIL: %s: errno=%d (%s)\n", message, errno, + strerror(errno)); + failures++; +} + +static void check_mount_root(const char *path, int expected) +{ + struct statx_abi status; + memset(&status, 0, sizeof(status)); + + errno = 0; + long result = syscall(SYS_statx, AT_FDCWD, path, + AT_NO_AUTOMOUNT | AT_STATX_DONT_SYNC, + STATX_TYPE | STATX_INO, &status); + + char message[160]; + snprintf(message, sizeof(message), "statx(2) succeeds for %s", path); + check(result == 0, message); + if (result != 0) { + return; + } + + snprintf(message, sizeof(message), + "statx(2) reports STATX_ATTR_MOUNT_ROOT support for %s", path); + check((status.stx_attributes_mask & STATX_ATTR_MOUNT_ROOT) != 0, + message); + + snprintf(message, sizeof(message), + "statx(2) reports the expected mount-root state for %s", path); + check(((status.stx_attributes & STATX_ATTR_MOUNT_ROOT) != 0) == expected, + message); +} + +static uint64_t check_mount_id(const char *path) +{ + struct statx_abi status; + memset(&status, 0, sizeof(status)); + + errno = 0; + long result = syscall(SYS_statx, AT_FDCWD, path, + AT_NO_AUTOMOUNT | AT_STATX_DONT_SYNC, + STATX_TYPE | STATX_INO | STATX_MNT_ID, &status); + + char message[160]; + snprintf(message, sizeof(message), "statx(2) returns a mount ID for %s", + path); + check(result == 0 && (status.stx_mask & STATX_MNT_ID) != 0 && + status.stx_mnt_id != 0, + message); + return status.stx_mnt_id; +} + +static void check_name_to_handle_mount_id(const char *path, + uint64_t expected_mount_id) +{ + struct file_handle_abi handle; + int mount_id = 0; + memset(&handle, 0, sizeof(handle)); + handle.handle_bytes = sizeof(handle.bytes); + + errno = 0; + long result = syscall(SYS_name_to_handle_at, AT_FDCWD, path, &handle, + &mount_id, 0); + + char message[192]; + snprintf(message, sizeof(message), + "name_to_handle_at(2) mount ID matches statx(2) for %s", path); + check(result == 0 && mount_id > 0 && + (uint64_t)mount_id == expected_mount_id, + message); +} + +int main(void) +{ + check_mount_root("/", 1); + check_mount_root("/proc", 1); + check_mount_root("/proc/1", 0); + check_mount_root("/run", 0); + + uint64_t root_mount_id = check_mount_id("/"); + uint64_t proc_mount_id = check_mount_id("/proc"); + uint64_t proc_child_mount_id = check_mount_id("/proc/1"); + check(root_mount_id != 0 && proc_mount_id != 0 && + root_mount_id != proc_mount_id, + "statx(2) distinguishes separate mounts"); + check(proc_mount_id != 0 && proc_mount_id == proc_child_mount_id, + "statx(2) keeps one mount ID within a mount"); + check_name_to_handle_mount_id("/proc", proc_mount_id); + + if (failures != 0) { + fprintf(stderr, "STARRY_STATX_MOUNT_ROOT_FAILED: %d checks\n", + failures); + return 1; + } + + puts("STARRY_STATX_MOUNT_ROOT_PASSED"); + return 0; +}