rust-lang / libs-team

The home of the library team
Apache License 2.0
115 stars 18 forks source link

Add interface to open null device as a `File` #289

Closed epilys closed 10 months ago

epilys commented 10 months ago

Proposal

Problem statement

When using process::Stdio it's possible to pass a Null file descriptor to the spawned process. But there's no portable unix way of actually opening the null device and std library users have to do it manually.

Motivating examples or use cases

/dev/null is useful when dealing with output streams in general. When setting up processes and stream redirections manually, it's very possible a "black hole" file descriptor you are writing into but never need the data itself is needed. This is such a common scenario in unix programming that I won't write down a specific use scenario but I'm open to providing some if it's deemed necessary for motivation.

Solution sketch

Add a method null to std::os::unix::fs::OpenOptionsExt:

fn null(&self) -> io::Result<fs::File>;

with the impl:

fn null(&self) -> io::Result<fs::File> {
 Ok(fs::File::from_inner(crate::sys::fs::File::null(self.as_inner())?))
}

and add the following method in sys::unix::fs::File:

    pub fn null(opts: &OpenOptions) -> io::Result<Self> {
        use crate::ffi::CStr;
        use crate::sys::unix::DEV_NULL;

        let path = unsafe { CStr::from_ptr(DEV_NULL.as_ptr() as *const _) };
        Self::open_c(&path, opts)
    }

I've pushed a draft implementation here: https://github.com/epilys/rust/tree/156fb1bc2b5828ec4c280be434dcd49566f87019

I'd also add a simple unit test to make sure the device can be succesfully opened. I was no sure what feature/stability I need to gate this though.

Alternatives

-

Links and related work

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

Second, if there's a concrete solution:

shepmaster commented 10 months ago

I think it’d be useful to explain why you cannot use one of

epilys commented 10 months ago

@shepmaster because these do not give you a file descriptor.

the8472 commented 10 months ago

When setting up processes and stream redirections manually, it's very possible a "black hole" file descriptor you are writing into but never need the data itself is needed.

By manually you mean not using process:Command, using some other APIs for process spawning instead, such as nix? If so, why should this be in std instead of that crate.

This is such a common scenario in unix programming that I won't write down a specific use scenario but I'm open to providing some if it's deemed necessary for motivation.

I think this is necessary. Either just opening /dev/null like any other file or using Stdio::null seems like it would already cover a lot of ground. So you have to explain

But there's no portable unix way of actually opening the null device

/dev/null is part of posix.

epilys commented 10 months ago

.

By manually you mean not using process:Command, using some other APIs for process spawning instead, such as nix? If so, why should this be in std instead of that crate.

It should be in std for the same reason File, std::os::fd::RawFd etc are. File descriptors are not used for standard input/output/error only.

/dev/null is part of posix.

I don't think I understand what this is in response to, can you clarify?

pitaj commented 10 months ago

I think they're saying that the standard Posix way of opening a null file with Rust would be

std::fs::File::open("/dev/null")
epilys commented 10 months ago

I was looking at the Stdio::Null impl to see if it was doing something different than that, which was my first thought, and the hardcoded path value is not always /dev/null but:

cfg_if::cfg_if! {
    if #[cfg(target_os = "fuchsia")] {
        // fuchsia doesn't have /dev/null
    } else if #[cfg(target_os = "redox")] {
        const DEV_NULL: &str = "null:\0";
    } else if #[cfg(target_os = "vxworks")] {
        const DEV_NULL: &str = "/null\0";
    } else {
        const DEV_NULL: &str = "/dev/null\0";
    }
}

So it seemed to me that to support target_family = "unix" consistently the same logic should be followed when opening it as a file. What do you think?

pitaj commented 10 months ago

AFAIK, Fuchsia, Redox, and VxWorks are not Unixes

epilys commented 10 months ago

The code is quoted from library/std/src/sys/unix/process/process_common.rs, it's not mine. I may was mistaken to think std/src/sys/unix/process/ is about target_family; what is it for then and what does the unix in the module path mean?

pitaj commented 10 months ago

The placement of code in std can be a little confusing. Often common unix code is used for anything that isn't Windows, which includes things like Redox that definitely aren't Unix either.

epilys commented 10 months ago

What would be the appropriate place for opening a null device file descript as OwnedFd or File then?

pitaj commented 10 months ago

I'm not sure.

Regarding the ACP:

  1. Why make this Unix specific? Windows also has NUL which acts similarly. And as shown, other OSes have support for a null device as well. There's precedence in Stdio::null
  2. On Unix, the user can just use File::open("/dev/null"), so why have just that in the standard library?
epilys commented 10 months ago

Thanks for the feedback everyone,