Phase 17: SCP over SFTP subsystem + EOF/CLOSE fixes
Some checks failed
Test / build (push) Has been cancelled
Test / test (push) Has been cancelled

This commit is contained in:
Warren
2026-06-20 16:31:00 +08:00
parent 56217bc9a5
commit 5b439dfbef
3 changed files with 88 additions and 16 deletions

View File

@@ -180,6 +180,7 @@ impl ChannelManager {
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
scp_output_file: None, // Phase 17: SCP file receive
direct_tcpip: None,
forwarded_tcpip: None,
};
@@ -259,6 +260,7 @@ impl ChannelManager {
sftp_input_buffer: Vec::new(),
scp_input_buffer: Vec::new(),
scp_state: ScpState::Idle, // ⭐⭐⭐⭐⭐ Phase 8.3: SCP state machine
scp_output_file: None, // Phase 17: SCP file receive
direct_tcpip: Some(direct_tcpip),
forwarded_tcpip: None,
};
@@ -335,6 +337,7 @@ impl ChannelManager {
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
scp_output_file: None, // Phase 17: SCP file receive
direct_tcpip: None,
forwarded_tcpip: Some(forwarded_tcpip),
};
@@ -769,12 +772,19 @@ impl ChannelManager {
);
// ⭐⭐⭐⭐⭐ Phase 8: SCP handler (subsystem)
// ⭐⭐⭐⭐⭐ Phase 8.3: Complete SCP file transfer implementation
// Reference: OpenSSH scp.c: sink() (destination mode)
// Window Control - decrease local_window
channel.local_window -= data.len() as u32;
channel.local_consumed += data.len() as u32;
// ⭐⭐⭐⭐⭐ Phase 17: If this channel has an SFTP handler but NOT an SCP
// handler, skip the SCP state machine to avoid interpreting SFTP
// binary data as SCP text commands.
if channel.sftp_handler.is_some() && channel.scp_handler.is_none() {
// Fall through to SFTP handler code below
} else {
// ⭐⭐⭐⭐⭐ Phase 8.3: Complete SCP file transfer implementation
// Reference: OpenSSH scp.c: sink() (destination mode)
// ⭐⭐⭐⭐⭐ Phase 8.3: SCP state machine logic
match channel.scp_state.clone() {
@@ -793,6 +803,7 @@ impl ChannelManager {
match first_char {
Some('C') => {
// File command: C0644 size filename
// ⭐⭐⭐⭐⭐ Phase 17: VFS-backed file receive
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() == 3 {
let mode_str = parts[0].trim_start_matches('C');
@@ -801,34 +812,57 @@ impl ChannelManager {
info!("SCP receive file: mode={}, size={}, name={}", mode_str, size, filename);
// Open file via VFS
if let Some(ref scp_handler) = channel.scp_handler {
let resolved = scp_handler.resolve_path(filename)?;
info!("SCP resolved path: {:?}", resolved);
// Create parent directory
if let Some(parent) = resolved.parent() {
let _ = scp_handler.vfs.create_dir_all(parent, 0o755);
}
// Open file for writing
use crate::vfs::open_flags::OpenFlags;
let open_flags = {
let mut f = OpenFlags::new();
f.write = true;
f.create = true;
f.truncate = true;
f
};
let file = scp_handler.vfs.open_file(&resolved, &open_flags)?;
channel.scp_output_file = Some(file);
}
// Update state
channel.scp_state = ScpState::FileCommandReceived {
size,
filename: filename.to_string(),
remaining: size,
};
// Send ACK
response.push(0);
} else {
warn!("Invalid C command format: {}", line);
response.extend_from_slice(format!("Invalid command\n").as_bytes());
response.extend_from_slice(b"Invalid command\n");
}
}
Some('D') => {
// Directory command: D0755 0 dirname
// ⭐⭐⭐⭐⭐ Phase 17: VFS-backed directory creation
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() == 3 {
let dirname = parts[2];
info!("SCP create directory: {}", dirname);
// Create directory using VFS
// TODO: Need to get VFS from scp_handler
// For now, just send ACK
if let Some(ref scp_handler) = channel.scp_handler {
let resolved = scp_handler.resolve_path(dirname)?;
scp_handler.vfs.create_dir_all(&resolved, 0o755)?;
}
response.push(0);
} else {
warn!("Invalid D command format: {}", line);
response.extend_from_slice(format!("Invalid command\n").as_bytes());
response.extend_from_slice(b"Invalid command\n");
}
}
Some('E') => {
@@ -870,12 +904,23 @@ impl ChannelManager {
// Receive file data
let to_receive = std::cmp::min(data.len() as u64, remaining);
// TODO: Write to file using VFS
// For now, just consume the data
// ⭐⭐⭐⭐⭐ Phase 17: Write to file via VFS
if let Some(ref mut file) = channel.scp_output_file {
let write_data = &data[..to_receive as usize];
if !write_data.is_empty() {
file.write_all(write_data)?;
}
}
let new_remaining = remaining - to_receive;
if new_remaining == 0 {
info!("SCP file complete: {}", filename);
// Flush and close file
if let Some(mut file) = channel.scp_output_file.take() {
file.flush()?;
}
channel.scp_state = ScpState::Idle;
// Send final ACK
@@ -890,13 +935,14 @@ impl ChannelManager {
}
ScpState::DirectoryCreated { dirname } => {
info!("SCP in directory: {}", dirname);
// TODO: Handle directory operations
}
}
return Ok(None);
} // end of else block — SCP channels return here
// ⭐⭐⭐⭐⭐ Phase 16.5: rsync in-process handler (no child process)
// Only reached for SFTP channels (sftp_handler.is_some() && scp_handler.is_none())
if let Some(rsync_handler) = &mut channel.rsync_handler {
info!(
"⭐⭐⭐⭐⭐ [RSYNC_DATA] Feeding {} bytes to RsyncHandler",
@@ -1123,7 +1169,11 @@ impl ChannelManager {
info!("Channel close: channel={}", recipient_channel);
// 移除channel参考OpenSSH channel.c
if let Some(channel) = self.channels.remove(&recipient_channel) {
if let Some(mut channel) = self.channels.remove(&recipient_channel) {
// Phase 17: Flush and close SCP output file
if let Some(mut file) = channel.scp_output_file.take() {
let _ = file.flush();
}
info!("Channel {} removed", recipient_channel);
// 发送SSH_MSG_CHANNEL_CLOSE回应
@@ -1288,6 +1338,11 @@ impl ChannelManager {
}
}
/// ⭐⭐⭐⭐⭐ Phase 17: Check if a specific channel has an exec process
pub fn channel_has_exec_process(&self, channel_id: u32) -> bool {
self.channels.get(&channel_id).map_or(false, |ch| ch.exec_process.is_some())
}
/// 获取channel输出Phase 6新增
pub fn get_channel_output(&mut self, channel_id: u32) -> Option<Vec<u8>> {
if let Some(channel) = self.channels.get_mut(&channel_id) {
@@ -2043,6 +2098,7 @@ struct Channel {
scp_input_buffer: Vec<u8>, // Phase 14.4修复累积不完整的SCP packets
// ⭐⭐⭐⭐⭐ Phase 8.3: SCP file transfer state machine
scp_state: ScpState, // Phase 8.3: SCP state tracking
scp_output_file: Option<Box<dyn crate::vfs::VfsFile>>, // Phase 17: open file for SCP receive
// Phase 13.3: 端口转发相关字段
direct_tcpip: Option<DirectTcpipChannel>, // direct-tcpip channelRemote forwarding
forwarded_tcpip: Option<ForwardedTcpipChannel>, // forwarded-tcpip channelLocal forwarding