Import event-fw from upstream
authorsangwan.kwon <sangwan.kwon@samsung.com>
Fri, 17 May 2019 05:39:58 +0000 (14:39 +0900)
committerSangwan Kwon <sangwan.kwon@samsung.com>
Wed, 12 Jun 2019 00:04:42 +0000 (09:04 +0900)
Signed-off-by: sangwan.kwon <sangwan.kwon@samsung.com>
13 files changed:
.gitignore
Makefile
include/osquery/database/db_handle.h
include/osquery/events.h [new file with mode: 0644]
osquery/CMakeLists.txt
osquery/events/CMakeLists.txt [new file with mode: 0644]
osquery/events/events.cpp [new file with mode: 0644]
osquery/events/events_database_tests.cpp [new file with mode: 0644]
osquery/events/events_tests.cpp [new file with mode: 0644]
osquery/events/linux/inotify.cpp [new file with mode: 0644]
osquery/events/linux/inotify.h [new file with mode: 0644]
osquery/events/linux/inotify_tests.cpp [new file with mode: 0644]
packaging/osquery.spec

index 2e7904a5d679d4125eb49026f36480d25fd5dd2f..42eb25cf4aea31301e0693b8e6ca15369479a98d 100644 (file)
@@ -9,3 +9,4 @@
 
 # Build
 build/*
+deps/*
index 0509e215b938b82c29421bc53aaaae09192a857f..2265a2d77a52eab596c137b8942d60f935d0c836 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,7 @@ clean:
 
 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
index f9c9896ae7229fc6c68f4cab665d7996727abb1b..f7c309eee42ab0598215b5c5db2b761de0f3d802 100644 (file)
@@ -247,6 +247,6 @@ class DBHandle {
 
   friend class DBHandleTests;
   friend class QueryTests;
-//  friend class EventsDatabaseTests;
+  friend class EventsDatabaseTests;
 };
 }
diff --git a/include/osquery/events.h b/include/osquery/events.h
new file mode 100644 (file)
index 0000000..d312e05
--- /dev/null
@@ -0,0 +1,777 @@
+// 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);
+}
+}
index 917965271fd6bd34c4bad064a3c6c55290ec3f1f..c21cad3d4edb4d2c5eb21f20cd0a715c7b2d3999 100644 (file)
@@ -55,9 +55,10 @@ ADD_SUBDIRECTORY(core)
 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})
diff --git a/osquery/events/CMakeLists.txt b/osquery/events/CMakeLists.txt
new file mode 100644 (file)
index 0000000..bbc4d78
--- /dev/null
@@ -0,0 +1,6 @@
+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)
diff --git a/osquery/events/events.cpp b/osquery/events/events.cpp
new file mode 100644 (file)
index 0000000..694337f
--- /dev/null
@@ -0,0 +1,387 @@
+// 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);
+  }
+}
+}
+}
diff --git a/osquery/events/events_database_tests.cpp b/osquery/events/events_database_tests.cpp
new file mode 100644 (file)
index 0000000..200cbdf
--- /dev/null
@@ -0,0 +1,76 @@
+// 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;
+}
diff --git a/osquery/events/events_tests.cpp b/osquery/events/events_tests.cpp
new file mode 100644 (file)
index 0000000..b6a876a
--- /dev/null
@@ -0,0 +1,236 @@
+// 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();
+}
diff --git a/osquery/events/linux/inotify.cpp b/osquery/events/linux/inotify.cpp
new file mode 100644 (file)
index 0000000..2d33117
--- /dev/null
@@ -0,0 +1,231 @@
+// 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());
+}
+}
diff --git a/osquery/events/linux/inotify.h b/osquery/events/linux/inotify.h
new file mode 100644 (file)
index 0000000..fa5dfea
--- /dev/null
@@ -0,0 +1,136 @@
+// 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);
+};
+}
diff --git a/osquery/events/linux/inotify_tests.cpp b/osquery/events/linux/inotify_tests.cpp
new file mode 100644 (file)
index 0000000..ef7f283
--- /dev/null
@@ -0,0 +1,294 @@
+// 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();
+}
index 9cb71d58a68453d78b14eb6d49e943f771ce2cb2..a9f9bea91500f7ae06526ba194a73fe589286ff8 100644 (file)
@@ -80,3 +80,6 @@ Testcases for osquery
 %{_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