Fix exit-status: send SSH_MSG_CHANNEL_REQUEST exit-status per RFC 4254 §6.10
Some checks failed
Test / build (push) Has been cancelled
Test / test (push) Has been cancelled

This commit is contained in:
Warren
2026-06-20 15:47:07 +08:00
parent e0e145e277
commit 8bcda75f83
2 changed files with 66 additions and 5 deletions

View File

@@ -176,6 +176,7 @@ impl ChannelManager {
scp_handler: None,
rsync_handler: None,
exec_process: None, // Phase 14: 交互式exec
exit_status: None, // ⭐⭐⭐⭐⭐ exit status from child process
sftp_input_buffer: Vec::new(), // ⭐⭐⭐⭐⭐ Phase 14.2修复SFTP packet累积
scp_input_buffer: Vec::new(), // ⭐⭐⭐⭐⭐ Phase 14.4修复SCP packet累积
scp_state: ScpState::Idle, // ⭐⭐⭐⭐⭐ Phase 8.3: SCP state machine
@@ -254,6 +255,7 @@ impl ChannelManager {
scp_handler: None,
rsync_handler: None,
exec_process: None,
exit_status: None, // ⭐⭐⭐⭐⭐ exit status from child process
sftp_input_buffer: Vec::new(),
scp_input_buffer: Vec::new(),
scp_state: ScpState::Idle, // ⭐⭐⭐⭐⭐ Phase 8.3: SCP state machine
@@ -329,6 +331,7 @@ impl ChannelManager {
scp_handler: None,
rsync_handler: None,
exec_process: None, // Phase 14: 交互式exec
exit_status: None, // ⭐⭐⭐⭐⭐ exit status from child process
sftp_input_buffer: Vec::new(), // ⭐⭐⭐⭐⭐ Phase 14.2修复
scp_input_buffer: Vec::new(), // ⭐⭐⭐⭐⭐ Phase 14.4修复
scp_state: ScpState::Idle, // ⭐⭐⭐⭐⭐ Phase 8.3: SCP state machine
@@ -1303,6 +1306,19 @@ impl ChannelManager {
/// ⭐⭐⭐⭐⭐ 关键:非阻塞读取数据,不等待子进程完成
/// ⭐⭐⭐⭐⭐ Phase 14.2: 处理child exited发送EOF + CLOSE
/// 参考OpenSSH session.c: do_exec_no_pty()
/// Build SSH_MSG_CHANNEL_REQUEST "exit-status" (RFC 4254 §6.10)
pub fn build_channel_exit_status(&self, channel: u32, exit_code: u32) -> Result<SshPacket> {
let mut payload = Vec::new();
payload.write_u8(PacketType::SSH_MSG_CHANNEL_REQUEST as u8)?;
payload.write_u32::<BigEndian>(channel)?;
let name = "exit-status";
payload.write_u32::<BigEndian>(name.len() as u32)?;
payload.write_all(name.as_bytes())?;
payload.write_u8(0)?; // FALSE (want_reply)
payload.write_u32::<BigEndian>(exit_code)?;
Ok(SshPacket::new(payload))
}
pub fn handle_child_exited(&mut self) -> Result<Vec<SshPacket>> {
// 1. 收集需要处理的channel IDs (exec_process OR rsync_handler)
let channel_ids: Vec<u32> = self
@@ -1320,6 +1336,14 @@ impl ChannelManager {
// 2. 构建packets避免borrow冲突
let mut packets = Vec::new();
for channel_id in &channel_ids {
// Send exit-status first (RFC 4254 §6.10)
let exit_code = self.channels.get(channel_id)
.and_then(|c| c.exit_status)
.unwrap_or(255);
let exit_packet = self.build_channel_exit_status(*channel_id, exit_code)?;
packets.push(exit_packet);
// Then EOF + CLOSE
let eof_packet = self.build_channel_eof(*channel_id)?;
packets.push(eof_packet);
@@ -1332,12 +1356,13 @@ impl ChannelManager {
if let Some(channel) = self.channels.get_mut(channel_id) {
channel.exec_process = None;
channel.rsync_handler = None;
channel.exit_status = None;
}
}
if !channel_ids.is_empty() {
info!(
"Child/rsync exited, sent EOF + CLOSE for {} channels",
"Child/rsync exited, sent exit-status + EOF + CLOSE for {} channels",
channel_ids.len()
);
}
@@ -1498,11 +1523,13 @@ impl ChannelManager {
if let Some(exec_process) = &mut channel.exec_process {
match exec_process.child.try_wait() {
Ok(Some(status)) => {
let exit_code = status.code().unwrap_or(-1) as u32;
info!(
"Child process exited (channel {}, status: {:?})",
channel_id, status
"Child process exited (channel {}, exit_status: {}, status: {:?})",
channel_id, exit_code, status
);
child_exited = true;
channel.exit_status = Some(exit_code);
let command_str = exec_process.command.clone();
let should_trigger_hook = status.success()
@@ -2005,6 +2032,7 @@ struct Channel {
scp_handler: Option<ScpHandler>, // Phase 8: SCP处理器
rsync_handler: Option<RsyncHandler>, // Phase 8: rsync处理器
exec_process: Option<ExecProcess>, // Phase 14: 交互式exec进程
exit_status: Option<u32>, // ⭐⭐⭐⭐⭐ exit status from child process
// ⭐⭐⭐⭐⭐ Critical修复SFTP packet累积buffer
sftp_input_buffer: Vec<u8>, // Phase 14.2修复累积不完整的SFTP packets
// ⭐⭐⭐⭐⭐ Phase 14.4SCP packet累积buffer