1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-27 02:25:28 +03:00

Create SftpError type and associated structs to provide more verbose errors for sftp

This commit is contained in:
Chip Senkbeil 2021-09-26 20:54:08 -05:00 committed by Wez Furlong
parent 4a0376e6de
commit 072bb1c470
7 changed files with 421 additions and 175 deletions

1
Cargo.lock generated
View File

@ -5381,6 +5381,7 @@ dependencies = [
"ssh2",
"structopt",
"termwiz",
"thiserror",
]
[[package]]

View File

@ -24,18 +24,19 @@ portable-pty = { version="0.5", path = "../pty" }
regex = "1"
smol = "1.2"
ssh2 = {version="0.9.3", features=["openssl-on-win32"]}
thiserror = "1.0"
# Not used directly, but is used to centralize the openssl vendor feature selection
async_ossl = { path = "../async_ossl" }
[dev-dependencies]
assert_fs = "1.0.4"
indoc = "1.0.3"
indoc = "1.0"
k9 = "0.11.0"
once_cell = "1.8.0"
predicates = "2.0.2"
once_cell = "1.8"
predicates = "2.0"
pretty_env_logger = "0.4"
rstest = "0.11.0"
rstest = "0.11"
shell-words = "1.0"
smol-potat = "1.1.2"
structopt = "0.3"

View File

