How to send prompt input to std::process::Command in rust? - bash

I'm writing a cli-tool to automate the archlinux installation, that is based on toml config files.
Currently i have this problem: Once the base system is installed and configured, the next topic is creating the users and set their passwords.
Like this:
passwd $user
And this needs to get the password as prompt input
New password:
I'm trying make something like this with rust:
use std::process::Command;
struct User {
....
username: String
pwd: String
}
...
fn set_pwd(self) -> Result<()> {
Command::new("chroot")
.arg("/mnt")
.arg("passwd")
.arg(self.username)
.spawn()
}
...
The problem is that I don't understand, how to pass the password as prompt input to the bash process.
Update:
This question https://stackoverflow.coam/questions/21615188/how-to-send-input-to-a-program-through-stdin-in-rust is something similar, but the implementation is a little different. Because it is a version of the standard library from some time ago.

finally i based on this question How to send input to a program through stdin in Rust
finally the method look like this...
fn set_pwd(self) -> Result<()> {
match Command::new("chroot")
.stdin(Stdio::piped())
.arg("/mnt")
.arg("passwd")
.arg(self.username)
.spawn()
{
Ok(mut child) => {
let pwd = format!("{}\n{}", self.pwd, self.pwd);
child.stdin.as_ref().unwrap().write(pwd.as_bytes()).unwrap();
child.wait().unwrap();
Ok(())
}
Err(_e) => Err(()),
}
}
the difference with the other question, is that instead of using a BuffWriter and the write! macro, the std::process::ChildStdin implements the std::io::Write trait and provides a write method.

Related

Using Rust to test telnet connection and log results

I am a Rust newbie. I've been wanting to learn Rust and decided my first project would be to build a connection testing tool packaged an executable for the tech support people at my work. Basically the tool needs to run by end-users on Windows computers and test three or more URLs using ping, tracert, and telnet. Also after running the tool, the results of the three commands should be logged into text files and lastly enclosed in a zip file at the end.
I put some code together and it's mostly working except for the telnet portion. I've been pulling my hair out trying to figure out why the telnet part is not working. I was able to compile my Rust code successfully and it runs, but no matter what the telnet part generates a telnet connection failed in the log indicating the telnet was not successful, even though I am able to run the telnet command on the same machine using the same command manually typed (telnet service1.somedomain.com 13101). So I can see telnet is installed and working...
Further down below is my code. I added some println! statements on lines 59-61 and the only clue I see so far is the status code that prints out when I run the tool says telnet command exited with status: exit code: 0xffffffff and nothing prints for telnet stdout/stderr. This seems to indicate telnet is aborting or not found, so I used the full path to the telnet.exe file on the Windows machine (C:\Windows\System32\telnet.exe) instead of just "telnet" and still got the same error.
use std::process::Command;
use std::fs::File;
use std::io::prelude::*;
use std::io::Error;
use std::path::{Path, PathBuf};
use std::io::Cursor;
//use zip::write::FileOptions;
//use zip::result::ZipWriter;
use zip::write::{FileOptions, ZipWriter};
use zip::result::ZipResult;
fn main() -> Result<(), Error> {
// Set the domains and ports to be tested
let domains_and_ports = [
("www.somedomain.com", "80"),
("serivce1.somedomain.com", "13101"),
("service2.somedomain.com", "13103")];
// Create a zip file to store the results
let zip_file_path = Path::new("results.zip");
let zip_file = File::create(zip_file_path)?;
let mut zip_writer = ZipWriter::new(zip_file);
// Set the password to encrypt the zip file
let password = b"password";
// Iterate over each domain and port combination
for (domain, port) in domains_and_ports {
// Run the ping command
let ping_output = Command::new("cmd")
.args(&["/C", format!("ping {}", domain).as_str()])
.output()?;
let ping_file_path = write_output_to_file(domain, "ping.txt", &ping_output)?;
// Run the tracert command
let tracert_output = Command::new("cmd")
.args(&["/C", format!("tracert {}", domain).as_str()])
.output()?;
let tracert_file_path = write_output_to_file(domain, "tracert.txt", &tracert_output)?;
let telnet_output = Command::new("cmd")
.args(&["/C", format!("telnet {} {}", domain, port).as_str()])
.output()?;
let telnet_file_path = write_output_to_file(domain, "telnet.txt", &telnet_output)?;
// Write the ping, tracert, and telnet results to the zip file
zip_writer.start_file(format!("{}_ping.txt", domain), FileOptions::default())?;
zip_writer.write_all(&read_file_contents(ping_file_path)?)?;
zip_writer.start_file(format!("{}_tracert.txt", domain), FileOptions::default())?;
zip_writer.write_all(&read_file_contents(tracert_file_path)?)?;
zip_writer.start_file(format!("{}_telnet.txt", domain), FileOptions::default())?;
println!("telnet command exited with status: {}", telnet_output.status);
println!("telnet stdout: {}", String::from_utf8_lossy(&telnet_output.stdout));
println!("telnet stderr: {}", String::from_utf8_lossy(&telnet_output.stderr));
if telnet_output.status.success() {
zip_writer.write_all(b"telnet connection successful")?;
} else {
zip_writer.write_all(b"telnet connection failed")?;
}
}
// Close the zip file
zip_writer.finish()?;
println!("Results saved in results.zip");
Ok(())
}
fn write_output_to_file(domain: &str, command: &str, output: &std::process::Output) -> Result<PathBuf, Error> {
let fname = format!("{}_{}", domain, command).as_str().to_owned();
//let file_path = Path::new(format!("{}_{}", domain, command).as_str());
let file_path = Path::new(&fname);
let mut file = File::create(file_path)?;
file.write_all(&output.stdout)?;
file.write_all(&output.stderr)?;
Ok(file_path.to_path_buf())
}
fn read_file_contents(file_path: PathBuf) -> Result<Vec<u8>, Error> {
let mut file = File::open(file_path)?;
let mut contents = Vec::new();
file.read_to_end(&mut contents)?;
Ok(contents)
}
This is what prints (one set shown for first domain, but it prints three sets with same error, for each domain/port combination)...
Does anyone have experience running telnet commands on Windows, plus logging telnet results using Rust? If anyone has tips or example code to fix my example above I would be truly grateful. As a side-note, I know something similar could be done with a Windows batch file or Python but I was really wanting to get the Rust project working so I could have a little "win" with this my first go around with it.
Thanks in advance for any help you can offer!
PS - the domains and ports are made-up for this post :).

