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(
|
static const std::shared_ptr<ConfigParserPlugin> getParser(
|
||||||
const std::string& parser);
|
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.
|
* @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 tree The input configuration tree.
|
||||||
* @param pack True if the tree was built from pack data, otherwise false.
|
* @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,
|
const boost::property_tree::ptree& tree,
|
||||||
bool pack = false);
|
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'.
|
* @brief When config sources are updated the config will 'purge'.
|
||||||
*
|
*
|
||||||
@ -257,6 +276,11 @@ class Config : private boost::noncopyable {
|
|||||||
*/
|
*/
|
||||||
void purge();
|
void purge();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the configuration state, reserved for testing only.
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Schedule of packs and their queries.
|
/// Schedule of packs and their queries.
|
||||||
std::shared_ptr<Schedule> schedule_;
|
std::shared_ptr<Schedule> schedule_;
|
||||||
@ -285,21 +309,14 @@ class Config : private boost::noncopyable {
|
|||||||
friend class Initializer;
|
friend class Initializer;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FRIEND_TEST(ConfigTests, test_plugin_reconfigure);
|
friend class ConfigTests;
|
||||||
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 FilePathsConfigParserPluginTests;
|
friend class FilePathsConfigParserPluginTests;
|
||||||
|
friend class FileEventsTableTests;
|
||||||
FRIEND_TEST(OptionsConfigParserPluginTests, test_get_option);
|
FRIEND_TEST(OptionsConfigParserPluginTests, test_get_option);
|
||||||
FRIEND_TEST(PacksTests, test_discovery_cache);
|
FRIEND_TEST(PacksTests, test_discovery_cache);
|
||||||
FRIEND_TEST(SchedulerTests, test_monitor);
|
FRIEND_TEST(SchedulerTests, test_monitor);
|
||||||
FRIEND_TEST(SchedulerTests, test_config_results_purge);
|
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.
|
/// Use a single placeholder for the EventContextRef passed to EventCallback.
|
||||||
using EventCallback = std::function<
|
using EventCallback = std::function<Status(const EventContextRef&,
|
||||||
Status(const EventContextRef&, const SubscriptionContextRef&)>;
|
const SubscriptionContextRef&)>;
|
||||||
|
|
||||||
/// An EventPublisher must track every subscription added.
|
/// An EventPublisher must track every subscription added.
|
||||||
using SubscriptionVector = std::vector<SubscriptionRef>;
|
using SubscriptionVector = std::vector<SubscriptionRef>;
|
||||||
@ -171,6 +171,9 @@ struct Subscription : private boost::noncopyable {
|
|||||||
subscription->callback = ec;
|
subscription->callback = ec;
|
||||||
return subscription;
|
return subscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
Subscription() = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
class EventPublisherPlugin : public Plugin {
|
class EventPublisherPlugin : public Plugin {
|
||||||
@ -236,10 +239,8 @@ class EventPublisherPlugin : public Plugin {
|
|||||||
return Status(0);
|
return Status(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove all subscriptions.
|
/// Remove all subscriptions from a named subscriber.
|
||||||
virtual void removeSubscriptions() {
|
virtual void removeSubscriptions(const std::string& subscriber);
|
||||||
SubscriptionVector().swap(subscriptions_);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Overriding the EventPublisher constructor is not recommended.
|
/// Overriding the EventPublisher constructor is not recommended.
|
||||||
@ -273,7 +274,7 @@ class EventPublisherPlugin : public Plugin {
|
|||||||
void hasStarted(bool started) { started_ = started; }
|
void hasStarted(bool started) { started_ = started; }
|
||||||
|
|
||||||
/// Get the number of publisher restarts.
|
/// Get the number of publisher restarts.
|
||||||
size_t restartCount() { return restart_count_; }
|
size_t restartCount() const { return restart_count_; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EventPublisherPlugin(EventPublisherPlugin const&) = delete;
|
explicit EventPublisherPlugin(EventPublisherPlugin const&) = delete;
|
||||||
@ -320,11 +321,20 @@ class EventPublisherPlugin : public Plugin {
|
|||||||
friend class EventFactory;
|
friend class EventFactory;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FRIEND_TEST(EventsTests, test_event_pub);
|
FRIEND_TEST(EventsTests, test_event_publisher);
|
||||||
FRIEND_TEST(EventsTests, test_fire_event);
|
FRIEND_TEST(EventsTests, test_fire_event);
|
||||||
};
|
};
|
||||||
|
|
||||||
class EventSubscriberPlugin : public Plugin {
|
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:
|
protected:
|
||||||
/**
|
/**
|
||||||
* @brief Store parsed event data from an EventCallback in a backing store.
|
* @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
|
* publishers. The namespace is a combination of the publisher and subscriber
|
||||||
* registry plugin names.
|
* 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.
|
/// Disable event expiration for this subscriber.
|
||||||
void doNotExpire() { expire_events_ = false; }
|
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.
|
/// A helper value counting the number of fired events tracked by publishers.
|
||||||
EventContextID event_count_{0};
|
EventContextID event_count_{0};
|
||||||
|
|
||||||
@ -494,7 +517,7 @@ class EventSubscriberPlugin : public Plugin {
|
|||||||
size_t subscription_count_{0};
|
size_t subscription_count_{0};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Status setUp() { return Status(0, "Setup never used"); }
|
Status setUp() override { return Status(0, "Setup never used"); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Do not respond to periodic/scheduled/triggered event expiration requests.
|
/// 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.
|
* @param ec The event that was fired.
|
||||||
*/
|
*/
|
||||||
void fireCallback(const SubscriptionRef& sub,
|
void fireCallback(const SubscriptionRef& sub,
|
||||||
const EventContextRef& ec) const {
|
const EventContextRef& ec) const override {
|
||||||
auto pub_sc = getSubscriptionContext(sub->context);
|
auto pub_sc = getSubscriptionContext(sub->context);
|
||||||
auto pub_ec = getEventContext(ec);
|
auto pub_ec = getEventContext(ec);
|
||||||
if (shouldFire(pub_sc, pub_ec) && sub->callback != nullptr) {
|
if (shouldFire(pub_sc, pub_ec) && sub->callback != nullptr) {
|
||||||
@ -822,8 +845,8 @@ class EventPublisher : public EventPublisherPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FRIEND_TEST(EventsTests, test_event_sub_subscribe);
|
FRIEND_TEST(EventsTests, test_event_subscriber_subscribe);
|
||||||
FRIEND_TEST(EventsTests, test_event_sub_context);
|
FRIEND_TEST(EventsTests, test_event_subscriber_context);
|
||||||
FRIEND_TEST(EventsTests, test_fire_event);
|
FRIEND_TEST(EventsTests, test_fire_event);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -847,14 +870,6 @@ class EventSubscriber : public EventSubscriberPlugin {
|
|||||||
using ECRef = typename PUB::ECRef;
|
using ECRef = typename PUB::ECRef;
|
||||||
|
|
||||||
public:
|
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.
|
* @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
|
* plugin name assigned to publishers. The corresponding publisher name is
|
||||||
* interpreted as the subscriber's event 'type'.
|
* interpreted as the subscriber's event 'type'.
|
||||||
*/
|
*/
|
||||||
virtual EventPublisherID& getType() const {
|
virtual EventPublisherID& getType() const override {
|
||||||
static EventPublisherID type = EventFactory::getType<PUB>();
|
static EventPublisherID type = EventFactory::getType<PUB>();
|
||||||
return type;
|
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:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Request the subscriber's initialization state.
|
* @brief Request the subscriber's initialization state.
|
||||||
|
@ -456,6 +456,19 @@ class RegistryHelper : public RegistryHelperCore {
|
|||||||
return ditems;
|
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:
|
public:
|
||||||
RegistryHelper(RegistryHelper const&) = delete;
|
RegistryHelper(RegistryHelper const&) = delete;
|
||||||
void operator=(RegistryHelper const&) = delete;
|
void operator=(RegistryHelper const&) = delete;
|
||||||
@ -463,6 +476,9 @@ class RegistryHelper : public RegistryHelperCore {
|
|||||||
private:
|
private:
|
||||||
AddExternalCallback add_;
|
AddExternalCallback add_;
|
||||||
RemoveExternalCallback remove_;
|
RemoveExternalCallback remove_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FRIEND_TEST(EventsTests, test_event_subscriber_configure);
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Helper definition for a shared pointer to a Plugin.
|
/// 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
|
* The actual plugins must add themselves to a registry type and should
|
||||||
* implement the Plugin and RegistryType interfaces.
|
* 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;
|
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.
|
// Compute a 'synthesized' hash using the content before it is parsed.
|
||||||
hashSource(name, json);
|
hashSource(source, json);
|
||||||
|
|
||||||
// Remove all packs from this source.
|
// Remove all packs from this source.
|
||||||
schedule_->removeAll(name);
|
schedule_->removeAll(source);
|
||||||
// Remove all files from this source.
|
// Remove all files from this source.
|
||||||
removeFiles(name);
|
removeFiles(source);
|
||||||
|
|
||||||
// load the config (source.second) into a pt::ptree
|
// load the config (source.second) into a pt::ptree
|
||||||
pt::ptree tree;
|
pt::ptree tree;
|
||||||
@ -367,7 +368,7 @@ Status Config::updateSource(const std::string& name, const std::string& json) {
|
|||||||
auto& schedule = tree.get_child("schedule");
|
auto& schedule = tree.get_child("schedule");
|
||||||
pt::ptree main_pack;
|
pt::ptree main_pack;
|
||||||
main_pack.add_child("queries", schedule);
|
main_pack.add_child("queries", schedule);
|
||||||
addPack("main", name, main_pack);
|
addPack("main", source, main_pack);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tree.count("scheduledQueries") > 0 && !Registry::external()) {
|
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;
|
pt::ptree legacy_pack;
|
||||||
legacy_pack.add_child("queries", queries);
|
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
|
// 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, "");
|
auto value = packs.get<std::string>(pack.first, "");
|
||||||
if (value.empty()) {
|
if (value.empty()) {
|
||||||
// The pack is a JSON object, treat the content as pack data.
|
// 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 {
|
} 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
|
// If the pack value is a string (and not a JSON object) then it is a
|
||||||
// resource to be handled by the config plugin.
|
// resource to be handled by the config plugin.
|
||||||
PluginResponse response;
|
PluginResponse response;
|
||||||
PluginRequest request = {
|
PluginRequest request = {
|
||||||
{"action", "genPack"}, {"name", pack.first}, {"value", value}};
|
{"action", "genPack"}, {"name", name}, {"value", target}};
|
||||||
Registry::call("config", request, response);
|
Registry::call("config", request, response);
|
||||||
|
|
||||||
if (response.size() == 0 || response[0].count(pack.first) == 0) {
|
if (response.size() == 0 || response[0].count(name) == 0) {
|
||||||
continue;
|
return Status(1, "Invalid plugin response");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pt::ptree pack_tree;
|
pt::ptree pack_tree;
|
||||||
std::stringstream pack_stream;
|
std::stringstream pack_stream;
|
||||||
pack_stream << response[0][pack.first];
|
pack_stream << response[0][name];
|
||||||
pt::read_json(pack_stream, pack_tree);
|
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) {
|
} 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;
|
||||||
}
|
}
|
||||||
}
|
return Status(0);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyParsers(name, tree, false);
|
|
||||||
return Status(0, "OK");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::applyParsers(const std::string& source,
|
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,
|
void Config::recordQueryPerformance(const std::string& name,
|
||||||
size_t delay,
|
size_t delay,
|
||||||
size_t size,
|
size_t size,
|
||||||
|
@ -35,10 +35,18 @@ extern void saveScheduleBlacklist(
|
|||||||
extern void stripConfigComments(std::string& json);
|
extern void stripConfigComments(std::string& json);
|
||||||
|
|
||||||
class ConfigTests : public testing::Test {
|
class ConfigTests : public testing::Test {
|
||||||
|
public:
|
||||||
|
ConfigTests() { Config::getInstance().reset(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void SetUp() { createMockFileStructure(); }
|
void SetUp() { createMockFileStructure(); }
|
||||||
|
|
||||||
void TearDown() { tearDownMockFileStructure(); }
|
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 {
|
class TestConfigPlugin : public ConfigPlugin {
|
||||||
@ -66,6 +74,7 @@ class TestConfigPlugin : public ConfigPlugin {
|
|||||||
return Status(0, "OK");
|
return Status(0, "OK");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
int genConfigCount{0};
|
int genConfigCount{0};
|
||||||
int genPackCount{0};
|
int genPackCount{0};
|
||||||
};
|
};
|
||||||
@ -83,37 +92,9 @@ TEST_F(ConfigTests, test_plugin) {
|
|||||||
EXPECT_EQ(status.toString(), "OK");
|
EXPECT_EQ(status.toString(), "OK");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ConfigTests, test_bad_config_update) {
|
TEST_F(ConfigTests, test_invalid_content) {
|
||||||
std::string bad_json = "{\"options\": {},}";
|
std::string bad_json = "{\"options\": {},}";
|
||||||
ASSERT_NO_THROW(Config::getInstance().update({{"bad_source", bad_json}}));
|
ASSERT_NO_THROW(get().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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ConfigTests, test_strip_comments) {
|
TEST_F(ConfigTests, test_strip_comments) {
|
||||||
@ -127,7 +108,132 @@ TEST_F(ConfigTests, test_strip_comments) {
|
|||||||
EXPECT_EQ(actual, expected);
|
EXPECT_EQ(actual, expected);
|
||||||
|
|
||||||
// Make sure the config update source logic applies the stripping.
|
// 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 {
|
class TestConfigParserPlugin : public ConfigParserPlugin {
|
||||||
@ -161,107 +267,14 @@ class TestConfigParserPlugin : public ConfigParserPlugin {
|
|||||||
// An intermediate boolean to check parser updates.
|
// An intermediate boolean to check parser updates.
|
||||||
bool TestConfigParserPlugin::update_called = false;
|
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) {
|
TEST_F(ConfigTests, test_get_parser) {
|
||||||
Registry::add<TestConfigParserPlugin>("config_parser", "test");
|
Registry::add<TestConfigParserPlugin>("config_parser", "test");
|
||||||
EXPECT_TRUE(Registry::setActive("config_parser", "test").ok());
|
|
||||||
|
|
||||||
Config c;
|
auto s = get().update(getTestConfigMap());
|
||||||
auto s = c.update(getTestConfigMap());
|
|
||||||
EXPECT_TRUE(s.ok());
|
EXPECT_TRUE(s.ok());
|
||||||
EXPECT_EQ(s.toString(), "OK");
|
EXPECT_EQ(s.toString(), "OK");
|
||||||
|
|
||||||
auto plugin = Config::getInstance().getParser("test");
|
auto plugin = get().getParser("test");
|
||||||
EXPECT_TRUE(plugin != nullptr);
|
EXPECT_TRUE(plugin != nullptr);
|
||||||
EXPECT_TRUE(plugin.get() != nullptr);
|
EXPECT_TRUE(plugin.get() != nullptr);
|
||||||
|
|
||||||
@ -273,73 +286,63 @@ TEST_F(ConfigTests, test_get_parser) {
|
|||||||
EXPECT_EQ(data.count("dictionary"), 1U);
|
EXPECT_EQ(data.count("dictionary"), 1U);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ConfigTests, test_pack_file_paths) {
|
class PlaceboConfigParserPlugin : public ConfigParserPlugin {
|
||||||
Config c;
|
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;
|
size_t count = 0;
|
||||||
auto fileCounter =
|
auto fileCounter =
|
||||||
[&count](const std::string& c, const std::vector<std::string>& files) {
|
[&count](const std::string& c, const std::vector<std::string>& files) {
|
||||||
count += files.size();
|
count += files.size();
|
||||||
};
|
};
|
||||||
|
|
||||||
c.addPack("unrestricted_pack", "", getUnrestrictedPack());
|
get().addPack("unrestricted_pack", "", getUnrestrictedPack());
|
||||||
Config::getInstance().files(fileCounter);
|
get().files(fileCounter);
|
||||||
EXPECT_EQ(count, 7U);
|
EXPECT_EQ(count, 2U);
|
||||||
|
|
||||||
c.removePack("unrestricted_pack");
|
|
||||||
count = 0;
|
count = 0;
|
||||||
Config::getInstance().files(fileCounter);
|
get().removePack("unrestricted_pack");
|
||||||
EXPECT_EQ(count, 5U);
|
get().files(fileCounter);
|
||||||
|
EXPECT_EQ(count, 0U);
|
||||||
|
|
||||||
c.addPack("restricted_pack", "", getRestrictedPack());
|
|
||||||
count = 0;
|
count = 0;
|
||||||
Config::getInstance().files(fileCounter);
|
get().addPack("restricted_pack", "", getRestrictedPack());
|
||||||
EXPECT_EQ(count, 5U);
|
get().files(fileCounter);
|
||||||
}
|
EXPECT_EQ(count, 0U);
|
||||||
|
|
||||||
TEST_F(ConfigTests, test_noninline_pack) {
|
// Test a more-generic update.
|
||||||
Registry::add<TestConfigPlugin>("config", "test");
|
count = 0;
|
||||||
|
get().update({{"data", "{\"file_paths\": {\"new\": [\"/new\"]}}"}});
|
||||||
|
get().files(fileCounter);
|
||||||
|
EXPECT_EQ(count, 1U);
|
||||||
|
|
||||||
// Change the active config plugin.
|
count = 0;
|
||||||
EXPECT_TRUE(Registry::setActive("config", "test").ok());
|
get().update({{"data", "{}"}});
|
||||||
|
get().files(fileCounter);
|
||||||
const auto& plugin = std::dynamic_pointer_cast<TestConfigPlugin>(
|
EXPECT_EQ(count, 0U);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,10 @@ void FSEventsSubscriptionContext::requireAction(const std::string& action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FSEventsEventPublisher::restart() {
|
void FSEventsEventPublisher::restart() {
|
||||||
|
// Build paths as CFStrings
|
||||||
|
std::vector<CFStringRef> cf_paths;
|
||||||
|
{
|
||||||
|
ReadLock lock(mutex_);
|
||||||
if (paths_.empty()) {
|
if (paths_.empty()) {
|
||||||
// There are no paths to watch.
|
// There are no paths to watch.
|
||||||
paths_.insert("/dev/null");
|
paths_.insert("/dev/null");
|
||||||
@ -65,13 +69,12 @@ void FSEventsEventPublisher::restart() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build paths as CFStrings
|
|
||||||
std::vector<CFStringRef> cf_paths;
|
|
||||||
for (const auto& path : paths_) {
|
for (const auto& path : paths_) {
|
||||||
auto cf_path =
|
auto cf_path = CFStringCreateWithCString(
|
||||||
CFStringCreateWithCString(nullptr, path.c_str(), kCFStringEncodingUTF8);
|
nullptr, path.c_str(), kCFStringEncodingUTF8);
|
||||||
cf_paths.push_back(cf_path);
|
cf_paths.push_back(cf_path);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The FSEvents watch takes a CFArrayRef
|
// The FSEvents watch takes a CFArrayRef
|
||||||
auto watch_list = CFArrayCreate(nullptr,
|
auto watch_list = CFArrayCreate(nullptr,
|
||||||
@ -144,14 +147,9 @@ void FSEventsEventPublisher::tearDown() {
|
|||||||
run_loop_ = nullptr;
|
run_loop_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FSEventsEventPublisher::configure() {
|
std::set<std::string> FSEventsEventPublisher::transformSubscription(
|
||||||
// Rebuild the watch paths.
|
FSEventsSubscriptionContextRef& sc) const {
|
||||||
for (auto& sub : subscriptions_) {
|
std::set<std::string> paths;
|
||||||
auto sc = getSubscriptionContext(sub->context);
|
|
||||||
if (sc->discovered_.size() > 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc->discovered_ = sc->path;
|
sc->discovered_ = sc->path;
|
||||||
if (sc->path.find("**") != std::string::npos) {
|
if (sc->path.find("**") != std::string::npos) {
|
||||||
// Double star will indicate recursive matches, restricted to endings.
|
// 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
|
// FSEvents needs a real path, if the wildcard is within the path then
|
||||||
// a configure-time resolve is required.
|
// a configure-time resolve is required.
|
||||||
if (sc->discovered_.find('*') != std::string::npos) {
|
if (sc->discovered_.find('*') != std::string::npos) {
|
||||||
std::vector<std::string> paths;
|
std::vector<std::string> exploded_paths;
|
||||||
resolveFilePattern(sc->discovered_, paths);
|
resolveFilePattern(sc->discovered_, exploded_paths);
|
||||||
for (const auto& path : paths) {
|
for (const auto& path : exploded_paths) {
|
||||||
paths_.insert(path);
|
paths.insert(path);
|
||||||
}
|
}
|
||||||
sc->recursive_match = sc->recursive;
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
auto paths = transformSubscription(sc);
|
||||||
|
paths_.insert(paths.begin(), paths.end());
|
||||||
}
|
}
|
||||||
paths_.insert(sc->discovered_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
restart();
|
restart();
|
||||||
@ -270,9 +283,13 @@ bool FSEventsEventPublisher::shouldFire(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FSEventsEventPublisher::removeSubscriptions() {
|
void FSEventsEventPublisher::removeSubscriptions(
|
||||||
|
const std::string& subscription) {
|
||||||
|
{
|
||||||
|
WriteLock lock(mutex_);
|
||||||
std::set<std::string>().swap(paths_);
|
std::set<std::string>().swap(paths_);
|
||||||
EventPublisherPlugin::removeSubscriptions();
|
}
|
||||||
|
EventPublisherPlugin::removeSubscriptions(subscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FSEventsEventPublisher::flush(bool async) {
|
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();
|
return paths_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FSEventsEventPublisher::isStreamRunning() {
|
bool FSEventsEventPublisher::isStreamRunning() const {
|
||||||
if (stream_ == nullptr || !stream_started_ || run_loop_ == nullptr) {
|
if (stream_ == nullptr || !stream_started_ || run_loop_ == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ class FSEventsEventPublisher
|
|||||||
void end() override { stop(); }
|
void end() override { stop(); }
|
||||||
|
|
||||||
/// Delete all paths from prior configuration.
|
/// Delete all paths from prior configuration.
|
||||||
void removeSubscriptions() override;
|
void removeSubscriptions(const std::string& subscriber) override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// FSEvents registers a client callback instead of using a select/poll loop.
|
/// FSEvents registers a client callback instead of using a select/poll loop.
|
||||||
@ -114,32 +114,46 @@ class FSEventsEventPublisher
|
|||||||
const FSEventStreamEventId fsevent_ids[]);
|
const FSEventStreamEventId fsevent_ids[]);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool shouldFire(const FSEventsSubscriptionContextRef& mc,
|
bool shouldFire(const FSEventsSubscriptionContextRef& sc,
|
||||||
const FSEventsEventContextRef& ec) const override;
|
const FSEventsEventContextRef& ec) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Restart the run loop.
|
/// Restart the run loop.
|
||||||
void restart();
|
void restart();
|
||||||
|
|
||||||
// Stop the stream and the run loop.
|
/// Stop the stream and the run loop.
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
// Cause the FSEvents to flush kernel-buffered events.
|
/// Cause the FSEvents to flush kernel-buffered events.
|
||||||
void flush(bool async = false);
|
void flush(bool async = false);
|
||||||
|
|
||||||
private:
|
/**
|
||||||
// Check if the stream (and run loop) are running.
|
* @brief Each subscription is 'parsed' during configuration.
|
||||||
bool isStreamRunning();
|
*
|
||||||
|
* 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.
|
private:
|
||||||
size_t numSubscriptionedPaths();
|
/// Check if the stream (and run loop) are running.
|
||||||
|
bool isStreamRunning() const;
|
||||||
|
|
||||||
|
/// Count the number of subscriptioned paths.
|
||||||
|
size_t numSubscriptionedPaths() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Local reference to the start, stop, restart event stream.
|
/// Local reference to the start, stop, restart event stream.
|
||||||
FSEventStreamRef stream_{nullptr};
|
FSEventStreamRef stream_{nullptr};
|
||||||
|
|
||||||
/// Has the FSEvents run loop and stream been started.
|
/// 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.
|
/// Set of paths to monitor, determined by a configure step.
|
||||||
std::set<std::string> paths_;
|
std::set<std::string> paths_;
|
||||||
@ -154,6 +168,9 @@ class FSEventsEventPublisher
|
|||||||
/// For testing only, allow the event stream to publish its own events.
|
/// For testing only, allow the event stream to publish its own events.
|
||||||
bool no_self_{true};
|
bool no_self_{true};
|
||||||
|
|
||||||
|
/// Access to watched path set.
|
||||||
|
mutable boost::shared_mutex mutex_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class FSEventsTests;
|
friend class FSEventsTests;
|
||||||
FRIEND_TEST(FSEventsTests, test_register_event_pub);
|
FRIEND_TEST(FSEventsTests, test_register_event_pub);
|
||||||
|
@ -31,6 +31,9 @@ DECLARE_bool(verbose);
|
|||||||
class FSEventsTests : public testing::Test {
|
class FSEventsTests : public testing::Test {
|
||||||
protected:
|
protected:
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
|
// FSEvents will use data from the config and config parsers.
|
||||||
|
Registry::registry("config_parser")->setUp();
|
||||||
|
|
||||||
FLAGS_verbose = true;
|
FLAGS_verbose = true;
|
||||||
trigger_path = kTestWorkingDirectory + "fsevents" +
|
trigger_path = kTestWorkingDirectory + "fsevents" +
|
||||||
std::to_string(rand() % 10000 + 10000);
|
std::to_string(rand() % 10000 + 10000);
|
||||||
|
@ -512,6 +512,15 @@ Status EventSubscriberPlugin::add(Row& r, EventTime event_time) {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EventPublisherRef EventSubscriberPlugin::getPublisher() const {
|
||||||
|
return EventFactory::getEventPublisher(getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventSubscriberPlugin::removeSubscriptions() {
|
||||||
|
subscription_count_ = 0;
|
||||||
|
getPublisher()->removeSubscriptions(getName());
|
||||||
|
}
|
||||||
|
|
||||||
void EventFactory::delay() {
|
void EventFactory::delay() {
|
||||||
// Caller may disable event publisher threads.
|
// Caller may disable event publisher threads.
|
||||||
if (FLAGS_disable_events) {
|
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) {
|
Status EventFactory::run(EventPublisherID& type_id) {
|
||||||
if (FLAGS_disable_events) {
|
if (FLAGS_disable_events) {
|
||||||
return Status(0, "Events disabled");
|
return Status(0, "Events disabled");
|
||||||
|
@ -85,6 +85,7 @@ bool INotifyEventPublisher::monitorSubscription(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDirectory(sc->discovered_) && sc->discovered_.back() != '/') {
|
if (isDirectory(sc->discovered_) && sc->discovered_.back() != '/') {
|
||||||
sc->path += '/';
|
sc->path += '/';
|
||||||
sc->discovered_ += '/';
|
sc->discovered_ += '/';
|
||||||
@ -117,12 +118,21 @@ Status INotifyEventPublisher::restartMonitoring() {
|
|||||||
|
|
||||||
last_restart_ = getUnixTime();
|
last_restart_ = getUnixTime();
|
||||||
VLOG(1) << "inotify was overflown, attempting to restart handle";
|
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);
|
removeMonitor(desc, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Then remove all path/descriptor mappings.
|
||||||
|
WriteLock lock(mutex_);
|
||||||
path_descriptors_.clear();
|
path_descriptors_.clear();
|
||||||
descriptor_paths_.clear();
|
descriptor_paths_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconfigure ourself, the subscribers will not reconfigure.
|
||||||
configure();
|
configure();
|
||||||
return Status(0, "OK");
|
return Status(0, "OK");
|
||||||
}
|
}
|
||||||
@ -186,13 +196,13 @@ Status INotifyEventPublisher::run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
INotifyEventContextRef INotifyEventPublisher::createEventContextFrom(
|
INotifyEventContextRef INotifyEventPublisher::createEventContextFrom(
|
||||||
struct inotify_event* event) {
|
struct inotify_event* event) const {
|
||||||
auto shared_event = std::make_shared<struct inotify_event>(*event);
|
auto shared_event = std::make_shared<struct inotify_event>(*event);
|
||||||
auto ec = createEventContext();
|
auto ec = createEventContext();
|
||||||
ec->event = shared_event;
|
ec->event = shared_event;
|
||||||
|
|
||||||
// Get the pathname the watch fired on.
|
// Get the pathname the watch fired on.
|
||||||
ec->path = descriptor_paths_[event->wd];
|
ec->path = descriptor_paths_.at(event->wd);
|
||||||
if (event->len > 1) {
|
if (event->len > 1) {
|
||||||
ec->path += event->name;
|
ec->path += event->name;
|
||||||
}
|
}
|
||||||
@ -250,6 +260,8 @@ bool INotifyEventPublisher::addMonitor(const std::string& path,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
WriteLock lock(mutex_);
|
||||||
// Keep a list of the watch descriptors
|
// Keep a list of the watch descriptors
|
||||||
descriptors_.push_back(watch);
|
descriptors_.push_back(watch);
|
||||||
// Keep a map of the path -> watch descriptor
|
// 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)
|
// Keep a map of the opposite (descriptor -> path)
|
||||||
descriptor_paths_[watch] = path;
|
descriptor_paths_[watch] = path;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (recursive && isDirectory(path).ok()) {
|
if (recursive && isDirectory(path).ok()) {
|
||||||
std::vector<std::string> children;
|
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) {
|
bool INotifyEventPublisher::removeMonitor(const std::string& path, bool force) {
|
||||||
|
{
|
||||||
|
ReadLock lock(mutex_);
|
||||||
// If force then remove from INotify, otherwise cleanup file descriptors.
|
// If force then remove from INotify, otherwise cleanup file descriptors.
|
||||||
if (path_descriptors_.find(path) == path_descriptors_.end()) {
|
if (path_descriptors_.find(path) == path_descriptors_.end()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int watch = path_descriptors_[path];
|
int watch = 0;
|
||||||
|
{
|
||||||
|
WriteLock lock(mutex_);
|
||||||
|
watch = path_descriptors_[path];
|
||||||
path_descriptors_.erase(path);
|
path_descriptors_.erase(path);
|
||||||
descriptor_paths_.erase(watch);
|
descriptor_paths_.erase(watch);
|
||||||
|
|
||||||
auto position = std::find(descriptors_.begin(), descriptors_.end(), watch);
|
auto position = std::find(descriptors_.begin(), descriptors_.end(), watch);
|
||||||
descriptors_.erase(position);
|
descriptors_.erase(position);
|
||||||
|
}
|
||||||
|
|
||||||
if (force) {
|
if (force) {
|
||||||
::inotify_rm_watch(getHandle(), watch);
|
::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) {
|
bool INotifyEventPublisher::removeMonitor(int watch, bool force) {
|
||||||
|
std::string path;
|
||||||
|
{
|
||||||
|
ReadLock lock(mutex_);
|
||||||
if (descriptor_paths_.find(watch) == descriptor_paths_.end()) {
|
if (descriptor_paths_.find(watch) == descriptor_paths_.end()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
path = descriptor_paths_[watch];
|
||||||
auto path = descriptor_paths_[watch];
|
}
|
||||||
return removeMonitor(path, force);
|
return removeMonitor(path, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
void INotifyEventPublisher::removeSubscriptions() {
|
void INotifyEventPublisher::removeSubscriptions(const std::string& subscriber) {
|
||||||
auto paths = descriptor_paths_;
|
auto paths = descriptor_paths_;
|
||||||
for (const auto& path : paths) {
|
for (const auto& path : paths) {
|
||||||
removeMonitor(path.first, true);
|
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;
|
std::string parent_path;
|
||||||
if (!isDirectory(path).ok()) {
|
if (!isDirectory(path).ok()) {
|
||||||
if (path_descriptors_.find(path) != path_descriptors_.end()) {
|
if (path_descriptors_.find(path) != path_descriptors_.end()) {
|
||||||
|
@ -131,17 +131,18 @@ class INotifyEventPublisher
|
|||||||
Status run() override;
|
Status run() override;
|
||||||
|
|
||||||
/// Remove all monitors and subscriptions.
|
/// Remove all monitors and subscriptions.
|
||||||
void removeSubscriptions() override;
|
void removeSubscriptions(const std::string& subscriber) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Helper/specialized event context creation.
|
/// 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.
|
/// 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.
|
/// 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.
|
* @brief Add an INotify watch (monitor) on this path.
|
||||||
@ -176,10 +177,10 @@ class INotifyEventPublisher
|
|||||||
const INotifyEventContextRef& ec) const override;
|
const INotifyEventContextRef& ec) const override;
|
||||||
|
|
||||||
/// Get the INotify file descriptor.
|
/// Get the INotify file descriptor.
|
||||||
int getHandle() { return inotify_handle_; }
|
int getHandle() const { return inotify_handle_; }
|
||||||
|
|
||||||
/// Get the number of actual INotify active descriptors.
|
/// 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
|
/// If we overflow, try and restart the monitor
|
||||||
Status restartMonitoring();
|
Status restartMonitoring();
|
||||||
@ -194,10 +195,13 @@ class INotifyEventPublisher
|
|||||||
DescriptorPathMap descriptor_paths_;
|
DescriptorPathMap descriptor_paths_;
|
||||||
|
|
||||||
/// The inotify file descriptor handle.
|
/// The inotify file descriptor handle.
|
||||||
int inotify_handle_{-1};
|
std::atomic<int> inotify_handle_{-1};
|
||||||
|
|
||||||
/// Time in seconds of the last inotify restart.
|
/// 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:
|
public:
|
||||||
friend class INotifyTests;
|
friend class INotifyTests;
|
||||||
|
@ -32,6 +32,9 @@ const int kMaxEventLatency = 3000;
|
|||||||
class INotifyTests : public testing::Test {
|
class INotifyTests : public testing::Test {
|
||||||
protected:
|
protected:
|
||||||
void SetUp() override {
|
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_path = kTestWorkingDirectory + "inotify-trigger";
|
||||||
real_test_dir = kTestWorkingDirectory + "inotify-triggers";
|
real_test_dir = kTestWorkingDirectory + "inotify-triggers";
|
||||||
real_test_dir_path = real_test_dir + "/1";
|
real_test_dir_path = real_test_dir + "/1";
|
||||||
@ -287,7 +290,7 @@ TEST_F(INotifyTests, test_inotify_run) {
|
|||||||
auto sub = std::make_shared<TestINotifyEventSubscriber>();
|
auto sub = std::make_shared<TestINotifyEventSubscriber>();
|
||||||
EventFactory::registerEventSubscriber(sub);
|
EventFactory::registerEventSubscriber(sub);
|
||||||
|
|
||||||
// Create a subscriptioning context
|
// Create a subscription context
|
||||||
auto mc = std::make_shared<INotifySubscriptionContext>();
|
auto mc = std::make_shared<INotifySubscriptionContext>();
|
||||||
mc->path = real_test_path;
|
mc->path = real_test_path;
|
||||||
mc->mask = IN_ALL_EVENTS;
|
mc->mask = IN_ALL_EVENTS;
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <osquery/config.h>
|
||||||
#include <osquery/events.h>
|
#include <osquery/events.h>
|
||||||
#include <osquery/tables.h>
|
#include <osquery/tables.h>
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ namespace osquery {
|
|||||||
|
|
||||||
class EventsTests : public ::testing::Test {
|
class EventsTests : public ::testing::Test {
|
||||||
public:
|
public:
|
||||||
|
void SetUp() override { Registry::registry("config_parser")->setUp(); }
|
||||||
void TearDown() override { EventFactory::end(true); }
|
void TearDown() override { EventFactory::end(true); }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -53,7 +55,7 @@ class AnotherFakeEventPublisher
|
|||||||
DECLARE_PUBLISHER("AnotherFakePublisher");
|
DECLARE_PUBLISHER("AnotherFakePublisher");
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(EventsTests, test_event_pub) {
|
TEST_F(EventsTests, test_event_publisher) {
|
||||||
auto pub = std::make_shared<FakeEventPublisher>();
|
auto pub = std::make_shared<FakeEventPublisher>();
|
||||||
EXPECT_EQ(pub->type(), "FakePublisher");
|
EXPECT_EQ(pub->type(), "FakePublisher");
|
||||||
|
|
||||||
@ -62,7 +64,7 @@ TEST_F(EventsTests, test_event_pub) {
|
|||||||
EXPECT_EQ(typeid(FakeSubscriptionContext), typeid(*pub_sub));
|
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 basic_pub = std::make_shared<BasicEventPublisher>();
|
||||||
auto status = EventFactory::registerEventPublisher(basic_pub);
|
auto status = EventFactory::registerEventPublisher(basic_pub);
|
||||||
|
|
||||||
@ -82,7 +84,7 @@ TEST_F(EventsTests, test_register_event_pub) {
|
|||||||
EXPECT_TRUE(status.ok());
|
EXPECT_TRUE(status.ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(EventsTests, test_event_pub_types) {
|
TEST_F(EventsTests, test_event_publisher_types) {
|
||||||
auto pub = std::make_shared<FakeEventPublisher>();
|
auto pub = std::make_shared<FakeEventPublisher>();
|
||||||
EXPECT_EQ(pub->type(), "FakePublisher");
|
EXPECT_EQ(pub->type(), "FakePublisher");
|
||||||
|
|
||||||
@ -91,7 +93,7 @@ TEST_F(EventsTests, test_event_pub_types) {
|
|||||||
EXPECT_EQ(pub->type(), pub2->type());
|
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 pub = std::make_shared<BasicEventPublisher>();
|
||||||
auto status = EventFactory::registerEventPublisher(pub);
|
auto status = EventFactory::registerEventPublisher(pub);
|
||||||
EXPECT_TRUE(status.ok());
|
EXPECT_TRUE(status.ok());
|
||||||
@ -196,7 +198,7 @@ class TestEventPublisher
|
|||||||
int smallest_ever_{0};
|
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>();
|
auto basic_pub = std::make_shared<BasicEventPublisher>();
|
||||||
EventFactory::registerEventPublisher(basic_pub);
|
EventFactory::registerEventPublisher(basic_pub);
|
||||||
auto pub = std::make_shared<TestEventPublisher>();
|
auto pub = std::make_shared<TestEventPublisher>();
|
||||||
@ -267,9 +269,12 @@ class FakeEventSubscriber : public EventSubscriber<FakeEventPublisher> {
|
|||||||
bool bellHathTolled{false};
|
bool bellHathTolled{false};
|
||||||
bool contextBellHathTolled{false};
|
bool contextBellHathTolled{false};
|
||||||
bool shouldFireBethHathTolled{false};
|
bool shouldFireBethHathTolled{false};
|
||||||
|
size_t timesConfigured{0};
|
||||||
|
|
||||||
FakeEventSubscriber() { setName("FakeSubscriber"); }
|
FakeEventSubscriber() { setName("FakeSubscriber"); }
|
||||||
|
|
||||||
|
void configure() override { timesConfigured++; }
|
||||||
|
|
||||||
Status Callback(const ECRef& ec, const SCRef& sc) {
|
Status Callback(const ECRef& ec, const SCRef& sc) {
|
||||||
// We don't care about the subscription or the event contexts.
|
// We don't care about the subscription or the event contexts.
|
||||||
bellHathTolled = true;
|
bellHathTolled = true;
|
||||||
@ -299,13 +304,13 @@ class FakeEventSubscriber : public EventSubscriber<FakeEventPublisher> {
|
|||||||
FRIEND_TEST(EventsTests, test_subscriber_names);
|
FRIEND_TEST(EventsTests, test_subscriber_names);
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(EventsTests, test_event_sub) {
|
TEST_F(EventsTests, test_event_subscriber) {
|
||||||
auto sub = std::make_shared<FakeEventSubscriber>();
|
auto sub = std::make_shared<FakeEventSubscriber>();
|
||||||
EXPECT_EQ(sub->getType(), "FakePublisher");
|
EXPECT_EQ(sub->getType(), "FakePublisher");
|
||||||
EXPECT_EQ(sub->getName(), "FakeSubscriber");
|
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>();
|
auto pub = std::make_shared<FakeEventPublisher>();
|
||||||
EventFactory::registerEventPublisher(pub);
|
EventFactory::registerEventPublisher(pub);
|
||||||
|
|
||||||
@ -323,7 +328,7 @@ TEST_F(EventsTests, test_event_sub_subscribe) {
|
|||||||
EXPECT_TRUE(sub->bellHathTolled);
|
EXPECT_TRUE(sub->bellHathTolled);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(EventsTests, test_event_sub_context) {
|
TEST_F(EventsTests, test_event_subscriber_context) {
|
||||||
auto pub = std::make_shared<FakeEventPublisher>();
|
auto pub = std::make_shared<FakeEventPublisher>();
|
||||||
EventFactory::registerEventPublisher(pub);
|
EventFactory::registerEventPublisher(pub);
|
||||||
|
|
||||||
@ -339,6 +344,26 @@ TEST_F(EventsTests, test_event_sub_context) {
|
|||||||
EXPECT_TRUE(sub->contextBellHathTolled);
|
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) {
|
TEST_F(EventsTests, test_fire_event) {
|
||||||
Status status;
|
Status status;
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ void RegistryHelperCore::setUp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RegistryHelperCore::configure() {
|
void RegistryHelperCore::configure() {
|
||||||
if (active_.size() != 0 && exists(active_, true)) {
|
if (!active_.empty() && exists(active_, true)) {
|
||||||
items_.at(active_)->configure();
|
items_.at(active_)->configure();
|
||||||
} else {
|
} else {
|
||||||
for (auto& item : items_) {
|
for (auto& item : items_) {
|
||||||
|
@ -62,8 +62,7 @@ REGISTER(FileEventSubscriber, "event_subscriber", "file_events");
|
|||||||
void FileEventSubscriber::configure() {
|
void FileEventSubscriber::configure() {
|
||||||
// Clear all paths from FSEvents.
|
// Clear all paths from FSEvents.
|
||||||
// There may be a better way to find the set intersection/difference.
|
// There may be a better way to find the set intersection/difference.
|
||||||
auto pub = getPublisher();
|
removeSubscriptions();
|
||||||
pub->removeSubscriptions();
|
|
||||||
|
|
||||||
Config::getInstance().files([this](const std::string& category,
|
Config::getInstance().files([this](const std::string& category,
|
||||||
const std::vector<std::string>& files) {
|
const std::vector<std::string>& files) {
|
||||||
|
@ -59,8 +59,7 @@ REGISTER(FileEventSubscriber, "event_subscriber", "file_events");
|
|||||||
void FileEventSubscriber::configure() {
|
void FileEventSubscriber::configure() {
|
||||||
// Clear all monitors from INotify.
|
// Clear all monitors from INotify.
|
||||||
// There may be a better way to find the set intersection/difference.
|
// There may be a better way to find the set intersection/difference.
|
||||||
auto pub = getPublisher();
|
removeSubscriptions();
|
||||||
pub->removeSubscriptions();
|
|
||||||
|
|
||||||
auto parser = Config::getParser("file_paths");
|
auto parser = Config::getParser("file_paths");
|
||||||
auto& accesses = parser->getData().get_child("file_accesses");
|
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) {
|
if ((sc->mask & kFileAccessMasks) != kFileAccessMasks) {
|
||||||
// Add hashing and 'join' against the file table for stat-information.
|
// Add hashing and 'join' against the file table for stat-information.
|
||||||
decorateFileEvent(
|
decorateFileEvent(ec->path,
|
||||||
ec->path, (ec->action == "CREATED" || ec->action == "UPDATED"), r);
|
(ec->action == "CREATED" || ec->action == "UPDATED"), r);
|
||||||
} else {
|
} else {
|
||||||
// The access event on Linux would generate additional events if stated.
|
// The access event on Linux would generate additional events if stated.
|
||||||
for (const auto& column : kCommonFileColumns) {
|
for (const auto& column : kCommonFileColumns) {
|
||||||
|
@ -40,10 +40,8 @@ class ProcessFileEventSubscriber
|
|||||||
REGISTER(ProcessFileEventSubscriber, "event_subscriber", "process_file_events");
|
REGISTER(ProcessFileEventSubscriber, "event_subscriber", "process_file_events");
|
||||||
|
|
||||||
void ProcessFileEventSubscriber::configure() {
|
void ProcessFileEventSubscriber::configure() {
|
||||||
|
|
||||||
// There may be a better way to find the set intersection/difference.
|
// There may be a better way to find the set intersection/difference.
|
||||||
auto pub = getPublisher();
|
removeSubscriptions();
|
||||||
pub->removeSubscriptions();
|
|
||||||
|
|
||||||
Config::getInstance().files([this](const std::string &category,
|
Config::getInstance().files([this](const std::string &category,
|
||||||
const std::vector<std::string> &files) {
|
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 {
|
class YARAEventSubscriber : public FileEventSubscriber {
|
||||||
public:
|
public:
|
||||||
Status init() override;
|
Status init() override {
|
||||||
|
configure();
|
||||||
|
return Status(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void configure() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
@ -74,23 +79,33 @@ class YARAEventSubscriber : public FileEventSubscriber {
|
|||||||
*/
|
*/
|
||||||
REGISTER(YARAEventSubscriber, "event_subscriber", "yara_events");
|
REGISTER(YARAEventSubscriber, "event_subscriber", "yara_events");
|
||||||
|
|
||||||
Status YARAEventSubscriber::init() {
|
void YARAEventSubscriber::configure() {
|
||||||
Status status;
|
removeSubscriptions();
|
||||||
|
|
||||||
|
// There is a special yara parser that tracks the related top-level keys.
|
||||||
auto plugin = Config::getParser("yara");
|
auto plugin = Config::getParser("yara");
|
||||||
if (plugin == nullptr || plugin.get() == nullptr) {
|
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();
|
const auto& yara_config = plugin->getData();
|
||||||
if (yara_config.count("file_paths") == 0) {
|
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;
|
std::map<std::string, std::vector<std::string> > file_map;
|
||||||
Config::getInstance().files([&file_map](
|
Config::getInstance().files([&file_map](
|
||||||
const std::string& category,
|
const std::string& category, const std::vector<std::string>& files) {
|
||||||
const std::vector<std::string>& files) { file_map[category] = 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) {
|
for (const auto& yara_path_element : yara_paths) {
|
||||||
// Subscribe to each file for the given key (category).
|
// Subscribe to each file for the given key (category).
|
||||||
if (file_map.count(yara_path_element.first) == 0) {
|
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)) {
|
for (const auto& file : file_map.at(yara_path_element.first)) {
|
||||||
VLOG(1) << "Added YARA listener to: " << file;
|
VLOG(1) << "Added YARA listener to: " << file;
|
||||||
auto sc = createSubscriptionContext();
|
auto sc = createSubscriptionContext();
|
||||||
|
sc->recursive = 0;
|
||||||
sc->path = file;
|
sc->path = file;
|
||||||
sc->mask = FILE_CHANGE_MASK;
|
sc->mask = FILE_CHANGE_MASK;
|
||||||
sc->recursive = true;
|
|
||||||
sc->category = yara_path_element.first;
|
sc->category = yara_path_element.first;
|
||||||
subscribe(&YARAEventSubscriber::Callback, sc);
|
subscribe(&YARAEventSubscriber::Callback, sc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Status(0, "OK");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Status YARAEventSubscriber::Callback(const FileEventContextRef& ec,
|
Status YARAEventSubscriber::Callback(const FileEventContextRef& ec,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"packs": {
|
"packs": {
|
||||||
|
// This pack is "non-inline", meaning it should trigger genPack.
|
||||||
"tester": "lester",
|
"tester": "lester",
|
||||||
|
// This pack is "inlined", the content is a JSON dictionary.
|
||||||
"foobar": {
|
"foobar": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"queries": {
|
"queries": {
|
||||||
|
Loading…
Reference in New Issue
Block a user