Skip to content

Commit 77c3daa

Browse files
committed
Change dup2's second operand from &OwnedFd to &mut OwnedFd.
And similar for `dup3`. The idea behind using `&OwnedFd` is that `dup2`'s second operand isn't like a normal borrow. It effectively closes the old file descriptor, and creates a new one with the same index. This could break assumptions of classes that have an `AsFd` to allow users to do special I/O operations, but which don't expect users can close and reopen their file descriptor as some completely unrelated resource. However, the existence of things like [`FilelikeView`], as well as the `ManuallyDrop` pattern, mean that `&OwnedFd` doesn't actually prevent users from using `dup2` on a `BorrowedFd`. With sunfishcode/io-lifetimes#32 though, `&mut OwnedFd` would be sufficient, because it removes the `DerefMut` implementation. So change `rustix` stance to be that `dup2` requires `&mut OwnedFd`. This means that it's no longer possible to pass the same file descriptor to both operands of `dup2` or `dup3` with safe Rust, which means it's not possible to observe the difference in behavior in that case, so remove the `dup3.rs` test. [`FilelikeView`]: https://docs.rs/io-lifetimes/latest/io_lifetimes/views/struct.FilelikeView.html
1 parent 9140010 commit 77c3daa

File tree

6 files changed

+30
-76
lines changed

6 files changed

+30
-76
lines changed

examples/dup2_to_replace_stdio.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ fn main() {
1616
// that stdin and stdout will be open or safe to use. It's ok here, because
1717
// we're directly inside `main`, so we know that stdin and stdout haven't
1818
// been closed and aren't being used for other purposes.
19-
let (stdin, stdout) = unsafe { (rustix::io::take_stdin(), rustix::io::take_stdout()) };
19+
let (mut stdin, mut stdout) = unsafe { (rustix::io::take_stdin(), rustix::io::take_stdout()) };
2020

2121
// Use `dup2` to copy our new file descriptors over the stdio file descriptors.
2222
//
23-
// These take their second argument as an `&OwnedFd` rather than the usual
24-
// `impl AsFd` because they conceptually do a `close` on the original file
25-
// descriptor, which one shouldn't be able to do with just a `BorrowedFd`.
26-
dup2(&reader, &stdin).unwrap();
27-
dup2(&writer, &stdout).unwrap();
23+
// These take their second argument as an `&mut OwnedFd` rather than the
24+
// usual `impl AsFd` because they conceptually do a `close` on the original
25+
// file descriptor, which one shouldn't be able to do with just a
26+
// `BorrowedFd`.
27+
dup2(&reader, &mut stdin).unwrap();
28+
dup2(&writer, &mut stdout).unwrap();
2829

2930
// Then, forget the stdio `OwnedFd`s, because actually dropping them would
3031
// close them. Here, we want stdin and stdout to remain open for the rest

src/imp/libc/io/syscalls.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ pub(crate) fn dup(fd: BorrowedFd<'_>) -> io::Result<OwnedFd> {
375375
}
376376

377377
#[cfg(not(target_os = "wasi"))]
378-
pub(crate) fn dup2(fd: BorrowedFd<'_>, new: &OwnedFd) -> io::Result<()> {
378+
pub(crate) fn dup2(fd: BorrowedFd<'_>, new: &mut OwnedFd) -> io::Result<()> {
379379
unsafe { ret_discarded_fd(c::dup2(borrowed_fd(fd), borrowed_fd(new.as_fd()))) }
380380
}
381381

@@ -387,7 +387,7 @@ pub(crate) fn dup2(fd: BorrowedFd<'_>, new: &OwnedFd) -> io::Result<()> {
387387
target_os = "redox",
388388
target_os = "wasi"
389389
)))]
390-
pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &OwnedFd, flags: DupFlags) -> io::Result<()> {
390+
pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &mut OwnedFd, flags: DupFlags) -> io::Result<()> {
391391
unsafe {
392392
ret_discarded_fd(c::dup3(
393393
borrowed_fd(fd),
@@ -404,14 +404,11 @@ pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &OwnedFd, flags: DupFlags) -> io::Re
404404
target_os = "ios",
405405
target_os = "redox"
406406
))]
407-
pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &OwnedFd, _flags: DupFlags) -> io::Result<()> {
407+
pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &mut OwnedFd, _flags: DupFlags) -> io::Result<()> {
408408
// Android 5.0 has `dup3`, but libc doesn't have bindings. Emulate it
409-
// using `dup2`, including the difference of failing with `EINVAL`
410-
// when the file descriptors are equal.
411-
use std::os::unix::io::AsRawFd;
412-
if fd.as_raw_fd() == new.as_raw_fd() {
413-
return Err(io::Errno::INVAL);
414-
}
409+
// using `dup2`. We don't need to worry about the difference between
410+
// `dup2` and `dup3` when the file descriptors are equal because we
411+
// have an `&mut OwnedFd` which means `fd` doesn't alias it.
415412
dup2(fd, new)
416413
}
417414

src/imp/linux_raw/io/syscalls.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -394,15 +394,12 @@ pub(crate) fn dup(fd: BorrowedFd<'_>) -> io::Result<OwnedFd> {
394394
}
395395

