diff --git a/falco.yaml b/falco.yaml index 64220494..a2bef48b 100644 --- a/falco.yaml +++ b/falco.yaml @@ -31,6 +31,10 @@ log_level: info # "info", "debug". priority: debug +# Whether or not output to any of the output channels below is +# buffered. Defaults to true +buffered_outputs: true + # A throttling mechanism implemented as a token bucket limits the # rate of falco notifications. This throttling is controlled by the following configuration # options: diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index 6d266311..4df8fe79 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -22,7 +22,8 @@ along with falco. If not, see . using namespace std; falco_configuration::falco_configuration() - : m_config(NULL) + : m_buffered_outputs(true), + m_config(NULL) { } @@ -142,6 +143,8 @@ void falco_configuration::init(string conf_filename, list &cmdline_optio } m_min_priority = (falco_common::priority_type) (it - falco_common::priority_names.begin()); + m_buffered_outputs = m_config->get_scalar("buffered_outputs", true); + falco_logger::log_stderr = m_config->get_scalar("log_stderr", false); falco_logger::log_syslog = m_config->get_scalar("log_syslog", true); } diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index 37f0d073..a6ba8fdb 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -172,6 +172,8 @@ class falco_configuration uint32_t m_notifications_max_burst; falco_common::priority_type m_min_priority; + + bool m_buffered_outputs; private: void init_cmdline_options(std::list &cmdline_options); diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index 75f01e5a..26c8f44b 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -107,6 +107,10 @@ static void usage() " Can not be specified with -t.\n" " -t Only run those rules with a tag=. Can be specified multiple times.\n" " Can not be specified with -T/-D.\n" + " -U,--unbuffered Turn off output buffering to configured outputs. This causes every\n" + " single line emitted by falco to be flushed, which generates higher CPU\n" + " usage but is useful when piping those outputs into another process\n" + " or into a script.\n" " -v Verbose output.\n" " --version Print version number.\n" "\n" @@ -256,6 +260,8 @@ int falco_init(int argc, char **argv) int file_limit = 0; unsigned long event_limit = 0L; bool compress = false; + bool buffered_outputs = true; + bool buffered_cmdline = false; // Used for stats uint64_t num_evts; @@ -272,6 +278,7 @@ int falco_init(int argc, char **argv) {"option", required_argument, 0, 'o'}, {"print", required_argument, 0, 'p' }, {"pidfile", required_argument, 0, 'P' }, + {"unbuffered", no_argument, 0, 'U' }, {"version", no_argument, 0, 0 }, {"writefile", required_argument, 0, 'w' }, @@ -290,7 +297,7 @@ int falco_init(int argc, char **argv) // Parse the args // while((op = getopt_long(argc, argv, - "hc:AdD:e:k:K:Ll:m:M:o:P:p:r:s:T:t:vw:", + "hc:AdD:e:k:K:Ll:m:M:o:P:p:r:s:T:t:Uvw:", long_options, &long_index)) != -1) { switch(op) @@ -378,6 +385,10 @@ int falco_init(int argc, char **argv) case 't': enabled_rule_tags.insert(optarg); break; + case 'U': + buffered_outputs = false; + buffered_cmdline = true; + break; case 'v': verbose = true; break; @@ -463,6 +474,11 @@ int falco_init(int argc, char **argv) engine->set_min_priority(config.m_min_priority); + if(buffered_cmdline) + { + config.m_buffered_outputs = buffered_outputs; + } + for (auto filename : config.m_rules_filenames) { engine->load_rules_file(filename, verbose, all_events); @@ -503,7 +519,9 @@ int falco_init(int argc, char **argv) engine->enable_rule_by_tag(enabled_rule_tags, true); } - outputs->init(config.m_json_output, config.m_notifications_rate, config.m_notifications_max_burst); + outputs->init(config.m_json_output, + config.m_notifications_rate, config.m_notifications_max_burst, + config.m_buffered_outputs); if(!all_events) { diff --git a/userspace/falco/falco_outputs.cpp b/userspace/falco/falco_outputs.cpp index 49cfcdf1..473a8c27 100644 --- a/userspace/falco/falco_outputs.cpp +++ b/userspace/falco/falco_outputs.cpp @@ -27,7 +27,8 @@ along with falco. If not, see . using namespace std; falco_outputs::falco_outputs() - : m_initialized(false) + : m_initialized(false), + m_buffered(true) { } @@ -51,7 +52,7 @@ falco_outputs::~falco_outputs() } } -void falco_outputs::init(bool json_output, uint32_t rate, uint32_t max_burst) +void falco_outputs::init(bool json_output, uint32_t rate, uint32_t max_burst, bool buffered) { // The engine must have been given an inspector by now. if(! m_inspector) @@ -70,12 +71,14 @@ void falco_outputs::init(bool json_output, uint32_t rate, uint32_t max_burst) m_notifications_tb.init(rate, max_burst); + m_buffered = buffered; + m_initialized = true; } void falco_outputs::add_output(output_config oc) { - uint8_t nargs = 1; + uint8_t nargs = 2; lua_getglobal(m_ls, m_lua_add_output.c_str()); if(!lua_isfunction(m_ls, -1)) @@ -83,11 +86,12 @@ void falco_outputs::add_output(output_config oc) throw falco_exception("No function " + m_lua_add_output + " found. "); } lua_pushstring(m_ls, oc.name.c_str()); + lua_pushnumber(m_ls, (m_buffered ? 1 : 0)); // If we have options, build up a lua table containing them if (oc.options.size()) { - nargs = 2; + nargs = 3; lua_createtable(m_ls, 0, oc.options.size()); for (auto it = oc.options.cbegin(); it != oc.options.cend(); ++it) diff --git a/userspace/falco/falco_outputs.h b/userspace/falco/falco_outputs.h index 7f3f1281..236a8caf 100644 --- a/userspace/falco/falco_outputs.h +++ b/userspace/falco/falco_outputs.h @@ -41,7 +41,7 @@ public: std::map options; }; - void init(bool json_output, uint32_t rate, uint32_t max_burst); + void init(bool json_output, uint32_t rate, uint32_t max_burst, bool buffered); void add_output(output_config oc); @@ -57,6 +57,8 @@ private: // Rate limits notifications token_bucket m_notifications_tb; + bool m_buffered; + std::string m_lua_add_output = "add_output"; std::string m_lua_output_event = "output_event"; std::string m_lua_output_cleanup = "output_cleanup"; diff --git a/userspace/falco/lua/output.lua b/userspace/falco/lua/output.lua index b0f5145a..35b4eadc 100644 --- a/userspace/falco/lua/output.lua +++ b/userspace/falco/lua/output.lua @@ -20,10 +20,17 @@ local mod = {} local outputs = {} -function mod.stdout(priority, priority_num, msg) +function mod.stdout(priority, priority_num, buffered, msg) + if buffered == 0 then + io.stdout:setvbuf 'no' + end print (msg) end +function mod.stdout_cleanup() + io.stdout:flush() +end + function mod.file_validate(options) if (not type(options.filename) == 'string') then error("File output needs to be configured with a valid filename") @@ -37,10 +44,13 @@ function mod.file_validate(options) end -function mod.file(priority, priority_num, msg, options) +function mod.file(priority, priority_num, buffered, msg, options) if options.keep_alive == "true" then if file == nil then file = io.open(options.filename, "a+") + if buffered == 0 then + file:setvbuf 'no' + end end else file = io.open(options.filename, "a+") @@ -55,11 +65,22 @@ function mod.file(priority, priority_num, msg, options) end end -function mod.syslog(priority, priority_num, msg, options) +function mod.file_cleanup() + if file ~= nil then + file:flush() + file:close() + file = nil + end +end + +function mod.syslog(priority, priority_num, buffered, msg, options) falco.syslog(priority_num, msg) end -function mod.program(priority, priority_num, msg, options) +function mod.syslog_cleanup() +end + +function mod.program(priority, priority_num, buffered, msg, options) -- XXX Ideally we'd check that the program ran -- successfully. However, the luajit we're using returns true even -- when the shell can't run the program. @@ -68,6 +89,9 @@ function mod.program(priority, priority_num, msg, options) if options.keep_alive == "true" then if file == nil then file = io.popen(options.program, "w") + if buffered == 0 then + file:setvbuf 'no' + end end else file = io.popen(options.program, "w") @@ -82,6 +106,14 @@ function mod.program(priority, priority_num, msg, options) end end +function mod.program_cleanup() + if file ~= nil then + file:flush() + file:close() + file = nil + end +end + function output_event(event, rule, priority, priority_num, format) -- If format starts with a *, remove it, as we're adding our own -- prefix here. @@ -94,15 +126,18 @@ function output_event(event, rule, priority, priority_num, format) msg = formats.format_event(event, rule, priority, format) for index,o in ipairs(outputs) do - o.output(priority, priority_num, msg, o.config) + o.output(priority, priority_num, o.buffered, msg, o.config) end end function output_cleanup() formats.free_formatters() + for index,o in ipairs(outputs) do + o.cleanup() + end end -function add_output(output_name, config) +function add_output(output_name, buffered, config) if not (type(mod[output_name]) == 'function') then error("rule_loader.add_output(): invalid output_name: "..output_name) end @@ -113,7 +148,9 @@ function add_output(output_name, config) mod[output_name.."_validate"](config) end - table.insert(outputs, {output = mod[output_name], config=config}) + table.insert(outputs, {output = mod[output_name], + cleanup = mod[output_name.."_cleanup"], + buffered=buffered, config=config}) end return mod