use std::fs::{self, OpenOptions}; use std::io::{Read, Write}; #[cfg(posix)] use std::os::unix::fs::PermissionsExt as _; use std::path::{Path, PathBuf}; use filetime::FileTime; use remotefs::File; use remotefs::fs::{FileType, Metadata, UnixPex}; use super::{HostBridge, HostResult}; use crate::host::{HostError, HostErrorType}; use crate::utils::path; /// Localhost is the entity which holds the information about the current directory and host. /// It provides functions to navigate across the local host file system pub struct Localhost { wrkdir: PathBuf, files: Vec, } impl Localhost { /// Instantiates a new Localhost struct pub fn new(wrkdir: PathBuf) -> HostResult { debug!("Initializing localhost at {}", wrkdir.display()); let mut host: Localhost = Localhost { wrkdir, files: Vec::new(), }; // Check if dir exists let pwd = host.pwd()?; if !host.exists(&pwd)? { error!( "Failed to initialize localhost: {} doesn't exist", host.wrkdir.display() ); return Err(HostError::new( HostErrorType::NoSuchFileOrDirectory, None, host.wrkdir.as_path(), )); } // Retrieve files for provided path host.files = match host.list_dir(&pwd) { Ok(files) => files, Err(err) => { error!( "Failed to initialize localhost: could not scan wrkdir: {}", err ); return Err(err); } }; info!("Localhost initialized with success"); Ok(host) } /// Convert path to absolute path fn to_path(&self, p: &Path) -> PathBuf { path::absolutize(self.wrkdir.as_path(), p) } } impl HostBridge for Localhost { fn connect(&mut self) -> HostResult<()> { Ok(()) } fn disconnect(&mut self) -> HostResult<()> { Ok(()) } fn is_connected(&mut self) -> bool { true } fn is_localhost(&self) -> bool { true } fn pwd(&mut self) -> HostResult { Ok(self.wrkdir.clone()) } fn change_wrkdir(&mut self, new_dir: &std::path::Path) -> HostResult { let new_dir: PathBuf = self.to_path(new_dir); info!("Changing localhost directory to {}...", new_dir.display()); // Check whether directory exists if !self.exists(new_dir.as_path())? { error!("Could not change directory: No such file or directory"); return Err(HostError::new( HostErrorType::NoSuchFileOrDirectory, None, new_dir.as_path(), )); } // Change directory if let Err(err) = std::env::set_current_dir(new_dir.as_path()) { error!("Could not enter directory: {}", err); return Err(HostError::new( HostErrorType::NoSuchFileOrDirectory, Some(err), new_dir.as_path(), )); } let prev_dir: PathBuf = self.wrkdir.clone(); // Backup location // Update working directory // Change dir self.wrkdir = new_dir; // Scan new directory let pwd = self.pwd()?; self.files = match self.list_dir(&pwd) { Ok(files) => files, Err(err) => { error!("Could not scan new directory: {}", err); // Restore directory self.wrkdir = prev_dir; return Err(err); } }; debug!("Changed directory to {}", self.wrkdir.display()); Ok(self.wrkdir.clone()) } fn mkdir_ex(&mut self, dir_name: &std::path::Path, ignore_existing: bool) -> HostResult<()> { let dir_path: PathBuf = self.to_path(dir_name); info!("Making directory {}", dir_path.display()); // If dir already exists, return Error if dir_path.exists() { match ignore_existing { true => return Ok(()), false => { return Err(HostError::new( HostErrorType::FileAlreadyExists, None, dir_path.as_path(), )); } } } match std::fs::create_dir(dir_path.as_path()) { Ok(_) => { // Update dir if dir_name.is_relative() { let pwd = self.pwd()?; self.files = self.list_dir(&pwd)?; } info!("Created directory {}", dir_path.display()); Ok(()) } Err(err) => { error!("Could not make directory: {}", err); Err(HostError::new( HostErrorType::CouldNotCreateFile, Some(err), dir_path.as_path(), )) } } } fn remove(&mut self, entry: &File) -> HostResult<()> { if entry.is_dir() { // If file doesn't exist; return error debug!("Removing directory {}", entry.path().display()); if !entry.path().exists() { error!("Directory doesn't exist"); return Err(HostError::new( HostErrorType::NoSuchFileOrDirectory, None, entry.path(), )); } // Remove match std::fs::remove_dir_all(entry.path()) { Ok(_) => { // Update dir let pwd = self.pwd()?; self.files = self.list_dir(&pwd)?; info!("Removed directory {}", entry.path().display()); Ok(()) } Err(err) => { error!("Could not remove directory: {}", err); Err(HostError::new( HostErrorType::DeleteFailed, Some(err), entry.path(), )) } } } else { // If file doesn't exist; return error debug!("Removing file {}", entry.path().display()); if !entry.path().exists() { error!("File doesn't exist"); return Err(HostError::new( HostErrorType::NoSuchFileOrDirectory, None, entry.path(), )); } // Remove match std::fs::remove_file(entry.path()) { Ok(_) => { // Update dir let pwd = self.pwd()?; self.files = self.list_dir(&pwd)?; info!("Removed file {}", entry.path().display()); Ok(()) } Err(err) => { error!("Could not remove file: {}", err); Err(HostError::new( HostErrorType::DeleteFailed, Some(err), entry.path(), )) } } } } fn rename(&mut self, entry: &File, dst_path: &std::path::Path) -> HostResult<()> { match std::fs::rename(entry.path(), dst_path) { Ok(_) => { // Scan dir let pwd = self.pwd()?; self.files = self.list_dir(&pwd)?; debug!( "Moved file {} to {}", entry.path().display(), dst_path.display() ); Ok(()) } Err(err) => { error!( "Failed to move {} to {}: {}", entry.path().display(), dst_path.display(), err ); Err(HostError::new( HostErrorType::CouldNotCreateFile, Some(err), entry.path(), )) } } } fn copy(&mut self, entry: &File, dst: &std::path::Path) -> HostResult<()> { // Get absolute path of dest let dst: PathBuf = self.to_path(dst); info!( "Copying file {} to {}", entry.path().display(), dst.display() ); // Match entry if entry.is_dir() { // If destination path doesn't exist, create destination if !dst.exists() { debug!("Directory {} doesn't exist; creating it", dst.display()); self.mkdir(dst.as_path())?; } // Scan dir let dir_files: Vec = self.list_dir(entry.path())?; // Iterate files for dir_entry in dir_files.iter() { // Calculate dst let mut sub_dst: PathBuf = dst.clone(); sub_dst.push(dir_entry.name()); // Call function recursively self.copy(dir_entry, sub_dst.as_path())?; } } else { // Copy file // If destination path is a directory, push file name let dst: PathBuf = match dst.as_path().is_dir() { true => { let mut p: PathBuf = dst.clone(); p.push(entry.name().as_str()); p } false => dst.clone(), }; // Copy entry path to dst path if let Err(err) = std::fs::copy(entry.path(), dst.as_path()) { error!("Failed to copy file: {}", err); return Err(HostError::new( HostErrorType::CouldNotCreateFile, Some(err), entry.path(), )); } info!("File copied"); } // Reload directory if dst is pwd let pwd = self.pwd()?; match dst.is_dir() { true => { if dst == pwd { self.files = self.list_dir(&pwd)?; } else if let Some(parent) = dst.parent() { // If parent is pwd, scan directory if parent == pwd { self.files = self.list_dir(&pwd)?; } } } false => { if let Some(parent) = dst.parent() { // If parent is pwd, scan directory if parent == pwd { self.files = self.list_dir(&pwd)?; } } } } Ok(()) } fn stat(&mut self, path: &std::path::Path) -> HostResult { info!("Stating file {}", path.display()); let path: PathBuf = self.to_path(path); let attr = match fs::metadata(path.as_path()) { Ok(metadata) => metadata, Err(err) => { error!("Could not read file metadata: {}", err); return Err(HostError::new( HostErrorType::FileNotAccessible, Some(err), path.as_path(), )); } }; let mut metadata = Metadata::from(attr); if let Ok(symlink) = fs::read_link(path.as_path()) { metadata.set_symlink(symlink); metadata.file_type = FileType::Symlink; } // Match dir / file Ok(File { path, metadata }) } fn exists(&mut self, path: &Path) -> HostResult { Ok(path.exists()) } fn list_dir(&mut self, path: &Path) -> HostResult> { info!("Reading directory {}", path.display()); match std::fs::read_dir(path) { Ok(e) => { let mut fs_entries: Vec = Vec::new(); for entry in e.flatten() { // NOTE: 0.4.1, don't fail if stat for one file fails match self.stat(entry.path().as_path()) { Ok(entry) => fs_entries.push(entry), Err(e) => error!("Failed to stat {}: {}", entry.path().display(), e), } } Ok(fs_entries) } Err(err) => Err(HostError::new( HostErrorType::DirNotAccessible, Some(err), path, )), } } fn setstat(&mut self, path: &Path, metadata: &Metadata) -> HostResult<()> { debug!("Setting stat for file at {}", path.display()); if let Some(mtime) = metadata.modified { let mtime = FileTime::from_system_time(mtime); debug!("setting mtime {:?}", mtime); filetime::set_file_mtime(path, mtime) .map_err(|e| HostError::new(HostErrorType::FileNotAccessible, Some(e), path))?; } if let Some(atime) = metadata.accessed { let atime = FileTime::from_system_time(atime); filetime::set_file_atime(path, atime) .map_err(|e| HostError::new(HostErrorType::FileNotAccessible, Some(e), path))?; } #[cfg(posix)] if let Some(mode) = metadata.mode { self.chmod(path, mode)?; } Ok(()) } fn exec(&mut self, cmd: &str) -> HostResult { // Make command let args: Vec<&str> = cmd.split(' ').collect(); let cmd: &str = args.first().unwrap(); let argv: &[&str] = &args[1..]; info!("Executing command: {} {:?}", cmd, argv); match std::process::Command::new(cmd).args(argv).output() { Ok(output) => match std::str::from_utf8(&output.stdout) { Ok(s) => { info!("Command output: {}", s); Ok(s.to_string()) } Err(_) => Ok(String::new()), }, Err(err) => { error!("Failed to run command: {}", err); Err(HostError::new( HostErrorType::ExecutionFailed, Some(err), self.wrkdir.as_path(), )) } } } #[cfg(posix)] fn symlink(&mut self, src: &Path, dst: &Path) -> HostResult<()> { let src = self.to_path(src); std::os::unix::fs::symlink(dst, src.as_path()).map_err(|e| { error!( "Failed to create symlink at {} pointing at {}: {}", src.display(), dst.display(), e ); HostError::new(HostErrorType::CouldNotCreateFile, Some(e), src.as_path()) }) } #[cfg(win)] fn symlink(&mut self, _src: &Path, _dst: &Path) -> HostResult<()> { warn!("Cannot create symlink on Windows"); Err(HostError::from(HostErrorType::NotImplemented)) } #[cfg(posix)] fn chmod(&mut self, path: &std::path::Path, pex: UnixPex) -> HostResult<()> { let path: PathBuf = self.to_path(path); // Get metadta match fs::metadata(path.as_path()) { Ok(metadata) => { let mut mpex = metadata.permissions(); mpex.set_mode(pex.into()); match fs::set_permissions(path.as_path(), mpex) { Ok(_) => { info!("Changed mode for {} to {:?}", path.display(), pex); Ok(()) } Err(err) => { error!("Could not change mode for file {}: {}", path.display(), err); Err(HostError::new( HostErrorType::FileNotAccessible, Some(err), path.as_path(), )) } } } Err(err) => { error!( "Chmod failed; could not read metadata for file {}: {}", path.display(), err ); Err(HostError::new( HostErrorType::FileNotAccessible, Some(err), path.as_path(), )) } } } #[cfg(win)] fn chmod(&mut self, _path: &std::path::Path, _pex: UnixPex) -> HostResult<()> { warn!("Cannot set file mode on Windows"); Err(HostError::from(HostErrorType::NotImplemented)) } fn open_file(&mut self, file: &std::path::Path) -> HostResult> { let file: PathBuf = self.to_path(file); info!("Opening file {} for read", file.display()); if !self.exists(file.as_path())? { error!("File doesn't exist!"); return Err(HostError::new( HostErrorType::NoSuchFileOrDirectory, None, file.as_path(), )); } match OpenOptions::new() .create(false) .read(true) .write(false) .open(file.as_path()) { Ok(f) => Ok(Box::new(f)), Err(err) => { error!("Could not open file for read: {}", err); Err(HostError::new( HostErrorType::FileNotAccessible, Some(err), file.as_path(), )) } } } fn create_file( &mut self, file: &Path, _metadata: &Metadata, ) -> HostResult> { let file: PathBuf = self.to_path(file); info!("Opening file {} for write", file.display()); match OpenOptions::new() .create(true) .write(true) .truncate(true) .open(file.as_path()) { Ok(f) => Ok(Box::new(f)), Err(err) => { error!("Failed to open file: {}", err); match self.exists(file.as_path())? { true => Err(HostError::new( HostErrorType::ReadonlyFile, Some(err), file.as_path(), )), false => Err(HostError::new( HostErrorType::FileNotAccessible, Some(err), file.as_path(), )), } } } } fn finalize_write(&mut self, _writer: Box) -> HostResult<()> { // no-op Ok(()) } } #[cfg(test)] mod tests { #[cfg(posix)] use std::fs::File as StdFile; #[cfg(posix)] use std::io::Write; use std::ops::AddAssign; #[cfg(posix)] use std::os::unix::fs::{PermissionsExt, symlink}; use std::time::{Duration, SystemTime}; use pretty_assertions::assert_eq; use super::*; #[cfg(posix)] use crate::utils::test_helpers::make_fsentry; use crate::utils::test_helpers::{create_sample_file, make_file_at}; #[test] fn test_host_error_new() { let error: HostError = HostError::new(HostErrorType::CouldNotCreateFile, None, Path::new("/tmp")); assert!(error.ioerr.is_none()); assert_eq!(error.path.as_ref().unwrap(), Path::new("/tmp")); } #[test] #[cfg(posix)] #[cfg(not(feature = "isolated-tests"))] fn test_host_localhost_new() { let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); assert_eq!(host.wrkdir, PathBuf::from("/dev")); // Scan dir let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap(); let mut counter: usize = 0; for _ in entries { counter += 1; } assert_eq!(host.files.len(), counter); } #[test] #[cfg(win)] fn test_host_localhost_new() { let mut host: Localhost = Localhost::new(PathBuf::from("C:\\users")).ok().unwrap(); assert_eq!(host.wrkdir, PathBuf::from("C:\\users")); // Scan dir let entries = std::fs::read_dir(PathBuf::from("C:\\users").as_path()).unwrap(); let mut counter: usize = 0; for _ in entries { counter = counter + 1; } assert_eq!(host.files.len(), counter); } #[test] #[should_panic] fn test_host_localhost_new_bad() { Localhost::new(PathBuf::from("/omargabber/123/345")) .ok() .unwrap(); } #[test] #[cfg(posix)] fn test_host_localhost_pwd() { let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); assert_eq!(host.pwd().unwrap(), PathBuf::from("/dev")); } #[test] #[cfg(posix)] #[cfg(not(feature = "isolated-tests"))] fn test_host_localhost_change_dir() { let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); let new_dir: PathBuf = PathBuf::from("/dev"); assert!(host.change_wrkdir(new_dir.as_path()).is_ok()); // Verify new files // Scan dir let entries = std::fs::read_dir(new_dir.as_path()).unwrap(); let mut counter: usize = 0; for _ in entries { counter += 1; } assert_eq!(host.files.len(), counter); } #[test] #[cfg(posix)] #[should_panic] fn test_host_localhost_change_dir_failed() { let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); let new_dir: PathBuf = PathBuf::from("/omar/gabber/123/456"); assert!(host.change_wrkdir(new_dir.as_path()).is_ok()); } #[test] #[cfg(posix)] fn test_host_localhost_open_read() { let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); // Create temp file let file: tempfile::NamedTempFile = create_sample_file(); assert!(host.open_file(file.path()).is_ok()); } #[test] #[cfg(posix)] #[should_panic] fn test_host_localhost_open_read_err_no_such_file() { let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); assert!( host.open_file(PathBuf::from("/bin/foo-bar-test-omar-123-456-789.txt").as_path()) .is_ok() ); } #[test] #[cfg(any(target_os = "macos", target_os = "linux"))] fn test_host_localhost_open_read_err_not_accessible() { let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); let file: tempfile::NamedTempFile = create_sample_file(); //let mut perms = fs::metadata(file.path())?.permissions(); fs::set_permissions(file.path(), PermissionsExt::from_mode(0o222)).unwrap(); //fs::set_permissions(file.path(), perms)?; assert!(host.open_file(file.path()).is_err()); } #[test] #[cfg(posix)] fn test_host_localhost_open_write() { let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); // Create temp file let file: tempfile::NamedTempFile = create_sample_file(); assert!(host.create_file(file.path(), &Metadata::default()).is_ok()); } #[test] #[cfg(any(target_os = "macos", target_os = "linux"))] fn test_host_localhost_open_write_err() { let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); let file: tempfile::NamedTempFile = create_sample_file(); //let mut perms = fs::metadata(file.path())?.permissions(); fs::set_permissions(file.path(), PermissionsExt::from_mode(0o444)).unwrap(); //fs::set_permissions(file.path(), perms)?; assert!(host.create_file(file.path(), &Metadata::default()).is_err()); } #[cfg(posix)] #[test] fn test_host_localhost_symlinks() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); // Create sample file assert!(StdFile::create(format!("{}/foo.txt", tmpdir.path().display()).as_str()).is_ok()); // Create symlink assert!( symlink( format!("{}/foo.txt", tmpdir.path().display()), format!("{}/bar.txt", tmpdir.path().display()) ) .is_ok() ); // Get dir let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let files: Vec = host.files.clone(); // Verify files let file_0: &File = files.get(0).unwrap(); if file_0.name() == *"foo.txt" { assert!(file_0.metadata.symlink.is_none()); } else { assert_eq!( file_0.metadata.symlink.as_ref().unwrap(), &PathBuf::from(format!("{}/foo.txt", tmpdir.path().display())) ); } // Verify simlink let file_1: &File = files.get(1).unwrap(); if file_1.name() == *"bar.txt" { assert_eq!( file_1.metadata.symlink.as_ref().unwrap(), &PathBuf::from(format!("{}/foo.txt", tmpdir.path().display())) ); } else { assert!(file_1.metadata.symlink.is_none()); } } #[test] #[cfg(posix)] fn test_host_localhost_mkdir() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let files: Vec = host.files.clone(); assert_eq!(files.len(), 0); // There should be 0 files now assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok()); let files: Vec = host.files.clone(); assert_eq!(files.len(), 1); // There should be 1 file now // Try to re-create directory assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_err()); // Try abs path assert!( host.mkdir_ex(PathBuf::from("/tmp/test_dir_123456789").as_path(), true) .is_ok() ); // Fail assert!( host.mkdir_ex( PathBuf::from("/aaaa/oooooo/tmp/test_dir_123456789").as_path(), true ) .is_err() ); } #[test] #[cfg(posix)] fn test_host_localhost_remove() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); // Create sample file assert!(StdFile::create(format!("{}/foo.txt", tmpdir.path().display()).as_str()).is_ok()); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let files: Vec = host.files.clone(); assert_eq!(files.len(), 1); // There should be 1 file now // Remove file assert!(host.remove(files.get(0).unwrap()).is_ok()); // There should be 0 files now let files: Vec = host.files.clone(); assert_eq!(files.len(), 0); // There should be 0 files now // Create directory assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok()); // Delete directory let files: Vec = host.files.clone(); assert_eq!(files.len(), 1); // There should be 1 file now assert!(host.remove(files.get(0).unwrap()).is_ok()); // Remove unexisting directory assert!( host.remove(&make_fsentry(PathBuf::from("/a/b/c/d"), true)) .is_err() ); assert!( host.remove(&make_fsentry(PathBuf::from("/aaaaaaa"), false)) .is_err() ); } #[test] #[cfg(posix)] fn test_host_localhost_rename() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); // Create sample file let src_path: PathBuf = PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()).as_str()); assert!(StdFile::create(src_path.as_path()).is_ok()); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let files: Vec = host.files.clone(); assert_eq!(files.len(), 1); // There should be 1 file now assert_eq!(files.get(0).unwrap().name(), "foo.txt"); // Rename file let dst_path: PathBuf = PathBuf::from(format!("{}/bar.txt", tmpdir.path().display()).as_str()); assert!( host.rename(files.get(0).unwrap(), dst_path.as_path()) .is_ok() ); // There should be still 1 file now, but named bar.txt let files: Vec = host.files.clone(); assert_eq!(files.len(), 1); // There should be 0 files now assert_eq!(files.get(0).unwrap().name(), "bar.txt"); // Fail let bad_path: PathBuf = PathBuf::from("/asdailsjoidoewojdijow/ashdiuahu"); assert!( host.rename(files.get(0).unwrap(), bad_path.as_path()) .is_err() ); } #[test] fn should_setstat() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); let file: tempfile::NamedTempFile = create_sample_file(); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); // stat let mut filemeta = host.stat(file.path()).unwrap(); let mut new_atime = SystemTime::UNIX_EPOCH; new_atime.add_assign(Duration::from_secs(1612164210)); let mut new_mtime = SystemTime::UNIX_EPOCH; new_mtime.add_assign(Duration::from_secs(1613160210)); filemeta.metadata.accessed = Some(new_atime); filemeta.metadata.modified = Some(new_mtime); // setstat assert!(host.setstat(filemeta.path(), filemeta.metadata()).is_ok()); let new_metadata = host.stat(file.path()).unwrap(); assert_eq!(new_metadata.metadata().accessed, Some(new_atime)); assert_eq!(new_metadata.metadata().modified, Some(new_mtime)); } #[cfg(posix)] #[test] fn test_host_chmod() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); let file: tempfile::NamedTempFile = create_sample_file(); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); // Chmod to file assert!(host.chmod(file.path(), UnixPex::from(0o755)).is_ok()); // Chmod to dir assert!(host.chmod(tmpdir.path(), UnixPex::from(0o750)).is_ok()); // Error assert!( host.chmod( Path::new("/tmp/krgiogoiegj/kwrgnoerig"), UnixPex::from(0o777) ) .is_err() ); } #[cfg(posix)] #[test] fn test_host_copy_file_absolute() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); // Create file in tmpdir let mut file1_path: PathBuf = PathBuf::from(tmpdir.path()); file1_path.push("foo.txt"); // Write file 1 let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap(); assert!(file1.write_all(b"Hello world!\n").is_ok()); // Get file 2 path let mut file2_path: PathBuf = PathBuf::from(tmpdir.path()); file2_path.push("bar.txt"); // Create host let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let file1_entry: File = host.files.get(0).unwrap().clone(); assert_eq!(file1_entry.name(), String::from("foo.txt")); // Copy assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); // Verify host has two files assert_eq!(host.files.len(), 2); // Fail copy assert!( host.copy( &make_fsentry(PathBuf::from("/a/a7/a/a7a"), false), PathBuf::from("571k422i").as_path() ) .is_err() ); } #[cfg(posix)] #[test] fn test_host_copy_file_relative() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); // Create file in tmpdir let mut file1_path: PathBuf = PathBuf::from(tmpdir.path()); file1_path.push("foo.txt"); // Write file 1 let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap(); assert!(file1.write_all(b"Hello world!\n").is_ok()); // Get file 2 path let file2_path: PathBuf = PathBuf::from("bar.txt"); // Create host let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let file1_entry: File = host.files.get(0).unwrap().clone(); assert_eq!(file1_entry.name(), String::from("foo.txt")); // Copy assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); // Verify host has two files assert_eq!(host.files.len(), 2); } #[cfg(posix)] #[test] fn test_host_copy_directory_absolute() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); // Create directory in tmpdir let mut dir_src: PathBuf = PathBuf::from(tmpdir.path()); dir_src.push("test_dir/"); assert!(std::fs::create_dir(dir_src.as_path()).is_ok()); // Create file in src dir let mut file1_path: PathBuf = dir_src.clone(); file1_path.push("foo.txt"); // Write file 1 let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap(); assert!(file1.write_all(b"Hello world!\n").is_ok()); // Copy dir src to dir ddest let mut dir_dest: PathBuf = PathBuf::from(tmpdir.path()); dir_dest.push("test_dest_dir/"); // Create host let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let dir_src_entry: File = host.files.get(0).unwrap().clone(); assert_eq!(dir_src_entry.name(), String::from("test_dir")); // Copy assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok()); // Verify host has two files assert_eq!(host.files.len(), 2); // Verify dir_dest contains foo.txt let mut test_file_path: PathBuf = dir_dest.clone(); test_file_path.push("foo.txt"); assert!(host.stat(test_file_path.as_path()).is_ok()); } #[cfg(posix)] #[test] fn test_host_copy_directory_relative() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); // Create directory in tmpdir let mut dir_src: PathBuf = PathBuf::from(tmpdir.path()); dir_src.push("test_dir/"); assert!(std::fs::create_dir(dir_src.as_path()).is_ok()); // Create file in src dir let mut file1_path: PathBuf = dir_src.clone(); file1_path.push("foo.txt"); // Write file 1 let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap(); assert!(file1.write_all(b"Hello world!\n").is_ok()); // Copy dir src to dir ddest let dir_dest: PathBuf = PathBuf::from("test_dest_dir/"); // Create host let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let dir_src_entry: File = host.files.get(0).unwrap().clone(); assert_eq!(dir_src_entry.name(), String::from("test_dir")); // Copy assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok()); // Verify host has two files assert_eq!(host.files.len(), 2); // Verify dir_dest contains foo.txt let mut test_file_path: PathBuf = dir_dest.clone(); test_file_path.push("foo.txt"); assert!(host.stat(test_file_path.as_path()).is_ok()); } #[test] fn test_host_exec() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); // Execute assert!(host.exec("echo 5").ok().unwrap().as_str().contains("5")); } #[cfg(posix)] #[test] fn should_create_symlink() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); let dir_path: &Path = tmpdir.path(); // Make file assert!(make_file_at(dir_path, "pippo.txt").is_ok()); let mut host: Localhost = Localhost::new(PathBuf::from(dir_path)).ok().unwrap(); let mut p = dir_path.to_path_buf(); p.push("pippo.txt"); // Make symlink assert!(host.symlink(Path::new("link.txt"), p.as_path()).is_ok()); // Fail symlink assert!(host.symlink(Path::new("link.txt"), p.as_path()).is_err()); assert!( host.symlink(Path::new("/tmp/oooo/aaaa"), p.as_path()) .is_err() ); } #[test] fn test_host_fmt_error() { let err: HostError = HostError::new( HostErrorType::CouldNotCreateFile, Some(std::io::Error::from(std::io::ErrorKind::AddrInUse)), Path::new("/tmp"), ); assert_eq!( format!("{err}"), String::from("Could not create file: address in use (/tmp)"), ); assert_eq!( format!("{}", HostError::from(HostErrorType::DeleteFailed)), String::from("Could not delete file") ); assert_eq!( format!("{}", HostError::from(HostErrorType::ExecutionFailed)), String::from("Command execution failed"), ); assert_eq!( format!("{}", HostError::from(HostErrorType::DirNotAccessible)), String::from("Could not access directory"), ); assert_eq!( format!("{}", HostError::from(HostErrorType::NoSuchFileOrDirectory)), String::from("No such file or directory") ); assert_eq!( format!("{}", HostError::from(HostErrorType::ReadonlyFile)), String::from("File is readonly") ); assert_eq!( format!("{}", HostError::from(HostErrorType::FileNotAccessible)), String::from("Could not access file") ); assert_eq!( format!("{}", HostError::from(HostErrorType::FileAlreadyExists)), String::from("File already exists") ); } }