zeek/scripts/base/utils/exec.bro
2013-03-13 14:36:27 -04:00

207 lines
No EOL
6.1 KiB
Text

##! A module for executing external command line programs.
##! This requires code that is still in topic branches and
##! definitely won't currently work on any released version of Bro.
@load base/frameworks/input
module Exec;
export {
type Command: record {
## The command line to execute.
## Use care to avoid injection attacks!
cmd: string;
## Provide standard in to the program as a
## string.
stdin: string &default="";
## If additional files are required to be read
## in as part of the output of the command they
## can be defined here.
read_files: set[string] &optional;
};
type Result: record {
## Exit code from the program.
exit_code: count &default=0;
## Each line of standard out.
stdout: vector of string &optional;
## Each line of standard error.
stderr: vector of string &optional;
## If additional files were requested to be read in
## the content of the files will be available here.
files: table[string] of string_vec &optional;
};
## Function for running command line programs and getting
## output. This is an asynchronous function which is meant
## to be run with the `when` statement.
##
## cmd: The command to run. Use care to avoid injection attacks!
##
## returns: A record representing the full results from the
## external program execution.
global run: function(cmd: Command): Result;
}
redef record Command += {
# The prefix name for tracking temp files.
prefix_name: string &optional;
};
global results: table[string] of Result = table();
global finished_commands: set[string];
global tmp_files: set[string] = set();
type OneLine: record { line: string; };
event Exec::stdout_line(description: Input::EventDescription, tpe: Input::Event, s: string)
{
local name = sub(description$name, /_[^_]*$/, "");
local result = results[name];
if ( ! results[name]?$stdout )
result$stdout = vector(s);
else
result$stdout[|result$stdout|] = s;
}
event Exec::stderr_line(description: Input::EventDescription, tpe: Input::Event, s: string)
{
local name = sub(description$name, /_[^_]*$/, "");
local result = results[name];
if ( ! results[name]?$stderr )
result$stderr = vector(s);
else
result$stderr[|result$stderr|] = s;
}
event Exec::file_line(description: Input::EventDescription, tpe: Input::Event, s: string)
{
local parts = split1(description$name, /_/);
local name = parts[1];
local track_file = parts[2];
local result = results[name];
if ( ! result?$files )
result$files = table();
if ( track_file !in result$files )
result$files[track_file] = vector(s);
else
result$files[track_file][|result$files[track_file]|] = s;
}
event Exec::cleanup_and_do_callback(name: string)
{
Input::remove(fmt("%s_stdout", name));
system(fmt("rm %s_stdout", name));
delete tmp_files[fmt("%s_stdout", name)];
Input::remove(fmt("%s_stderr", name));
system(fmt("rm %s_stderr", name));
delete tmp_files[fmt("%s_stderr", name)];
Input::remove(fmt("%s_done", name));
system(fmt("rm %s_done", name));
delete tmp_files[fmt("%s_done", name)];
# Indicate to the "when" async watcher that this command is done.
add finished_commands[name];
}
event Exec::run_done(description: Input::EventDescription, tpe: Input::Event, s: string)
{
local name = sub(description$name, /_[^_]*$/, "");
if ( /^exit_code:/ in s )
results[name]$exit_code = to_count(split1(s, /:/)[2]);
else if ( s == "done" )
# Wait one second to allow all threads to read all of their input
# and forward it.
schedule 1sec { Exec::cleanup_and_do_callback(name) };
}
event Exec::start_watching_files(cmd: Command)
{
Input::add_event([$source=fmt("%s_done", cmd$prefix_name),
$name=fmt("%s_done", cmd$prefix_name),
$reader=Input::READER_RAW,
$mode=Input::STREAM,
$want_record=F,
$fields=OneLine,
$ev=Exec::run_done]);
Input::add_event([$source=fmt("%s_stdout", cmd$prefix_name),
$name=fmt("%s_stdout", cmd$prefix_name),
$reader=Input::READER_RAW,
$mode=Input::STREAM,
$want_record=F,
$fields=OneLine,
$ev=Exec::stdout_line]);
Input::add_event([$source=fmt("%s_stderr", cmd$prefix_name),
$name=fmt("%s_stderr", cmd$prefix_name),
$reader=Input::READER_RAW,
$mode=Input::STREAM,
$want_record=F,
$fields=OneLine,
$ev=Exec::stderr_line]);
if ( cmd?$read_files )
{
for ( read_file in cmd$read_files )
{
Input::add_event([$source=fmt("%s", read_file),
$name=fmt("%s_%s", cmd$prefix_name, read_file),
$reader=Input::READER_RAW,
$mode=Input::STREAM,
$want_record=F,
$fields=OneLine,
$ev=Exec::file_line]);
}
}
}
function run(cmd: Command): Result
{
cmd$prefix_name = "/tmp/bro-exec-" + unique_id("");
system(fmt("touch %s_done %s_stdout %s_stderr 2>/dev/null", cmd$prefix_name, cmd$prefix_name, cmd$prefix_name));
add tmp_files[fmt("%s_done", cmd$prefix_name)];
add tmp_files[fmt("%s_stdout", cmd$prefix_name)];
add tmp_files[fmt("%s_stderr", cmd$prefix_name)];
if ( cmd?$read_files )
{
for ( read_file in cmd$read_files )
{
system(fmt("touch %s 2>/dev/null", read_file));
add tmp_files[read_file];
}
}
piped_exec(fmt("%s 2>> %s_stderr 1>> %s_stdout; echo \"exit_code:${?}\" >> %s_done; echo \"done\" >> %s_done",
cmd$cmd, cmd$prefix_name, cmd$prefix_name, cmd$prefix_name, cmd$prefix_name),
cmd$stdin);
results[cmd$prefix_name] = [];
schedule 1msec { Exec::start_watching_files(cmd) };
return when ( cmd$prefix_name in finished_commands )
{
delete finished_commands[cmd$prefix_name];
local result = results[cmd$prefix_name];
delete results[cmd$prefix_name];
return result;
}
}
event bro_done()
{
# We are punting here and just deleting any files that haven't been processed yet.
for ( fname in tmp_files )
{
system(fmt("rm \"%s\"", str_shell_escape(fname)));
}
}