Skip to content

Commit ac636db

Browse files
authored
Merge pull request #4212 from tiif/setfl
Support F_GETFL and F_SETFL for fcntl
2 parents 83198e5 + ba67acd commit ac636db

File tree

7 files changed

+262
-8
lines changed

7 files changed

+262
-8
lines changed

src/shims/files.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,20 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
202202
fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
203203
panic!("Not a unix file descriptor: {}", self.name());
204204
}
205+
206+
/// Implementation of fcntl(F_GETFL) for this FD.
207+
fn get_flags<'tcx>(&self, _ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
208+
throw_unsup_format!("fcntl: {} is not supported for F_GETFL", self.name());
209+
}
210+
211+
/// Implementation of fcntl(F_SETFL) for this FD.
212+
fn set_flags<'tcx>(
213+
&self,
214+
_flag: i32,
215+
_ecx: &mut MiriInterpCx<'tcx>,
216+
) -> InterpResult<'tcx, Scalar> {
217+
throw_unsup_format!("fcntl: {} is not supported for F_SETFL", self.name());
218+
}
205219
}
206220

207221
impl FileDescription for io::Stdin {

src/shims/unix/fd.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
141141
let f_getfd = this.eval_libc_i32("F_GETFD");
142142
let f_dupfd = this.eval_libc_i32("F_DUPFD");
143143
let f_dupfd_cloexec = this.eval_libc_i32("F_DUPFD_CLOEXEC");
144+
let f_getfl = this.eval_libc_i32("F_GETFL");
145+
let f_setfl = this.eval_libc_i32("F_SETFL");
144146

145147
// We only support getting the flags for a descriptor.
146148
match cmd {
@@ -175,6 +177,25 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
175177
this.set_last_error_and_return_i32(LibcError("EBADF"))
176178
}
177179
}
180+
cmd if cmd == f_getfl => {
181+
// Check if this is a valid open file descriptor.
182+
let Some(fd) = this.machine.fds.get(fd_num) else {
183+
return this.set_last_error_and_return_i32(LibcError("EBADF"));
184+
};
185+
186+
fd.get_flags(this)
187+
}
188+
cmd if cmd == f_setfl => {
189+
// Check if this is a valid open file descriptor.
190+
let Some(fd) = this.machine.fds.get(fd_num) else {
191+
return this.set_last_error_and_return_i32(LibcError("EBADF"));
192+
};
193+
194+
let [flag] = check_min_vararg_count("fcntl(fd, F_SETFL, ...)", varargs)?;
195+
let flag = this.read_scalar(flag)?.to_i32()?;
196+
197+
fd.set_flags(flag, this)
198+
}
178199
cmd if this.tcx.sess.target.os == "macos"
179200
&& cmd == this.eval_libc_i32("F_FULLFSYNC") =>
180201
{

src/shims/unix/unnamed_socket.rs

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ use crate::*;
2020
/// be configured in the real system.
2121
const MAX_SOCKETPAIR_BUFFER_CAPACITY: usize = 212992;
2222

23+
#[derive(Debug, PartialEq)]
24+
enum AnonSocketType {
25+
// Either end of the socketpair fd.
26+
Socketpair,
27+
// Read end of the pipe.
28+
PipeRead,
29+
// Write end of the pipe.
30+
PipeWrite,
31+
}
32+
2333
/// One end of a pair of connected unnamed sockets.
2434
#[derive(Debug)]
2535
struct AnonSocket {
@@ -40,7 +50,10 @@ struct AnonSocket {
4050
/// A list of thread ids blocked because the buffer was full.
4151
/// Once another thread reads some bytes, these threads will be unblocked.
4252
blocked_write_tid: RefCell<Vec<ThreadId>>,
43-
is_nonblock: bool,
53+
/// Whether this fd is non-blocking or not.
54+
is_nonblock: Cell<bool>,
55+
// Differentiate between different AnonSocket fd types.
56+
fd_type: AnonSocketType,
4457
}
4558

4659
#[derive(Debug)]
@@ -63,7 +76,10 @@ impl AnonSocket {
6376

6477
impl FileDescription for AnonSocket {
6578
fn name(&self) -> &'static str {
66-
"socketpair"
79+
match self.fd_type {
80+
AnonSocketType::Socketpair => "socketpair",
81+
AnonSocketType::PipeRead | AnonSocketType::PipeWrite => "pipe",
82+
}
6783
}
6884

6985
fn close<'tcx>(
@@ -110,6 +126,66 @@ impl FileDescription for AnonSocket {
110126
fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
111127
self
112128
}
129+
130+
fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
131+
let mut flags = 0;
132+
133+
// Get flag for file access mode.
134+
// The flag for both socketpair and pipe will remain the same even when the peer
135+
// fd is closed, so we need to look at the original type of this socket, not at whether
136+
// the peer socket still exists.
137+
match self.fd_type {
138+
AnonSocketType::Socketpair => {
139+
flags |= ecx.eval_libc_i32("O_RDWR");
140+
}
141+
AnonSocketType::PipeRead => {
142+
flags |= ecx.eval_libc_i32("O_RDONLY");
143+
}
144+
AnonSocketType::PipeWrite => {
145+
flags |= ecx.eval_libc_i32("O_WRONLY");
146+
}
147+
}
148+
149+
// Get flag for blocking status.
150+
if self.is_nonblock.get() {
151+
flags |= ecx.eval_libc_i32("O_NONBLOCK");
152+
}
153+
154+
interp_ok(Scalar::from_i32(flags))
155+
}
156+
157+
fn set_flags<'tcx>(
158+
&self,
159+
mut flag: i32,
160+
ecx: &mut MiriInterpCx<'tcx>,
161+
) -> InterpResult<'tcx, Scalar> {
162+
// FIXME: File creation flags should be ignored.
163+
164+
let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK");
165+
let o_rdonly = ecx.eval_libc_i32("O_RDONLY");
166+
let o_wronly = ecx.eval_libc_i32("O_WRONLY");
167+
let o_rdwr = ecx.eval_libc_i32("O_RDWR");
168+
169+
// O_NONBLOCK flag can be set / unset by user.
170+
if flag & o_nonblock == o_nonblock {
171+
self.is_nonblock.set(true);
172+
flag &= !o_nonblock;
173+
} else {
174+
self.is_nonblock.set(false);
175+
}
176+
177+
// Ignore all file access mode flags.
178+
flag &= !(o_rdonly | o_wronly | o_rdwr);
179+
180+
// Throw error if there is any unsupported flag.
181+
if flag != 0 {
182+
throw_unsup_format!(
183+
"fcntl: only O_NONBLOCK is supported for F_SETFL on socketpairs and pipes"
184+
)
185+
}
186+
187+
interp_ok(Scalar::from_i32(0))
188+
}
113189
}
114190