396396
#[inline]
397-
pub(crate) fn dup2(fd: BorrowedFd<'_>, new: &OwnedFd) -> io::Result<()> {
397+
pub(crate) fn dup2(fd: BorrowedFd<'_>, new: &mut OwnedFd) -> io::Result<()> {
398398
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
399399
{
400-
// `dup3` fails if the old and new file descriptors have the same
401-
// value, so emulate the `dup2` behavior.
402-
use crate::fd::AsRawFd;
403-
if fd.as_raw_fd() == new.as_raw_fd() {
404-
return Ok(());
405-
}
400+
// We don't need to worry about the difference between `dup2` and
401+
// `dup3` when the file descriptors are equal because we have an
402+
// `&mut OwnedFd` which means `fd` doesn't alias it.
406403
dup3(fd, new, DupFlags::empty())
407404
}
408405

@@ -413,7 +410,7 @@ pub(crate) fn dup2(fd: BorrowedFd<'_>, new: &OwnedFd) -> io::Result<()> {
413410
}
414411

415412
#[inline]
416-
pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &OwnedFd, flags: DupFlags) -> io::Result<()> {
413+
pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &mut OwnedFd, flags: DupFlags) -> io::Result<()> {
417414
unsafe { ret_discarded_fd(syscall_readonly!(__NR_dup3, fd, new.as_fd(), flags)) }
418415
}
419416

src/io/dup.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,16 @@ pub fn dup<Fd: AsFd>(fd: Fd) -> io::Result<OwnedFd> {
3131
imp::io::syscalls::dup(fd.as_fd())
3232
}
3333

34-
/// `dup2(fd, new)`—Creates a new `OwnedFd` instance that shares the
35-
/// same underlying [file description] as the existing `OwnedFd` instance,
36-
/// closing `new` and reusing its file descriptor.
34+
/// `dup2(fd, new)`—Changes the [file description] of a file descriptor.
35+
///
36+
/// `dup2` conceptually closes `new` and then sets the file description for
37+
/// `new` to be the same as the one for `fd`. This is a very unusual operation,
38+
/// and should only be used on file descriptors where you know how `new` will
39+
/// be subsequently used.
3740
///
3841
/// This function does not set the `O_CLOEXEC` flag. To do a `dup2` that does
3942
/// set `O_CLOEXEC`, use [`dup3`] with [`DupFlags::CLOEXEC`] on platforms which
40-
/// support it.
43+
/// support it, or [`fcntl_dupfd_cloexec`]
4144
///
4245
/// # References
4346
/// - [POSIX]
@@ -46,15 +49,15 @@ pub fn dup<Fd: AsFd>(fd: Fd) -> io::Result<OwnedFd> {
4649
/// [file description]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_258
4750
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup2.html
4851
/// [Linux]: https://man7.org/linux/man-pages/man2/dup2.2.html
52+
/// [`fcntl_dupfd_cloexec`]: crate::fs::fcntl_dupfd_cloexec
4953
#[cfg(not(target_os = "wasi"))]
5054
#[inline]
51-
pub fn dup2<Fd: AsFd>(fd: Fd, new: &OwnedFd) -> io::Result<()> {
55+
pub fn dup2<Fd: AsFd>(fd: Fd, new: &mut OwnedFd) -> io::Result<()> {
5256
imp::io::syscalls::dup2(fd.as_fd(), new)
5357
}
5458

55-
/// `dup3(fd, new, flags)`—Creates a new `OwnedFd` instance that shares the
56-
/// same underlying [file description] as the existing `OwnedFd` instance,
57-
/// closing `new` and reusing its file descriptor, with flags.
59+
/// `dup3(fd, new, flags)`—Changes the [file description] of a file
60+
/// descriptor, with flags.
5861
///
5962
/// `dup3` is the same as [`dup2`] but adds an additional flags operand,
6063
/// and it fails in the case that `fd` and `new` have the same file descriptor
@@ -70,6 +73,6 @@ pub fn dup2<Fd: AsFd>(fd: Fd, new: &OwnedFd) -> io::Result<()> {
7073
/// [Linux]: https://man7.org/linux/man-pages/man2/dup2.2.html
7174
#[cfg(not(target_os = "wasi"))]
7275
#[inline]
73-
pub fn dup3<Fd: AsFd>(fd: Fd, new: &OwnedFd, flags: DupFlags) -> io::Result<()> {
76+
pub fn dup3<Fd: AsFd>(fd: Fd, new: &mut OwnedFd, flags: DupFlags) -> io::Result<()> {
7477
imp::io::syscalls::dup3(fd.as_fd(), new, flags)
7578
}

tests/io/dup3.rs

Lines changed: 0 additions & 41 deletions
This file was deleted.

tests/io/main.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
#[cfg(not(windows))]
88
#[cfg(not(target_os = "wasi"))]
99
mod dup2_to_replace_stdio;
10-
#[cfg(not(windows))]
11-
#[cfg(not(target_os = "wasi"))]
12-
mod dup3;
1310
#[cfg(not(feature = "rustc-dep-of-std"))] // TODO
1411
#[cfg(not(windows))]
1512
#[cfg(feature = "net")]

0 commit comments

Comments
 (0)