@ -18,7 +18,7 @@ use std::time::Duration;
mod sftp;
pub use sftp::{
File, FilePermissions, FileType, Metadata, OpenFileType, OpenOptions, RenameOptions, Sftp,
WriteMode,
SftpChannelError, SftpChannelResult, SftpError, SftpResult, WriteMode,
};
use sftp::{FileId, FileRequest, SftpRequest};
@ -628,16 +628,19 @@ impl SessionInner {
let mode = open_mode.opts.mode;
let open_type: ssh2::OpenType = open_mode.opts.ty.into();
let ssh_file = self.init_sftp(sess)?.open_mode(
open_mode.filename.as_path(),
flags,
mode,
open_type,
)?;
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.open_mode(open_mode.filename.as_path(), flags, mode, open_type)
.map_err(SftpChannelError::from)
});
let (file_id, file) = self.make_file();
open_mode.reply.try_send(file)?;
self.files.insert(file_id, ssh_file);
match result {
Ok(ssh_file) => {
let (file_id, file) = self.make_file();
open_mode.reply.try_send(Ok(file))?;
self.files.insert(file_id, ssh_file);
}
Err(x) => open_mode.reply.try_send(Err(x))?,
}
Ok(())
}
@ -646,11 +649,19 @@ impl SessionInner {
///
/// See [`Sftp::open`] for more information.
pub fn open(&mut self, sess: &ssh2::Session, open: &sftp::Open) -> anyhow::Result<()> {
let ssh_file = self.init_sftp(sess)?.open(open.filename.as_path())?;
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.open(open.filename.as_path())
.map_err(SftpChannelError::from)
});
let (file_id, file) = self.make_file();
open.reply.try_send(file)?;
self.files.insert(file_id, ssh_file);
match result {
Ok(ssh_file) => {
let (file_id, file) = self.make_file();
open.reply.try_send(Ok(file))?;
self.files.insert(file_id, ssh_file);
}
Err(x) => open.reply.try_send(Err(x))?,
}
Ok(())
}
@ -659,11 +670,19 @@ impl SessionInner {
///
/// See [`Sftp::create`] for more information.
pub fn create(&mut self, sess: &ssh2::Session, create: &sftp::Create) -> anyhow::Result<()> {
let ssh_file = self.init_sftp(sess)?.create(create.filename.as_path())?;
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.create(create.filename.as_path())
.map_err(SftpChannelError::from)
});
let (file_id, file) = self.make_file();
create.reply.try_send(file)?;
self.files.insert(file_id, ssh_file);
match result {
Ok(ssh_file) => {
let (file_id, file) = self.make_file();
create.reply.try_send(Ok(file))?;
self.files.insert(file_id, ssh_file);
}
Err(x) => create.reply.try_send(Err(x))?,
}
Ok(())
}
@ -672,11 +691,19 @@ impl SessionInner {
///
/// See [`Sftp::opendir`] for more information.
pub fn opendir(&mut self, sess: &ssh2::Session, opendir: &sftp::Opendir) -> anyhow::Result<()> {
let ssh_file = self.init_sftp(sess)?.opendir(opendir.filename.as_path())?;
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.opendir(opendir.filename.as_path())
.map_err(SftpChannelError::from)
});
let (file_id, file) = self.make_file();
opendir.reply.try_send(file)?;
self.files.insert(file_id, ssh_file);
match result {
Ok(ssh_file) => {
let (file_id, file) = self.make_file();
opendir.reply.try_send(Ok(file))?;
self.files.insert(file_id, ssh_file);
}
Err(x) => opendir.reply.try_send(Err(x))?,
}
Ok(())
}
@ -702,9 +729,9 @@ impl SessionInner {
} = write_file;
if let Some(file) = self.files.get_mut(file_id) {
file.write_all(data)?;
let result = file.write_all(data).map_err(SftpChannelError::from);
reply.try_send(result)?;
}
reply.try_send(())?;
Ok(())
}
@ -724,11 +751,13 @@ impl SessionInner {
if let Some(file) = self.files.get_mut(file_id) {
// TODO: Move this somewhere to avoid re-allocating buffer
let mut buf = vec![0u8; *max_bytes];
let n = file.read(&mut buf)?;
buf.truncate(n);
reply.try_send(buf)?;
} else {
reply.try_send(Vec::new())?;
match file.read(&mut buf).map_err(SftpChannelError::from) {
Ok(n) => {
buf.truncate(n);
reply.try_send(Ok(buf))?;
}
Err(x) => reply.try_send(Err(x))?,
}
}
Ok(())
@ -741,7 +770,7 @@ impl SessionInner {
close_file: &sftp::CloseFile,
) -> anyhow::Result<()> {
self.files.remove(&close_file.file_id);
close_file.reply.try_send(())?;
close_file.reply.try_send(Ok(()))?;
Ok(())
}
@ -753,9 +782,9 @@ impl SessionInner {
flush_file: &sftp::FlushFile,
) -> anyhow::Result<()> {
if let Some(file) = self.files.get_mut(&flush_file.file_id) {
file.flush()?;
let result = file.flush().map_err(SftpChannelError::from);
flush_file.reply.try_send(result)?;
}
flush_file.reply.try_send(())?;
Ok(())
}
@ -773,9 +802,11 @@ impl SessionInner {
} = setstat_file;
if let Some(file) = self.files.get_mut(file_id) {
file.setstat((*metadata).into())?;
let result = file
.setstat((*metadata).into())
.map_err(SftpChannelError::from);
reply.try_send(result)?;
}
reply.try_send(())?;
Ok(())
}
@ -787,8 +818,11 @@ impl SessionInner {
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(Metadata::from(stat))?;
let result = file
.stat()
.map(Metadata::from)
.map_err(SftpChannelError::from);
stat_file.reply.try_send(result)?;
}
Ok(())
@ -801,8 +835,11 @@ impl SessionInner {
readdir_file: &sftp::ReaddirFile,
) -> anyhow::Result<()> {
if let Some(file) = self.files.get_mut(&readdir_file.file_id) {
let (path, stat) = file.readdir()?;
readdir_file.reply.try_send((path, Metadata::from(stat)))?;
let result = file
.readdir()
.map(|(path, stat)| (path, Metadata::from(stat)))
.map_err(SftpChannelError::from);
readdir_file.reply.try_send(result)?;
}
Ok(())
@ -815,9 +852,9 @@ impl SessionInner {
fsync_file: &sftp::FsyncFile,
) -> anyhow::Result<()> {
if let Some(file) = self.files.get_mut(&fsync_file.file_id) {
file.fsync()?;
let result = file.fsync().map_err(SftpChannelError::from);
fsync_file.reply.try_send(result)?;
}
fsync_file.reply.try_send(())?;
Ok(())
}
@ -826,12 +863,16 @@ impl SessionInner {
///
/// See [`Sftp::readdir`] for more information.
pub fn readdir(&mut self, sess: &ssh2::Session, readdir: &sftp::Readdir) -> anyhow::Result<()> {
let result = self
.init_sftp(sess)?
.readdir(readdir.filename.as_path())?
.into_iter()
.map(|(path, stat)| (path, Metadata::from(stat)))
.collect();
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.readdir(readdir.filename.as_path())
.map(|entries| {
entries
.into_iter()
.map(|(path, stat)| (path, Metadata::from(stat)))
.collect()
})
.map_err(SftpChannelError::from)
});
readdir.reply.try_send(result)?;
Ok(())
@ -841,9 +882,11 @@ impl SessionInner {
///
/// See [`Sftp::rmdir`] for more information.
pub fn mkdir(&mut self, sess: &ssh2::Session, mkdir: &sftp::Mkdir) -> anyhow::Result<()> {
self.init_sftp(sess)?
.mkdir(mkdir.filename.as_path(), mkdir.mode)?;
mkdir.reply.try_send(())?;
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.mkdir(mkdir.filename.as_path(), mkdir.mode)
.map_err(SftpChannelError::from)
});
mkdir.reply.try_send(result)?;
Ok(())
}
@ -852,8 +895,11 @@ impl SessionInner {
///
/// See [`Sftp::rmdir`] for more information.
pub fn rmdir(&mut self, sess: &ssh2::Session, rmdir: &sftp::Rmdir) -> anyhow::Result<()> {
self.init_sftp(sess)?.rmdir(rmdir.filename.as_path())?;
rmdir.reply.try_send(())?;
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.rmdir(rmdir.filename.as_path())
.map_err(SftpChannelError::from)
});
rmdir.reply.try_send(result)?;
Ok(())
}
@ -862,8 +908,12 @@ impl SessionInner {
///
/// See [`Sftp::stat`] for more information.
pub fn stat(&mut self, sess: &ssh2::Session, stat: &sftp::Stat) -> anyhow::Result<()> {
let metadata = Metadata::from(self.init_sftp(sess)?.stat(stat.filename.as_path())?);
stat.reply.try_send(metadata)?;
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.stat(stat.filename.as_path())
.map(Metadata::from)
.map_err(SftpChannelError::from)
});
stat.reply.try_send(result)?;
Ok(())
}
@ -872,8 +922,12 @@ impl SessionInner {
///
/// See [`Sftp::lstat`] for more information.
pub fn lstat(&mut self, sess: &ssh2::Session, lstat: &sftp::Lstat) -> anyhow::Result<()> {
let metadata = Metadata::from(self.init_sftp(sess)?.lstat(lstat.filename.as_path())?);
lstat.reply.try_send(metadata)?;
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.lstat(lstat.filename.as_path())
.map(Metadata::from)
.map_err(SftpChannelError::from)
});
lstat.reply.try_send(result)?;
Ok(())
}
@ -882,9 +936,11 @@ impl SessionInner {
///
/// See [`Sftp::setstat`] for more information.
pub fn setstat(&mut self, sess: &ssh2::Session, setstat: &sftp::Setstat) -> anyhow::Result<()> {
self.init_sftp(sess)?
.setstat(setstat.filename.as_path(), setstat.metadata.into())?;
setstat.reply.try_send(())?;
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.setstat(setstat.filename.as_path(), setstat.metadata.into())
.map_err(SftpChannelError::from)
});
setstat.reply.try_send(result)?;
Ok(())
}
@ -893,9 +949,11 @@ impl SessionInner {
///
/// See [`Sftp::symlink`] for more information.
pub fn symlink(&mut self, sess: &ssh2::Session, symlink: &sftp::Symlink) -> anyhow::Result<()> {
self.init_sftp(sess)?
.symlink(symlink.path.as_path(), symlink.target.as_path())?;
symlink.reply.try_send(())?;
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.symlink(symlink.path.as_path(), symlink.target.as_path())
.map_err(SftpChannelError::from)
});
symlink.reply.try_send(result)?;
Ok(())
}
@ -908,8 +966,11 @@ impl SessionInner {
sess: &ssh2::Session,
readlink: &sftp::Readlink,
) -> anyhow::Result<()> {
let path = self.init_sftp(sess)?.readlink(readlink.path.as_path())?;
readlink.reply.try_send(path)?;
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.readlink(readlink.path.as_path())
.map_err(SftpChannelError::from)
});
readlink.reply.try_send(result)?;
Ok(())
}
@ -922,8 +983,11 @@ impl SessionInner {
sess: &ssh2::Session,
realpath: &sftp::Realpath,
) -> anyhow::Result<()> {
let path = self.init_sftp(sess)?.realpath(realpath.path.as_path())?;
realpath.reply.try_send(path)?;
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.realpath(realpath.path.as_path())
.map_err(SftpChannelError::from)
});
realpath.reply.try_send(result)?;
Ok(())
}
@ -932,12 +996,15 @@ impl SessionInner {
///
/// See [`Sftp::rename`] for more information.
pub fn rename(&mut self, sess: &ssh2::Session, rename: &sftp::Rename) -> anyhow::Result<()> {
self.init_sftp(sess)?.rename(
rename.src.as_path(),
rename.dst.as_path(),
Some(rename.opts.into()),
)?;
rename.reply.try_send(())?;
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.rename(
rename.src.as_path(),
rename.dst.as_path(),
Some(rename.opts.into()),
)
.map_err(SftpChannelError::from)
});
rename.reply.try_send(result)?;
Ok(())
}
@ -946,8 +1013,11 @@ impl SessionInner {
///
/// See [`Sftp::unlink`] for more information.
pub fn unlink(&mut self, sess: &ssh2::Session, unlink: &sftp::Unlink) -> anyhow::Result<()> {
self.init_sftp(sess)?.unlink(unlink.file.as_path())?;
unlink.reply.try_send(())?;
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.unlink(unlink.file.as_path())
.map_err(SftpChannelError::from)
});
unlink.reply.try_send(result)?;
Ok(())
}
@ -956,28 +1026,19 @@ impl SessionInner {
fn init_sftp<'a, 'b>(
&'a mut self,
sess: &'b ssh2::Session,
) -> anyhow::Result<&'a mut ssh2::Sftp> {
) -> SftpChannelResult<&'a mut ssh2::Sftp> {
if self.sftp.is_none() {
self.do_blocking(sess, |this, sess| {
this.sftp = Some(sess.sftp()?);
Ok(())
})?;
let blocking = sess.is_blocking();
sess.set_blocking(true);
self.sftp = Some(sess.sftp()?);
sess.set_blocking(blocking);
}
// NOTE: sftp should have been replaced with Some(sftp) from above
Ok(self.sftp.as_mut().unwrap())
}
fn do_blocking<F, R>(&mut self, sess: &ssh2::Session, mut f: F) -> anyhow::Result<R>
where
F: FnMut(&mut Self, &ssh2::Session) -> anyhow::Result<R>,
{
let blocking = sess.is_blocking();
sess.set_blocking(true);
let result = f(self, sess);
sess.set_blocking(blocking);
result
}
}
#[derive(Clone)]