115191
/// Write to AnonSocket based on the space available and return the written byte size.
@@ -141,7 +217,7 @@ fn anonsocket_write<'tcx>(
141217
// Let's see if we can write.
142218
let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(writebuf.borrow().buf.len());
143219
if available_space == 0 {
144-
if self_ref.is_nonblock {
220+
if self_ref.is_nonblock.get() {
145221
// Non-blocking socketpair with a full buffer.
146222
return finish.call(ecx, Err(ErrorKind::WouldBlock.into()));
147223
} else {
@@ -223,7 +299,7 @@ fn anonsocket_read<'tcx>(
223299
// Socketpair with no peer and empty buffer.
224300
// 0 bytes successfully read indicates end-of-file.
225301
return finish.call(ecx, Ok(0));
226-
} else if self_ref.is_nonblock {
302+
} else if self_ref.is_nonblock.get() {
227303
// Non-blocking socketpair with writer and empty buffer.
228304
// https://linux.die.net/man/2/read
229305
// EAGAIN or EWOULDBLOCK can be returned for socket,
@@ -407,15 +483,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
407483
peer_lost_data: Cell::new(false),
408484
blocked_read_tid: RefCell::new(Vec::new()),
409485
blocked_write_tid: RefCell::new(Vec::new()),
410-
is_nonblock: is_sock_nonblock,
486+
is_nonblock: Cell::new(is_sock_nonblock),
487+
fd_type: AnonSocketType::Socketpair,
411488
});
412489
let fd1 = fds.new_ref(AnonSocket {
413490
readbuf: Some(RefCell::new(Buffer::new())),
414491
peer_fd: OnceCell::new(),
415492
peer_lost_data: Cell::new(false),
416493
blocked_read_tid: RefCell::new(Vec::new()),
417494
blocked_write_tid: RefCell::new(Vec::new()),
418-
is_nonblock: is_sock_nonblock,
495+
is_nonblock: Cell::new(is_sock_nonblock),
496+
fd_type: AnonSocketType::Socketpair,
419497
});
420498

421499
// Make the file descriptions point to each other.
@@ -475,15 +553,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
475553
peer_lost_data: Cell::new(false),
476554
blocked_read_tid: RefCell::new(Vec::new()),
477555
blocked_write_tid: RefCell::new(Vec::new()),
478-
is_nonblock,
556+
is_nonblock: Cell::new(is_nonblock),
557+
fd_type: AnonSocketType::PipeRead,
479558
});
480559
let fd1 = fds.new_ref(AnonSocket {
481560
readbuf: None,
482561
peer_fd: OnceCell::new(),
483562
peer_lost_data: Cell::new(false),
484563
blocked_read_tid: RefCell::new(Vec::new()),
485564
blocked_write_tid: RefCell::new(Vec::new()),
486-
is_nonblock,
565+
is_nonblock: Cell::new(is_nonblock),
566+
fd_type: AnonSocketType::PipeWrite,
487567
});
488568

