1
1
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:
Chip Senkbeil 2021-09-26 14:32:54 -05:00 committed by Wez Furlong
parent 86c307a214
commit 4a0376e6de
7 changed files with 418 additions and 97 deletions

View File

@ -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};

View File

@ -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(())?;

View File

@ -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<()>,
} }

View File

@ -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()

View 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,
}
}
}
}

View File

@ -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(),

View File

@ -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() {