mirror of
https://github.com/wez/wezterm.git
synced 2024-12-23 05:12:40 +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
|
||||
pub use filedescriptor::FileDescriptor;
|
||||
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;
|
||||
|
||||
mod sftp;
|
||||
pub use sftp::{File, Sftp};
|
||||
pub use sftp::{
|
||||
File, FilePermissions, FileType, Metadata, OpenFileType, OpenOptions, RenameOptions, Sftp,
|
||||
WriteMode,
|
||||
};
|
||||
use sftp::{FileId, FileRequest, SftpRequest};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -621,11 +624,15 @@ impl SessionInner {
|
||||
sess: &ssh2::Session,
|
||||
open_mode: &sftp::OpenMode,
|
||||
) -> 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(
|
||||
open_mode.filename.as_path(),
|
||||
open_mode.flags,
|
||||
open_mode.mode,
|
||||
open_mode.open_type,
|
||||
flags,
|
||||
mode,
|
||||
open_type,
|
||||
)?;
|
||||
|
||||
let (file_id, file) = self.make_file();
|
||||
@ -761,12 +768,12 @@ impl SessionInner {
|
||||
) -> anyhow::Result<()> {
|
||||
let sftp::SetstatFile {
|
||||
file_id,
|
||||
stat,
|
||||
metadata,
|
||||
reply,
|
||||
} = setstat_file;
|
||||
|
||||
if let Some(file) = self.files.get_mut(file_id) {
|
||||
file.setstat(stat.clone())?;
|
||||
file.setstat((*metadata).into())?;
|
||||
}
|
||||
reply.try_send(())?;
|
||||
|
||||
@ -781,7 +788,7 @@ impl SessionInner {
|
||||
) -> anyhow::Result<()> {
|
||||
if let Some(file) = self.files.get_mut(&stat_file.file_id) {
|
||||
let stat = file.stat()?;
|
||||
stat_file.reply.try_send(stat)?;
|
||||
stat_file.reply.try_send(Metadata::from(stat))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -794,8 +801,8 @@ impl SessionInner {
|
||||
readdir_file: &sftp::ReaddirFile,
|
||||
) -> anyhow::Result<()> {
|
||||
if let Some(file) = self.files.get_mut(&readdir_file.file_id) {
|
||||
let result = file.readdir()?;
|
||||
readdir_file.reply.try_send(result)?;
|
||||
let (path, stat) = file.readdir()?;
|
||||
readdir_file.reply.try_send((path, Metadata::from(stat)))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -819,7 +826,12 @@ 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())?;
|
||||
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)?;
|
||||
|
||||
Ok(())
|
||||
@ -850,8 +862,8 @@ impl SessionInner {
|
||||
///
|
||||
/// See [`Sftp::stat`] for more information.
|
||||
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())?;
|
||||
stat.reply.try_send(stat_data)?;
|
||||
let metadata = Metadata::from(self.init_sftp(sess)?.stat(stat.filename.as_path())?);
|
||||
stat.reply.try_send(metadata)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -860,8 +872,8 @@ impl SessionInner {
|
||||
///
|
||||
/// See [`Sftp::lstat`] for more information.
|
||||
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())?;
|
||||
lstat.reply.try_send(stat_data)?;
|
||||
let metadata = Metadata::from(self.init_sftp(sess)?.lstat(lstat.filename.as_path())?);
|
||||
lstat.reply.try_send(metadata)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -871,7 +883,7 @@ 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.stat.clone())?;
|
||||
.setstat(setstat.filename.as_path(), setstat.metadata.into())?;
|
||||
setstat.reply.try_send(())?;
|
||||
|
||||
Ok(())
|
||||
@ -923,7 +935,7 @@ impl SessionInner {
|
||||
self.init_sftp(sess)?.rename(
|
||||
rename.src.as_path(),
|
||||
rename.dst.as_path(),
|
||||
rename.flags.as_ref().copied(),
|
||||
Some(rename.opts.into()),
|
||||
)?;
|
||||
rename.reply.try_send(())?;
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
use super::{SessionRequest, SessionSender};
|
||||
use smol::channel::{bounded, Sender};
|
||||
use ssh2::{FileStat, OpenFlags, OpenType, RenameFlags};
|
||||
use std::{fmt, path::PathBuf};
|
||||
use std::path::PathBuf;
|
||||
|
||||
mod file;
|
||||
pub use file::File;
|
||||
@ -10,6 +9,11 @@ pub(crate) use file::{
|
||||
StatFile, WriteFile,
|
||||
};
|
||||
|
||||
mod types;
|
||||
pub use types::{
|
||||
FilePermissions, FileType, Metadata, OpenFileType, OpenOptions, RenameOptions, WriteMode,
|
||||
};
|
||||
|
||||
/// Represents an open sftp channel for performing filesystem operations
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Sftp {
|
||||
@ -23,17 +27,14 @@ impl Sftp {
|
||||
pub async fn open_mode(
|
||||
&self,
|
||||
filename: impl Into<PathBuf>,
|
||||
flags: OpenFlags,
|
||||
mode: i32,
|
||||
open_type: OpenType,
|
||||
opts: OpenOptions,
|
||||
) -> anyhow::Result<File> {
|
||||
let (reply, rx) = bounded(1);
|
||||
|
||||
self.tx
|
||||
.send(SessionRequest::Sftp(SftpRequest::OpenMode(OpenMode {
|
||||
filename: filename.into(),
|
||||
flags,
|
||||
mode,
|
||||
open_type,
|
||||
opts,
|
||||
reply,
|
||||
})))
|
||||
.await?;
|
||||
@ -99,7 +100,7 @@ impl Sftp {
|
||||
pub async fn readdir(
|
||||
&self,
|
||||
filename: impl Into<PathBuf>,
|
||||
) -> anyhow::Result<Vec<(PathBuf, FileStat)>> {
|
||||
) -> anyhow::Result<Vec<(PathBuf, Metadata)>> {
|
||||
let (reply, rx) = bounded(1);
|
||||
self.tx
|
||||
.send(SessionRequest::Sftp(SftpRequest::Readdir(Readdir {
|
||||
@ -145,7 +146,7 @@ impl Sftp {
|
||||
/// 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<FileStat> {
|
||||
pub async fn stat(&self, filename: impl Into<PathBuf>) -> anyhow::Result<Metadata> {
|
||||
let (reply, rx) = bounded(1);
|
||||
self.tx
|
||||
.send(SessionRequest::Sftp(SftpRequest::Stat(Stat {
|
||||
@ -160,7 +161,7 @@ impl Sftp {
|
||||
/// 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<FileStat> {
|
||||
pub async fn lstat(&self, filename: impl Into<PathBuf>) -> anyhow::Result<Metadata> {
|
||||
let (reply, rx) = bounded(1);
|
||||
self.tx
|
||||
.send(SessionRequest::Sftp(SftpRequest::Lstat(Lstat {
|
||||
@ -178,13 +179,13 @@ impl Sftp {
|
||||
pub async fn setstat(
|
||||
&self,
|
||||
filename: impl Into<PathBuf>,
|
||||
stat: FileStat,
|
||||
metadata: Metadata,
|
||||
) -> anyhow::Result<()> {
|
||||
let (reply, rx) = bounded(1);
|
||||
self.tx
|
||||
.send(SessionRequest::Sftp(SftpRequest::Setstat(Setstat {
|
||||
filename: filename.into(),
|
||||
stat,
|
||||
metadata,
|
||||
reply,
|
||||
})))
|
||||
.await?;
|
||||
@ -249,14 +250,14 @@ impl Sftp {
|
||||
&self,
|
||||
src: impl Into<PathBuf>,
|
||||
dst: impl Into<PathBuf>,
|
||||
flags: Option<RenameFlags>,
|
||||
opts: RenameOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
let (reply, rx) = bounded(1);
|
||||
self.tx
|
||||
.send(SessionRequest::Sftp(SftpRequest::Rename(Rename {
|
||||
src: src.into(),
|
||||
dst: dst.into(),
|
||||
flags,
|
||||
opts,
|
||||
reply,
|
||||
})))
|
||||
.await?;
|
||||
@ -302,33 +303,13 @@ pub(crate) enum SftpRequest {
|
||||
File(FileRequest),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct OpenMode {
|
||||
pub filename: PathBuf,
|
||||
pub flags: OpenFlags,
|
||||
pub mode: i32,
|
||||
pub open_type: OpenType,
|
||||
pub opts: OpenOptions,
|
||||
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)]
|
||||
pub(crate) struct Open {
|
||||
pub filename: PathBuf,
|
||||
@ -350,7 +331,7 @@ pub(crate) struct Opendir {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Readdir {
|
||||
pub filename: PathBuf,
|
||||
pub reply: Sender<Vec<(PathBuf, FileStat)>>,
|
||||
pub reply: Sender<Vec<(PathBuf, Metadata)>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -369,19 +350,19 @@ pub(crate) struct Rmdir {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Stat {
|
||||
pub filename: PathBuf,
|
||||
pub reply: Sender<FileStat>,
|
||||
pub reply: Sender<Metadata>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Lstat {
|
||||
pub filename: PathBuf,
|
||||
pub reply: Sender<FileStat>,
|
||||
pub reply: Sender<Metadata>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Setstat {
|
||||
pub filename: PathBuf,
|
||||
pub stat: FileStat,
|
||||
pub metadata: Metadata,
|
||||
pub reply: Sender<()>,
|
||||
}
|
||||
|
||||
@ -408,7 +389,7 @@ pub(crate) struct Realpath {
|
||||
pub(crate) struct Rename {
|
||||
pub src: PathBuf,
|
||||
pub dst: PathBuf,
|
||||
pub flags: Option<RenameFlags>,
|
||||
pub opts: RenameOptions,
|
||||
pub reply: Sender<()>,
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::{FileStat, SessionRequest, SessionSender, SftpRequest};
|
||||
use super::{Metadata, SessionRequest, SessionSender, SftpRequest};
|
||||
use smol::{
|
||||
channel::{bounded, Sender},
|
||||
future::FutureExt,
|
||||
@ -70,20 +70,20 @@ pub(crate) struct FlushFile {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SetstatFile {
|
||||
pub file_id: FileId,
|
||||
pub stat: FileStat,
|
||||
pub metadata: Metadata,
|
||||
pub reply: Sender<()>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct StatFile {
|
||||
pub file_id: FileId,
|
||||
pub reply: Sender<FileStat>,
|
||||
pub reply: Sender<Metadata>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ReaddirFile {
|
||||
pub file_id: FileId,
|
||||
pub reply: Sender<(PathBuf, FileStat)>,
|
||||
pub reply: Sender<(PathBuf, Metadata)>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -131,7 +131,7 @@ impl File {
|
||||
/// Set the metadata for this handle.
|
||||
///
|
||||
/// 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);
|
||||
self.tx
|
||||
.as_ref()
|
||||
@ -139,7 +139,7 @@ impl File {
|
||||
.send(SessionRequest::Sftp(SftpRequest::File(
|
||||
FileRequest::Setstat(SetstatFile {
|
||||
file_id: self.file_id,
|
||||
stat,
|
||||
metadata,
|
||||
reply,
|
||||
}),
|
||||
)))
|
||||
@ -151,7 +151,7 @@ impl File {
|
||||
/// Get the metadata for this handle.
|
||||
///
|
||||
/// 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);
|
||||
self.tx
|
||||
.as_ref()
|
||||
@ -178,7 +178,7 @@ impl File {
|
||||
/// files in this directory.
|
||||
///
|
||||
/// 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);
|
||||
self.tx
|
||||
.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 predicates::prelude::*;
|
||||
use rstest::*;
|
||||
use ssh2::FileType;
|
||||
use std::path::PathBuf;
|
||||
use wezterm_ssh::Session;
|
||||
use wezterm_ssh::{FileType, Session};
|
||||
|
||||
// Sftp file tests
|
||||
mod file;
|
||||
@ -51,7 +50,7 @@ async fn readdir_should_return_list_of_directories_files_and_symlinks(#[future]
|
||||
.await
|
||||
.expect("Failed to read directory")
|
||||
.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)>>();
|
||||
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");
|
||||
link.symlink_to_file(file.path()).unwrap();
|
||||
|
||||
let stat = session
|
||||
let metadata = session
|
||||
.sftp()
|
||||
.stat(link.path())
|
||||
.await
|
||||
.expect("Failed to stat symlink");
|
||||
|
||||
// Verify that file stat makes sense
|
||||
assert!(stat.is_file(), "Invalid file stat returned");
|
||||
assert!(stat.file_type().is_file(), "Invalid file stat returned");
|
||||
assert!(!stat.file_type().is_symlink(), "Invalid file stat returned");
|
||||
assert!(metadata.is_file(), "Invalid file stat returned");
|
||||
assert!(metadata.ty.is_file(), "Invalid file stat returned");
|
||||
assert!(!metadata.ty.is_symlink(), "Invalid file stat returned");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@ -252,16 +251,16 @@ async fn stat_should_return_metadata_about_the_dir_pointed_to_by_a_symlink(
|
||||
let link = temp.child("link");
|
||||
link.symlink_to_dir(dir.path()).unwrap();
|
||||
|
||||
let stat = session
|
||||
let metadata = session
|
||||
.sftp()
|
||||
.stat(link.path())
|
||||
.await
|
||||
.expect("Failed to stat symlink");
|
||||
|
||||
// Verify that file stat makes sense
|
||||
assert!(stat.is_dir(), "Invalid file stat returned");
|
||||
assert!(stat.file_type().is_dir(), "Invalid file stat returned");
|
||||
assert!(!stat.file_type().is_symlink(), "Invalid file stat returned");
|
||||
assert!(metadata.is_dir(), "Invalid file stat returned");
|
||||
assert!(metadata.ty.is_dir(), "Invalid file stat returned");
|
||||
assert!(!metadata.ty.is_symlink(), "Invalid file stat returned");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@ -325,19 +324,16 @@ async fn lstat_should_return_metadata_about_symlink_pointing_to_a_file(#[future]
|
||||
let link = temp.child("link");
|
||||
link.symlink_to_file(file.path()).unwrap();
|
||||
|
||||
let lstat = session
|
||||
let metadata = session
|
||||
.sftp()
|
||||
.lstat(link.path())
|
||||
.await
|
||||
.expect("Failed to lstat symlink");
|
||||
|
||||
// Verify that file lstat makes sense
|
||||
assert!(!lstat.is_file(), "Invalid file lstat returned");
|
||||
assert!(!lstat.file_type().is_file(), "Invalid file lstat returned");
|
||||
assert!(
|
||||
lstat.file_type().is_symlink(),
|
||||
"Invalid file lstat returned"
|
||||
);
|
||||
assert!(!metadata.is_file(), "Invalid file lstat returned");
|
||||
assert!(!metadata.ty.is_file(), "Invalid file lstat returned");
|
||||
assert!(metadata.ty.is_symlink(), "Invalid file lstat returned");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@ -354,19 +350,16 @@ async fn lstat_should_return_metadata_about_symlink_pointing_to_a_directory(
|
||||
let link = temp.child("link");
|
||||
link.symlink_to_dir(dir.path()).unwrap();
|
||||
|
||||
let lstat = session
|
||||
let metadata = session
|
||||
.sftp()
|
||||
.lstat(link.path())
|
||||
.await
|
||||
.expect("Failed to lstat symlink");
|
||||
|
||||
// Verify that file lstat makes sense
|
||||
assert!(!lstat.is_dir(), "Invalid file lstat returned");
|
||||
assert!(!lstat.file_type().is_dir(), "Invalid file lstat returned");
|
||||
assert!(
|
||||
lstat.file_type().is_symlink(),
|
||||
"Invalid file lstat returned"
|
||||
);
|
||||
assert!(!metadata.is_dir(), "Invalid file lstat returned");
|
||||
assert!(!metadata.ty.is_dir(), "Invalid file lstat returned");
|
||||
assert!(metadata.ty.is_symlink(), "Invalid file lstat returned");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@ -586,7 +579,7 @@ async fn rename_should_support_singular_file(#[future] session: Session) {
|
||||
|
||||
session
|
||||
.sftp()
|
||||
.rename(file.path(), dst.path(), None)
|
||||
.rename(file.path(), dst.path(), Default::default())
|
||||
.await
|
||||
.expect("Failed to rename file");
|
||||
|
||||
@ -612,7 +605,7 @@ async fn rename_should_support_dirtectory(#[future] session: Session) {
|
||||
|
||||
session
|
||||
.sftp()
|
||||
.rename(dir.path(), dst.path(), None)
|
||||
.rename(dir.path(), dst.path(), Default::default())
|
||||
.await
|
||||
.expect("Failed to rename directory");
|
||||
|
||||
@ -637,7 +630,7 @@ async fn rename_should_fail_if_source_path_missing(#[future] session: Session) {
|
||||
|
||||
let result = session
|
||||
.sftp()
|
||||
.rename(missing.path(), dst.path(), None)
|
||||
.rename(missing.path(), dst.path(), Default::default())
|
||||
.await;
|
||||
assert!(
|
||||
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)
|
||||
let mut contents = Vec::new();
|
||||
while let Ok((path, stat)) = remote_dir.readdir().await {
|
||||
let ft = stat.file_type();
|
||||
while let Ok((path, metadata)) = remote_dir.readdir().await {
|
||||
let ft = metadata.ty;
|
||||
contents.push((
|
||||
path,
|
||||
if ft.is_dir() {
|
||||
|
Loading…
Reference in New Issue
Block a user