489569
// Make the file descriptions point to each other.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//@ignore-target: windows # Sockets/pipes are not implemented yet
2+
//~^ ERROR: deadlock: the evaluated program deadlocked
3+
//@compile-flags: -Zmiri-deterministic-concurrency
4+
use std::thread;
5+
6+
/// If an O_NONBLOCK flag is set while the fd is blocking, that fd will not be woken up.
7+
fn main() {
8+
let mut fds = [-1, -1];
9+
let res = unsafe { libc::pipe(fds.as_mut_ptr()) };
10+
assert_eq!(res, 0);
11+
let mut buf: [u8; 5] = [0; 5];
12+
let _thread1 = thread::spawn(move || {
13+
// Add O_NONBLOCK flag while pipe is still block on read.
14+
let res = unsafe { libc::fcntl(fds[0], libc::F_SETFL, libc::O_NONBLOCK) };
15+
assert_eq!(res, 0);
16+
});
17+
// Main thread will block on read.
18+
let _res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
19+
//~^ ERROR: deadlock: the evaluated program deadlocked
20+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
error: deadlock: the evaluated program deadlocked
2+
|
3+
= note: the evaluated program deadlocked
4+
= note: (no span available)
5+
= note: BACKTRACE on thread `unnamed-ID`:
6+
7+
error: deadlock: the evaluated program deadlocked
8+
--> tests/fail-dep/libc/fcntl_fsetfl_while_blocking.rs:LL:CC
9+
|
10+
LL | let _res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
11+
| ^ the evaluated program deadlocked
12+
|
13+
= note: BACKTRACE:
14+
= note: inside `main` at tests/fail-dep/libc/fcntl_fsetfl_while_blocking.rs:LL:CC
15+
16+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
17+
18+
error: aborting due to 2 previous errors
19+

tests/pass-dep/libc/libc-pipe.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ fn main() {
1515
))]
1616
// `pipe2` only exists in some specific os.
1717
test_pipe2();
18+
test_pipe_setfl_getfl();
19+
test_pipe_fcntl_threaded();
1820
}
1921

