Skip to content

Commit

Permalink
fix:命名管道读行为不符合posix规范问题 (DragonOS-Community#1066)
Browse files Browse the repository at this point in the history
fix(pipe): 增强FIFO管道的读写逻辑,解决问题一:非阻塞模式下的无写端错误返回
feat(test_fifo_write): 增强FIFO写入测试,添加信号处理、非阻塞模式支持和多场景测试

---------
Signed-off-by: xiaolin2004 <[email protected]>
Co-authored-by: xiaolin2004 <[email protected]>
  • Loading branch information
fslongjin and xiaolin2004 authored Nov 28, 2024
1 parent df3bf6d commit ffa8e88
Show file tree
Hide file tree
Showing 4 changed files with 328 additions and 9 deletions.
66 changes: 57 additions & 9 deletions kernel/src/ipc/pipe.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use core::sync::atomic::compiler_fence;

use crate::{
arch::ipc::signal::{SigCode, Signal},
filesystem::vfs::{
core::generate_inode_id, file::FileMode, syscall::ModeType, FilePrivateData, FileSystem,
FileType, IndexNode, Metadata,
Expand All @@ -8,7 +11,7 @@ use crate::{
wait_queue::WaitQueue,
},
net::event_poll::{EPollEventType, EPollItem, EventPoll},
process::ProcessState,
process::{ProcessManager, ProcessState},
sched::SchedMode,
time::PosixTimeSpec,
};
Expand All @@ -20,6 +23,8 @@ use alloc::{
};
use system_error::SystemError;

use super::signal_types::{SigInfo, SigType};

/// 我们设定pipe_buff的总大小为1024字节
const PIPE_BUFF_SIZE: usize = 1024;

Expand Down Expand Up @@ -59,6 +64,7 @@ pub struct InnerPipeInode {
metadata: Metadata,
reader: u32,
writer: u32,
had_reader: bool,
epitems: SpinLock<LinkedList<Arc<EPollItem>>>,
}

Expand Down Expand Up @@ -131,6 +137,7 @@ impl LockedPipeInode {
valid_cnt: 0,
read_pos: 0,
write_pos: 0,
had_reader: false,
data: [0; PIPE_BUFF_SIZE],

metadata: Metadata {
Expand Down Expand Up @@ -278,15 +285,27 @@ impl IndexNode for LockedPipeInode {
mut data: SpinLockGuard<FilePrivateData>,
mode: &crate::filesystem::vfs::file::FileMode,
) -> Result<(), SystemError> {
let accmode = mode.accmode();
let mut guard = self.inner.lock();
// 不能以读写方式打开管道
if mode.contains(FileMode::O_RDWR) {
if accmode == FileMode::O_RDWR.bits() {
return Err(SystemError::EACCES);
}
if mode.contains(FileMode::O_RDONLY) {
} else if accmode == FileMode::O_RDONLY.bits() {
guard.reader += 1;
}
if mode.contains(FileMode::O_WRONLY) {
guard.had_reader = true;
// println!(
// "FIFO: pipe try open in read mode with reader pid:{:?}",
// ProcessManager::current_pid()
// );
} else if accmode == FileMode::O_WRONLY.bits() {
// println!(
// "FIFO: pipe try open in write mode with {} reader, writer pid:{:?}",
// guard.reader,
// ProcessManager::current_pid()
// );
if guard.reader == 0 && mode.contains(FileMode::O_NONBLOCK) {
return Err(SystemError::ENXIO);
}
guard.writer += 1;
}

Expand All @@ -311,10 +330,11 @@ impl IndexNode for LockedPipeInode {
} else {
return Err(SystemError::EBADF);
}
let accmode = mode.accmode();
let mut guard = self.inner.lock();

// 写端关闭
if mode.contains(FileMode::O_WRONLY) {
if accmode == FileMode::O_WRONLY.bits() {
assert!(guard.writer > 0);
guard.writer -= 1;
// 如果已经没有写端了,则唤醒读端
Expand All @@ -325,7 +345,7 @@ impl IndexNode for LockedPipeInode {
}

// 读端关闭
if mode.contains(FileMode::O_RDONLY) {
if accmode == FileMode::O_RDONLY.bits() {
assert!(guard.reader > 0);
guard.reader -= 1;
// 如果已经没有写端了,则唤醒读端
Expand Down Expand Up @@ -361,7 +381,35 @@ impl IndexNode for LockedPipeInode {
let mut inode = self.inner.lock();

if inode.reader == 0 {
// TODO: 如果已经没有读端存在了,则向写端进程发送SIGPIPE信号
if !inode.had_reader {
// 如果从未有读端,直接返回 ENXIO,无论是否阻塞模式
return Err(SystemError::ENXIO);
} else {
// 如果曾经有读端,现在已关闭
match mode.contains(FileMode::O_NONBLOCK) {
true => {
// 非阻塞模式,直接返回 EPIPE
return Err(SystemError::EPIPE);
}
false => {
let sig = Signal::SIGPIPE;
let mut info = SigInfo::new(
sig,
0,
SigCode::Kernel,
SigType::Kill(ProcessManager::current_pid()),
);
compiler_fence(core::sync::atomic::Ordering::SeqCst);

let _retval = sig
.send_signal_info(Some(&mut info), ProcessManager::current_pid())
.map(|x| x as usize);

compiler_fence(core::sync::atomic::Ordering::SeqCst);
return Err(SystemError::EPIPE);
}
}
}
}

// 如果管道空间不够
Expand Down
20 changes: 20 additions & 0 deletions user/apps/test_fifo_write/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
ifeq ($(ARCH), x86_64)
CROSS_COMPILE=x86_64-linux-musl-
else ifeq ($(ARCH), riscv64)
CROSS_COMPILE=riscv64-linux-musl-
endif

CC=$(CROSS_COMPILE)gcc

.PHONY: all
all: main.c
$(CC) -static -o test_fifo_write main.c

.PHONY: install clean
install: all
mv test_fifo_write $(DADK_CURRENT_BUILD_DIR)/test_fifo_write

clean:
rm test_fifo_write *.o

fmt:
210 changes: 210 additions & 0 deletions user/apps/test_fifo_write/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>

#define TEST_ASSERT(left, right, success_msg, fail_msg) \
do { \
if ((left) == (right)) { \
printf("[PASS] %s\n", success_msg); \
} else { \
printf("[FAIL] %s: Expected %d, but got %d\n", fail_msg, (right), \
(left)); \
} \
} while (0)

#define FIFO_PATH "/bin/test_fifo" // 使用 /tmp 目录避免权限问题

typedef struct {
int fd;
int error_code;
} FifoWriteResult;

// 信号处理函数
void sigpipe_handler(int signo) {
if (signo == SIGPIPE) {
printf("Received SIGPIPE signal. Write operation failed.\n");
}
}

const char *scenarios[] = {"No readers (FIFO never had readers)",
"Reader exists but disconnects",
"Active reader exists"};

FifoWriteResult test_fifo_write(int scenario_index, int nonblocking) {
FifoWriteResult result = {.fd = -1, .error_code = 0};
int fd;
const char *data = "Hello, FIFO!";

// Set write mode and non-blocking flag
int flags = O_WRONLY;
if (nonblocking) {
flags |= O_NONBLOCK;
}

// Open the FIFO write end
fd = open(FIFO_PATH, flags);
if (fd == -1) {
result.fd = fd;
result.error_code = errno;

if (errno == ENXIO) {
printf("Result: Failed to open FIFO for writing (ENXIO: No readers).\n");
} else {
perror("Failed to open FIFO for writing");
}
return result; // Return early with error details
}

// Write data
ssize_t bytes_written = write(fd, data, strlen(data));
if (bytes_written == -1) {
result.error_code = errno;

if (bytes_written == -1) {
if (errno == EPIPE) {
printf("Result: Write failed with EPIPE (no readers available).\n");
} else if (errno == ENXIO) {
printf("Result: Write failed with ENXIO (FIFO never had readers).\n");
} else if (errno == EAGAIN) {
printf("Result: Write failed with EAGAIN (nonblocking write, pipe full "
"or no readers).\n");
} else {
perror("Write failed with an unexpected error");
}
} else {
printf("Result: Write succeeded. Bytes written: %zd\n", bytes_written);
}

result.fd = fd;
close(fd);
return result; // Return with fd and error_code
}
}

void test_case1(int nonblocking) {
// Case 1: No readers (FIFO never had readers)
FifoWriteResult result = test_fifo_write(0, nonblocking);

char buffer[100];
sprintf(buffer, "Fail with unexpected error %d", result.error_code);
TEST_ASSERT(result.error_code, ENXIO, "write(2) fails with the error ENXIO",
buffer);
}

void test_case2(int nonblocking) {
pid_t reader_pid;

// Case 2: Reader exists but disconnects
reader_pid = fork();
if (reader_pid == 0) {
// Child process acts as a reader
int reader_fd = open(FIFO_PATH, O_RDONLY);
if (reader_fd == -1) {
perror("Reader failed to open FIFO");
exit(EXIT_FAILURE);
}
sleep(2); // Simulate a brief existence of the reader
close(reader_fd);
exit(EXIT_SUCCESS);
}

sleep(5); // Ensure the reader has opened the FIFO
FifoWriteResult result = test_fifo_write(1, nonblocking);
waitpid(reader_pid, NULL, 0); // Wait for the reader process to exit

if (nonblocking) {
TEST_ASSERT(result.error_code, EPIPE,
"Non-Blocking Write failed with EPIPE",
"Non-Blocking Write failed with wrong error type");
} else {
TEST_ASSERT(result.error_code, EPIPE, "Blocking Write failed with EPIPE",
"Blocking Write failed with wrong error type");
}
}

void test_case3(int nonblocking) {
pid_t reader_pid;

// Case 3: Active reader exists
reader_pid = fork();
if (reader_pid == 0) {
// Child process acts as a reader
int reader_fd = open(FIFO_PATH, O_RDONLY);
if (reader_fd == -1) {
perror("Reader failed to open FIFO");
exit(EXIT_FAILURE);
}
sleep(5); // Keep the reader active
close(reader_fd);
exit(EXIT_SUCCESS);
}

sleep(1); // Ensure the reader has opened the FIFO
FifoWriteResult result = test_fifo_write(2, nonblocking);

waitpid(reader_pid, NULL, 0); // Wait for the reader process to exit

TEST_ASSERT(result.error_code, 0, "write succeed", "write failed");
}

void run_tests(int nonblocking) {
for (int i = 0; i < 3; i++) {
printf("\n--- Testing: %s (nonblocking=%d) ---\n", scenarios[i],
nonblocking);
switch (i) {
case 0:
// test_case1(nonblocking);
break;
case 1:
test_case2(nonblocking);
break;
case 2:
// test_case3(nonblocking);
break;
}
}
}

void test_blocking() {
// 创建 FIFO
if (mkfifo(FIFO_PATH, 0666) == -1 && errno != EEXIST) {
perror("mkfifo failed");
exit(EXIT_FAILURE);
}

// 测试阻塞模式下的三种情况
printf("========== Testing Blocking Mode ==========\n");
run_tests(0); // 阻塞模式
// 删除 FIFO
unlink(FIFO_PATH);
}

void test_non_blocking() {
// 创建 FIFO
if (mkfifo(FIFO_PATH, 0666) == -1 && errno != EEXIST) {
perror("mkfifo failed");
exit(EXIT_FAILURE);
}
// 测试非阻塞模式下的三种情况
printf("\n========== Testing Nonblocking Mode ==========\n");
run_tests(1); // 非阻塞模式
// 删除 FIFO
unlink(FIFO_PATH);
}

int main() {
// 设置 SIGPIPE 信号处理
signal(SIGPIPE, sigpipe_handler);

// test_blocking();
test_non_blocking();

printf("\nAll tests completed.\n");
return 0;
}
Loading

0 comments on commit ffa8e88

Please sign in to comment.