russh_process/
lib.rs

1#![doc = include_str!("../README.md")]
2use core::pin::Pin;
3use core::task::{Context, Poll};
4use russh::{Channel, ChannelId, ChannelMsg};
5use std::io;
6use tokio::io::{AsyncRead, AsyncWrite, ReadBuf, ReadHalf, SimplexStream, WriteHalf};
7use tokio::task::JoinHandle;
8
9mod child;
10/// The extension for
11/// [`russh::client`](https://docs.rs/russh/latest/russh/client/index.html)
12pub mod client;
13pub use crate::child::{Child, ChildStderr, ChildStdin, ChildStdout, Output};
14use crate::child::ChildImp;
15
16#[derive(Debug, thiserror::Error)]
17#[error("{0}")]
18pub enum Error {
19    IoError(#[from] io::Error),
20    Russh(#[from] russh::Error),
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct ExitStatus {
25    pub(crate) inner: ExitStatusImp,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub(crate) enum ExitStatusImp {
30    Code(u32),
31    Processing,
32}
33
34impl ExitStatus {
35    /// Returns true if the command was successful.
36    pub fn success(&self) -> bool {
37        match &self.inner {
38            ExitStatusImp::Code(code) => *code == 0,
39            ExitStatusImp::Processing => false,
40        }
41    }
42
43    /// Retrieves the exit code if available.
44    /// Returns `None` if the process is still in progress.
45    pub fn code(&self) -> Option<u32> {
46        match &self.inner {
47            ExitStatusImp::Code(code) => Some(*code),
48            ExitStatusImp::Processing => None,
49        }
50    }
51}
52
53/// Represents a command to be executed over an SSH channel.
54///
55/// Mimics the behavior of [`std::process::Command`].
56/// Encapsulates the SSH channel and the command to be executed.
57#[derive(Debug)]
58pub struct Command<S>
59where
60    S: From<(ChannelId, ChannelMsg)> + Send + Sync + 'static,
61{
62    inner: Channel<S>,
63    command: Vec<u8>,
64}
65
66impl<S> Command<S>
67where
68    S: From<(ChannelId, ChannelMsg)> + Send + Sync + 'static,
69{
70    pub fn from_channel<A: Into<Vec<u8>>>(inner: Channel<S>, command: A) -> Self {
71        let command = command.into();
72        Self { inner, command }
73    }
74}
75
76
77impl<S> Command<S>
78where
79    S: From<(ChannelId, ChannelMsg)> + Send + Sync + 'static,
80{
81    pub async fn spawn(self) -> Result<Child, Error> {
82        self.inner.exec(true, self.command).await?;
83        use tokio::io::simplex;
84        let stdin_buf_size = 4096;
85        let stdout_buf_size = 4096;
86        let stderr_buf_size = 4096;
87
88        let (stdin_rx, stdin_tx) = simplex(stdin_buf_size);
89        let (stdout_rx, stdout_tx) = simplex(stdout_buf_size);
90        let (stderr_rx, stderr_tx) = simplex(stderr_buf_size);
91        let stdin = ChildStdin { inner: stdin_tx };
92        let stdout = ChildStdout { inner: stdout_rx };
93        let stderr = ChildStderr { inner: stderr_rx };
94
95        let imp = ChildImp {
96            channel: self.inner,
97            stdin_rx,
98            stdout_tx,
99            stderr_tx,
100        };
101
102        let handle = tokio::spawn(async move { imp.wait().await });
103
104        Ok(Child {
105            stdin: Some(stdin),
106            stdout: Some(stdout),
107            stderr: Some(stderr),
108            handle,
109        })
110    }
111
112    pub async fn output(self) -> Result<Output, Error> {
113        let child = self.spawn().await?;
114        Ok(child.wait_with_output().await?)
115    }
116}