View File

@ -1,6 +1,10 @@
use super::{SessionRequest, SessionSender};
use smol::channel::{bounded, Sender};
use smol::channel::{bounded, RecvError, Sender};
use std::path::PathBuf;
use thiserror::Error;
mod error;
pub use error::{SftpError, SftpResult};
mod file;
pub use file::File;
@ -14,6 +18,28 @@ pub use types::{
FilePermissions, FileType, Metadata, OpenFileType, OpenOptions, RenameOptions, WriteMode,
};
/// Represents the result of some SFTP channel operation
pub type SftpChannelResult<T> = Result<T, SftpChannelError>;
/// Represents an error that can occur when working with the SFTP channel
#[derive(Debug, Error)]
pub enum SftpChannelError {
#[error(transparent)]
Sftp(#[from] SftpError),
#[error("File IO failed: {}", .0)]
FileIo(#[from] std::io::Error),
#[error("Failed to send request: {}", .0)]
SendFailed(#[from] anyhow::Error),
#[error("Failed to receive response: {}", .0)]
RecvFailed(#[from] RecvError),
#[error("Library-specific error: {}", .0)]
Other(#[source] ssh2::Error),
}
/// Represents an open sftp channel for performing filesystem operations
#[derive(Clone, Debug)]
pub struct Sftp {
@ -28,7 +54,7 @@ impl Sftp {
&self,
filename: impl Into<PathBuf>,
opts: OpenOptions,
) -> anyhow::Result<File> {
) -> SftpChannelResult<File> {
let (reply, rx) = bounded(1);
self.tx
@ -38,7 +64,7 @@ impl Sftp {
reply,
})))
.await?;
let mut result = rx.recv().await?;
let mut result = rx.recv().await??;
result.initialize_sender(self.tx.clone());
Ok(result)
}
@ -46,7 +72,7 @@ impl Sftp {
/// Helper to open a file in the `Read` mode.
///
/// See [`Sftp::open`] for more information.
pub async fn open(&self, filename: impl Into<PathBuf>) -> anyhow::Result<File> {
pub async fn open(&self, filename: impl Into<PathBuf>) -> SftpChannelResult<File> {
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Open(Open {
@ -54,7 +80,7 @@ impl Sftp {
reply,
})))
.await?;
let mut result = rx.recv().await?;
let mut result = rx.recv().await??;
result.initialize_sender(self.tx.clone());
Ok(result)
}
@ -62,7 +88,7 @@ impl Sftp {
/// Helper to create a file in write-only mode with truncation.
///
/// See [`Sftp::create`] for more information.
pub async fn create(&self, filename: impl Into<PathBuf>) -> anyhow::Result<File> {
pub async fn create(&self, filename: impl Into<PathBuf>) -> SftpChannelResult<File> {
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Create(Create {
@ -70,7 +96,7 @@ impl Sftp {
reply,
})))
.await?;
let mut result = rx.recv().await?;
let mut result = rx.recv().await??;
result.initialize_sender(self.tx.clone());
Ok(result)
}
@ -78,7 +104,7 @@ impl Sftp {
/// Helper to open a directory for reading its contents.
///
/// See [`Sftp::opendir`] for more information.
pub async fn opendir(&self, filename: impl Into<PathBuf>) -> anyhow::Result<File> {
pub async fn opendir(&self, filename: impl Into<PathBuf>) -> SftpChannelResult<File> {
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Opendir(Opendir {
@ -86,7 +112,7 @@ impl Sftp {
reply,
})))
.await?;
let mut result = rx.recv().await?;
let mut result = rx.recv().await??;
result.initialize_sender(self.tx.clone());
Ok(result)
}
@ -100,7 +126,7 @@ impl Sftp {
pub async fn readdir(
&self,
filename: impl Into<PathBuf>,
) -> anyhow::Result<Vec<(PathBuf, Metadata)>> {
) -> SftpChannelResult<Vec<(PathBuf, Metadata)>> {
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Readdir(Readdir {
@ -108,14 +134,14 @@ impl Sftp {
reply,
})))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
/// Create a directory on the remote filesystem.
///
/// See [`Sftp::rmdir`] for more information.
pub async fn mkdir(&self, filename: impl Into<PathBuf>, mode: i32) -> anyhow::Result<()> {
pub async fn mkdir(&self, filename: impl Into<PathBuf>, mode: i32) -> SftpChannelResult<()> {
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Mkdir(Mkdir {
@ -124,14 +150,14 @@ impl Sftp {
reply,
})))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
/// Remove a directory from the remote filesystem.
///
/// See [`Sftp::rmdir`] for more information.
pub async fn rmdir(&self, filename: impl Into<PathBuf>) -> anyhow::Result<()> {
pub async fn rmdir(&self, filename: impl Into<PathBuf>) -> SftpChannelResult<()> {
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Rmdir(Rmdir {
@ -139,14 +165,14 @@ impl Sftp {
reply,
})))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
/// Get the metadata for a file, performed by stat(2).
///
/// See [`Sftp::stat`] for more information.
pub async fn stat(&self, filename: impl Into<PathBuf>) -> anyhow::Result<Metadata> {
pub async fn stat(&self, filename: impl Into<PathBuf>) -> SftpChannelResult<Metadata> {
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Stat(Stat {
@ -154,14 +180,14 @@ impl Sftp {
reply,
})))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
/// Get the metadata for a file, performed by lstat(2).
///
/// See [`Sftp::lstat`] for more information.
pub async fn lstat(&self, filename: impl Into<PathBuf>) -> anyhow::Result<Metadata> {
pub async fn lstat(&self, filename: impl Into<PathBuf>) -> SftpChannelResult<Metadata> {
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Lstat(Lstat {
@ -169,7 +195,7 @@ impl Sftp {
reply,
})))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
@ -180,7 +206,7 @@ impl Sftp {
&self,
filename: impl Into<PathBuf>,
metadata: Metadata,
) -> anyhow::Result<()> {
) -> SftpChannelResult<()> {
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Setstat(Setstat {
@ -189,7 +215,7 @@ impl Sftp {
reply,
})))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
@ -200,7 +226,7 @@ impl Sftp {
&self,
path: impl Into<PathBuf>,
target: impl Into<PathBuf>,
) -> anyhow::Result<()> {
) -> SftpChannelResult<()> {
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Symlink(Symlink {
@ -209,14 +235,14 @@ impl Sftp {
reply,
})))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
/// Read a symlink at `path`.
///
/// See [`Sftp::readlink`] for more information.
pub async fn readlink(&self, path: impl Into<PathBuf>) -> anyhow::Result<PathBuf> {
pub async fn readlink(&self, path: impl Into<PathBuf>) -> SftpChannelResult<PathBuf> {
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Readlink(Readlink {
@ -224,14 +250,14 @@ impl Sftp {
reply,
})))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
/// Resolve the real path for `path`.
///
/// See [`Sftp::realpath`] for more information.
pub async fn realpath(&self, path: impl Into<PathBuf>) -> anyhow::Result<PathBuf> {
pub async fn realpath(&self, path: impl Into<PathBuf>) -> SftpChannelResult<PathBuf> {
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Realpath(Realpath {
@ -239,7 +265,7 @@ impl Sftp {
reply,
})))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
@ -251,7 +277,7 @@ impl Sftp {
src: impl Into<PathBuf>,
dst: impl Into<PathBuf>,
opts: RenameOptions,
) -> anyhow::Result<()> {
) -> SftpChannelResult<()> {
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Rename(Rename {
@ -261,14 +287,14 @@ impl Sftp {
reply,
})))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
/// Remove a file on the remote filesystem.
///
/// See [`Sftp::unlink`] for more information.
pub async fn unlink(&self, file: impl Into<PathBuf>) -> anyhow::Result<()> {
pub async fn unlink(&self, file: impl Into<PathBuf>) -> SftpChannelResult<()> {
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Unlink(Unlink {
@ -276,7 +302,7 @@ impl Sftp {
reply,
})))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
}
@ -307,82 +333,82 @@ pub(crate) enum SftpRequest {
pub(crate) struct OpenMode {
pub filename: PathBuf,
pub opts: OpenOptions,
pub reply: Sender<File>,
pub reply: Sender<SftpChannelResult<File>>,
}
#[derive(Debug)]
pub(crate) struct Open {
pub filename: PathBuf,
pub reply: Sender<File>,
pub reply: Sender<SftpChannelResult<File>>,
}
#[derive(Debug)]
pub(crate) struct Create {
pub filename: PathBuf,
pub reply: Sender<File>,
pub reply: Sender<SftpChannelResult<File>>,
}
#[derive(Debug)]
pub(crate) struct Opendir {
pub filename: PathBuf,
pub reply: Sender<File>,
pub reply: Sender<SftpChannelResult<File>>,
}
#[derive(Debug)]
pub(crate) struct Readdir {
pub filename: PathBuf,
pub reply: Sender<Vec<(PathBuf, Metadata)>>,
pub reply: Sender<SftpChannelResult<Vec<(PathBuf, Metadata)>>>,
}
#[derive(Debug)]
pub(crate) struct Mkdir {
pub filename: PathBuf,
pub mode: i32,
pub reply: Sender<()>,
pub reply: Sender<SftpChannelResult<()>>,
}
#[derive(Debug)]
pub(crate) struct Rmdir {
pub filename: PathBuf,
pub reply: Sender<()>,
pub reply: Sender<SftpChannelResult<()>>,
}
#[derive(Debug)]
pub(crate) struct Stat {
pub filename: PathBuf,
pub reply: Sender<Metadata>,
pub reply: Sender<SftpChannelResult<Metadata>>,
}
#[derive(Debug)]
pub(crate) struct Lstat {
pub filename: PathBuf,
pub reply: Sender<Metadata>,
pub reply: Sender<SftpChannelResult<Metadata>>,
}
#[derive(Debug)]
pub(crate) struct Setstat {
pub filename: PathBuf,
pub metadata: Metadata,
pub reply: Sender<()>,
pub reply: Sender<SftpChannelResult<()>>,
}
#[derive(Debug)]
pub(crate) struct Symlink {
pub path: PathBuf,
pub target: PathBuf,
pub reply: Sender<()>,
pub reply: Sender<SftpChannelResult<()>>,
}
#[derive(Debug)]
pub(crate) struct Readlink {
pub path: PathBuf,
pub reply: Sender<PathBuf>,
pub reply: Sender<SftpChannelResult<PathBuf>>,
}
#[derive(Debug)]
pub(crate) struct Realpath {
pub path: PathBuf,
pub reply: Sender<PathBuf>,
pub reply: Sender<SftpChannelResult<PathBuf>>,
}
#[derive(Debug)]
@ -390,11 +416,25 @@ pub(crate) struct Rename {
pub src: PathBuf,
pub dst: PathBuf,
pub opts: RenameOptions,
pub reply: Sender<()>,
pub reply: Sender<SftpChannelResult<()>>,
}
#[derive(Debug)]
pub(crate) struct Unlink {
pub file: PathBuf,
pub reply: Sender<()>,
pub reply: Sender<SftpChannelResult<()>>,
}
mod ssh2_impl {
use super::*;
use std::convert::TryFrom;
impl From<ssh2::Error> for SftpChannelError {
fn from(err: ssh2::Error) -> Self {
match SftpError::try_from(err) {
Ok(x) => Self::Sftp(x),
Err(x) => Self::Other(x),
}
}
}
}

View File

@ -0,0 +1,138 @@
use std::convert::TryFrom;
use thiserror::Error;
/// Represents a result whose error is [`SftpError`]
pub type SftpResult<T> = Result<T, SftpError>;
/// Represents errors associated with sftp operations
#[derive(Copy, Clone, Debug, Error, Hash, PartialEq, Eq)]
pub enum SftpError {
// Following are available on libssh and libssh2
#[error("End-of-file encountered")]
Eof = 1,
#[error("File doesn't exist")]
NoSuchFile = 2,
#[error("Permission denied")]
PermissionDenied = 3,
#[error("Generic failure")]
Failure = 4,
#[error("Garbage received from server")]
BadMessage = 5,
#[error("No connection has been set up")]
NoConnection = 6,
#[error("There was a connection, but we lost it")]
ConnectionLost = 7,
#[error("Operation not supported by the server")]
OpUnsupported = 8,
#[error("Invalid file handle")]
InvalidHandle = 9,
#[error("No such file or directory path exists")]
NoSuchPath = 10,
#[error("An attempt to create an already existing file or directory has been made")]
FileAlreadyExists = 11,
#[error("We are trying to write on a write-protected filesystem")]
WriteProtect = 12,
#[error("No media in remote drive")]
NoMedia = 13,
// Below are libssh2-specific errors
#[error("No space available on filesystem")]
NoSpaceOnFilesystem = 14,
#[error("Quota exceeded")]
QuotaExceeded = 15,
#[error("Unknown principal")]
UnknownPrincipal = 16,
#[error("Filesystem lock conflict")]
LockConflict = 17,
#[error("Directory is not empty")]
DirNotEmpty = 18,
#[error("Operation attempted against a path that is not a directory")]
NotADirectory = 19,
#[error("Filename invalid")]
InvalidFilename = 20,
#[error("Symlink loop encountered")]
LinkLoop = 21,
}
impl SftpError {
/// Produces an SFTP error from the given code if it matches a known error type
pub fn from_error_code(code: i32) -> Option<SftpError> {
Self::try_from(code).ok()
}
/// Converts into an error code
pub fn to_error_code(self) -> i32 {
self as i32
}
}
impl TryFrom<i32> for SftpError {
type Error = Result<(), i32>;
/// Attempt to convert an arbitrary code to an sftp error, returning
/// `Ok` if matching an sftp error or `Err` if the code represented a
/// success or was unknown
fn try_from(code: i32) -> Result<Self, Self::Error> {
match code {
// 0 means okay in libssh and libssh2, which isn't an error
0 => Err(Ok(())),
1 => Ok(Self::Eof),
2 => Ok(Self::NoSuchFile),
3 => Ok(Self::PermissionDenied),
4 => Ok(Self::Failure),
5 => Ok(Self::BadMessage),
6 => Ok(Self::NoConnection),
7 => Ok(Self::ConnectionLost),
8 => Ok(Self::OpUnsupported),
9 => Ok(Self::InvalidHandle),
10 => Ok(Self::NoSuchPath),
11 => Ok(Self::FileAlreadyExists),
12 => Ok(Self::WriteProtect),
13 => Ok(Self::NoMedia),
14 => Ok(Self::NoSpaceOnFilesystem),
15 => Ok(Self::QuotaExceeded),
16 => Ok(Self::UnknownPrincipal),
17 => Ok(Self::LockConflict),
18 => Ok(Self::DirNotEmpty),
19 => Ok(Self::NotADirectory),
20 => Ok(Self::InvalidFilename),
21 => Ok(Self::LinkLoop),
// Unsupported codes get reflected back
x => Err(Err(x)),
}
}
}
mod ssh2_impl {
use super::*;
impl TryFrom<ssh2::Error> for SftpError {
type Error = ssh2::Error;
fn try_from(err: ssh2::Error) -> Result<Self, Self::Error> {
match err.code() {
ssh2::ErrorCode::SFTP(x) => match Self::from_error_code(x) {
Some(err) => Ok(err),
None => Err(err),
},
_ => Err(err),
}
}
}
impl TryFrom<ssh2::ErrorCode> for SftpError {
type Error = ssh2::ErrorCode;
fn try_from(code: ssh2::ErrorCode) -> Result<Self, Self::Error> {
match code {
ssh2::ErrorCode::SFTP(x) => match Self::from_error_code(x) {
Some(err) => Ok(err),
None => Err(code),
},
x => Err(x),
}
}
}
}

View File

@ -1,16 +1,8 @@
use super::{Metadata, SessionRequest, SessionSender, SftpRequest};
use smol::{
channel::{bounded, Sender},
future::FutureExt,
};
use std::{
fmt,
future::Future,
io,
path::PathBuf,
pin::Pin,
task::{Context, Poll},
};
use super::{Metadata, SessionRequest, SessionSender, SftpChannelResult, SftpRequest};
use smol::channel::{bounded, Sender};
use smol::future::FutureExt;
use std::task::{Context, Poll};
use std::{fmt, future::Future, io, path::PathBuf, pin::Pin};
pub(crate) type FileId = usize;
@ -45,51 +37,51 @@ pub(crate) enum FileRequest {
pub(crate) struct WriteFile {
pub file_id: FileId,
pub data: Vec<u8>,
pub reply: Sender<()>,
pub reply: Sender<SftpChannelResult<()>>,
}
#[derive(Debug)]
pub(crate) struct ReadFile {
pub file_id: FileId,
pub max_bytes: usize,
pub reply: Sender<Vec<u8>>,
pub reply: Sender<SftpChannelResult<Vec<u8>>>,
}
#[derive(Debug)]
pub(crate) struct CloseFile {
pub file_id: FileId,
pub reply: Sender<()>,
pub reply: Sender<SftpChannelResult<()>>,
}
#[derive(Debug)]
pub(crate) struct FlushFile {
pub file_id: FileId,
pub reply: Sender<()>,
pub reply: Sender<SftpChannelResult<()>>,
}
#[derive(Debug)]
pub(crate) struct SetstatFile {
pub file_id: FileId,
pub metadata: Metadata,
pub reply: Sender<()>,
pub reply: Sender<SftpChannelResult<()>>,
}
#[derive(Debug)]
pub(crate) struct StatFile {
pub file_id: FileId,
pub reply: Sender<Metadata>,
pub reply: Sender<SftpChannelResult<Metadata>>,
}
#[derive(Debug)]
pub(crate) struct ReaddirFile {
pub file_id: FileId,
pub reply: Sender<(PathBuf, Metadata)>,
pub reply: Sender<SftpChannelResult<(PathBuf, Metadata)>>,
}
#[derive(Debug)]
pub(crate) struct FsyncFile {
pub file_id: FileId,
pub reply: Sender<()>,
pub reply: Sender<SftpChannelResult<()>>,
}
impl fmt::Debug for File {
@ -131,7 +123,7 @@ impl File {
/// Set the metadata for this handle.
///
/// See [`ssh2::File::setstat`] for more information.
pub async fn setstat(&self, metadata: Metadata) -> anyhow::Result<()> {
pub async fn setstat(&self, metadata: Metadata) -> SftpChannelResult<()> {
let (reply, rx) = bounded(1);
self.tx
.as_ref()
@ -144,14 +136,14 @@ impl File {
}),
)))
.await?;
let result = rx.recv().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<Metadata> {
pub async fn stat(&self) -> SftpChannelResult<Metadata> {
let (reply, rx) = bounded(1);
self.tx
.as_ref()
@ -163,7 +155,7 @@ impl File {
},
))))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
@ -190,7 +182,7 @@ impl File {
}),
)))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
@ -198,7 +190,7 @@ impl File {
/// (like fsync(2)).
///
/// See [`ssh2::File::fsync`] for more information.
pub async fn fsync(&self) -> anyhow::Result<()> {
pub async fn fsync(&self) -> SftpChannelResult<()> {
let (reply, rx) = bounded(1);
self.tx
.as_ref()
@ -210,7 +202,7 @@ impl File {
},
))))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
}
@ -329,7 +321,7 @@ impl smol::io::AsyncWrite for File {
}
/// Writes some bytes to the file.
async fn inner_write(tx: SessionSender, file_id: usize, data: Vec<u8>) -> anyhow::Result<()> {
async fn inner_write(tx: SessionSender, file_id: usize, data: Vec<u8>) -> SftpChannelResult<()> {
let (reply, rx) = bounded(1);
tx.send(SessionRequest::Sftp(SftpRequest::File(FileRequest::Write(
WriteFile {
@ -339,7 +331,7 @@ async fn inner_write(tx: SessionSender, file_id: usize, data: Vec<u8>) -> anyhow
},
))))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
@ -351,7 +343,7 @@ async fn inner_read(
tx: SessionSender,
file_id: usize,
max_bytes: usize,
) -> anyhow::Result<Vec<u8>> {
) -> SftpChannelResult<Vec<u8>> {
let (reply, rx) = bounded(1);
tx.send(SessionRequest::Sftp(SftpRequest::File(FileRequest::Read(
ReadFile {
@ -361,28 +353,28 @@ async fn inner_read(
},
))))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
/// Flushes the remote file
async fn inner_flush(tx: SessionSender, file_id: usize) -> anyhow::Result<()> {
async fn inner_flush(tx: SessionSender, file_id: usize) -> SftpChannelResult<()> {
let (reply, rx) = bounded(1);
tx.send(SessionRequest::Sftp(SftpRequest::File(FileRequest::Flush(
FlushFile { file_id, reply },
))))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}
/// Closes the handle to the remote file
async fn inner_close(tx: SessionSender, file_id: usize) -> anyhow::Result<()> {
async fn inner_close(tx: SessionSender, file_id: usize) -> SftpChannelResult<()> {
let (reply, rx) = bounded(1);
tx.send(SessionRequest::Sftp(SftpRequest::File(FileRequest::Close(
CloseFile { file_id, reply },
))))
.await?;
let result = rx.recv().await?;
let result = rx.recv().await??;
Ok(result)
}

View File

@ -23,14 +23,17 @@ pub enum FileType {
}
impl FileType {
/// Returns true if file is a type of directory
pub fn is_dir(self) -> bool {
matches!(self, Self::Dir)
}
/// Returns true if file is a type of regular file
pub fn is_file(self) -> bool {
matches!(self, Self::File)
}
/// Returns true if file is a type of symlink
pub fn is_symlink(self) -> bool {
matches!(self, Self::Symlink)
}
@ -164,17 +167,27 @@ impl Metadata {
self.size.unwrap_or(0)
}
/// Returns true if metadata is for a directory
pub fn is_dir(self) -> bool {
self.ty.is_dir()
}
/// Returns true if metadata is for a regular file
pub fn is_file(self) -> bool {
self.ty.is_file()
}
/// Returns true if metadata is for a symlink
pub fn is_symlink(self) -> bool {
self.ty.is_symlink()
}
/// Returns true if metadata permissions indicate file is readonly
pub fn is_readonly(self) -> bool {
self.permissions
.map(FilePermissions::is_readonly)
.unwrap_or_default()
}
}
/// Represents options to provide when opening a file or directory