diff --git a/wezterm-ssh/src/session.rs b/wezterm-ssh/src/session.rs index f4d6ee813..981edc855 100644 --- a/wezterm-ssh/src/session.rs +++ b/wezterm-ssh/src/session.rs @@ -17,7 +17,7 @@ use std::time::Duration; mod sftp; pub use sftp::{File, Sftp}; -use sftp::{FileId, SftpRequest}; +use sftp::{FileId, FileRequest, SftpRequest}; #[derive(Debug)] pub enum SessionEvent { @@ -415,30 +415,54 @@ impl SessionInner { } Ok(true) } - SessionRequest::Sftp(SftpRequest::WriteFile(write_file)) => { + SessionRequest::Sftp(SftpRequest::File(FileRequest::Write(write_file))) => { if let Err(err) = self.write_file(&sess, &write_file) { log::error!("{:?} -> error: {:#}", write_file, err); } Ok(true) } - SessionRequest::Sftp(SftpRequest::ReadFile(read_file)) => { + SessionRequest::Sftp(SftpRequest::File(FileRequest::Read(read_file))) => { if let Err(err) = self.read_file(&sess, &read_file) { log::error!("{:?} -> error: {:#}", read_file, err); } Ok(true) } - SessionRequest::Sftp(SftpRequest::CloseFile(close_file)) => { + SessionRequest::Sftp(SftpRequest::File(FileRequest::Close(close_file))) => { if let Err(err) = self.close_file(&sess, &close_file) { log::error!("{:?} -> error: {:#}", close_file, err); } Ok(true) } - SessionRequest::Sftp(SftpRequest::FlushFile(flush_file)) => { + SessionRequest::Sftp(SftpRequest::File(FileRequest::Flush(flush_file))) => { if let Err(err) = self.flush_file(&sess, &flush_file) { log::error!("{:?} -> error: {:#}", flush_file, err); } Ok(true) } + SessionRequest::Sftp(SftpRequest::File(FileRequest::Setstat(setstat_file))) => { + if let Err(err) = self.setstat_file(&sess, &setstat_file) { + log::error!("{:?} -> error: {:#}", setstat_file, err); + } + Ok(true) + } + SessionRequest::Sftp(SftpRequest::File(FileRequest::Stat(stat_file))) => { + if let Err(err) = self.stat_file(&sess, &stat_file) { + log::error!("{:?} -> error: {:#}", stat_file, err); + } + Ok(true) + } + SessionRequest::Sftp(SftpRequest::File(FileRequest::Readdir(readdir_file))) => { + if let Err(err) = self.readdir_file(&sess, &readdir_file) { + log::error!("{:?} -> error: {:#}", readdir_file, err); + } + Ok(true) + } + SessionRequest::Sftp(SftpRequest::File(FileRequest::Fsync(fsync_file))) => { + if let Err(err) = self.fsync_file(&sess, &fsync_file) { + log::error!("{:?} -> error: {:#}", fsync_file, err); + } + Ok(true) + } SessionRequest::Sftp(SftpRequest::Readdir(readdir)) => { if let Err(err) = self.readdir(&sess, &readdir) { log::error!("{:?} -> error: {:#}", readdir, err); @@ -729,6 +753,68 @@ impl SessionInner { Ok(()) } + /// Sets file stat. + fn setstat_file( + &mut self, + _sess: &ssh2::Session, + setstat_file: &sftp::SetstatFile, + ) -> anyhow::Result<()> { + let sftp::SetstatFile { + file_id, + stat, + reply, + } = setstat_file; + + if let Some(file) = self.files.get_mut(file_id) { + file.setstat(stat.clone())?; + } + reply.try_send(())?; + + Ok(()) + } + + /// Gets file stat. + fn stat_file( + &mut self, + _sess: &ssh2::Session, + stat_file: &sftp::StatFile, + ) -> anyhow::Result<()> { + if let Some(file) = self.files.get_mut(&stat_file.file_id) { + let stat = file.stat()?; + stat_file.reply.try_send(stat)?; + } + + Ok(()) + } + + /// Performs readdir for file. + fn readdir_file( + &mut self, + _sess: &ssh2::Session, + readdir_file: &sftp::ReaddirFile, + ) -> anyhow::Result<()> { + if let Some(file) = self.files.get_mut(&readdir_file.file_id) { + let result = file.readdir()?; + readdir_file.reply.try_send(result)?; + } + + Ok(()) + } + + /// Fsync file. + fn fsync_file( + &mut self, + _sess: &ssh2::Session, + fsync_file: &sftp::FsyncFile, + ) -> anyhow::Result<()> { + if let Some(file) = self.files.get_mut(&fsync_file.file_id) { + file.fsync()?; + } + fsync_file.reply.try_send(())?; + + Ok(()) + } + /// Convenience function to read the files in a directory. /// /// See [`Sftp::readdir`] for more information. diff --git a/wezterm-ssh/src/session/sftp.rs b/wezterm-ssh/src/session/sftp.rs index 2c25513ac..e93ef8818 100644 --- a/wezterm-ssh/src/session/sftp.rs +++ b/wezterm-ssh/src/session/sftp.rs @@ -5,7 +5,10 @@ use std::{fmt, path::PathBuf}; mod file; pub use file::File; -pub(crate) use file::FileId; +pub(crate) use file::{ + CloseFile, FileId, FileRequest, FlushFile, FsyncFile, ReadFile, ReaddirFile, SetstatFile, + StatFile, WriteFile, +}; /// Represents an open sftp channel for performing filesystem operations #[derive(Clone, Debug)] @@ -276,7 +279,6 @@ impl Sftp { #[derive(Debug)] pub(crate) enum SftpRequest { - // Below are standard SFTP operations OpenMode(OpenMode), Open(Open), Create(Create), @@ -293,11 +295,8 @@ pub(crate) enum SftpRequest { Rename(Rename), Unlink(Unlink), - // Below are specialized SFTP operations for files - WriteFile(WriteFile), - ReadFile(ReadFile), - CloseFile(CloseFile), - FlushFile(FlushFile), + /// Specialized type for file-based operations + File(FileRequest), } pub(crate) struct OpenMode { @@ -345,32 +344,6 @@ pub(crate) struct Opendir { pub reply: Sender, } -#[derive(Debug)] -pub(crate) struct WriteFile { - pub file_id: FileId, - pub data: Vec, - pub reply: Sender<()>, -} - -#[derive(Debug)] -pub(crate) struct ReadFile { - pub file_id: FileId, - pub max_bytes: usize, - pub reply: Sender>, -} - -#[derive(Debug)] -pub(crate) struct CloseFile { - pub file_id: FileId, - pub reply: Sender<()>, -} - -#[derive(Debug)] -pub(crate) struct FlushFile { - pub file_id: FileId, - pub reply: Sender<()>, -} - #[derive(Debug)] pub(crate) struct Readdir { pub filename: PathBuf, diff --git a/wezterm-ssh/src/session/sftp/file.rs b/wezterm-ssh/src/session/sftp/file.rs index c2b326d95..bf7541e9e 100644 --- a/wezterm-ssh/src/session/sftp/file.rs +++ b/wezterm-ssh/src/session/sftp/file.rs @@ -1,11 +1,13 @@ -use super::{ - CloseFile, FlushFile, ReadFile, SessionRequest, SessionSender, SftpRequest, WriteFile, +use super::{FileStat, SessionRequest, SessionSender, SftpRequest}; +use smol::{ + channel::{bounded, Sender}, + future::FutureExt, }; -use smol::{channel::bounded, future::FutureExt}; use std::{ fmt, future::Future, io, + path::PathBuf, pin::Pin, task::{Context, Poll}, }; @@ -27,6 +29,69 @@ struct FileState { f_close: Option> + Send + Sync + 'static>>>, } +#[derive(Debug)] +pub(crate) enum FileRequest { + Write(WriteFile), + Read(ReadFile), + Close(CloseFile), + Flush(FlushFile), + Setstat(SetstatFile), + Stat(StatFile), + Readdir(ReaddirFile), + Fsync(FsyncFile), +} + +#[derive(Debug)] +pub(crate) struct WriteFile { + pub file_id: FileId, + pub data: Vec, + pub reply: Sender<()>, +} + +#[derive(Debug)] +pub(crate) struct ReadFile { + pub file_id: FileId, + pub max_bytes: usize, + pub reply: Sender>, +} + +#[derive(Debug)] +pub(crate) struct CloseFile { + pub file_id: FileId, + pub reply: Sender<()>, +} + +#[derive(Debug)] +pub(crate) struct FlushFile { + pub file_id: FileId, + pub reply: Sender<()>, +} + +#[derive(Debug)] +pub(crate) struct SetstatFile { + pub file_id: FileId, + pub stat: FileStat, + pub reply: Sender<()>, +} + +#[derive(Debug)] +pub(crate) struct StatFile { + pub file_id: FileId, + pub reply: Sender, +} + +#[derive(Debug)] +pub(crate) struct ReaddirFile { + pub file_id: FileId, + pub reply: Sender<(PathBuf, FileStat)>, +} + +#[derive(Debug)] +pub(crate) struct FsyncFile { + pub file_id: FileId, + pub reply: Sender<()>, +} + impl fmt::Debug for File { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("File") @@ -35,6 +100,21 @@ impl fmt::Debug for File { } } +impl Drop for File { + /// Attempts to close the file that exists in the dedicated ssh2 thread + fn drop(&mut self) { + if let Some(tx) = self.tx.take() { + let (reply, _) = bounded(1); + let _ = tx.try_send(SessionRequest::Sftp(SftpRequest::File(FileRequest::Close( + CloseFile { + file_id: self.file_id, + reply, + }, + )))); + } + } +} + impl File { pub(crate) fn new(file_id: FileId) -> Self { Self { @@ -47,6 +127,85 @@ impl File { pub(crate) fn initialize_sender(&mut self, sender: SessionSender) { self.tx.replace(sender); } + + /// Set the metadata for this handle. + /// + /// See [`ssh2::File::setstat`] for more information. + pub async fn setstat(&self, stat: FileStat) -> anyhow::Result<()> { + let (reply, rx) = bounded(1); + self.tx + .as_ref() + .unwrap() + .send(SessionRequest::Sftp(SftpRequest::File( + FileRequest::Setstat(SetstatFile { + file_id: self.file_id, + stat, + reply, + }), + ))) + .await?; + let result = rx.recv().await?; + Ok(result) + } + + /// Get the metadata for this handle. + /// + /// See [`ssh2::File::stat`] for more information. + pub async fn stat(&self) -> anyhow::Result { + let (reply, rx) = bounded(1); + self.tx + .as_ref() + .unwrap() + .send(SessionRequest::Sftp(SftpRequest::File(FileRequest::Stat( + StatFile { + file_id: self.file_id, + reply, + }, + )))) + .await?; + let result = rx.recv().await?; + Ok(result) + } + + /// Reads a block of data from a handle and returns file entry information for the next entry, + /// if any. + /// + /// See [`ssh2::File::readdir`] for more information. + pub async fn readdir(&self) -> anyhow::Result<(PathBuf, FileStat)> { + let (reply, rx) = bounded(1); + self.tx + .as_ref() + .unwrap() + .send(SessionRequest::Sftp(SftpRequest::File( + FileRequest::Readdir(ReaddirFile { + file_id: self.file_id, + reply, + }), + ))) + .await?; + let result = rx.recv().await?; + Ok(result) + } + + /// This function causes the remote server to synchronize the file data and metadata to disk + /// (like fsync(2)). + /// + /// See [`ssh2::File::fsync`] for more information. + pub async fn fsync(&self) -> anyhow::Result<()> { + let (reply, rx) = bounded(1); + self.tx + .as_ref() + .unwrap() + .send(SessionRequest::Sftp(SftpRequest::File(FileRequest::Fsync( + FsyncFile { + file_id: self.file_id, + reply, + }, + )))) + .await?; + let result = rx.recv().await?; + Ok(result) + } } impl smol::io::AsyncRead for File { @@ -165,11 +324,13 @@ impl smol::io::AsyncWrite for File { /// Writes some bytes to the file. async fn inner_write(tx: SessionSender, file_id: usize, data: Vec) -> anyhow::Result<()> { let (reply, rx) = bounded(1); - tx.send(SessionRequest::Sftp(SftpRequest::WriteFile(WriteFile { - file_id, - data, - reply, - }))) + tx.send(SessionRequest::Sftp(SftpRequest::File(FileRequest::Write( + WriteFile { + file_id, + data, + reply, + }, + )))) .await?; let result = rx.recv().await?; Ok(result) @@ -185,11 +346,13 @@ async fn inner_read( max_bytes: usize, ) -> anyhow::Result> { let (reply, rx) = bounded(1); - tx.send(SessionRequest::Sftp(SftpRequest::ReadFile(ReadFile { - file_id, - max_bytes, - reply, - }))) + tx.send(SessionRequest::Sftp(SftpRequest::File(FileRequest::Read( + ReadFile { + file_id, + max_bytes, + reply, + }, + )))) .await?; let result = rx.recv().await?; Ok(result) @@ -198,10 +361,9 @@ async fn inner_read( /// Flushes the remote file async fn inner_flush(tx: SessionSender, file_id: usize) -> anyhow::Result<()> { let (reply, rx) = bounded(1); - tx.send(SessionRequest::Sftp(SftpRequest::FlushFile(FlushFile { - file_id, - reply, - }))) + tx.send(SessionRequest::Sftp(SftpRequest::File(FileRequest::Flush( + FlushFile { file_id, reply }, + )))) .await?; let result = rx.recv().await?; Ok(result) @@ -210,10 +372,9 @@ async fn inner_flush(tx: SessionSender, file_id: usize) -> anyhow::Result<()> { /// Closes the handle to the remote file async fn inner_close(tx: SessionSender, file_id: usize) -> anyhow::Result<()> { let (reply, rx) = bounded(1); - tx.send(SessionRequest::Sftp(SftpRequest::CloseFile(CloseFile { - file_id, - reply, - }))) + tx.send(SessionRequest::Sftp(SftpRequest::File(FileRequest::Close( + CloseFile { file_id, reply }, + )))) .await?; let result = rx.recv().await?; Ok(result)