How can i Print own Error Message on Java Jar CMD

how can I Print adittional information to Command line Console?
Output now is:
C:\Users\admin\Desktop\java>java -jar pdf.jar
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
at readDataIn.main(readDataIn.java:31)
Code:
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
try {
String arg = args[0];
fileNameSource = "import/" + arg + ".xml";
fileNameTarget = "export/" + arg + ".pdf";
} catch (Exception e) {
// TODO: handle exception
**System.out.println("Personal-Number is missing");**
e.printStackTrace();
}
How can i give the information out, that the Personal Number ist Missing?
First of all, as a general rule you should check for possible exceptions before they actually occur if that is possible, which in your case it definitely is.
So instead of catching the ArrayIndexOutOfBounds insert an if statement that checks the length of the args array before accessing it.
if(args.length == 0){
// no argument has been provided
// handle error here
}
In terms of how to handle the error, there are many options available and depending of what you want to do either could be a good fit.
IllegalArgumentException
It is a common idiom in Java that whenever a function receives an invalid/ illegal argument to throw an IllegalArgumentException.
if (args.length == 0){
throw new IllegalArgumentException("Personal number is missing");
}
This will print the message that you have provided and the stack trace. However if your application should be a Command Line Interface (CLI) you should not use this kind of error handling.
Print message & exit program
if (args.length == 0){
// notice: "err" instead of "out": print to stderr instead of stdout
System.err.println("Personal number is missing");
// exit program with non-zero exit code as exit code == 0 means everything is fine
System.exit(1);
}
For more information on stdout and stderr see this StackOverflow question.
This is what many CLI applications and e.g. java itself does. When you type java fdsdfsdfs or some similar nonsense as an argument Java will give you an error message and exit with some non-zero return code ("1" in this case).
It is also common that CLI applications print an error message and following some usage information on how to correctly use the application or provide a help command so a user can get more information. This happens for example if you just enter java without any parameters.
So it is really up to you what you want to do.
If you are thinking of implementing a full featured CLI application with more (complex) commands with multiple options etc. you should consider using a CLI library like JCommander or Apache Commons CLI as parsing command line arguments can quickly get ugly. All these common things are already handled there.
Logging
In case your application is some script that will be executed in a non-interactive way logging the error to a file and exiting with a non-zero exit code might also be an option.
PS
Your code looks to me like it should not compile at all as you are not declaring a type for your variables fileNameSource and fileNameTarget.
Use String or var here (assuming you're running > Java 11).
String fileNameSource = "import/" + arg + ".xml";
var fileNameTarget = "export/" + arg + ".pdf";
You might also need to consider that your program name is part of the args array, so you might have more than 0 values in the array and therefore might need to adjust the if statements above.
You may be interested in picocli, which is a modern CLI library for Java and other JVM languages.
Picocli does some basic validation automatically, and results in very compact code that produces user-friendly applications. For example:
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
#Command(name = "myapp", mixinStandardHelpOptions = true, version = "1.0",
description = "This command does something useful.")
class MyApp implements Runnable {
#Parameters(description = "File name (without extension) of the file to import and export.")
private String personalNumber;
#Override
public void run() {
String fileNameSource = "import/" + personalNumber + ".xml";
String fileNameTarget = "export/" + personalNumber + ".pdf";
// remaining business logic
}
public static void main(String[] args) {
System.exit(new CommandLine(new MyApp()).execute(args));
}
}
If I run this class without any parameters, the following message is printed to the standard error stream, and the process finished with exit code 2. (Exit codes are customizable.)
Missing required parameter: '<personalNumber>'
Usage: myapp [-hV] <personalNumber>
This command does something useful.
<personalNumber> File name (without extension) of the file to import
and export.
-h, --help Show this help message and exit.
-V, --version Print version information and exit.
The usage help message is created automatically from the descriptions of the command, and the descriptions of its options and positional parameters, but can be further customized.
Note how the mixinStandardHelpOptions = true annotation adds --help and --version options to the command. These options are handled by the library without requiring any further logic in the application.
Picocli comes with an annotation processor that makes it very easy to turn your application into a native image with GraalVM. Native images have faster startup time and lower runtime memory overhead compared to a Java VM.

