Skip to content
Merged
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
4 changes: 3 additions & 1 deletion os/StarryOS/kernel/src/pseudofs/proc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,9 @@ fn render_task_status_fields(status: &TaskStatusFields<'_>) -> String {
Cpus_allowed:\t{}\n\
Cpus_allowed_list:\t{}\n\
Mems_allowed:\t1\n\
Mems_allowed_list:\t0",
Mems_allowed_list:\t0\n\
voluntary_ctxt_switches:\t0\n\
Comment thread
mai-team-app[bot] marked this conversation as resolved.
nonvoluntary_ctxt_switches:\t0",
base.name,
base.state,
base.tgid,
Expand Down
36 changes: 25 additions & 11 deletions os/StarryOS/kernel/src/syscall/fs/ctl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,23 @@ use ax_task::current;
use axfs_ng_vfs::{DeviceId, MetadataUpdate, NodePermission, NodeType, path::Path};
use linux_raw_sys::{
general::*,
ioctl::{BLKGETSIZE64, BLKRAGET, BLKSSZGET, FIOASYNC, FIONBIO, TCGETS, TIOCGWINSZ},
ioctl::{FIOASYNC, FIONBIO},
};
use starry_vm::{VmPtr, vm_write_slice};

use crate::{
file::{Directory, FileLike, fd_is_path, get_file_like, resolve_at, with_fs},
file::{Directory, FD_TABLE, FileLike, fd_is_path, get_file_like, resolve_at, with_fs},
mm::vm_load_string,
task::AsThread,
time::TimeValueLike,
};

/// `FIOCLEX` / `FIONCLEX`: set / clear the close-on-exec flag on a file descriptor
/// via `ioctl` (the ioctl spelling of `fcntl(fd, F_SETFD, ...)`). libc/musl and CPython
/// use these on freshly-opened fds; Linux implements them generically for any fd.
const FIOCLEX: u32 = 0x5451;
const FIONCLEX: u32 = 0x5450;

fn path_info_at(dirfd: i32, path: &str) -> AxResult<(String, bool)> {
with_fs(dirfd, |fs| {
let loc = fs.resolve_no_follow(path)?;
Expand All @@ -51,19 +57,27 @@ pub fn sys_ioctl(fd: i32, cmd: u32, arg: usize) -> AxResult<isize> {
f.set_async_mode(val != 0)?;
return Ok(0);
}
// FIOCLEX/FIONCLEX are fd-table operations (close-on-exec), not device commands —
// handle them here so any fd (not just ttys) accepts them, as Linux does. Without
// this, curses/CPython (glances) hit "Unsupported ioctl command".
if cmd == FIOCLEX || cmd == FIONCLEX {
Comment thread
mai-team-app[bot] marked this conversation as resolved.
FD_TABLE
.write()
.get_mut(fd as _)
.ok_or(AxError::BadFileDescriptor)?
.cloexec = cmd == FIOCLEX;
return Ok(0);
}
f.ioctl(cmd, arg)
.map(|result| result as isize)
.inspect_err(|err| {
if *err == AxError::NotATty {
// Applications commonly probe non-terminal/blobk fds with
// these ioctls; suppress noise.
if matches!(
cmd,
TIOCGWINSZ | TCGETS | BLKGETSIZE64 | BLKRAGET | BLKSSZGET
) {
return;
}
warn!("Unsupported ioctl command: {cmd} for fd: {fd}");
// `NotATty` is a legitimate negative answer to the isatty/termios/winsize/
// console probes (TCGETS, KDGKBTYPE, TIOCGPGRP, ...) that libc, ncurses and
// CPython fire at every fd — not an unimplemented command. Log at debug
// only: a warn per probe spams the serial console and visibly corrupts
// full-screen TUIs (htop/glances) drawing on that same console.
debug!("ioctl {cmd} on non-tty fd {fd} -> ENOTTY (probe)");
}
})
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.20)
project(test-ioctl-cloexec C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
add_executable(test-ioctl-cloexec src/main.c)
target_include_directories(test-ioctl-cloexec PRIVATE src)
target_compile_options(test-ioctl-cloexec PRIVATE -Wall -Wextra -Werror)
install(TARGETS test-ioctl-cloexec RUNTIME DESTINATION usr/bin/starry-test-suit)
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#define _GNU_SOURCE
#include "test_framework.h"
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>

/*
* ioctl(FIOCLEX) / ioctl(FIONCLEX) 回归测试 — 对应 PR #1168 (fix-tui-procfs-ioctl)。
*
* 触发背景 (为什么写这个测例):
* ncurses / CPython (glances/htop) 在每个新开 fd 上用 ioctl(FIOCLEX) 设置
* close-on-exec —— 这是 fcntl(fd, F_SETFD, FD_CLOEXEC) 的 ioctl 拼写。starry
* 此前只在 tty 设备路径里识别 ioctl, 普通(非 tty) fd 上 FIOCLEX 落到通用
* 分支 -> "Unsupported ioctl command: 21585" 并被拒绝, FD_CLOEXEC 没被设置。
*
* man 2 ioctl_tty / linux ioctl 通用语义:
* FIOCLEX (0x5451): set close-on-exec == fcntl(fd, F_SETFD, FD_CLOEXEC)
* FIONCLEX (0x5450): clear close-on-exec == fcntl(fd, F_SETFD, 0)
* Linux 对**任意** fd 都通用实现 (不限于 tty)。
*
* starry 实现 (kernel/src/syscall/fs/ctl.rs sys_ioctl):
* FIOCLEX/FIONCLEX 在 ioctl 入口直接改 FD_TABLE 项的 cloexec 位 (镜像 fcntl
* F_SETFD), 任意 fd 接受; 无效 fd 由入口 get_file_like(fd) 先返回 EBADF。
*
* 修复前: ioctl(FIOCLEX) 在普通 fd 上失败 / FD_CLOEXEC 不变 -> 本测例 FAIL。
* 修复后: FD_CLOEXEC 经 fcntl(F_GETFD) 可见地被 set/clear -> PASS。
*
* 用非 tty fd: pipe (与 test_ioctl_fionbio_int 同源), 不依赖 /dev/tty 是否存在。
*/

#ifndef FIOCLEX
#define FIOCLEX 0x5451
#endif
#ifndef FIONCLEX
#define FIONCLEX 0x5450
#endif

int main(void)
{
TEST_START("ioctl FIOCLEX/FIONCLEX -> FD_CLOEXEC");

int p[2];
CHECK_RET(pipe(p), 0, "pipe() 创建非 tty fd 成功");

/* 新建 fd 默认 close-on-exec 关闭 */
int fd0 = fcntl(p[0], F_GETFD);
CHECK(fd0 >= 0, "F_GETFD 初始读取成功");
CHECK((fd0 & FD_CLOEXEC) == 0, "新 pipe fd 初始 FD_CLOEXEC 未设置");

/* ---- FIOCLEX 应 set FD_CLOEXEC (等价 fcntl F_SETFD FD_CLOEXEC) ---- */
CHECK_RET(ioctl(p[0], FIOCLEX), 0, "ioctl(FIOCLEX) 在非 tty fd 上返回 0");
int fd1 = fcntl(p[0], F_GETFD);
CHECK(fd1 >= 0, "F_GETFD (FIOCLEX 后) 成功");
CHECK((fd1 & FD_CLOEXEC) != 0, "FIOCLEX 后 FD_CLOEXEC 被设置");

/* ---- FIONCLEX 应 clear FD_CLOEXEC ---- */
CHECK_RET(ioctl(p[0], FIONCLEX), 0, "ioctl(FIONCLEX) 返回 0");
int fd2 = fcntl(p[0], F_GETFD);
CHECK(fd2 >= 0, "F_GETFD (FIONCLEX 后) 成功");
CHECK((fd2 & FD_CLOEXEC) == 0, "FIONCLEX 后 FD_CLOEXEC 被清除");

/* ---- 与 fcntl(F_SETFD) 互证: 两条路径应一致 ---- */
CHECK_RET(fcntl(p[0], F_SETFD, FD_CLOEXEC), 0, "fcntl(F_SETFD, FD_CLOEXEC) 成功");
CHECK((fcntl(p[0], F_GETFD) & FD_CLOEXEC) != 0, "fcntl 设置后 FD_CLOEXEC 可见");
CHECK_RET(ioctl(p[0], FIONCLEX), 0, "ioctl(FIONCLEX) 再次清除 fcntl 设的位");
CHECK((fcntl(p[0], F_GETFD) & FD_CLOEXEC) == 0, "FIONCLEX 清除 fcntl 设的位生效");

close(p[0]);
close(p[1]);

/* ---- 无效 fd 应返回 EBADF (入口 get_file_like 先校验) ---- */
CHECK_ERR(ioctl(-1, FIOCLEX), EBADF, "ioctl(FIOCLEX) 无效 fd=-1 返回 EBADF");
CHECK_ERR(ioctl(p[0], FIOCLEX), EBADF, "ioctl(FIOCLEX) 已关闭 fd 返回 EBADF");

TEST_DONE();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#pragma once

/* 必须在最前面定义,确保 pipe2/gettid 等可用 */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

/*
* StarryOS Syscall Test Framework
*
* 极简独立测试框架:每个文件测一个 syscall,独立编译运行。
* 目标:出错时精确定位到 源文件:行号 -> 哪个调用 -> 什么结果
*
* 用法:
* TEST_START("测试名");
* CHECK(call == expected, "描述");
* CHECK_ERR(call, EBADF, "描述");
* TEST_DONE();
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

static int __pass = 0;
static int __fail = 0;

/* ---- 核心: 带文件名+行号的检查宏 ---- */

/* 检查条件为真 */
#define CHECK(cond, msg) do { \
if (cond) { \
printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \
__pass++; \
} else { \
printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \
__FILE__, __LINE__, msg, errno, strerror(errno)); \
__fail++; \
} \
} while(0)

/* 检查 syscall 返回特定值 */
#define CHECK_RET(call, expected, msg) do { \
errno = 0; \
long _r = (long)(call); \
long _e = (long)(expected); \
if (_r == _e) { \
printf(" PASS | %s:%d | %s (ret=%ld)\n", \
__FILE__, __LINE__, msg, _r); \
__pass++; \
} else { \
printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \
__FILE__, __LINE__, msg, _e, _r, errno, strerror(errno));\
__fail++; \
} \
} while(0)

/* 检查 syscall 失败且 errno 符合预期 */
#define CHECK_ERR(call, exp_errno, msg) do { \
errno = 0; \
long _r = (long)(call); \
if (_r == -1 && errno == (exp_errno)) { \
printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \
__FILE__, __LINE__, msg, errno); \
__pass++; \
} else { \
printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \
__FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno));\
__fail++; \
} \
} while(0)

