Cleanup/stabilize file_events-related APIs

This commit is contained in:
Teddy Reed 2016-02-10 20:08:34 -08:00
parent 90d2ac4c76
commit 4031e299bb
20 changed files with 681 additions and 393 deletions

View File

@ -216,6 +216,36 @@ class Config : private boost::noncopyable {
static const std::shared_ptr<ConfigParserPlugin> getParser(
const std::string& parser);
protected:
/**
* @brief Call the genConfig method of the config retriever plugin.
*
* This may perform a resource load such as TCP request or filesystem read.
*/
Status load();
/// A step method for Config::update.
Status updateSource(const std::string& source, const std::string& json);
/**
* @brief Generate pack content from a resource handled by the Plugin.
*
* Configuration content may set pack values to JSON strings instead of an
* embedded dictionary representing the pack content. When a string is
* encountered the config assumes this is a 'resource' handled by the Plugin.
*
* The value, or target, is sent to the ConfigPlugin via a registry request.
* The plugin response is assumed, and used, as the pack content.
*
* @param name A pack name provided and handled by the ConfigPlugin.
* @param source The config content source identifier.
* @param target A resource (path, URL, etc) handled by the ConfigPlugin.
* @return status On success the response will be JSON parsed.
*/
Status genPack(const std::string& name,
const std::string& source,
const std::string& target);
/**
* @brief Apply each ConfigParser to an input property tree.
*
@ -231,20 +261,9 @@ class Config : private boost::noncopyable {
* @param tree The input configuration tree.
* @param pack True if the tree was built from pack data, otherwise false.
*/
static void applyParsers(const std::string& source,
const boost::property_tree::ptree& tree,
bool pack = false);
protected:
/**
* @brief Call the genConfig method of the config retriever plugin.
*
* This may perform a resource load such as TCP request or filesystem read.
*/
Status load();
/// A step method for Config::update.
Status updateSource(const std::string& name, const std::string& json);
void applyParsers(const std::string& source,
const boost::property_tree::ptree& tree,
bool pack = false);
/**
* @brief When config sources are updated the config will 'purge'.
@ -257,6 +276,11 @@ class Config : private boost::noncopyable {
*/
void purge();
/**
* @brief Reset the configuration state, reserved for testing only.
*/
void reset();
protected:
/// Schedule of packs and their queries.
std::shared_ptr<Schedule> schedule_;
@ -285,21 +309,14 @@ class Config : private boost::noncopyable {
friend class Initializer;
private:
FRIEND_TEST(ConfigTests, test_plugin_reconfigure);
FRIEND_TEST(ConfigTests, test_parse);
FRIEND_TEST(ConfigTests, test_remove);
FRIEND_TEST(ConfigTests, test_get_scheduled_queries);
FRIEND_TEST(ConfigTests, test_get_parser);
FRIEND_TEST(ConfigTests, test_add_remove_pack);
FRIEND_TEST(ConfigTests, test_update_clear);
FRIEND_TEST(ConfigTests, test_pack_file_paths);
FRIEND_TEST(ConfigTests, test_noninline_pack);
friend class ConfigTests;
friend class FilePathsConfigParserPluginTests;
friend class FileEventsTableTests;
FRIEND_TEST(OptionsConfigParserPluginTests, test_get_option);
FRIEND_TEST(PacksTests, test_discovery_cache);
FRIEND_TEST(SchedulerTests, test_monitor);
FRIEND_TEST(SchedulerTests, test_config_results_purge);
FRIEND_TEST(EventsTests, test_event_subscriber_configure);
};
/**

View File

@ -106,8 +106,8 @@ enum EventSubscriberState {
};
/// Use a single placeholder for the EventContextRef passed to EventCallback.
using EventCallback = std::function<
Status(const EventContextRef&, const SubscriptionContextRef&)>;
using EventCallback = std::function<Status(const EventContextRef&,
const SubscriptionContextRef&)>;
/// An EventPublisher must track every subscription added.
using SubscriptionVector = std::vector<SubscriptionRef>;
@ -171,6 +171,9 @@ struct Subscription : private boost::noncopyable {
subscription->callback = ec;
return subscription;
}
public:
Subscription() = delete;
};
class EventPublisherPlugin : public Plugin {
@ -236,10 +239,8 @@ class EventPublisherPlugin : public Plugin {
return Status(0);
}
/// Remove all subscriptions.
virtual void removeSubscriptions() {
SubscriptionVector().swap(subscriptions_);
}
/// Remove all subscriptions from a named subscriber.
virtual void removeSubscriptions(const std::string& subscriber);
public:
/// Overriding the EventPublisher constructor is not recommended.
@ -273,7 +274,7 @@ class EventPublisherPlugin : public Plugin {
void hasStarted(bool started) { started_ = started; }
/// Get the number of publisher restarts.
size_t restartCount() { return restart_count_; }
size_t restartCount() const { return restart_count_; }
public:
explicit EventPublisherPlugin(EventPublisherPlugin const&) = delete;
@ -320,11 +321,20 @@ class EventPublisherPlugin : public Plugin {
friend class EventFactory;
private:
FRIEND_TEST(EventsTests, test_event_pub);
FRIEND_TEST(EventsTests, test_event_publisher);
FRIEND_TEST(EventsTests, test_fire_event);
};
class EventSubscriberPlugin : public Plugin {
public:
/**
* @brief Add Subscription%s to the EventPublisher this module will act on.
*
* When the EventSubscriber%'s `init` method is called you are assured the
* EventPublisher has `setUp` and is ready to subscription for events.
*/
virtual Status init() { return Status(0); }
protected:
/**
* @brief Store parsed event data from an EventCallback in a backing store.
@ -482,11 +492,24 @@ class EventSubscriberPlugin : public Plugin {
* publishers. The namespace is a combination of the publisher and subscriber
* registry plugin names.
*/
virtual EventPublisherID dbNamespace() const = 0;
/// See getType for lookup rational.
virtual EventPublisherID dbNamespace() const {
return getType() + '.' + getName();
}
/// Disable event expiration for this subscriber.
void doNotExpire() { expire_events_ = false; }
/// Trampoline into the EventFactory and lookup the name of the publisher.
virtual EventPublisherID& getType() const = 0;
/// Get a handle to the EventPublisher.
EventPublisherRef getPublisher() const;
/// Remove all subscriptions from this subscriber.
void removeSubscriptions();
protected:
/// A helper value counting the number of fired events tracked by publishers.
EventContextID event_count_{0};
@ -494,7 +517,7 @@ class EventSubscriberPlugin : public Plugin {
size_t subscription_count_{0};
private:
Status setUp() { return Status(0, "Setup never used"); }
Status setUp() override { return Status(0, "Setup never used"); }
private:
/// Do not respond to periodic/scheduled/triggered event expiration requests.
@ -799,7 +822,7 @@ class EventPublisher : public EventPublisherPlugin {
* @param ec The event that was fired.
*/
void fireCallback(const SubscriptionRef& sub,
const EventContextRef& ec) const {
const EventContextRef& ec) const override {
auto pub_sc = getSubscriptionContext(sub->context);
auto pub_ec = getEventContext(ec);
if (shouldFire(pub_sc, pub_ec) && sub->callback != nullptr) {
@ -822,8 +845,8 @@ class EventPublisher : public EventPublisherPlugin {
}
private:
FRIEND_TEST(EventsTests, test_event_sub_subscribe);
FRIEND_TEST(EventsTests, test_event_sub_context);
FRIEND_TEST(EventsTests, test_event_subscriber_subscribe);
FRIEND_TEST(EventsTests, test_event_subscriber_context);
FRIEND_TEST(EventsTests, test_fire_event);
};
@ -847,14 +870,6 @@ class EventSubscriber : public EventSubscriberPlugin {
using ECRef = typename PUB::ECRef;
public:
/**
* @brief Add Subscription%s to the EventPublisher this module will act on.
*
* When the EventSubscriber%'s `init` method is called you are assured the
* EventPublisher has `setUp` and is ready to subscription for events.
*/
virtual Status init() { return Status(0); }
/**
* @brief The registry plugin name for the subscriber's publisher.
*
@ -862,7 +877,7 @@ class EventSubscriber : public EventSubscriberPlugin {
* plugin name assigned to publishers. The corresponding publisher name is
* interpreted as the subscriber's event 'type'.
*/
virtual EventPublisherID& getType() const {
virtual EventPublisherID& getType() const override {
static EventPublisherID type = EventFactory::getType<PUB>();
return type;
};
@ -901,16 +916,6 @@ class EventSubscriber : public EventSubscriberPlugin {
}
}
/// See getType for lookup rational.
virtual EventPublisherID dbNamespace() const {
return getType() + '.' + getName();
}
/// Get a handle to the EventPublisher.
EventPublisherRef getPublisher() {
return EventFactory::getEventPublisher(getType());
}
public:
/**
* @brief Request the subscriber's initialization state.

View File

@ -456,6 +456,19 @@ class RegistryHelper : public RegistryHelperCore {
return ditems;
}
protected:
/**
* @brief Add an existing plugin to this registry, used for testing only.
*
* @param item A PluginType-cased registry item.
* @param item_name An identifier for this registry plugin.
* @return A success/failure status.
*/
Status add(const std::shared_ptr<RegistryType>& item) {
items_[item->getName()] = item;
return RegistryHelperCore::add(item->getName(), true);
}
public:
RegistryHelper(RegistryHelper const&) = delete;
void operator=(RegistryHelper const&) = delete;
@ -463,6 +476,9 @@ class RegistryHelper : public RegistryHelperCore {
private:
AddExternalCallback add_;
RemoveExternalCallback remove_;
private:
FRIEND_TEST(EventsTests, test_event_subscriber_configure);
};
/// Helper definition for a shared pointer to a Plugin.
@ -782,5 +798,5 @@ class RegistryFactory : private boost::noncopyable {
* The actual plugins must add themselves to a registry type and should
* implement the Plugin and RegistryType interfaces.
*/
class Registry : public RegistryFactory {};
using Registry = RegistryFactory;
}

View File

@ -341,14 +341,15 @@ inline void stripConfigComments(std::string& json) {
json = sink;
}
Status Config::updateSource(const std::string& name, const std::string& json) {
Status Config::updateSource(const std::string& source,
const std::string& json) {
// Compute a 'synthesized' hash using the content before it is parsed.
hashSource(name, json);
hashSource(source, json);
// Remove all packs from this source.
schedule_->removeAll(name);
schedule_->removeAll(source);
// Remove all files from this source.
removeFiles(name);
removeFiles(source);
// load the config (source.second) into a pt::ptree
pt::ptree tree;
@ -367,7 +368,7 @@ Status Config::updateSource(const std::string& name, const std::string& json) {
auto& schedule = tree.get_child("schedule");
pt::ptree main_pack;
main_pack.add_child("queries", schedule);
addPack("main", name, main_pack);
addPack("main", source, main_pack);
}
if (tree.count("scheduledQueries") > 0 && !Registry::external()) {
@ -382,7 +383,7 @@ Status Config::updateSource(const std::string& name, const std::string& json) {
}
pt::ptree legacy_pack;
legacy_pack.add_child("queries", queries);
addPack("legacy_main", name, legacy_pack);
addPack("legacy_main", source, legacy_pack);
}
// extract the "packs" key into additional pack objects
@ -392,36 +393,43 @@ Status Config::updateSource(const std::string& name, const std::string& json) {
auto value = packs.get<std::string>(pack.first, "");
if (value.empty()) {
// The pack is a JSON object, treat the content as pack data.
addPack(pack.first, name, pack.second);
addPack(pack.first, source, pack.second);
} else {
// If the pack value is a string (and not a JSON object) then it is a
// resource to be handled by the config plugin.
PluginResponse response;
PluginRequest request = {
{"action", "genPack"}, {"name", pack.first}, {"value", value}};
Registry::call("config", request, response);
if (response.size() == 0 || response[0].count(pack.first) == 0) {
continue;
}
try {
pt::ptree pack_tree;
std::stringstream pack_stream;
pack_stream << response[0][pack.first];
pt::read_json(pack_stream, pack_tree);
addPack(pack.first, name, pack_tree);
} catch (const pt::json_parser::json_parser_error& e) {
LOG(WARNING) << "Error parsing the pack JSON: " << pack.first;
}
genPack(pack.first, source, value);
}
}
}
applyParsers(name, tree, false);
applyParsers(source, tree, false);
return Status(0, "OK");
}
Status Config::genPack(const std::string& name,
const std::string& source,
const std::string& target) {
// If the pack value is a string (and not a JSON object) then it is a
// resource to be handled by the config plugin.
PluginResponse response;
PluginRequest request = {
{"action", "genPack"}, {"name", name}, {"value", target}};
Registry::call("config", request, response);
if (response.size() == 0 || response[0].count(name) == 0) {
return Status(1, "Invalid plugin response");
}
try {
pt::ptree pack_tree;
std::stringstream pack_stream;
pack_stream << response[0][name];
pt::read_json(pack_stream, pack_tree);
addPack(name, source, pack_tree);
} catch (const pt::json_parser::json_parser_error& e) {
LOG(WARNING) << "Error parsing the pack JSON: " << name;
}
return Status(0);
}
void Config::applyParsers(const std::string& source,
const pt::ptree& tree,
bool pack) {
@ -560,6 +568,16 @@ void Config::purge() {
}
}
void Config::reset() {
schedule_ = std::make_shared<Schedule>();
std::map<std::string, QueryPerformance>().swap(performance_);
std::map<std::string, FileCategories>().swap(files_);
std::map<std::string, std::string>().swap(hash_);
valid_ = false;
loaded_ = false;
start_time_ = 0;
}
void Config::recordQueryPerformance(const std::string& name,
size_t delay,
size_t size,

View File

@ -35,10 +35,18 @@ extern void saveScheduleBlacklist(
extern void stripConfigComments(std::string& json);
class ConfigTests : public testing::Test {
public:
ConfigTests() { Config::getInstance().reset(); }
protected:
void SetUp() { createMockFileStructure(); }
void TearDown() { tearDownMockFileStructure(); }
protected:
Status load() { return Config::getInstance().load(); }
void setLoaded() { Config::getInstance().loaded_ = true; }
Config& get() { return Config::getInstance(); }
};
class TestConfigPlugin : public ConfigPlugin {
@ -66,6 +74,7 @@ class TestConfigPlugin : public ConfigPlugin {
return Status(0, "OK");
}
public:
int genConfigCount{0};
int genPackCount{0};
};
@ -83,37 +92,9 @@ TEST_F(ConfigTests, test_plugin) {
EXPECT_EQ(status.toString(), "OK");
}
TEST_F(ConfigTests, test_bad_config_update) {
TEST_F(ConfigTests, test_invalid_content) {
std::string bad_json = "{\"options\": {},}";
ASSERT_NO_THROW(Config::getInstance().update({{"bad_source", bad_json}}));
}
class PlaceboConfigParserPlugin : public ConfigParserPlugin {
public:
std::vector<std::string> keys() const override { return {}; }
Status update(const std::string&, const ParserConfig&) override {
return Status(0);
}
/// Make sure configure is called.
void configure() override { configures++; }
size_t configures{0};
};
TEST_F(ConfigTests, test_plugin_reconfigure) {
// Add a configuration plugin (could be any plugin) that will react to
// config updates.
Registry::add<PlaceboConfigParserPlugin>("config_parser", "placebo");
// Create a config that has been loaded.
Config c;
c.loaded_ = true;
c.update({{"data", "{}"}});
// Get the placebo.
auto placebo = std::static_pointer_cast<PlaceboConfigParserPlugin>(
Registry::get("config_parser", "placebo"));
EXPECT_EQ(placebo->configures, 1U);
ASSERT_NO_THROW(get().update({{"bad_source", bad_json}}));
}
TEST_F(ConfigTests, test_strip_comments) {
@ -127,7 +108,132 @@ TEST_F(ConfigTests, test_strip_comments) {
EXPECT_EQ(actual, expected);
// Make sure the config update source logic applies the stripping.
EXPECT_TRUE(Config::getInstance().update({{"data", json_comments}}));
EXPECT_TRUE(get().update({{"data", json_comments}}));
}
TEST_F(ConfigTests, test_schedule_blacklist) {
auto current_time = getUnixTime();
std::map<std::string, size_t> blacklist;
saveScheduleBlacklist(blacklist);
restoreScheduleBlacklist(blacklist);
EXPECT_EQ(blacklist.size(), 0U);
// Create some entries.
blacklist["test_1"] = current_time * 2;
blacklist["test_2"] = current_time * 3;
saveScheduleBlacklist(blacklist);
blacklist.clear();
restoreScheduleBlacklist(blacklist);
ASSERT_EQ(blacklist.count("test_1"), 1U);
ASSERT_EQ(blacklist.count("test_2"), 1U);
EXPECT_EQ(blacklist.at("test_1"), current_time * 2);
EXPECT_EQ(blacklist.at("test_2"), current_time * 3);
// Now save an expired query.
blacklist["test_1"] = 1;
saveScheduleBlacklist(blacklist);
blacklist.clear();
// When restoring, the values below the current time will not be included.
restoreScheduleBlacklist(blacklist);
EXPECT_EQ(blacklist.size(), 1U);
}
TEST_F(ConfigTests, test_pack_noninline) {
Registry::add<TestConfigPlugin>("config", "test");
// Change the active config plugin.
EXPECT_TRUE(Registry::setActive("config", "test").ok());
// Get a specialized config/test plugin.
const auto& plugin = std::dynamic_pointer_cast<TestConfigPlugin>(
Registry::get("config", "test"));
this->load();
// Expect the test plugin to have recorded 1 pack.
// This value is incremented when its genPack method is called.
EXPECT_EQ(plugin->genPackCount, 1);
int total_packs = 0;
// Expect the config to have recorded a pack for the inline and non-inline.
get().packs(
[&total_packs](const std::shared_ptr<Pack>& pack) { total_packs++; });
EXPECT_EQ(total_packs, 2);
}
TEST_F(ConfigTests, test_pack_restrictions) {
auto tree = getExamplePacksConfig();
auto packs = tree.get_child("packs");
for (const auto& pack : packs) {
get().addPack(pack.first, "", pack.second);
}
std::map<std::string, bool> results = {
{"unrestricted_pack", true},
{"discovery_pack", false},
{"fake_version_pack", false},
// Although this is a valid discovery query, there is no SQL plugin in
// the core tests.
{"valid_discovery_pack", false},
{"restricted_pack", false},
};
get().packs(([&results](std::shared_ptr<Pack>& pack) {
if (results[pack->getName()]) {
EXPECT_TRUE(pack->shouldPackExecute());
} else {
EXPECT_FALSE(pack->shouldPackExecute());
}
}));
}
TEST_F(ConfigTests, test_pack_removal) {
size_t pack_count = 0;
get().packs(([&pack_count](std::shared_ptr<Pack>& pack) { pack_count++; }));
EXPECT_EQ(pack_count, 0U);
pack_count = 0;
get().addPack("unrestricted_pack", "", getUnrestrictedPack());
get().packs(([&pack_count](std::shared_ptr<Pack>& pack) { pack_count++; }));
EXPECT_EQ(pack_count, 1U);
pack_count = 0;
get().removePack("unrestricted_pack");
get().packs(([&pack_count](std::shared_ptr<Pack>& pack) { pack_count++; }));
EXPECT_EQ(pack_count, 0U);
}
TEST_F(ConfigTests, test_content_update) {
// Read config content manually.
std::string content;
readFile(kTestDataPath + "test_parse_items.conf", content);
// Create the output of a `genConfig`.
std::map<std::string, std::string> config_data;
config_data["awesome"] = content;
// Update, then clear, packs should have been cleared.
get().update(config_data);
size_t count = 0;
auto packCounter = [&count](std::shared_ptr<Pack>& pack) { count++; };
get().packs(packCounter);
EXPECT_GT(count, 0U);
// Now clear.
config_data["awesome"] = "";
get().update(config_data);
count = 0;
get().packs(packCounter);
EXPECT_EQ(count, 0U);
}
TEST_F(ConfigTests, test_get_scheduled_queries) {
std::vector<ScheduledQuery> queries;
get().addPack("unrestricted_pack", "", getUnrestrictedPack());
get().scheduledQueries(
([&queries](const std::string&, const ScheduledQuery& query) {
queries.push_back(query);
}));
EXPECT_EQ(queries.size(), getUnrestrictedPack().get_child("queries").size());
}
class TestConfigParserPlugin : public ConfigParserPlugin {
@ -161,107 +267,14 @@ class TestConfigParserPlugin : public ConfigParserPlugin {
// An intermediate boolean to check parser updates.
bool TestConfigParserPlugin::update_called = false;
TEST_F(ConfigTests, test_parse) {
Config c;
auto tree = getExamplePacksConfig();
auto packs = tree.get_child("packs");
for (const auto& pack : packs) {
c.addPack(pack.first, "", pack.second);
}
std::map<std::string, bool> results = {
{"unrestricted_pack", true},
{"discovery_pack", false},
{"fake_version_pack", false},
// Although this is a valid discovery query, there is no SQL plugin in
// the core tests.
{"valid_discovery_pack", false},
{"restricted_pack", false},
};
c.packs(([&results](std::shared_ptr<Pack>& pack) {
if (results[pack->getName()]) {
EXPECT_TRUE(pack->shouldPackExecute());
} else {
EXPECT_FALSE(pack->shouldPackExecute());
}
}));
}
TEST_F(ConfigTests, test_remove) {
Config c;
c.addPack("unrestricted_pack", "", getUnrestrictedPack());
c.removePack("unrestricted_pack");
c.packs(([](std::shared_ptr<Pack>& pack) {
EXPECT_NE("unrestricted_pack", pack->getName());
}));
}
TEST_F(ConfigTests, test_add_remove_pack) {
Config c;
size_t pack_count = 0;
c.packs(([&pack_count](std::shared_ptr<Pack>& pack) { pack_count++; }));
EXPECT_EQ(pack_count, 0U);
pack_count = 0;
c.addPack("unrestricted_pack", "", getUnrestrictedPack());
c.packs(([&pack_count](std::shared_ptr<Pack>& pack) { pack_count++; }));
EXPECT_EQ(pack_count, 1U);
pack_count = 0;
c.removePack("unrestricted_pack");
c.packs(([&pack_count](std::shared_ptr<Pack>& pack) { pack_count++; }));
EXPECT_EQ(pack_count, 0U);
}
TEST_F(ConfigTests, test_update_clear) {
// Read config content manually.
std::string content;
readFile(kTestDataPath + "test_parse_items.conf", content);
// Create the output of a `genConfig`.
std::map<std::string, std::string> config_data;
config_data["awesome"] = content;
// Update, then clear, packs should have been cleared.
Config c;
c.update(config_data);
size_t count = 0;
auto packCounter = [&count](std::shared_ptr<Pack>& pack) { count++; };
c.packs(packCounter);
EXPECT_GT(count, 0U);
// Now clear.
config_data["awesome"] = "";
c.update(config_data);
count = 0;
c.packs(packCounter);
EXPECT_EQ(count, 0U);
}
TEST_F(ConfigTests, test_get_scheduled_queries) {
Config c;
std::vector<ScheduledQuery> queries;
c.addPack("unrestricted_pack", "", getUnrestrictedPack());
c.scheduledQueries(
([&queries](const std::string&, const ScheduledQuery& query) {
queries.push_back(query);
}));
EXPECT_EQ(queries.size(), getUnrestrictedPack().get_child("queries").size());
}
TEST_F(ConfigTests, test_get_parser) {
Registry::add<TestConfigParserPlugin>("config_parser", "test");
EXPECT_TRUE(Registry::setActive("config_parser", "test").ok());
Config c;
auto s = c.update(getTestConfigMap());
auto s = get().update(getTestConfigMap());
EXPECT_TRUE(s.ok());
EXPECT_EQ(s.toString(), "OK");
auto plugin = Config::getInstance().getParser("test");
auto plugin = get().getParser("test");
EXPECT_TRUE(plugin != nullptr);
EXPECT_TRUE(plugin.get() != nullptr);
@ -273,73 +286,63 @@ TEST_F(ConfigTests, test_get_parser) {
EXPECT_EQ(data.count("dictionary"), 1U);
}
TEST_F(ConfigTests, test_pack_file_paths) {
Config c;
class PlaceboConfigParserPlugin : public ConfigParserPlugin {
public:
std::vector<std::string> keys() const override { return {}; }
Status update(const std::string&, const ParserConfig&) override {
return Status(0);
}
/// Make sure configure is called.
void configure() override { configures++; }
size_t configures{0};
};
TEST_F(ConfigTests, test_plugin_reconfigure) {
// Add a configuration plugin (could be any plugin) that will react to
// config updates.
Registry::add<PlaceboConfigParserPlugin>("config_parser", "placebo");
// Create a config that has been loaded.
setLoaded();
get().update({{"data", "{}"}});
// Get the placebo.
auto placebo = std::static_pointer_cast<PlaceboConfigParserPlugin>(
Registry::get("config_parser", "placebo"));
EXPECT_EQ(placebo->configures, 1U);
}
TEST_F(ConfigTests, test_pack_file_paths) {
size_t count = 0;
auto fileCounter =
[&count](const std::string& c, const std::vector<std::string>& files) {
count += files.size();
};
c.addPack("unrestricted_pack", "", getUnrestrictedPack());
Config::getInstance().files(fileCounter);
EXPECT_EQ(count, 7U);
get().addPack("unrestricted_pack", "", getUnrestrictedPack());
get().files(fileCounter);
EXPECT_EQ(count, 2U);
c.removePack("unrestricted_pack");
count = 0;
Config::getInstance().files(fileCounter);
EXPECT_EQ(count, 5U);
get().removePack("unrestricted_pack");
get().files(fileCounter);
EXPECT_EQ(count, 0U);
c.addPack("restricted_pack", "", getRestrictedPack());
count = 0;
Config::getInstance().files(fileCounter);
EXPECT_EQ(count, 5U);
}
get().addPack("restricted_pack", "", getRestrictedPack());
get().files(fileCounter);
EXPECT_EQ(count, 0U);
TEST_F(ConfigTests, test_noninline_pack) {
Registry::add<TestConfigPlugin>("config", "test");
// Test a more-generic update.
count = 0;
get().update({{"data", "{\"file_paths\": {\"new\": [\"/new\"]}}"}});
get().files(fileCounter);
EXPECT_EQ(count, 1U);
// Change the active config plugin.
EXPECT_TRUE(Registry::setActive("config", "test").ok());
const auto& plugin = std::dynamic_pointer_cast<TestConfigPlugin>(
Registry::get("config", "test"));
Config c;
c.load();
EXPECT_EQ(plugin->genPackCount, 1);
int total_packs = 0;
c.packs([&total_packs](const std::shared_ptr<Pack>& pack) { total_packs++; });
EXPECT_EQ(total_packs, 2);
}
TEST_F(ConfigTests, test_blacklist) {
auto current_time = getUnixTime();
std::map<std::string, size_t> blacklist;
saveScheduleBlacklist(blacklist);
restoreScheduleBlacklist(blacklist);
EXPECT_EQ(blacklist.size(), 0U);
// Create some entries.
blacklist["test_1"] = current_time * 2;
blacklist["test_2"] = current_time * 3;
saveScheduleBlacklist(blacklist);
blacklist.clear();
restoreScheduleBlacklist(blacklist);
ASSERT_EQ(blacklist.count("test_1"), 1U);
ASSERT_EQ(blacklist.count("test_2"), 1U);
EXPECT_EQ(blacklist.at("test_1"), current_time * 2);
EXPECT_EQ(blacklist.at("test_2"), current_time * 3);
// Now save an expired query.
blacklist["test_1"] = 1;
saveScheduleBlacklist(blacklist);
blacklist.clear();
// When restoring, the values below the current time will not be included.
restoreScheduleBlacklist(blacklist);
EXPECT_EQ(blacklist.size(), 1U);
count = 0;
get().update({{"data", "{}"}});
get().files(fileCounter);
EXPECT_EQ(count, 0U);
}
}

View File

@ -55,22 +55,25 @@ void FSEventsSubscriptionContext::requireAction(const std::string& action) {
}
void FSEventsEventPublisher::restart() {
if (paths_.empty()) {
// There are no paths to watch.
paths_.insert("/dev/null");
}
if (run_loop_ == nullptr) {
// There is no run loop to restart.
return;
}
// Build paths as CFStrings
std::vector<CFStringRef> cf_paths;
for (const auto& path : paths_) {
auto cf_path =
CFStringCreateWithCString(nullptr, path.c_str(), kCFStringEncodingUTF8);
cf_paths.push_back(cf_path);
{
ReadLock lock(mutex_);
if (paths_.empty()) {
// There are no paths to watch.
paths_.insert("/dev/null");
}
if (run_loop_ == nullptr) {
// There is no run loop to restart.
return;
}
for (const auto& path : paths_) {
auto cf_path = CFStringCreateWithCString(
nullptr, path.c_str(), kCFStringEncodingUTF8);
cf_paths.push_back(cf_path);
}
}
// The FSEvents watch takes a CFArrayRef
@ -144,44 +147,54 @@ void FSEventsEventPublisher::tearDown() {
run_loop_ = nullptr;
}
std::set<std::string> FSEventsEventPublisher::transformSubscription(
FSEventsSubscriptionContextRef& sc) const {
std::set<std::string> paths;
sc->discovered_ = sc->path;
if (sc->path.find("**") != std::string::npos) {
// Double star will indicate recursive matches, restricted to endings.
sc->recursive = true;
sc->discovered_ = sc->path.substr(0, sc->path.find("**"));
// Remove '**' from the subscription path (used to match later).
sc->path = sc->discovered_;
}
// If the path 'still' OR 'either' contains a single wildcard.
if (sc->path.find('*') != std::string::npos) {
// First check if the wildcard is applied to the end.
auto fullpath = fs::path(sc->path);
if (fullpath.filename().string().find('*') != std::string::npos) {
sc->discovered_ = fullpath.parent_path().string();
}
// FSEvents needs a real path, if the wildcard is within the path then
// a configure-time resolve is required.
if (sc->discovered_.find('*') != std::string::npos) {
std::vector<std::string> exploded_paths;
resolveFilePattern(sc->discovered_, exploded_paths);
for (const auto& path : exploded_paths) {
paths.insert(path);
}
sc->recursive_match = sc->recursive;
return paths;
}
}
paths.insert(sc->discovered_);
return paths;
}
void FSEventsEventPublisher::configure() {
// Rebuild the watch paths.
for (auto& sub : subscriptions_) {
auto sc = getSubscriptionContext(sub->context);
if (sc->discovered_.size() > 0) {
continue;
}
sc->discovered_ = sc->path;
if (sc->path.find("**") != std::string::npos) {
// Double star will indicate recursive matches, restricted to endings.
sc->recursive = true;
sc->discovered_ = sc->path.substr(0, sc->path.find("**"));
// Remove '**' from the subscription path (used to match later).
sc->path = sc->discovered_;
}
// If the path 'still' OR 'either' contains a single wildcard.
if (sc->path.find('*') != std::string::npos) {
// First check if the wildcard is applied to the end.
auto fullpath = fs::path(sc->path);
if (fullpath.filename().string().find('*') != std::string::npos) {
sc->discovered_ = fullpath.parent_path().string();
}
// FSEvents needs a real path, if the wildcard is within the path then
// a configure-time resolve is required.
if (sc->discovered_.find('*') != std::string::npos) {
std::vector<std::string> paths;
resolveFilePattern(sc->discovered_, paths);
for (const auto& path : paths) {
paths_.insert(path);
}
sc->recursive_match = sc->recursive;
{
WriteLock lock(mutex_);
for (auto& sub : subscriptions_) {
auto sc = getSubscriptionContext(sub->context);
if (sc->discovered_.size() > 0) {
continue;
}
auto paths = transformSubscription(sc);
paths_.insert(paths.begin(), paths.end());
}
paths_.insert(sc->discovered_);
}
restart();
@ -270,9 +283,13 @@ bool FSEventsEventPublisher::shouldFire(
return true;
}
void FSEventsEventPublisher::removeSubscriptions() {
std::set<std::string>().swap(paths_);
EventPublisherPlugin::removeSubscriptions();
void FSEventsEventPublisher::removeSubscriptions(
const std::string& subscription) {
{
WriteLock lock(mutex_);
std::set<std::string>().swap(paths_);
}
EventPublisherPlugin::removeSubscriptions(subscription);
}
void FSEventsEventPublisher::flush(bool async) {
@ -285,11 +302,11 @@ void FSEventsEventPublisher::flush(bool async) {
}
}
size_t FSEventsEventPublisher::numSubscriptionedPaths() {
size_t FSEventsEventPublisher::numSubscriptionedPaths() const {
return paths_.size();
}
bool FSEventsEventPublisher::isStreamRunning() {
bool FSEventsEventPublisher::isStreamRunning() const {
if (stream_ == nullptr || !stream_started_ || run_loop_ == nullptr) {
return false;
}

View File

@ -102,7 +102,7 @@ class FSEventsEventPublisher
void end() override { stop(); }
/// Delete all paths from prior configuration.
void removeSubscriptions() override;
void removeSubscriptions(const std::string& subscriber) override;
public:
/// FSEvents registers a client callback instead of using a select/poll loop.
@ -114,32 +114,46 @@ class FSEventsEventPublisher
const FSEventStreamEventId fsevent_ids[]);
public:
bool shouldFire(const FSEventsSubscriptionContextRef& mc,
bool shouldFire(const FSEventsSubscriptionContextRef& sc,
const FSEventsEventContextRef& ec) const override;
private:
// Restart the run loop.
/// Restart the run loop.
void restart();
// Stop the stream and the run loop.
/// Stop the stream and the run loop.
void stop();
// Cause the FSEvents to flush kernel-buffered events.
/// Cause the FSEvents to flush kernel-buffered events.
void flush(bool async = false);
private:
// Check if the stream (and run loop) are running.
bool isStreamRunning();
/**
* @brief Each subscription is 'parsed' during configuration.
*
* For each subscription, FSEvents will 'parse' the requested path and
* options. Requests for recursion or path globbing will be resolved.
* The input subscription will be modified such that 'fire'-matching can
* backtrace event paths to requested subscriptions.
*
* @params subscription The mutable subscription.
* @return A set of output paths to monitor.
*/
std::set<std::string> transformSubscription(
FSEventsSubscriptionContextRef& sc) const;
// Count the number of subscriptioned paths.
size_t numSubscriptionedPaths();
private:
/// Check if the stream (and run loop) are running.
bool isStreamRunning() const;
/// Count the number of subscriptioned paths.
size_t numSubscriptionedPaths() const;
private:
/// Local reference to the start, stop, restart event stream.
FSEventStreamRef stream_{nullptr};
/// Has the FSEvents run loop and stream been started.
bool stream_started_{false};
std::atomic<bool> stream_started_{false};
/// Set of paths to monitor, determined by a configure step.
std::set<std::string> paths_;
@ -154,6 +168,9 @@ class FSEventsEventPublisher
/// For testing only, allow the event stream to publish its own events.
bool no_self_{true};
/// Access to watched path set.
mutable boost::shared_mutex mutex_;
private:
friend class FSEventsTests;
FRIEND_TEST(FSEventsTests, test_register_event_pub);

View File

@ -31,6 +31,9 @@ DECLARE_bool(verbose);
class FSEventsTests : public testing::Test {
protected:
void SetUp() override {
// FSEvents will use data from the config and config parsers.
Registry::registry("config_parser")->setUp();
FLAGS_verbose = true;
trigger_path = kTestWorkingDirectory + "fsevents" +
std::to_string(rand() % 10000 + 10000);

View File

@ -512,6 +512,15 @@ Status EventSubscriberPlugin::add(Row& r, EventTime event_time) {
return status;
}
EventPublisherRef EventSubscriberPlugin::getPublisher() const {
return EventFactory::getEventPublisher(getType());
}
void EventSubscriberPlugin::removeSubscriptions() {
subscription_count_ = 0;
getPublisher()->removeSubscriptions(getName());
}
void EventFactory::delay() {
// Caller may disable event publisher threads.
if (FLAGS_disable_events) {
@ -530,6 +539,16 @@ void EventFactory::delay() {
}
}
void EventPublisherPlugin::removeSubscriptions(const std::string& subscriber) {
auto end =
std::remove_if(subscriptions_.begin(),
subscriptions_.end(),
[&subscriber](const SubscriptionRef& subscription) {
return (subscription->subscriber_name == subscriber);
});
subscriptions_.erase(end, subscriptions_.end());
}
Status EventFactory::run(EventPublisherID& type_id) {
if (FLAGS_disable_events) {
return Status(0, "Events disabled");

View File

@ -85,6 +85,7 @@ bool INotifyEventPublisher::monitorSubscription(
return true;
}
}
if (isDirectory(sc->discovered_) && sc->discovered_.back() != '/') {
sc->path += '/';
sc->discovered_ += '/';
@ -117,12 +118,21 @@ Status INotifyEventPublisher::restartMonitoring() {
last_restart_ = getUnixTime();
VLOG(1) << "inotify was overflown, attempting to restart handle";
for (const auto& desc : descriptors_) {
// Create a copy of the descriptors, then remove each.
auto descriptors = descriptors_;
for (const auto& desc : descriptors) {
removeMonitor(desc, true);
}
path_descriptors_.clear();
descriptor_paths_.clear();
{
// Then remove all path/descriptor mappings.
WriteLock lock(mutex_);
path_descriptors_.clear();
descriptor_paths_.clear();
}
// Reconfigure ourself, the subscribers will not reconfigure.
configure();
return Status(0, "OK");
}
@ -186,13 +196,13 @@ Status INotifyEventPublisher::run() {
}
INotifyEventContextRef INotifyEventPublisher::createEventContextFrom(
struct inotify_event* event) {
struct inotify_event* event) const {
auto shared_event = std::make_shared<struct inotify_event>(*event);
auto ec = createEventContext();
ec->event = shared_event;
// Get the pathname the watch fired on.
ec->path = descriptor_paths_[event->wd];
ec->path = descriptor_paths_.at(event->wd);
if (event->len > 1) {
ec->path += event->name;
}
@ -250,12 +260,15 @@ bool INotifyEventPublisher::addMonitor(const std::string& path,
return false;
}
// Keep a list of the watch descriptors
descriptors_.push_back(watch);
// Keep a map of the path -> watch descriptor
path_descriptors_[path] = watch;
// Keep a map of the opposite (descriptor -> path)
descriptor_paths_[watch] = path;
{
WriteLock lock(mutex_);
// Keep a list of the watch descriptors
descriptors_.push_back(watch);
// Keep a map of the path -> watch descriptor
path_descriptors_[path] = watch;
// Keep a map of the opposite (descriptor -> path)
descriptor_paths_[watch] = path;
}
}
if (recursive && isDirectory(path).ok()) {
@ -274,17 +287,24 @@ bool INotifyEventPublisher::addMonitor(const std::string& path,
}
bool INotifyEventPublisher::removeMonitor(const std::string& path, bool force) {
// If force then remove from INotify, otherwise cleanup file descriptors.
if (path_descriptors_.find(path) == path_descriptors_.end()) {
return false;
{
ReadLock lock(mutex_);
// If force then remove from INotify, otherwise cleanup file descriptors.
if (path_descriptors_.find(path) == path_descriptors_.end()) {
return false;
}
}
int watch = path_descriptors_[path];
path_descriptors_.erase(path);
descriptor_paths_.erase(watch);
int watch = 0;
{
WriteLock lock(mutex_);
watch = path_descriptors_[path];
path_descriptors_.erase(path);
descriptor_paths_.erase(watch);
auto position = std::find(descriptors_.begin(), descriptors_.end(), watch);
descriptors_.erase(position);
auto position = std::find(descriptors_.begin(), descriptors_.end(), watch);
descriptors_.erase(position);
}
if (force) {
::inotify_rm_watch(getHandle(), watch);
@ -293,23 +313,27 @@ bool INotifyEventPublisher::removeMonitor(const std::string& path, bool force) {
}
bool INotifyEventPublisher::removeMonitor(int watch, bool force) {
if (descriptor_paths_.find(watch) == descriptor_paths_.end()) {
return false;
std::string path;
{
ReadLock lock(mutex_);
if (descriptor_paths_.find(watch) == descriptor_paths_.end()) {
return false;
}
path = descriptor_paths_[watch];
}
auto path = descriptor_paths_[watch];
return removeMonitor(path, force);
}
void INotifyEventPublisher::removeSubscriptions() {
void INotifyEventPublisher::removeSubscriptions(const std::string& subscriber) {
auto paths = descriptor_paths_;
for (const auto& path : paths) {
removeMonitor(path.first, true);
}
EventPublisherPlugin::removeSubscriptions();
EventPublisherPlugin::removeSubscriptions(subscriber);
}
bool INotifyEventPublisher::isPathMonitored(const std::string& path) {
bool INotifyEventPublisher::isPathMonitored(const std::string& path) const {
ReadLock lock(mutex_);
std::string parent_path;
if (!isDirectory(path).ok()) {
if (path_descriptors_.find(path) != path_descriptors_.end()) {

View File

@ -131,17 +131,18 @@ class INotifyEventPublisher
Status run() override;
/// Remove all monitors and subscriptions.
void removeSubscriptions() override;
void removeSubscriptions(const std::string& subscriber) override;
private:
/// Helper/specialized event context creation.
INotifyEventContextRef createEventContextFrom(struct inotify_event* event);
INotifyEventContextRef createEventContextFrom(
struct inotify_event* event) const;
/// Check if the application-global `inotify` handle is alive.
bool isHandleOpen() { return inotify_handle_ > 0; }
bool isHandleOpen() const { return inotify_handle_ > 0; }
/// Check all added Subscription%s for a path.
bool isPathMonitored(const std::string& path);
bool isPathMonitored(const std::string& path) const;
/**
* @brief Add an INotify watch (monitor) on this path.
@ -176,10 +177,10 @@ class INotifyEventPublisher
const INotifyEventContextRef& ec) const override;
/// Get the INotify file descriptor.
int getHandle() { return inotify_handle_; }
int getHandle() const { return inotify_handle_; }
/// Get the number of actual INotify active descriptors.
size_t numDescriptors() { return descriptors_.size(); }
size_t numDescriptors() const { return descriptors_.size(); }
/// If we overflow, try and restart the monitor
Status restartMonitoring();
@ -194,10 +195,13 @@ class INotifyEventPublisher
DescriptorPathMap descriptor_paths_;
/// The inotify file descriptor handle.
int inotify_handle_{-1};
std::atomic<int> inotify_handle_{-1};
/// Time in seconds of the last inotify restart.
int last_restart_{-1};
std::atomic<int> last_restart_{-1};
/// Access to path and descriptor mappings.
mutable boost::shared_mutex mutex_;
public:
friend class INotifyTests;

View File

@ -32,6 +32,9 @@ const int kMaxEventLatency = 3000;
class INotifyTests : public testing::Test {
protected:
void SetUp() override {
// INotify will use data from the config and config parsers.
Registry::registry("config_parser")->setUp();
real_test_path = kTestWorkingDirectory + "inotify-trigger";
real_test_dir = kTestWorkingDirectory + "inotify-triggers";
real_test_dir_path = real_test_dir + "/1";
@ -287,7 +290,7 @@ TEST_F(INotifyTests, test_inotify_run) {
auto sub = std::make_shared<TestINotifyEventSubscriber>();
EventFactory::registerEventSubscriber(sub);
// Create a subscriptioning context
// Create a subscription context
auto mc = std::make_shared<INotifySubscriptionContext>();
mc->path = real_test_path;
mc->mask = IN_ALL_EVENTS;

View File

@ -12,6 +12,7 @@
#include <gtest/gtest.h>
#include <osquery/config.h>
#include <osquery/events.h>
#include <osquery/tables.h>
@ -19,6 +20,7 @@ namespace osquery {
class EventsTests : public ::testing::Test {
public:
void SetUp() override { Registry::registry("config_parser")->setUp(); }
void TearDown() override { EventFactory::end(true); }
};
@ -53,7 +55,7 @@ class AnotherFakeEventPublisher
DECLARE_PUBLISHER("AnotherFakePublisher");
};
TEST_F(EventsTests, test_event_pub) {
TEST_F(EventsTests, test_event_publisher) {
auto pub = std::make_shared<FakeEventPublisher>();
EXPECT_EQ(pub->type(), "FakePublisher");
@ -62,7 +64,7 @@ TEST_F(EventsTests, test_event_pub) {
EXPECT_EQ(typeid(FakeSubscriptionContext), typeid(*pub_sub));
}
TEST_F(EventsTests, test_register_event_pub) {
TEST_F(EventsTests, test_register_event_publisher) {
auto basic_pub = std::make_shared<BasicEventPublisher>();
auto status = EventFactory::registerEventPublisher(basic_pub);
@ -82,7 +84,7 @@ TEST_F(EventsTests, test_register_event_pub) {
EXPECT_TRUE(status.ok());
}
TEST_F(EventsTests, test_event_pub_types) {
TEST_F(EventsTests, test_event_publisher_types) {
auto pub = std::make_shared<FakeEventPublisher>();
EXPECT_EQ(pub->type(), "FakePublisher");
@ -91,7 +93,7 @@ TEST_F(EventsTests, test_event_pub_types) {
EXPECT_EQ(pub->type(), pub2->type());
}
TEST_F(EventsTests, test_create_event_pub) {
TEST_F(EventsTests, test_duplicate_event_publisher) {
auto pub = std::make_shared<BasicEventPublisher>();
auto status = EventFactory::registerEventPublisher(pub);
EXPECT_TRUE(status.ok());
@ -196,7 +198,7 @@ class TestEventPublisher
int smallest_ever_{0};
};
TEST_F(EventsTests, test_create_custom_event_pub) {
TEST_F(EventsTests, test_create_custom_event_publisher) {
auto basic_pub = std::make_shared<BasicEventPublisher>();
EventFactory::registerEventPublisher(basic_pub);
auto pub = std::make_shared<TestEventPublisher>();
@ -267,9 +269,12 @@ class FakeEventSubscriber : public EventSubscriber<FakeEventPublisher> {
bool bellHathTolled{false};
bool contextBellHathTolled{false};
bool shouldFireBethHathTolled{false};
size_t timesConfigured{0};
FakeEventSubscriber() { setName("FakeSubscriber"); }
void configure() override { timesConfigured++; }
Status Callback(const ECRef& ec, const SCRef& sc) {
// We don't care about the subscription or the event contexts.
bellHathTolled = true;
@ -299,13 +304,13 @@ class FakeEventSubscriber : public EventSubscriber<FakeEventPublisher> {
FRIEND_TEST(EventsTests, test_subscriber_names);
};
TEST_F(EventsTests, test_event_sub) {
TEST_F(EventsTests, test_event_subscriber) {
auto sub = std::make_shared<FakeEventSubscriber>();
EXPECT_EQ(sub->getType(), "FakePublisher");
EXPECT_EQ(sub->getName(), "FakeSubscriber");
}
TEST_F(EventsTests, test_event_sub_subscribe) {
TEST_F(EventsTests, test_event_subscriber_subscribe) {
auto pub = std::make_shared<FakeEventPublisher>();
EventFactory::registerEventPublisher(pub);
@ -323,7 +328,7 @@ TEST_F(EventsTests, test_event_sub_subscribe) {
EXPECT_TRUE(sub->bellHathTolled);
}
TEST_F(EventsTests, test_event_sub_context) {
TEST_F(EventsTests, test_event_subscriber_context) {
auto pub = std::make_shared<FakeEventPublisher>();
EventFactory::registerEventPublisher(pub);
@ -339,6 +344,26 @@ TEST_F(EventsTests, test_event_sub_context) {
EXPECT_TRUE(sub->contextBellHathTolled);
}
TEST_F(EventsTests, test_event_subscriber_configure) {
auto sub = std::make_shared<FakeEventSubscriber>();
EventFactory::registerEventSubscriber(sub);
// Register this subscriber (within the RegistryFactory), so it receives
// configure/reconfigure events.
auto registry = Registry::registry("event_subscriber");
registry->add(sub);
// Assure we start from a base state.
EXPECT_EQ(sub->timesConfigured, 0U);
// Force the config into a loaded state.
Config::getInstance().loaded_ = true;
Config::getInstance().update({{"data", "{}"}});
EXPECT_EQ(sub->timesConfigured, 1U);
registry->remove(sub->getName());
Config::getInstance().update({{"data", "{}"}});
EXPECT_EQ(sub->timesConfigured, 1U);
}
TEST_F(EventsTests, test_fire_event) {
Status status;

View File

@ -188,7 +188,7 @@ void RegistryHelperCore::setUp() {
}
void RegistryHelperCore::configure() {
if (active_.size() != 0 && exists(active_, true)) {
if (!active_.empty() && exists(active_, true)) {
items_.at(active_)->configure();
} else {
for (auto& item : items_) {

View File

@ -62,8 +62,7 @@ REGISTER(FileEventSubscriber, "event_subscriber", "file_events");
void FileEventSubscriber::configure() {
// Clear all paths from FSEvents.
// There may be a better way to find the set intersection/difference.
auto pub = getPublisher();
pub->removeSubscriptions();
removeSubscriptions();
Config::getInstance().files([this](const std::string& category,
const std::vector<std::string>& files) {

View File

@ -59,8 +59,7 @@ REGISTER(FileEventSubscriber, "event_subscriber", "file_events");
void FileEventSubscriber::configure() {
// Clear all monitors from INotify.
// There may be a better way to find the set intersection/difference.
auto pub = getPublisher();
pub->removeSubscriptions();
removeSubscriptions();
auto parser = Config::getParser("file_paths");
auto& accesses = parser->getData().get_child("file_accesses");
@ -95,8 +94,8 @@ Status FileEventSubscriber::Callback(const ECRef& ec, const SCRef& sc) {
if ((sc->mask & kFileAccessMasks) != kFileAccessMasks) {
// Add hashing and 'join' against the file table for stat-information.
decorateFileEvent(
ec->path, (ec->action == "CREATED" || ec->action == "UPDATED"), r);
decorateFileEvent(ec->path,
(ec->action == "CREATED" || ec->action == "UPDATED"), r);
} else {
// The access event on Linux would generate additional events if stated.
for (const auto& column : kCommonFileColumns) {

View File

@ -40,10 +40,8 @@ class ProcessFileEventSubscriber
REGISTER(ProcessFileEventSubscriber, "event_subscriber", "process_file_events");
void ProcessFileEventSubscriber::configure() {
// There may be a better way to find the set intersection/difference.
auto pub = getPublisher();
pub->removeSubscriptions();
removeSubscriptions();
Config::getInstance().files([this](const std::string &category,
const std::vector<std::string> &files) {

View File

@ -0,0 +1,106 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include <gtest/gtest.h>
#include <boost/property_tree/json_parser.hpp>
#include <osquery/config.h>
#include <osquery/events.h>
#include <osquery/flags.h>
#include <osquery/logger.h>
#include <osquery/registry.h>
#include <osquery/sql.h>
#include "osquery/core/test_util.h"
#include "osquery/tables/events/event_utils.h"
namespace osquery {
DECLARE_bool(registry_exceptions);
class FileEventSubscriber;
class FileEventsTableTests : public testing::Test {
public:
void SetUp() override {
Config::getInstance().reset();
// Promote registry access exceptions when testing tables and SQL.
exceptions_ = FLAGS_registry_exceptions;
FLAGS_registry_exceptions = true;
// Setup configuration parsers for file paths accesses.
Registry::registry("config_parser")->setUp();
}
void TearDown() override { FLAGS_registry_exceptions = exceptions_; }
protected:
Status load() { return Config::getInstance().load(); }
private:
bool exceptions_{false};
};
TEST_F(FileEventsTableTests, test_subscriber_exists) {
ASSERT_TRUE(Registry::exists("event_subscriber", "file_events"));
// Note: do not perform a reinterpret cast like this.
auto plugin = Registry::get("event_subscriber", "file_events");
auto* subscriber =
reinterpret_cast<std::shared_ptr<FileEventSubscriber>*>(&plugin);
EXPECT_NE(subscriber, nullptr);
}
TEST_F(FileEventsTableTests, test_table_empty) {
// Attach/create the publishers.
attachEvents();
auto results = SQL::selectAllFrom("file_events");
EXPECT_EQ(results.size(), 0U);
}
class FileEventsTestsConfigPlugin : public ConfigPlugin {
public:
Status genConfig(std::map<std::string, std::string>& config) override {
std::stringstream ss;
pt::write_json(ss, getUnrestrictedPack(), false);
config["data"] = ss.str();
return Status(0);
}
};
TEST_F(FileEventsTableTests, test_configure_subscriptions) {
// Attach/create the publishers.
attachEvents();
// Load a configuration with file paths, verify subscriptions.
Registry::add<FileEventsTestsConfigPlugin>("config", "file_events_tests");
Registry::setActive("config", "file_events_tests");
this->load();
// Explicitly request a configure for subscribers.
Registry::registry("event_subscriber")->configure();
std::string q = "select * from osquery_events where name = 'file_events'";
auto results = SQL::SQL(q);
ASSERT_EQ(results.rows().size(), 1U);
auto& row = results.rows()[0];
// Expect the paths within "unrestricted_pack" to be created as subscriptions.
EXPECT_EQ(row.at("subscriptions"), "2");
// The most important part, make sure a reconfigure removes the subscriptions.
Config::getInstance().update({{"data", "{}"}});
results = SQL::SQL(q);
auto& row2 = results.rows()[0];
EXPECT_EQ(row2.at("subscriptions"), "0");
}
}

View File

@ -50,7 +50,12 @@ using FileSubscriptionContextRef = INotifySubscriptionContextRef;
*/
class YARAEventSubscriber : public FileEventSubscriber {
public:
Status init() override;
Status init() override {
configure();
return Status(0);
}
void configure() override;
private:
/**
@ -74,23 +79,33 @@ class YARAEventSubscriber : public FileEventSubscriber {
*/
REGISTER(YARAEventSubscriber, "event_subscriber", "yara_events");
Status YARAEventSubscriber::init() {
Status status;
void YARAEventSubscriber::configure() {
removeSubscriptions();
// There is a special yara parser that tracks the related top-level keys.
auto plugin = Config::getParser("yara");
if (plugin == nullptr || plugin.get() == nullptr) {
return Status(1, "Could not get yara config parser");
return;
}
// Bail if there is no configured set of opt-in paths for yara.
const auto& yara_config = plugin->getData();
if (yara_config.count("file_paths") == 0) {
return Status(0, "OK");
return;
}
const auto& yara_paths = yara_config.get_child("file_paths");
// Collect the set of paths, we are mostly concerned with the categories.
// But the subscriber must duplicate the set of subscriptions such that the
// publisher's 'fire'-matching logic routes related events to our callback.
std::map<std::string, std::vector<std::string> > file_map;
Config::getInstance().files([&file_map](
const std::string& category,
const std::vector<std::string>& files) { file_map[category] = files; });
const std::string& category, const std::vector<std::string>& files) {
file_map[category] = files;
});
// For each category within yara's file_paths, add a subscription to the
// corresponding set of paths.
const auto& yara_paths = yara_config.get_child("file_paths");
for (const auto& yara_path_element : yara_paths) {
// Subscribe to each file for the given key (category).
if (file_map.count(yara_path_element.first) == 0) {
@ -102,15 +117,13 @@ Status YARAEventSubscriber::init() {
for (const auto& file : file_map.at(yara_path_element.first)) {
VLOG(1) << "Added YARA listener to: " << file;
auto sc = createSubscriptionContext();
sc->recursive = 0;
sc->path = file;
sc->mask = FILE_CHANGE_MASK;
sc->recursive = true;
sc->category = yara_path_element.first;
subscribe(&YARAEventSubscriber::Callback, sc);
}
}
return Status(0, "OK");
}
Status YARAEventSubscriber::Callback(const FileEventContextRef& ec,

View File

@ -1,6 +1,8 @@
{
"packs": {
// This pack is "non-inline", meaning it should trigger genPack.
"tester": "lester",
// This pack is "inlined", the content is a JSON dictionary.
"foobar": {
"version": "1.5.0",
"queries": {