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?
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.
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.
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
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"
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...
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.
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.
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?
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)
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.
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)
}
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.
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?