mirror of
https://github.com/nushell/nushell.git
synced 2025-05-28 02:21:19 +00:00
<!-- if this PR closes one or more issues, you can automatically link the PR with them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g. - this PR should close https://github.com/nushell/nushell/issues/12874 - fixes https://github.com/nushell/nushell/issues/12874 I want to fix the issue which is induced by the fix for https://github.com/nushell/nushell/issues/12369. after this pr. This pr induced a new error for unix system, in order to show coredump messages you can also mention related issues, PRs or discussions! --> # Description <!-- Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes. Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience. --> - this PR should close https://github.com/nushell/nushell/issues/12874 - fixes https://github.com/nushell/nushell/issues/12874 I want to fix the issue which is induced by the fix for https://github.com/nushell/nushell/issues/12369. after this pr. This pr induced a new error for unix system, in order to show coredump messages # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> after fix for 12874, coredump message is messing, so I want to fix it # Tests + Formatting <!-- Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging)) - `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the tests for the standard library > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` --> # After Submitting <!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->  --------- Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
309 lines
9.6 KiB
Rust
309 lines
9.6 KiB
Rust
use crate::{
|
|
byte_stream::convert_file, process::ExitStatus, ErrSpan, IntoSpanned, ShellError, Span,
|
|
};
|
|
use nu_system::ForegroundChild;
|
|
use os_pipe::PipeReader;
|
|
use std::{
|
|
fmt::Debug,
|
|
io::{self, Read},
|
|
sync::mpsc::{self, Receiver, RecvError, TryRecvError},
|
|
thread,
|
|
};
|
|
|
|
#[derive(Debug)]
|
|
enum ExitStatusFuture {
|
|
Finished(Result<ExitStatus, Box<ShellError>>),
|
|
Running(Receiver<io::Result<ExitStatus>>),
|
|
}
|
|
|
|
impl ExitStatusFuture {
|
|
fn wait(&mut self, span: Span) -> Result<ExitStatus, ShellError> {
|
|
match self {
|
|
ExitStatusFuture::Finished(Ok(status)) => Ok(*status),
|
|
ExitStatusFuture::Finished(Err(err)) => Err(err.as_ref().clone()),
|
|
ExitStatusFuture::Running(receiver) => {
|
|
let code = match receiver.recv() {
|
|
Ok(Ok(status)) => {
|
|
#[cfg(unix)]
|
|
if let ExitStatus::Signaled {
|
|
signal,
|
|
core_dumped: true,
|
|
} = status
|
|
{
|
|
return Err(ShellError::CoredumpErrorSpanned {
|
|
msg: format!("coredump detected. received signal: {signal}"),
|
|
signal,
|
|
span,
|
|
});
|
|
}
|
|
Ok(status)
|
|
}
|
|
Ok(Err(err)) => Err(ShellError::IOErrorSpanned {
|
|
msg: format!("failed to get exit code: {err:?}"),
|
|
span,
|
|
}),
|
|
Err(RecvError) => Err(ShellError::IOErrorSpanned {
|
|
msg: "failed to get exit code".into(),
|
|
span,
|
|
}),
|
|
};
|
|
|
|
*self = ExitStatusFuture::Finished(code.clone().map_err(Box::new));
|
|
|
|
code
|
|
}
|
|
}
|
|
}
|
|
|
|
fn try_wait(&mut self, span: Span) -> Result<Option<ExitStatus>, ShellError> {
|
|
match self {
|
|
ExitStatusFuture::Finished(Ok(code)) => Ok(Some(*code)),
|
|
ExitStatusFuture::Finished(Err(err)) => Err(err.as_ref().clone()),
|
|
ExitStatusFuture::Running(receiver) => {
|
|
let code = match receiver.try_recv() {
|
|
Ok(Ok(status)) => Ok(Some(status)),
|
|
Ok(Err(err)) => Err(ShellError::IOErrorSpanned {
|
|
msg: format!("failed to get exit code: {err:?}"),
|
|
span,
|
|
}),
|
|
Err(TryRecvError::Disconnected) => Err(ShellError::IOErrorSpanned {
|
|
msg: "failed to get exit code".into(),
|
|
span,
|
|
}),
|
|
Err(TryRecvError::Empty) => Ok(None),
|
|
};
|
|
|
|
if let Some(code) = code.clone().transpose() {
|
|
*self = ExitStatusFuture::Finished(code.map_err(Box::new));
|
|
}
|
|
|
|
code
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub enum ChildPipe {
|
|
Pipe(PipeReader),
|
|
Tee(Box<dyn Read + Send + 'static>),
|
|
}
|
|
|
|
impl Debug for ChildPipe {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("ChildPipe").finish()
|
|
}
|
|
}
|
|
|
|
impl From<PipeReader> for ChildPipe {
|
|
fn from(pipe: PipeReader) -> Self {
|
|
Self::Pipe(pipe)
|
|
}
|
|
}
|
|
|
|
impl Read for ChildPipe {
|
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
match self {
|
|
ChildPipe::Pipe(pipe) => pipe.read(buf),
|
|
ChildPipe::Tee(tee) => tee.read(buf),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ChildProcess {
|
|
pub stdout: Option<ChildPipe>,
|
|
pub stderr: Option<ChildPipe>,
|
|
exit_status: ExitStatusFuture,
|
|
span: Span,
|
|
}
|
|
|
|
impl ChildProcess {
|
|
pub fn new(
|
|
mut child: ForegroundChild,
|
|
reader: Option<PipeReader>,
|
|
swap: bool,
|
|
span: Span,
|
|
) -> Result<Self, ShellError> {
|
|
let (stdout, stderr) = if let Some(combined) = reader {
|
|
(Some(combined), None)
|
|
} else {
|
|
let stdout = child.as_mut().stdout.take().map(convert_file);
|
|
let stderr = child.as_mut().stderr.take().map(convert_file);
|
|
|
|
if swap {
|
|
(stderr, stdout)
|
|
} else {
|
|
(stdout, stderr)
|
|
}
|
|
};
|
|
|
|
// Create a thread to wait for the exit status.
|
|
let (exit_status_sender, exit_status) = mpsc::channel();
|
|
|
|
thread::Builder::new()
|
|
.name("exit status waiter".into())
|
|
.spawn(move || exit_status_sender.send(child.wait().map(Into::into)))
|
|
.err_span(span)?;
|
|
|
|
Ok(Self::from_raw(stdout, stderr, Some(exit_status), span))
|
|
}
|
|
|
|
pub fn from_raw(
|
|
stdout: Option<PipeReader>,
|
|
stderr: Option<PipeReader>,
|
|
exit_status: Option<Receiver<io::Result<ExitStatus>>>,
|
|
span: Span,
|
|
) -> Self {
|
|
Self {
|
|
stdout: stdout.map(Into::into),
|
|
stderr: stderr.map(Into::into),
|
|
exit_status: exit_status
|
|
.map(ExitStatusFuture::Running)
|
|
.unwrap_or(ExitStatusFuture::Finished(Ok(ExitStatus::Exited(0)))),
|
|
span,
|
|
}
|
|
}
|
|
|
|
pub fn set_exit_code(&mut self, exit_code: i32) {
|
|
self.exit_status = ExitStatusFuture::Finished(Ok(ExitStatus::Exited(exit_code)));
|
|
}
|
|
|
|
pub fn span(&self) -> Span {
|
|
self.span
|
|
}
|
|
|
|
pub fn into_bytes(mut self) -> Result<Vec<u8>, ShellError> {
|
|
if self.stderr.is_some() {
|
|
debug_assert!(false, "stderr should not exist");
|
|
return Err(ShellError::IOErrorSpanned {
|
|
msg: "internal error".into(),
|
|
span: self.span,
|
|
});
|
|
}
|
|
|
|
let bytes = if let Some(stdout) = self.stdout {
|
|
collect_bytes(stdout).err_span(self.span)?
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
|
|
// TODO: check exit_status
|
|
self.exit_status.wait(self.span)?;
|
|
|
|
Ok(bytes)
|
|
}
|
|
|
|
pub fn wait(mut self) -> Result<ExitStatus, ShellError> {
|
|
if let Some(stdout) = self.stdout.take() {
|
|
let stderr = self
|
|
.stderr
|
|
.take()
|
|
.map(|stderr| {
|
|
thread::Builder::new()
|
|
.name("stderr consumer".into())
|
|
.spawn(move || consume_pipe(stderr))
|
|
})
|
|
.transpose()
|
|
.err_span(self.span)?;
|
|
|
|
let res = consume_pipe(stdout);
|
|
|
|
if let Some(handle) = stderr {
|
|
handle
|
|
.join()
|
|
.map_err(|e| match e.downcast::<io::Error>() {
|
|
Ok(io) => ShellError::from((*io).into_spanned(self.span)),
|
|
Err(err) => ShellError::GenericError {
|
|
error: "Unknown error".into(),
|
|
msg: format!("{err:?}"),
|
|
span: Some(self.span),
|
|
help: None,
|
|
inner: Vec::new(),
|
|
},
|
|
})?
|
|
.err_span(self.span)?;
|
|
}
|
|
|
|
res.err_span(self.span)?;
|
|
} else if let Some(stderr) = self.stderr.take() {
|
|
consume_pipe(stderr).err_span(self.span)?;
|
|
}
|
|
|
|
self.exit_status.wait(self.span)
|
|
}
|
|
|
|
pub fn try_wait(&mut self) -> Result<Option<ExitStatus>, ShellError> {
|
|
self.exit_status.try_wait(self.span)
|
|
}
|
|
|
|
pub fn wait_with_output(mut self) -> Result<ProcessOutput, ShellError> {
|
|
let (stdout, stderr) = if let Some(stdout) = self.stdout {
|
|
let stderr = self
|
|
.stderr
|
|
.map(|stderr| thread::Builder::new().spawn(move || collect_bytes(stderr)))
|
|
.transpose()
|
|
.err_span(self.span)?;
|
|
|
|
let stdout = collect_bytes(stdout).err_span(self.span)?;
|
|
|
|
let stderr = stderr
|
|
.map(|handle| {
|
|
handle.join().map_err(|e| match e.downcast::<io::Error>() {
|
|
Ok(io) => ShellError::from((*io).into_spanned(self.span)),
|
|
Err(err) => ShellError::GenericError {
|
|
error: "Unknown error".into(),
|
|
msg: format!("{err:?}"),
|
|
span: Some(self.span),
|
|
help: None,
|
|
inner: Vec::new(),
|
|
},
|
|
})
|
|
})
|
|
.transpose()?
|
|
.transpose()
|
|
.err_span(self.span)?;
|
|
|
|
(Some(stdout), stderr)
|
|
} else {
|
|
let stderr = self
|
|
.stderr
|
|
.map(collect_bytes)
|
|
.transpose()
|
|
.err_span(self.span)?;
|
|
|
|
(None, stderr)
|
|
};
|
|
|
|
let exit_status = self.exit_status.wait(self.span)?;
|
|
|
|
Ok(ProcessOutput {
|
|
stdout,
|
|
stderr,
|
|
exit_status,
|
|
})
|
|
}
|
|
}
|
|
|
|
fn collect_bytes(pipe: ChildPipe) -> io::Result<Vec<u8>> {
|
|
let mut buf = Vec::new();
|
|
match pipe {
|
|
ChildPipe::Pipe(mut pipe) => pipe.read_to_end(&mut buf),
|
|
ChildPipe::Tee(mut tee) => tee.read_to_end(&mut buf),
|
|
}?;
|
|
Ok(buf)
|
|
}
|
|
|
|
fn consume_pipe(pipe: ChildPipe) -> io::Result<()> {
|
|
match pipe {
|
|
ChildPipe::Pipe(mut pipe) => io::copy(&mut pipe, &mut io::sink()),
|
|
ChildPipe::Tee(mut tee) => io::copy(&mut tee, &mut io::sink()),
|
|
}?;
|
|
Ok(())
|
|
}
|
|
|
|
pub struct ProcessOutput {
|
|
pub stdout: Option<Vec<u8>>,
|
|
pub stderr: Option<Vec<u8>>,
|
|
pub exit_status: ExitStatus,
|
|
}
|