// Copyright (c) 2023 Huawei Device Co., Ltd. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::io; use std::io::IoSlice; #[cfg(unix)] use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd}; use std::pin::Pin; use std::process::{Child as StdChild, ExitStatus, Output}; use std::task::{Context, Poll}; use crate::io::{AsyncRead, AsyncReadExt, AsyncWrite, ReadBuf}; use crate::process::sys::ChildStdio; #[derive(Debug)] pub(crate) enum ChildState { Pending(super::sys::Child), Ready(ExitStatus), } /// Handle of child process #[derive(Debug)] pub struct Child { state: ChildState, // Weather kill the child when drop kill_on_drop: bool, /// Options of stdin stdin: Option<ChildStdin>, /// Options of stdout stdout: Option<ChildStdout>, /// Options of stderr stderr: Option<ChildStderr>, } impl Child { pub(crate) fn new( child: StdChild, kill_on_drop: bool, stdin: Option<ChildStdin>, stdout: Option<ChildStdout>, stderr: Option<ChildStderr>, ) -> io::Result<Self> { Ok(Self { state: ChildState::Pending(super::sys::Child::new(child)?), kill_on_drop, stdin, stdout, stderr, }) } /// Gets the OS-assigned process identifier associated with this child. /// /// If the child process is exited, it returns `None`. /// /// # Example /// /// ```no_run /// use ylong_runtime::process::Command; /// /// fn command() { /// let mut child = Command::new("ls") /// .spawn() /// .expect("ls command failed to start"); /// let _id = child.id().expect("the child process is exited"); /// } /// ``` pub fn id(&self) -> Option<u32> { match &self.state { ChildState::Pending(child) => Some(child.id()), ChildState::Ready(_) => None, } } /// Takes the stdin of this child, remain `None`. /// /// # Example /// /// ```no_run /// use std::process::Stdio; /// /// use ylong_runtime::process::Command; /// /// fn command() { /// let mut child = Command::new("ls") /// .stdin(Stdio::piped()) /// .spawn() /// .expect("ls command failed to start"); /// let _stdin = child.take_stdin().unwrap(); /// } /// ``` pub fn take_stdin(&mut self) -> Option<ChildStdin> { self.stdin.take() } /// Takes the stdout of this child, remain `None`. /// /// # Example /// /// ```no_run /// use std::process::Stdio; /// /// use ylong_runtime::process::Command; /// /// fn command() { /// let mut child = Command::new("ls") /// .stdout(Stdio::piped()) /// .spawn() /// .expect("ls command failed to start"); /// let _stdout = child.take_stdout().unwrap(); /// } /// ``` pub fn take_stdout(&mut self) -> Option<ChildStdout> { self.stdout.take() } /// Takes the stderr of this child, remain `None`. /// /// # Example /// /// ```no_run /// use std::process::Stdio; /// /// use ylong_runtime::process::Command; /// /// fn command() { /// let mut child = Command::new("ls") /// .stderr(Stdio::piped()) /// .spawn() /// .expect("ls command failed to start"); /// let _stderr = child.take_stderr().unwrap(); /// } /// ``` pub fn take_stderr(&mut self) -> Option<ChildStderr> { self.stderr.take() } /// Tries to kill the child process, but doesn't wait for it to take effect. /// /// On Unix, this is equivalent to sending a SIGKILL. User should ensure /// either `child.wait().await` or `child.try_wait()` is invoked /// successfully, otherwise the child process will be a Zombie Process. /// /// # Example /// /// ```no_run /// use std::io; /// use std::process::ExitStatus; /// /// use ylong_runtime::process::Command; /// /// async fn command() -> io::Result<ExitStatus> { /// let mut child = Command::new("ls") /// .spawn() /// .expect("ls command failed to start"); /// child.start_kill().expect("failed to start_kill"); /// child.wait().await /// } /// ``` pub fn start_kill(&mut self) -> io::Result<()> { match &mut self.state { ChildState::Pending(ref mut child) => { let res = child.kill(); if res.is_ok() { self.kill_on_drop = false; } res } ChildState::Ready(_) => Err(io::Error::new( io::ErrorKind::InvalidInput, "can not kill an exited process", )), } } /// Kills the child process and wait(). /// /// # Example /// /// ```no_run /// use ylong_runtime::process::Command; /// /// async fn command() { /// let mut child = Command::new("ls") /// .spawn() /// .expect("ls command failed to start"); /// child.kill().await.expect("failed to kill"); /// } /// ``` pub async fn kill(&mut self) -> io::Result<()> { self.start_kill()?; self.wait().await.map(|_| ()) } /// Waits for the child process to exit, and return the status. /// /// # Example /// /// ```no_run /// use ylong_runtime::process::Command; /// /// async fn command() { /// let mut child = Command::new("ls") /// .spawn() /// .expect("ls command failed to start"); /// let res = child.wait().await.expect("failed to kill"); /// } /// ``` pub async fn wait(&mut self) -> io::Result<ExitStatus> { // take stdin to avoid deadlock. drop(self.take_stdin()); match &mut self.state { ChildState::Pending(child) => { let res = child.await; if let Ok(exit_status) = res { self.kill_on_drop = false; self.state = ChildState::Ready(exit_status); } res } ChildState::Ready(exit_status) => Ok(*exit_status), } } /// Tries to get th exit status of the child if it is already exited. /// /// # Example /// /// ```no_run /// use ylong_runtime::process::Command; /// /// async fn command() { /// let mut child = Command::new("ls") /// .spawn() /// .expect("ls command failed to start"); /// let res = child.try_wait().expect("failed to try_wait!"); /// } /// ``` pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> { match &mut self.state { ChildState::Pending(child) => { let res = child.try_wait(); if let Ok(Some(exit_status)) = res { // the child is exited, no need for a kill self.kill_on_drop = false; self.state = ChildState::Ready(exit_status); } res } ChildState::Ready(exit_status) => Ok(Some(*exit_status)), } } /// Returns the `Output` with exit status, stdout and stderr of child /// process. /// /// # Example /// /// ```no_run /// use ylong_runtime::process::Command; /// /// async fn command() { /// let mut child = Command::new("ls") /// .spawn() /// .expect("ls command failed to start"); /// let res = child.output_wait().await.expect("failed to output_wait"); /// } /// ``` pub async fn output_wait(&mut self) -> io::Result<Output> { async fn read_to_end<T: AsyncRead + Unpin>(io: &mut Option<T>) -> io::Result<Vec<u8>> { let mut vec = Vec::new(); if let Some(io) = io.as_mut() { io.read_to_end(&mut vec).await?; } Ok(vec) } let mut child_stdout = self.take_stdout(); let mut child_stderr = self.take_stderr(); let fut1 = self.wait(); let fut2 = read_to_end(&mut child_stdout); let fut3 = read_to_end(&mut child_stderr); let (status, stdout, stderr) = crate::process::try_join3::try_join3(fut1, fut2, fut3).await?; drop(child_stdout); drop(child_stderr); Ok(Output { status, stdout, stderr, }) } } impl Drop for Child { fn drop(&mut self) { if self.kill_on_drop { if let ChildState::Pending(child) = &mut self.state { let _ = child.kill(); } } } } /// Standard input stream of Child which implements `AsyncWrite` trait #[derive(Debug)] pub struct ChildStdin { inner: ChildStdio, } /// Standard output stream of Child which implements `AsyncRead` trait #[derive(Debug)] pub struct ChildStdout { inner: ChildStdio, } /// Standard err stream of Child which implements `AsyncRead` trait #[derive(Debug)] pub struct ChildStderr { inner: ChildStdio, } impl AsyncWrite for ChildStdin { fn poll_write( mut self: Pin<&mut Self>, ctx: &mut Context<'_>, buffer: &[u8], ) -> Poll<io::Result<usize>> { Pin::new(&mut self.inner).poll_write(ctx, buffer) } fn poll_write_vectored( mut self: Pin<&mut Self>, ctx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll<io::Result<usize>> { Pin::new(&mut self.inner).poll_write_vectored(ctx, bufs) } fn is_write_vectored(&self) -> bool { self.inner.is_write_vectored() } fn poll_flush(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> { Pin::new(&mut self.inner).poll_flush(ctx) } fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> { Pin::new(&mut self.inner).poll_shutdown(ctx) } } impl AsyncRead for ChildStdout { fn poll_read( mut self: Pin<&mut Self>, ctx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll<io::Result<()>> { Pin::new(&mut self.inner).poll_read(ctx, buf) } } impl AsyncRead for ChildStderr { fn poll_read( mut self: Pin<&mut Self>, ctx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll<io::Result<()>> { Pin::new(&mut self.inner).poll_read(ctx, buf) } } macro_rules! impl_common_traits { ($ident:ident) => { impl $ident { pub(crate) fn new(inner: ChildStdio) -> Self { Self { inner } } /// Creates an async `ChildStd` from `std::process::ChildStd` pub fn from_std(inner: std::process::$ident) -> io::Result<Self> { super::sys::stdio(inner).map(|inner| Self { inner }) } /// Convert to OwnedFd #[cfg(unix)] pub fn into_owned_fd(self) -> io::Result<OwnedFd> { self.inner.into_owned_fd() } } impl TryInto<std::process::Stdio> for $ident { type Error = io::Error; fn try_into(self) -> Result<std::process::Stdio, Self::Error> { super::sys::to_stdio(self.inner) } } #[cfg(unix)] impl AsRawFd for $ident { fn as_raw_fd(&self) -> RawFd { self.inner.as_raw_fd() } } #[cfg(unix)] impl AsFd for $ident { fn as_fd(&self) -> BorrowedFd<'_> { self.inner.as_fd() } } }; } impl_common_traits!(ChildStdin); impl_common_traits!(ChildStdout); impl_common_traits!(ChildStderr); #[cfg(test)] mod test { use std::os::fd::{AsFd, AsRawFd}; use std::process::Stdio; use crate::process::{Child, ChildStderr, ChildStdin, ChildStdout, Command}; /// UT test cases for Child. /// /// # Brief /// 1. Create a ylong_runtime's `Child` with std `Child`. /// 2. Check stdin/stdout/stderr. /// 3. Call `wait()` to exit Child. #[test] fn ut_process_child_new_test() { let mut command = std::process::Command::new("echo"); let std_child = command.spawn().unwrap(); let handle = crate::spawn(async move { let mut child = Child::new(std_child, false, None, None, None).unwrap(); assert!(!child.kill_on_drop); assert!(child.stdin.is_none()); assert!(child.stdout.is_none()); assert!(child.stderr.is_none()); assert!(child.id().is_some()); let status = child.wait().await.unwrap(); assert!(status.success()); }); crate::block_on(handle).unwrap(); } /// UT test cases for Child. /// /// # Brief /// 1. Create a `Command` with arg. /// 2. Use `spawn()` create a child handle /// 3. Use `try_wait()` waiting result until the child handle is ok. #[test] fn ut_process_try_wait_test() { let mut command = std::process::Command::new("echo"); let std_child = command.spawn().unwrap(); let handle = crate::spawn(async { let mut child = Child::new(std_child, false, None, None, None).unwrap(); loop { if child.try_wait().unwrap().is_some() { break; } } assert!(child.try_wait().unwrap().is_some()); }); crate::block_on(handle).unwrap(); } /// UT test cases for stdio. /// /// # Brief /// 1. Create a `Command` with arg. /// 2. Use `spawn()` create a child handle /// 3. Use `wait()` waiting result. #[test] fn ut_process_stdio_test() { let handle = crate::spawn(async { let mut command = Command::new("echo"); command .arg("Hello, world!") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); let mut child = command.spawn().unwrap(); let child_stdin = child.take_stdin().unwrap(); assert!(child_stdin.into_owned_fd().is_ok()); let child_stdout = child.take_stdout().unwrap(); assert!(child_stdout.into_owned_fd().is_ok()); let child_stderr = child.take_stderr().unwrap(); assert!(child_stderr.into_owned_fd().is_ok()); drop(child); let mut child = command.spawn().unwrap(); let child_stdin = child.take_stdin().unwrap(); assert!(child_stdin.as_fd().as_raw_fd() >= 0); assert!(child_stdin.as_raw_fd() >= 0); assert!(TryInto::<Stdio>::try_into(child_stdin).is_ok()); let child_stdout = child.take_stdout().unwrap(); assert!(child_stdout.as_fd().as_raw_fd() >= 0); assert!(child_stdout.as_raw_fd() >= 0); assert!(TryInto::<Stdio>::try_into(child_stdout).is_ok()); let child_stderr = child.take_stderr().unwrap(); assert!(child_stderr.as_fd().as_raw_fd() >= 0); assert!(child_stderr.as_raw_fd() >= 0); assert!(TryInto::<Stdio>::try_into(child_stderr).is_ok()); drop(child); }); crate::block_on(handle).unwrap(); } /// UT test cases for ChildStd. /// /// # Brief /// 1. Create a `std::process::Command`. /// 2. Use `spawn()` create a child handle /// 3. Use `from_std()` to convert std to ylong_runtime::process::ChildStd. #[test] fn ut_process_child_stdio_convert_test() { let mut command = std::process::Command::new("echo"); command .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); let mut child = command.spawn().unwrap(); let handle = crate::spawn(async move { let stdin = child.stdin.take().unwrap(); assert!(ChildStdin::from_std(stdin).is_ok()); let stdout = child.stdout.take().unwrap(); assert!(ChildStdout::from_std(stdout).is_ok()); let stderr = child.stderr.take().unwrap(); assert!(ChildStderr::from_std(stderr).is_ok()); }); crate::block_on(handle).unwrap(); } }