-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #22 from tinythings/isbm-sys_run-module
Add sys.run module
- Loading branch information
Showing
5 changed files
with
252 additions
and
17 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[package] | ||
name = "run" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
libsysinspect = { path = "../../../libsysinspect" } | ||
serde = "1.0.213" | ||
serde_json = "1.0.132" | ||
serde_yaml = "0.9.34" | ||
shlex = "1.3.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
use core::str; | ||
use libsysinspect::{ | ||
init_mod_doc, | ||
modlib::{ | ||
modinit::ModInterface, | ||
response::ModResponse, | ||
runtime::{self, get_arg, get_call_args, get_opt, send_call_response, ModRequest}, | ||
}, | ||
}; | ||
use serde_json::json; | ||
use shlex::Shlex; | ||
use std::{ | ||
collections::HashMap, | ||
io::Write, | ||
process::{Command, Stdio}, | ||
}; | ||
|
||
/// Parse passed environment. | ||
/// Env is passed in the form of key=value. The following form is supported: | ||
/// | ||
/// `VAR_ONE="value" VAR_TWO=value VAR_THREE="spaces are supported"` | ||
fn getenv(env: &str) -> HashMap<String, String> { | ||
let mut out = HashMap::new(); | ||
for elm in Shlex::new(env) { | ||
if let Some(pos) = elm.find('=') { | ||
out.insert(elm[..pos].to_string(), elm[pos + 1..].to_string().trim_matches('"').to_string()); | ||
} | ||
} | ||
|
||
out | ||
} | ||
|
||
/// Call an external command. | ||
/// In a pretty ugly way... | ||
fn call(cmd: &str, send: &str, locale: &str, env: &str, disown: bool) -> ModResponse { | ||
let mut resp = runtime::new_call_response(); | ||
resp.set_retcode(1); | ||
|
||
let args = cmd.split_whitespace().collect::<Vec<&str>>(); | ||
let mut l_loc = locale; | ||
if locale.is_empty() { | ||
l_loc = "C"; | ||
} | ||
|
||
let mut process = Command::new(args[0]); | ||
process.env_clear(); | ||
process.args(&args[1..]); | ||
|
||
// Set locale | ||
[("LC_ALL", l_loc), ("LANG", l_loc)].iter().for_each(|(n, v)| { | ||
process.env(n, v); | ||
}); | ||
|
||
// Set env | ||
getenv(env).into_iter().for_each(|(vr, vl)| { | ||
process.env(vr, vl); | ||
}); | ||
|
||
if disown { | ||
match process.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null()).spawn() { | ||
Ok(_) => { | ||
resp.set_retcode(0); | ||
resp.set_message(&format!("'{}' is running in background", cmd)); | ||
} | ||
Err(err) => resp.set_message(&err.to_string()), | ||
} | ||
return resp; | ||
} | ||
|
||
match process.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn() { | ||
Ok(mut p) => { | ||
if !send.is_empty() { | ||
if let Some(mut stdin) = p.stdin.take() { | ||
if let Err(err) = stdin.write_all(send.as_bytes()) { | ||
resp.set_message(&err.to_string()); | ||
return resp; | ||
} | ||
} | ||
} | ||
|
||
// XXX: In the moment this is blocking. If a command blocks, | ||
// then the whole thing will wait until forever. A better approach | ||
// would be to take stdout and then read it in a reader, while maintaining | ||
// a timeout and then kill the child. | ||
match p.wait_with_output() { | ||
Ok(out) => { | ||
if out.status.success() { | ||
match str::from_utf8(&out.stdout) { | ||
Ok(stdout) => { | ||
if let Err(err) = resp.set_data(json!({"stdout": stdout})) { | ||
resp.set_message(&err.to_string()); | ||
} else { | ||
resp.set_retcode(0); | ||
resp.set_message(&format!("\"{}\" finished", cmd)); | ||
} | ||
resp | ||
} | ||
Err(err) => { | ||
resp.set_message(&format!("Error getting output: {:?}", err)); | ||
resp | ||
} | ||
} | ||
} else { | ||
match str::from_utf8(&out.stderr) { | ||
Ok(stderr) => { | ||
let mut r = runtime::new_call_response(); | ||
r.set_retcode(out.status.code().unwrap_or(1)); | ||
r.set_message(stderr); | ||
} | ||
Err(err) => { | ||
resp.set_message(&err.to_string()); | ||
} | ||
} | ||
resp | ||
} | ||
} | ||
Err(err) => { | ||
resp.set_message(&err.to_string()); | ||
resp | ||
} | ||
} | ||
} | ||
Err(err) => { | ||
resp.set_message(&format!("Error running '{}': {}", cmd, err)); | ||
resp | ||
} | ||
} | ||
} | ||
|
||
fn run_mod(rt: &ModRequest) -> ModResponse { | ||
let mut res = ModResponse::new(); | ||
|
||
let cmd = get_arg(rt, "cmd"); | ||
if cmd.is_empty() { | ||
res.set_retcode(1); | ||
res.set_message("Missing command"); | ||
return res; | ||
} | ||
|
||
call(&cmd, &get_arg(rt, "send"), &get_arg(rt, "locale"), &get_arg(rt, "env"), get_opt(rt, "disown")) | ||
} | ||
|
||
fn main() { | ||
let mod_doc = init_mod_doc!(ModInterface); | ||
if mod_doc.print_help() { | ||
return; | ||
} | ||
|
||
match get_call_args() { | ||
Ok(rt) => match send_call_response(&run_mod(&rt)) { | ||
Ok(_) => {} | ||
Err(err) => println!("Runtime error: {}", err), | ||
}, | ||
Err(err) => { | ||
println!("Arguments error: {}", err) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
name: "sys.run" | ||
version: "0.0.1" | ||
author: "Bo Maryniuk" | ||
description: | | ||
Plugin to run just a raw commands and return generically | ||
structured output from them. | ||
# Options, flags, switches | ||
options: | ||
- name: disown | ||
description: "Leave command running in the background" | ||
|
||
# Keyword arguments | ||
arguments: | ||
- name: cmd | ||
type: string | ||
required: true | ||
description: "Full command to run" | ||
|
||
- name: send | ||
type: string | ||
required: false | ||
description: "Send uninterpolated data to the program input (STDIN)" | ||
|
||
- name: env | ||
type: string | ||
required: false | ||
description: "Modify the environment for the target running command" | ||
|
||
- name: locale | ||
type: string | ||
required: false | ||
description: "Set the locale for this command (default: LC_CTYPE=C)" | ||
|
||
examples: | ||
- description: "Module usage example" | ||
code: | | ||
"arguments": { | ||
"cmd": "spotify --headless", | ||
"env": "PATH=$PATH:/opt/spotify/bin" | ||
} | ||
# Description of additional data format | ||
returns: | ||
description: | | ||
Returns just a regular text of the command STDOUT. | ||
NOTE: if "disown" flag is specified, no data is returned. | ||
example: | | ||
{ | ||
"stdout": "..." | ||
}, |