mirror of
https://github.com/wez/wezterm.git
synced 2024-12-23 13:21:38 +03:00
Refactor exposed ssh2 types to wrapper types
This commit is contained in:
parent
86c307a214
commit
4a0376e6de
@ -13,4 +13,3 @@ pub use session::*;
|
|||||||
// NOTE: Re-exported as is exposed in a public API of this crate
|
// NOTE: Re-exported as is exposed in a public API of this crate
|
||||||
pub use filedescriptor::FileDescriptor;
|
pub use filedescriptor::FileDescriptor;
|
||||||
pub use portable_pty::Child;
|
pub use portable_pty::Child;
|
||||||
pub use ssh2::{FileStat, OpenFlags, OpenType, RenameFlags};
|
|
||||||
|
@ -16,7 +16,10 @@ use std::sync::{Arc, Mutex};
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
mod sftp;
|
mod sftp;
|
||||||
pub use sftp::{File, Sftp};
|
pub use sftp::{
|
||||||
|
File, FilePermissions, FileType, Metadata, OpenFileType, OpenOptions, RenameOptions, Sftp,
|
||||||
|
WriteMode,
|
||||||
|
};
|
||||||
use sftp::{FileId, FileRequest, SftpRequest};
|
use sftp::{FileId, FileRequest, SftpRequest};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -621,11 +624,15 @@ impl SessionInner {
|
|||||||
sess: &ssh2::Session,
|
sess: &ssh2::Session,
|
||||||
open_mode: &sftp::OpenMode,
|
open_mode: &sftp::OpenMode,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
let flags: ssh2::OpenFlags = open_mode.opts.into();
|
||||||
|
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(
|
let ssh_file = self.init_sftp(sess)?.open_mode(
|
||||||
open_mode.filename.as_path(),
|
open_mode.filename.as_path(),
|
||||||
open_mode.flags,
|
flags,
|
||||||
open_mode.mode,
|
mode,
|
||||||
open_mode.open_type,
|
open_type,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let (file_id, file) = self.make_file();
|
let (file_id, file) = self.make_file();
|
||||||
@ -761,12 +768,12 @@ impl SessionInner {
|
|||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let sftp::SetstatFile {
|
let sftp::SetstatFile {
|
||||||
file_id,
|
file_id,
|
||||||
stat,
|
metadata,
|
||||||
reply,
|
reply,
|
||||||
} = setstat_file;
|
} = setstat_file;
|
||||||
|
|
||||||
if let Some(file) = self.files.get_mut(file_id) {
|
if let Some(file) = self.files.get_mut(file_id) {
|
||||||
file.setstat(stat.clone())?;
|
file.setstat((*metadata).into())?;
|
||||||
}
|
}
|
||||||
reply.try_send(())?;
|
reply.try_send(())?;
|
||||||
|
|
||||||
@ -781,7 +788,7 @@ impl SessionInner {
|
|||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if let Some(file) = self.files.get_mut(&stat_file.file_id) {
|
if let Some(file) = self.files.get_mut(&stat_file.file_id) {
|
||||||
let stat = file.stat()?;
|
let stat = file.stat()?;
|
||||||
stat_file.reply.try_send(stat)?;
|
stat_file.reply.try_send(Metadata::from(stat))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -794,8 +801,8 @@ impl SessionInner {
|
|||||||
readdir_file: &sftp::ReaddirFile,
|
readdir_file: &sftp::ReaddirFile,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if let Some(file) = self.files.get_mut(&readdir_file.file_id) {
|
if let Some(file) = self.files.get_mut(&readdir_file.file_id) {
|
||||||
let result = file.readdir()?;
|
let (path, stat) = file.readdir()?;
|
||||||
readdir_file.reply.try_send(result)?;
|
readdir_file.reply.try_send((path, Metadata::from(stat)))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -819,7 +826,12 @@ impl SessionInner {
|
|||||||
///
|
///
|
||||||
/// See [`Sftp::readdir`] for more information.
|
/// See [`Sftp::readdir`] for more information.
|
||||||
pub fn readdir(&mut self, sess: &ssh2::Session, readdir: &sftp::Readdir) -> anyhow::Result<()> {
|
pub fn readdir(&mut self, sess: &ssh2::Session, readdir: &sftp::Readdir) -> anyhow::Result<()> {
|
||||||
let result = self.init_sftp(sess)?.readdir(readdir.filename.as_path())?;
|
let result = self
|
||||||
|
.init_sftp(sess)?
|
||||||
|
.readdir(readdir.filename.as_path())?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(path, stat)| (path, Metadata::from(stat)))
|
||||||
|
.collect();
|
||||||
readdir.reply.try_send(result)?;
|
readdir.reply.try_send(result)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -850,8 +862,8 @@ impl SessionInner {
|
|||||||
///
|
///
|
||||||
/// See [`Sftp::stat`] for more information.
|
/// See [`Sftp::stat`] for more information.
|
||||||
pub fn stat(&mut self, sess: &ssh2::Session, stat: &sftp::Stat) -> anyhow::Result<()> {
|
pub fn stat(&mut self, sess: &ssh2::Session, stat: &sftp::Stat) -> anyhow::Result<()> {
|
||||||
let stat_data = self.init_sftp(sess)?.stat(stat.filename.as_path())?;
|
let metadata = Metadata::from(self.init_sftp(sess)?.stat(stat.filename.as_path())?);
|
||||||
stat.reply.try_send(stat_data)?;
|
stat.reply.try_send(metadata)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -860,8 +872,8 @@ impl SessionInner {
|
|||||||
///
|
///
|
||||||
/// See [`Sftp::lstat`] for more information.
|
/// See [`Sftp::lstat`] for more information.
|
||||||
pub fn lstat(&mut self, sess: &ssh2::Session, lstat: &sftp::Lstat) -> anyhow::Result<()> {
|
pub fn lstat(&mut self, sess: &ssh2::Session, lstat: &sftp::Lstat) -> anyhow::Result<()> {
|
||||||
let stat_data = self.init_sftp(sess)?.lstat(lstat.filename.as_path())?;
|
let metadata = Metadata::from(self.init_sftp(sess)?.lstat(lstat.filename.as_path())?);
|
||||||
lstat.reply.try_send(stat_data)?;
|
lstat.reply.try_send(metadata)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -871,7 +883,7 @@ impl SessionInner {
|
|||||||
/// See [`Sftp::setstat`] for more information.
|
/// See [`Sftp::setstat`] for more information.
|
||||||
pub fn setstat(&mut self, sess: &ssh2::Session, setstat: &sftp::Setstat) -> anyhow::Result<()> {
|
pub fn setstat(&mut self, sess: &ssh2::Session, setstat: &sftp::Setstat) -> anyhow::Result<()> {
|
||||||
self.init_sftp(sess)?
|
self.init_sftp(sess)?
|
||||||
.setstat(setstat.filename.as_path(), setstat.stat.clone())?;
|
.setstat(setstat.filename.as_path(), setstat.metadata.into())?;
|
||||||
setstat.reply.try_send(())?;
|
setstat.reply.try_send(())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -923,7 +935,7 @@ impl SessionInner {
|
|||||||
self.init_sftp(sess)?.rename(
|
self.init_sftp(sess)?.rename(
|
||||||
rename.src.as_path(),
|
rename.src.as_path(),
|
||||||
rename.dst.as_path(),
|
rename.dst.as_path(),
|
||||||
rename.flags.as_ref().copied(),
|
Some(rename.opts.into()),
|
||||||
)?;
|
)?;
|
||||||
rename.reply.try_send(())?;
|
rename.reply.try_send(())?;
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use super::{SessionRequest, SessionSender};
|
use super::{SessionRequest, SessionSender};
|
||||||
use smol::channel::{bounded, Sender};
|
use smol::channel::{bounded, Sender};
|
||||||
use ssh2::{FileStat, OpenFlags, OpenType, RenameFlags};
|
use std::path::PathBuf;
|
||||||
use std::{fmt, path::PathBuf};
|
|
||||||
|
|
||||||
mod file;
|
mod file;
|
||||||
pub use file::File;
|
pub use file::File;
|
||||||
@ -10,6 +9,11 @@ pub(crate) use file::{
|
|||||||
StatFile, WriteFile,
|
StatFile, WriteFile,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod types;
|
||||||
|
pub use types::{
|
||||||
|
FilePermissions, FileType, Metadata, OpenFileType, OpenOptions, RenameOptions, WriteMode,
|
||||||
|
};
|
||||||
|
|
||||||
/// Represents an open sftp channel for performing filesystem operations
|
/// Represents an open sftp channel for performing filesystem operations
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Sftp {
|
pub struct Sftp {
|
||||||
@ -23,17 +27,14 @@ impl Sftp {
|
|||||||
pub async fn open_mode(
|
pub async fn open_mode(
|
||||||
&self,
|
&self,
|
||||||
filename: impl Into<PathBuf>,
|
filename: impl Into<PathBuf>,
|
||||||
flags: OpenFlags,
|
opts: OpenOptions,
|
||||||
mode: i32,
|
|
||||||
open_type: OpenType,
|
|
||||||
) -> anyhow::Result<File> {
|
) -> anyhow::Result<File> {
|
||||||
let (reply, rx) = bounded(1);
|
let (reply, rx) = bounded(1);
|
||||||
|
|
||||||
self.tx
|
self.tx
|
||||||
.send(SessionRequest::Sftp(SftpRequest::OpenMode(OpenMode {
|
.send(SessionRequest::Sftp(SftpRequest::OpenMode(OpenMode {
|
||||||
filename: filename.into(),
|
filename: filename.into(),
|
||||||
flags,
|
opts,
|
||||||
mode,
|
|
||||||
open_type,
|
|
||||||
reply,
|
reply,
|
||||||
})))
|
})))
|
||||||
.await?;
|
.await?;
|
||||||
@ -99,7 +100,7 @@ impl Sftp {
|
|||||||
pub async fn readdir(
|
pub async fn readdir(
|
||||||
&self,
|
&self,
|
||||||
filename: impl Into<PathBuf>,
|
filename: impl Into<PathBuf>,
|
||||||
) -> anyhow::Result<Vec<(PathBuf, FileStat)>> {
|
) -> anyhow::Result<Vec<(PathBuf, Metadata)>> {
|
||||||
let (reply, rx) = bounded(1);
|
let (reply, rx) = bounded(1);
|
||||||
self.tx
|
self.tx
|
||||||
.send(SessionRequest::Sftp(SftpRequest::Readdir(Readdir {
|
.send(SessionRequest::Sftp(SftpRequest::Readdir(Readdir {
|
||||||
@ -145,7 +146,7 @@ impl Sftp {
|
|||||||
/// Get the metadata for a file, performed by stat(2).
|
/// Get the metadata for a file, performed by stat(2).
|
||||||
///
|
///
|
||||||
/// See [`Sftp::stat`] for more information.
|
/// See [`Sftp::stat`] for more information.
|
||||||
pub async fn stat(&self, filename: impl Into<PathBuf>) -> anyhow::Result<FileStat> {
|
pub async fn stat(&self, filename: impl Into<PathBuf>) -> anyhow::Result<Metadata> {
|
||||||
let (reply, rx) = bounded(1);
|
let (reply, rx) = bounded(1);
|
||||||
self.tx
|
self.tx
|
||||||
.send(SessionRequest::Sftp(SftpRequest::Stat(Stat {
|
.send(SessionRequest::Sftp(SftpRequest::Stat(Stat {
|
||||||
@ -160,7 +161,7 @@ impl Sftp {
|
|||||||
/// Get the metadata for a file, performed by lstat(2).
|
/// Get the metadata for a file, performed by lstat(2).
|
||||||
///
|
///
|
||||||
/// See [`Sftp::lstat`] for more information.
|
/// See [`Sftp::lstat`] for more information.
|
||||||
pub async fn lstat(&self, filename: impl Into<PathBuf>) -> anyhow::Result<FileStat> {
|
pub async fn lstat(&self, filename: impl Into<PathBuf>) -> anyhow::Result<Metadata> {
|
||||||
let (reply, rx) = bounded(1);
|
let (reply, rx) = bounded(1);
|
||||||
self.tx
|
self.tx
|
||||||
.send(SessionRequest::Sftp(SftpRequest::Lstat(Lstat {
|
.send(SessionRequest::Sftp(SftpRequest::Lstat(Lstat {
|
||||||
@ -178,13 +179,13 @@ impl Sftp {
|
|||||||
pub async fn setstat(
|
pub async fn setstat(
|
||||||
&self,
|
&self,
|
||||||
filename: impl Into<PathBuf>,
|
filename: impl Into<PathBuf>,
|
||||||
stat: FileStat,
|
metadata: Metadata,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let (reply, rx) = bounded(1);
|
let (reply, rx) = bounded(1);
|
||||||
self.tx
|
self.tx
|
||||||
.send(SessionRequest::Sftp(SftpRequest::Setstat(Setstat {
|
.send(SessionRequest::Sftp(SftpRequest::Setstat(Setstat {
|
||||||
filename: filename.into(),
|
filename: filename.into(),
|
||||||
stat,
|
metadata,
|
||||||
reply,
|
reply,
|
||||||
})))
|
})))
|
||||||
.await?;
|
.await?;
|
||||||
@ -249,14 +250,14 @@ impl Sftp {
|
|||||||
&self,
|
&self,
|
||||||
src: impl Into<PathBuf>,
|
src: impl Into<PathBuf>,
|
||||||
dst: impl Into<PathBuf>,
|
dst: impl Into<PathBuf>,
|
||||||
flags: Option<RenameFlags>,
|
opts: RenameOptions,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let (reply, rx) = bounded(1);
|
let (reply, rx) = bounded(1);
|
||||||
self.tx
|
self.tx
|
||||||
.send(SessionRequest::Sftp(SftpRequest::Rename(Rename {
|
.send(SessionRequest::Sftp(SftpRequest::Rename(Rename {
|
||||||
src: src.into(),
|
src: src.into(),
|
||||||
dst: dst.into(),
|
dst: dst.into(),
|
||||||
flags,
|
opts,
|
||||||
reply,
|
reply,
|
||||||
})))
|
})))
|
||||||
.await?;
|
.await?;
|
||||||
@ -302,33 +303,13 @@ pub(crate) enum SftpRequest {
|
|||||||
File(FileRequest),
|
File(FileRequest),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub(crate) struct OpenMode {
|
pub(crate) struct OpenMode {
|
||||||
pub filename: PathBuf,
|
pub filename: PathBuf,
|
||||||
pub flags: OpenFlags,
|
pub opts: OpenOptions,
|
||||||
pub mode: i32,
|
|
||||||
pub open_type: OpenType,
|
|
||||||
pub reply: Sender<File>,
|
pub reply: Sender<File>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for OpenMode {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
// NOTE: OpenType does not implement debug,
|
|
||||||
// so we create a string representation
|
|
||||||
let open_type_string = match self.open_type {
|
|
||||||
OpenType::Dir => String::from("OpenType::Dir"),
|
|
||||||
OpenType::File => String::from("OpenType::File"),
|
|
||||||
};
|
|
||||||
|
|
||||||
f.debug_struct("OpenMode")
|
|
||||||
.field("filename", &self.filename)
|
|
||||||
.field("flags", &self.flags)
|
|
||||||
.field("mode", &self.mode)
|
|
||||||
.field("open_type", &open_type_string)
|
|
||||||
.field("reply", &self.reply)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Open {
|
pub(crate) struct Open {
|
||||||
pub filename: PathBuf,
|
pub filename: PathBuf,
|
||||||
@ -350,7 +331,7 @@ pub(crate) struct Opendir {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Readdir {
|
pub(crate) struct Readdir {
|
||||||
pub filename: PathBuf,
|
pub filename: PathBuf,
|
||||||
pub reply: Sender<Vec<(PathBuf, FileStat)>>,
|
pub reply: Sender<Vec<(PathBuf, Metadata)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -369,19 +350,19 @@ pub(crate) struct Rmdir {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Stat {
|
pub(crate) struct Stat {
|
||||||
pub filename: PathBuf,
|
pub filename: PathBuf,
|
||||||
pub reply: Sender<FileStat>,
|
pub reply: Sender<Metadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Lstat {
|
pub(crate) struct Lstat {
|
||||||
pub filename: PathBuf,
|
pub filename: PathBuf,
|
||||||
pub reply: Sender<FileStat>,
|
pub reply: Sender<Metadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Setstat {
|
pub(crate) struct Setstat {
|
||||||
pub filename: PathBuf,
|
pub filename: PathBuf,
|
||||||
pub stat: FileStat,
|
pub metadata: Metadata,
|
||||||
pub reply: Sender<()>,
|
pub reply: Sender<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,7 +389,7 @@ pub(crate) struct Realpath {
|
|||||||
pub(crate) struct Rename {
|
pub(crate) struct Rename {
|
||||||
pub src: PathBuf,
|
pub src: PathBuf,
|
||||||
pub dst: PathBuf,
|
pub dst: PathBuf,
|
||||||
pub flags: Option<RenameFlags>,
|
pub opts: RenameOptions,
|
||||||
pub reply: Sender<()>,
|
pub reply: Sender<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{FileStat, SessionRequest, SessionSender, SftpRequest};
|
use super::{Metadata, SessionRequest, SessionSender, SftpRequest};
|
||||||
use smol::{
|
use smol::{
|
||||||
channel::{bounded, Sender},
|
channel::{bounded, Sender},
|
||||||
future::FutureExt,
|
future::FutureExt,
|
||||||
@ -70,20 +70,20 @@ pub(crate) struct FlushFile {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct SetstatFile {
|
pub(crate) struct SetstatFile {
|
||||||
pub file_id: FileId,
|
pub file_id: FileId,
|
||||||
pub stat: FileStat,
|
pub metadata: Metadata,
|
||||||
pub reply: Sender<()>,
|
pub reply: Sender<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct StatFile {
|
pub(crate) struct StatFile {
|
||||||
pub file_id: FileId,
|
pub file_id: FileId,
|
||||||
pub reply: Sender<FileStat>,
|
pub reply: Sender<Metadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct ReaddirFile {
|
pub(crate) struct ReaddirFile {
|
||||||
pub file_id: FileId,
|
pub file_id: FileId,
|
||||||
pub reply: Sender<(PathBuf, FileStat)>,
|
pub reply: Sender<(PathBuf, Metadata)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -131,7 +131,7 @@ impl File {
|
|||||||
/// Set the metadata for this handle.
|
/// Set the metadata for this handle.
|
||||||
///
|
///
|
||||||
/// See [`ssh2::File::setstat`] for more information.
|
/// See [`ssh2::File::setstat`] for more information.
|
||||||
pub async fn setstat(&self, stat: FileStat) -> anyhow::Result<()> {
|
pub async fn setstat(&self, metadata: Metadata) -> anyhow::Result<()> {
|
||||||
let (reply, rx) = bounded(1);
|
let (reply, rx) = bounded(1);
|
||||||
self.tx
|
self.tx
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -139,7 +139,7 @@ impl File {
|
|||||||
.send(SessionRequest::Sftp(SftpRequest::File(
|
.send(SessionRequest::Sftp(SftpRequest::File(
|
||||||
FileRequest::Setstat(SetstatFile {
|
FileRequest::Setstat(SetstatFile {
|
||||||
file_id: self.file_id,
|
file_id: self.file_id,
|
||||||
stat,
|
metadata,
|
||||||
reply,
|
reply,
|
||||||
}),
|
}),
|
||||||
)))
|
)))
|
||||||
@ -151,7 +151,7 @@ impl File {
|
|||||||
/// Get the metadata for this handle.
|
/// Get the metadata for this handle.
|
||||||
///
|
///
|
||||||
/// See [`ssh2::File::stat`] for more information.
|
/// See [`ssh2::File::stat`] for more information.
|
||||||
pub async fn stat(&self) -> anyhow::Result<FileStat> {
|
pub async fn stat(&self) -> anyhow::Result<Metadata> {
|
||||||
let (reply, rx) = bounded(1);
|
let (reply, rx) = bounded(1);
|
||||||
self.tx
|
self.tx
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -178,7 +178,7 @@ impl File {
|
|||||||
/// files in this directory.
|
/// files in this directory.
|
||||||
///
|
///
|
||||||
/// See [`ssh2::File::readdir`] for more information.
|
/// See [`ssh2::File::readdir`] for more information.
|
||||||
pub async fn readdir(&self) -> anyhow::Result<(PathBuf, FileStat)> {
|
pub async fn readdir(&self) -> anyhow::Result<(PathBuf, Metadata)> {
|
||||||
let (reply, rx) = bounded(1);
|
let (reply, rx) = bounded(1);
|
||||||
self.tx
|
self.tx
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
336
wezterm-ssh/src/session/sftp/types.rs
Normal file
336
wezterm-ssh/src/session/sftp/types.rs
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
const OCTAL_FT_DIR: u32 = 0o040000;
|
||||||
|
const OCTAL_FT_FILE: u32 = 0o100000;
|
||||||
|
const OCTAL_FT_SYMLINK: u32 = 0o120000;
|
||||||
|
const OCTAL_FT_OTHER: u32 = 0;
|
||||||
|
|
||||||
|
const OCTAL_PERM_OWNER_READ: u32 = 0o400;
|
||||||
|
const OCTAL_PERM_OWNER_WRITE: u32 = 0o200;
|
||||||
|
const OCTAL_PERM_OWNER_EXEC: u32 = 0o100;
|
||||||
|
const OCTAL_PERM_GROUP_READ: u32 = 0o40;
|
||||||
|
const OCTAL_PERM_GROUP_WRITE: u32 = 0o20;
|
||||||
|
const OCTAL_PERM_GROUP_EXEC: u32 = 0o10;
|
||||||
|
const OCTAL_PERM_OTHER_READ: u32 = 0o4;
|
||||||
|
const OCTAL_PERM_OTHER_WRITE: u32 = 0o2;
|
||||||
|
const OCTAL_PERM_OTHER_EXEC: u32 = 0o1;
|
||||||
|
|
||||||
|
/// Represents the type associated with a remote file
|
||||||
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
pub enum FileType {
|
||||||
|
Dir,
|
||||||
|
File,
|
||||||
|
Symlink,
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileType {
|
||||||
|
pub fn is_dir(self) -> bool {
|
||||||
|
matches!(self, Self::Dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_file(self) -> bool {
|
||||||
|
matches!(self, Self::File)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_symlink(self) -> bool {
|
||||||
|
matches!(self, Self::Symlink)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create from a unix mode bitset
|
||||||
|
pub fn from_unix_mode(mode: u32) -> Self {
|
||||||
|
if mode & OCTAL_FT_DIR != 0 {
|
||||||
|
Self::Dir
|
||||||
|
} else if mode & OCTAL_FT_FILE != 0 {
|
||||||
|
Self::File
|
||||||
|
} else if mode & OCTAL_FT_SYMLINK != 0 {
|
||||||
|
Self::Symlink
|
||||||
|
} else {
|
||||||
|
Self::Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert to a unix mode bitset
|
||||||
|
pub fn to_unix_mode(self) -> u32 {
|
||||||
|
match self {
|
||||||
|
FileType::Dir => OCTAL_FT_DIR,
|
||||||
|
FileType::File => OCTAL_FT_FILE,
|
||||||
|
FileType::Symlink => OCTAL_FT_SYMLINK,
|
||||||
|
FileType::Other => OCTAL_FT_OTHER,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents permissions associated with a remote file
|
||||||
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
pub struct FilePermissions {
|
||||||
|
pub owner_read: bool,
|
||||||
|
pub owner_write: bool,
|
||||||
|
pub owner_exec: bool,
|
||||||
|
|
||||||
|
pub group_read: bool,
|
||||||
|
pub group_write: bool,
|
||||||
|
pub group_exec: bool,
|
||||||
|
|
||||||
|
pub other_read: bool,
|
||||||
|
pub other_write: bool,
|
||||||
|
pub other_exec: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilePermissions {
|
||||||
|
pub fn is_readonly(self) -> bool {
|
||||||
|
!(self.owner_read || self.group_read || self.other_read)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create from a unix mode bitset
|
||||||
|
pub fn from_unix_mode(mode: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
owner_read: mode | OCTAL_PERM_OWNER_READ != 0,
|
||||||
|
owner_write: mode | OCTAL_PERM_OWNER_WRITE != 0,
|
||||||
|
owner_exec: mode | OCTAL_PERM_OWNER_EXEC != 0,
|
||||||
|
group_read: mode | OCTAL_PERM_GROUP_READ != 0,
|
||||||
|
group_write: mode | OCTAL_PERM_GROUP_WRITE != 0,
|
||||||
|
group_exec: mode | OCTAL_PERM_GROUP_EXEC != 0,
|
||||||
|
other_read: mode | OCTAL_PERM_OTHER_READ != 0,
|
||||||
|
other_write: mode | OCTAL_PERM_OTHER_WRITE != 0,
|
||||||
|
other_exec: mode | OCTAL_PERM_OTHER_EXEC != 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert to a unix mode bitset
|
||||||
|
pub fn to_unix_mode(self) -> u32 {
|
||||||
|
let mut mode: u32 = 0;
|
||||||
|
|
||||||
|
if self.owner_read {
|
||||||
|
mode |= OCTAL_PERM_OWNER_READ;
|
||||||
|
}
|
||||||
|
if self.owner_write {
|
||||||
|
mode |= OCTAL_PERM_OWNER_WRITE;
|
||||||
|
}
|
||||||
|
if self.owner_exec {
|
||||||
|
mode |= OCTAL_PERM_OWNER_EXEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.group_read {
|
||||||
|
mode |= OCTAL_PERM_GROUP_READ;
|
||||||
|
}
|
||||||
|
if self.group_write {
|
||||||
|
mode |= OCTAL_PERM_GROUP_WRITE;
|
||||||
|
}
|
||||||
|
if self.group_exec {
|
||||||
|
mode |= OCTAL_PERM_GROUP_EXEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.other_read {
|
||||||
|
mode |= OCTAL_PERM_OTHER_READ;
|
||||||
|
}
|
||||||
|
if self.other_write {
|
||||||
|
mode |= OCTAL_PERM_OTHER_WRITE;
|
||||||
|
}
|
||||||
|
if self.other_exec {
|
||||||
|
mode |= OCTAL_PERM_OTHER_EXEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
mode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents metadata about a remote file
|
||||||
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
pub struct Metadata {
|
||||||
|
/// Type of the remote file
|
||||||
|
pub ty: FileType,
|
||||||
|
|
||||||
|
/// Permissions associated with the file
|
||||||
|
pub permissions: Option<FilePermissions>,
|
||||||
|
|
||||||
|
/// File size, in bytes of the file
|
||||||
|
pub size: Option<u64>,
|
||||||
|
|
||||||
|
/// Owner ID of the file
|
||||||
|
pub uid: Option<u32>,
|
||||||
|
|
||||||
|
/// Owning group of the file
|
||||||
|
pub gid: Option<u32>,
|
||||||
|
|
||||||
|
/// Last access time of the file
|
||||||
|
pub accessed: Option<u64>,
|
||||||
|
|
||||||
|
/// Last modification time of the file
|
||||||
|
pub modified: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Metadata {
|
||||||
|
/// Returns the size of the file, in bytes (or zero if unknown)
|
||||||
|
pub fn len(self) -> u64 {
|
||||||
|
self.size.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_dir(self) -> bool {
|
||||||
|
self.ty.is_dir()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_file(self) -> bool {
|
||||||
|
self.ty.is_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_symlink(self) -> bool {
|
||||||
|
self.ty.is_symlink()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents options to provide when opening a file or directory
|
||||||
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
pub struct OpenOptions {
|
||||||
|
/// If true, opens a file (or directory) for reading
|
||||||
|
pub read: bool,
|
||||||
|
|
||||||
|
/// If provided, opens a file for writing or appending
|
||||||
|
pub write: Option<WriteMode>,
|
||||||
|
|
||||||
|
/// Unix mode that is used when creating a new file
|
||||||
|
pub mode: i32,
|
||||||
|
|
||||||
|
/// Whether opening a file or directory
|
||||||
|
pub ty: OpenFileType,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents whether opening a file or directory
|
||||||
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
pub enum OpenFileType {
|
||||||
|
Dir,
|
||||||
|
File,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents different writing modes for opening a file
|
||||||
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
pub enum WriteMode {
|
||||||
|
/// Append data to end of file instead of overwriting it
|
||||||
|
Append,
|
||||||
|
|
||||||
|
/// Overwrite an existing file when opening to write it
|
||||||
|
Write,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents options to provide when renaming a file or directory
|
||||||
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
pub struct RenameOptions {
|
||||||
|
/// Overwrite the destination if it exists, otherwise fail
|
||||||
|
pub overwrite: bool,
|
||||||
|
|
||||||
|
/// Request atomic rename operation
|
||||||
|
pub atomic: bool,
|
||||||
|
|
||||||
|
/// Request native system calls
|
||||||
|
pub native: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RenameOptions {
|
||||||
|
/// Default is to enable all options
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
overwrite: true,
|
||||||
|
atomic: true,
|
||||||
|
native: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains libssh2-specific implementations
|
||||||
|
mod ssh2_impl {
|
||||||
|
use super::*;
|
||||||
|
use ::ssh2::{
|
||||||
|
FileStat as Ssh2FileStat, FileType as Ssh2FileType, OpenFlags as Ssh2OpenFlags,
|
||||||
|
OpenType as Ssh2OpenType, RenameFlags as Ssh2RenameFlags,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl From<OpenFileType> for Ssh2OpenType {
|
||||||
|
fn from(ty: OpenFileType) -> Self {
|
||||||
|
match ty {
|
||||||
|
OpenFileType::Dir => Self::Dir,
|
||||||
|
OpenFileType::File => Self::File,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RenameOptions> for Ssh2RenameFlags {
|
||||||
|
fn from(opts: RenameOptions) -> Self {
|
||||||
|
let mut flags = Self::empty();
|
||||||
|
|
||||||
|
if opts.overwrite {
|
||||||
|
flags |= Self::OVERWRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.atomic {
|
||||||
|
flags |= Self::ATOMIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.native {
|
||||||
|
flags |= Self::NATIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<OpenOptions> for Ssh2OpenFlags {
|
||||||
|
fn from(opts: OpenOptions) -> Self {
|
||||||
|
let mut flags = Self::empty();
|
||||||
|
|
||||||
|
if opts.read {
|
||||||
|
flags |= Self::READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
match opts.write {
|
||||||
|
Some(WriteMode::Write) => flags |= Self::WRITE | Self::TRUNCATE,
|
||||||
|
Some(WriteMode::Append) => flags |= Self::WRITE | Self::APPEND | Self::CREATE,
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Ssh2FileType> for FileType {
|
||||||
|
fn from(ft: Ssh2FileType) -> Self {
|
||||||
|
if ft.is_dir() {
|
||||||
|
Self::Dir
|
||||||
|
} else if ft.is_file() {
|
||||||
|
Self::File
|
||||||
|
} else if ft.is_symlink() {
|
||||||
|
Self::Symlink
|
||||||
|
} else {
|
||||||
|
Self::Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Ssh2FileStat> for Metadata {
|
||||||
|
fn from(stat: Ssh2FileStat) -> Self {
|
||||||
|
Self {
|
||||||
|
ty: FileType::from(stat.file_type()),
|
||||||
|
permissions: stat.perm.map(FilePermissions::from_unix_mode),
|
||||||
|
size: stat.size,
|
||||||
|
uid: stat.uid,
|
||||||
|
gid: stat.gid,
|
||||||
|
accessed: stat.atime,
|
||||||
|
modified: stat.mtime,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Metadata> for Ssh2FileStat {
|
||||||
|
fn from(metadata: Metadata) -> Self {
|
||||||
|
let ft = metadata.ty;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
perm: metadata
|
||||||
|
.permissions
|
||||||
|
.map(|p| p.to_unix_mode() | ft.to_unix_mode()),
|
||||||
|
size: metadata.size,
|
||||||
|
uid: metadata.uid,
|
||||||
|
gid: metadata.gid,
|
||||||
|
atime: metadata.accessed,
|
||||||
|
mtime: metadata.modified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,8 @@ use crate::sshd::session;
|
|||||||
use assert_fs::{prelude::*, TempDir};
|
use assert_fs::{prelude::*, TempDir};
|
||||||
use predicates::prelude::*;
|
use predicates::prelude::*;
|
||||||
use rstest::*;
|
use rstest::*;
|
||||||
use ssh2::FileType;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use wezterm_ssh::Session;
|
use wezterm_ssh::{FileType, Session};
|
||||||
|
|
||||||
// Sftp file tests
|
// Sftp file tests
|
||||||
mod file;
|
mod file;
|
||||||
@ -51,7 +50,7 @@ async fn readdir_should_return_list_of_directories_files_and_symlinks(#[future]
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to read directory")
|
.expect("Failed to read directory")
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(p, s)| (p, file_type_to_str(s.file_type())))
|
.map(|(p, s)| (p, file_type_to_str(s.ty)))
|
||||||
.collect::<Vec<(PathBuf, &'static str)>>();
|
.collect::<Vec<(PathBuf, &'static str)>>();
|
||||||
contents.sort_unstable_by_key(|(p, _)| p.to_path_buf());
|
contents.sort_unstable_by_key(|(p, _)| p.to_path_buf());
|
||||||
|
|
||||||
@ -226,16 +225,16 @@ async fn stat_should_return_metadata_about_the_file_pointed_to_by_a_symlink(
|
|||||||
let link = temp.child("link");
|
let link = temp.child("link");
|
||||||
link.symlink_to_file(file.path()).unwrap();
|
link.symlink_to_file(file.path()).unwrap();
|
||||||
|
|
||||||
let stat = session
|
let metadata = session
|
||||||
.sftp()
|
.sftp()
|
||||||
.stat(link.path())
|
.stat(link.path())
|
||||||
.await
|
.await
|
||||||
.expect("Failed to stat symlink");
|
.expect("Failed to stat symlink");
|
||||||
|
|
||||||
// Verify that file stat makes sense
|
// Verify that file stat makes sense
|
||||||
assert!(stat.is_file(), "Invalid file stat returned");
|
assert!(metadata.is_file(), "Invalid file stat returned");
|
||||||
assert!(stat.file_type().is_file(), "Invalid file stat returned");
|
assert!(metadata.ty.is_file(), "Invalid file stat returned");
|
||||||
assert!(!stat.file_type().is_symlink(), "Invalid file stat returned");
|
assert!(!metadata.ty.is_symlink(), "Invalid file stat returned");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@ -252,16 +251,16 @@ async fn stat_should_return_metadata_about_the_dir_pointed_to_by_a_symlink(
|
|||||||
let link = temp.child("link");
|
let link = temp.child("link");
|
||||||
link.symlink_to_dir(dir.path()).unwrap();
|
link.symlink_to_dir(dir.path()).unwrap();
|
||||||
|
|
||||||
let stat = session
|
let metadata = session
|
||||||
.sftp()
|
.sftp()
|
||||||
.stat(link.path())
|
.stat(link.path())
|
||||||
.await
|
.await
|
||||||
.expect("Failed to stat symlink");
|
.expect("Failed to stat symlink");
|
||||||
|
|
||||||
// Verify that file stat makes sense
|
// Verify that file stat makes sense
|
||||||
assert!(stat.is_dir(), "Invalid file stat returned");
|
assert!(metadata.is_dir(), "Invalid file stat returned");
|
||||||
assert!(stat.file_type().is_dir(), "Invalid file stat returned");
|
assert!(metadata.ty.is_dir(), "Invalid file stat returned");
|
||||||
assert!(!stat.file_type().is_symlink(), "Invalid file stat returned");
|
assert!(!metadata.ty.is_symlink(), "Invalid file stat returned");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@ -325,19 +324,16 @@ async fn lstat_should_return_metadata_about_symlink_pointing_to_a_file(#[future]
|
|||||||
let link = temp.child("link");
|
let link = temp.child("link");
|
||||||
link.symlink_to_file(file.path()).unwrap();
|
link.symlink_to_file(file.path()).unwrap();
|
||||||
|
|
||||||
let lstat = session
|
let metadata = session
|
||||||
.sftp()
|
.sftp()
|
||||||
.lstat(link.path())
|
.lstat(link.path())
|
||||||
.await
|
.await
|
||||||
.expect("Failed to lstat symlink");
|
.expect("Failed to lstat symlink");
|
||||||
|
|
||||||
// Verify that file lstat makes sense
|
// Verify that file lstat makes sense
|
||||||
assert!(!lstat.is_file(), "Invalid file lstat returned");
|
assert!(!metadata.is_file(), "Invalid file lstat returned");
|
||||||
assert!(!lstat.file_type().is_file(), "Invalid file lstat returned");
|
assert!(!metadata.ty.is_file(), "Invalid file lstat returned");
|
||||||
assert!(
|
assert!(metadata.ty.is_symlink(), "Invalid file lstat returned");
|
||||||
lstat.file_type().is_symlink(),
|
|
||||||
"Invalid file lstat returned"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@ -354,19 +350,16 @@ async fn lstat_should_return_metadata_about_symlink_pointing_to_a_directory(
|
|||||||
let link = temp.child("link");
|
let link = temp.child("link");
|
||||||
link.symlink_to_dir(dir.path()).unwrap();
|
link.symlink_to_dir(dir.path()).unwrap();
|
||||||
|
|
||||||
let lstat = session
|
let metadata = session
|
||||||
.sftp()
|
.sftp()
|
||||||
.lstat(link.path())
|
.lstat(link.path())
|
||||||
.await
|
.await
|
||||||
.expect("Failed to lstat symlink");
|
.expect("Failed to lstat symlink");
|
||||||
|
|
||||||
// Verify that file lstat makes sense
|
// Verify that file lstat makes sense
|
||||||
assert!(!lstat.is_dir(), "Invalid file lstat returned");
|
assert!(!metadata.is_dir(), "Invalid file lstat returned");
|
||||||
assert!(!lstat.file_type().is_dir(), "Invalid file lstat returned");
|
assert!(!metadata.ty.is_dir(), "Invalid file lstat returned");
|
||||||
assert!(
|
assert!(metadata.ty.is_symlink(), "Invalid file lstat returned");
|
||||||
lstat.file_type().is_symlink(),
|
|
||||||
"Invalid file lstat returned"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@ -586,7 +579,7 @@ async fn rename_should_support_singular_file(#[future] session: Session) {
|
|||||||
|
|
||||||
session
|
session
|
||||||
.sftp()
|
.sftp()
|
||||||
.rename(file.path(), dst.path(), None)
|
.rename(file.path(), dst.path(), Default::default())
|
||||||
.await
|
.await
|
||||||
.expect("Failed to rename file");
|
.expect("Failed to rename file");
|
||||||
|
|
||||||
@ -612,7 +605,7 @@ async fn rename_should_support_dirtectory(#[future] session: Session) {
|
|||||||
|
|
||||||
session
|
session
|
||||||
.sftp()
|
.sftp()
|
||||||
.rename(dir.path(), dst.path(), None)
|
.rename(dir.path(), dst.path(), Default::default())
|
||||||
.await
|
.await
|
||||||
.expect("Failed to rename directory");
|
.expect("Failed to rename directory");
|
||||||
|
|
||||||
@ -637,7 +630,7 @@ async fn rename_should_fail_if_source_path_missing(#[future] session: Session) {
|
|||||||
|
|
||||||
let result = session
|
let result = session
|
||||||
.sftp()
|
.sftp()
|
||||||
.rename(missing.path(), dst.path(), None)
|
.rename(missing.path(), dst.path(), Default::default())
|
||||||
.await;
|
.await;
|
||||||
assert!(
|
assert!(
|
||||||
result.is_err(),
|
result.is_err(),
|
||||||
|
@ -47,8 +47,8 @@ async fn readdir_should_retrieve_next_dir_entry(#[future] session: Session) {
|
|||||||
|
|
||||||
// Collect all of the directory contents (. and .. are included)
|
// Collect all of the directory contents (. and .. are included)
|
||||||
let mut contents = Vec::new();
|
let mut contents = Vec::new();
|
||||||
while let Ok((path, stat)) = remote_dir.readdir().await {
|
while let Ok((path, metadata)) = remote_dir.readdir().await {
|
||||||
let ft = stat.file_type();
|
let ft = metadata.ty;
|
||||||
contents.push((
|
contents.push((
|
||||||
path,
|
path,
|
||||||
if ft.is_dir() {
|
if ft.is_dir() {
|
||||||
|
Loading…
Reference in New Issue
Block a user