How can one synchronize an external application with a DigitalMicrograph script?

This question has been inspired by the question 'Call script from command line'.
How can one write a script which acts on 'trigger events' in an application other than DigitalMicrograph?
i.e. some script functionality should be triggered by an external application.
The scripting language does not offer many 'external' interfaces in its current state. It is possible to call out to an external process with the command LaunchExternalProcess and wait for the process to complete, but there is no straight-forward way for an external process to call in, i.e. to start a script-action within DigitalMicrograph.
However, it is possible to work around that issue by using the system's file-system as a message queue. To do this, have a script running in the background which regularly checks if a certain file exists, and have the external application create such a file when it wants to trigger a scripting-action in DigitalMicrograph. The file content - if it is a simple text file - can also be used to transport information between the two applications.
Here is an example script which will wait until the file Trigger.txt appears in the root folder. The check is performed every 10seconds.
class WaitForExternal
{
string triggerFilePath
number taskID
void WaitOnTrigger( object self )
{
if ( DoesFileExist( triggerFilePath ) )
{
Result( GetTime(1) + ": Triggered! Now act..." )
If ( TwoButtonDialog( "Do you want to reset the trigger?", "Reset", "Stop" ) )
{
DeleteFile( triggerFilePath )
}
else
{
RemoveMainThreadTask( taskID )
}
}
else
{
Result( GetTime(1) + ": not yet\n" )
}
}
object Init( object self, string triggerPath, number waitSec )
{
triggerFilePath = triggerPath
taskID = self.AddMainThreadPeriodicTask( "WaitOnTrigger", waitSec )
return self
}
}
// Main script
{
string triggerPath = "C:\\Trigger.txt"
number pollingWait = 10
Alloc(WaitForExternal).Init( triggerPath, pollingWait )
}
Note that the periodic task waits idle in the background without interfering with the CPU, but the actual check is then performed on the main thread.

Cocoa Authorization in Swift

This is my first time writing in Swift, Cocoa (have experience in Cocoa Touch), and using Authorization, so I honestly have no idea if I am even on the right track. I am trying to make a modification to the hosts file, which requires user authentication, but both the AuthorizationCreate and AuthorizationExecuteWithPrivileges methods are giving errors.
var authorizationRef:AuthorizationRef
var status:OSStatus
status = AuthorizationCreate(nil, environment:kAuthorizationEmptyEnvironment, flags:kAuthorizationFlagDefaults, authorization:&authorizationRef)
let overwrite_hosts = "echo \(hostsContents) > /private/etc/hosts"
let args = [overwrite_hosts.cStringUsingEncoding(NSUTF8StringEncoding)]
status = AuthorizationExecuteWithPrivileges(authorizationRef, pathToTool:"/bin/sh", options:kAuthorizationFlagDefaults, arguments:args, communicationsPipe:nil)
Me calling AuthorizationCreate is throwing "Type '()' does not conform to protocol 'AuthorizationRef'" and my call of AuthorizationExecuteWithPrivileges is throwing "Could not find an overload for '__conversion' that accepts the supplied arguments"
Any ideas? Am I approaching this incorrectly?
Thanks for any help!
I was able to figure out how to do it via AppleScript, but you should be able to do it using the Authorization method I was trying before, therefore leaving this question open. Anybody looking for a quick solution (no error checks implemented) you can use what I wrote below:
func doScriptWithAdmin(inScript:String) -> String{
let script = "do shell script \"\(inScript)\" with administrator privileges"
var appleScript = NSAppleScript(source: script)
var eventResult = appleScript.executeAndReturnError(nil)
if !eventResult {
return "ERROR"
}else{
return eventResult.stringValue
}
}

