Phase 17: SCP over SFTP subsystem + EOF/CLOSE fixes
This commit is contained in:
@@ -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 channel(Remote forwarding)
|
||||
forwarded_tcpip: Option<ForwardedTcpipChannel>, // forwarded-tcpip channel(Local forwarding)
|
||||
|
||||
Reference in New Issue
Block a user