How to wait for the last cmd command to generate a file, and then read the binary of this file, using Rust - windows

I am writing a program, the desired effect is to generate a lua file, then compile this lua on windows platform using luac.exe and read the binary value of the generated luac.out file.
My code is here
fn test(lua_path: PathBuf, luac_path: String) -> Result<String, Box<dyn Error>> {
let index = luac_path.rfind("/").ok_or("path fail")?;
let (path, file) = luac_path.split_at(index + 1);
let cmd_str = format!("/c cd /d {} && .\\{} {}", path, file, lua_path.display());
let child = Command::new("cmd").raw_arg(cmd_str).spawn()?;
let output = child.wait_with_output()?;
if !output.status.success() {
let output_str = String::from_utf8(output.stderr)?;
return Err(output_str.into());
}
// Without the following code, luac.out will be generated normally.
// But once the following code is added, luac.out will be generated as 0kb and receive an access denied error
let out_path = Path::new(&path).join("luac.out");
let mut out_file = File::create(&out_path)?;
let mut buf = Vec::new();
out_file.read_to_end(&mut buf)?;
let result = String::from_utf8_lossy(&buf).to_string();
Ok(result)
}
My guess is that after executing child.wait, the file is not created to completion and then the program accesses it causing an error, how do I fix this?

Related

Insert into hashmap in a loop

I'm opening a CSV file and reading it using BufReader and splitting each line into a vector. Then I try to insert or update the count in a HashMap using a specific column as key.
let mut map: HashMap<&str, i32> = HashMap::new();
let reader = BufReader::new(input_file);
for line in reader.lines() {
let s = line.unwrap().to_string();
let tokens: Vec<&str> = s.split(&d).collect(); // <-- `s` does not live long enough
if tokens.len() > c {
println!("{}", tokens[c]);
let count = map.entry(tokens[c].to_string()).or_insert(0);
*count += 1;
}
}
The compiler kindly tells me s is shortlived. Storing from inside a loop a borrowed value to container in outer scope? suggests "owning" the string, so I tried to change
let count = map.entry(tokens[c]).or_insert(0);
to
let count = map.entry(tokens[c].to_string()).or_insert(0);
but I get the error
expected `&str`, found struct `std::string::String`
help: consider borrowing here: `&tokens[c].to_string()`
When I prepend ampersand (&) the error is
creates a temporary which is freed while still in use
note: consider using a `let` binding to create a longer lived
There is some deficiency in my Rust knowledge about borrowing. How can I make the hashmap own the string passed as key?
The easiest way for this to work is for your map to own the keys. This means that you must change its type from HasMap<&str, i32> (which borrows the keys) to HashMap<String, i32>. At which point you can call to_string to convert your tokens into owned strings:
let mut map: HashMap<String, i32> = HashMap::new();
let reader = BufReader::new(input_file);
for line in reader.lines() {
let s = line.unwrap().to_string();
let tokens:Vec<&str> = s.split(&d).collect();
if tokens.len() > c {
println!("{}", tokens[c]);
let count = map.entry(tokens[c].to_string()).or_insert(0);
*count += 1;
}
}
Note however that this means that tokens[c] will be duplicated even if it was already present in the map. You can avoid the extra duplication by trying to modify the counter with get_mut first, but this requires two lookups when the key is missing:
let mut map: HashMap<String, i32> = HashMap::new();
let reader = BufReader::new(input_file);
for line in reader.lines() {
let s = line.unwrap().to_string();
let tokens:Vec<&str> = s.split(&d).collect();
if tokens.len() > c {
println!("{}", tokens[c]);
if let Some (count) = map.get_mut (tokens[c]) {
*count += 1;
} else {
map.insert (tokens[c].to_string(), 1);
}
}
}
I don't know of a solution that would only copy the key when there was no previous entry but still do a single lookup.

Changing/Creating Windows registry.pol using Rust