problem in imagemagick and grails

i have a new problem in image magick that look strange ..
i'm using mac osx snow leopard and i've installed image magick on it and it's working fine on command ..
but when i call it from the grails class like the following snippet it gives me
"Cannot run program "convert": error=2, No such file or directory"
the code is :-
public static boolean resizeImage(String srcPath, String destPath,String size) {
ArrayList<String> command = new ArrayList<String>(10);
command.add("convert");
command.add("-geometry");
command.add(size);
command.add("-quality");
command.add("100" );
command.add(srcPath);
command.add(destPath);
System.out.println(command);
return exec((String[])command.toArray(new String[1]));
}
private static boolean exec(String[] command) {
Process proc;
try {
//System.out.println("Trying to execute command " + Arrays.asList(command));
proc = Runtime.getRuntime().exec(command);
} catch (IOException e) {
System.out.println("IOException while trying to execute " );
for(int i =0 ; i<command.length; i++) {
System.out.println(command[i]);
}
return false;
}
//System.out.println("Got process object, waiting to return.");
int exitStatus;
while (true) {
try {
exitStatus = proc.waitFor();
break;
} catch (java.lang.InterruptedException e) {
System.out.println("Interrupted: Ignoring and waiting");
}
}
if (exitStatus != 0) {
System.out.println("Error executing command: " + exitStatus);
}
return (exitStatus == 0);
}
i've tried normal command like ls and it's ok so the problem is that grails can't find convert command itself.. is it a os problem or something?
(see lower for the answer)
I have run into the same problem. The issue appears to be something with Mac OS X specifically, as we have several Linux instances running without error. The error looks similar to the following:
java.io.IOException: Cannot run program "/usr/bin/ImageMagick-6.7.3/bin/convert /a/temp/in/tmpPic3143119797006817740.png /a/temp/out/100000726.png": error=2, No such file or directory
All the files are there, and in chmod 777 directories - and as you pointed out, running the exact command from the shell works fine.
My theory at this point is that imagemgick can not load some sort of library itself, and the "no such file" is in reference to an dylib or something along those lines.
I have tried setting LD_LIBRARY_PATH and a few others to no avail.
I finally got this working. Here is how I have it setup. I hope this helps.
The crux of the fix, for me, was I wrapped the 'convert' into a shell script, set a bunch of environment variables, and then call that shell script instead of convert directly:
(convertWrapper.sh)
export MAGICK_HOME=/usr/local/ImageMagick-6.7.5
export MAGICK_CONFIGURE_PATH=${MAGICK_HOME}/etc/ImageMagick:${MAGICK_HOME}/share/doc/ImageMagick/www/source
export PATH=${PATH}:${MAGICK_HOME}/bin
export LD_LIBRARY_PATH=${MAGICK_HOME}/lib:${LD_LIBRARY_PATH}
export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:${MAGICK_HOME}/lib
export MAGICK_TMPDIR=/private/tmp
echo "$#" >> /private/tmp/m.log 2>&1
/usr/local/ImageMagick-6.7.5/bin/convert -verbose "$#" >> /private/tmp/m.log 2>&1
(convertWrapper.sh)
Additionally, the convert call was doing some rather complicated stuff, so I added the parameter '-respect-parenthesis' (which may or may not have had an effect).
I am not sure how much of the environment variable setting is needed as I was stabbing in the dark for a while, but since this is only for my development box...
You need to work out what your PATH is set to when you run a command from Java. It must be different to the one you have when running from the terminal.
Are you running Grails (via Tomcat?) as a different user? It might have a different path to your normal user.
you might want to try one of the Image Plugins that are part of the grails ecosystem
http://www.grails.org/ImageTools+plugin
the grails path when the app is running in the server is probably different from running java from the command line
I do so:
Put "convert" file to /usr/bin
Then add to Config.groovy:
gk {
imageMagickPath = "/usr/bin/convert"
}
Then in my ImageService.groovy:
import org.springframework.web.context.request.RequestContextHolder as RCH
[..]
def grailsApplication = RCH.requestAttributes.servletContext.grailsApplication
def imPath = grailsApplication.config.gk.imageMagickPath
def command = imPath + " some_properties"
def proc = Runtime.getRuntime().exec(command)
So this way you get command like: /usr/bin/convert some_properties
And it works, but don't forget to put file "convert" to you location and use it with this location.

Resources