2022
fn test_pipe() {
@@ -127,3 +129,68 @@ fn test_pipe2() {
127129
let res = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_NONBLOCK) };
128130
assert_eq!(res, 0);
129131
}
132+
133+
/// Basic test for pipe fcntl's F_SETFL and F_GETFL flag.
134+
fn test_pipe_setfl_getfl() {
135+
// Initialise pipe fds.
136+
let mut fds = [-1, -1];
137+
let res = unsafe { libc::pipe(fds.as_mut_ptr()) };
138+
assert_eq!(res, 0);
139+
140+
// Both sides should either have O_RONLY or O_WRONLY.
141+
let res = unsafe { libc::fcntl(fds[0], libc::F_GETFL) };
142+
assert_eq!(res, libc::O_RDONLY);
143+
let res = unsafe { libc::fcntl(fds[1], libc::F_GETFL) };
144+
assert_eq!(res, libc::O_WRONLY);
145+
146+
// Add the O_NONBLOCK flag with F_SETFL.
147+
let res = unsafe { libc::fcntl(fds[0], libc::F_SETFL, libc::O_NONBLOCK) };
148+
assert_eq!(res, 0);
149+
150+
// Test if the O_NONBLOCK flag is successfully added.
151+
let res = unsafe { libc::fcntl(fds[0], libc::F_GETFL) };
152+
assert_eq!(res, libc::O_RDONLY | libc::O_NONBLOCK);
153+
154+
// The other side remains unchanged.
155+
let res = unsafe { libc::fcntl(fds[1], libc::F_GETFL) };
156+
assert_eq!(res, libc::O_WRONLY);
157+
158+
// Test if O_NONBLOCK flag can be unset.
159+
let res = unsafe { libc::fcntl(fds[0], libc::F_SETFL, 0) };
160+
assert_eq!(res, 0);
161+
let res = unsafe { libc::fcntl(fds[0], libc::F_GETFL) };
162+
assert_eq!(res, libc::O_RDONLY);
163+
}
164+
165+
/// Test the behaviour of F_SETFL/F_GETFL when a fd is blocking.
166+
/// The expected execution is:
167+
/// 1. Main thread blocks on fds[0] `read`.
168+
/// 2. Thread 1 sets O_NONBLOCK flag on fds[0],
169+
/// checks the value of F_GETFL,
170+
/// then writes to fds[1] to unblock main thread's `read`.
171+
fn test_pipe_fcntl_threaded() {
172+
let mut fds = [-1, -1];
173+
let res = unsafe { libc::pipe(fds.as_mut_ptr()) };
174+
assert_eq!(res, 0);
175+
let mut buf: [u8; 5] = [0; 5];
176+
let thread1 = thread::spawn(move || {
177+
// Add O_NONBLOCK flag while pipe is still blocked on read.
178+
let res = unsafe { libc::fcntl(fds[0], libc::F_SETFL, libc::O_NONBLOCK) };
179+
assert_eq!(res, 0);
180+
181+
// Check the new flag value while the main thread is still blocked on fds[0].
182+
let res = unsafe { libc::fcntl(fds[0], libc::F_GETFL) };
183+
assert_eq!(res, libc::O_NONBLOCK);
184+
185+
// The write below will unblock the `read` in main thread: even though
186+
// the socket is now "non-blocking", the shim needs to deal correctly
187+
// with threads that were blocked before the socket was made non-blocking.
188+
let data = "abcde".as_bytes().as_ptr();
189+
let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
190+
assert_eq!(res, 5);
191+
});
192+
// The `read` below will block.
193+
let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
194+
thread1.join().unwrap();
195+
assert_eq!(res, 5);
196+
}

tests/pass-dep/libc/libc-socketpair.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ fn main() {
1212
test_race();
1313
test_blocking_read();
1414
test_blocking_write();
15+
test_socketpair_setfl_getfl();
1516
}
1617

1718
fn test_socketpair() {
@@ -182,3 +183,35 @@ fn test_blocking_write() {
182183
thread1.join().unwrap();
183184
thread2.join().unwrap();
184185
}
186+
187+
/// Basic test for socketpair fcntl's F_SETFL and F_GETFL flag.
188+
fn test_socketpair_setfl_getfl() {
189+
// Initialise socketpair fds.
190+
let mut fds = [-1, -1];
191+
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
192+
assert_eq!(res, 0);
193+
194+
// Test if both sides have O_RDWR.
195+
let res = unsafe { libc::fcntl(fds[0], libc::F_GETFL) };
196+
assert_eq!(res, libc::O_RDWR);
197+
let res = unsafe { libc::fcntl(fds[1], libc::F_GETFL) };
198+
assert_eq!(res, libc::O_RDWR);
199+
200+
// Add the O_NONBLOCK flag with F_SETFL.
201+
let res = unsafe { libc::fcntl(fds[0], libc::F_SETFL, libc::O_NONBLOCK) };
202+
assert_eq!(res, 0);
203+
204+
// Test if the O_NONBLOCK flag is successfully added.
205+
let res = unsafe { libc::fcntl(fds[0], libc::F_GETFL) };
206+
assert_eq!(res, libc::O_RDWR | libc::O_NONBLOCK);
207+
208+
// The other side remains unchanged.
209+
let res = unsafe { libc::fcntl(fds[1], libc::F_GETFL) };
210+
assert_eq!(res, libc::O_RDWR);
211+
212+
// Test if O_NONBLOCK flag can be unset.
213+
let res = unsafe { libc::fcntl(fds[0], libc::F_SETFL, 0) };
214+
assert_eq!(res, 0);
215+
let res = unsafe { libc::fcntl(fds[0], libc::F_GETFL) };
216+
assert_eq!(res, libc::O_RDWR);
217+
}

0 commit comments

Comments
 (0)