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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/*
* test-open-family —— open / openat 系统调用族「地毯式全覆盖」测例汇总
*
* 设计:28 个模块文件按维度切分(15 阶段① 基础+矩阵 / 8 阶段② ERRNO / 4 阶段③ openat / 1 阶段④ stress);每个模块自管私有 /tmp/topen_<name>/ 目录,
* 设计:30 个模块文件按维度切分(16 阶段① 基础+矩阵含 stress / 8 阶段② ERRNO / 4 阶段③ openat / 2 阶段④ 补充:设备节点目标 + fd 偏移/定位 I/O);每个模块自管私有 /tmp/topen_<name>/ 目录,
* 自计 __pass/__fail,run() 末尾打印小结并 return __fail。main 按顺序跑、汇总。
*
* 模块 → 见 notes/02 §17 + notes/10 §1.2 矩阵设计表。
Expand Down Expand Up @@ -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 子树。 */
Expand All @@ -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 宏的累计)。
Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#define _GNU_SOURCE
#include "test_framework.h"
#include "helpers.h"

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

/* 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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#define _GNU_SOURCE
#include "test_framework.h"
#include "helpers.h"

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

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