mirror of
https://github.com/valitydev/osquery-1.git
synced 2024-11-07 09:58:54 +00:00
Cleanup/stabilize file_events-related APIs
This commit is contained in:
parent
90d2ac4c76
commit
4031e299bb
@ -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,21 +261,10 @@ 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,
|
||||
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);
|
||||
|
||||
/**
|
||||
* @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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,34 +393,41 @@ 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 {
|
||||
genPack(pack.first, source, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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", pack.first}, {"value", value}};
|
||||
{"action", "genPack"}, {"name", name}, {"value", target}};
|
||||
Registry::call("config", request, response);
|
||||
|
||||
if (response.size() == 0 || response[0].count(pack.first) == 0) {
|
||||
continue;
|
||||
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][pack.first];
|
||||
pack_stream << response[0][name];
|
||||
pt::read_json(pack_stream, pack_tree);
|
||||
addPack(pack.first, name, pack_tree);
|
||||
addPack(name, source, pack_tree);
|
||||
} catch (const pt::json_parser::json_parser_error& e) {
|
||||
LOG(WARNING) << "Error parsing the pack JSON: " << pack.first;
|
||||
LOG(WARNING) << "Error parsing the pack JSON: " << name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyParsers(name, tree, false);
|
||||
return Status(0, "OK");
|
||||
return Status(0);
|
||||
}
|
||||
|
||||
void Config::applyParsers(const std::string& source,
|
||||
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,10 @@ void FSEventsSubscriptionContext::requireAction(const std::string& action) {
|
||||
}
|
||||
|
||||
void FSEventsEventPublisher::restart() {
|
||||
// Build paths as CFStrings
|
||||
std::vector<CFStringRef> cf_paths;
|
||||
{
|
||||
ReadLock lock(mutex_);
|
||||
if (paths_.empty()) {
|
||||
// There are no paths to watch.
|
||||
paths_.insert("/dev/null");
|
||||
@ -65,13 +69,12 @@ void FSEventsEventPublisher::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);
|
||||
auto cf_path = CFStringCreateWithCString(
|
||||
nullptr, path.c_str(), kCFStringEncodingUTF8);
|
||||
cf_paths.push_back(cf_path);
|
||||
}
|
||||
}
|
||||
|
||||
// The FSEvents watch takes a CFArrayRef
|
||||
auto watch_list = CFArrayCreate(nullptr,
|
||||
@ -144,14 +147,9 @@ void FSEventsEventPublisher::tearDown() {
|
||||
run_loop_ = nullptr;
|
||||
}
|
||||
|
||||
void FSEventsEventPublisher::configure() {
|
||||
// Rebuild the watch paths.
|
||||
for (auto& sub : subscriptions_) {
|
||||
auto sc = getSubscriptionContext(sub->context);
|
||||
if (sc->discovered_.size() > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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.
|
||||
@ -172,16 +170,31 @@ void FSEventsEventPublisher::configure() {
|
||||
// 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);
|
||||
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.
|
||||
{
|
||||
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() {
|
||||
void FSEventsEventPublisher::removeSubscriptions(
|
||||
const std::string& subscription) {
|
||||
{
|
||||
WriteLock lock(mutex_);
|
||||
std::set<std::string>().swap(paths_);
|
||||
EventPublisherPlugin::removeSubscriptions();
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
{
|
||||
// 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,6 +260,8 @@ bool INotifyEventPublisher::addMonitor(const std::string& path,
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
WriteLock lock(mutex_);
|
||||
// Keep a list of the watch descriptors
|
||||
descriptors_.push_back(watch);
|
||||
// Keep a map of the path -> watch descriptor
|
||||
@ -257,6 +269,7 @@ bool INotifyEventPublisher::addMonitor(const std::string& path,
|
||||
// Keep a map of the opposite (descriptor -> path)
|
||||
descriptor_paths_[watch] = path;
|
||||
}
|
||||
}
|
||||
|
||||
if (recursive && isDirectory(path).ok()) {
|
||||
std::vector<std::string> children;
|
||||
@ -274,17 +287,24 @@ bool INotifyEventPublisher::addMonitor(const std::string& path,
|
||||
}
|
||||
|
||||
bool INotifyEventPublisher::removeMonitor(const std::string& path, bool force) {
|
||||
{
|
||||
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];
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
std::string path;
|
||||
{
|
||||
ReadLock lock(mutex_);
|
||||
if (descriptor_paths_.find(watch) == descriptor_paths_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto path = descriptor_paths_[watch];
|
||||
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()) {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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_) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
106
osquery/tables/events/tests/file_events_tests.cpp
Normal file
106
osquery/tables/events/tests/file_events_tests.cpp
Normal 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");
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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": {
|
||||
|
Loading…
Reference in New Issue
Block a user