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
16 changes: 16 additions & 0 deletions os/StarryOS/kernel/src/file/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ const IFF_MULTICAST: i16 = 0x1000;
const IFREQ_NAME_LEN: usize = 16;
const IFREQ_DATA_OFFSET: usize = 16;
const IFREQ_COMPAT_LEN: usize = 40;
// ethtool ioctl; not exported by linux-raw-sys. The value is arch-independent.
const SIOCETHTOOL: u32 = 0x8946;
const IFCONF_LEN_OFFSET: usize = 0;
const IFCONF_BUF_OFFSET: usize = 8;

Expand Down Expand Up @@ -345,6 +347,20 @@ impl FileLike for Socket {
let idx = read_ifreq_interface(arg)?.id.get() as i32;
write_ifreq_data(arg, &idx.to_ne_bytes())?;
}
// Link speed/duplex query. No PHY is emulated, so report "not supported" the way a
// virtual NIC (loopback, tun/tap) does. Tools like psutil's net_if_stats() treat
// EOPNOTSUPP as "no ethtool" and degrade gracefully; any other errno makes them abort
// the whole interface-status probe. Resolve the interface first so an unknown name
// yields ENODEV, then fault on a bad ifr_data pointer, keeping Linux's error priority
// (ENODEV, then EFAULT, then EOPNOTSUPP) and parity with the sibling SIOC*IF* arms.
SIOCETHTOOL => {
read_ifreq_interface(arg)?;
let data_ptr = usize::from_ne_bytes(read_user_bytes::<8>(
(arg + IFREQ_DATA_OFFSET) as *const u8,
)?);
read_user_bytes::<4>(data_ptr as *const u8)?;
return Err(AxError::OperationNotSupported);
Comment thread
ZR233 marked this conversation as resolved.
}
_ => {
if super::wext::is_wext_ioctl(cmd) {
return super::wext::handle(cmd, arg);
Expand Down
25 changes: 25 additions & 0 deletions os/StarryOS/kernel/src/pseudofs/proc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,29 @@ fn render_mounts() -> String {
buf
}

fn render_mountinfo() -> String {
// /proc/<pid>/mountinfo (Linux fs/proc_namespace.c show_mountinfo layout):
// id parent major:minor root mount_point options [optional-fields] - fstype source super_opts
// Same mount set as render_mounts(): the root fs type is read live; the pseudo mounts are the
// fixed boot set. No optional propagation fields are emitted, so the "-" separator immediately
// precedes the fs type. Tools such as node_exporter's filesystem collector and findmnt read
// this file (in preference to /proc/mounts) to discover mount points before statfs().
let root_fstype = {
let ctx = FS_CONTEXT.lock();
ctx.root_dir().filesystem().name().to_string()
};
let mut buf = format!("21 20 {VIRTBLK_MAJOR}:0 / / rw,relatime - {root_fstype} /dev/vda rw\n");
buf.push_str("22 21 0:5 / /dev rw,nosuid,relatime - devtmpfs devtmpfs rw\n");
buf.push_str("23 22 0:16 / /dev/shm rw,nosuid,nodev - tmpfs tmpfs rw\n");
buf.push_str("24 21 0:17 / /tmp rw,nosuid,nodev - tmpfs tmpfs rw\n");
buf.push_str("25 21 0:18 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw\n");
buf.push_str("26 21 0:19 / /sys rw,nosuid,nodev,noexec,relatime - sysfs sysfs rw\n");
buf.push_str(
"27 26 0:20 / /sys/kernel/debug rw,nosuid,nodev,noexec,relatime - debugfs debugfs rw\n",
);
buf
}

pub fn new_procfs() -> Filesystem {
SimpleFs::new_with("proc".into(), 0x9fa0, builder)
}
Expand Down Expand Up @@ -1009,6 +1032,7 @@ impl SimpleDirOps for ThreadDir {
"mem",
"auxv",
"mounts",
"mountinfo",
"cmdline",
"comm",
"exe",
Expand Down Expand Up @@ -1104,6 +1128,7 @@ impl SimpleDirOps for ThreadDir {
.into(),
"auxv" => SimpleFile::new_regular(fs, move || Ok(render_thread_auxv(&task))).into(),
"mounts" => SimpleFile::new_regular(fs, move || Ok(render_mounts())).into(),
"mountinfo" => SimpleFile::new_regular(fs, move || Ok(render_mountinfo())).into(),
"cmdline" => SimpleFile::new_regular(fs, move || {
let cmdline = task.as_thread().proc_data.cmdline.read();
let mut buf = Vec::new();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.20)
project(test-ethtool-ioctl C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
add_executable(test-ethtool-ioctl src/main.c)
target_include_directories(test-ethtool-ioctl PRIVATE src)
target_compile_options(test-ethtool-ioctl PRIVATE -Wall -Wextra -Werror)
install(TARGETS test-ethtool-ioctl RUNTIME DESTINATION usr/bin/starry-test-suit)
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#define _GNU_SOURCE
#include "test_framework.h"
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>

/*
* SIOCETHTOOL ioctl 回归测试。
*
* 触发背景 (为什么写这个测例):
* glances 的 NETWORK 区块通过 psutil 的 net_if_stats() 采集每个接口的状态,
* 其中 speed/duplex 用 SIOCETHTOOL (ETHTOOL_GSET) 查询 PHY 链路。starry 没有
* 模拟 PHY, 此前 SIOCETHTOOL 落到 socket ioctl 的默认分支返回 ENOTTY。psutil
* 只把 EOPNOTSUPP 当作"无 ethtool"优雅降级, 遇到任何其它 errno 会 abort 整个
* interface-status 探测 -> glances NETWORK 区块渲染崩溃。内核现在像虚拟网卡
* (loopback / tun/tap) 一样对 SIOCETHTOOL 返回 EOPNOTSUPP。
*
* Linux 参照 (fresh host, kernel 6.6):
* ioctl(AF_INET sock, SIOCETHTOOL, {ifr_name="lo"}) => -1 errno=EOPNOTSUPP(95);
* loopback 无 ethtool_ops, dev_ethtool() 返回 -EOPNOTSUPP。
*
* 断言:
* 1. AF_INET SOCK_DGRAM 套接字创建成功。
* 2. 对 "lo" 发 SIOCETHTOOL 返回 -1 (查询被拒)。
* 3. errno == EOPNOTSUPP (优雅"无 ethtool"; 修复前是 ENOTTY, 会让 psutil abort)。
*/

/* linux-raw-sys / <linux/sockios.h> 常量, arch 无关; 避免依赖 <linux/ethtool.h>。*/
#ifndef SIOCETHTOOL
#define SIOCETHTOOL 0x8946
#endif
#define ETHTOOL_GSET 0x00000001u

int main(void)
{
TEST_START("SIOCETHTOOL returns EOPNOTSUPP on virtual NIC");
char msg[160];

int fd = socket(AF_INET, SOCK_DGRAM, 0);
CHECK(fd >= 0, "socket(AF_INET, SOCK_DGRAM) 创建成功");
if (fd < 0) {
TEST_DONE(3);
}

/*
* ifr_data 指向一个 ethtool 命令字 (ETHTOOL_GSET)。内核在读取 ifr_data 之前
* 就返回 EOPNOTSUPP, 但仍按 psutil 的真实调用形态填好请求。
*/
unsigned int ethcmd = ETHTOOL_GSET;
struct ifreq ifr;
memset(&ifr, 0, sizeof ifr);
strncpy(ifr.ifr_name, "lo", IFNAMSIZ - 1);
ifr.ifr_data = (void *)&ethcmd;

errno = 0;
int ret = ioctl(fd, SIOCETHTOOL, &ifr);
int saved_errno = errno;
close(fd);

snprintf(msg, sizeof msg, "SIOCETHTOOL 查询被拒 ret == -1 (实际 %d)", ret);
CHECK(ret == -1, msg);

snprintf(msg, sizeof msg,
"errno == EOPNOTSUPP(%d) 优雅降级 (实际 %d %s; 修复前为 ENOTTY)",
EOPNOTSUPP, saved_errno, strerror(saved_errno));
CHECK(saved_errno == EOPNOTSUPP, msg);

/*
* 未知接口名: 内核应先解析接口再判 ethtool, 因此返回 ENODEV 而非无条件
* EOPNOTSUPP。固定与 Linux 一致的错误优先级, 也与本分支其它 SIOC*IF* 一致。
*/
int fd2 = socket(AF_INET, SOCK_DGRAM, 0);
CHECK(fd2 >= 0, "socket(AF_INET, SOCK_DGRAM) 二次创建成功");

struct ifreq bad;
memset(&bad, 0, sizeof bad);
strncpy(bad.ifr_name, "nonexist99", IFNAMSIZ - 1);
bad.ifr_data = (void *)&ethcmd;

errno = 0;
int bad_ret = ioctl(fd2, SIOCETHTOOL, &bad);
int bad_errno = errno;
close(fd2);

snprintf(msg, sizeof msg, "SIOCETHTOOL 未知接口被拒 ret == -1 (实际 %d)", bad_ret);
CHECK(bad_ret == -1, msg);

snprintf(msg, sizeof msg,
"errno == ENODEV(%d) 接口校验先于 EOPNOTSUPP (实际 %d %s)", ENODEV,
bad_errno, strerror(bad_errno));
CHECK(bad_errno == ENODEV, msg);

/*
* 合法接口但 ifr_data 是坏指针: 内核读取 ethtool 命令字时先 fault, 返回
* EFAULT 而非 EOPNOTSUPP。错误优先级为 ENODEV -> EFAULT -> EOPNOTSUPP。
*/
int fd3 = socket(AF_INET, SOCK_DGRAM, 0);
CHECK(fd3 >= 0, "socket(AF_INET, SOCK_DGRAM) 三次创建成功");

struct ifreq faulty;
memset(&faulty, 0, sizeof faulty);
strncpy(faulty.ifr_name, "lo", IFNAMSIZ - 1);
faulty.ifr_data = (void *)1;

errno = 0;
int fault_ret = ioctl(fd3, SIOCETHTOOL, &faulty);
int fault_errno = errno;
close(fd3);

snprintf(msg, sizeof msg, "SIOCETHTOOL 坏 ifr_data 被拒 ret == -1 (实际 %d)", fault_ret);
CHECK(fault_ret == -1, msg);

snprintf(msg, sizeof msg,
"errno == EFAULT(%d) 坏指针先于 EOPNOTSUPP (实际 %d %s)", EFAULT,
fault_errno, strerror(fault_errno));
CHECK(fault_errno == EFAULT, msg);

// 9 = 本文件 CHECK 总数。
TEST_DONE(9);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#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")

/*
* 结束测试。expected = 本文件声明的 CHECK 总数。
* 只有恰好跑满 expected 个检查且零失败才返回 0(成功);
* 零检查 / 少跑(早退/异常) / 有失败 一律返回 1(失败),
* 堵死"只跑部分模块也算过"的假阳性。
*/
#define TEST_DONE(expected) \
printf("------------------------------------------------\n"); \
printf(" DONE: %d pass, %d fail, %d/%d checks run\n", \
__pass, __fail, __pass + __fail, (expected)); \
if ((__pass + __fail) != (expected)) \
printf(" FAIL | %s:%d | ran %d check(s), expected %d\n", \
__FILE__, __LINE__, __pass + __fail, (expected)); \
printf("================================================\n\n"); \
return ((__fail > 0) || ((__pass + __fail) != (expected))) ? 1 : 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.20)
project(test-mountinfo C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
add_executable(test-mountinfo src/main.c)
target_include_directories(test-mountinfo PRIVATE src)
target_compile_options(test-mountinfo PRIVATE -Wall -Wextra -Werror)
install(TARGETS test-mountinfo RUNTIME DESTINATION usr/bin/starry-test-suit)
Loading