Include_bytes!() not working how I thought it works

Continuing to work on the same program and I needed to find a solution to include a pre-existing binary in my Rust application. Similar to embedded resources in C#. Enter, include_bytes!() macro. I think I might be misunderstanding it's purpose or maybe it's the wrong tool for what I'm trying to do.

I have a function:

fn write_arti_bin(){
    let arti_bin = include_bytes!("C:\\Code\\rust\\src\\bins\\arti.exe");
    let filename= "arti.exe";
    let cur_dir = env::current_dir().unwrap();
    let full_path = PathBuf::from(cur_dir).join(filename);

    fs::write(full_path, arti_bin).expect("Unable to write file.");
}

This works just fine. When I compile the program and run it on my computer, it works fine. Thinking about it now, that makes sense that it would because my source arti.exe is still present at that absolute path...

I thought what would happen is that after compiling, the bytes would be in the output binary of my rust application. That does seem to be the case because when I run my binary on another computer, the arti.exe file is written to disk and is functional. BUT it errors out and panics because it can't find the source file included in let arti_bin = [...] line so it never executes the next step of the program. Again, this makes sense now because of course it couldn't find the source since it's a different machine. Although I am a bit confused why it writes to file at all if the source line happens before the write line but that's tangential to my question I think.

Since the bytes have already been read in when the program was compiled, it doesn't need to read from source anymore, it can just write the file to disk.

So my question is how do I achieve that?
Is there a decoration I can put that says to ignore that line if not running in debug mode or something?
Is include_bytes!() not the way to go for what I'm trying to do?

Thank you for your help as always!

include_bytes!() gets replaced by the bytes of the file you use it with during macro expansion. It will not ever read any file at runtime.

Can you show the exact panic that happens?

2 Likes

Yup...I think I figured it out though. PEBKAC strikes again. I was looking at the wrong line of code in the script. I thought it was erroring out at reading in the file but it was actually erroring out when I was attempting to build my command to execute said file. I never updated the command to a relative path.

That error references this line:

let tor_proxy = Command::new("C:\\Code\\rust\\src\\bins\\arti.exe")
        .arg("proxy")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("Proxy didn't start.")
        .stderr
        .ok_or_else(|| "Failed to start Arti.")
        .expect("Failed to grab output.");

I changed that to this:

let tor_proxy = Command::new("arti.exe")
        .current_dir(env::current_dir().unwrap())
        .arg("proxy")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("Proxy didn't start.")
        .stderr
        .ok_or_else(|| "Failed to start Arti.")
        .expect("Failed to grab output.");

And that seems to be happy now...unfortunately I'm running into a different error where Windows is giving permission denied when I try to delete the file I dropped to disk with this command:

pub fn delete_arti_bin(){
    let filename= "arti.exe";
    let cur_dir = env::current_dir().unwrap();
    let full_path = PathBuf::from(cur_dir).join(filename);

    fs::remove_file(full_path).expect("Unable to delete Arti bin.");
}

Error:

thread 'main' (18732) panicked at src\tor_proxy_controller.rs:43:32:
Arti bin not found in current directory.: Os { code: 5, kind: PermissionDenied, message: "Access is denied." }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

And I'm not sure why. I have full permissions to the file and the directory. It can obviously create the file but it won't delete it...oddly enough, running the program as admin (not ideal regardless) still gets a permission denied on the file deletion.

That's NOT the error message of this line:

When you drop some file to disk and Windows says "permission denied" on operations with this file, my first instinct would be "Defender checks preventing file from access".

Sorry for the confusion. It is the error message for that line. I executed a build from a few minutes before and I had changed the .expect[...] portion without rebuilding the binary. Here, I've updated the build and reran it just now. Same access denied error:

thread 'main' (17056) panicked at src\tor_proxy_controller.rs:43:32:
Unable to delete Arti bin.: Os { code: 5, kind: PermissionDenied, message: "Access is denied." }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
1 Like

