summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2023-06-09 13:24:39 -0400
committerGitHub <noreply@github.com>2023-06-09 13:24:39 -0400
commitff690b0ab44d1d8d8e5ecbc320a5bf752d5feacc (patch)
tree8c96368aabab34082a4233320917bcd4e0aa3a8e
parent748a102919a35d7f99db35f558e87e0904b1890b (diff)
chore: fix flaky stdio_streams_are_locked_in_permission_prompt (#19443)
-rw-r--r--cli/tests/integration/run_tests.rs1
-rw-r--r--cli/tests/testdata/run/stdio_streams_are_locked_in_permission_prompt/worker.js1
-rw-r--r--runtime/permissions/prompter.rs88
3 files changed, 56 insertions, 34 deletions
diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs
index 2accd5444..3fa2f9896 100644
--- a/cli/tests/integration/run_tests.rs
+++ b/cli/tests/integration/run_tests.rs
@@ -4370,6 +4370,7 @@ fn stdio_streams_are_locked_in_permission_prompt() {
std::thread::sleep(Duration::from_millis(50)); // give the other thread some time to output
console.write_line_raw("invalid");
console.expect("Unrecognized option.");
+ console.expect("Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all write permissions)");
console.write_line_raw("y");
console.expect("Granted write access to");
diff --git a/cli/tests/testdata/run/stdio_streams_are_locked_in_permission_prompt/worker.js b/cli/tests/testdata/run/stdio_streams_are_locked_in_permission_prompt/worker.js
index 6d67c51a2..1e5a632d3 100644
--- a/cli/tests/testdata/run/stdio_streams_are_locked_in_permission_prompt/worker.js
+++ b/cli/tests/testdata/run/stdio_streams_are_locked_in_permission_prompt/worker.js
@@ -1,2 +1 @@
-console.clear();
console.log("Are you sure you want to continue?");
diff --git a/runtime/permissions/prompter.rs b/runtime/permissions/prompter.rs
index 502771636..93a7c9686 100644
--- a/runtime/permissions/prompter.rs
+++ b/runtime/permissions/prompter.rs
@@ -5,6 +5,10 @@ use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex;
use once_cell::sync::Lazy;
use std::fmt::Write;
+use std::io::BufRead;
+use std::io::StderrLock;
+use std::io::StdinLock;
+use std::io::Write as IoWrite;
/// Helper function to strip ansi codes and ASCII control characters.
fn strip_ansi_codes_and_ascii_control(s: &str) -> std::borrow::Cow<str> {
@@ -85,7 +89,10 @@ impl PermissionPrompter for TtyPrompter {
};
#[cfg(unix)]
- fn clear_stdin() -> Result<(), AnyError> {
+ fn clear_stdin(
+ _stdin_lock: &mut StdinLock,
+ _stderr_lock: &mut StderrLock,
+ ) -> Result<(), AnyError> {
// TODO(bartlomieju):
#[allow(clippy::undocumented_unsafe_blocks)]
let r = unsafe { libc::tcflush(0, libc::TCIFLUSH) };
@@ -94,7 +101,10 @@ impl PermissionPrompter for TtyPrompter {
}
#[cfg(not(unix))]
- fn clear_stdin() -> Result<(), AnyError> {
+ fn clear_stdin(
+ stdin_lock: &mut StdinLock,
+ stderr_lock: &mut StderrLock,
+ ) -> Result<(), AnyError> {
use deno_core::anyhow::bail;
use winapi::shared::minwindef::TRUE;
use winapi::shared::minwindef::UINT;
@@ -118,11 +128,11 @@ impl PermissionPrompter for TtyPrompter {
// emulate an enter key press to clear any line buffered console characters
emulate_enter_key_press(stdin)?;
// read the buffered line or enter key press
- read_stdin_line()?;
+ read_stdin_line(stdin_lock)?;
// check if our emulated key press was executed
if is_input_buffer_empty(stdin)? {
// if so, move the cursor up to prevent a blank line
- move_cursor_up()?;
+ move_cursor_up(stderr_lock)?;
} else {
// the emulated key press is still pending, so a buffered line was read
// and we can flush the emulated key press
@@ -181,37 +191,36 @@ impl PermissionPrompter for TtyPrompter {
Ok(events_read == 0)
}
- fn move_cursor_up() -> Result<(), AnyError> {
- use std::io::Write;
- write!(std::io::stderr(), "\x1B[1A")?;
+ fn move_cursor_up(stderr_lock: &mut StderrLock) -> Result<(), AnyError> {
+ write!(stderr_lock, "\x1B[1A")?;
Ok(())
}
- fn read_stdin_line() -> Result<(), AnyError> {
+ fn read_stdin_line(stdin_lock: &mut StdinLock) -> Result<(), AnyError> {
let mut input = String::new();
- let stdin = std::io::stdin();
- stdin.read_line(&mut input)?;
+ stdin_lock.read_line(&mut input)?;
Ok(())
}
}
// Clear n-lines in terminal and move cursor to the beginning of the line.
- fn clear_n_lines(n: usize) {
- eprint!("\x1B[{n}A\x1B[0J");
+ fn clear_n_lines(stderr_lock: &mut StderrLock, n: usize) {
+ write!(stderr_lock, "\x1B[{n}A\x1B[0J").unwrap();
}
+ // Lock stdio streams, so no other output is written while the prompt is
+ // displayed.
+ let stdout_lock = std::io::stdout().lock();
+ let mut stderr_lock = std::io::stderr().lock();
+ let mut stdin_lock = std::io::stdin().lock();
+
// For security reasons we must consume everything in stdin so that previously
- // buffered data cannot effect the prompt.
- if let Err(err) = clear_stdin() {
+ // buffered data cannot affect the prompt.
+ if let Err(err) = clear_stdin(&mut stdin_lock, &mut stderr_lock) {
eprintln!("Error clearing stdin for permission prompt. {err:#}");
return PromptResponse::Deny; // don't grant permission if this fails
}
- // Lock stdio streams, so no other output is written while the prompt is
- // displayed.
- let _stdout_guard = std::io::stdout().lock();
- let _stderr_guard = std::io::stderr().lock();
-
let message = strip_ansi_codes_and_ascii_control(message);
let name = strip_ansi_codes_and_ascii_control(name);
let api_name = api_name.map(strip_ansi_codes_and_ascii_control);
@@ -238,13 +247,12 @@ impl PermissionPrompter for TtyPrompter {
write!(&mut output, "└ {}", colors::bold("Allow?")).unwrap();
write!(&mut output, " {opts} > ").unwrap();
- eprint!("{}", output);
+ stderr_lock.write_all(output.as_bytes()).unwrap();
}
let value = loop {
let mut input = String::new();
- let stdin = std::io::stdin();
- let result = stdin.read_line(&mut input);
+ let result = stdin_lock.read_line(&mut input);
if result.is_err() {
break PromptResponse::Deny;
};
@@ -254,34 +262,48 @@ impl PermissionPrompter for TtyPrompter {
};
match ch {
'y' | 'Y' => {
- clear_n_lines(if api_name.is_some() { 4 } else { 3 });
+ clear_n_lines(
+ &mut stderr_lock,
+ if api_name.is_some() { 4 } else { 3 },
+ );
let msg = format!("Granted {message}.");
- eprintln!("✅ {}", colors::bold(&msg));
+ writeln!(stderr_lock, "✅ {}", colors::bold(&msg)).unwrap();
break PromptResponse::Allow;
}
'n' | 'N' => {
- clear_n_lines(if api_name.is_some() { 4 } else { 3 });
+ clear_n_lines(
+ &mut stderr_lock,
+ if api_name.is_some() { 4 } else { 3 },
+ );
let msg = format!("Denied {message}.");
- eprintln!("❌ {}", colors::bold(&msg));
+ writeln!(stderr_lock, "❌ {}", colors::bold(&msg)).unwrap();
break PromptResponse::Deny;
}
'A' if is_unary => {
- clear_n_lines(if api_name.is_some() { 4 } else { 3 });
+ clear_n_lines(
+ &mut stderr_lock,
+ if api_name.is_some() { 4 } else { 3 },
+ );
let msg = format!("Granted all {name} access.");
- eprintln!("✅ {}", colors::bold(&msg));
+ writeln!(stderr_lock, "✅ {}", colors::bold(&msg)).unwrap();
break PromptResponse::AllowAll;
}
_ => {
// If we don't get a recognized option try again.
- clear_n_lines(1);
- eprint!("└ {}", colors::bold("Unrecognized option. Allow?"));
- eprint!(" {opts} > ");
+ clear_n_lines(&mut stderr_lock, 1);
+ write!(
+ stderr_lock,
+ "└ {} {opts} > ",
+ colors::bold("Unrecognized option. Allow?")
+ )
+ .unwrap();
}
};
};
- drop(_stdout_guard);
- drop(_stderr_guard);
+ drop(stdout_lock);
+ drop(stderr_lock);
+ drop(stdin_lock);
value
}