Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions os/StarryOS/kernel/src/file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -157,9 +157,11 @@ impl From<Kstat> 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 _;
Expand Down
18 changes: 15 additions & 3 deletions os/StarryOS/kernel/src/syscall/fs/stat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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<isize> {
let path = vm_load_path_string(path)?;
debug!("sys_statfs <= path: {path:?}");
Expand Down Expand Up @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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)
173 changes: 173 additions & 0 deletions test-suit/starryos/qemu/system/bugfix-statx-mount-root/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#define _GNU_SOURCE

#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>

#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;
}
Loading