I want to execute an external program via std::process::Command::spawn. Furthermore I want to know the reason why spawning the process failed: is it because the given program name doesn't exist/is not in PATH or because of some different error?
Example code of what I want to achieve:
match Command::new("rustc").spawn() {
Ok(_) => println!("Was spawned :)"),
Err(e) => {
if /* ??? */ {
println!("`rustc` was not found! Check your PATH!")
} else {
println!("Some strange error occurred :(");
}
},
}
When I try to execute a program that isn't on my system, I get:
Error { repr: Os { code: 2, message: "No such file or directory" } }
But I don't want to rely on that. Is there a way to determine if a program exists in PATH?
You can use e.kind() to find what ErrorKind the error was.
match Command::new("rustc").spawn() {
Ok(_) => println!("Was spawned :)"),
Err(e) => {
if let NotFound = e.kind() {
println!("`rustc` was not found! Check your PATH!")
} else {
println!("Some strange error occurred :(");
}
},
}
Edit: I didn't find any explicit documentation about what error kinds can be returned, so I looked up the source code. It seems the error is returned straight from the OS. The relevant code seems to be in src/libstd/sys/[unix/windows/..]/process.rs. A snippet from the Unix version:
One more edit: On a second thought, I'm not sure if the licenses actually allows posting parts of Rust sources here, so you can see it on github
Which just returns Error::from_raw_os_err(...). The Windows version seemed more complicated, and I couldn't immediately find where it even returns errors from. Either way, it seems you're at the mercy of your operating system regarding that. At least I found the following test in src/libstd/process.rs:
Same as above: github
That seems to guarantee that an ErrorKind::NotFound should be returned at least when the binary is not found. It makes sense to assume that the OS wouldn't give a NotFound error in other cases, but who knows. If you want to be absolutely sure that the program really was not found, you'll have to search the directories in $PATH manually. Something like:
use std::env;
use std::fs;
fn is_program_in_path(program: &str) -> bool {
if let Ok(path) = env::var("PATH") {
for p in path.split(":") {
let p_str = format!("{}/{}", p, program);
if fs::metadata(p_str).is_ok() {
return true;
}
}
}
false
}
fn main() {
let program = "rustca"; // shouldn't be found
if is_program_in_path(program) {
println!("Yes.");
} else {
println!("No.");
}
}
Related
I'm trying to delete a file tree in parallel with Rust. I'm using jwalk for parallel traversal and deletion of files. The code shown below deletes all the files. In general it's working as expected, but the performance is terrible.
Compared to a Python version I've implemented, it's 5 times slower on Windows! What am I doing wrong, so that the Rust version is that slow?
What I've found out so far is that std::fs::remove_file is the reason for the bad performance. I'm wondering if the implementation of this function has a performance issue, at least on Windows?
I'm using Rust version 1.42.0 with toolchain stable-x86_64-pc-windows-msvc.
let (tx, rx) = mpsc::channel();
WalkBuilder::new(tmpr)
.hidden(false)
.standard_filters(false)
.threads(cmp::min(30, num_cpus::get()))
.build_parallel()
.run(move || {
let tx = tx.clone();
Box::new(move |dir_entry_result| {
if let Ok(dir_entry) = dir_entry_result {
if dir_entry.file_type().unwrap().is_dir() {
tx.send(dir_entry.path().to_owned()).unwrap();
} else {
if let Err(_) = std::fs::remove_file(&dir_entry.path()) {
match fix_permissions(&dir_entry.path()) {
Ok(_) => {
if let Err(_) = std::fs::remove_file(&dir_entry.path()) {
tx.send(dir_entry.path().to_owned()).unwrap();
}
}
Err(_) => {
tx.send(dir_entry.path().to_owned()).unwrap();
}
}
}
}
}
ignore::WalkState::Continue
})
});
let paths: Vec<_> = rx.into_iter().collect();
I've found the reason for the slowdown. It was the virus scanner. The reason why the Python version was faster is that Python.exe is ignored by the virus scanner, but the Rust exe not. After I disabled the virus scanner the Rust version is now lightning fast.
I have a Rust program which is exiting silently without any trace of the reason in the logs. This would happen after several successful calls to the same method. The last log I see is one after which a FFI call is made. I do not get a log after the return of the FFI call.
use cpython;
use cpython::ObjectProtocol;
use cpython::PyResult;
fn is_complete(&self, check: bool) -> Result<bool, PyModuleError> {
let gil = cpython::Python::acquire_gil();
let py = gil.python();
debug!("Calling complete"); //This is the last log
let res = self
.py_complete
.call_method(py, "complete", (check,), None)
.expect("No method complete on python module")
.extract::<bool>(py).unwrap();
if res {
debug!("Returning true"); //This does not appear
Ok(true)
}
else {
debug!("Returning false"); //This does not appear
Ok(false)
}
}
The Python module does return a value as I had debug logs there as well to confirm.
I have tried using RUST_BACKTRACE=1 but in vain.
This works:
format!("{:?}", error))
// Os { code: 13, kind: PermissionDenied, message: "Permission denied" }
But I want only the message field, not the full debug print. How do I get it?
error.message // unknown field
error.message() // no method named `message` found for type `std::io::Error` in the current scope
I don't think there's anything that will get you exactly "Permission denied". The closest I know of is the Display implementation of the Error, which still includes the underlying error code:
use std::fs::File;
use std::error::Error;
fn main() {
let error = File::open("/does-not-exist").unwrap_err();
println!("{:?}", error);
// Error { repr: Os { code: 2, message: "No such file or directory" } }
println!("{}", error);
// No such file or directory (os error 2)
println!("{}", error.description());
// entity not found
}
If this is suitable, you can use error.to_string().
The standard library gets this string from sys::os, which gets defined based on the platform. For example, on UNIX-like platforms, it uses strerror_r. This function does not appear to be exposed in any public fashion, however.
Calling write_all on a file returns an error with the description: os error. Debug printing the error outputs: Err(Error { repr: Os(9) })
What does the error mean?
You didn't include any code, so I had to make wild guesses about what you are doing. Here's one piece of code that reproduces your error:
use std::fs;
use std::io::Write;
fn main() {
let mut f = fs::File::open("/").unwrap();
// f.write_all(b"hello").unwrap();
// Error { repr: Os(9) }
match f.write_all(b"hello") {
Ok(..) => {},
Err(e) => println!("{}", e),
}
// Bad file descriptor (os error 9)
}
If you use the Display ({}) format instead of Debug ({:?}), you will see an error message that is nicer than just the error code. Note that unwrap will use the Debug formatter, so you have to use match in this case.
You could also look up the error code in the kernel source. You don't indicate if you are running Windows (unlikely), OS X or Linux, so I guessed Linux.
There are lots of SO questions that then explain what the code can mean, but I'm sure you know how to search through those, now that you have a handle on the problem.
This question refers to Rust as of October 2014.
If you are using Rust 1.0 or above, you best look elsewhere for a solution.
I have a long running Rust process that generates log values, which I'm running using Process.
It looks at though I might be able to periodically "check on" the running process using set_timeout() and wait() and do something kind of high level loop like:
let mut child = match Command::new("thing").arg("...").spawn() {
Ok(child) => child,
Err(e) => fail!("failed to execute child: {}", e),
};
loop {
child.set_timeout(Some(100));
match child.wait() {
// ??? Something goes here
}
}
The things I'm not 100% on are; how do I tell the difference between a timeout error and a process-return error from wait(), and how to a use the PipeStream to "read as much as you can without blocking from the stream" every interval to push out.
Is this the best approach? Should I start a task to monitor stdout and stderr instead?
For distinguishing the errors from the process from the timeout, you have to manage the returns from wait, an example here:
fn run() {
let mut child = match Command::new("sleep").arg("1").spawn() {
Ok(child) => child,
Err(e) => fail!("failed to execute child: {}", e),
};
loop {
child.set_timeout(Some(1000));
match child.wait() {
// Here assume any error is timeout, you can filter from IoErrorKind
Err(..) => println!("Timeout"),
Ok(ExitStatus(0)) => {
println!("Finished without errors");
return;
}
Ok(ExitStatus(a)) => {
println!("Finished with error number: {}", a);
return;
}
Ok(ExitSignal(a)) => {
println!("Terminated by signal number: {}", a);
return;
}
}
}
}
About using streams, check with wait_with_output, or implement something similar with channels and threads : http://doc.rust-lang.org/src/std/home/rustbuild/src/rust-buildbot/slave/nightly-linux/build/src/libstd/io/process.rs.html#601
Hope it helped
Have a look in cargo:
https://docs.rs/cargo-util/0.1.1/cargo_util/struct.ProcessBuilder.html#method.exec_with_streaming
The only downside is that cargo-util seems to need openssl even with default-features=false...
But you can at least see how it and read2 are done.