Yup, on Windows you can't delete a file while it is still open. This is true for an executable file for as long as it is still running, but also more genereally for any file that might be open in some process for whatever reason. It could even be Windows Explorer "locking" the file, because it has the file open to read its meta data (icon, version info, etc. pp.) And, of course, "anti-virus" software is a common annoyance that might be locking your file while it is being "scanned" :tired_face:

So, I think you need a more "defensive" strategy here: Try to delete the file in a loop until that either succeeds or a certain amount of time has expired. Also don't treat failure to delete the file as "fatal" error. Rather put out some sort of warning in that case.


BTW: In my experience, if you write an executable file to disc and then wan't to execute it, it often takes a while (after you close the file handle that was used to write out the file) until that newly created file can actually be executed! Yet again, it seems like "anti-virus" software (or whatever) keeps an "exclusive" lock on the new file, causing access error when you try to run it...

2 Likes

My thoughts too. Unusual that it didn't yell at me when I dropped it to disk but I've had malware that I've worked on that can bypass static analysis but get caught upon execution so I suppose it's not too unusual.

I wasn't getting any alerts that defender blocked anything nor are there entries in Defender's logs saying it blocked or quarantined anything. Just in case though, I turned Defender off entirely and tried to rerun the program and received the same error. It can create the file but not delete it.

I thought about that too. I'll try putting it in a loop but just prior to deleting the file, I do issue a stop command.

This is how I call the stop and delete in my main thread:

tor_proxy_controller::stop_proxy();
tor_proxy_controller::delete_arti_bin();

You already have my delete code, here is my stop proxy code:

pub fn stop_proxy(){
    Command::new("powershell")
        .args(["$(Get-Process -Name arti).kill()"])
        .spawn()
        .expect("Arti process wasn't found by name.");
}

This might be a bit weird too. I haven't been able to find a solid way of finding the child process and stopping it from Rust. But I'm able to do it from powershell so I just made a command to do it that way.

This code works by the way, the process does disappear from procmon so the stop process command is working...maybe it's a race condition thing where I try to delete it too quickly? Maybe the process hasn't actually stopped by the time I try to delete the file. Which would also explain why, even running as admin, it was still getting an access denied.

I think your idea of throwing it in a for loop might be good. I'll keep checking to see when the process is no longer running via powershell and then try to delete the file.

Command::spawn() will give you a Child object, essentually a handle to the sub-process.

You should keep this object and call child_process::kill() as needed :scream:

Using powershell to find your own sub-process seems cumbersome and error-prone...

2 Likes

Yeah I agree...I didn't like it when I wrote it either lol.

I'm attempting to do that now. Originally when I spawned the child, I went straight to grabbing the stderr to start doing some output parsing.

    let tor_proxy = Command::new("arti.exe")
        .current_dir(env::current_dir().unwrap())
        .arg("proxy")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("Proxy didn't start.")
        .stderr
        .ok_or_else(|| "Failed to start Arti.")
        .expect("Failed to grab output.");

Which made tor_proxy a stderr type and thus not a Child. So now I'm trying to change split it out.

pub fn start_proxy() -> (Result<(),Error>, Child){
    write_arti_bin();

    // Builds the command
    let tor_proxy = Command::new("arti.exe")
        .current_dir(env::current_dir().unwrap())
        .arg("proxy")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("Proxy didn't start.")
    
    let tor_proxy_output = tor_proxy
        .stderr
        .ok_or_else(|| "Failed to start Arti.")
        .expect("Failed to grab output.");
    
    let output_reader = BufReader::new(tor_proxy_output);

    for line in output_reader
        .lines()
        .filter_map(|line| line.ok()){
            if check_if_finished(line) {
                return (Ok(()),tor_proxy);
            }
        }
    return (Err(Error),tor_proxy);
}

Now I need to return a tuple right? One return for my Result<(),Error so I can keep doing output parsing and now I also need to return...Child? BorrowedHandle? I'm getting an error on the last return [...] line of use of partially moved value: 'tor_proxy'. I think that's cause I pulled stderr out and gave it to tor_proxy_output...am I reading that right?

Just return Result<Child, Error> ?!

