-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of #12469 - arlosi:cred-stdio, r=weihanglo
cargo-credential: reset stdin & stdout to the Console Credential providers run with stdin and stdout piped to Cargo to communicate. This makes it more difficult for providers to do anything interactive. The current workaround is for a provider to use the `cargo_credential::tty()` function when reading from the console by re-opening stdin using `/dev/tty` or `CONIN$`. This PR makes the credential provider to re-attach itself to the current console so that reading from stdin and writing to stdout "just works" when inside the `perform` method of the provider. stderr is unaffected since it's not redirected by Cargo. Only the `cargo-credential` crate is changed. No changes are needed to Cargo. cc #8933
- Loading branch information
Showing
7 changed files
with
221 additions
and
30 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
//! Provider used for testing redirection of stdout. | ||
use cargo_credential::{Action, Credential, CredentialResponse, Error, RegistryInfo}; | ||
|
||
struct MyCredential; | ||
|
||
impl Credential for MyCredential { | ||
fn perform( | ||
&self, | ||
_registry: &RegistryInfo, | ||
_action: &Action, | ||
_args: &[&str], | ||
) -> Result<CredentialResponse, Error> { | ||
// Informational messages should be sent on stderr. | ||
eprintln!("message on stderr should be sent the the parent process"); | ||
|
||
// Reading from stdin and writing to stdout will go to the attached console (tty). | ||
println!("message from test credential provider"); | ||
Err(Error::OperationNotSupported) | ||
} | ||
} | ||
|
||
fn main() { | ||
cargo_credential::main(MyCredential); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
use std::{fs::File, io::Error}; | ||
|
||
/// Reset stdin and stdout to the attached console / tty for the duration of the closure. | ||
/// If no console is available, stdin and stdout will be redirected to null. | ||
pub fn stdin_stdout_to_console<F, T>(f: F) -> Result<T, Error> | ||
where | ||
F: FnOnce() -> T, | ||
{ | ||
let open_write = |f| std::fs::OpenOptions::new().write(true).open(f); | ||
|
||
let mut stdin = File::open(imp::IN_DEVICE).or_else(|_| File::open(imp::NULL_DEVICE))?; | ||
let mut stdout = open_write(imp::OUT_DEVICE).or_else(|_| open_write(imp::NULL_DEVICE))?; | ||
|
||
let _stdin_guard = imp::ReplacementGuard::new(Stdio::Stdin, &mut stdin)?; | ||
let _stdout_guard = imp::ReplacementGuard::new(Stdio::Stdout, &mut stdout)?; | ||
Ok(f()) | ||
} | ||
|
||
enum Stdio { | ||
Stdin, | ||
Stdout, | ||
} | ||
|
||
#[cfg(windows)] | ||
mod imp { | ||
use super::Stdio; | ||
use std::{fs::File, io::Error, os::windows::prelude::AsRawHandle}; | ||
use windows_sys::Win32::{ | ||
Foundation::{HANDLE, INVALID_HANDLE_VALUE}, | ||
System::Console::{ | ||
GetStdHandle, SetStdHandle, STD_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, | ||
}, | ||
}; | ||
pub const OUT_DEVICE: &str = "CONOUT$"; | ||
pub const IN_DEVICE: &str = "CONIN$"; | ||
pub const NULL_DEVICE: &str = "NUL"; | ||
|
||
/// Restores previous stdio when dropped. | ||
pub struct ReplacementGuard { | ||
std_handle: STD_HANDLE, | ||
previous: HANDLE, | ||
} | ||
|
||
impl ReplacementGuard { | ||
pub(super) fn new(stdio: Stdio, replacement: &mut File) -> Result<ReplacementGuard, Error> { | ||
let std_handle = match stdio { | ||
Stdio::Stdin => STD_INPUT_HANDLE, | ||
Stdio::Stdout => STD_OUTPUT_HANDLE, | ||
}; | ||
|
||
let previous; | ||
unsafe { | ||
// Make a copy of the current handle | ||
previous = GetStdHandle(std_handle); | ||
if previous == INVALID_HANDLE_VALUE { | ||
return Err(std::io::Error::last_os_error()); | ||
} | ||
|
||
// Replace stdin with the replacement handle | ||
if SetStdHandle(std_handle, replacement.as_raw_handle() as HANDLE) == 0 { | ||
return Err(std::io::Error::last_os_error()); | ||
} | ||
} | ||
|
||
Ok(ReplacementGuard { | ||
previous, | ||
std_handle, | ||
}) | ||
} | ||
} | ||
|
||
impl Drop for ReplacementGuard { | ||
fn drop(&mut self) { | ||
unsafe { | ||
// Put previous handle back in to stdin | ||
SetStdHandle(self.std_handle, self.previous); | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[cfg(unix)] | ||
mod imp { | ||
use super::Stdio; | ||
use libc::{close, dup, dup2, STDIN_FILENO, STDOUT_FILENO}; | ||
use std::{fs::File, io::Error, os::fd::AsRawFd}; | ||
pub const IN_DEVICE: &str = "/dev/tty"; | ||
pub const OUT_DEVICE: &str = "/dev/tty"; | ||
pub const NULL_DEVICE: &str = "/dev/null"; | ||
|
||
/// Restores previous stdio when dropped. | ||
pub struct ReplacementGuard { | ||
std_fileno: i32, | ||
previous: i32, | ||
} | ||
|
||
impl ReplacementGuard { | ||
pub(super) fn new(stdio: Stdio, replacement: &mut File) -> Result<ReplacementGuard, Error> { | ||
let std_fileno = match stdio { | ||
Stdio::Stdin => STDIN_FILENO, | ||
Stdio::Stdout => STDOUT_FILENO, | ||
}; | ||
|
||
let previous; | ||
unsafe { | ||
// Duplicate the existing stdin file to a new descriptor | ||
previous = dup(std_fileno); | ||
if previous == -1 { | ||
return Err(std::io::Error::last_os_error()); | ||
} | ||
// Replace stdin with the replacement file | ||
if dup2(replacement.as_raw_fd(), std_fileno) == -1 { | ||
return Err(std::io::Error::last_os_error()); | ||
} | ||
} | ||
|
||
Ok(ReplacementGuard { | ||
previous, | ||
std_fileno, | ||
}) | ||
} | ||
} | ||
|
||
impl Drop for ReplacementGuard { | ||
fn drop(&mut self) { | ||
unsafe { | ||
// Put previous file back in to stdin | ||
dup2(self.previous, self.std_fileno); | ||
// Close the file descriptor we used as a backup | ||
close(self.previous); | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use std::fs::OpenOptions; | ||
use std::io::{Seek, Write}; | ||
|
||
use super::imp::ReplacementGuard; | ||
use super::Stdio; | ||
|
||
#[test] | ||
fn stdin() { | ||
let tempdir = snapbox::path::PathFixture::mutable_temp().unwrap(); | ||
let file = tempdir.path().unwrap().join("stdin"); | ||
let mut file = OpenOptions::new() | ||
.read(true) | ||
.write(true) | ||
.create(true) | ||
.open(file) | ||
.unwrap(); | ||
|
||
writeln!(&mut file, "hello").unwrap(); | ||
file.seek(std::io::SeekFrom::Start(0)).unwrap(); | ||
{ | ||
let _guard = ReplacementGuard::new(Stdio::Stdin, &mut file).unwrap(); | ||
let line = std::io::stdin().lines().next().unwrap().unwrap(); | ||
assert_eq!(line, "hello"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters