Skip to content

Commit

Permalink
Support temporary data directories and files.
Browse files Browse the repository at this point in the history
Create and robustly delete temporary data, which is stored in the user data directory under `${data_dir}/cross-rs/tmp`. Therefore, if program execution is ever halted, deleting the directory will automatically cleanup any remaining data.

A termination handler cleans up the temporary data on exit by clearing a stack of temporary files/directories, and we have 2 resource handlers that cleanup the temporary data when the object is dropped.

The new installer for the startup hooks is `install_exit_hooks`, which installs both the panic and termination handlers. To get the directory containing all temporary data, use `cross::temp::dir()`.

An example of using temporary directories and files is:

```rust
{
    // these are all safe due to single-threaded execution
    let tmpdir1 = unsafe { temp::TempFile::new() }?;
    let tmpdir2 = unsafe { temp::TempFile::new() }?;
    let tmpfile1 = unsafe { temp::TempFile::new() }?;
    let tmpfile2 = unsafe { temp::TempFile::new() }?;

    for entry in fs::read_dir(tmpdir1.path()) {
        ...
    }
    for entry in fs::read_dir(tmpdir2.path()) {
        ...
    }
}   // cleanup tmpfile2 -> tmpfile1 -> tmpdir2 -> tmpdir1
```

Note that only these 2 wrappers are provided, since it guarantees the temporary file and directory stack stays ordered and resources are cleaned up as desired.
  • Loading branch information
Alexhuszagh committed Jun 16, 2022
1 parent 3ded782 commit 12f51bb
Show file tree
Hide file tree
Showing 10 changed files with 340 additions and 9 deletions.
111 changes: 111 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ serde_json = "1"
serde_ignored = "0.1.2"
shell-words = "1.1.0"
sha1_smol = "1.0.0"
ctrlc = { version = "3.2.2", features = ["termination"] }
directories = "4.0.1"
tempfile = "3.3.0"

[target.'cfg(not(windows))'.dependencies]
nix = { version = "0.24", default-features = false, features = ["user"] }
Expand Down
49 changes: 49 additions & 0 deletions src/bin/commands/clean.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::fs;

use super::images::RemoveImages;
use clap::Args;

#[derive(Args, Debug)]
pub struct Clean {
/// Provide verbose diagnostic output.
#[clap(short, long)]
pub verbose: bool,
/// Force removal of images.
#[clap(short, long)]
pub force: bool,
/// Remove local (development) images.
#[clap(short, long)]
pub local: bool,
/// Remove images. Default is a dry run.
#[clap(short, long)]
pub execute: bool,
/// Container engine (such as docker or podman).
#[clap(long)]
pub engine: Option<String>,
}

impl Clean {
pub fn run(self, engine: cross::docker::Engine) -> cross::Result<()> {
let tempdir = cross::temp::dir()?;
match self.execute {
true => {
if tempdir.exists() {
fs::remove_dir_all(tempdir)?;
}
}
false => println!("fs::remove_dir_all({})", tempdir.display()),
}

let remove_images = RemoveImages {
targets: vec![],
verbose: self.verbose,
force: self.force,
local: self.local,
execute: self.execute,
engine: None,
};
remove_images.run(engine)?;

Ok(())
}
}
26 changes: 18 additions & 8 deletions src/bin/commands/images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ pub struct ListImages {
pub engine: Option<String>,
}

impl ListImages {
pub fn run(self, engine: cross::docker::Engine) -> cross::Result<()> {
list_images(self, &engine)
}
}

#[derive(Args, Debug)]
pub struct RemoveImages {
/// If not provided, remove all images.
Expand All @@ -39,6 +45,16 @@ pub struct RemoveImages {
pub engine: Option<String>,
}

impl RemoveImages {
pub fn run(self, engine: cross::docker::Engine) -> cross::Result<()> {
if self.targets.is_empty() {
remove_all_images(self, &engine)
} else {
remove_target_images(self, &engine)
}
}
}

#[derive(Subcommand, Debug)]
pub enum Images {
/// List cross images in local storage.
Expand All @@ -50,14 +66,8 @@ pub enum Images {
impl Images {
pub fn run(self, engine: cross::docker::Engine) -> cross::Result<()> {
match self {
Images::List(args) => list_images(args, &engine),
Images::Remove(args) => {
if args.targets.is_empty() {
remove_all_images(args, &engine)
} else {
remove_target_images(args, &engine)
}
}
Images::List(args) => args.run(engine),
Images::Remove(args) => args.run(engine),
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/bin/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod clean;
mod images;

pub use self::clean::*;
pub use self::images::*;
6 changes: 6 additions & 0 deletions src/bin/cross-util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ enum Commands {
/// List cross images in local storage.
#[clap(subcommand)]
Images(commands::Images),
/// Clean all cross data in local storage.
Clean(commands::Clean),
}

fn is_toolchain(toolchain: &str) -> cross::Result<String> {
Expand Down Expand Up @@ -49,6 +51,10 @@ pub fn main() -> cross::Result<()> {
let engine = get_container_engine(args.engine(), args.verbose())?;
args.run(engine)?;
}
Commands::Clean(args) => {
let engine = get_container_engine(args.engine.as_deref(), args.verbose)?;
args.run(engine)?;
}
}

Ok(())
Expand Down
2 changes: 2 additions & 0 deletions src/bin/cross.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

pub fn main() -> cross::Result<()> {
cross::install_panic_hook()?;
cross::install_termination_hook()?;

let status = cross::run()?;
let code = status
.code()
Expand Down
26 changes: 26 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,39 @@
use crate::temp;

use std::sync::atomic::{AtomicBool, Ordering};

pub use color_eyre::Section;
pub use eyre::Context;
pub use eyre::Result;

pub static mut TERMINATED: AtomicBool = AtomicBool::new(false);

pub fn install_panic_hook() -> Result<()> {
color_eyre::config::HookBuilder::new()
.display_env_section(false)
.install()
}

/// # Safety
/// Safe as long as we have single-threaded execution.
unsafe fn termination_handler() {
// we can't warn the user here, since locks aren't signal-safe.
// we can delete files, since fdopendir is thread-safe, and
// `openat`, `unlinkat`, and `lstat` are signal-safe.
// https://man7.org/linux/man-pages/man7/signal-safety.7.html
if !TERMINATED.swap(true, Ordering::SeqCst) && temp::has_tempfiles() {
temp::clean();
}

// EOWNERDEAD, seems to be the same on linux, macos, and bash on windows.
std::process::exit(130);
}

pub fn install_termination_hook() -> Result<()> {
// SAFETY: safe since single-threaded execution.
ctrlc::set_handler(|| unsafe { termination_handler() }).map_err(Into::into)
}

#[derive(Debug, thiserror::Error)]
pub enum CommandError {
#[error("`{1}` failed with exit code: {0}")]
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ mod id;
mod interpreter;
mod rustc;
mod rustup;
pub mod temp;

use std::env;
use std::io::{self, Write};
Expand All @@ -44,7 +45,7 @@ use self::cross_toml::CrossToml;
use self::errors::Context;
use self::rustc::{TargetList, VersionMetaExt};

pub use self::errors::{install_panic_hook, Result};
pub use self::errors::{install_panic_hook, install_termination_hook, Result};
pub use self::extensions::{CommandExt, OutputExt};

pub const CROSS_LABEL_DOMAIN: &str = "org.cross-rs";
Expand Down
Loading

0 comments on commit 12f51bb

Please sign in to comment.