Also, I wouldn't use expect() or unwrap() here, because they essentialy crash your application when an error occurs. Instead, return the error upwards and let caller handle it as needed.

IMHO, it is okay to use expect() or unwrap() for things that really are expected to never fail, and that would be considered "fatal" errors in the case that they ever actually fail.

Creating a sub-process can fail for a multitude of reasons, and that should not crash your entire program. Instead, this kind of "expected" (sometimes) error should be handled gracefully...

(for example, by waiting a few seconds and retrying)

1 Like

That's fair. This is still very much in development and as I'm learning these things I'll go back and improve it. Since the whole point of the application is to send HTTP traffic through this proxy, I was ok with it crashing the whole thing if the proxy subprocess didn't start but I understand your point. I'll implement more graceful handling.

Regarding your initial response, when I change:

pub fn start_proxy() -> Result<(),Error>{
[...]

to

pub fn start_proxy() -> Result<Child,Error>{
[...]

When I get down to the returns...how do I actually do that?

[...]
            if check_if_finished(line) {
                return Ok(tor_proxy)
            }
        }
    return Err(Error)
}

If I write this, I get an error with tor_proxy, which is a Child type now, still talking about the partially moved value. Which I think is referring to me pulling .stderr out earlier in the function?

Whole function:

pub fn start_proxy() -> Result<Child,Error>{
    write_arti_bin();

    // Builds the command
    let tor_proxy = Command::new("arti.exe")
        .current_dir(env::current_dir().unwrap())
        .arg("proxy")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("Proxy didn't start.");
    
    let tor_proxy_output = tor_proxy
        .stderr
        .ok_or_else(|| "Failed to start Arti.")
        .expect("Failed to grab output.");
    
    let output_reader = BufReader::new(tor_proxy_output);

    for line in output_reader
        .lines()
        .filter_map(|line| line.ok()){
            if check_if_finished(line) {
                return Ok(tor_proxy)
            }
        }
    return Err(Error)
}

I think you should split things up:

use std::io::Error;

fn write_arti_bin() -> Result<(), Error> {
    /* ... */
}

pub fn start_proxy() -> Result<Child, Error> {
    write_arti_bin()?; // <-- Use ? operator to pass possible error upwards

    // Builds the command
    Command::new("arti.exe")
        .current_dir(env::current_dir().unwrap())
        .arg("proxy")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
}

Let the caller do whatever it needs to do with the returned Child obejct.

...or handle the error.


BTW: env::current_dir() is unreliable, since the "current directory" is per-process, and any thread in your process may change the "current directory" at any time. Also, the initial "current directory", at the start of your program, depends on many things that are outside of your control. Do not assume that "current directory" is the directory where your EXE file is located!

I'd rather do something like:

let exe_file = NamedTempFile::with_suffix("exe")?;
write_arti_bin(exe_file.as_file_mut());
Command::new(exe_file.path()).arg(/* ... */)

This creates a new unqiue temporary file, in the user's TEMP directory, and we can refer to that file via its full path when we want write to it, or when we want to start it as a sub-process.

Also, NamedTempFile automatically cleans up the file, when it is dropped.

2 Likes

Ohhhh good to know. Thank you! I didn't realize it wasn't reliable...

The documentation reminds that you should do tor_proxy.stderr.take() which would leave the object whole.

Wait...I thought .take() empties whatever you're taking doesn't it? Or maybe I can explain it better this way. If there's constant output going to child.stderr and I child.stderr.take() in the middle of the output, what happens to the next output that would normally come to child.stderr? Does it know to come to whatever object took .stderr or does it not go anywhere because child no longer has stderr?

Say, we do let taken_stderr = child.stderr.take().expect("stderr pipe absent");

The pipe's output will be readable from the taken_stderr, since it is the same pipe just at another place.

Wait...I thought .take() empties whatever you're taking doesn't it?

Yes, child.stderr will be None at that point (but the child is still a whole object).

The doc link for that is Child in std::process - Rust (I apologize if the documentation site does not open for you somehow, though).

1 Like