Opublikowane źródło
Pobierz źródło ZIP
CH-J Tools
Przeglądaj katalogi i pliki dla konkretnego wydania aplikacji.
CH-J / Server Manager
1704 pliki
0.1.2-beta
Dodano: · Built:
1
//! Exec channels over an existing SSH connection (port of `_execFixed`/`_execStream`).2
//! Output is decoded incrementally, bounded by an output limit and a deadline, and the3
//! channel is cancelled when the session is lost. No API accepts commands from the UI:4
//! callers are Core services with fixed scripts.6
use crate::core::error::{CoreError, CoreResult};7
use crate::core::ssh::utf8::Utf8Decoder;8
use russh::client::Msg;9
use russh::{ChannelMsg, ChannelReadHalf, ChannelWriteHalf};10
use std::time::Duration;11
use tokio::sync::watch;12
use tokio::time::Instant;14
pub const MAX_COMMAND_OUTPUT: usize = 512 * 1024;16
#[derive(Debug, Clone, PartialEq, Eq)]17
pub enum ExecEvent {18
Stdout,19
Stderr,20
Done,21
}23
#[derive(Debug, Clone)]24
pub struct ExecResult {25
pub code: Option<i64>,26
pub signal: Option<String>,27
pub stdout: String,28
pub stderr: String,29
}31
pub struct ExecChannel {32
read: ChannelReadHalf,33
write: ChannelWriteHalf<Msg>,34
pub stdout: String,35
pub stderr: String,36
decode_out: Utf8Decoder,37
decode_err: Utf8Decoder,38
received: usize,39
limit: usize,40
deadline: Instant,41
lost: watch::Receiver<bool>,42
code: Option<i64>,43
signal: Option<String>,44
done: bool,45
eof_sent: bool,46
}48
impl ExecChannel {49
pub fn new(read: ChannelReadHalf, write: ChannelWriteHalf<Msg>, timeout: Duration, limit: usize, lost: watch::Receiver<bool>) -> Self {50
Self {51
read,52
write,53
stdout: String::new(),54
stderr: String::new(),55
decode_out: Utf8Decoder::default(),56
decode_err: Utf8Decoder::default(),57
received: 0,58
limit,59
deadline: Instant::now() + timeout,60
lost,61
code: None,62
signal: None,63
done: false,64
eof_sent: false,65
}66
}68
pub async fn write(&self, data: &[u8]) {69
if self.done || self.eof_sent {70
return;71
}72
let _ = self.write.data_bytes(data.to_vec()).await;73
}75
pub async fn eof(&mut self) {76
if !self.eof_sent && !self.done {77
self.eof_sent = true;78
let _ = self.write.eof().await;79
}80
}82
pub async fn close(&self) {83
let _ = self.write.close().await;84
}86
pub fn is_done(&self) -> bool {87
self.done88
}90
/// Waits for the next output chunk (appended to `stdout`/`stderr`) or the end of the channel.91
pub async fn next(&mut self) -> CoreResult<ExecEvent> {92
if self.done {93
return Ok(ExecEvent::Done);94
}95
if *self.lost.borrow() {96
return Err(CoreError::new("SESSION_NOT_CONNECTED", "The SSH connection was lost."));97
}98
loop {99
let message = tokio::select! {100
_ = tokio::time::sleep_until(self.deadline) => {101
self.close().await;102
return Err(CoreError::new("REMOTE_COMMAND_TIMEOUT", "The remote command timed out."));103
}104
changed = self.lost.changed() => {105
if changed.is_err() || *self.lost.borrow() {106
return Err(CoreError::new("SESSION_NOT_CONNECTED", "The SSH connection was lost."));107
}108
continue;109
}110
message = self.read.wait() => message,111
};112
match message {113
Some(ChannelMsg::Data { data }) => {114
self.account(data.len()).await?;115
let text = self.decode_out.write(&data);116
self.stdout.push_str(&text);117
return Ok(ExecEvent::Stdout);118
}119
Some(ChannelMsg::ExtendedData { data, ext }) => {120
self.account(data.len()).await?;121
if ext == 1 {122
let text = self.decode_err.write(&data);123
self.stderr.push_str(&text);124
return Ok(ExecEvent::Stderr);125
}126
}127
Some(ChannelMsg::ExitStatus { exit_status }) => self.code = Some(exit_status as i64),128
Some(ChannelMsg::ExitSignal { signal_name, .. }) => self.signal = Some(format!("{signal_name:?}")),129
Some(ChannelMsg::Close) | None => {130
self.done = true;131
let rest = self.decode_out.end();132
self.stdout.push_str(&rest);133
let rest = self.decode_err.end();134
self.stderr.push_str(&rest);135
return Ok(ExecEvent::Done);136
}137
Some(ChannelMsg::OpenFailure(reason)) => {138
return Err(CoreError::new("REMOTE_COMMAND_FAILED", format!("The remote channel could not be opened: {reason:?}")));139
}140
Some(_) => {}141
}142
}143
}145
async fn account(&mut self, length: usize) -> CoreResult<()> {146
self.received += length;147
if self.received > self.limit {148
self.close().await;149
return Err(CoreError::new("REMOTE_OUTPUT_TOO_LARGE", "The remote response is too large."));150
}151
Ok(())152
}154
/// Reads until the channel closes; returns the exit code instead of failing on non-zero.155
pub async fn finish(mut self) -> CoreResult<ExecResult> {156
while self.next().await? != ExecEvent::Done {}157
Ok(ExecResult { code: self.code, signal: self.signal.clone(), stdout: std::mem::take(&mut self.stdout), stderr: std::mem::take(&mut self.stderr) })158
}159
}SHA-256
3e05e5be6436c67a8d11152dd826bf8699f7fee2c807834b0a965ad3705afd2bCH-J Proprietary Software License 1.14 · Źródło jest dostarczane pod CH-J Proprietary Software License 1.14. Jego dostępność nie zmienia warunków licencji ani nie przyznaje dodatkowych praw.
SHA-256
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f