diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-open-family/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-open-family/c/CMakeLists.txt index e8e6fb7112..3ecf3bf278 100644 --- a/test-suit/starryos/normal/qemu-smp1/syscall/test-open-family/c/CMakeLists.txt +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-open-family/c/CMakeLists.txt @@ -4,7 +4,7 @@ set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS OFF) -# 地毯式全覆盖 28 模块(按维度切分),每模块自计 __pass/__fail,main 汇总。 +# 地毯式全覆盖 30 模块(按维度切分),每模块自计 __pass/__fail,main 汇总。 # 见 notes/02 §17 + notes/10 §1.2。 add_executable(test-open-family src/main.c @@ -36,7 +36,9 @@ add_executable(test-open-family src/openat_dirfd.c src/openat_err.c src/openat_flag_matrix.c - src/openat_creat.c) + src/openat_creat.c + src/open_dev_null_zero.c + src/open_pread_pwrite.c) target_include_directories(test-open-family PRIVATE src) target_compile_options(test-open-family PRIVATE -Wall -Wextra -Werror) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-open-family/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-open-family/c/src/main.c index b7c56b49a0..439cea3aef 100644 --- a/test-suit/starryos/normal/qemu-smp1/syscall/test-open-family/c/src/main.c +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-open-family/c/src/main.c @@ -10,7 +10,7 @@ /* * test-open-family —— open / openat 系统调用族「地毯式全覆盖」测例汇总 * - * 设计:28 个模块文件按维度切分(15 阶段① 基础+矩阵 / 8 阶段② ERRNO / 4 阶段③ openat / 1 阶段④ stress);每个模块自管私有 /tmp/topen_/ 目录, + * 设计:30 个模块文件按维度切分(16 阶段① 基础+矩阵含 stress / 8 阶段② ERRNO / 4 阶段③ openat / 2 阶段④ 补充:设备节点目标 + fd 偏移/定位 I/O);每个模块自管私有 /tmp/topen_/ 目录, * 自计 __pass/__fail,run() 末尾打印小结并 return __fail。main 按顺序跑、汇总。 * * 模块 → 见 notes/02 §17 + notes/10 §1.2 矩阵设计表。 @@ -52,6 +52,9 @@ int openat_dirfd_run(void); int openat_err_run(void); int openat_flag_matrix_run(void); int openat_creat_run(void); +/* 阶段④ 补充:未覆盖的 open 目标类型 / fd 偏移语义 */ +int open_dev_null_zero_run(void); +int open_pread_pwrite_run(void); /* ── global setup / teardown ── * 建立全模块共用的只读基线环境:OF_DIR 子树。 */ @@ -77,7 +80,7 @@ static void global_teardown(void) int main(void) { - TEST_START("open family: 地毯式全覆盖(28 模块)"); + TEST_START("open family: 地毯式全覆盖(30 模块)"); global_setup(); /* setup_fail:取自 global_setup 的 __fail(来自 CHECK 宏的累计)。 @@ -154,6 +157,9 @@ int main(void) COLLECT("openat_err", openat_err_run()); COLLECT("openat_flagmat", openat_flag_matrix_run()); COLLECT("openat_creat", openat_creat_run()); + /* 阶段④ 补充 */ + COLLECT("dev_null_zero", open_dev_null_zero_run()); + COLLECT("pread_pwrite", open_pread_pwrite_run()); #undef COLLECT diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-open-family/c/src/open_dev_null_zero.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-open-family/c/src/open_dev_null_zero.c new file mode 100644 index 0000000000..7d667460ea --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-open-family/c/src/open_dev_null_zero.c @@ -0,0 +1,118 @@ +#define _GNU_SOURCE +#include "test_framework.h" +#include "helpers.h" + +#include +#include +#include +#include +#include + +/* open() 打开「字符设备节点」目标 —— /dev/null 与 /dev/zero。 + * + * 现有 28 模块覆盖 open 的目标类型:普通文件 / 目录 / 有效 symlink / + * 悬空 symlink / 目录 symlink,但未覆盖「打开字符设备节点」这一目标类型。 + * 本模块补该缺口:open 对设备节点解析成功后返回 fd,其 read/write 语义由 + * 设备驱动决定。 + * + * man 2 open 原文(节选): + * "open() ... return a file descriptor ... a small, nonnegative integer + * ... The file offset is set to the beginning of the file." + * man 4 null(/dev/null、/dev/zero)原文(节选): + * "Data written to a null ... special file is discarded." + * "Reads from a null special file always return end of file (i.e., + * read(2) returns 0), whereas reads from /dev/zero always return + * bytes containing zero ('\0' characters)." + * "Writes to ... /dev/zero ... succeed ... the data is discarded." + * + * 仅验「打开成功 + 设备 read/write 语义 + honored flag 不被破坏」, + * 不验设备驱动实现细节。 */ + +/* /dev/null:read 立即 EOF;write 接受并丢弃(返回写入字节数)。 */ +static void devnull_semantics(void) +{ + int fd = open("/dev/null", O_RDONLY); + CHECK(fd >= 0, "/dev/null: open O_RDONLY ok"); + if (fd >= 0) { + char buf[16]; + CHECK(read(fd, buf, sizeof(buf)) == 0, "/dev/null: read returns 0 (EOF)"); + close(fd); + } + + fd = open("/dev/null", O_WRONLY); + CHECK(fd >= 0, "/dev/null: open O_WRONLY ok"); + if (fd >= 0) { + CHECK(write(fd, "discard me", 10) == 10, "/dev/null: write accepts + discards (returns count)"); + close(fd); + } + + fd = open("/dev/null", O_RDWR); + CHECK(fd >= 0, "/dev/null: open O_RDWR ok"); + if (fd >= 0) { + char buf[4]; + CHECK(write(fd, "x", 1) == 1, "/dev/null: O_RDWR write ok"); + CHECK(read(fd, buf, sizeof(buf)) == 0, "/dev/null: O_RDWR read EOF"); + close(fd); + } +} + +/* /dev/zero:read 总返回全 0 字节(无限源);write 接受并丢弃。 */ +static void devzero_semantics(void) +{ + int fd = open("/dev/zero", O_RDONLY); + CHECK(fd >= 0, "/dev/zero: open O_RDONLY ok"); + if (fd >= 0) { + char buf[64]; + memset(buf, 0xAB, sizeof(buf)); + ssize_t n = read(fd, buf, sizeof(buf)); + CHECK(n == (ssize_t)sizeof(buf), "/dev/zero: read fills the whole buffer"); + int all_zero = 1; + for (size_t i = 0; i < sizeof(buf); i++) + if (buf[i] != 0) + all_zero = 0; + CHECK(all_zero, "/dev/zero: read returns all-zero bytes"); + + /* 第二次 read 仍是全 0(无限源,offset 推进不影响内容) */ + memset(buf, 0xAB, sizeof(buf)); + n = read(fd, buf, 8); + int second_zero = (n == 8); + for (int i = 0; i < 8 && second_zero; i++) + if (buf[i] != 0) + second_zero = 0; + CHECK(second_zero, "/dev/zero: second read still all-zero"); + close(fd); + } + + fd = open("/dev/zero", O_WRONLY); + CHECK(fd >= 0, "/dev/zero: open O_WRONLY ok"); + if (fd >= 0) { + CHECK(write(fd, "ignored", 7) == 7, "/dev/zero: write accepts + discards"); + close(fd); + } +} + +/* honored flag(O_CLOEXEC)在设备节点上同样生效。 */ +static void device_honored_flags(void) +{ + int fd = open("/dev/null", O_RDONLY | O_CLOEXEC); + CHECK(fd >= 0, "/dev/null: open O_RDONLY|O_CLOEXEC ok"); + if (fd >= 0) { + int fl = fcntl(fd, F_GETFD); + CHECK(fl >= 0 && (fl & FD_CLOEXEC), "/dev/null: FD_CLOEXEC honored on a device fd"); + close(fd); + } + + /* O_DIRECTORY 用在非目录的设备节点上 → ENOTDIR(与普通文件一致) */ + CHECK_ERR(open("/dev/null", O_RDONLY | O_DIRECTORY), ENOTDIR, + "/dev/null: O_DIRECTORY on a char device → ENOTDIR"); +} + +int open_dev_null_zero_run(void) +{ + printf("\n----- open_dev_null_zero -----\n"); + devnull_semantics(); + devzero_semantics(); + device_honored_flags(); + printf(" ----- open_dev_null_zero: %d pass, %d fail -----\n", __pass, __fail); + return __fail; +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-open-family/c/src/open_pread_pwrite.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-open-family/c/src/open_pread_pwrite.c new file mode 100644 index 0000000000..2a089d403c --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-open-family/c/src/open_pread_pwrite.c @@ -0,0 +1,110 @@ +#define _GNU_SOURCE +#include "test_framework.h" +#include "helpers.h" + +#include +#include +#include +#include +#include + +#define M_DIR OF_MOD("pread_pwrite") +#define M_FILE M_DIR "/ro" /* 只读基线,内容 "0123456789",测例不改 */ +#define M_FILEW M_DIR "/rw" /* pwrite 目标,会被改写 */ + +/* open() 返回的 fd 的「文件偏移 / 定位 I/O」语义。 + * + * 现有模块 open_fd_semantics 覆盖 fd 表(dup / 最低可用 fd / O_CLOEXEC 表项), + * 但未覆盖 open 返回的 open file description 的「文件偏移」这一维度。本模块补: + * 初始 offset、read 推进 offset、pread/pwrite 不改 offset、lseek 定位。 + * + * man 2 open 原文: + * "A call to open() creates a new open file description ... The file offset + * is set to the beginning of the file (see lseek(2))." + * man 2 pread 原文: + * "pread() reads up to count bytes from file descriptor fd at offset offset + * (from the start of the file) into the buffer ... The file offset is not + * changed." + * man 2 pwrite 原文: + * "pwrite() writes up to count bytes from the buffer ... to the file + * descriptor fd at offset offset ... The file offset is not changed." */ + +static void mod_setup(void) +{ + cleanup_tree(M_DIR); + CHECK(ensure_dir(M_DIR) == 0, "pread_pwrite setup: mkdir"); + CHECK(write_file(M_FILE, "0123456789", 10, 0644) == 0, "pread_pwrite setup: ro 10-byte file"); + CHECK(write_file(M_FILEW, "0123456789", 10, 0644) == 0, "pread_pwrite setup: rw 10-byte file"); +} + +/* open 后初始 offset = 0;read 按读取字节数推进 offset */ +static void initial_offset_and_read_advance(void) +{ + int fd = open(M_FILE, O_RDONLY); + CHECK(fd >= 0, "open RDONLY ok"); + if (fd < 0) + return; + CHECK(lseek(fd, 0, SEEK_CUR) == 0, "open: initial file offset is 0"); + char buf[4] = {0}; + CHECK(read(fd, buf, 3) == 3 && memcmp(buf, "012", 3) == 0, "read 3 bytes → '012'"); + CHECK(lseek(fd, 0, SEEK_CUR) == 3, "read advanced offset to 3"); + close(fd); +} + +/* pread 在指定 offset 读,且不改变 fd 的 offset */ +static void pread_no_offset_change(void) +{ + int fd = open(M_FILE, O_RDONLY); + CHECK(fd >= 0, "open RDONLY ok"); + if (fd < 0) + return; + char buf[4] = {0}; + CHECK(pread(fd, buf, 3, 5) == 3 && memcmp(buf, "567", 3) == 0, "pread at off=5 reads '567'"); + CHECK(lseek(fd, 0, SEEK_CUR) == 0, "pread did NOT change the fd offset (still 0)"); + /* 普通 read 仍从 offset 0 开始 */ + memset(buf, 0, sizeof(buf)); + CHECK(read(fd, buf, 3) == 3 && memcmp(buf, "012", 3) == 0, "read after pread still reads from 0"); + close(fd); +} + +/* pwrite 写到指定 offset,且不改变 fd 的 offset */ +static void pwrite_no_offset_change(void) +{ + int fd = open(M_FILEW, O_RDWR); + CHECK(fd >= 0, "open RDWR ok"); + if (fd < 0) + return; + CHECK(pwrite(fd, "XY", 2, 4) == 2, "pwrite 2 bytes at off=4"); + CHECK(lseek(fd, 0, SEEK_CUR) == 0, "pwrite did NOT change the fd offset (still 0)"); + char buf[16] = {0}; + CHECK(pread(fd, buf, 10, 0) == 10 && memcmp(buf, "0123XY6789", 10) == 0, + "pwrite landed exactly at off=4"); + close(fd); +} + +/* lseek SEEK_END / SEEK_SET 定位(针对未改写的只读文件) */ +static void lseek_positions(void) +{ + int fd = open(M_FILE, O_RDONLY); + CHECK(fd >= 0, "open RDONLY ok"); + if (fd < 0) + return; + CHECK(lseek(fd, 0, SEEK_END) == 10, "SEEK_END == file size 10"); + CHECK(lseek(fd, 4, SEEK_SET) == 4, "SEEK_SET to 4"); + char c = 0; + CHECK(read(fd, &c, 1) == 1 && c == '4', "read at SEEK_SET 4 gives '4'"); + close(fd); +} + +int open_pread_pwrite_run(void) +{ + printf("\n----- open_pread_pwrite -----\n"); + mod_setup(); + initial_offset_and_read_advance(); + pread_no_offset_change(); + pwrite_no_offset_change(); + lseek_positions(); + cleanup_tree(M_DIR); + printf(" ----- open_pread_pwrite: %d pass, %d fail -----\n", __pass, __fail); + return __fail; +}