Add support for ioctl FIONREAD, FIOCLEX, FIONCLEX for pipe and uds file

This commit is contained in:
Hui, Chunyang 2021-05-24 03:38:19 +00:00 committed by Zongmin.Gu
parent 7542a74ebb
commit 883f7b259f
8 changed files with 265 additions and 24 deletions

@ -422,6 +422,12 @@ impl<I> Consumer<I> {
let rb_consumer = self.inner.lock().unwrap(); let rb_consumer = self.inner.lock().unwrap();
rb_consumer.capacity() 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<I: Copy> Consumer<I> { impl<I: Copy> Consumer<I> {

@ -42,6 +42,10 @@ impl_ioctl_nums_and_cmds! {
TIOCNOTTY => (0x5422, ()), TIOCNOTTY => (0x5422, ()),
// Get the number of bytes in the input buffer // Get the number of bytes in the input buffer
FIONREAD => (0x541B, mut i32), 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 // Low-level access to Linux network devices on man7/netdevice.7
// Only non-privileged operations are supported for now // Only non-privileged operations are supported for now
SIOCGIFNAME => (0x8910, mut IfReq), SIOCGIFNAME => (0x8910, mut IfReq),
@ -94,8 +98,21 @@ impl<'a> IoctlCmd<'a> {
pub fn do_ioctl(fd: FileDesc, cmd: &mut IoctlCmd) -> Result<i32> { pub fn do_ioctl(fd: FileDesc, cmd: &mut IoctlCmd) -> Result<i32> {
debug!("ioctl: fd: {}, cmd: {:?}", fd, cmd); debug!("ioctl: fd: {}, cmd: {:?}", fd, cmd);
let file_ref = current!().file(fd)?; let current = current!();
file_ref.ioctl(cmd) 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" { extern "C" {

@ -108,7 +108,23 @@ impl File for PipeReader {
} }
fn ioctl(&self, cmd: &mut IoctlCmd) -> Result<i32> { fn ioctl(&self, cmd: &mut IoctlCmd) -> Result<i32> {
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<i32> { fn ioctl(&self, cmd: &mut IoctlCmd) -> Result<i32> {
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")) .ok_or_else(|| errno!(EBADF, "not a pipe writer"))
} }
} }
fn ioctl_inner(cmd: &mut IoctlCmd) -> Result<i32> {
match cmd {
IoctlCmd::TCGETS(_) => return_errno!(ENOTTY, "not tty device"),
IoctlCmd::TCSETS(_) => return_errno!(ENOTTY, "not tty device"),
_ => return_errno!(ENOSYS, "not supported"),
};
unreachable!();
}

@ -55,16 +55,21 @@ impl File for Stream {
fn ioctl(&self, cmd: &mut IoctlCmd) -> Result<i32> { fn ioctl(&self, cmd: &mut IoctlCmd) -> Result<i32> {
match cmd { 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() { IoctlCmd::FIONREAD(arg) => match &*self.inner() {
Status::Connected(endpoint) => { Status::Connected(endpoint) => {
let bytes_to_read = endpoint.bytes_to_read().min(std::i32::MAX as usize) as i32; let bytes_to_read = endpoint.bytes_to_read().min(std::i32::MAX as usize) as i32;
**arg = bytes_to_read; **arg = bytes_to_read;
Ok(0)
} }
_ => return_errno!(ENOTCONN, "unconnected socket"), _ => return_errno!(ENOTCONN, "unconnected socket"),
}, },
_ => return_errno!(EINVAL, "unknown ioctl cmd for unix socket"), _ => return_errno!(EINVAL, "unknown ioctl cmd for unix socket"),
} }
Ok(0)
} }
fn access_mode(&self) -> Result<AccessMode> { fn access_mode(&self) -> Result<AccessMode> {

@ -5,11 +5,14 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#define _GNU_SOURCE
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <termios.h> #include <termios.h>
#include <unistd.h> #include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
#include <sgx_report.h> #include <sgx_report.h>
#include <sgx_quote.h> #include <sgx_quote.h>
#ifndef OCCLUM_DISABLE_DCAP #ifndef OCCLUM_DISABLE_DCAP
@ -487,21 +490,78 @@ int test_ioctl_SIOCGIFCONF(void) {
} }
int test_ioctl_FIONBIO(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); 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); // change this fd to "no close-on-exec"
if ((actual_flags & O_NONBLOCK) == 0) { int ret = ioctl(fd, FIONCLEX, NULL);
close(sock); if (ret != 0) {
THROW_ERROR("failed to check the O_NONBLOCK flag after FIONBIO"); 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; return 0;
} }
@ -522,6 +582,7 @@ static test_case_t test_cases[] = {
#endif #endif
TEST_CASE(test_ioctl_SIOCGIFCONF), TEST_CASE(test_ioctl_SIOCGIFCONF),
TEST_CASE(test_ioctl_FIONBIO), TEST_CASE(test_ioctl_FIONBIO),
TEST_CASE(test_ioctl_FIOCLEX),
}; };
int main() { int main() {

@ -4,8 +4,11 @@
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <features.h> #include <features.h>
#include <sys/stat.h>
#include "test.h" #include "test.h"
char **g_argv;
void sigio_handler(int sig) { void sigio_handler(int sig) {
printf("[child] SIGIO is caught in child!\n"); printf("[child] SIGIO is caught in child!\n");
} }
@ -70,6 +73,33 @@ int test_spawn_attribute_sigdef() {
return 0; 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 // Test suite
// ============================================================================ // ============================================================================
@ -81,6 +111,8 @@ int start_test(const char *test_name) {
return test_spawn_attribute_sigmask(); return test_spawn_attribute_sigmask();
} else if (strcmp(test_name, "sigdef") == 0) { } else if (strcmp(test_name, "sigdef") == 0) {
return test_spawn_attribute_sigdef(); return test_spawn_attribute_sigdef();
} else if (strcmp(test_name, "fioclex") == 0) {
return test_ioctl_fioclex();
} else { } else {
fprintf(stderr, "[child] test case not found\n"); fprintf(stderr, "[child] test case not found\n");
return -1; return -1;
@ -89,7 +121,7 @@ int start_test(const char *test_name) {
void print_usage() { void print_usage() {
fprintf(stderr, "Usage:\n nauty_child [-t testcase1] [-t testcase2] ...\n\n"); fprintf(stderr, "Usage:\n nauty_child [-t testcase1] [-t testcase2] ...\n\n");
fprintf(stderr, " Now support testcase: <sigmask, sigdef>\n"); fprintf(stderr, " Now support testcase: <sigmask, sigdef, fioclex>\n");
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
@ -98,6 +130,7 @@ int main(int argc, char *argv[]) {
return 0; return 0;
} }
g_argv = argv;
int opt; int opt;
char *testcase_name = calloc(1, TEST_NAME_MAX); char *testcase_name = calloc(1, TEST_NAME_MAX);
while ((opt = getopt(argc, argv, "t:")) != -1) { while ((opt = getopt(argc, argv, "t:")) != -1) {

@ -6,6 +6,7 @@
#include <sys/wait.h> #include <sys/wait.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h> #include <fcntl.h>
#include <poll.h> #include <poll.h>
#include <unistd.h> #include <unistd.h>
@ -329,6 +330,61 @@ int test_select_read_write() {
return 0; 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 // Test suite
// ============================================================================ // ============================================================================
@ -344,6 +400,7 @@ static test_case_t test_cases[] = {
TEST_CASE(test_poll_no_timeout), TEST_CASE(test_poll_no_timeout),
TEST_CASE(test_epoll_no_timeout), TEST_CASE(test_epoll_no_timeout),
TEST_CASE(test_select_read_write), TEST_CASE(test_select_read_write),
TEST_CASE(test_ioctl_fionread),
}; };
int main(int argc, const char *argv[]) { int main(int argc, const char *argv[]) {

@ -2,6 +2,7 @@
#include <sys/wait.h> #include <sys/wait.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/un.h> #include <sys/un.h>
#include <sys/ioctl.h>
#include <poll.h> #include <poll.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
@ -245,12 +246,61 @@ int test_getname() {
return 0; 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[] = { static test_case_t test_cases[] = {
TEST_CASE(test_unix_socket_inter_process), TEST_CASE(test_unix_socket_inter_process),
TEST_CASE(test_socketpair_inter_process), TEST_CASE(test_socketpair_inter_process),
TEST_CASE(test_multiple_socketpairs), TEST_CASE(test_multiple_socketpairs),
TEST_CASE(test_poll), TEST_CASE(test_poll),
TEST_CASE(test_getname), TEST_CASE(test_getname),
TEST_CASE(test_ioctl_fionread),
}; };
int main(int argc, const char *argv[]) { int main(int argc, const char *argv[]) {