docker_run:
docker build --network=host --tag tizen-osquery ./docker
- docker run --rm -it --net=host -v $(shell pwd):/usr/src tizen-osquery
+ docker run --rm -it --net=host --privileged -v $(shell pwd):/usr/src tizen-osquery
%::
mkdir -p build
friend class DBHandleTests;
friend class QueryTests;
-// friend class EventsDatabaseTests;
+ friend class EventsDatabaseTests;
};
}
--- /dev/null
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <map>
+#include <vector>
+
+#include <boost/make_shared.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/locks.hpp>
+#include <boost/thread/mutex.hpp>
+
+#include "osquery/status.h"
+#include "osquery/database.h"
+#include "osquery/registry.h"
+
+namespace osquery {
+
+struct Subscription;
+class EventPublisher;
+class EventSubscriber;
+
+typedef const std::string EventPublisherID;
+typedef const std::string EventID;
+typedef uint32_t EventContextID;
+typedef uint32_t EventTime;
+typedef std::pair<EventID, EventTime> EventRecord;
+
+/**
+ * @brief An EventPublisher will define a SubscriptionContext for
+ * EventSubscriber%s to use.
+ *
+ * Most EventPublisher%s will reqire specific information for interacting with
+ * an OS to receive events. The SubscriptionContext contains information the
+ * EventPublisher will use to register OS API callbacks, create
+ * subscriptioning/listening handles, etc.
+ *
+ * Linux `inotify` should implement a SubscriptionContext that subscriptions
+ * filesystem events based on a filesystem path. `libpcap` will subscription on
+ * networking protocols at various stacks. Process creation may subscription on
+ * process name, parent pid, etc.
+ */
+struct SubscriptionContext {};
+
+/**
+ * @brief An EventSubscriber EventCallback method will receive an EventContext.
+ *
+ * The EventContext contains the event-related data supplied by an
+ * EventPublisher when the event occures. If a subscriptioning EventSubscriber
+ * is should be called for the event the EventSubscriber%'s EventCallback is
+ * passed an EventContext.
+ */
+struct EventContext {
+ /// An unique counting ID specific to the EventPublisher%'s fired events.
+ EventContextID id;
+ /// The time the event occured.
+ EventTime time;
+ /// The string representation of the time, often used for indexing.
+ std::string time_string;
+};
+
+typedef std::shared_ptr<Subscription> SubscriptionRef;
+typedef std::shared_ptr<EventPublisher> EventPublisherRef;
+typedef std::shared_ptr<SubscriptionContext> SubscriptionContextRef;
+typedef std::shared_ptr<EventContext> EventContextRef;
+typedef std::shared_ptr<EventSubscriber> EventSubscriberRef;
+
+typedef std::function<Status(EventContextRef, bool)> EventCallback;
+
+/// An EventPublisher must track every subscription added.
+typedef std::vector<SubscriptionRef> SubscriptionVector;
+
+/// The EventFactory tracks every EventPublisher and the name it specifies.
+typedef std::map<EventPublisherID, EventPublisherRef> EventPublisherMap;
+
+/// The set of search-time binned lookup tables.
+extern const std::vector<size_t> kEventTimeLists;
+
+/**
+ * @brief Helper type casting methods for EventPublisher classes.
+ *
+ * A new osquery EventPublisher should subclass EventPublisher and use the
+ * following:
+ *
+ * @code{.cpp}
+ * #include "osquery/events.h"
+ *
+ * class MyEventPublisher: public EventPublisher {
+ * DECLARE_EVENTPUBLISHER(MyEventPublisher, MySubscriptionContext,
+ * MyEventContext);
+ * }
+ * @endcode
+ *
+ * This assumes new EventPublisher%s will always include a custom
+ * SubscriptionContext and EventContext. In the above example
+ * MySubscriptionContext allows EventSubscriber%s to downselect or customize
+ * what events to handle. And MyEventContext includes fields specific to the
+ * new EventPublisher.
+ */
+#define DECLARE_EVENTPUBLISHER(TYPE, MONITOR, EVENT) \
+ public: \
+ EventPublisherID type() const { return #TYPE; } \
+ bool shouldFire(const SubscriptionContextRef mc, const EventContextRef ec) { \
+ if (#MONITOR == "SubscriptionContext" && #EVENT == "EventContext") \
+ return true; \
+ return shouldFire(getSubscriptionContext(mc), getEventContext(ec)); \
+ } \
+ static std::shared_ptr<EVENT> getEventContext(EventContextRef context) { \
+ return std::static_pointer_cast<EVENT>(context); \
+ } \
+ static std::shared_ptr<MONITOR> getSubscriptionContext( \
+ SubscriptionContextRef context) { \
+ return std::static_pointer_cast<MONITOR>(context); \
+ } \
+ static std::shared_ptr<EVENT> createEventContext() { \
+ return std::make_shared<EVENT>(); \
+ } \
+ static std::shared_ptr<MONITOR> createSubscriptionContext() { \
+ return std::make_shared<MONITOR>(); \
+ }
+
+/**
+ * @brief Required getter and namespace helper methods for EventSubscriber%s.
+ *
+ * A new osquery `EventSubscriber` should subclass EventSubscriber with the
+ * following:
+ *
+ * @code{.cpp}
+ * #include "osquery/events.h"
+ *
+ * class MyEventSubscriber: public EventSubscriber {
+ * DECLARE_EVENTSUBSCRIBER(MyEventSubscriber, MyEventPublisher);
+ * }
+ * @endcode
+ *
+ * EventSubscriber%s should be specific to an EventPublisher.
+ */
+#define DECLARE_EVENTSUBSCRIBER(NAME, TYPE) \
+ public: \
+ static std::shared_ptr<NAME> getInstance() { \
+ static auto q = std::shared_ptr<NAME>(new NAME()); \
+ return q; \
+ } \
+ static QueryData genTable() __attribute__((used)) { \
+ return getInstance()->get(0, 0); \
+ } \
+ \
+ private: \
+ EventPublisherID name() const { return #NAME; } \
+ EventPublisherID type() const { return #TYPE; } \
+ NAME() {}
+
+/**
+ * @brief Required callin EventSubscriber method declaration helper.
+ *
+ * An EventSubscriber will include 1 or more EventCallback methods. Consider the
+ * following flow: (1) Event occurs, (2) EventCallback is called with the
+ * event details, (3) details logged, (4) details are queried.
+ *
+ * The above logic can be supplied in a class-like namespace with static
+ * callin/callback functions:
+ *
+ * @code{.cpp}
+ * #include "osquery/events.h"
+ *
+ * class MyEventSubscriber: public EventSubscriber {
+ * DECLARE_EVENTSUBSCRIBER(MyEventSubscriber, MyEventPublisher);
+ * DECLARE_CALLBACK(MyCallback, MyEventContext)
+ *
+ * Status ModuleMyCallback(EventContextID, EventTime, MyEventContext);
+ * }
+ * @endcode
+ *
+ * And then somewhere else in code the callback can be registered:
+ *
+ * @code{.cpp}
+ * EventFactory::addSubscription("MyEventPublisher", my_subscription_context,
+ * MyEventSubscriber::MyCallback);
+ * @endcode
+ *
+ * The binding from static method, function pointer, and EventSubscriber
+ * instance boilerplate code is added automatically.
+ * Note: The macro will append `Module` to `MyCallback`.
+ */
+#define DECLARE_CALLBACK(NAME, EVENT) \
+ public: \
+ static Status Event##NAME(const EventContextRef ec, bool reserved) { \
+ auto ec_ = std::static_pointer_cast<EVENT>(ec); \
+ return getInstance()->NAME(ec_); \
+ } \
+ \
+ private: \
+ void BindTo##NAME(const SubscriptionContextRef mc) { \
+ EventFactory::addSubscription(type(), mc, Event##NAME); \
+ }
+
+/**
+ * @brief Bind a subscription context to a declared EventCallback for this
+ *module.
+ *
+ * Binding refers to the association of a callback for this EventSubscriber to
+ * a configured SubscriptionContext. Under the hood "binding" creates a factory
+ * Subscription for the EventPublisher used by the EventSubscriber. Such that
+ * when an event of the EventPublisher is fired, if the event details match the
+ * specifics of the SubscriptionContext the EventSubscription%'s EventCallback
+ * will be called.
+ *
+ * @code{.cpp}
+ * #include "osquery/events.h"
+ *
+ * class MyEventSubscriber: public EventSubscriber {
+ * DECLARE_EVENTSUBSCRIBER(MyEventSubscriber, MyEventPublisher);
+ * DECLARE_CALLBACK(MyCallback, MyEventContext);
+ *
+ * public:
+ * void init() {
+ * auto mc = MyEventPublisher::createSubscriptionContext();
+ * mc->requirement = "SOME_SPECIFIC_DETAIL";
+ * BIND_CALLBACK(MyCallback, mc);
+ * }
+ * Status MyCallback(const MyEventContextRef ec) {}
+ * }
+ * @endcode
+ *
+ * The symbol `MyCallback` must match in `DECLARE_CALLBACK`, `BIND_CALLBACK` and
+ * as a member of this EventSubscriber.
+ *
+ * @param NAME The symbol for the EventCallback method used in DECLARE_CALLBACK.
+ * @param MC The SubscriptionContext to bind.
+ */
+#define BIND_CALLBACK(NAME, MC) \
+ EventFactory::addSubscription(type(), MC, Event##NAME);
+
+/**
+ * @brief A Subscription is used to configure an EventPublisher and bind a
+ * callback.
+ *
+ * A Subscription is the input to an EventPublisher when the EventPublisher
+ * decides on the scope and details of the events it watches and generates.
+ * An example includes a filesystem change event. A subscription would include
+ * a path with optional recursion and attribute selectors as well as a callback
+ * function to fire when an event for that path and selector occurs.
+ *
+ * A Subscription also functions to greatly scope an EventPublisher%'s work.
+ * Using the same filesystem example and the Linux inotify subsystem a
+ * Subscription limits the number of inode watches to only those requested by
+ * appropriate EventSubscriber%s.
+ * Note: EventSubscriber%s and Subscriptions can be configured by the osquery
+ * user.
+ *
+ * Subscriptions are usually created with EventFactory members:
+ *
+ * @code{.cpp}
+ * EventFactory::addSubscription("MyEventPublisher", my_subscription_context);
+ * @endcode
+ */
+struct Subscription {
+ public:
+ /// An EventPublisher%-specific SubscriptionContext.
+ SubscriptionContextRef context;
+ /// An EventSubscription member EventCallback method.
+ EventCallback callback;
+
+ static SubscriptionRef create() { return std::make_shared<Subscription>(); }
+
+ static SubscriptionRef create(const SubscriptionContextRef mc,
+ EventCallback ec = 0) {
+ auto subscription = std::make_shared<Subscription>();
+ subscription->context = mc;
+ subscription->callback = ec;
+ return subscription;
+ }
+};
+
+/**
+ * @brief Generate OS events of a type (FS, Network, Syscall, ioctl).
+ *
+ * A 'class' of OS events is abstracted into an EventPublisher responsible for
+ * remaining as agile as possible given a known-set of subscriptions.
+ *
+ * The lifecycle of an EventPublisher may include, `setUp`, `configure`, `run`,
+ * `tearDown`, and `fire`. `setUp` and `tearDown` happen when osquery starts and
+ * stops either as a daemon or interactive shell. `configure` is a pseudo-start
+ * called every time a Subscription is added. EventPublisher%s can adjust their
+ * scope/agility specific to each added subscription by overriding
+ *`addSubscription`,
+ * and or globally in `configure`.
+ *
+ * Not all EventPublisher%s leverage pure async OS APIs, and most will require a
+ * run loop either polling with a timeout on a descriptor or for a change. When
+ * osquery initializes the EventFactory will optionally create a thread for each
+ * EventPublisher using `run` as the thread's entrypoint. This is called in a
+ * within-thread loop where returning a FAILED status ends the run loop and
+ * shuts down the thread.
+ *
+ * To opt-out of polling in a thread consider the following run implementation:
+ *
+ * @code{.cpp}
+ * Status run() { return Status(1, "Not Implemented") }
+ * @endcode
+ *
+ * The final lifecycle component, `fire` will iterate over the EventPublisher
+ * Subscription%s and call `shouldFire` for each, using the EventContext fired.
+ * The `shouldFire` method should check the subscription-specific selectors and
+ * only call the Subscription%'s callback function is the EventContext
+ * (thus event) matches.
+ */
+class EventPublisher {
+ public:
+ /**
+ * @brief A new Subscription was added, potentially change state based on all
+ * subscriptions for this EventPublisher.
+ *
+ * `configure` allows the EventPublisher to optimize on the state of all
+ * subscriptions. An example is Linux `inotify` where multiple
+ * EventSubscription%s will subscription identical paths, e.g., /etc for
+ * config changes. Since Linux `inotify` has a subscription limit, `configure`
+ * can depup paths.
+ */
+ virtual void configure() {}
+
+ /**
+ * @brief Perform handle opening, OS API callback registration.
+ *
+ * `setUp` is the event framework's EventPublisher constructor equivilent.
+ * When `setUp` is called the EventPublisher is running in a dedicated thread
+ * and may manage/allocate/wait for resources.
+ */
+ virtual void setUp() {}
+
+ /**
+ * @brief Perform handle closing, resource cleanup.
+ *
+ * osquery is about to end, the EventPublisher should close handle descriptors
+ * unblock resources, and prepare to exit.
+ */
+ virtual void tearDown() {}
+
+ /**
+ * @brief Implement a step of an optional run loop.
+ *
+ * @return A SUCCESS status will immediately call `run` again. A FAILED status
+ * will exit the run loop and the thread.
+ */
+ virtual Status run();
+
+ /**
+ * @brief A new EventSubscriber is subscriptioning events of this
+ * EventPublisher.
+ *
+ * @param subscription The Subscription context information and optional
+ * EventCallback.
+ *
+ * @return If the Subscription is not appropriate (mismatched type) fail.
+ */
+ virtual Status addSubscription(const SubscriptionRef subscription) {
+ subscriptions_.push_back(subscription);
+ return Status(0, "OK");
+ }
+
+ /// Number of Subscription%s watching this EventPublisher.
+ size_t numSubscriptions() { return subscriptions_.size(); }
+
+ /**
+ * @brief The number of events fired by this EventPublisher.
+ *
+ * @return The number of events.
+ */
+ size_t numEvents() { return next_ec_id_; }
+
+ /// Overriding the EventPublisher constructor is not recommended.
+ EventPublisher() : next_ec_id_(0) {};
+
+ /// Return a string identifier associated with this EventPublisher.
+ virtual EventPublisherID type() const = 0;
+
+ /// Return a string identifier for the given EventPublisher symbol.
+ template <typename T>
+ static EventPublisherID type() {
+ const auto& event_pub = new T();
+ auto type_id = event_pub->type();
+ delete event_pub;
+ return type_id;
+ }
+
+ public:
+ /**
+ * @brief The generic check loop to call SubscriptionContext callback methods.
+ *
+ * It is NOT recommended to override `fire`. The simple logic of enumerating
+ * the Subscription%s and using `shouldFire` is more appropraite.
+ *
+ * @param ec The EventContext created and fired by the EventPublisher.
+ * @param time The most accurate time associated with the event.
+ */
+ void fire(const EventContextRef ec, EventTime time = 0);
+
+ protected:
+ /**
+ * @brief The generic `fire` will call `shouldFire` for each Subscription.
+ *
+ * @param mc A SubscriptionContext with optional specifications for events
+ * details.
+ * @param ec The event fired with event details.
+ *
+ * @return should the Subscription%'s EventCallback be called for this event.
+ */
+ virtual bool shouldFire(const SubscriptionContextRef mc,
+ const EventContextRef ec);
+
+ protected:
+ /// The EventPublisher will keep track of Subscription%s that contain callins.
+ SubscriptionVector subscriptions_;
+
+ /// An Event ID is assigned by the EventPublisher within the EventContext.
+ /// This is not used to store event date in the backing store.
+ EventContextID next_ec_id_;
+
+ private:
+ /// A lock for incrementing the next EventContextID.
+ boost::mutex ec_id_lock_;
+
+ private:
+ FRIEND_TEST(EventsTests, test_fire_event);
+};
+
+/**
+ * @brief An interface binding Subscriptions, event response, and table
+ *generation.
+ *
+ * Use the EventSubscriber interface when adding event subscriptions and
+ * defining callin functions. The EventCallback is usually a member function
+ * for an EventSubscriber. The EventSubscriber interface includes a very
+ * important `add` method that abstracts the needed event to backing store
+ * interaction.
+ *
+ * Storing event data in the backing store must match a table spec for queries.
+ * Small overheads exist that help query-time indexing and lookups.
+ */
+class EventSubscriber {
+ public:
+ /// Called after EventPublisher `setUp`. Add all Subscription%s here.
+ /**
+ * @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 void init() {}
+
+ /**
+ * @brief Suggested entrypoint for table generation.
+ *
+ * The EventSubscriber is a convention that removes a lot of boilerplate event
+ * subscriptioning and acting. The `genTable` static entrypoint is the
+ * suggested method for table specs.
+ *
+ * @return The query-time table data, retrieved from a backing store.
+ */
+ static QueryData genTable();
+
+ protected:
+ /**
+ * @brief Store parsed event data from an EventCallback in a backing store.
+ *
+ * Within a EventCallback the EventSubscriber has an opprotunity to create
+ * an osquery Row element, add the relevant table data for the EventSubscriber
+ * and store that element in the osquery backing store. At query-time
+ * the added data will apply selection criteria and return these elements.
+ * The backing store data retrieval is optimized by time-based indexes. It
+ * is important to added EventTime as it relates to "when the event occured".
+ *
+ * @param r An osquery Row element.
+ * @param time The time the added event occured.
+ *
+ * @return Was the element added to the backing store.
+ */
+ virtual Status add(const osquery::Row& r, EventTime time) final;
+
+ /**
+ * @brief Return all events added by this EventSubscriber within start, stop.
+ *
+ * This is used internally (for the most part) by EventSubscriber::genTable.
+ *
+ * @param start Inclusive lower bound time limit.
+ * @param stop Inclusive upper bound time limit.
+ * @return Set of event rows matching time limits.
+ */
+ virtual QueryData get(EventTime start, EventTime stop);
+
+ /*
+ * @brief When `get`ting event results, return EventID%s from time indexes.
+ *
+ * Used by EventSubscriber::get to retrieve EventID, EventTime indexes. This
+ * applies the lookup-efficiency checks for time list appropriate bins.
+ * If the time range in 24 hours and there is a 24-hour list bin it will
+ * be queried using a single backing store `Get` followed by two `Get`s of
+ * the most-specific boundary lists.
+ *
+ * @return List of EventID, EventTime%s
+ */
+ std::vector<EventRecord> getRecords(EventTime start, EventTime stop);
+
+ private:
+ /**
+ * @brief Get a unique storage-related EventID.
+ *
+ * An EventID is an index/element-identifier for the backing store.
+ * Each EventPublisher maintains a fired EventContextID to identify the many
+ * events that may or may not be fired to subscriptioning criteria for this
+ * EventSubscriber. This EventContextID is NOT the same as an EventID.
+ * EventSubscriber development should not require use of EventID%s, if this
+ * indexing is required within-EventCallback consider an
+ * EventSubscriber%-unique indexing, counting mechanic.
+ *
+ * @return A unique ID for backing storage.
+ */
+ EventID getEventID();
+
+ /*
+ * @brief Add an EventID, EventTime pair to all matching list types.
+ *
+ * The list types are defined by time size. Based on the EventTime this pair
+ * is added to the list bin for each list type. If there are two list types:
+ * 60 seconds and 3600 seconds and `time` is 92, this pair will be added to
+ * list type 1 bin 4 and list type 2 bin 1.
+ *
+ * @param eid A unique EventID.
+ * @param time The time when this EventID%'s event occured.
+ *
+ * @return Were the indexes recorded.
+ */
+ Status recordEvent(EventID eid, EventTime time);
+
+ protected:
+ /**
+ * @brief A single instance requirement for static callback facilities.
+ *
+ * The EventSubscriber constructor is NOT responsible for adding
+ * Subscription%s. Please use `init` for adding Subscription%s as all
+ * EventPublisher instances will have run `setUp` and initialized their run
+ * loops.
+ */
+ EventSubscriber() {}
+
+ /// Backing storage indexing namespace definition methods.
+ EventPublisherID dbNamespace() { return type() + "." + name(); }
+ /// The string EventPublisher identifying this EventSubscriber.
+ virtual EventPublisherID type() const = 0;
+ /// The string name identifying this EventSubscriber.
+ virtual EventPublisherID name() const = 0;
+
+ private:
+ /// Lock used when incrementing the EventID database index.
+ boost::mutex event_id_lock_;
+
+ /// Lock used when recording an EventID and time into search bins.
+ boost::mutex event_record_lock_;
+
+ private:
+ FRIEND_TEST(EventsDatabaseTests, test_event_module_id);
+ FRIEND_TEST(EventsDatabaseTests, test_unique_event_module_id);
+};
+
+/**
+ * @brief A factory for associating event generators to EventPublisherID%s.
+ *
+ * This factory both registers new event types and the subscriptions that use
+ * them. An EventPublisher is also a factory, the single event factory arbitates
+ * Subscription creatating and management for each associated EventPublisher.
+ *
+ * Since event types may be plugins, they are created using the factory.
+ * Since subscriptions may be configured/disabled they are also factory-managed.
+ */
+class EventFactory {
+ public:
+ /// Access to the EventFactory instance.
+ static EventFactory& getInstance();
+
+ /**
+ * @brief Add an EventPublisher to the factory.
+ *
+ * The registration is mostly abstracted using osquery's registery.
+ */
+ template <typename T>
+ static Status registerEventPublisher() {
+ auto event_pub = std::make_shared<T>();
+ return EventFactory::registerEventPublisher(event_pub);
+ }
+
+ /**
+ * @brief Add an EventPublisher to the factory.
+ *
+ * The registration is mostly abstracted using osquery's registery.
+ *
+ * @param event_pub If for some reason the caller needs access to the
+ * EventPublisher instance they can register-by-instance.
+ *
+ * Access to the EventPublisher instance is not discouraged, but using the
+ * EventFactory `getEventPublisher` accessor is encouraged.
+ */
+ static Status registerEventPublisher(const EventPublisherRef event_pub);
+
+ /**
+ * @brief Add an EventSubscriber to the factory.
+ *
+ * The registration is mostly abstracted using osquery's registery.
+ */
+ template <typename T>
+ static Status registerEventSubscriber() {
+ auto event_module = T::getInstance();
+ return EventFactory::registerEventSubscriber(event_module);
+ }
+
+ /**
+ * @brief Add an EventSubscriber to the factory.
+ *
+ * The registration is mostly abstracted using osquery's registery.
+ *
+ * @param event_module If the caller must access the EventSubscriber instance
+ * control may be passed to the registry.
+ *
+ * Access to the EventSubscriber instance outside of the within-instance
+ * table generation method and set of EventCallback%s is discouraged.
+ */
+ static Status registerEventSubscriber(const EventSubscriberRef event_module);
+
+ /**
+ * @brief Add a SubscriptionContext and EventCallback Subscription to an
+ *EventPublisher.
+ *
+ * Create a Subscription from a given SubscriptionContext and EventCallback
+ * and add that Subscription to the EventPublisher assosicated identiter.
+ *
+ * @param type_id The string for an EventPublisher receiving the Subscription.
+ * @param mc A SubscriptionContext related to the EventPublisher.
+ * @param cb When the EventPublisher fires an event the SubscriptionContext
+ * will be evaluated, if the event matches optional specifics in the context
+ * this callback function will be called. It should belong to an
+ * EventSubscription.
+ *
+ * @return Was the SubscriptionContext appropriate for the EventPublisher.
+ */
+ static Status addSubscription(EventPublisherID type_id,
+ const SubscriptionContextRef mc,
+ EventCallback cb = 0);
+
+ /// Add a Subscription using a caller Subscription instance.
+ static Status addSubscription(EventPublisherID type_id,
+ const SubscriptionRef subscription);
+
+ /// Add a Subscription by templating the EventPublisher, using a
+ /// SubscriptionContext.
+ template <typename T>
+ static Status addSubscription(const SubscriptionContextRef mc,
+ EventCallback cb = 0) {
+ return addSubscription(EventPublisher::type<T>(), mc, cb);
+ }
+
+ /// Add a Subscription by templating the EventPublisher, using a Subscription
+ /// instance.
+ template <typename T>
+ static Status addSubscription(const SubscriptionRef subscription) {
+ return addSubscription(EventPublisher::type<T>(), subscription);
+ }
+
+ /// Get the total number of Subscription%s across ALL EventPublisher%s.
+ static size_t numSubscriptions(EventPublisherID type_id);
+
+ /// Get the number of EventPublishers.
+ static size_t numEventPublishers() {
+ return EventFactory::getInstance().event_pubs_.size();
+ }
+
+ /**
+ * @brief Halt the EventPublisher run loop and call its `tearDown`.
+ *
+ * Any EventSubscriber%s with Subscription%s for this EventPublisher will
+ * become useless. osquery instanciators MUST deregister events.
+ * EventPublisher%s assume they can hook/trampoline, which requires cleanup.
+ *
+ * @param event_pub The string label for the EventPublisher.
+ *
+ * @return Did the EventPublisher deregister cleanly.
+ */
+ static Status deregisterEventPublisher(const EventPublisherRef event_pub);
+
+ /// Deregister an EventPublisher by EventPublisherID.
+ static Status deregisterEventPublisher(EventPublisherID type_id);
+
+ /// Deregister all EventPublisher%s.
+ static Status deregisterEventPublishers();
+
+ /// Return an instance to a registered EventPublisher.
+ static EventPublisherRef getEventPublisher(EventPublisherID);
+
+ public:
+ /// The dispatched event thread's entrypoint (if needed).
+ static Status run(EventPublisherID type_id);
+
+ /// An initializer's entrypoint for spawning all event type run loops.
+ static void delay();
+
+ public:
+ /// If a static EventPublisher callback wants to fire
+ template <typename T>
+ static void fire(const EventContextRef ec) {
+ auto event_pub = getEventPublisher(EventPublisher::type<T>());
+ event_pub->fire(ec);
+ }
+
+ /**
+ * @brief End all EventPublisher run loops and call their `tearDown` methods.
+ *
+ * End is NOT the same as deregistration.
+ *
+ * @param should_end Reset the "is ending" state if False.
+ */
+ static void end(bool should_end = true);
+
+ private:
+ /// An EventFactory will exist for the lifetime of the application.
+ EventFactory() { ending_ = false; }
+ EventFactory(EventFactory const&);
+ void operator=(EventFactory const&);
+
+ private:
+ /// Set ending to True to cause event type run loops to finish.
+ bool ending_;
+
+ /// Set of registered EventPublisher instances.
+ EventPublisherMap event_pubs_;
+
+ /// Set of running EventPublisher run loop threads.
+ std::vector<std::shared_ptr<boost::thread>> threads_;
+
+ /// Set of instanciated EventSubscriber Subscription sets (with callbacks and
+ /// state).
+ std::vector<EventSubscriberRef> event_modules_;
+};
+}
+
+/// Expose a Plugin-like Registry for EventPublisher instances.
+DECLARE_REGISTRY(EventPublishers, std::string, EventPublisherRef)
+#define REGISTERED_EVENTPUBLISHERS REGISTRY(EventPublishers)
+#define REGISTER_EVENTPUBLISHER(decorator) \
+ REGISTER(EventPublishers, #decorator, std::make_shared<decorator>())
+
+/**
+ * @brief Expose a Plugin-link Registry for EventSubscriber instances.
+ *
+ * In most cases the EventSubscriber class will organize itself to include
+ * an generator entry point for query-time table generation too.
+ */
+DECLARE_REGISTRY(EventSubscribers, std::string, EventSubscriberRef)
+#define REGISTERED_EVENTSUBSCRIBERS REGISTRY(EventSubscribers)
+#define REGISTER_EVENTSUBSCRIBER(decorator) \
+ REGISTER(EventSubscribers, #decorator, decorator::getInstance())
+
+namespace osquery {
+namespace registries {
+/**
+ * @brief A utility method for moving EventPublisher%s and EventSubscriber%s
+ * (plugins) into the EventFactory.
+ *
+ * To handle run-time and compile-time EventPublisher and EventSubscriber
+ * additions as plugins or extensions, the osquery Registry workflow is used.
+ * During application launch (or within plugin load) the EventFactory faucet
+ * moves managed instances of these types to the EventFactory. The
+ * EventPublisher and EventSubscriber lifecycle/developer workflow is unknown
+ * to the Registry.
+ */
+void faucet(EventPublishers ets, EventSubscribers ems);
+}
+}
ADD_SUBDIRECTORY(config)
ADD_SUBDIRECTORY(dispatcher)
ADD_SUBDIRECTORY(database)
-ADD_SUBDIRECTORY(registry)
+ADD_SUBDIRECTORY(events)
ADD_SUBDIRECTORY(filesystem)
ADD_SUBDIRECTORY(logger)
+ADD_SUBDIRECTORY(registry)
ADD_SUBDIRECTORY(scheduler)
ADD_LIBRARY(${TARGET_OSQUERY_LIB} STATIC main/lib.cpp ${${TARGET_OSQUERY_LIB}_SRCS})
--- /dev/null
+ADD_OSQUERY_LIBRARY(osquery_events events.cpp
+ linux/inotify.cpp)
+
+ADD_OSQUERY_TEST(osquery_events_tests events_tests.cpp)
+ADD_OSQUERY_TEST(osquery_events_database_tests events_database_tests.cpp)
+ADD_OSQUERY_TEST(osquery_inotify_tests linux/inotify_tests.cpp)
--- /dev/null
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <glog/logging.h>
+
+#include "osquery/core.h"
+#include "osquery/core/conversions.h"
+#include "osquery/events.h"
+#include "osquery/dispatcher.h"
+
+namespace osquery {
+
+const std::vector<size_t> kEventTimeLists = {1 * 60, // 1 minute
+ 1 * 60 * 60, // 1 hour
+ 12 * 60 * 60, // half-day
+};
+
+void EventPublisher::fire(const EventContextRef ec, EventTime time) {
+ EventContextID ec_id;
+
+ {
+ boost::lock_guard<boost::mutex> lock(ec_id_lock_);
+ ec_id = next_ec_id_++;
+ }
+
+ // Fill in EventContext ID and time if needed.
+ if (ec != nullptr) {
+ ec->id = ec_id;
+ if (ec->time == 0) {
+ if (time == 0) {
+ time = getUnixTime();
+ }
+ ec->time = time;
+ }
+
+ // Set the optional string-verion of the time for DB columns.
+ ec->time_string = boost::lexical_cast<std::string>(ec->time);
+ }
+
+ for (const auto& subscription : subscriptions_) {
+ auto callback = subscription->callback;
+ if (shouldFire(subscription->context, ec) && callback != nullptr) {
+ callback(ec, false);
+ } else {
+ }
+ }
+}
+
+bool EventPublisher::shouldFire(const SubscriptionContextRef mc,
+ const EventContextRef ec) {
+ return true;
+}
+
+Status EventPublisher::run() {
+ // Runloops/entrypoints are ONLY implemented if needed.
+ return Status(1, "No runloop required");
+}
+
+std::vector<EventRecord> EventSubscriber::getRecords(EventTime start,
+ EventTime stop) {
+ Status status;
+ std::vector<EventRecord> records;
+ auto db = DBHandle::getInstance();
+
+ std::string index_key = "indexes." + dbNamespace();
+ std::string record_key = "records." + dbNamespace();
+
+ // For now, cheat and use the first list type.
+ std::string list_key = boost::lexical_cast<std::string>(kEventTimeLists[0]);
+ std::string index_value;
+
+ // Get all bins for this list type.
+ status = db->Get(kEvents, index_key + "." + list_key, index_value);
+ if (index_value.length() == 0) {
+ // There are no events in this time range.
+ return records;
+ }
+ // Tokenize the value into our bins of the list type.
+ std::vector<std::string> lists;
+ boost::split(lists, index_value, boost::is_any_of(","));
+ std::string record_value;
+ for (const auto& list_id : lists) {
+ status = db->Get(
+ kEvents, record_key + "." + list_key + "." + list_id, record_value);
+ if (record_value.length() == 0) {
+ // There are actually no events in this bin, interesting error case.
+ continue;
+ }
+ std::vector<std::string> bin_records;
+ boost::split(bin_records, record_value, boost::is_any_of(",:"));
+ auto bin_it = bin_records.begin();
+ for (; bin_it != bin_records.end(); bin_it++) {
+ std::string eid = *bin_it;
+ EventTime time = boost::lexical_cast<EventTime>(*(++bin_it));
+ records.push_back(std::make_pair(eid, time));
+ }
+ }
+
+ // Now all the event_ids/event_times within the binned range exist.
+ // Select further on the EXACT time range.
+
+ return records;
+}
+
+Status EventSubscriber::recordEvent(EventID eid, EventTime time) {
+ Status status;
+ auto db = DBHandle::getInstance();
+ std::string time_value = boost::lexical_cast<std::string>(time);
+
+ // The record is identified by the event type then module name.
+ std::string index_key = "indexes." + dbNamespace();
+ std::string record_key = "records." + dbNamespace();
+ // The list key includes the list type (bin size) and the list ID (bin).
+ std::string list_key;
+ std::string list_id;
+
+ for (auto time_list : kEventTimeLists) {
+ // The list_id is the MOST-Specific key ID, the bin for this list.
+ // If the event time was 13 and the time_list is 5 seconds, lid = 2.
+ list_id = boost::lexical_cast<std::string>(time / time_list);
+ // The list name identifies the 'type' of list.
+ list_key = boost::lexical_cast<std::string>(time_list);
+ // list_key = list_key + "." + list_id;
+
+ {
+ boost::lock_guard<boost::mutex> lock(event_record_lock_);
+ // Append the record (eid, unix_time) to the list bin.
+ std::string record_value;
+ status = db->Get(
+ kEvents, record_key + "." + list_key + "." + list_id, record_value);
+
+ if (record_value.length() == 0) {
+ // This is a new list_id for list_key, append the ID to the indirect
+ // lookup for this list_key.
+ std::string index_value;
+ status = db->Get(kEvents, index_key + "." + list_key, index_value);
+ if (index_value.length() == 0) {
+ // A new index.
+ index_value = list_id;
+ } else {
+ index_value += "," + list_id;
+ }
+ status = db->Put(kEvents, index_key + "." + list_key, index_value);
+ record_value = eid + ":" + time_value;
+ } else {
+ // Tokenize a record using ',' and the EID/time using ':'.
+ record_value += "," + eid + ":" + time_value;
+ }
+ status = db->Put(
+ kEvents, record_key + "." + list_key + "." + list_id, record_value);
+ if (!status.ok()) {
+ LOG(ERROR) << "Could not put Event Record key: " << record_key << "."
+ << list_key << "." << list_id;
+ }
+ }
+ }
+
+ return Status(0, "OK");
+}
+
+EventID EventSubscriber::getEventID() {
+ Status status;
+ auto db = DBHandle::getInstance();
+ // First get an event ID from the meta key.
+ std::string eid_key = "eid." + dbNamespace();
+ std::string last_eid_value;
+ std::string eid_value;
+
+ {
+ boost::lock_guard<boost::mutex> lock(event_id_lock_);
+ status = db->Get(kEvents, eid_key, last_eid_value);
+ if (!status.ok()) {
+ last_eid_value = "0";
+ }
+
+ size_t eid = boost::lexical_cast<size_t>(last_eid_value) + 1;
+ eid_value = boost::lexical_cast<std::string>(eid);
+ status = db->Put(kEvents, eid_key, eid_value);
+ }
+
+ if (!status.ok()) {
+ return "0";
+ }
+
+ return eid_value;
+}
+
+QueryData EventSubscriber::get(EventTime start, EventTime stop) {
+ QueryData results;
+ Status status;
+ auto db = DBHandle::getInstance();
+
+ // Get the records for this time range.
+ auto records = getRecords(start, stop);
+
+ std::string events_key = "data." + dbNamespace();
+
+ // Select records using event_ids as keys.
+ std::string data_value;
+ for (const auto& record : records) {
+ Row r;
+ status = db->Get(kEvents, events_key + "." + record.first, data_value);
+ if (data_value.length() == 0) {
+ // THere is no record here, interesting error case.
+ continue;
+ }
+ status = deserializeRowJSON(data_value, r);
+ if (status.ok()) {
+ results.push_back(r);
+ }
+ }
+ return results;
+}
+
+Status EventSubscriber::add(const Row& r, EventTime time) {
+ Status status;
+ auto db = DBHandle::getInstance();
+
+ // Get and increment the EID for this module.
+ EventID eid = getEventID();
+
+ std::string event_key = "data." + dbNamespace() + "." + eid;
+ std::string data;
+
+ status = serializeRowJSON(r, data);
+ if (!status.ok()) {
+ return status;
+ }
+
+ // Store the event data.
+ status = db->Put(kEvents, event_key, data);
+ // Record the event in the indexing bins.
+ recordEvent(eid, time);
+ return status;
+}
+
+void EventFactory::delay() {
+ auto& ef = EventFactory::getInstance();
+ for (const auto& eventtype : EventFactory::getInstance().event_pubs_) {
+ auto thread_ = std::make_shared<boost::thread>(
+ boost::bind(&EventFactory::run, eventtype.first));
+ ef.threads_.push_back(thread_);
+ }
+}
+
+Status EventFactory::run(EventPublisherID type_id) {
+ // An interesting take on an event dispatched entrypoint.
+ // There is little introspection into the event type.
+ // Assume it can either make use of an entrypoint poller/selector or
+ // take care of async callback registrations in setUp/configure/run
+ // only once and handle event queueing/firing in callbacks.
+ auto event_pub = EventFactory::getInstance().getEventPublisher(type_id);
+ if (event_pub == nullptr) {
+ return Status(1, "No Event Type");
+ }
+
+ Status status = Status(0, "OK");
+ while (!EventFactory::getInstance().ending_ && status.ok()) {
+ // Can optionally implement a global cooloff latency here.
+ status = event_pub->run();
+ }
+
+ // The runloop status is not reflective of the event type's.
+ return Status(0, "OK");
+}
+
+void EventFactory::end(bool should_end) {
+ EventFactory::getInstance().ending_ = should_end;
+ // Join on the thread group.
+ ::usleep(400);
+}
+
+// There's no reason for the event factory to keep multiple instances.
+EventFactory& EventFactory::getInstance() {
+ static EventFactory ef;
+ return ef;
+}
+
+Status EventFactory::registerEventPublisher(const EventPublisherRef event_pub) {
+ auto& ef = EventFactory::getInstance();
+ auto type_id = event_pub->type();
+
+ if (ef.getEventPublisher(type_id) != nullptr) {
+ // This is a duplicate type id?
+ return Status(1, "Duplicate Event Type");
+ }
+
+ ef.event_pubs_[type_id] = event_pub;
+ event_pub->setUp();
+ return Status(0, "OK");
+}
+
+Status EventFactory::registerEventSubscriber(
+ const EventSubscriberRef event_module) {
+ auto& ef = EventFactory::getInstance();
+ // Let the module initialize any Subscriptions.
+ event_module->init();
+ ef.event_modules_.push_back(event_module);
+ return Status(0, "OK");
+}
+
+Status EventFactory::addSubscription(EventPublisherID type_id,
+ const SubscriptionRef subscription) {
+ auto event_pub = EventFactory::getInstance().getEventPublisher(type_id);
+ if (event_pub == nullptr) {
+ // Cannot create a Subscription for a missing type_id.
+ return Status(1, "No Event Type");
+ }
+
+ // The event factory is responsible for configuring the event types.
+ auto status = event_pub->addSubscription(subscription);
+ event_pub->configure();
+ return status;
+}
+
+Status EventFactory::addSubscription(EventPublisherID type_id,
+ const SubscriptionContextRef mc,
+ EventCallback cb) {
+ auto subscription = Subscription::create(mc, cb);
+ return EventFactory::addSubscription(type_id, subscription);
+}
+
+size_t EventFactory::numSubscriptions(EventPublisherID type_id) {
+ const auto& event_pub =
+ EventFactory::getInstance().getEventPublisher(type_id);
+ if (event_pub != nullptr) {
+ return event_pub->numSubscriptions();
+ }
+ return 0;
+}
+
+std::shared_ptr<EventPublisher> EventFactory::getEventPublisher(
+ EventPublisherID type_id) {
+ auto& ef = EventFactory::getInstance();
+ const auto& it = ef.event_pubs_.find(type_id);
+ if (it != ef.event_pubs_.end()) {
+ return ef.event_pubs_[type_id];
+ }
+ return nullptr;
+}
+
+Status EventFactory::deregisterEventPublisher(
+ const EventPublisherRef event_pub) {
+ return EventFactory::deregisterEventPublisher(event_pub->type());
+}
+
+Status EventFactory::deregisterEventPublisher(EventPublisherID type_id) {
+ auto& ef = EventFactory::getInstance();
+ const auto& it = ef.event_pubs_.find(type_id);
+ if (it == ef.event_pubs_.end()) {
+ return Status(1, "No Event Type registered");
+ }
+
+ ef.event_pubs_[type_id]->tearDown();
+ ef.event_pubs_.erase(it);
+ return Status(0, "OK");
+}
+
+Status EventFactory::deregisterEventPublishers() {
+ auto& ef = EventFactory::getInstance();
+ auto it = ef.event_pubs_.begin();
+ for (; it != ef.event_pubs_.end(); it++) {
+ it->second->tearDown();
+ }
+
+ ef.event_pubs_.erase(ef.event_pubs_.begin(), ef.event_pubs_.end());
+ return Status(0, "OK");
+}
+}
+
+namespace osquery {
+namespace registries {
+void faucet(EventPublishers ets, EventSubscribers ems) {
+ auto& ef = osquery::EventFactory::getInstance();
+ for (const auto& event_pub : ets) {
+ ef.registerEventPublisher(event_pub.second);
+ }
+
+ for (const auto& event_module : ems) {
+ ef.registerEventSubscriber(event_module.second);
+ }
+}
+}
+}
--- /dev/null
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include <boost/filesystem/operations.hpp>
+
+#include <gtest/gtest.h>
+
+#include "osquery/events.h"
+#include "osquery/core/test_util.h"
+
+const std::string kTestingEventsDBPath = "/tmp/rocksdb-osquery-testevents";
+
+namespace osquery {
+
+class EventsDatabaseTests : public ::testing::Test {
+ public:
+ void SetUp() {
+ // Setup a testing DB instance
+ DBHandle::getInstanceAtPath(kTestingEventsDBPath);
+ }
+};
+
+class FakeEventSubscriber : public EventSubscriber {
+ DECLARE_EVENTSUBSCRIBER(FakeEventSubscriber, FakeEventPublisher);
+
+ public:
+ Status testAdd(int i) {
+ Row r;
+ r["testing"] = "hello from space";
+ return add(r, i);
+ }
+};
+
+class FakeEventPublisher : public EventPublisher {
+ DECLARE_EVENTPUBLISHER(FakeEventPublisher, SubscriptionContext, EventContext);
+};
+
+class AnotherFakeEventSubscriber : public EventSubscriber {
+ DECLARE_EVENTSUBSCRIBER(AnotherFakeEventSubscriber, FakeEventPublisher);
+};
+
+TEST_F(EventsDatabaseTests, test_event_module_id) {
+ auto fake_event_module = FakeEventSubscriber::getInstance();
+ // Not normally available outside of EventSubscriber->Add().
+ auto event_id1 = fake_event_module->getEventID();
+ EXPECT_EQ(event_id1, "1");
+ auto event_id2 = fake_event_module->getEventID();
+ EXPECT_EQ(event_id2, "2");
+}
+
+TEST_F(EventsDatabaseTests, test_unique_event_module_id) {
+ auto fake_event_module = FakeEventSubscriber::getInstance();
+ auto another_fake_event_module = AnotherFakeEventSubscriber::getInstance();
+ // Not normally available outside of EventSubscriber->Add().
+ auto event_id1 = fake_event_module->getEventID();
+ EXPECT_EQ(event_id1, "3");
+ auto event_id2 = another_fake_event_module->getEventID();
+ EXPECT_EQ(event_id2, "1");
+}
+
+TEST_F(EventsDatabaseTests, test_event_add) {
+ Row r;
+ r["testing"] = std::string("hello from space");
+ size_t event_time = 10;
+
+ auto fake_event_module = FakeEventSubscriber::getInstance();
+ auto status = fake_event_module->testAdd(1);
+ EXPECT_TRUE(status.ok());
+}
+}
+
+int main(int argc, char* argv[]) {
+ testing::InitGoogleTest(&argc, argv);
+ int status = RUN_ALL_TESTS();
+ boost::filesystem::remove_all(kTestingEventsDBPath);
+ return status;
+}
--- /dev/null
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include "osquery/events.h"
+
+#include <gtest/gtest.h>
+
+namespace osquery {
+
+class EventsTests : public testing::Test {
+ public:
+ void TearDown() { EventFactory::deregisterEventPublishers(); }
+};
+
+class BasicEventPublisher : public EventPublisher {
+ DECLARE_EVENTPUBLISHER(BasicEventPublisher,
+ SubscriptionContext,
+ EventContext);
+};
+
+class FakeBasicEventPublisher : public EventPublisher {
+ DECLARE_EVENTPUBLISHER(FakeBasicEventPublisher,
+ SubscriptionContext,
+ EventContext);
+};
+
+TEST_F(EventsTests, test_register_event_pub) {
+ Status status;
+
+ // A caller may register an event type using the class template.
+ status = EventFactory::registerEventPublisher<BasicEventPublisher>();
+ EXPECT_TRUE(status.ok());
+
+ // May also register the event_pub instance
+ auto event_pub_instance = std::make_shared<FakeBasicEventPublisher>();
+ status = EventFactory::registerEventPublisher(event_pub_instance);
+ EXPECT_TRUE(status.ok());
+
+ // May NOT register without subclassing, enforced at compile time.
+}
+
+TEST_F(EventsTests, test_create_event_pub) {
+ Status status;
+
+ status = EventFactory::registerEventPublisher<BasicEventPublisher>();
+ EXPECT_TRUE(status.ok());
+
+ // Do not register the same event type twice.
+ status = EventFactory::registerEventPublisher<BasicEventPublisher>();
+ EXPECT_FALSE(status.ok());
+
+ // Make sure only the first event type was recorded.
+ EXPECT_EQ(EventFactory::numEventPublishers(), 1);
+}
+
+TEST_F(EventsTests, test_create_subscription) {
+ Status status;
+
+ EventFactory::registerEventPublisher<BasicEventPublisher>();
+
+ // Make sure a subscription cannot be added for a non-existent event type.
+ // Note: It normally would not make sense to create a blank subscription.
+ auto subscription = Subscription::create();
+ status =
+ EventFactory::addSubscription("FakeBasicEventPublisher", subscription);
+ EXPECT_FALSE(status.ok());
+
+ // In this case we can still add a blank subscription to an existing event
+ // type.
+ status = EventFactory::addSubscription("BasicEventPublisher", subscription);
+ EXPECT_TRUE(status.ok());
+
+ // Make sure the subscription is added.
+ EXPECT_EQ(EventFactory::numSubscriptions("BasicEventPublisher"), 1);
+}
+
+TEST_F(EventsTests, test_multiple_subscriptions) {
+ Status status;
+
+ EventFactory::registerEventPublisher<BasicEventPublisher>();
+
+ auto subscription = Subscription::create();
+ status = EventFactory::addSubscription("BasicEventPublisher", subscription);
+ status = EventFactory::addSubscription("BasicEventPublisher", subscription);
+
+ EXPECT_EQ(EventFactory::numSubscriptions("BasicEventPublisher"), 2);
+}
+
+struct TestSubscriptionContext : public SubscriptionContext {
+ int smallest;
+};
+
+class TestEventPublisher : public EventPublisher {
+ DECLARE_EVENTPUBLISHER(TestEventPublisher,
+ TestSubscriptionContext,
+ EventContext);
+
+ public:
+ void setUp() { smallest_ever_ += 1; }
+
+ void configure() {
+ int smallest_subscription = smallest_ever_;
+
+ configure_run = true;
+ for (const auto& subscription : subscriptions_) {
+ auto subscription_context = getSubscriptionContext(subscription->context);
+ if (smallest_subscription > subscription_context->smallest) {
+ smallest_subscription = subscription_context->smallest;
+ }
+ }
+
+ smallest_ever_ = smallest_subscription;
+ }
+
+ void tearDown() { smallest_ever_ += 1; }
+
+ TestEventPublisher() : EventPublisher() {
+ smallest_ever_ = 0;
+ configure_run = false;
+ }
+
+ // Custom methods do not make sense, but for testing it exists.
+ int getTestValue() { return smallest_ever_; }
+
+ public:
+ bool configure_run;
+
+ private:
+ int smallest_ever_;
+};
+
+TEST_F(EventsTests, test_create_custom_event_pub) {
+ Status status;
+
+ status = EventFactory::registerEventPublisher<BasicEventPublisher>();
+ auto test_event_pub = std::make_shared<TestEventPublisher>();
+ status = EventFactory::registerEventPublisher(test_event_pub);
+
+ // These event types have unique event type IDs
+ EXPECT_TRUE(status.ok());
+ EXPECT_EQ(EventFactory::numEventPublishers(), 2);
+
+ // Make sure the setUp function was called.
+ EXPECT_EQ(test_event_pub->getTestValue(), 1);
+}
+
+TEST_F(EventsTests, test_custom_subscription) {
+ Status status;
+
+ // Step 1, register event type
+ auto event_pub = std::make_shared<TestEventPublisher>();
+ status = EventFactory::registerEventPublisher(event_pub);
+
+ // Step 2, create and configure a subscription context
+ auto subscription_context = std::make_shared<TestSubscriptionContext>();
+ subscription_context->smallest = -1;
+
+ // Step 3, add the subscription to the event type
+ status =
+ EventFactory::addSubscription("TestEventPublisher", subscription_context);
+ EXPECT_TRUE(status.ok());
+ EXPECT_EQ(event_pub->numSubscriptions(), 1);
+
+ // The event type must run configure for each added subscription.
+ EXPECT_TRUE(event_pub->configure_run);
+ EXPECT_EQ(event_pub->getTestValue(), -1);
+}
+
+TEST_F(EventsTests, test_tear_down) {
+ Status status;
+
+ auto event_pub = std::make_shared<TestEventPublisher>();
+ status = EventFactory::registerEventPublisher(event_pub);
+
+ // Make sure set up incremented the test value.
+ EXPECT_EQ(event_pub->getTestValue(), 1);
+
+ status = EventFactory::deregisterEventPublisher("TestEventPublisher");
+ EXPECT_TRUE(status.ok());
+
+ // Make sure tear down inremented the test value.
+ EXPECT_EQ(event_pub->getTestValue(), 2);
+
+ // Once more, now deregistering all event types.
+ status = EventFactory::registerEventPublisher(event_pub);
+ EXPECT_EQ(event_pub->getTestValue(), 3);
+
+ status = EventFactory::deregisterEventPublishers();
+ EXPECT_TRUE(status.ok());
+
+ EXPECT_EQ(event_pub->getTestValue(), 4);
+
+ // Make sure the factory state represented.
+ EXPECT_EQ(EventFactory::numEventPublishers(), 0);
+}
+
+static int kBellHathTolled = 0;
+
+Status TestTheeCallback(EventContextRef context, bool reserved) {
+ kBellHathTolled += 1;
+ return Status(0, "OK");
+}
+
+TEST_F(EventsTests, test_fire_event) {
+ Status status;
+
+ auto event_pub = std::make_shared<BasicEventPublisher>();
+ status = EventFactory::registerEventPublisher(event_pub);
+
+ auto subscription = Subscription::create();
+ subscription->callback = TestTheeCallback;
+ status = EventFactory::addSubscription("BasicEventPublisher", subscription);
+
+ // The event context creation would normally happen in the event type.
+ auto ec = event_pub->createEventContext();
+ event_pub->fire(ec, 0);
+ EXPECT_EQ(kBellHathTolled, 1);
+
+ auto second_subscription = Subscription::create();
+ status =
+ EventFactory::addSubscription("BasicEventPublisher", second_subscription);
+
+ // Now there are two subscriptions (one sans callback).
+ event_pub->fire(ec, 0);
+ EXPECT_EQ(kBellHathTolled, 2);
+
+ // Now both subscriptions have callbacks.
+ second_subscription->callback = TestTheeCallback;
+ event_pub->fire(ec, 0);
+ EXPECT_EQ(kBellHathTolled, 4);
+}
+}
+
+int main(int argc, char* argv[]) {
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
--- /dev/null
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include <sstream>
+
+#include <linux/limits.h>
+
+#include "osquery/events.h"
+#include "osquery/filesystem.h"
+#include "osquery/events/linux/inotify.h"
+
+#include <glog/logging.h>
+
+namespace osquery {
+
+REGISTER_EVENTPUBLISHER(INotifyEventPublisher)
+
+int kINotifyULatency = 200;
+static const uint32_t BUFFER_SIZE =
+ (10 * ((sizeof(struct inotify_event)) + NAME_MAX + 1));
+
+std::map<int, std::string> kMaskActions = {{IN_ACCESS, "ACCESSED"},
+ {IN_ATTRIB, "ATTRIBUTES_MODIFIED"},
+ {IN_CLOSE_WRITE, "UPDATED"},
+ {IN_CREATE, "CREATED"},
+ {IN_DELETE, "DELETED"},
+ {IN_MODIFY, "UPDATED"},
+ {IN_MOVED_FROM, "MOVED_FROM"},
+ {IN_MOVED_TO, "MOVED_TO"},
+ {IN_OPEN, "OPENED"}, };
+
+void INotifyEventPublisher::setUp() {
+ inotify_handle_ = ::inotify_init();
+ // If this does not work throw an exception.
+ if (inotify_handle_ == -1) {
+ // Todo: throw exception and DO NOT register this eventtype.
+ }
+}
+
+void INotifyEventPublisher::configure() {
+ for (const auto& sub : subscriptions_) {
+ // Anytime a configure is called, try to monitor all subscriptions.
+ // Configure is called as a response to removing/adding subscriptions.
+ // This means recalculating all monitored paths.
+ auto sc = getSubscriptionContext(sub->context);
+ addMonitor(sc->path, sc->recursive);
+ }
+}
+
+void INotifyEventPublisher::tearDown() {
+ ::close(inotify_handle_);
+ inotify_handle_ = -1;
+}
+
+Status INotifyEventPublisher::run() {
+ // Get a while wraper for free.
+ char buffer[BUFFER_SIZE];
+ fd_set set;
+
+ FD_ZERO(&set);
+ FD_SET(getHandle(), &set);
+
+ struct timeval timeout = {0, kINotifyULatency};
+ int selector = ::select(getHandle() + 1, &set, nullptr, nullptr, &timeout);
+ if (selector == -1) {
+ LOG(ERROR) << "Could not read inotify handle";
+ return Status(1, "INotify handle failed");
+ }
+
+ if (selector == 0) {
+ // Read timeout.
+ return Status(0, "Continue");
+ }
+ ssize_t record_num = ::read(getHandle(), buffer, BUFFER_SIZE);
+ if (record_num == 0 || record_num == -1) {
+ return Status(1, "INotify read failed");
+ }
+
+ for (char* p = buffer; p < buffer + record_num;) {
+ // Cast the inotify struct, make shared pointer, and append to contexts.
+ auto event = reinterpret_cast<struct inotify_event*>(p);
+ if (event->mask & IN_Q_OVERFLOW) {
+ // The inotify queue was overflown (remove all paths).
+ return Status(1, "Overflow");
+ }
+
+ if (event->mask & IN_IGNORED) {
+ // This inotify watch was removed.
+ removeMonitor(event->wd, false);
+ } else if (event->mask & IN_MOVE_SELF) {
+ // This inotify path was moved, but is still watched.
+ removeMonitor(event->wd, true);
+ } else if (event->mask & IN_DELETE_SELF) {
+ // A file was moved to replace the watched path.
+ removeMonitor(event->wd, false);
+ } else {
+ auto ec = createEventContext(event);
+ fire(ec);
+ }
+ // Continue to iterate
+ p += (sizeof(struct inotify_event)) + event->len;
+ }
+
+ ::usleep(kINotifyULatency);
+ return Status(0, "Continue");
+}
+
+INotifyEventContextRef INotifyEventPublisher::createEventContext(
+ struct inotify_event* event) {
+ auto shared_event = std::make_shared<struct inotify_event>(*event);
+ auto ec = createEventContext();
+ ec->event = shared_event;
+
+ // Get the pathname the watch fired on.
+ std::ostringstream path;
+ path << descriptor_paths_[event->wd];
+ if (event->len > 1) {
+ path << "/" << event->name;
+ }
+ ec->path = path.str();
+
+ // Set the action (may be multiple)
+ for (const auto& action : kMaskActions) {
+ if (event->mask & action.first) {
+ ec->action = action.second;
+ break;
+ }
+ }
+ return ec;
+}
+
+bool INotifyEventPublisher::shouldFire(const INotifySubscriptionContextRef sc,
+ const INotifyEventContextRef ec) {
+ if (!sc->recursive && sc->path != ec->path) {
+ // Monitored path is not recursive and path is not an exact match.
+ return false;
+ }
+
+ if (ec->path.find(sc->path) != 0) {
+ // The path does not exist as the base event path.
+ return false;
+ }
+
+ // The subscription may supply a required event mask.
+ if (sc->mask != 0 && !(ec->event->mask & sc->mask)) {
+ return false;
+ }
+ return true;
+}
+
+bool INotifyEventPublisher::addMonitor(const std::string& path,
+ bool recursive) {
+ if (!isPathMonitored(path)) {
+ int watch = ::inotify_add_watch(getHandle(), path.c_str(), IN_ALL_EVENTS);
+ if (watch == -1) {
+ LOG(ERROR) << "Could not add inotfy watch on: " << path;
+ return false;
+ }
+
+ // Keep a list of the watch descriptors
+ descriptors_.push_back(watch);
+ // Keep a map of the path -> watch descriptor
+ path_descriptors_[path] = watch;
+ // Keep a map of the opposite (descriptor -> path)
+ descriptor_paths_[watch] = path;
+ }
+
+ if (recursive && isDirectory(path).ok()) {
+ std::vector<std::string> children;
+ // Get a list of children of this directory (requesed recursive watches).
+ if (!listFilesInDirectory(path, children).ok()) {
+ return false;
+ }
+
+ for (const auto& child : children) {
+ // Only watch child directories, a watch on the directory implies files.
+ if (isDirectory(child).ok()) {
+ addMonitor(child, recursive);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool INotifyEventPublisher::removeMonitor(const std::string& path, bool force) {
+ // If force then remove from INotify, otherwise cleanup file descriptors.
+ if (path_descriptors_.find(path) == path_descriptors_.end()) {
+ return false;
+ }
+
+ int 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);
+ }
+ return true;
+}
+
+bool INotifyEventPublisher::removeMonitor(int watch, bool force) {
+ if (descriptor_paths_.find(watch) == descriptor_paths_.end()) {
+ return false;
+ }
+
+ std::string path = descriptor_paths_[watch];
+ return removeMonitor(path, force);
+}
+
+bool INotifyEventPublisher::isPathMonitored(const std::string& path) {
+ std::string parent_path;
+ if (!isDirectory(path).ok()) {
+ if (path_descriptors_.find(path) != path_descriptors_.end()) {
+ // Path is a file, and is directly monitored.
+ return true;
+ }
+ if (!getDirectory(path, parent_path).ok()) {
+ // Could not get parent of unmonitored file.
+ return false;
+ }
+ } else {
+ parent_path = path;
+ }
+
+ // Directory or parent of file monitoring
+ return (path_descriptors_.find(parent_path) != path_descriptors_.end());
+}
+}
--- /dev/null
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <boost/make_shared.hpp>
+
+#include <sys/inotify.h>
+#include <sys/stat.h>
+
+#include "osquery/status.h"
+#include "osquery/events.h"
+
+namespace osquery {
+
+extern std::map<int, std::string> kMaskActions;
+
+/**
+ * @brief Subscriptioning details for INotifyEventPublisher events.
+ *
+ * This context is specific to INotifyEventPublisher. It allows the
+ *subscriptioning
+ * EventSubscriber to set a path (file or directory) and a limited action mask.
+ * Events are passed to the subscriptioning EventSubscriber if they match the
+ *context
+ * path (or anything within a directory if the path is a directory) and if the
+ * event action is part of the mask. If the mask is 0 then all actions are
+ * passed to the EventSubscriber.
+ */
+struct INotifySubscriptionContext : public SubscriptionContext {
+ /// Subscription the following filesystem path.
+ std::string path;
+ /// Limit the `inotify` actions to the subscriptioned mask (if not 0).
+ uint32_t mask;
+ /// Treat this path as a directory and subscription recursively.
+ bool recursive;
+
+ INotifySubscriptionContext() : mask(0), recursive(false) {}
+
+ /**
+ * @brief Helper method to map a string action to `inotify` action mask bit.
+ *
+ * This helper method will set the `mask` value for this SubscriptionContext.
+ *
+ * @param action The string action, a value in kMaskAction%s.
+ */
+ void requireAction(std::string action) {
+ for (const auto& bit : kMaskActions) {
+ if (action == bit.second) {
+ mask = mask | bit.first;
+ }
+ }
+ }
+};
+
+/**
+ * @brief Event details for INotifyEventPublisher events.
+ */
+struct INotifyEventContext : public EventContext {
+ /// The inotify_event structure if the EventSubscriber want to interact.
+ std::shared_ptr<struct inotify_event> event;
+ /// A string path parsed from the inotify_event.
+ std::string path;
+ /// A string action representing the event action `inotify` bit.
+ std::string action;
+};
+
+typedef std::shared_ptr<INotifyEventContext> INotifyEventContextRef;
+typedef std::shared_ptr<INotifySubscriptionContext>
+INotifySubscriptionContextRef;
+
+// Thread-safe containers
+typedef std::vector<int> DescriptorVector;
+typedef std::map<std::string, int> PathDescriptorMap;
+typedef std::map<int, std::string> DescriptorPathMap;
+
+/**
+ * @brief A Linux `inotify` EventPublisher.
+ *
+ * This EventPublisher allows EventSubscriber%s to subscription for Linux
+ *`inotify` events.
+ * Since these events are limited this EventPublisher will optimize the watch
+ * descriptors, keep track of the usage, implement optimizations/priority
+ * where possible, and abstract file system events to a path/action context.
+ *
+ * Uses INotifySubscriptionContext and INotifyEventContext for subscriptioning,
+ *eventing.
+ */
+class INotifyEventPublisher : public EventPublisher {
+ DECLARE_EVENTPUBLISHER(INotifyEventPublisher,
+ INotifySubscriptionContext,
+ INotifyEventContext);
+
+ public:
+ /// Create an `inotify` handle descriptor.
+ void setUp();
+ void configure();
+ /// Release the `inotify` handle descriptor.
+ void tearDown();
+
+ Status run();
+
+ INotifyEventPublisher() : EventPublisher() { inotify_handle_ = -1; }
+ /// Check if the application-global `inotify` handle is alive.
+ bool isHandleOpen() { return inotify_handle_ > 0; }
+
+ private:
+ INotifyEventContextRef createEventContext(struct inotify_event* event);
+ /// Check all added Subscription%s for a path.
+ bool isPathMonitored(const std::string& path);
+ /// Add an INotify watch (monitor) on this path.
+ bool addMonitor(const std::string& path, bool recursive);
+ /// Remove an INotify watch (monitor) from our tracking.
+ bool removeMonitor(const std::string& path, bool force = false);
+ bool removeMonitor(int watch, bool force = false);
+ /// Given a SubscriptionContext and INotifyEventContext match path and action.
+ bool shouldFire(const INotifySubscriptionContextRef mc,
+ const INotifyEventContextRef ec);
+ /// Get the INotify file descriptor.
+ int getHandle() { return inotify_handle_; }
+ /// Get the number of actual INotify active descriptors.
+ int numDescriptors() { return descriptors_.size(); }
+
+ // Consider an event queue if separating buffering from firing/servicing.
+ DescriptorVector descriptors_;
+ PathDescriptorMap path_descriptors_;
+ DescriptorPathMap descriptor_paths_;
+ int inotify_handle_;
+
+ public:
+ FRIEND_TEST(INotifyTests, test_inotify_optimization);
+};
+}
--- /dev/null
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include <stdio.h>
+
+#include <boost/filesystem/operations.hpp>
+#include <boost/filesystem/path.hpp>
+#include <boost/thread.hpp>
+
+#include <gtest/gtest.h>
+
+#include "osquery/events.h"
+#include "osquery/events/linux/inotify.h"
+#include "osquery/filesystem.h"
+
+namespace osquery {
+
+const std::string kRealTestPath = "/tmp/osquery-inotify-trigger";
+const std::string kRealTestDir = "/tmp/osquery-inotify-triggers";
+const std::string kRealTestDirPath = "/tmp/osquery-inotify-triggers/1";
+const std::string kRealTestSubDir = "/tmp/osquery-inotify-triggers/2";
+const std::string kRealTestSubDirPath = "/tmp/osquery-inotify-triggers/2/1";
+
+int kMaxEventLatency = 3000;
+
+class INotifyTests : public testing::Test {
+ protected:
+ void TearDown() {
+ EventFactory::deregisterEventPublishers();
+ boost::filesystem::remove_all(kRealTestPath);
+ boost::filesystem::remove_all(kRealTestDir);
+ }
+
+ void StartEventLoop() {
+ event_pub_ = std::make_shared<INotifyEventPublisher>();
+ EventFactory::registerEventPublisher(event_pub_);
+ FILE* fd = fopen(kRealTestPath.c_str(), "w");
+ fclose(fd);
+
+ temp_thread_ = boost::thread(EventFactory::run, "INotifyEventPublisher");
+ }
+
+ void SubscriptionAction(const std::string& path,
+ uint32_t mask = 0,
+ EventCallback ec = 0) {
+ auto mc = std::make_shared<INotifySubscriptionContext>();
+ mc->path = path;
+ mc->mask = mask;
+
+ EventFactory::addSubscription("INotifyEventPublisher", mc, ec);
+ }
+
+ bool WaitForEvents(int max, int num_events = 0) {
+ int delay = 0;
+ while (delay <= max * 1000) {
+ if (num_events > 0 && event_pub_->numEvents() >= num_events) {
+ return true;
+ } else if (num_events == 0 && event_pub_->numEvents() > 0) {
+ return true;
+ }
+ delay += 50;
+ ::usleep(50);
+ }
+ return false;
+ }
+
+ void TriggerEvent(const std::string& path) {
+ FILE* fd = fopen(path.c_str(), "w");
+ fputs("inotify", fd);
+ fclose(fd);
+ }
+
+ void EndEventLoop() {
+ EventFactory::end();
+ event_pub_->tearDown();
+ temp_thread_.join();
+ EventFactory::end(false);
+ }
+
+ std::shared_ptr<INotifyEventPublisher> event_pub_;
+ boost::thread temp_thread_;
+};
+
+TEST_F(INotifyTests, test_register_event_pub) {
+ auto status = EventFactory::registerEventPublisher<INotifyEventPublisher>();
+ EXPECT_TRUE(status.ok());
+
+ // Make sure only one event type exists
+ EXPECT_EQ(EventFactory::numEventPublishers(), 1);
+}
+
+TEST_F(INotifyTests, test_inotify_init) {
+ // Handle should not be initialized during ctor.
+ auto event_pub = std::make_shared<INotifyEventPublisher>();
+ EXPECT_FALSE(event_pub->isHandleOpen());
+
+ // Registering the event type initializes inotify.
+ EventFactory::registerEventPublisher(event_pub);
+ EXPECT_TRUE(event_pub->isHandleOpen());
+
+ // Similarly deregistering closes the handle.
+ EventFactory::deregisterEventPublishers();
+ EXPECT_FALSE(event_pub->isHandleOpen());
+}
+
+TEST_F(INotifyTests, test_inotify_add_subscription_missing_path) {
+ EventFactory::registerEventPublisher<INotifyEventPublisher>();
+
+ // This subscription path is fake, and will succeed.
+ auto mc = std::make_shared<INotifySubscriptionContext>();
+ mc->path = "/this/path/is/fake";
+
+ auto subscription = Subscription::create(mc);
+ auto status =
+ EventFactory::addSubscription("INotifyEventPublisher", subscription);
+ EXPECT_TRUE(status.ok());
+}
+
+TEST_F(INotifyTests, test_inotify_add_subscription_success) {
+ EventFactory::registerEventPublisher<INotifyEventPublisher>();
+
+ // This subscription path *should* be real.
+ auto mc = std::make_shared<INotifySubscriptionContext>();
+ mc->path = "/";
+
+ auto subscription = Subscription::create(mc);
+ auto status =
+ EventFactory::addSubscription("INotifyEventPublisher", subscription);
+ EXPECT_TRUE(status.ok());
+}
+
+TEST_F(INotifyTests, test_inotify_run) {
+ // Assume event type is registered.
+ event_pub_ = std::make_shared<INotifyEventPublisher>();
+ EventFactory::registerEventPublisher(event_pub_);
+
+ // Create a temporary file to watch, open writeable
+ FILE* fd = fopen(kRealTestPath.c_str(), "w");
+
+ // Create a subscriptioning context
+ auto mc = std::make_shared<INotifySubscriptionContext>();
+ mc->path = kRealTestPath;
+ EventFactory::addSubscription("INotifyEventPublisher",
+ Subscription::create(mc));
+
+ // Create an event loop thread (similar to main)
+ boost::thread temp_thread(EventFactory::run, "INotifyEventPublisher");
+ EXPECT_TRUE(event_pub_->numEvents() == 0);
+
+ // Cause an inotify event by writing to the watched path.
+ TriggerEvent(kRealTestPath);
+
+// TBD: Prevous opened fd cannot caught event.
+// fputs("inotify", fd);
+// fclose(fd);
+
+ // Wait for the thread's run loop to select.
+ WaitForEvents(kMaxEventLatency);
+ EXPECT_TRUE(event_pub_->numEvents() > 0);
+
+ // Cause the thread to tear down.
+ EventFactory::end();
+ temp_thread.join();
+ // Reset the event factory state.
+ EventFactory::end(false);
+}
+
+class TestINotifyEventSubscriber : public EventSubscriber {
+ DECLARE_EVENTSUBSCRIBER(TestINotifyEventSubscriber, INotifyEventPublisher);
+ DECLARE_CALLBACK(SimpleCallback, INotifyEventContext);
+ DECLARE_CALLBACK(Callback, INotifyEventContext);
+
+ public:
+ void init() { callback_count_ = 0; }
+ Status SimpleCallback(const INotifyEventContextRef ec) {
+ callback_count_ += 1;
+ return Status(0, "OK");
+ }
+
+ Status Callback(const INotifyEventContextRef ec) {
+ Row r;
+ r["action"] = ec->action;
+ r["path"] = ec->path;
+
+ // Normally would call Add here.
+ actions_.push_back(ec->action);
+ callback_count_ += 1;
+ return Status(0, "OK");
+ }
+
+ static void WaitForEvents(int max, int num_events = 1) {
+ int delay = 0;
+ while (delay < max * 1000) {
+ if (getInstance()->callback_count_ >= num_events) {
+ return;
+ }
+ ::usleep(50);
+ delay += 50;
+ }
+ }
+
+ static std::vector<std::string> actions() { return getInstance()->actions_; }
+
+ static int count() { return getInstance()->callback_count_; }
+
+ public:
+ int callback_count_;
+ std::vector<std::string> actions_;
+};
+
+TEST_F(INotifyTests, test_inotify_fire_event) {
+ // Assume event type is registered.
+ StartEventLoop();
+ TestINotifyEventSubscriber::getInstance()->init();
+
+ // Create a subscriptioning context, note the added Event to the symbol
+ SubscriptionAction(
+ kRealTestPath, 0, TestINotifyEventSubscriber::EventSimpleCallback);
+ TriggerEvent(kRealTestPath);
+
+ TestINotifyEventSubscriber::WaitForEvents(kMaxEventLatency);
+
+ // Make sure our expected event fired (aka subscription callback was called).
+ EXPECT_TRUE(TestINotifyEventSubscriber::count() > 0);
+
+ // Cause the thread to tear down.
+ EndEventLoop();
+}
+
+TEST_F(INotifyTests, test_inotify_event_action) {
+ // Assume event type is registered.
+ StartEventLoop();
+ TestINotifyEventSubscriber::getInstance()->init();
+
+ SubscriptionAction(
+ kRealTestPath, 0, TestINotifyEventSubscriber::EventCallback);
+ TriggerEvent(kRealTestPath);
+
+ TestINotifyEventSubscriber::WaitForEvents(kMaxEventLatency, 1);
+
+ // Make sure the inotify action was expected.
+ EXPECT_EQ(TestINotifyEventSubscriber::actions().size(), 1);
+ EXPECT_EQ(TestINotifyEventSubscriber::actions()[0], "UPDATED");
+
+ // Cause the thread to tear down.
+ EndEventLoop();
+}
+
+TEST_F(INotifyTests, test_inotify_optimization) {
+ // Assume event type is registered.
+ StartEventLoop();
+
+ boost::filesystem::create_directory(kRealTestDir);
+
+ // Adding a descriptor to a directory will monitor files within.
+ SubscriptionAction(kRealTestDir);
+ EXPECT_TRUE(event_pub_->isPathMonitored(kRealTestDirPath));
+
+ // Adding a subscription to a file within a monitored directory is fine
+ // but this will NOT cause an additional INotify watch.
+ SubscriptionAction(kRealTestDirPath);
+ EXPECT_EQ(event_pub_->numDescriptors(), 1);
+
+ // Cause the thread to tear down.
+ EndEventLoop();
+}
+
+TEST_F(INotifyTests, test_inotify_recursion) {
+ StartEventLoop();
+ TestINotifyEventSubscriber::getInstance()->init();
+
+ boost::filesystem::create_directory(kRealTestDir);
+ boost::filesystem::create_directory(kRealTestSubDir);
+
+ // Subscribe to the directory inode
+ auto mc = std::make_shared<INotifySubscriptionContext>();
+ mc->path = kRealTestDir;
+ mc->recursive = true;
+
+ EventFactory::addSubscription(
+ "INotifyEventPublisher", mc, TestINotifyEventSubscriber::EventCallback);
+ // Trigger on a subdirectory's file.
+ TriggerEvent(kRealTestSubDirPath);
+
+ TestINotifyEventSubscriber::WaitForEvents(kMaxEventLatency, 1);
+ EXPECT_TRUE(TestINotifyEventSubscriber::count() > 0);
+
+ EndEventLoop();
+}
+}
+
+int main(int argc, char* argv[]) {
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
%{_bindir}/osquery_logger_tests
%{_bindir}/osquery_conversions_tests
%{_bindir}/osquery_dispatcher_tests
+%{_bindir}/osquery_events_tests
+%{_bindir}/osquery_events_database_tests
+%{_bindir}/osquery_inotify_tests