I'm trying to edit registry.pol file.
, but I do not know how to save changes.
I used crate registry-pol (https://docs.rs/registry-pol/1.0.0/registry_pol/v1/fn.parse.html) to read content of the current file.
let mut f = File::open("c:\\windows\\System32\\GroupPolicy\\machine\\registry.pol").unwrap();
let mut buffer = Vec::new();
f.read_to_end(&mut buffer).unwrap();
let mut x = registry_pol::v1::parse(&buffer).unwrap();
let value: u32 = 1;
let bytes = value.to_le_bytes();
x.push(registry_pol::v1::RegistryValue {
key: "any key".to_string(),
value: Some("any value".to_string()),
data_type: Some(registry_pol::v1::RegistryValueType::REG_DWORD),
data: Some(bytes.to_vec()),
});
But if I add anything to Vec I do not have a clue how to save it as new registry.pol file.
Thanks

std::process::Command cannot run hdiutil on macOS (mount failed - No such file or directory) but the command works fine when run in the terminal

hdiutils, when fed a correct path to a valid file, returns error 2, no such file or directory. When I join the indices of the command array with " ", print them, copy them and run the exact string in a terminal, it works fine.
This is the function edited to contain only the relevant bits. In order to reproduce my error, you will need a disk image located at ~/Downloads/StarUML.dmg.
use std::env;
use std::fs;
use std::process::Command;
fn setup_downloads(download_name: &str) {
let downloads_path: String = {
if cfg!(unix) {
//these both yield options to unwrap
let path = env::home_dir().unwrap();
let mut downloads_path = path.to_str().unwrap().to_owned();
downloads_path += "/Downloads/";
downloads_path
} else {
"we currently only support Mac OS".to_string()
}
};
let files_in_downloads =
fs::read_dir(&downloads_path).expect("the read_dir that sets files_in_downloads broke");
let mut file_path: String = "None".to_string();
for file_name in files_in_downloads {
let file_name: String = file_name
.expect("the pre string result which sets file_name has broken")
.file_name()
.into_string()
.expect("the post string result which sets file_name has broken")
.to_owned();
if file_name.contains(&download_name) {
file_path = format!("'{}{}'", &downloads_path, &file_name);
}
}
let len = file_path.len();
if file_path[len - 4..len - 1] == "dmg".to_string() {
let mount_command = ["hdiutil", "mount"];
let output = Command::new(&mount_command[0])
.arg(&mount_command[1])
.arg(&file_path)
.output()
.expect("failed to execute mount cmd");
if output.status.success() {
println!(
"command successful, returns: {}",
String::from_utf8_lossy(&output.stderr).into_owned()
);
} else {
println!(
"command failed, returns: {}",
String::from_utf8_lossy(&output.stderr).into_owned()
);
}
}
}
fn main() {
setup_downloads(&"StarUML".to_string());
}
Split your Command into a variable and print it using the debugging formatter after you have specified the arguments:
let mut c = Command::new(&mount_command[0]);
c
.arg(&mount_command[1])
.arg(&file_path);
println!("{:?}", c);
This outputs
"hdiutil" "mount" "\'/Users/shep/Downloads/StarUML.dmg\'"
Note that Command automatically provides quoting for each argument, but you have added your own set of single quotes:
format!("'{}{}'", &downloads_path, &file_name);
// ^ ^
Remove these single quotes.

How to read one single char in Rust? [duplicate]

I want to run an executable that blocks on stdin and when a key is pressed that same character is printed immediately without Enter having to be pressed.
How can I read one character from stdin without having to hit Enter? I started with this example:
fn main() {
println!("Type something!");
let mut line = String::new();
let input = std::io::stdin().read_line(&mut line).expect("Failed to read line");
println!("{}", input);
}
I looked through the API and tried replacing read_line() with bytes(), but everything I try requires me to hit Enter before read occurs.
This question was asked for C/C++, but there seems to be no standard way to do it: Capture characters from standard input without waiting for enter to be pressed
It might not be doable in Rust considering it's not simple in C/C++.
While #Jon's solution using ncurses works, ncurses clears the screen by design. I came up with this solution that uses the termios crate for my little project to learn Rust. The idea is to modify ECHO and ICANON flags by accessing tcsetattr through termios bindings.
extern crate termios;
use std::io;
use std::io::Read;
use std::io::Write;
use termios::{Termios, TCSANOW, ECHO, ICANON, tcsetattr};
fn main() {
let stdin = 0; // couldn't get std::os::unix::io::FromRawFd to work
// on /dev/stdin or /dev/tty
let termios = Termios::from_fd(stdin).unwrap();
let mut new_termios = termios.clone(); // make a mutable copy of termios
// that we will modify
new_termios.c_lflag &= !(ICANON | ECHO); // no echo and canonical mode
tcsetattr(stdin, TCSANOW, &mut new_termios).unwrap();
let stdout = io::stdout();
let mut reader = io::stdin();
let mut buffer = [0;1]; // read exactly one byte
print!("Hit a key! ");
stdout.lock().flush().unwrap();
reader.read_exact(&mut buffer).unwrap();
println!("You have hit: {:?}", buffer);
tcsetattr(stdin, TCSANOW, & termios).unwrap(); // reset the stdin to
// original termios data
}
One advantage of reading a single byte is capturing arrow keys, ctrl etc. Extended F-keys are not captured (although ncurses can capture these).
This solution is intended for UNIX-like platforms. I have no experience with Windows, but according to this forum perhaps something similar can be achieved using SetConsoleMode in Windows.
Use one of the 'ncurses' libraries now available, for instance this one.
Add the dependency in Cargo
[dependencies]
ncurses = "5.86.0"
and include in main.rs:
extern crate ncurses;
use ncurses::*; // watch for globs
Follow the examples in the library to initialize ncurses and wait for single character input like this:
initscr();
/* Print to the back buffer. */
printw("Hello, world!");
/* Update the screen. */
refresh();
/* Wait for a key press. */
getch();
/* Terminate ncurses. */
endwin();
You can also use termion, but you will have to enable the raw TTY mode which changes the behavior of stdout as well. See the example below (tested with Rust 1.34.0). Note that internally, it also wraps the termios UNIX API.
Cargo.toml
[dependencies]
termion = "1.5.2"
main.rs
use std::io;
use std::io::Write;
use std::thread;
use std::time;
use termion;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
fn main() {
// Set terminal to raw mode to allow reading stdin one key at a time
let mut stdout = io::stdout().into_raw_mode().unwrap();
// Use asynchronous stdin
let mut stdin = termion::async_stdin().keys();
loop {
// Read input (if any)
let input = stdin.next();
// If a key was pressed
if let Some(Ok(key)) = input {
match key {
// Exit if 'q' is pressed
termion::event::Key::Char('q') => break,
// Else print the pressed key
_ => {
write!(
stdout,
"{}{}Key pressed: {:?}",
termion::clear::All,
termion::cursor::Goto(1, 1),
key
)
.unwrap();
stdout.lock().flush().unwrap();
}
}
}
thread::sleep(time::Duration::from_millis(50));
}
}
Here's a lightweight solution only using the libc crate based some code from the console crate:
fn setup_raw_terminal() -> io::Result<()> {
unsafe {
let tty;
let fd = if libc::isatty(libc::STDIN_FILENO) == 1 {
libc::STDIN_FILENO
} else {
tty = fs::File::open("/dev/tty")?;
tty.as_raw_fd()
};
let mut ptr = core::mem::MaybeUninit::uninit();
if libc::tcgetattr(fd, ptr.as_mut_ptr()) == 0 {
let mut termios = ptr.assume_init();
let c_oflag = termios.c_oflag;
libc::cfmakeraw(&mut termios);
termios.c_oflag = c_oflag;
if libc::tcsetattr(fd, libc::TCSADRAIN, &termios) == 0 {
return Ok(());
}
}
}
Err(io::Error::last_os_error())
}
It needs to be called before reading stdin:
let mut buf = [0u8; 1024];
let mut stdin = io::stdin();
setup_raw_terminal()?;
loop {
let size = stdin.read(&mut buf)?;
let data = &buf[0..size];
println!("stdin data: {}", data);
}

How to send input to a program through stdin in Rust

I am attempting to write a shell in Rust. One of the functions of a shell is being able to redirect input to a file, redirect a file to input, and pipe output of a program into another program. I am using the run::process_output functions in std to run programs and get their output, but I don't know how to send input as if it was stdin to a program after running it. Is there some way to create an object that is directly connected to the ran program and push in input like it was typed in stdin?
This program demonstrates how you can launch external programs and stream their stdout -> stdin together:
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::process::{Command, Stdio};
fn main() {
// Create some argument vectors for lanuching external programs
let a = vec!["view", "-h", "file.bam"];
let outsam = vec!["view", "-bh", "-o", "rust.bam", "-"];
let mut child = Command::new("samtools")
.args(&a)
.stdout(Stdio::piped())
.spawn()
.unwrap();
let outchild = Command::new("samtools")
.args(&outsam)
.stdin(Stdio::piped())
.spawn()
.unwrap();
// Create a handle and writer for the stdin of the second process
let mut outstdin = outchild.stdin.unwrap();
let mut writer = BufWriter::new(&mut outstdin);
// Loop over the output from the first process
if let Some(ref mut stdout) = child.stdout {
for line in BufReader::new(stdout).lines() {
let mut l: String = line.unwrap();
// Need to add an end of line character back to the string
let eol: &str = "\n";
l = l + eol;
// Print some select lines from the first child to stdin of second
if (l.chars().skip(0).next().unwrap()) == '#' {
// convert the string into bytes and write to second process
let bytestring = l.as_bytes();
writer.write_all(bytestring).unwrap();
}
}
}
}
You'll need a handle to a running process to do this.
// spawn process
let mut p = std::process::Command::new(prog).arg(arg).spawn().unwrap();
// give that process some input, processes love input
p.stdin.as_mut().unwrap().write_str(contents);
// wait for it to complete, you may need to explicitly close stdin above
// i.e. p.stdin.as_mut().unwrap().close();
p.wait();
The above should let you send arbitrary input to a process. It would be important to close the stdin pipe if the spawned process reads until eof, like many programs do.
An updated version of Michael's answer. If your output/input is small, you can read it into a string and pipe it back in the following manner:
let output = Command::new("ls").arg("-aFl")
.output().unwrap().stdout;
let output = String::from_utf8_lossy(&output);
println!("First program output: {:?}", output);
let put_command = Command::new("my_other_program")
.stdin(Stdio::piped())
.spawn().unwrap();
write!(put_command.stdin.unwrap(), "{}", output).unwrap();

Resources