Access a246a19387
fix: coredump without any messages (#13034)
<!--
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.
-->


![image](https://github.com/nushell/nushell/assets/60290287/6d8ab756-3031-4212-a5f5-5f71be3857f9)

---------

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2024-06-07 08:02:52 -05:00

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,
}