diff --git a/litebox_runner_linux_userland/tests/run.rs b/litebox_runner_linux_userland/tests/run.rs index 50bb6c1d9..219d27da1 100644 --- a/litebox_runner_linux_userland/tests/run.rs +++ b/litebox_runner_linux_userland/tests/run.rs @@ -590,3 +590,45 @@ fn test_tun_and_runner_with_iperf3() { has_started.store(true, std::sync::atomic::Ordering::Relaxed); runner.run(); } + +#[cfg(target_arch = "x86_64")] +#[test] +fn test_tun_with_curl() { + use std::io::{Read, Write}; + use std::net::TcpListener; + + const RESPONSE_BODY: &str = "#!/bin/bash\necho 'Hello from litebox!'\n"; + + // Bind to an OS-assigned port on all interfaces. + let listener = TcpListener::bind("0.0.0.0:0").expect("Failed to bind HTTP server"); + let port = listener.local_addr().unwrap().port(); + + let server_thread = std::thread::spawn(move || { + let (mut stream, _) = listener.accept().expect("Failed to accept connection"); + let mut buf = [0u8; 4096]; + let n = stream.read(&mut buf).expect("Failed to read request"); + let request = String::from_utf8_lossy(&buf[..n]); + println!("Received HTTP request:\n{request}"); + + let response = format!( + "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", + RESPONSE_BODY.len(), + RESPONSE_BODY + ); + stream + .write_all(response.as_bytes()) + .expect("Failed to send response"); + }); + + let curl_path = run_which("curl"); + let url = format!("http://10.0.0.1:{port}/something"); + let output = Runner::new(Backend::Rewriter, &curl_path, "curl_rewriter") + .args(["-sS", &url]) + .tun_device_name("tun99") + .output(); + + server_thread.join().expect("Server thread panicked"); + + let output_str = String::from_utf8_lossy(&output); + assert!(output_str.contains(RESPONSE_BODY), "Unexpected curl output"); +} diff --git a/litebox_shim_linux/src/lib.rs b/litebox_shim_linux/src/lib.rs index cd2fa1420..570386bd6 100644 --- a/litebox_shim_linux/src/lib.rs +++ b/litebox_shim_linux/src/lib.rs @@ -341,6 +341,9 @@ fn default_fs( // Special override so that `GETFL` can return stdio-specific flags pub(crate) struct StdioStatusFlags(litebox::fs::OFlags); +/// Status flags for pipes +pub(crate) struct PipeStatusFlags(pub litebox::fs::OFlags); + impl syscalls::file::FilesState { fn initialize_stdio_in_shared_descriptors_table(&self, global: &GlobalState) { use litebox::fs::{Mode, OFlags}; diff --git a/litebox_shim_linux/src/syscalls/file.rs b/litebox_shim_linux/src/syscalls/file.rs index e90f5a464..41233915d 100644 --- a/litebox_shim_linux/src/syscalls/file.rs +++ b/litebox_shim_linux/src/syscalls/file.rs @@ -1012,109 +1012,93 @@ impl Task { .ok_or(Errno::EBADF)? .set_file_descriptor_flags(&self.global, &files, flags) .map(|()| 0), - FcntlArg::GETFL => match desc { - Descriptor::LiteBoxRawFd(raw_fd) => Ok(files - .run_on_raw_fd( - *raw_fd, - |fd| { - Ok(self - .global - .litebox - .descriptor_table() - .with_metadata(fd, |crate::StdioStatusFlags(flags)| { - *flags & OFlags::STATUS_FLAGS_MASK - }) - .unwrap_or(OFlags::empty())) - }, - |fd| { - Ok(self - .global - .litebox - .descriptor_table() - .with_metadata(fd, |crate::syscalls::net::SocketOFlags(flags)| { - *flags & OFlags::STATUS_FLAGS_MASK - }) - .unwrap_or(OFlags::empty())) - }, - |fd| { - let pipes = &self.global.pipes; - let flags = OFlags::from(pipes.get_flags(fd).map_err(Errno::from)?); - let dirn = match pipes.half_pipe_type(fd)? { - litebox::pipes::HalfPipeType::SenderHalf => OFlags::WRONLY, - litebox::pipes::HalfPipeType::ReceiverHalf => OFlags::RDONLY, - }; - Ok(dirn | flags) - }, - ) - .flatten()? - .bits()), - Descriptor::Eventfd { file, .. } => Ok(file.get_status().bits()), - Descriptor::Epoll { file, .. } => Ok(file.get_status().bits()), - Descriptor::Unix { file, .. } => Ok(file.get_status().bits()), - }, + FcntlArg::GETFL => { + macro_rules! getfl_from_metadata { + ($fd:expr, $MetaType:path) => { + Ok(self + .global + .litebox + .descriptor_table() + .with_metadata($fd, |$MetaType(flags)| { + *flags & OFlags::STATUS_FLAGS_MASK + }) + .unwrap_or(OFlags::empty())) + }; + } + match desc { + Descriptor::LiteBoxRawFd(raw_fd) => Ok(files + .run_on_raw_fd( + *raw_fd, + |fd| getfl_from_metadata!(fd, crate::StdioStatusFlags), + |fd| getfl_from_metadata!(fd, crate::syscalls::net::SocketOFlags), + |fd| getfl_from_metadata!(fd, crate::PipeStatusFlags), + ) + .flatten()? + .bits()), + Descriptor::Eventfd { file, .. } => Ok(file.get_status().bits()), + Descriptor::Epoll { file, .. } => Ok(file.get_status().bits()), + Descriptor::Unix { file, .. } => Ok(file.get_status().bits()), + } + } FcntlArg::SETFL(flags) => { let setfl_mask = OFlags::APPEND | OFlags::NONBLOCK | OFlags::NDELAY | OFlags::DIRECT | OFlags::NOATIME; + let flags = flags & setfl_mask; macro_rules! toggle_flags { ($t:ident) => { - let diff = $t.get_status() ^ flags; + let diff = ($t.get_status() & setfl_mask) ^ flags; if diff.intersects(OFlags::APPEND | OFlags::DIRECT | OFlags::NOATIME) { - todo!("unsupported flags"); + log_unsupported!("unsupported flags"); } $t.set_status(flags & setfl_mask, true); $t.set_status(flags.complement() & setfl_mask, false); }; } + macro_rules! setfl_in_metadata { + ($fd:expr, $MetaType:path, $no_metadata_msg:expr) => { + setfl_in_metadata!($fd, $MetaType, $no_metadata_msg, |diff: OFlags| { + if diff.intersects(OFlags::APPEND | OFlags::DIRECT | OFlags::NOATIME) { + log_unsupported!("unsupported flags"); + } + }) + }; + ($fd:expr, $MetaType:path, $no_metadata_msg:expr, $check_diff:expr) => { + self.global + .litebox + .descriptor_table_mut() + .with_metadata_mut($fd, |$MetaType(f)| { + let diff = (*f & setfl_mask) ^ flags; + $check_diff(diff); + f.toggle(diff); + }) + .map_err(|err| match err { + MetadataError::ClosedFd => Errno::EBADF, + MetadataError::NoSuchMetadata => $no_metadata_msg, + }) + }; + } match desc { Descriptor::LiteBoxRawFd(raw_fd) => files.run_on_raw_fd( *raw_fd, |fd| { - self.global - .litebox - .descriptor_table_mut() - .with_metadata_mut(fd, |crate::StdioStatusFlags(f)| { - let diff = *f ^ flags; - if diff.intersects( - OFlags::APPEND | OFlags::DIRECT | OFlags::NOATIME, - ) { - todo!("unsupported flags"); - } - f.toggle(diff); - }) - .map_err(|err| match err { - MetadataError::ClosedFd => Errno::EBADF, - MetadataError::NoSuchMetadata => { - unimplemented!("SETFL on non-stdio") - } - }) + setfl_in_metadata!( + fd, + crate::StdioStatusFlags, + unimplemented!("SETFL on non-stdio") + ) }, |fd| { - self.global - .litebox - .descriptor_table_mut() - .with_metadata_mut(fd, |crate::syscalls::net::SocketOFlags(f)| { - let diff = *f ^ flags; - if diff.intersects( - OFlags::APPEND | OFlags::DIRECT | OFlags::NOATIME, - ) { - todo!("unsupported flags"); - } - f.toggle(diff); - }) - .map_err(|err| match err { - MetadataError::ClosedFd => Errno::EBADF, - MetadataError::NoSuchMetadata => { - unreachable!("all sockets have SocketOFlags when created") - } - }) + setfl_in_metadata!( + fd, + crate::syscalls::net::SocketOFlags, + unreachable!("all sockets have SocketOFlags when created") + ) }, |fd| { - if flags.intersects(OFlags::NONBLOCK.complement()) { - todo!("unsupported flags for pipes") - } + // Update the actual pipe non-blocking behavior self.global .pipes .update_flags( @@ -1122,7 +1106,14 @@ impl Task { litebox::pipes::Flags::NON_BLOCKING, flags.intersects(OFlags::NONBLOCK), ) - .map_err(Errno::from) + .map_err(Errno::from)?; + // Record all status flags in metadata for F_GETFL + setfl_in_metadata!( + fd, + crate::PipeStatusFlags, + unreachable!("all pipes have PipeStatusFlags when created"), + |_| {} + ) }, )??, Descriptor::Eventfd { file, .. } => { @@ -1291,6 +1282,21 @@ impl Task { core::num::NonZero::new(4096), ); + { + let initial_status = OFlags::from(pipe_flags); + let mut dt = self.global.litebox.descriptor_table_mut(); + let old = dt.set_entry_metadata( + &writer, + crate::PipeStatusFlags(initial_status | OFlags::WRONLY), + ); + assert!(old.is_none()); + let old = dt.set_entry_metadata( + &reader, + crate::PipeStatusFlags(initial_status | OFlags::RDONLY), + ); + assert!(old.is_none()); + } + if cloexec { let mut dt = self.global.litebox.descriptor_table_mut(); let None = dt.set_fd_metadata(&writer, FileDescriptorFlags::FD_CLOEXEC) else { diff --git a/litebox_shim_linux/src/syscalls/tests.rs b/litebox_shim_linux/src/syscalls/tests.rs index f91652965..80c08cfec 100644 --- a/litebox_shim_linux/src/syscalls/tests.rs +++ b/litebox_shim_linux/src/syscalls/tests.rs @@ -71,8 +71,8 @@ fn test_fcntl() { .unwrap(); assert_eq!(task.sys_fcntl(fd, FcntlArg::GETFD).unwrap(), 0); - task.sys_fcntl(fd, FcntlArg::SETFL(OFlags::empty())) - .unwrap(); + // OFlags::RDWR should be ignored + task.sys_fcntl(fd, FcntlArg::SETFL(OFlags::RDWR)).unwrap(); assert_eq!(task.sys_fcntl(fd, FcntlArg::GETFL).unwrap(), flags2.bits()); };