From 9b6e1864d8a62a8fc1c9ddca14fef30771b3c083 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Tue, 1 Dec 2020 10:57:13 +0100 Subject: [PATCH] stat method for file transfers --- src/filetransfer/mod.rs | 5 + src/filetransfer/sftp_transfer.rs | 190 +++++++++++++++++++----------- 2 files changed, 123 insertions(+), 72 deletions(-) diff --git a/src/filetransfer/mod.rs b/src/filetransfer/mod.rs index 78f8110..585a59f 100644 --- a/src/filetransfer/mod.rs +++ b/src/filetransfer/mod.rs @@ -141,6 +141,11 @@ pub trait FileTransfer { /// Rename file or a directory fn rename(&self, file: &FsEntry, dst: &Path) -> Result<(), FileTransferError>; + /// ### stat + /// + /// Stat file and return FsEntry + fn stat(&self, path: &Path) -> Result; + /// ### send_file /// /// Send file to remote diff --git a/src/filetransfer/sftp_transfer.rs b/src/filetransfer/sftp_transfer.rs index b0cd9b1..5670656 100644 --- a/src/filetransfer/sftp_transfer.rs +++ b/src/filetransfer/sftp_transfer.rs @@ -31,7 +31,7 @@ use super::{FileTransfer, FileTransferError}; use crate::fs::{FsDirectory, FsEntry, FsFile}; // Includes -use ssh2::{Session, Sftp}; +use ssh2::{FileStat, Session, Sftp}; use std::io::{Read, Seek, Write}; use std::net::TcpStream; use std::path::{Path, PathBuf}; @@ -100,6 +100,78 @@ impl SftpFileTransfer { false => PathBuf::from(p), } } + + /// ### make_fsentry + /// + /// Make fsentry from path and metadata + fn make_fsentry(&self, path: &Path, metadata: &FileStat) -> FsEntry { + // Get common parameters + let file_name: String = String::from(path.file_name().unwrap().to_str().unwrap_or("")); + let file_type: Option = match path.extension() { + Some(ext) => Some(String::from(ext.to_str().unwrap_or(""))), + None => None, + }; + let uid: Option = metadata.uid; + let gid: Option = metadata.gid; + let pex: Option<(u8, u8, u8)> = match metadata.perm { + Some(perms) => Some(( + ((perms >> 6) & 0x7) as u8, + ((perms >> 3) & 0x7) as u8, + (perms & 0x7) as u8, + )), + None => None, + }; + let size: u64 = metadata.size.unwrap_or(0); + let mut atime: SystemTime = SystemTime::UNIX_EPOCH; + atime = atime + .checked_add(Duration::from_secs(metadata.atime.unwrap_or(0))) + .unwrap_or(SystemTime::UNIX_EPOCH); + let mut mtime: SystemTime = SystemTime::UNIX_EPOCH; + mtime = mtime + .checked_add(Duration::from_secs(metadata.mtime.unwrap_or(0))) + .unwrap_or(SystemTime::UNIX_EPOCH); + // Check if symlink + let is_symlink: bool = metadata.file_type().is_symlink(); + let symlink: Option = match is_symlink { + true => { + // Read symlink + match self.sftp.as_ref().unwrap().readlink(path) { + Ok(p) => Some(p), + Err(_) => None, + } + } + false => None, + }; + // Is a directory? + match metadata.is_dir() { + true => FsEntry::Directory(FsDirectory { + name: file_name, + abs_path: PathBuf::from(path), + last_change_time: mtime, + last_access_time: atime, + creation_time: SystemTime::UNIX_EPOCH, + readonly: false, + symlink: symlink, + user: uid, + group: gid, + unix_pex: pex, + }), + false => FsEntry::File(FsFile { + name: file_name, + abs_path: PathBuf::from(path), + size: size as usize, + ftype: file_type, + last_change_time: mtime, + last_access_time: atime, + creation_time: SystemTime::UNIX_EPOCH, + readonly: false, + symlink: symlink, + user: uid, + group: gid, + unix_pex: pex, + }), + } + } } impl FileTransfer for SftpFileTransfer { @@ -229,77 +301,7 @@ impl FileTransfer for SftpFileTransfer { let mut entries: Vec = Vec::with_capacity(files.len()); // Iterate over files for (path, metadata) in files { - // Get common parameters - let file_name: String = - String::from(path.file_name().unwrap().to_str().unwrap_or("")); - let file_type: Option = match path.extension() { - Some(ext) => Some(String::from(ext.to_str().unwrap_or(""))), - None => None, - }; - let uid: Option = metadata.uid; - let gid: Option = metadata.gid; - let pex: Option<(u8, u8, u8)> = match metadata.perm { - Some(perms) => Some(( - ((perms >> 6) & 0x7) as u8, - ((perms >> 3) & 0x7) as u8, - (perms & 0x7) as u8, - )), - None => None, - }; - let size: u64 = metadata.size.unwrap_or(0); - let mut atime: SystemTime = SystemTime::UNIX_EPOCH; - atime = atime - .checked_add(Duration::from_secs(metadata.atime.unwrap_or(0))) - .unwrap_or(SystemTime::UNIX_EPOCH); - let mut mtime: SystemTime = SystemTime::UNIX_EPOCH; - mtime = mtime - .checked_add(Duration::from_secs(metadata.mtime.unwrap_or(0))) - .unwrap_or(SystemTime::UNIX_EPOCH); - // Check if symlink - let is_symlink: bool = metadata.file_type().is_symlink(); - let symlink: Option = match is_symlink { - true => { - // Read symlink - match sftp.readlink(path.as_path()) { - Ok(p) => Some(p), - Err(_) => None, - } - } - false => None, - }; - // Is a directory? - match metadata.is_dir() { - true => { - entries.push(FsEntry::Directory(FsDirectory { - name: file_name, - abs_path: path.clone(), - last_change_time: mtime, - last_access_time: atime, - creation_time: SystemTime::UNIX_EPOCH, - readonly: false, - symlink: symlink, - user: uid, - group: gid, - unix_pex: pex, - })); - } - false => { - entries.push(FsEntry::File(FsFile { - name: file_name, - abs_path: path.clone(), - size: size as usize, - ftype: file_type, - last_change_time: mtime, - last_access_time: atime, - creation_time: SystemTime::UNIX_EPOCH, - readonly: false, - symlink: symlink, - user: uid, - group: gid, - unix_pex: pex, - })); - } - } + entries.push(self.make_fsentry(path.as_path(), &metadata)); } Ok(entries) } @@ -389,6 +391,27 @@ impl FileTransfer for SftpFileTransfer { } } + /// ### stat + /// + /// Stat file and return FsEntry + fn stat(&self, path: &Path) -> Result { + match self.sftp.as_ref() { + Some(sftp) => { + // Get path + let dir: PathBuf = match self.get_remote_path(path) { + Ok(p) => p, + Err(err) => return Err(err), + }; + // Get file + match sftp.stat(dir.as_path()) { + Ok(metadata) => Ok(self.make_fsentry(dir.as_path(), &metadata)), + Err(_) => Err(FileTransferError::NoSuchFileOrDirectory), + } + } + None => Err(FileTransferError::UninitializedSession), + } + } + /// ### send_file /// /// Send file to remote @@ -603,6 +626,29 @@ mod tests { assert!(client.disconnect().is_ok()); } + #[test] + fn test_filetransfer_sftp_stat() { + let mut client: SftpFileTransfer = SftpFileTransfer::new(); + assert!(client + .connect( + String::from("test.rebex.net"), + 22, + Some(String::from("demo")), + Some(String::from("password")) + ) + .is_ok()); + // Check session and sftp + assert!(client.session.is_some()); + assert!(client.sftp.is_some()); + assert_eq!(client.wrkdir, PathBuf::from("/")); + let file: FsEntry = client.stat(PathBuf::from("readme.txt").as_path()).ok().unwrap(); + if let FsEntry::File(file) = file { + assert_eq!(file.abs_path, PathBuf::from("/readme.txt")); + } else { + panic!("Expected readme.txt to be a file"); + } + } + #[test] fn test_filetransfer_sftp_recv() { let mut client: SftpFileTransfer = SftpFileTransfer::new();