-
-
Notifications
You must be signed in to change notification settings - Fork 15
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
Ensure stdio handles are never null when converting to HandleRef
#12
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love it either, but I think this at least re-capitulates the state of things from before, so I'm inclined to move forward. The thing that especially bugs me about this is the silent failure mode.
I guess we should eventually make the stdin/stdout/stderr APIs return a Result
, but that really just passes the buck on up.
} else { | ||
handle | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My guess is that trying to write to such a handle will just result in nothing happening, right? And I think that's what the behavior was before this change, so I guess this doesn't make anything worse.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be an Err
when used rather than a panic when created, which was the status quo before the standard library added the assert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh! That's great then. Non-silent failure mode. That makes me happier.
impl HandleRef { | ||
/// Create a borrowed handle to stdin. | ||
/// | ||
/// When the returned handle is dropped, stdin is not closed. | ||
pub fn stdin() -> HandleRef { | ||
unsafe { HandleRef::from_raw_handle(io::stdin().as_raw_handle()) } | ||
unsafe { HandleRef::from_raw_handle(nonnull_handle(io::stdin())) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we include a note in the stdin/stdout/stderr docs here that say what the behavior here is when stdin/stdout/stderr aren't available? I think we should also specifically mention the Windows GUI subsystem thing, to make it particularly concrete.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added some documentation. I hedged slightly, saying using such a handle is "likely" to fail because I'm not sure I can guarantee the behaviour of every file function in the Windows API, now and in the future.
Could you comment on how this fits into the big picture? The linked testcase is now fixed with a patch that is now in Rust nightly, and we expect to backport it to beta and possibly stable. Are you expecting to revert this patch when that fix reaches Rust stable, or are you expecting to keep it? The issues around NULL and INVALID_HANDLE_VALUE in the Windows API are confusing as it is, and I'm a little nervous about this patch silently translating from NULL to INVALID_HANDLE_VALUE, introducing another potential moving part into that system. |
Sure. As I said in the OP I'm unhappy with this PR so I'll start by explaining why before explaining my reasoning for doing it. This is likely not anything new to you but I want to be clear to everyone. A Ultimately the fix for this is to make a breaking change to the API of So given all that, why did I submit this PR? Because this appears to be affecting numerous applications right now. And this comment indicated that the upstream patch may not be backported to stable. I'd also note that neither this PR nor the upstream patch prevents the I/O unsafety in this specific case, it just removes the panic. The point about translating null to |
Brainstorming a little here: if it makes sense for winapi-util to translate NULL to INVALID_HANDLE_VALUE here, would it make sense to go even further and make A downside might be backwards compatibility. Any code expecting to be able to detect "windows" mode or detached consoles by checking for NULL would no longer be able to distinguish that case.
It's my impression that this kind of "temporary |
Perhaps! To be honest though I'm not totally on board with Rust's process spawning behaviour either. I do think it should pass on NULL values because that can have a different meaning to INVALID_HANDLE_VALUE (the first is presumably intentional whereas the latter is likely the result of an error somewhere). Admittedly, I'm not sure if the difference matters too much to the subprocess. Another gotcha (which has be brought up a few times before) is that Hm, I think in general a generic
I would be on board with that in principle. And, yeah, I suspect it's a common enough pattern. Of course there might be something I'm not thinking of here so I don't want to say "yes definitely" until I'm more certain there are no hidden gotchas. As mentioned, a safer way to share handles is using Edit: Well the std actually does have |
So to avoid going too far off the topic of winapi-util, I imagine the lowest level of the API might look something like this: pub trait AsIoHandle {
/// Returns a raw handle to an I/O object such as a file or pipe.
///
/// For types like [`File`] that are always valid file-like objects, this
/// is simply a wrapper for [`AsRawHandle`]. However stdio types (such as
/// [`io::Stdout`]) may fail if there is no handle or the handle is invalid.
/// Most commonly this will occur when an application is using the Windows
/// GUI subsystem.
fn as_io_handle(&self) -> io::Result<RawHandle>;
}
impl AsIoHandle for io::Stdout {
fn as_io_handle(&self) -> io::Result<RawHandle> {
validate_stdio_handle(self.as_raw_handle())
}
}
fn validate_stdio_handle(handle: RawHandle) -> io::Result<RawHandle> {
// Handle values with the high bit set (i.e. `< 0`) are pseudo handles.
// These are magic values whose meaning is highly context dependent; each
// function may interpret them differently or not at all. It doesn't make
// sense to own, borrow or use these handles in a generic I/O context.
//
// A null handle is never valid.
if (handle as isize) <= 0 {
Err(io::Error::new(io::ErrorKind::Other, "the handle is invalid"))
} else {
Ok(handle)
}
} Where |
@ChrisDenton The original bug here was fixed on Rust stable in Rust 1.57, so I believe we can close this issue now. And the discussion of NULL handles moved here rust-lang/rust#90964, which led to rust-lang/rust#93263. |
This fixes this test case, which means
env_logger
will no longer panic on stable. I'm not totally happy with this change because it's a workaround rather than a real fix. But I think something like this is the best the can be done without changing the API or how people use it.