/* ---- 测试边界 ---- */
#define TEST_START(name) \
printf("================================================\n"); \
printf(" TEST: %s\n", name); \
printf(" FILE: %s\n", __FILE__); \
printf("================================================\n")

#define TEST_DONE() \
printf("------------------------------------------------\n"); \
printf(" DONE: %d pass, %d fail\n", __pass, __fail); \
printf("================================================\n\n"); \
return __fail > 0 ? 1 : 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.20)
project(test-procfs-ctxt-switches C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
add_executable(test-procfs-ctxt-switches src/main.c)
target_include_directories(test-procfs-ctxt-switches PRIVATE src)
target_compile_options(test-procfs-ctxt-switches PRIVATE -Wall -Wextra -Werror)
install(TARGETS test-procfs-ctxt-switches RUNTIME DESTINATION usr/bin/starry-test-suit)
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#define _GNU_SOURCE
#include "test_framework.h"
#include <stdint.h>
#include <unistd.h>

/*
* /proc/<pid>/status 上下文切换计数回归测试 — 对应 PR #1168 (fix-tui-procfs-ioctl)。
*
* 触发背景 (为什么写这个测例):
* htop / glances 解析 /proc/<pid>/status 的 voluntary_ctxt_switches /
* nonvoluntary_ctxt_switches 来显示每进程上下文切换数。starry 此前 status
* 缺这两行 -> TUI 解析得不到字段。补 stub 行后可被解析。
*
* man 5 proc §/proc/[pid]/status:
* "voluntary_ctxt_switches, nonvoluntary_ctxt_switches: Number of voluntary
* and involuntary context switches (since Linux 2.6.23)."
* 格式: "<name>:\t<number>"。
*
* starry 实现 (kernel/src/pseudofs/proc.rs): status 末尾追加两行
* voluntary_ctxt_switches:\t<n> / nonvoluntary_ctxt_switches:\t<n>。
*
* 修复前: 字段不存在 -> 找不到 -> FAIL。
* 修复后: 两字段都存在且能解析为非负整数 -> PASS。
*
* 同时覆盖 /proc/self/status 与 /proc/<getpid()>/status 两种路径。
*/

/* 在 status 文件里查找 "<name>:" 行并把其后的整数解析到 *out。找到且可解析返回 0。 */
static int find_status_field(const char *path, const char *name, unsigned long *out)
{
FILE *f = fopen(path, "r");
if (!f)
return -1;
char line[256];
int rc = -1;
size_t nlen = strlen(name);
while (fgets(line, sizeof line, f)) {
if (strncmp(line, name, nlen) == 0 && line[nlen] == ':') {
if (sscanf(line + nlen + 1, "%lu", out) == 1)
rc = 0;
break;
}
}
fclose(f);
return rc;
}

static void check_path(const char *path)
{
unsigned long v = 0;
char msg[160];

int rc = find_status_field(path, "voluntary_ctxt_switches", &v);
snprintf(msg, sizeof msg, "%s: voluntary_ctxt_switches 存在且可解析为整数 (=%lu)",
path, v);
CHECK(rc == 0, msg);

v = 0;
rc = find_status_field(path, "nonvoluntary_ctxt_switches", &v);
snprintf(msg, sizeof msg, "%s: nonvoluntary_ctxt_switches 存在且可解析为整数 (=%lu)",
path, v);
CHECK(rc == 0, msg);
}

int main(void)
{
TEST_START("/proc/<pid>/status ctxt_switches");

check_path("/proc/self/status");

char pidpath[64];
snprintf(pidpath, sizeof pidpath, "/proc/%d/status", (int)getpid());
check_path(pidpath);

TEST_DONE();
}
Loading
Loading