Best way to hold onto a temp file and directory so it doesn't get automatically cleaned up until later?

Hey all,

I've been working on getting my program running and I've converted my previous code to using the tempfile crate. Currently my code is organized like this and I apologize if I completely butcher the explanation:

I'll try to keep some brevity here but if more code is necessary to explain what I'm trying to do, let me know and I'll add it.

main.rs:

[...]
let mut proxy = tor_proxy_controller::start_proxy(pid);

//do proxy things

tor_proxy_controller::delete_arti_bin(proxy);
[...]

tor_proxy_controller.rs:

pub fn start_proxy(pid: u32) -> Result<Child,Error>{
    let arti = temp_file_controller::create_arti_bin(pid);
[...]

temp_file_controller.rs:

pub fn create_arti_bin(pid: u32) -> Result<PathBuf, std::io::Error>{
    let temp_dir = tempdir()?;
    let filename = format!("arti_{}.exe",pid);
    let file = temp_dir.path().join(filename);

    fs::write(&file, ARTI_BYTES)?;

    Ok(file)
}

pub fn delete_arti_bin(bin:Child, dir:TempDir) -> Result<(), Box<dyn std::error::Error>>{
    drop(bin);
    dir.close()?;
    Ok(())
}

I think I know how to keep the temp file and directory around at least until I'm done using it in main. By passing the PathBuf from temp_file_controller to proxy_controller, then passing the resulting Result<Child,Error> to main, I think that keeps everything around and running.

If I'm just returning the file I create within the temporary directory, how can I drop the file and close() the directory from main later on? At that point in main it's a Result<Child,Error> type.

Can I extract out the tempdir from the Child portion of the proxy object and be able to access it that way?

Or is there a way for me to somehow hold onto the PathBuf and TempDir created in temp_file_controller values so that I can just call delete_arti_bin and have the information already held so I don't need to pass anything at all?

I'm also thinking of the future where I'd like to be able to create multiple proxies simultaneously on various ports. Whatever I make here, I'd like to be extendable to handling multiple binaries.

I hope this all makes sense...kind of a stream of consciousness so thank you for your assistance as always.

Didn't they warn that having multiple Tor instances can produce mysterious failures?

I'm also confused why you use external executable rather than a crate which would interface with the network from your own binary.

You can probably do something like

pub struct Proxy {
    // I'm guessing this is what `Child` is
    process: std::process::Child,
    temp_dir: TempDir,
}

pub fn start_proxy(pid: u32) -> Result<Proxy, Error> {
    // Might need some adjustment to `Error`, unsure
    let temp_dir = tempdir()?;
    let arti = temp_file_controller::create_arti_bin(&temp_dir, pid)?;
    // ...
    Ok(Proxy { process, temp_dir })
}

impl Proxy {
    fn delete_arti_bin(self) -> Result<(), Box<dyn Error> { ... }
}
2 Likes

Oh that's interesting. I'll give this a shot.

EDIT:
So...I'm a bit stumped. I have the struct created in the temp_file_controller.rs script:

use std::{fmt::Error, fs::{self}, path::PathBuf, process::Child};
use tempfile::{TempDir, tempdir};

const ARTI_BYTES: &[u8] =  include_bytes!("C:\\Code\\spiderweb\\src\\bins\\arti.exe");

pub struct Proxy{
    pub filepath: PathBuf,
    pub dir: TempDir
}

pub fn create_arti_bin(pid: u32) -> Result<Proxy,Error>{
    let temp_dir = match tempdir() {
        Ok(dir) => {dir},
        Err(e) => {panic!("Unable to create temporary directory: {:?}",e)},
    };

    let filename = format!("arti_{}.exe",pid);
    let file = temp_dir.path().join(filename);

    match fs::write(&file, ARTI_BYTES) {
        Ok(f) => {f},
        Err(e) => {panic!("Unable to write Arti bytes to temp file: {:?}",e)},
    };

    //Ok(file)
    Ok(Proxy { filepath: file, dir: temp_dir })
}
[...]

Now I'm working on the delete_arti_bin implementation and I think I'm still stuck at where I was before so I must not be understanding how a struct is supposed to work. Or I'm doing a conversion that's rendering the custom struct pointless.

Here's where I think I'm invalidating the struct's purpose:

In main I call for the creation of the binary:

let mut proxy = tor_proxy_controller::start_proxy(pid); // proxy is a Result<Child,Error> here.

In tor_proxy_controller I now have the new struct working but I return a Child type to main.

pub fn start_proxy(pid: u32) -> Result<Child,Error>{
    let arti = temp_file_controller::create_arti_bin(pid); // This is my new struct and arti is now a Result<Proxy,Error>

    match arti {
        Ok(arti_bin) => {
            let mut tor_proxy = Command::new(arti_bin.filepath)
[...]
    Ok(tor_proxy) //This is a Child type. Currently returning to main's original call, not my custom Proxy struct type anymore.
[...]

So now...back at main and I'm at the same question as before. I have a Result<Child,Error> and I need to send it back to delete the temp file and close the temp dir. This is the current impl I'm trying to work with:

impl Proxy {
    pub fn delete_arti_bin(bin:PathBuf,dir:TempDir) -> Result<(), Box<dyn std::error::Error>>{
        drop(bin);
        dir.close()?;
        Ok(())
    }
}

I need to get from a single Result<Child,Error> to a PathBuf and TempDir which...I don't think I can do.


Potential solution? I was thinking I could write a new function in tor_proxy_controller that would act as a master control for creating and deleting the binary while also communicating to main. So main would call something like:

tor_proxy_controller::master_control("start");
// Do proxy things
tor_proxy_controller::master_control("stop");

Then in tor_proxy_controller I would do something like:

pub fn master_control(cmd:String){
    let mut arti:Result<Proxy,Error>;
    let mut tor_proxy:Child;

    match cmd.as_str {
        "start" => {
            arti = temp_file_controller::create_arti_bin
            // Do my current arti spawn and return a Child
            Ok(tor_proxy)
        }
        "stop" => {
            temp_file_controller:delete_arti_bin(arti);
        }

Or am I making all of this way more complicated than it needs to be?

Have a struct that holds both the Child and the TempDir (and if you need it the PathBuf).

    pub fn start_proxy(pid: u32) -> Result<Child, Error> {
        let arti = temp_file_controller::create_arti_bin(pid);

Remember, every local in start_proxy will drop by the end of the function if you don't return it. You don't want the TempDir to drop, so you need to return it (and everything else you need to use later) in some form. Making a struct that owns everything you need to return is a common way to accomplish this.

1 Like

Because of how I populate the structs, I don't know how to create a field but not initialize it separately. So my struct needed 3 fields to work but when it's initially created, I only have the information to populate 2 of the 3 fields. The third gets populated in a different function.

So I ended up creating two structs. The first with the two fields I could populate in one function, then a second struct that had two more fields. The first field was the first struct type and the second field was the third bit of info I needed from the separate function. So I think I nested structs? Not sure if that's appropriate but I got my program working!

Thanks for the help!

So much to learn with this language...

It's fine, or you could destruct one to create the other. Both approaches are valid.