From 883f7b259fd36b55e2215e55cee3bd695d9be58d Mon Sep 17 00:00:00 2001 From: "Hui, Chunyang" Date: Mon, 24 May 2021 03:38:19 +0000 Subject: [PATCH] Add support for ioctl FIONREAD, FIOCLEX, FIONCLEX for pipe and uds file --- src/libos/src/fs/channel.rs | 6 ++ src/libos/src/fs/file_ops/ioctl/mod.rs | 21 +++++- src/libos/src/fs/pipe.rs | 34 ++++++--- src/libos/src/net/socket/unix/stream/file.rs | 7 +- test/ioctl/main.c | 79 +++++++++++++++++--- test/naughty_child/main.c | 35 ++++++++- test/pipe/main.c | 57 ++++++++++++++ test/unix_socket/main.c | 50 +++++++++++++ 8 files changed, 265 insertions(+), 24 deletions(-) diff --git a/src/libos/src/fs/channel.rs b/src/libos/src/fs/channel.rs index 1db402ee..53d10bc0 100644 --- a/src/libos/src/fs/channel.rs +++ b/src/libos/src/fs/channel.rs @@ -422,6 +422,12 @@ impl Consumer { let rb_consumer = self.inner.lock().unwrap(); rb_consumer.capacity() } + + // Get the length of data stored in the buffer + pub fn ready_len(&self) -> usize { + let rb_consumer = self.inner.lock().unwrap(); + rb_consumer.len() + } } impl Consumer { diff --git a/src/libos/src/fs/file_ops/ioctl/mod.rs b/src/libos/src/fs/file_ops/ioctl/mod.rs index 5457815c..aef55253 100644 --- a/src/libos/src/fs/file_ops/ioctl/mod.rs +++ b/src/libos/src/fs/file_ops/ioctl/mod.rs @@ -42,6 +42,10 @@ impl_ioctl_nums_and_cmds! { TIOCNOTTY => (0x5422, ()), // Get the number of bytes in the input buffer FIONREAD => (0x541B, mut i32), + // Don't close on exec + FIONCLEX => (0x5450, ()), + // Set close on exec + FIOCLEX => (0x5451, ()), // Low-level access to Linux network devices on man7/netdevice.7 // Only non-privileged operations are supported for now SIOCGIFNAME => (0x8910, mut IfReq), @@ -94,8 +98,21 @@ impl<'a> IoctlCmd<'a> { pub fn do_ioctl(fd: FileDesc, cmd: &mut IoctlCmd) -> Result { debug!("ioctl: fd: {}, cmd: {:?}", fd, cmd); - let file_ref = current!().file(fd)?; - file_ref.ioctl(cmd) + let current = current!(); + let file_ref = current.file(fd)?; + let mut file_table = current.files().lock().unwrap(); + let mut entry = file_table.get_entry_mut(fd)?; + match cmd { + IoctlCmd::FIONCLEX(_) => { + entry.set_close_on_spawn(false); + return Ok(0); + } + IoctlCmd::FIOCLEX(_) => { + entry.set_close_on_spawn(true); + return Ok(0); + } + _ => return file_ref.ioctl(cmd), + } } extern "C" { diff --git a/src/libos/src/fs/pipe.rs b/src/libos/src/fs/pipe.rs index f1950f04..4e6e312f 100644 --- a/src/libos/src/fs/pipe.rs +++ b/src/libos/src/fs/pipe.rs @@ -108,7 +108,23 @@ impl File for PipeReader { } fn ioctl(&self, cmd: &mut IoctlCmd) -> Result { - ioctl_inner(cmd) + match cmd { + IoctlCmd::TCGETS(_) => return_errno!(ENOTTY, "not tty device"), + IoctlCmd::TCSETS(_) => return_errno!(ENOTTY, "not tty device"), + IoctlCmd::FIONREAD(arg) => { + let ready_len = self.get_ready_len().min(std::i32::MAX as usize) as i32; + **arg = ready_len; + return Ok(0); + } + _ => return_errno!(ENOSYS, "not supported"), + }; + unreachable!(); + } +} + +impl PipeReader { + fn get_ready_len(&self) -> usize { + self.consumer.ready_len() } } @@ -194,7 +210,12 @@ impl File for PipeWriter { } fn ioctl(&self, cmd: &mut IoctlCmd) -> Result { - ioctl_inner(cmd) + match cmd { + IoctlCmd::TCGETS(_) => return_errno!(ENOTTY, "not tty device"), + IoctlCmd::TCSETS(_) => return_errno!(ENOTTY, "not tty device"), + _ => return_errno!(ENOSYS, "not supported"), + }; + unreachable!(); } } @@ -248,12 +269,3 @@ impl PipeType for FileRef { .ok_or_else(|| errno!(EBADF, "not a pipe writer")) } } - -fn ioctl_inner(cmd: &mut IoctlCmd) -> Result { - match cmd { - IoctlCmd::TCGETS(_) => return_errno!(ENOTTY, "not tty device"), - IoctlCmd::TCSETS(_) => return_errno!(ENOTTY, "not tty device"), - _ => return_errno!(ENOSYS, "not supported"), - }; - unreachable!(); -} diff --git a/src/libos/src/net/socket/unix/stream/file.rs b/src/libos/src/net/socket/unix/stream/file.rs index 20722554..7e7c644e 100644 --- a/src/libos/src/net/socket/unix/stream/file.rs +++ b/src/libos/src/net/socket/unix/stream/file.rs @@ -55,16 +55,21 @@ impl File for Stream { fn ioctl(&self, cmd: &mut IoctlCmd) -> Result { match cmd { + IoctlCmd::TCGETS(_) => return_errno!(ENOTTY, "not tty device"), + IoctlCmd::TCSETS(_) => return_errno!(ENOTTY, "not tty device"), + IoctlCmd::FIONBIO(nonblocking) => { + self.set_nonblocking(**nonblocking != 0); + } IoctlCmd::FIONREAD(arg) => match &*self.inner() { Status::Connected(endpoint) => { let bytes_to_read = endpoint.bytes_to_read().min(std::i32::MAX as usize) as i32; **arg = bytes_to_read; - Ok(0) } _ => return_errno!(ENOTCONN, "unconnected socket"), }, _ => return_errno!(EINVAL, "unknown ioctl cmd for unix socket"), } + Ok(0) } fn access_mode(&self) -> Result { diff --git a/test/ioctl/main.c b/test/ioctl/main.c index 68b16023..0d1cb3cb 100644 --- a/test/ioctl/main.c +++ b/test/ioctl/main.c @@ -5,11 +5,14 @@ #include #include #include +#define _GNU_SOURCE #include #include #include #include #include +#include +#include #include #include #ifndef OCCLUM_DISABLE_DCAP @@ -487,21 +490,78 @@ int test_ioctl_SIOCGIFCONF(void) { } int test_ioctl_FIONBIO(void) { - int sock = socket(AF_INET, SOCK_STREAM, 0); + int test_sock[2], sock; + test_sock[0] = socket(AF_INET, SOCK_STREAM, 0); + test_sock[1] = socket(AF_UNIX, SOCK_STREAM, 0); + + for (int i = 0; i < 2; i++) { + sock = test_sock[i]; + int on = 1; + if (ioctl(sock, FIONBIO, &on) < 0) { + close(sock); + THROW_ERROR("ioctl FIONBIO failed"); + } + + int actual_flags = fcntl(sock, F_GETFL); + if ((actual_flags & O_NONBLOCK) == 0) { + close(sock); + THROW_ERROR("failed to check the O_NONBLOCK flag after FIONBIO"); + } - int on = 1; - if (ioctl(sock, FIONBIO, &on) < 0) { close(sock); - THROW_ERROR("ioctl FIONBIO failed"); + } + return 0; +} + +int test_ioctl_FIOCLEX(void) { + // Open a file with O_CLOEXEC (close-on-exec) + char *tmp_file = "/tmp/test_fioclex"; + int fd = open(tmp_file, O_CREAT | O_CLOEXEC, 0666); + if (fd < 0) { + THROW_ERROR("failed to open the tmp file"); } - int actual_flags = fcntl(sock, F_GETFL); - if ((actual_flags & O_NONBLOCK) == 0) { - close(sock); - THROW_ERROR("failed to check the O_NONBLOCK flag after FIONBIO"); + // change this fd to "no close-on-exec" + int ret = ioctl(fd, FIONCLEX, NULL); + if (ret != 0) { + THROW_ERROR("ioctl FIONCLEX failed"); } - close(sock); + int pipefds[2]; + ret = pipe(pipefds); + if (ret != 0) { + THROW_ERROR("failed to create pipe"); + } + + // set close on exec on reader end + ret = ioctl(pipefds[0], FIOCLEX, NULL); + if (ret != 0) { + THROW_ERROR("ioctl FIOCLEX failed"); + } + + // construct child process args + int child_pid, status; + int child_argc = + 6; // ./nauty_child -t fioclex regular_file_fd pipe_reader_fd pipe_writer_fd + char **child_argv = calloc(1, sizeof(char *) * (child_argc + 1)); + child_argv[0] = strdup("naughty_child"); + child_argv[1] = strdup("-t"); + child_argv[2] = strdup("fioclex"); + asprintf(&child_argv[3], "%d", fd); + asprintf(&child_argv[4], "%d", pipefds[0]); + asprintf(&child_argv[5], "%d", pipefds[1]); + + ret = posix_spawn(&child_pid, "/bin/naughty_child", NULL, NULL, child_argv, NULL); + if (ret != 0) { + THROW_ERROR("failed to spawn a child process\n"); + } + + ret = waitpid(child_pid, &status, 0); + if (ret < 0) { + THROW_ERROR("failed to wait4 the child process"); + } + printf("child process %d exit status = %d\n", child_pid, status); + return 0; } @@ -522,6 +582,7 @@ static test_case_t test_cases[] = { #endif TEST_CASE(test_ioctl_SIOCGIFCONF), TEST_CASE(test_ioctl_FIONBIO), + TEST_CASE(test_ioctl_FIOCLEX), }; int main() { diff --git a/test/naughty_child/main.c b/test/naughty_child/main.c index a5b28f2d..f5de6723 100644 --- a/test/naughty_child/main.c +++ b/test/naughty_child/main.c @@ -4,8 +4,11 @@ #include #include #include +#include #include "test.h" +char **g_argv; + void sigio_handler(int sig) { printf("[child] SIGIO is caught in child!\n"); } @@ -70,6 +73,33 @@ int test_spawn_attribute_sigdef() { return 0; } +int test_ioctl_fioclex() { + int regular_file_fd = atoi(g_argv[3]); + int pipe_reader_fd = atoi(g_argv[4]); + int pipe_writer_fd = atoi(g_argv[5]); + + // regular file is set with ioctl FIONCLEX + struct stat stat_buf; + int ret = fstat(regular_file_fd, &stat_buf); + if (ret != 0 || !S_ISREG(stat_buf.st_mode)) { + THROW_ERROR("fstat regular file fd error"); + } + + // pipe reader is set with ioctl FIOCLEX + ret = fstat(pipe_reader_fd, &stat_buf); + if (ret != -1 || errno != EBADF) { + THROW_ERROR("fstat pipe reader fd error"); + } + + // pipe writer is set with default and should inherit by child + ret = fstat(pipe_writer_fd, &stat_buf); + if (ret != 0 || !S_ISFIFO(stat_buf.st_mode)) { + THROW_ERROR("fstat pipe writer fd error"); + } + + return 0; +} + // ============================================================================ // Test suite // ============================================================================ @@ -81,6 +111,8 @@ int start_test(const char *test_name) { return test_spawn_attribute_sigmask(); } else if (strcmp(test_name, "sigdef") == 0) { return test_spawn_attribute_sigdef(); + } else if (strcmp(test_name, "fioclex") == 0) { + return test_ioctl_fioclex(); } else { fprintf(stderr, "[child] test case not found\n"); return -1; @@ -89,7 +121,7 @@ int start_test(const char *test_name) { void print_usage() { fprintf(stderr, "Usage:\n nauty_child [-t testcase1] [-t testcase2] ...\n\n"); - fprintf(stderr, " Now support testcase: \n"); + fprintf(stderr, " Now support testcase: \n"); } int main(int argc, char *argv[]) { @@ -98,6 +130,7 @@ int main(int argc, char *argv[]) { return 0; } + g_argv = argv; int opt; char *testcase_name = calloc(1, TEST_NAME_MAX); while ((opt = getopt(argc, argv, "t:")) != -1) { diff --git a/test/pipe/main.c b/test/pipe/main.c index 8711c9ed..38412153 100644 --- a/test/pipe/main.c +++ b/test/pipe/main.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -329,6 +330,61 @@ int test_select_read_write() { return 0; } +int test_ioctl_fionread() { + int pipe_fds[2]; + if (pipe(pipe_fds) < 0) { + THROW_ERROR("failed to create a pipe"); + } + + int pipe_rd_fd = pipe_fds[0]; + int pipe_wr_fd = pipe_fds[1]; + + posix_spawn_file_actions_t file_actions; + posix_spawn_file_actions_init(&file_actions); + posix_spawn_file_actions_adddup2(&file_actions, pipe_wr_fd, STDOUT_FILENO); + posix_spawn_file_actions_addclose(&file_actions, pipe_rd_fd); + + const char *msg = "Echo!\n"; + const char *child_prog = "/bin/hello_world"; + const char *child_argv[3] = { child_prog, msg, NULL }; + int child_pid; + if (posix_spawn(&child_pid, child_prog, &file_actions, + NULL, (char *const *)child_argv, NULL) < 0) { + THROW_ERROR("failed to spawn a child process"); + } + int status = 0; + if (wait4(child_pid, &status, 0, NULL) < 0) { + THROW_ERROR("failed to wait4 the child process"); + } + + close(pipe_wr_fd); + + const char *expected_str = msg; + size_t expected_len = strlen(expected_str); + char actual_str[32] = {0}; + + int data_len_ready = 0; + if ( ioctl(pipe_rd_fd, FIONREAD, &data_len_ready) < 0 ) { + THROW_ERROR("ioctl FIONREAD failed"); + } + + // data_len_ready will include '\0' + if (data_len_ready - 1 != expected_len) { + THROW_ERROR("ioctl FIONREAD value not match"); + } + + if (read(pipe_rd_fd, actual_str, sizeof(actual_str) - 1) < 0) { + THROW_ERROR("reading pipe failed"); + }; + + if (strncmp(expected_str, actual_str, expected_len) != 0) { + THROW_ERROR("received string is not as expected"); + } + + close(pipe_rd_fd); + return 0; +} + // ============================================================================ // Test suite // ============================================================================ @@ -344,6 +400,7 @@ static test_case_t test_cases[] = { TEST_CASE(test_poll_no_timeout), TEST_CASE(test_epoll_no_timeout), TEST_CASE(test_select_read_write), + TEST_CASE(test_ioctl_fionread), }; int main(int argc, const char *argv[]) { diff --git a/test/unix_socket/main.c b/test/unix_socket/main.c index ebd6bb83..3ca99f9a 100644 --- a/test/unix_socket/main.c +++ b/test/unix_socket/main.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -245,12 +246,61 @@ int test_getname() { return 0; } +int test_ioctl_fionread() { + int ret = 0; + int sockets[2]; + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); + if (ret < 0) { + THROW_ERROR("failed to create a unix socket"); + } + + const char *child_prog = "/bin/hello_world"; + const char *child_argv[3] = { child_prog, ECHO_MSG, NULL }; + int child_pid; + posix_spawn_file_actions_t file_actions; + + posix_spawn_file_actions_init(&file_actions); + posix_spawn_file_actions_adddup2(&file_actions, sockets[0], STDOUT_FILENO); + posix_spawn_file_actions_addclose(&file_actions, sockets[1]); + + if (posix_spawn(&child_pid, child_prog, &file_actions, + NULL, (char *const *)child_argv, NULL) < 0) { + THROW_ERROR("failed to spawn a child process"); + } + + int status = 0; + if (wait4(child_pid, &status, 0, NULL) < 0) { + THROW_ERROR("failed to wait4 the child process"); + } + + // data should be ready + int data_len_ready = 0; + if (ioctl(sockets[1], FIONREAD, &data_len_ready) < 0) { + THROW_ERROR("failed to ioctl with FIONREAD option"); + } + + // data_len_ready will include '\0' + if (data_len_ready - 1 != strlen(ECHO_MSG)) { + THROW_ERROR("ioctl FIONREAD value not match"); + } + + char actual_str[32] = {0}; + read(sockets[1], actual_str, 32); + if (strncmp(actual_str, ECHO_MSG, sizeof(ECHO_MSG) - 1) != 0) { + printf("data read is :%s\n", actual_str); + THROW_ERROR("received string is not as expected"); + } + + return 0; +} + static test_case_t test_cases[] = { TEST_CASE(test_unix_socket_inter_process), TEST_CASE(test_socketpair_inter_process), TEST_CASE(test_multiple_socketpairs), TEST_CASE(test_poll), TEST_CASE(test_getname), + TEST_CASE(test_ioctl_fionread), }; int main(int argc, const char *argv[]) {