Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Atomically set CLOEXEC on duplicated sockets #27980

Merged
merged 1 commit into from
Aug 31, 2015

Conversation

tbu-
Copy link
Contributor

@tbu- tbu- commented Aug 24, 2015

Still needs values of F_DUPFD_CLOEXEC on other OSs.

For Bitrig, NetBSD and OpenBSD the constant was incorrectly in posix01, when
it's actually posix08. In order to maintain backwards-compatiblity, the
constant was only copied, not moved.

cc #24237

@rust-highfive
Copy link
Collaborator

r? @nikomatsakis

(rust_highfive has picked a reviewer for you, use r? to override)

@tbu-
Copy link
Contributor Author

tbu- commented Aug 24, 2015

@barosl Could you provide the constants for the different OSs again, please?

@tbu-
Copy link
Contributor Author

tbu- commented Aug 24, 2015

r? @alexcrichton

#[cfg(target_os = "freebsd")]
pub mod posix08 {
use types::os::arch::c95::c_int;
pub const O_CLOEXEC: c_int = 0x100000;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pub const F_DUPFD_CLOEXEC : c_int = 17;

@tbu-
Copy link
Contributor Author

tbu- commented Aug 24, 2015

@nagisa May I ask how you get all these constants? Do you have virtual machines? Do you look them up in the source code?

@nagisa
Copy link
Member

nagisa commented Aug 24, 2015

I looked them up in the source code using crossreferences available online. e.g. FreeBSD or XNU/OS X

@nagisa
Copy link
Member

nagisa commented Aug 24, 2015

Is it necessary to base this PR onto #27971 instead of master?

@tbu-
Copy link
Contributor Author

tbu- commented Aug 24, 2015

@nagisa Yes, otherwise merge conflicts would occur around the posix08 constants.

@alexcrichton
Copy link
Member

Can this also avoid copying the constant definition in liblibc? This probably is not the only mistake in the organization of liblibc, so it's better to reduce the duplication than try to keep it correct from where it is today.

@tbu-
Copy link
Contributor Author

tbu- commented Aug 24, 2015

@alexcrichton Should the new constants be put in the incorrect module as well (for consistency) or in the posix08 module (for correctness)?

@@ -66,7 +67,30 @@ impl Socket {
}

pub fn duplicate(&self) -> io::Result<Socket> {
let fd = try!(cvt(unsafe { libc::dup(self.0.raw()) }));
use libc::funcs::posix88::fcntl::fcntl;
const ZERO: c_int = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come this and MINUS_ONE are constants? Could they just be literals and/or locals?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why I added MINUS_ONE, and will remove that, but ZERO is kind of required to make sure the type of 0 is c_int as this is passed as varargs to fcntl.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does anything go wrong if it's just passed as a function argument?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't guarantee it's c_int then, because the compiler doesn't (/can't) know the type of the third parameter. Whether anything goes wrong or not, one would need to have better understanding of this situation than I do because this is unsafe code.

(For reference, the function signature is declared as int fcntl(int filedes, int cmd, ...), and in our case int fcntl(int filedes, F_DUPFD_CLOEXEC, int arg).)

@alexcrichton
Copy link
Member

So long as they find their way into either liblibc or sys::c I don't particularly mind where they're located.

}
});
unsafe { FCNTL_DUPFD }
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment explaining what this function is doing? I'm also somewhat wary about the assert! here as there's probably some random kernel which returns some obscure error code. Are we also guaranteed that EINVAL is never returned for a bad file descriptor?

I'm not sure if there's some prior art to draw from in this sort of detection, but I'm a little wary of this...

@barosl
Copy link
Contributor

barosl commented Aug 24, 2015

Here you are!

Linux: 1030 (0x406)
FreeBSD: 17 (0x11)
OpenBSD: 10 (0xa)
NetBSD: 12 (0xc)
Dragonfly: 17 (0x11)
Bitrig: 10 (0xa)

NetBSD's seems to differ from the current value of 10.

Tested on my VMs.

@tbu-
Copy link
Contributor Author

tbu- commented Aug 24, 2015

@alexcrichton Added a comment and referenced prior art: http://comments.gmane.org/gmane.linux.lib.musl.general/2963.

