Skip to content

Commit

Permalink
Adding tests for the mount archive feature
Browse files Browse the repository at this point in the history
  • Loading branch information
WolverinDEV committed Aug 1, 2024
1 parent bc5e81f commit caf1dc0
Show file tree
Hide file tree
Showing 5 changed files with 356 additions and 42 deletions.
32 changes: 29 additions & 3 deletions src/bin/conserve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
use std::cell::RefCell;
use std::fs::{File, OpenOptions};
use std::io::{BufWriter, Write};
use std::io::{self, BufWriter, Read, Write};
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use std::time::Instant;
Expand Down Expand Up @@ -188,7 +188,7 @@ enum Command {
/// Target folder where the archive should be mounted to
destination: PathBuf,

/// Create the target folder and remove all temporarly created
/// Create the target folder and remove all temporarily created
/// files on exit
#[arg(long, default_value_t = true)]
cleanup: bool,
Expand Down Expand Up @@ -490,7 +490,33 @@ impl Command {
} => {
let archive = Archive::open(open_transport(archive)?)?;
let options = MountOptions { clean: *cleanup };
mount(archive, destination, options)?;
let projection = match mount(archive, destination, options) {
Ok(handle) => handle,
Err(Error::MountDestinationExists) => {
error!("The destination already exists.");
error!("Please ensure, that the destination does not exists.");
return Ok(ExitCode::Failure);
}
Err(Error::MountDestinationDoesNotExists) => {
error!("The destination does not exists.");
error!("Please ensure, that the destination does exist prior mounting.");
return Ok(ExitCode::Failure);
}
Err(error) => return Err(error),
};

info!(
"Projection started at {}.",
projection.mount_root().display()
);
{
info!("Press any key to stop the projection...");
let mut stdin = io::stdin();
let _ = stdin.read(&mut [0u8]).unwrap();
}

info!("Stopping projection.");
drop(projection);
}
Command::Restore {
archive,
Expand Down
6 changes: 6 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ pub enum Error {
#[error("This feature is not implemented")]
NotImplemented,

#[error("The destination already exists")]
MountDestinationExists,

#[error("The destination does not exists")]
MountDestinationDoesNotExists,

/// Generic IO error.
#[error(transparent)]
IOError {
Expand Down
13 changes: 12 additions & 1 deletion src/mount/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@ pub struct MountOptions {
pub clean: bool,
}

pub fn mount(archive: Archive, destination: &Path, options: MountOptions) -> Result<()> {
/// Handle for the mount controller.
/// Once dropped, the projection will be stopped and if specified so by MountOptions cleaned.
pub trait MountHandle {
/// Returns the root path where the archive has been mounted.
fn mount_root(&self) -> &Path;
}

pub fn mount(
archive: Archive,
destination: &Path,
options: MountOptions,
) -> Result<Box<dyn MountHandle>> {
#[cfg(windows)]
return projfs::mount(archive, destination, options);

Expand Down
89 changes: 51 additions & 38 deletions src/mount/projfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use std::{
use bytes::Bytes;
use itertools::Itertools;
use lru::LruCache;
use tracing::{debug, error, info, warn};
use tracing::{debug, info, warn};
use windows_projfs::{
DirectoryEntry, DirectoryInfo, FileInfo, Notification, ProjectedFileSystem,
ProjectedFileSystemSource,
Expand All @@ -23,10 +23,10 @@ use windows_projfs::{
use crate::{
hunk_index::IndexHunkIndex,
monitor::{void::VoidMonitor, Monitor},
Apath, Archive, BandId, BandSelectionPolicy, IndexEntry, Kind, Result, StoredTree,
Apath, Archive, BandId, BandSelectionPolicy, Error, IndexEntry, Kind, Result, StoredTree,
};

use super::MountOptions;
use super::{MountHandle, MountOptions};

struct StoredFileReader {
iter: Peekable<Box<dyn Iterator<Item = Result<Bytes>>>>,
Expand Down Expand Up @@ -161,7 +161,7 @@ struct ArchiveProjectionSource {
hunk_index_cache: Mutex<LruCache<BandId, Arc<IndexHunkIndex>>>,

/*
* Cache the last accessed hunks to improve directory travesal speed.
* Cache the last accessed hunks to improve directory traversal speed.
*/
#[allow(clippy::type_complexity)]
hunk_content_cache: Mutex<LruCache<(BandId, u32), Arc<Vec<IndexEntry>>>>,
Expand Down Expand Up @@ -216,7 +216,7 @@ impl ArchiveProjectionSource {
.lock()
.unwrap()
.try_get_or_insert(band_id, || {
/* Inform the user that this band has been cached as this is most likely a heavy operaton (cpu and memory wise) */
/* Inform the user that this band has been cached as this is most likely a heavy operation (cpu and memory wise) */
info!("Caching files for band {}", stored_tree.band().id());

let helper = IndexHunkIndex::from_index(&stored_tree.band().index())?;
Expand Down Expand Up @@ -450,7 +450,7 @@ impl ProjectedFileSystemSource for ArchiveProjectionSource {
if notification.is_cancelable()
&& !matches!(notification, Notification::FilePreConvertToFull(_))
{
/* try to cancel everything, except retriving data */
/* try to cancel everything, except retrieving data */
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
Expand All @@ -459,19 +459,51 @@ impl ProjectedFileSystemSource for ArchiveProjectionSource {
}

const ERROR_CODE_VIRTUALIZATION_TEMPORARILY_UNAVAILABLE: i32 = 369;
pub fn mount(archive: Archive, destination: &Path, options: MountOptions) -> Result<()> {
struct WindowsMountHandle {
_projection: ProjectedFileSystem,
path: PathBuf,
cleanup: bool,
}

impl Drop for WindowsMountHandle {
fn drop(&mut self) {
if self.cleanup {
debug!("Removing destination {}", self.path.display());
let mut attempt_count = 0;
while let Err(err) = fs::remove_dir_all(&self.path) {
attempt_count += 1;
if err.raw_os_error().unwrap_or_default()
!= ERROR_CODE_VIRTUALIZATION_TEMPORARILY_UNAVAILABLE
|| attempt_count > 5
{
warn!("Failed to clean up projection destination: {}", err);
break;
}
std::thread::sleep(Duration::from_secs(1));
}
}
}
}

impl MountHandle for WindowsMountHandle {
fn mount_root(&self) -> &Path {
&self.path
}
}

pub fn mount(
archive: Archive,
destination: &Path,
options: MountOptions,
) -> Result<Box<dyn MountHandle>> {
if options.clean {
if destination.exists() {
error!("The destination already exists.");
error!("Please ensure, that the destination does not exists.");
return Ok(());
return Err(Error::MountDestinationExists);
}

fs::create_dir_all(destination)?;
} else if !destination.exists() {
error!("The destination does not exists.");
error!("Please ensure, that the destination does exist prior mounting.");
return Ok(());
return Err(Error::MountDestinationDoesNotExists);
}

let source = ArchiveProjectionSource {
Expand All @@ -486,31 +518,12 @@ pub fn mount(archive: Archive, destination: &Path, options: MountOptions) -> Res
};

let projection = ProjectedFileSystem::new(destination, source)?;
info!("Projection started at {}.", destination.display());
{
info!("Press any key to stop the projection...");
let mut stdin = io::stdin();
let _ = stdin.read(&mut [0u8]).unwrap();
}

info!("Stopping projection.");
drop(projection);
let handle: Box<dyn MountHandle> = Box::new(WindowsMountHandle {
_projection: projection,

if options.clean {
debug!("Removing destination {}", destination.display());
let mut attempt_count = 0;
while let Err(err) = fs::remove_dir_all(destination) {
attempt_count += 1;
if err.raw_os_error().unwrap_or_default()
!= ERROR_CODE_VIRTUALIZATION_TEMPORARILY_UNAVAILABLE
|| attempt_count > 5
{
warn!("Failed to clean up projection destination: {}", err);
break;
}
std::thread::sleep(Duration::from_secs(1));
}
}
path: destination.to_owned(),
cleanup: options.clean,
});

Ok(())
Ok(handle)
}
Loading

0 comments on commit caf1dc0

Please sign in to comment.