@@ -4632,7 +4646,7 @@ pub mod consts {
pub const F_GETLK : c_int = 7;
pub const F_SETLK : c_int = 8;
pub const F_SETLKW : c_int = 9;
pub const F_DUPFD_CLOEXEC : c_int = 10;
pub use consts::os::posix08::F_DUPFD_CLOEXEC;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, #27930 already introduced a breaking change to libc, which changed the location of _PC_NAME_MAX from the funcs module (!) to the consts module. So personally, I think it would be clearer if this got removed completely, as we're committing breakage anyways. But YMMV!

@alexcrichton
Copy link
Member

Hm it looks like the implementation here is pretty different from that inside MUSL. The mailing list thread indicates that there are two conditions we can get EINVAL:

  1. The file descriptor argument is too big
  2. The cloexec variant isn't supported

It's probably tough to say where EBADF vs EINVAL is returned, and that thread doesn't seem to have a conclusion about whether EINVAL could perhaps be returned for bad file descriptors. How about switching up the implementation to the just-as-efficient (on newer systems at least) but simpler (and closer to what MUSL does):

pub fn duplicate(&self) -> io::Result<Socket> {
    cvt(unsafe {
        libc::fcntl(self.0.raw(), libc::FCNTL_DUPFD_CLOEXEC, 0)
    }).or_else(|e| {
        if e.raw_os_error() == Some(libc::EINVAL) {
            cvt(unsafe { libc::fcntl(self.0.raw(), libc::FCNTL_DUPFD, 0) })
        } else {
            Err(e)
        }
    }).map(|fd| {
        let fd = FileDesc::new();
        fd.set_cloexec();
        Socket(fd)
    })
}

We know that the first case of EINVAL can't be returned because we have a valid file descriptor so that's all we need to check for. It's also not clear to me whether this is worth the current platform-specific logic plus the caching via Once, so slimming it down and unifying it for all platforms is a bonus.

@tbu-
Copy link
Contributor Author

tbu- commented Aug 25, 2015

@alexcrichton The documentation of the syscall and the linked mailing list thread agree on that EINVAL is only returned on unknown command or incorrect third parameter, but we know that 0 is always valid here, so it must be the bad command. The kernel source code first switches on the command (and it has to, because the first parameter can have different meanings depending on the command), so EBADF is returned later than EINVAL on invalid first parameter.

I'll try to get closer to MUSL though...

@tbu-
Copy link
Contributor Author

tbu- commented Aug 25, 2015

@alexcrichton It's now nearer to the MUSL code.

Err(ref e) if e.raw_os_error() == Some(libc::EINVAL) => {
EMULATE_F_DUPFD_CLOEXEC.store(true, atomic::Ordering::Relaxed);
}
res => return res.map(|fd| Socket(FileDesc::new(fd))),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An interesting point of the MUSL thread indicated that some newer kernels apparently broke F_DUPFD_CLOEXEC such that it was the same as F_DUPFD, and hence even if this syscall succeeds they still set cloexec. It may be good to just keep the same common code at the end where set_cloexec is called?

@tbu-
Copy link
Contributor Author

tbu- commented Aug 29, 2015

@alexcrichton Done.

@alexcrichton
Copy link
Member

Looks good to me, thanks! r=me with a squash and a rebase

For Bitrig, NetBSD and OpenBSD the constant was incorrectly in posix01, when
it's actually posix08, so we move it. This is a [breaking-change], but we
already had one in rust-lang#27930.

Fix NetBSD's F_DUPFD_CLOEXEC constant.

For a similar feature detection, see this musl thread:
http://comments.gmane.org/gmane.linux.lib.musl.general/2963

This assumes that an int literal has type `c_int` for varidic functions.
@tbu-
Copy link
Contributor Author

tbu- commented Aug 30, 2015

@alexcrichton Rebased.

@alexcrichton
Copy link
Member

@bors: r+ 1f81ef4

bors added a commit that referenced this pull request Aug 31, 2015
Still needs values of F_DUPFD_CLOEXEC on other OSs.

For Bitrig, NetBSD and OpenBSD the constant was incorrectly in posix01, when
it's actually posix08. In order to maintain backwards-compatiblity, the
constant was only copied, not moved.

cc #24237
@bors
Copy link
Contributor

bors commented Aug 31, 2015

⌛ Testing commit 1f81ef4 with merge 05cc464...

@bors bors merged commit 1f81ef4 into rust-lang:master Aug 31, 2015
bors added a commit that referenced this pull request Feb 6, 2016
These commits finish up closing out #24237 by filling out all locations we create new file descriptors with variants that atomically create the file descriptor and set CLOEXEC where possible. Previous support for doing this in `File::open` was added in #27971 and support for `try_clone` was added in #27980. This commit fills out:

* `Socket::new` now passes `SOCK_CLOEXEC`
* `Socket::accept` now uses `accept4`
* `pipe2` is used instead of `pipe`

Unfortunately most of this support is Linux-specific, and most of it is post-2.6.18 (our oldest supported version), so all of the detection here is done dynamically. It looks like OSX does not have equivalent variants for these functions, so there's nothing more we can do there. Support for BSDs can be added over time if they also have these functions.

Closes #24237
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants