From: Sangwan Kwon Date: Wed, 27 May 2020 06:05:47 +0000 (+0900) Subject: Apply formatting to osquery X-Git-Tag: submit/tizen/20200810.073515~10 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=f0253eb393bfb4881beb96b0d7e452b0f8bc8588;p=platform%2Fcore%2Fsecurity%2Fvist.git Apply formatting to osquery Change-Id: I5ccc0302d11102c1f03c4e78ffe1af9051232e15 Signed-off-by: Sangwan Kwon --- diff --git a/src/osquery/core/plugins/plugin.cpp b/src/osquery/core/plugins/plugin.cpp index a214b3d..afcc47e 100644 --- a/src/osquery/core/plugins/plugin.cpp +++ b/src/osquery/core/plugins/plugin.cpp @@ -10,21 +10,23 @@ namespace osquery { -void Plugin::setName(const std::string& name) { - if (!name_.empty() && name != name_) { - std::string error = "Cannot rename plugin " + name_ + " to " + name; - throw std::runtime_error(error); - } +void Plugin::setName(const std::string& name) +{ + if (!name_.empty() && name != name_) { + std::string error = "Cannot rename plugin " + name_ + " to " + name; + throw std::runtime_error(error); + } - name_ = name; + name_ = name; } -PluginResponse tableRowsToPluginResponse(const TableRows& rows) { - PluginResponse result; - for (const auto& row : rows) { - result.push_back(static_cast(*row)); - } - return result; +PluginResponse tableRowsToPluginResponse(const TableRows& rows) +{ + PluginResponse result; + for (const auto& row : rows) { + result.push_back(static_cast(*row)); + } + return result; } } // namespace osquery diff --git a/src/osquery/core/plugins/plugin.h b/src/osquery/core/plugins/plugin.h index 3ea4d8c..12e41b3 100644 --- a/src/osquery/core/plugins/plugin.h +++ b/src/osquery/core/plugins/plugin.h @@ -38,53 +38,56 @@ using PluginRequest = std::map; using PluginResponse = std::vector; class Plugin : private boost::noncopyable { - public: - virtual ~Plugin() = default; - - public: - /// The plugin may perform some initialization, not required. - virtual Status setUp() { - return Status::success(); - } - - /// The plugin may perform some tear down, release, not required. - virtual void tearDown() {} - - /// The plugin may react to configuration updates. - virtual void configure() {} - - /// The plugin may publish route info (other than registry type and name). - virtual PluginResponse routeInfo() const { - return PluginResponse(); - } - - /** - * @brief Plugins act by being called, using a request, returning a response. - * - * The plugin request is a thrift-serializable object. A response is optional - * but the API for using a plugin's call is defined by the registry. In most - * cases there are multiple supported call 'actions'. A registry type, or - * the plugin class, will define the action key and supported actions. - * - * @param request A plugin request input, including optional action. - * @param response A plugin response output. - * - * @return Status of the call, if the action was handled corrected. - */ - virtual Status call(const PluginRequest& request, - PluginResponse& response) = 0; - - /// Allow the plugin to introspect into the registered name (for logging). - virtual void setName(const std::string& name) final; - - /// Force call-sites to use #getName to access the plugin item's name. - virtual const std::string& getName() const { - return name_; - } - - protected: - /// Customized name for the plugin, usually set by the registry. - std::string name_; +public: + virtual ~Plugin() = default; + +public: + /// The plugin may perform some initialization, not required. + virtual Status setUp() + { + return Status::success(); + } + + /// The plugin may perform some tear down, release, not required. + virtual void tearDown() {} + + /// The plugin may react to configuration updates. + virtual void configure() {} + + /// The plugin may publish route info (other than registry type and name). + virtual PluginResponse routeInfo() const + { + return PluginResponse(); + } + + /** + * @brief Plugins act by being called, using a request, returning a response. + * + * The plugin request is a thrift-serializable object. A response is optional + * but the API for using a plugin's call is defined by the registry. In most + * cases there are multiple supported call 'actions'. A registry type, or + * the plugin class, will define the action key and supported actions. + * + * @param request A plugin request input, including optional action. + * @param response A plugin response output. + * + * @return Status of the call, if the action was handled corrected. + */ + virtual Status call(const PluginRequest& request, + PluginResponse& response) = 0; + + /// Allow the plugin to introspect into the registered name (for logging). + virtual void setName(const std::string& name) final; + + /// Force call-sites to use #getName to access the plugin item's name. + virtual const std::string& getName() const + { + return name_; + } + +protected: + /// Customized name for the plugin, usually set by the registry. + std::string name_; }; /// Helper definition for a shared pointer to a Plugin. diff --git a/src/osquery/core/plugins/sql.h b/src/osquery/core/plugins/sql.h index dc4d4ec..c39b9eb 100644 --- a/src/osquery/core/plugins/sql.h +++ b/src/osquery/core/plugins/sql.h @@ -26,37 +26,38 @@ namespace osquery { class SQLPlugin : public Plugin { - public: - /// Run a SQL query string against the SQL implementation. - virtual Status query(const std::string& query, - QueryData& results, - bool use_cache) const = 0; - - /// Use the SQL implementation to parse a query string and return details - /// (name, type) about the columns. - virtual Status getQueryColumns(const std::string& query, - TableColumns& columns) const = 0; - - /// Given a query, return the list of scanned tables. - virtual Status getQueryTables(const std::string& query, - std::vector& tables) const = 0; - - /** - * @brief Attach a table at runtime. - * - * The SQL implementation plugin may need to manage how virtual tables are - * attached at run time. In the case of SQLite where a single DB object is - * managed, tables are enumerated and attached during initialization. - */ - virtual Status attach(const std::string& /*name*/) { - return Status::success(); - } - - /// Tables may be detached by name. - virtual void detach(const std::string& /*name*/) {} - - public: - Status call(const PluginRequest& request, PluginResponse& response) override; +public: + /// Run a SQL query string against the SQL implementation. + virtual Status query(const std::string& query, + QueryData& results, + bool use_cache) const = 0; + + /// Use the SQL implementation to parse a query string and return details + /// (name, type) about the columns. + virtual Status getQueryColumns(const std::string& query, + TableColumns& columns) const = 0; + + /// Given a query, return the list of scanned tables. + virtual Status getQueryTables(const std::string& query, + std::vector& tables) const = 0; + + /** + * @brief Attach a table at runtime. + * + * The SQL implementation plugin may need to manage how virtual tables are + * attached at run time. In the case of SQLite where a single DB object is + * managed, tables are enumerated and attached during initialization. + */ + virtual Status attach(const std::string& /*name*/) + { + return Status::success(); + } + + /// Tables may be detached by name. + virtual void detach(const std::string& /*name*/) {} + +public: + Status call(const PluginRequest& request, PluginResponse& response) override; }; } // namespace osquery diff --git a/src/osquery/core/query.cpp b/src/osquery/core/query.cpp index a38c749..8231e30 100644 --- a/src/osquery/core/query.cpp +++ b/src/osquery/core/query.cpp @@ -17,90 +17,99 @@ namespace osquery { -uint64_t Query::getPreviousEpoch() const { - return 0; +uint64_t Query::getPreviousEpoch() const +{ + return 0; } -uint64_t Query::getQueryCounter(bool new_query) const { - return 0; +uint64_t Query::getQueryCounter(bool new_query) const +{ + return 0; } -Status Query::getPreviousQueryResults(QueryDataSet& results) const { - return Status::success(); +Status Query::getPreviousQueryResults(QueryDataSet& results) const +{ + return Status::success(); } -std::vector Query::getStoredQueryNames() { - std::vector results; - return results; +std::vector Query::getStoredQueryNames() +{ + std::vector results; + return results; } -bool Query::isQueryNameInDatabase() const { - auto names = Query::getStoredQueryNames(); - return std::find(names.begin(), names.end(), name_) != names.end(); +bool Query::isQueryNameInDatabase() const +{ + auto names = Query::getStoredQueryNames(); + return std::find(names.begin(), names.end(), name_) != names.end(); } static inline void saveQuery(const std::string& name, - const std::string& query) { + const std::string& query) +{ } -bool Query::isNewQuery() const { - return true; +bool Query::isNewQuery() const +{ + return true; } Status Query::addNewResults(QueryDataTyped qd, - const uint64_t epoch, - uint64_t& counter) const { - DiffResults dr; - return addNewResults(std::move(qd), epoch, counter, dr, false); + const uint64_t epoch, + uint64_t& counter) const +{ + DiffResults dr; + return addNewResults(std::move(qd), epoch, counter, dr, false); } Status Query::addNewResults(QueryDataTyped current_qd, - const uint64_t current_epoch, - uint64_t& counter, - DiffResults& dr, - bool calculate_diff) const { - // The current results are 'fresh' when not calculating a differential. - bool fresh_results = !calculate_diff; - bool new_query = false; - if (!isQueryNameInDatabase()) { - // This is the first encounter of the scheduled query. - fresh_results = true; - INFO(OSQUERY) << "Storing initial results for new scheduled query: " << name_; - saveQuery(name_, query_); - } else if (getPreviousEpoch() != current_epoch) { - fresh_results = true; - INFO(OSQUERY) << "New Epoch " << current_epoch << " for scheduled query " - << name_; - } else if (isNewQuery()) { - // This query is 'new' in that the previous results may be invalid. - new_query = true; - INFO(OSQUERY) << "Scheduled query has been updated: " + name_; - saveQuery(name_, query_); - } - - // Use a 'target' avoid copying the query data when serializing and saving. - // If a differential is requested and needed the target remains the original - // query data, otherwise the content is moved to the differential's added set. - const auto* target_gd = ¤t_qd; - bool update_db = true; - if (!fresh_results && calculate_diff) { - // Get the rows from the last run of this query name. - QueryDataSet previous_qd; - auto status = getPreviousQueryResults(previous_qd); - if (!status.ok()) { - return status; - } - - // Calculate the differential between previous and current query results. - dr = diff(previous_qd, current_qd); - - update_db = (!dr.added.empty() || !dr.removed.empty()); - } else { - dr.added = std::move(current_qd); - target_gd = &dr.added; - } - - return Status::success(); + const uint64_t current_epoch, + uint64_t& counter, + DiffResults& dr, + bool calculate_diff) const +{ + // The current results are 'fresh' when not calculating a differential. + bool fresh_results = !calculate_diff; + bool new_query = false; + if (!isQueryNameInDatabase()) { + // This is the first encounter of the scheduled query. + fresh_results = true; + INFO(OSQUERY) << "Storing initial results for new scheduled query: " << name_; + saveQuery(name_, query_); + } else if (getPreviousEpoch() != current_epoch) { + fresh_results = true; + INFO(OSQUERY) << "New Epoch " << current_epoch << " for scheduled query " + << name_; + } else if (isNewQuery()) { + // This query is 'new' in that the previous results may be invalid. + new_query = true; + INFO(OSQUERY) << "Scheduled query has been updated: " + name_; + saveQuery(name_, query_); + } + + // Use a 'target' avoid copying the query data when serializing and saving. + // If a differential is requested and needed the target remains the original + // query data, otherwise the content is moved to the differential's added set. + const auto* target_gd = ¤t_qd; + bool update_db = true; + if (!fresh_results && calculate_diff) { + // Get the rows from the last run of this query name. + QueryDataSet previous_qd; + auto status = getPreviousQueryResults(previous_qd); + if (!status.ok()) { + return status; + } + + // Calculate the differential between previous and current query results. + dr = diff(previous_qd, current_qd); + + update_db = (!dr.added.empty() || !dr.removed.empty()); + } else { + dr.added = std::move(current_qd); + target_gd = &dr.added; + } + + return Status::success(); } } diff --git a/src/osquery/core/sql/column.cpp b/src/osquery/core/sql/column.cpp index 78f469b..9c1d738 100644 --- a/src/osquery/core/sql/column.cpp +++ b/src/osquery/core/sql/column.cpp @@ -11,13 +11,13 @@ namespace osquery { const std::map kColumnTypeNames = { - {UNKNOWN_TYPE, "UNKNOWN"}, - {TEXT_TYPE, "TEXT"}, - {INTEGER_TYPE, "INTEGER"}, - {BIGINT_TYPE, "BIGINT"}, - {UNSIGNED_BIGINT_TYPE, "UNSIGNED BIGINT"}, - {DOUBLE_TYPE, "DOUBLE"}, - {BLOB_TYPE, "BLOB"}, + {UNKNOWN_TYPE, "UNKNOWN"}, + {TEXT_TYPE, "TEXT"}, + {INTEGER_TYPE, "INTEGER"}, + {BIGINT_TYPE, "BIGINT"}, + {UNSIGNED_BIGINT_TYPE, "UNSIGNED BIGINT"}, + {DOUBLE_TYPE, "DOUBLE"}, + {BLOB_TYPE, "BLOB"}, }; } diff --git a/src/osquery/core/sql/column.h b/src/osquery/core/sql/column.h index 8fcd965..4ebf1c2 100644 --- a/src/osquery/core/sql/column.h +++ b/src/osquery/core/sql/column.h @@ -35,62 +35,64 @@ namespace osquery { * to communicate these subtleties to the user. */ enum class ColumnOptions { - /// Default/no options. - DEFAULT = 0, - - /// Treat this column as a primary key. - INDEX = 1, - - /// This column MUST be included in the query predicate. - REQUIRED = 2, - - /* - * @brief This column is used to generate additional information. - * - * If this column is included in the query predicate, the table will generate - * additional information. Consider the browser_plugins or shell history - * tables: by default they list the plugins or history relative to the user - * running the query. However, if the calling query specifies a UID explicitly - * in the predicate, the meaning of the table changes and results for that - * user are returned instead. - */ - ADDITIONAL = 4, - - /* - * @brief This column can be used to optimize the query. - * - * If this column is included in the query predicate, the table will generate - * optimized information. Consider the system_controls table, a default filter - * without a query predicate lists all of the keys. When a specific domain is - * included in the predicate then the table will only issue syscalls/lookups - * for that domain, greatly optimizing the time and utilization. - * - * This optimization does not mean the column is an index. - */ - OPTIMIZED = 8, - - /// This column should be hidden from '*'' selects. - HIDDEN = 16, + /// Default/no options. + DEFAULT = 0, + + /// Treat this column as a primary key. + INDEX = 1, + + /// This column MUST be included in the query predicate. + REQUIRED = 2, + + /* + * @brief This column is used to generate additional information. + * + * If this column is included in the query predicate, the table will generate + * additional information. Consider the browser_plugins or shell history + * tables: by default they list the plugins or history relative to the user + * running the query. However, if the calling query specifies a UID explicitly + * in the predicate, the meaning of the table changes and results for that + * user are returned instead. + */ + ADDITIONAL = 4, + + /* + * @brief This column can be used to optimize the query. + * + * If this column is included in the query predicate, the table will generate + * optimized information. Consider the system_controls table, a default filter + * without a query predicate lists all of the keys. When a specific domain is + * included in the predicate then the table will only issue syscalls/lookups + * for that domain, greatly optimizing the time and utilization. + * + * This optimization does not mean the column is an index. + */ + OPTIMIZED = 8, + + /// This column should be hidden from '*'' selects. + HIDDEN = 16, }; /// Treat column options as a set of flags. -inline ColumnOptions operator|(ColumnOptions a, ColumnOptions b) { - return static_cast(static_cast(a) | static_cast(b)); +inline ColumnOptions operator|(ColumnOptions a, ColumnOptions b) +{ + return static_cast(static_cast(a) | static_cast(b)); } /// Treat column options as a set of flags. -inline size_t operator&(ColumnOptions a, ColumnOptions b) { - return static_cast(a) & static_cast(b); +inline size_t operator&(ColumnOptions a, ColumnOptions b) +{ + return static_cast(a) & static_cast(b); } enum ColumnType { - UNKNOWN_TYPE = 0, - TEXT_TYPE, - INTEGER_TYPE, - BIGINT_TYPE, - UNSIGNED_BIGINT_TYPE, - DOUBLE_TYPE, - BLOB_TYPE, + UNKNOWN_TYPE = 0, + TEXT_TYPE, + INTEGER_TYPE, + BIGINT_TYPE, + UNSIGNED_BIGINT_TYPE, + DOUBLE_TYPE, + BLOB_TYPE, }; /// Map of type constant to the SQLite string-name representation. @@ -101,7 +103,7 @@ using TableName = std::string; /// Alias for an ordered list of column name and corresponding SQL type. using TableColumns = - std::vector>; + std::vector>; /// Alias for map of column alias sets. using ColumnAliasSet = std::map>; diff --git a/src/osquery/core/sql/diff_results.cpp b/src/osquery/core/sql/diff_results.cpp index ba35e57..4fc42db 100644 --- a/src/osquery/core/sql/diff_results.cpp +++ b/src/osquery/core/sql/diff_results.cpp @@ -10,23 +10,24 @@ namespace osquery { -DiffResults diff(QueryDataSet& old, QueryDataTyped& current) { - DiffResults r; +DiffResults diff(QueryDataSet& old, QueryDataTyped& current) +{ + DiffResults r; - for (auto& i : current) { - auto item = old.find(i); - if (item != old.end()) { - old.erase(item); - } else { - r.added.push_back(i); - } - } + for (auto& i : current) { + auto item = old.find(i); + if (item != old.end()) { + old.erase(item); + } else { + r.added.push_back(i); + } + } - for (auto& i : old) { - r.removed.push_back(std::move(i)); - } + for (auto& i : old) { + r.removed.push_back(std::move(i)); + } - return r; + return r; } } // namespace osquery diff --git a/src/osquery/core/sql/diff_results.h b/src/osquery/core/sql/diff_results.h index 0b1732e..edbf025 100644 --- a/src/osquery/core/sql/diff_results.h +++ b/src/osquery/core/sql/diff_results.h @@ -22,26 +22,28 @@ namespace osquery { * "removed" subset of rows. */ struct DiffResults : private only_movable { - public: - /// vector of added rows - QueryDataTyped added; - - /// vector of removed rows - QueryDataTyped removed; - - DiffResults() {} - DiffResults(DiffResults&&) = default; - DiffResults& operator=(DiffResults&&) = default; - - /// equals operator - bool operator==(const DiffResults& comp) const { - return (comp.added == added) && (comp.removed == removed); - } - - /// not equals operator - bool operator!=(const DiffResults& comp) const { - return !(*this == comp); - } +public: + /// vector of added rows + QueryDataTyped added; + + /// vector of removed rows + QueryDataTyped removed; + + DiffResults() {} + DiffResults(DiffResults&&) = default; + DiffResults& operator=(DiffResults&&) = default; + + /// equals operator + bool operator==(const DiffResults& comp) const + { + return (comp.added == added) && (comp.removed == removed); + } + + /// not equals operator + bool operator!=(const DiffResults& comp) const + { + return !(*this == comp); + } }; /** diff --git a/src/osquery/core/sql/scheduled_query.h b/src/osquery/core/sql/scheduled_query.h index e50758f..8dd928a 100644 --- a/src/osquery/core/sql/scheduled_query.h +++ b/src/osquery/core/sql/scheduled_query.h @@ -22,53 +22,55 @@ namespace osquery { * attributes. Those attributes are represented in this data structure. */ struct ScheduledQuery : private only_movable { - /// Name of the pack containing query - std::string pack_name; - - /// Name of the query - std::string name; - - /// The SQL query. - std::string query; - - /// Owner of the query - std::string oncall; - - /// How often the query should be executed, in second. - size_t interval{0}; - - /// A temporary splayed internal. - size_t splayed_interval{0}; - - /** - * @brief Queries are blacklisted based on logic in the configuration. - * - * Most calls to inspect scheduled queries will abstract away the blacklisting - * concept and only return non-blacklisted queries. The config may be asked - * to return all queries, thus it is important to capture this optional data. - */ - bool blacklisted{false}; - - /// Set of query options. - std::map options; - - ScheduledQuery(const std::string& pack_name, - const std::string& name, - const std::string& query) - : pack_name(pack_name), name(name), query(query) {} - ScheduledQuery() = default; - ScheduledQuery(ScheduledQuery&&) = default; - ScheduledQuery& operator=(ScheduledQuery&&) = default; - - /// equals operator - bool operator==(const ScheduledQuery& comp) const { - return (comp.query == query) && (comp.interval == interval); - } - - /// not equals operator - bool operator!=(const ScheduledQuery& comp) const { - return !(*this == comp); - } + /// Name of the pack containing query + std::string pack_name; + + /// Name of the query + std::string name; + + /// The SQL query. + std::string query; + + /// Owner of the query + std::string oncall; + + /// How often the query should be executed, in second. + size_t interval{0}; + + /// A temporary splayed internal. + size_t splayed_interval{0}; + + /** + * @brief Queries are blacklisted based on logic in the configuration. + * + * Most calls to inspect scheduled queries will abstract away the blacklisting + * concept and only return non-blacklisted queries. The config may be asked + * to return all queries, thus it is important to capture this optional data. + */ + bool blacklisted{false}; + + /// Set of query options. + std::map options; + + ScheduledQuery(const std::string& pack_name, + const std::string& name, + const std::string& query) + : pack_name(pack_name), name(name), query(query) {} + ScheduledQuery() = default; + ScheduledQuery(ScheduledQuery&&) = default; + ScheduledQuery& operator=(ScheduledQuery&&) = default; + + /// equals operator + bool operator==(const ScheduledQuery& comp) const + { + return (comp.query == query) && (comp.interval == interval); + } + + /// not equals operator + bool operator!=(const ScheduledQuery& comp) const + { + return !(*this == comp); + } }; } // namespace osquery diff --git a/src/osquery/core/sql/table_row.h b/src/osquery/core/sql/table_row.h index 9b517c5..5d63e84 100644 --- a/src/osquery/core/sql/table_row.h +++ b/src/osquery/core/sql/table_row.h @@ -28,36 +28,36 @@ using TableRows = std::vector; * a map or code generated with type-safe fields. */ class TableRow { - public: - TableRow() = default; - virtual ~TableRow() {} - - /** - * Output the rowid of the current row into pRowid, returning SQLITE_OK if - * successful or SQLITE_ERROR if not. - */ - virtual int get_rowid(sqlite_int64 default_value, - sqlite_int64* pRowid) const = 0; - /** - * Invoke the appropriate sqlite3_result_xxx method for the given column, or - * null if the value does not fit the column type. - */ - virtual int get_column(sqlite3_context* ctx, - sqlite3_vtab* pVtab, - int col) = 0; - /** - * Clone this row. - */ - virtual TableRowHolder clone() const = 0; - - /** - * Convert this row to a string map. - */ - virtual operator Row() const = 0; - - protected: - TableRow(const TableRow&) = default; - TableRow& operator=(const TableRow&) = default; +public: + TableRow() = default; + virtual ~TableRow() {} + + /** + * Output the rowid of the current row into pRowid, returning SQLITE_OK if + * successful or SQLITE_ERROR if not. + */ + virtual int get_rowid(sqlite_int64 default_value, + sqlite_int64* pRowid) const = 0; + /** + * Invoke the appropriate sqlite3_result_xxx method for the given column, or + * null if the value does not fit the column type. + */ + virtual int get_column(sqlite3_context* ctx, + sqlite3_vtab* pVtab, + int col) = 0; + /** + * Clone this row. + */ + virtual TableRowHolder clone() const = 0; + + /** + * Convert this row to a string map. + */ + virtual operator Row() const = 0; + +protected: + TableRow(const TableRow&) = default; + TableRow& operator=(const TableRow&) = default; }; } // namespace osquery diff --git a/src/osquery/core/tables.cpp b/src/osquery/core/tables.cpp index 8d1c400..48ced6c 100644 --- a/src/osquery/core/tables.cpp +++ b/src/osquery/core/tables.cpp @@ -20,26 +20,29 @@ size_t TablePlugin::kCacheInterval = 0; size_t TablePlugin::kCacheStep = 0; Status TablePlugin::addExternal(const std::string& name, - const PluginResponse& response) { - // Attach the table. - if (response.size() == 0) { - // Invalid table route info. - // Tables must broadcast their column information, this is used while the - // core is deciding if the extension's route is valid. - return Status(1, "Invalid route info"); - } + const PluginResponse& response) +{ + // Attach the table. + if (response.size() == 0) { + // Invalid table route info. + // Tables must broadcast their column information, this is used while the + // core is deciding if the extension's route is valid. + return Status(1, "Invalid route info"); + } - // Use the SQL registry to attach the name/definition. - return Registry::call("sql", "sql", {{"action", "attach"}, {"table", name}}); + // Use the SQL registry to attach the name/definition. + return Registry::call("sql", "sql", {{"action", "attach"}, {"table", name}}); } -void TablePlugin::removeExternal(const std::string& name) { - // Detach the table name. - Registry::call("sql", "sql", {{"action", "detach"}, {"table", name}}); +void TablePlugin::removeExternal(const std::string& name) +{ + // Detach the table name. + Registry::call("sql", "sql", {{"action", "detach"}, {"table", name}}); } void TablePlugin::setRequestFromContext(const QueryContext& context, - PluginRequest& request) { + PluginRequest& request) +{ vist::json::Json document; vist::json::Array constraints; @@ -69,7 +72,8 @@ void TablePlugin::setRequestFromContext(const QueryContext& context, DEBUG(OSQUERY) << "request context->" << request["context"]; } -QueryContext TablePlugin::getContextFromRequest(const PluginRequest& request) const { +QueryContext TablePlugin::getContextFromRequest(const PluginRequest& request) const +{ QueryContext context; if (request.count("context") == 0) return context; @@ -83,13 +87,13 @@ QueryContext TablePlugin::getContextFromRequest(const PluginRequest& request) co UsedColumns colsUsed; Array array = document.get("colsUsed"); for (auto i = 0; i < array.size(); i++) { - std::string name = array.at(i); + std::string name = array.at(i); colsUsed.insert(name); } context.colsUsed = colsUsed; } - Array constraints = document.get("constraints"); + Array constraints = document.get("constraints"); for (auto i = 0; i < constraints.size(); i++) { auto constraint = Object::Create(constraints.at(i)); std::string name = constraint["name"]; @@ -100,350 +104,368 @@ QueryContext TablePlugin::getContextFromRequest(const PluginRequest& request) co } UsedColumnsBitset TablePlugin::usedColumnsToBitset( - const UsedColumns usedColumns) const { - UsedColumnsBitset result; - - const auto columns = this->columns(); - const auto aliases = this->aliasedColumns(); - for (size_t i = 0; i < columns.size(); i++) { - auto column_name = std::get<0>(columns[i]); - const auto& aliased_name = aliases.find(column_name); - if (aliased_name != aliases.end()) { - column_name = aliased_name->second; - } - if (usedColumns.find(column_name) != usedColumns.end()) { - result.set(i); - } - } + const UsedColumns usedColumns) const +{ + UsedColumnsBitset result; + + const auto columns = this->columns(); + const auto aliases = this->aliasedColumns(); + for (size_t i = 0; i < columns.size(); i++) { + auto column_name = std::get<0>(columns[i]); + const auto& aliased_name = aliases.find(column_name); + if (aliased_name != aliases.end()) { + column_name = aliased_name->second; + } + if (usedColumns.find(column_name) != usedColumns.end()) { + result.set(i); + } + } - return result; + return result; } Status TablePlugin::call(const PluginRequest& request, - PluginResponse& response) { - response.clear(); - - // TablePlugin API calling requires an action. - if (request.count("action") == 0) { - return Status(1, "Table plugins must include a request action"); - } - - const auto& action = request.at("action"); - - if (action == "generate") { - auto context = getContextFromRequest(request); - TableRows result = generate(context); - response = tableRowsToPluginResponse(result); - } else if (action == "delete") { - auto context = getContextFromRequest(request); - response = delete_(context, request); - } else if (action == "insert") { - auto context = getContextFromRequest(request); - response = insert(context, request); - } else if (action == "update") { - auto context = getContextFromRequest(request); - response = update(context, request); - } else if (action == "columns") { - response = routeInfo(); - } else { - return Status(1, "Unknown table plugin action: " + action); - } - - return Status::success(); -} - -std::string TablePlugin::columnDefinition(bool is_extension) const { - return osquery::columnDefinition(columns(), is_extension); -} - -PluginResponse TablePlugin::routeInfo() const { - // Route info consists of the serialized column information. - PluginResponse response; - for (const auto& column : columns()) { - response.push_back( - {{"id", "column"}, - {"name", std::get<0>(column)}, - {"type", columnTypeName(std::get<1>(column))}, - {"op", INTEGER(static_cast(std::get<2>(column)))}}); - } - // Each table name alias is provided such that the core may add the views. - // These views need to be removed when the backing table is detached. - for (const auto& alias : aliases()) { - response.push_back({{"id", "alias"}, {"alias", alias}}); - } - - // Each column alias must be provided, additionally to the column's option. - // This sets up the value-replacement move within the SQL implementation. - for (const auto& target : columnAliases()) { - for (const auto& alias : target.second) { - response.push_back( - {{"id", "columnAlias"}, {"name", alias}, {"target", target.first}}); - } - } - - response.push_back( - {{"id", "attributes"}, - {"attributes", INTEGER(static_cast(attributes()))}}); - return response; -} - -static bool cacheAllowed(const TableColumns& cols, const QueryContext& ctx) { - if (!ctx.useCache()) { - // The query execution did not request use of the warm cache. - return false; - } - - auto uncachable = ColumnOptions::INDEX | ColumnOptions::REQUIRED | - ColumnOptions::ADDITIONAL | ColumnOptions::OPTIMIZED; - for (const auto& column : cols) { - auto opts = std::get<2>(column) & uncachable; - if (opts && ctx.constraints.at(std::get<0>(column)).exists()) { - return false; - } - } - return true; -} - -bool TablePlugin::isCached(size_t step, const QueryContext& ctx) const { - // Perform the step comparison first, because it's easy. - return (step < last_cached_ + last_interval_ && cacheAllowed(columns(), ctx)); -} - -TableRows TablePlugin::getCache() const { - TableRows results; - return results; + PluginResponse& response) +{ + response.clear(); + + // TablePlugin API calling requires an action. + if (request.count("action") == 0) { + return Status(1, "Table plugins must include a request action"); + } + + const auto& action = request.at("action"); + + if (action == "generate") { + auto context = getContextFromRequest(request); + TableRows result = generate(context); + response = tableRowsToPluginResponse(result); + } else if (action == "delete") { + auto context = getContextFromRequest(request); + response = delete_(context, request); + } else if (action == "insert") { + auto context = getContextFromRequest(request); + response = insert(context, request); + } else if (action == "update") { + auto context = getContextFromRequest(request); + response = update(context, request); + } else if (action == "columns") { + response = routeInfo(); + } else { + return Status(1, "Unknown table plugin action: " + action); + } + + return Status::success(); +} + +std::string TablePlugin::columnDefinition(bool is_extension) const +{ + return osquery::columnDefinition(columns(), is_extension); +} + +PluginResponse TablePlugin::routeInfo() const +{ + // Route info consists of the serialized column information. + PluginResponse response; + for (const auto& column : columns()) { + response.push_back({ + {"id", "column"}, + {"name", std::get<0>(column)}, + {"type", columnTypeName(std::get<1>(column))}, + {"op", INTEGER(static_cast(std::get<2>(column)))}}); + } + // Each table name alias is provided such that the core may add the views. + // These views need to be removed when the backing table is detached. + for (const auto& alias : aliases()) { + response.push_back({{"id", "alias"}, {"alias", alias}}); + } + + // Each column alias must be provided, additionally to the column's option. + // This sets up the value-replacement move within the SQL implementation. + for (const auto& target : columnAliases()) { + for (const auto& alias : target.second) { + response.push_back( + {{"id", "columnAlias"}, {"name", alias}, {"target", target.first}}); + } + } + + response.push_back({ + {"id", "attributes"}, + {"attributes", INTEGER(static_cast(attributes()))}}); + return response; +} + +static bool cacheAllowed(const TableColumns& cols, const QueryContext& ctx) +{ + if (!ctx.useCache()) { + // The query execution did not request use of the warm cache. + return false; + } + + auto uncachable = ColumnOptions::INDEX | ColumnOptions::REQUIRED | + ColumnOptions::ADDITIONAL | ColumnOptions::OPTIMIZED; + for (const auto& column : cols) { + auto opts = std::get<2>(column) & uncachable; + if (opts && ctx.constraints.at(std::get<0>(column)).exists()) { + return false; + } + } + return true; +} + +bool TablePlugin::isCached(size_t step, const QueryContext& ctx) const +{ + // Perform the step comparison first, because it's easy. + return (step < last_cached_ + last_interval_ && cacheAllowed(columns(), ctx)); +} + +TableRows TablePlugin::getCache() const +{ + TableRows results; + return results; } void TablePlugin::setCache(size_t step, - size_t interval, - const QueryContext& ctx, - const TableRows& results) { - return; -} - -std::string columnDefinition(const TableColumns& columns, bool is_extension) { - std::map epilog; - bool indexed = false; - std::vector pkeys; - - std::string statement = "("; - for (size_t i = 0; i < columns.size(); ++i) { - const auto& column = columns.at(i); - statement += - '`' + std::get<0>(column) + "` " + columnTypeName(std::get<1>(column)); - auto& options = std::get<2>(column); - if (options & (ColumnOptions::INDEX | ColumnOptions::ADDITIONAL)) { - if (options & ColumnOptions::INDEX) { - indexed = true; - } - pkeys.push_back(std::get<0>(column)); - epilog["WITHOUT ROWID"] = true; - } - if (options & ColumnOptions::HIDDEN) { - statement += " HIDDEN"; - } - if (i < columns.size() - 1) { - statement += ", "; - } - } - - // If there are only 'additional' columns (rare), do not attempt a pkey. - if (!indexed) { - epilog["WITHOUT ROWID"] = false; - pkeys.clear(); - } - - // Append the primary keys, if any were defined. - if (!pkeys.empty()) { - statement += ", PRIMARY KEY ("; - for (auto pkey = pkeys.begin(); pkey != pkeys.end();) { - statement += '`' + std::move(*pkey) + '`'; - if (++pkey != pkeys.end()) { - statement += ", "; - } - } - statement += ')'; - } - - // Tables implemented by extension can be made read/write; make sure to always - // keep the rowid column, as we need it to reference rows when handling UPDATE - // and DELETE queries - if (is_extension) { - epilog["WITHOUT ROWID"] = false; - } - - statement += ')'; - for (auto& ei : epilog) { - if (ei.second) { - statement += ' ' + std::move(ei.first); - } - } - return statement; + size_t interval, + const QueryContext& ctx, + const TableRows& results) +{ + return; +} + +std::string columnDefinition(const TableColumns& columns, bool is_extension) +{ + std::map epilog; + bool indexed = false; + std::vector pkeys; + + std::string statement = "("; + for (size_t i = 0; i < columns.size(); ++i) { + const auto& column = columns.at(i); + statement += + '`' + std::get<0>(column) + "` " + columnTypeName(std::get<1>(column)); + auto& options = std::get<2>(column); + if (options & (ColumnOptions::INDEX | ColumnOptions::ADDITIONAL)) { + if (options & ColumnOptions::INDEX) { + indexed = true; + } + pkeys.push_back(std::get<0>(column)); + epilog["WITHOUT ROWID"] = true; + } + if (options & ColumnOptions::HIDDEN) { + statement += " HIDDEN"; + } + if (i < columns.size() - 1) { + statement += ", "; + } + } + + // If there are only 'additional' columns (rare), do not attempt a pkey. + if (!indexed) { + epilog["WITHOUT ROWID"] = false; + pkeys.clear(); + } + + // Append the primary keys, if any were defined. + if (!pkeys.empty()) { + statement += ", PRIMARY KEY ("; + for (auto pkey = pkeys.begin(); pkey != pkeys.end();) { + statement += '`' + std::move(*pkey) + '`'; + if (++pkey != pkeys.end()) { + statement += ", "; + } + } + statement += ')'; + } + + // Tables implemented by extension can be made read/write; make sure to always + // keep the rowid column, as we need it to reference rows when handling UPDATE + // and DELETE queries + if (is_extension) { + epilog["WITHOUT ROWID"] = false; + } + + statement += ')'; + for (auto& ei : epilog) { + if (ei.second) { + statement += ' ' + std::move(ei.first); + } + } + return statement; } std::string columnDefinition(const PluginResponse& response, - bool aliases, - bool is_extension) { - TableColumns columns; - // Maintain a map of column to the type, for alias type lookups. - std::map column_types; - for (const auto& column : response) { - auto id = column.find("id"); - if (id == column.end()) { - continue; - } - - auto cname = column.find("name"); - auto ctype = column.find("type"); - if (id->second == "column" && cname != column.end() && - ctype != column.end()) { - auto options = ColumnOptions::DEFAULT; - - auto cop = column.find("op"); - if (cop != column.end()) { - auto op = tryTo(cop->second); - if (op) { - options = static_cast(op.take()); - } - } - auto column_type = columnTypeName(ctype->second); - columns.push_back(make_tuple(cname->second, column_type, options)); - if (aliases) { - column_types[cname->second] = column_type; - } - } else if (id->second == "columnAlias" && cname != column.end() && - aliases) { - auto ctarget = column.find("target"); - if (ctarget != column.end()) { - auto target_ctype = column_types.find(ctarget->second); - if (target_ctype != column_types.end()) { - columns.push_back(make_tuple( - cname->second, target_ctype->second, ColumnOptions::HIDDEN)); - } - } - } - } - return columnDefinition(columns, is_extension); -} - -ColumnType columnTypeName(const std::string& type) { - for (const auto& col : kColumnTypeNames) { - if (col.second == type) { - return col.first; - } - } - return UNKNOWN_TYPE; -} - -bool ConstraintList::exists(const ConstraintOperatorFlag ops) const { - if (ops == ANY_OP) { - return (constraints_.size() > 0); - } else { - for (const struct Constraint& c : constraints_) { - if (c.op & ops) { - return true; - } - } - return false; - } -} - -bool ConstraintList::matches(const std::string& expr) const { - // Support each SQL affinity type casting. - if (affinity == TEXT_TYPE) { - return literal_matches(expr); - } else if (affinity == INTEGER_TYPE) { - auto lexpr = tryTo(expr); - if (lexpr) { - return literal_matches(lexpr.take()); - } - } else if (affinity == BIGINT_TYPE) { - auto lexpr = tryTo(expr); - if (lexpr) { - return literal_matches(lexpr.take()); - } - } else if (affinity == UNSIGNED_BIGINT_TYPE) { - auto lexpr = tryTo(expr); - if (lexpr) { - return literal_matches(lexpr.take()); - } - } - - return false; + bool aliases, + bool is_extension) +{ + TableColumns columns; + // Maintain a map of column to the type, for alias type lookups. + std::map column_types; + for (const auto& column : response) { + auto id = column.find("id"); + if (id == column.end()) { + continue; + } + + auto cname = column.find("name"); + auto ctype = column.find("type"); + if (id->second == "column" && cname != column.end() && + ctype != column.end()) { + auto options = ColumnOptions::DEFAULT; + + auto cop = column.find("op"); + if (cop != column.end()) { + auto op = tryTo(cop->second); + if (op) { + options = static_cast(op.take()); + } + } + auto column_type = columnTypeName(ctype->second); + columns.push_back(make_tuple(cname->second, column_type, options)); + if (aliases) { + column_types[cname->second] = column_type; + } + } else if (id->second == "columnAlias" && cname != column.end() && + aliases) { + auto ctarget = column.find("target"); + if (ctarget != column.end()) { + auto target_ctype = column_types.find(ctarget->second); + if (target_ctype != column_types.end()) { + columns.push_back(make_tuple( + cname->second, target_ctype->second, ColumnOptions::HIDDEN)); + } + } + } + } + return columnDefinition(columns, is_extension); +} + +ColumnType columnTypeName(const std::string& type) +{ + for (const auto& col : kColumnTypeNames) { + if (col.second == type) { + return col.first; + } + } + return UNKNOWN_TYPE; +} + +bool ConstraintList::exists(const ConstraintOperatorFlag ops) const +{ + if (ops == ANY_OP) { + return (constraints_.size() > 0); + } else { + for (const struct Constraint& c : constraints_) { + if (c.op & ops) { + return true; + } + } + return false; + } +} + +bool ConstraintList::matches(const std::string& expr) const +{ + // Support each SQL affinity type casting. + if (affinity == TEXT_TYPE) { + return literal_matches(expr); + } else if (affinity == INTEGER_TYPE) { + auto lexpr = tryTo(expr); + if (lexpr) { + return literal_matches(lexpr.take()); + } + } else if (affinity == BIGINT_TYPE) { + auto lexpr = tryTo(expr); + if (lexpr) { + return literal_matches(lexpr.take()); + } + } else if (affinity == UNSIGNED_BIGINT_TYPE) { + auto lexpr = tryTo(expr); + if (lexpr) { + return literal_matches(lexpr.take()); + } + } + + return false; } template -bool ConstraintList::literal_matches(const T& base_expr) const { - bool aggregate = true; - for (size_t i = 0; i < constraints_.size(); ++i) { - auto constraint_expr = tryTo(constraints_[i].expr); - if (!constraint_expr) { - // Cannot cast input constraint to column type. - return false; - } - if (constraints_[i].op == EQUALS) { - aggregate = aggregate && (base_expr == constraint_expr.take()); - } else if (constraints_[i].op == GREATER_THAN) { - aggregate = aggregate && (base_expr > constraint_expr.take()); - } else if (constraints_[i].op == LESS_THAN) { - aggregate = aggregate && (base_expr < constraint_expr.take()); - } else if (constraints_[i].op == GREATER_THAN_OR_EQUALS) { - aggregate = aggregate && (base_expr >= constraint_expr.take()); - } else if (constraints_[i].op == LESS_THAN_OR_EQUALS) { - aggregate = aggregate && (base_expr <= constraint_expr.take()); - } else { - // Unsupported constraint. Should match every thing. - return true; - } - if (!aggregate) { - // Speed up comparison. - return false; - } - } - return true; -} - -std::set ConstraintList::getAll(ConstraintOperator op) const { - std::set set; - for (size_t i = 0; i < constraints_.size(); ++i) { - if (constraints_[i].op == op) { - // TODO: this does not apply a distinct. - set.insert(constraints_[i].expr); - } - } - return set; +bool ConstraintList::literal_matches(const T& base_expr) const +{ + bool aggregate = true; + for (size_t i = 0; i < constraints_.size(); ++i) { + auto constraint_expr = tryTo(constraints_[i].expr); + if (!constraint_expr) { + // Cannot cast input constraint to column type. + return false; + } + if (constraints_[i].op == EQUALS) { + aggregate = aggregate && (base_expr == constraint_expr.take()); + } else if (constraints_[i].op == GREATER_THAN) { + aggregate = aggregate && (base_expr > constraint_expr.take()); + } else if (constraints_[i].op == LESS_THAN) { + aggregate = aggregate && (base_expr < constraint_expr.take()); + } else if (constraints_[i].op == GREATER_THAN_OR_EQUALS) { + aggregate = aggregate && (base_expr >= constraint_expr.take()); + } else if (constraints_[i].op == LESS_THAN_OR_EQUALS) { + aggregate = aggregate && (base_expr <= constraint_expr.take()); + } else { + // Unsupported constraint. Should match every thing. + return true; + } + if (!aggregate) { + // Speed up comparison. + return false; + } + } + return true; +} + +std::set ConstraintList::getAll(ConstraintOperator op) const +{ + std::set set; + for (size_t i = 0; i < constraints_.size(); ++i) { + if (constraints_[i].op == op) { + // TODO: this does not apply a distinct. + set.insert(constraints_[i].expr); + } + } + return set; } template -std::set ConstraintList::getAll(ConstraintOperator /* op */) const { - std::set cs; - for (const auto& item : constraints_) { - auto exp = tryTo(item.expr); - if (exp) { - cs.insert(exp.take()); - } - } - return cs; +std::set ConstraintList::getAll(ConstraintOperator /* op */) const +{ + std::set cs; + for (const auto& item : constraints_) { + auto exp = tryTo(item.expr); + if (exp) { + cs.insert(exp.take()); + } + } + return cs; } template <> -std::set ConstraintList::getAll(ConstraintOperator op) const { - return getAll(op); +std::set ConstraintList::getAll(ConstraintOperator op) const +{ + return getAll(op); } /// Explicit getAll for INTEGER. template std::set ConstraintList::getAll( - ConstraintOperator) const; + ConstraintOperator) const; /// Explicit getAll for BIGINT. template std::set ConstraintList::getAll( - ConstraintOperator) const; + ConstraintOperator) const; /// Explicit getAll for UNSIGNED_BIGINT. template std::set - ConstraintList::getAll(ConstraintOperator) const; +ConstraintList::getAll(ConstraintOperator) const; -vist::json::Object ConstraintList::serialize() const { +vist::json::Object ConstraintList::serialize() const +{ // format: { "affinity": "TEXT", "list": [ { "expr": "1", "op": 2 } ] } vist::json::Array list; for (const auto& constraint : constraints_) { @@ -460,12 +482,13 @@ vist::json::Object ConstraintList::serialize() const { return object; } -void ConstraintList::deserialize(vist::json::Object& obj) { +void ConstraintList::deserialize(vist::json::Object& obj) +{ using namespace vist::json; Array list = obj.get("list"); for (auto i = 0; i < list.size(); i++) { auto element = vist::json::Object::Create(list.at(i)); - int op = element["op"]; + int op = element["op"]; Constraint constraint(static_cast(op)); constraint.expr = static_cast(element["expr"]); this->constraints_.emplace_back(std::move(constraint)); @@ -475,61 +498,70 @@ void ConstraintList::deserialize(vist::json::Object& obj) { this->affinity = columnTypeName(name); } -bool QueryContext::isColumnUsed(const std::string& colName) const { - return !colsUsed || colsUsed->find(colName) != colsUsed->end(); +bool QueryContext::isColumnUsed(const std::string& colName) const +{ + return !colsUsed || colsUsed->find(colName) != colsUsed->end(); } bool QueryContext::isAnyColumnUsed( - std::initializer_list colNames) const { - for (auto& colName : colNames) { - if (isColumnUsed(colName)) { - return true; - } - } - return false; + std::initializer_list colNames) const +{ + for (auto& colName : colNames) { + if (isColumnUsed(colName)) { + return true; + } + } + return false; } -void QueryContext::useCache(bool use_cache) { - use_cache_ = use_cache; +void QueryContext::useCache(bool use_cache) +{ + use_cache_ = use_cache; } -bool QueryContext::useCache() const { - return use_cache_; +bool QueryContext::useCache() const +{ + return use_cache_; } void QueryContext::setCache(const std::string& index, - const TableRowHolder& cache) { - table_->cache[index] = cache->clone(); + const TableRowHolder& cache) +{ + table_->cache[index] = cache->clone(); } -bool QueryContext::isCached(const std::string& index) const { - return (table_->cache.count(index) != 0); +bool QueryContext::isCached(const std::string& index) const +{ + return (table_->cache.count(index) != 0); } -TableRowHolder QueryContext::getCache(const std::string& index) { - return table_->cache[index]->clone(); +TableRowHolder QueryContext::getCache(const std::string& index) +{ + return table_->cache[index]->clone(); } bool QueryContext::hasConstraint(const std::string& column, - ConstraintOperator op) const { - if (constraints.count(column) == 0) { - return false; - } - return constraints.at(column).exists(op); + ConstraintOperator op) const +{ + if (constraints.count(column) == 0) { + return false; + } + return constraints.at(column).exists(op); } Status QueryContext::expandConstraints( - const std::string& column, - ConstraintOperator op, - std::set& output, - std::function& output)> predicate) { - for (const auto& constraint : constraints[column].getAll(op)) { - auto status = predicate(constraint, output); - if (!status) { - return status; - } - } - return Status(0); + const std::string& column, + ConstraintOperator op, + std::set& output, + std::function& output)> predicate) +{ + for (const auto& constraint : constraints[column].getAll(op)) { + auto status = predicate(constraint, output); + if (!status) { + return status; + } + } + return Status(0); } } // namespace osquery diff --git a/src/osquery/core/tests/query_tests.cpp b/src/osquery/core/tests/query_tests.cpp index 1c9c110..338b72d 100644 --- a/src/osquery/core/tests/query_tests.cpp +++ b/src/osquery/core/tests/query_tests.cpp @@ -22,135 +22,143 @@ namespace osquery { DECLARE_bool(disable_database); DECLARE_bool(log_numerics_as_numbers); class QueryTests : public testing::Test { - public: - QueryTests() { - registryAndPluginInit(); - FLAGS_disable_database = true; - DatabasePlugin::setAllowOpen(true); - DatabasePlugin::initPlugin(); - } +public: + QueryTests() + { + registryAndPluginInit(); + FLAGS_disable_database = true; + DatabasePlugin::setAllowOpen(true); + DatabasePlugin::initPlugin(); + } }; -TEST_F(QueryTests, test_private_members) { - auto query = getOsqueryScheduledQuery(); - auto cf = Query("foobar", query); - EXPECT_EQ(cf.query_, query.query); +TEST_F(QueryTests, test_private_members) +{ + auto query = getOsqueryScheduledQuery(); + auto cf = Query("foobar", query); + EXPECT_EQ(cf.query_, query.query); } -TEST_F(QueryTests, test_add_and_get_current_results) { - FLAGS_log_numerics_as_numbers = true; - // Test adding a "current" set of results to a scheduled query instance. - auto query = getOsqueryScheduledQuery(); - auto cf = Query("foobar", query); - uint64_t counter = 128; - auto status = cf.addNewResults(getTestDBExpectedResults(), 0, counter); - EXPECT_TRUE(status.ok()); - EXPECT_EQ(status.toString(), "OK"); - EXPECT_EQ(counter, 0UL); - - // Simulate results from several schedule runs, calculate differentials. - uint64_t expected_counter = counter + 1; - for (auto result : getTestDBResultStream()) { - // Get the results from the previous query execution (from the DB). - QueryDataSet previous_qd; - status = cf.getPreviousQueryResults(previous_qd); - EXPECT_TRUE(status.ok()); - EXPECT_EQ(status.toString(), "OK"); - - // Add the "current" results and output the differentials. - DiffResults dr; - counter = 128; - auto s = cf.addNewResults(result.second, 0, counter, dr, true); - EXPECT_TRUE(s.ok()); - EXPECT_EQ(counter, expected_counter++); - - // Call the diffing utility directly. - DiffResults expected = diff(previous_qd, result.second); - EXPECT_EQ(dr, expected); - - // After Query::addNewResults the previous results are now current. - QueryDataSet qds_previous; - cf.getPreviousQueryResults(qds_previous); - - QueryDataSet qds; - for (auto& i : result.second) { - qds.insert(i); - } - - EXPECT_EQ(qds_previous, qds); - } +TEST_F(QueryTests, test_add_and_get_current_results) +{ + FLAGS_log_numerics_as_numbers = true; + // Test adding a "current" set of results to a scheduled query instance. + auto query = getOsqueryScheduledQuery(); + auto cf = Query("foobar", query); + uint64_t counter = 128; + auto status = cf.addNewResults(getTestDBExpectedResults(), 0, counter); + EXPECT_TRUE(status.ok()); + EXPECT_EQ(status.toString(), "OK"); + EXPECT_EQ(counter, 0UL); + + // Simulate results from several schedule runs, calculate differentials. + uint64_t expected_counter = counter + 1; + for (auto result : getTestDBResultStream()) { + // Get the results from the previous query execution (from the DB). + QueryDataSet previous_qd; + status = cf.getPreviousQueryResults(previous_qd); + EXPECT_TRUE(status.ok()); + EXPECT_EQ(status.toString(), "OK"); + + // Add the "current" results and output the differentials. + DiffResults dr; + counter = 128; + auto s = cf.addNewResults(result.second, 0, counter, dr, true); + EXPECT_TRUE(s.ok()); + EXPECT_EQ(counter, expected_counter++); + + // Call the diffing utility directly. + DiffResults expected = diff(previous_qd, result.second); + EXPECT_EQ(dr, expected); + + // After Query::addNewResults the previous results are now current. + QueryDataSet qds_previous; + cf.getPreviousQueryResults(qds_previous); + + QueryDataSet qds; + for (auto& i : result.second) { + qds.insert(i); + } + + EXPECT_EQ(qds_previous, qds); + } } -TEST_F(QueryTests, test_get_query_results) { - // Grab an expected set of query data and add it as the previous result. - auto encoded_qd = getSerializedQueryDataJSON(); - auto query = getOsqueryScheduledQuery(); - auto status = setDatabaseValue(kQueries, "foobar", encoded_qd.first); - EXPECT_TRUE(status.ok()); - - // Use the Query retrieval API to check the now "previous" result. - QueryDataSet previous_qd; - auto cf = Query("foobar", query); - status = cf.getPreviousQueryResults(previous_qd); - EXPECT_TRUE(status.ok()); +TEST_F(QueryTests, test_get_query_results) +{ + // Grab an expected set of query data and add it as the previous result. + auto encoded_qd = getSerializedQueryDataJSON(); + auto query = getOsqueryScheduledQuery(); + auto status = setDatabaseValue(kQueries, "foobar", encoded_qd.first); + EXPECT_TRUE(status.ok()); + + // Use the Query retrieval API to check the now "previous" result. + QueryDataSet previous_qd; + auto cf = Query("foobar", query); + status = cf.getPreviousQueryResults(previous_qd); + EXPECT_TRUE(status.ok()); } -TEST_F(QueryTests, test_query_name_not_found_in_db) { - // Try to retrieve results from a query that has not executed. - QueryDataSet previous_qd; - auto query = getOsqueryScheduledQuery(); - auto cf = Query("not_a_real_query", query); - auto status = cf.getPreviousQueryResults(previous_qd); - EXPECT_FALSE(status.ok()); - EXPECT_TRUE(previous_qd.empty()); +TEST_F(QueryTests, test_query_name_not_found_in_db) +{ + // Try to retrieve results from a query that has not executed. + QueryDataSet previous_qd; + auto query = getOsqueryScheduledQuery(); + auto cf = Query("not_a_real_query", query); + auto status = cf.getPreviousQueryResults(previous_qd); + EXPECT_FALSE(status.ok()); + EXPECT_TRUE(previous_qd.empty()); } -TEST_F(QueryTests, test_is_query_name_in_database) { - auto query = getOsqueryScheduledQuery(); - auto cf = Query("foobar", query); - auto encoded_qd = getSerializedQueryDataJSON(); - auto status = setDatabaseValue(kQueries, "foobar", encoded_qd.first); - EXPECT_TRUE(status.ok()); - // Now test that the query name exists. - EXPECT_TRUE(cf.isQueryNameInDatabase()); +TEST_F(QueryTests, test_is_query_name_in_database) +{ + auto query = getOsqueryScheduledQuery(); + auto cf = Query("foobar", query); + auto encoded_qd = getSerializedQueryDataJSON(); + auto status = setDatabaseValue(kQueries, "foobar", encoded_qd.first); + EXPECT_TRUE(status.ok()); + // Now test that the query name exists. + EXPECT_TRUE(cf.isQueryNameInDatabase()); } -TEST_F(QueryTests, test_query_name_updated) { - // Try to retrieve results from a query that has not executed. - QueryDataSet previous_qd; - auto query = getOsqueryScheduledQuery(); - auto cf = Query("will_update_query", query); - EXPECT_TRUE(cf.isNewQuery()); - EXPECT_TRUE(cf.isNewQuery()); - - DiffResults dr; - uint64_t counter = 128; - auto results = getTestDBExpectedResults(); - cf.addNewResults(results, 0, counter, dr); - EXPECT_FALSE(cf.isNewQuery()); - EXPECT_EQ(counter, 0UL); - - query.query += " LIMIT 1"; - counter = 128; - auto cf2 = Query("will_update_query", query); - EXPECT_TRUE(cf2.isQueryNameInDatabase()); - EXPECT_TRUE(cf2.isNewQuery()); - cf2.addNewResults(results, 0, counter, dr); - EXPECT_FALSE(cf2.isNewQuery()); - EXPECT_EQ(counter, 0UL); +TEST_F(QueryTests, test_query_name_updated) +{ + // Try to retrieve results from a query that has not executed. + QueryDataSet previous_qd; + auto query = getOsqueryScheduledQuery(); + auto cf = Query("will_update_query", query); + EXPECT_TRUE(cf.isNewQuery()); + EXPECT_TRUE(cf.isNewQuery()); + + DiffResults dr; + uint64_t counter = 128; + auto results = getTestDBExpectedResults(); + cf.addNewResults(results, 0, counter, dr); + EXPECT_FALSE(cf.isNewQuery()); + EXPECT_EQ(counter, 0UL); + + query.query += " LIMIT 1"; + counter = 128; + auto cf2 = Query("will_update_query", query); + EXPECT_TRUE(cf2.isQueryNameInDatabase()); + EXPECT_TRUE(cf2.isNewQuery()); + cf2.addNewResults(results, 0, counter, dr); + EXPECT_FALSE(cf2.isNewQuery()); + EXPECT_EQ(counter, 0UL); } -TEST_F(QueryTests, test_get_stored_query_names) { - auto query = getOsqueryScheduledQuery(); - auto cf = Query("foobar", query); - auto encoded_qd = getSerializedQueryDataJSON(); - auto status = setDatabaseValue(kQueries, "foobar", encoded_qd.first); - EXPECT_TRUE(status.ok()); - - // Stored query names is a factory method included alongside every query. - // It will include the set of query names with existing "previous" results. - auto names = cf.getStoredQueryNames(); - auto in_vector = std::find(names.begin(), names.end(), "foobar"); - EXPECT_NE(in_vector, names.end()); +TEST_F(QueryTests, test_get_stored_query_names) +{ + auto query = getOsqueryScheduledQuery(); + auto cf = Query("foobar", query); + auto encoded_qd = getSerializedQueryDataJSON(); + auto status = setDatabaseValue(kQueries, "foobar", encoded_qd.first); + EXPECT_TRUE(status.ok()); + + // Stored query names is a factory method included alongside every query. + // It will include the set of query names with existing "previous" results. + auto names = cf.getStoredQueryNames(); + auto in_vector = std::find(names.begin(), names.end(), "foobar"); + EXPECT_NE(in_vector, names.end()); } } diff --git a/src/osquery/core/tests/tables_tests.cpp b/src/osquery/core/tests/tables_tests.cpp index 703c2b6..b945b0f 100644 --- a/src/osquery/core/tests/tables_tests.cpp +++ b/src/osquery/core/tests/tables_tests.cpp @@ -19,176 +19,185 @@ DECLARE_bool(disable_database); class TablesTests : public testing::Test { protected: - void SetUp() { - Initializer::platformSetup(); - registryAndPluginInit(); - FLAGS_disable_database = true; - DatabasePlugin::setAllowOpen(true); - DatabasePlugin::initPlugin(); - } + void SetUp() + { + Initializer::platformSetup(); + registryAndPluginInit(); + FLAGS_disable_database = true; + DatabasePlugin::setAllowOpen(true); + DatabasePlugin::initPlugin(); + } }; -TEST_F(TablesTests, test_constraint) { - auto constraint = Constraint(EQUALS); - constraint.expr = "none"; +TEST_F(TablesTests, test_constraint) +{ + auto constraint = Constraint(EQUALS); + constraint.expr = "none"; - EXPECT_EQ(constraint.op, EQUALS); - EXPECT_EQ(constraint.expr, "none"); + EXPECT_EQ(constraint.op, EQUALS); + EXPECT_EQ(constraint.expr, "none"); } -TEST_F(TablesTests, test_constraint_list) { - struct ConstraintList cl; +TEST_F(TablesTests, test_constraint_list) +{ + struct ConstraintList cl; - auto constraint = Constraint(EQUALS); - constraint.expr = "some"; + auto constraint = Constraint(EQUALS); + constraint.expr = "some"; - // The constraint list is a simple struct. - cl.add(constraint); - EXPECT_EQ(cl.constraints_.size(), 1U); + // The constraint list is a simple struct. + cl.add(constraint); + EXPECT_EQ(cl.constraints_.size(), 1U); - constraint = Constraint(EQUALS); - constraint.expr = "some_other"; - cl.add(constraint); + constraint = Constraint(EQUALS); + constraint.expr = "some_other"; + cl.add(constraint); - constraint = Constraint(GREATER_THAN); - constraint.expr = "more_than"; - cl.add(constraint); - EXPECT_EQ(cl.constraints_.size(), 3U); + constraint = Constraint(GREATER_THAN); + constraint.expr = "more_than"; + cl.add(constraint); + EXPECT_EQ(cl.constraints_.size(), 3U); - auto all_equals = cl.getAll(EQUALS); - EXPECT_EQ(all_equals.size(), 2U); + auto all_equals = cl.getAll(EQUALS); + EXPECT_EQ(all_equals.size(), 2U); } -TEST_F(TablesTests, test_constraint_matching) { - struct ConstraintList cl; - // An empty constraint list has expectations. - EXPECT_FALSE(cl.exists()); - EXPECT_FALSE(cl.exists(GREATER_THAN)); - EXPECT_TRUE(cl.notExistsOrMatches("some")); - - auto constraint = Constraint(EQUALS); - constraint.expr = "some"; - cl.add(constraint); - - // Test existence checks based on flags. - EXPECT_TRUE(cl.exists()); - EXPECT_TRUE(cl.exists(EQUALS)); - EXPECT_TRUE(cl.exists(EQUALS | LESS_THAN)); - EXPECT_FALSE(cl.exists(LESS_THAN)); - - EXPECT_TRUE(cl.notExistsOrMatches("some")); - EXPECT_TRUE(cl.matches("some")); - EXPECT_FALSE(cl.notExistsOrMatches("not_some")); - - struct ConstraintList cl2; - cl2.affinity = INTEGER_TYPE; - constraint = Constraint(LESS_THAN); - constraint.expr = "1000"; - cl2.add(constraint); - constraint = Constraint(GREATER_THAN); - constraint.expr = "1"; - cl2.add(constraint); - - // Test both SQL-provided string types. - EXPECT_TRUE(cl2.matches("10")); - // ...and the type literal. - EXPECT_TRUE(cl2.matches(10)); - - // Test operator lower bounds. - EXPECT_FALSE(cl2.matches(0)); - EXPECT_FALSE(cl2.matches(1)); - - // Test operator upper bounds. - EXPECT_FALSE(cl2.matches(1000)); - EXPECT_FALSE(cl2.matches(1001)); - - // Now test inclusive bounds. - struct ConstraintList cl3; - constraint = Constraint(LESS_THAN_OR_EQUALS); - constraint.expr = "1000"; - cl3.add(constraint); - constraint = Constraint(GREATER_THAN_OR_EQUALS); - constraint.expr = "1"; - cl3.add(constraint); - - EXPECT_FALSE(cl3.matches(1001)); - EXPECT_TRUE(cl3.matches(1000)); - - EXPECT_FALSE(cl3.matches(0)); - EXPECT_TRUE(cl3.matches(1)); +TEST_F(TablesTests, test_constraint_matching) +{ + struct ConstraintList cl; + // An empty constraint list has expectations. + EXPECT_FALSE(cl.exists()); + EXPECT_FALSE(cl.exists(GREATER_THAN)); + EXPECT_TRUE(cl.notExistsOrMatches("some")); + + auto constraint = Constraint(EQUALS); + constraint.expr = "some"; + cl.add(constraint); + + // Test existence checks based on flags. + EXPECT_TRUE(cl.exists()); + EXPECT_TRUE(cl.exists(EQUALS)); + EXPECT_TRUE(cl.exists(EQUALS | LESS_THAN)); + EXPECT_FALSE(cl.exists(LESS_THAN)); + + EXPECT_TRUE(cl.notExistsOrMatches("some")); + EXPECT_TRUE(cl.matches("some")); + EXPECT_FALSE(cl.notExistsOrMatches("not_some")); + + struct ConstraintList cl2; + cl2.affinity = INTEGER_TYPE; + constraint = Constraint(LESS_THAN); + constraint.expr = "1000"; + cl2.add(constraint); + constraint = Constraint(GREATER_THAN); + constraint.expr = "1"; + cl2.add(constraint); + + // Test both SQL-provided string types. + EXPECT_TRUE(cl2.matches("10")); + // ...and the type literal. + EXPECT_TRUE(cl2.matches(10)); + + // Test operator lower bounds. + EXPECT_FALSE(cl2.matches(0)); + EXPECT_FALSE(cl2.matches(1)); + + // Test operator upper bounds. + EXPECT_FALSE(cl2.matches(1000)); + EXPECT_FALSE(cl2.matches(1001)); + + // Now test inclusive bounds. + struct ConstraintList cl3; + constraint = Constraint(LESS_THAN_OR_EQUALS); + constraint.expr = "1000"; + cl3.add(constraint); + constraint = Constraint(GREATER_THAN_OR_EQUALS); + constraint.expr = "1"; + cl3.add(constraint); + + EXPECT_FALSE(cl3.matches(1001)); + EXPECT_TRUE(cl3.matches(1000)); + + EXPECT_FALSE(cl3.matches(0)); + EXPECT_TRUE(cl3.matches(1)); } -TEST_F(TablesTests, test_constraint_map) { - ConstraintMap cm; +TEST_F(TablesTests, test_constraint_map) +{ + ConstraintMap cm; - cm["path"].add(Constraint(EQUALS, "some")); + cm["path"].add(Constraint(EQUALS, "some")); - // If a constraint list exists for a map key, normal constraints apply. - EXPECT_TRUE(cm["path"].matches("some")); - EXPECT_FALSE(cm["path"].matches("not_some")); + // If a constraint list exists for a map key, normal constraints apply. + EXPECT_TRUE(cm["path"].matches("some")); + EXPECT_FALSE(cm["path"].matches("not_some")); - // If a constraint list does not exist, then all checks will match. - // If there is no predicate clause then all results will match. - EXPECT_TRUE(cm["not_path"].matches("some")); - EXPECT_TRUE(cm["not_path"].notExistsOrMatches("some")); - EXPECT_FALSE(cm["not_path"].exists()); - EXPECT_FALSE(cm["not_path"].existsAndMatches("some")); + // If a constraint list does not exist, then all checks will match. + // If there is no predicate clause then all results will match. + EXPECT_TRUE(cm["not_path"].matches("some")); + EXPECT_TRUE(cm["not_path"].notExistsOrMatches("some")); + EXPECT_FALSE(cm["not_path"].exists()); + EXPECT_FALSE(cm["not_path"].existsAndMatches("some")); - // And of the column has constraints: - EXPECT_TRUE(cm["path"].notExistsOrMatches("some")); - EXPECT_FALSE(cm["path"].notExistsOrMatches("not_some")); - EXPECT_TRUE(cm["path"].exists()); - EXPECT_TRUE(cm["path"].existsAndMatches("some")); + // And of the column has constraints: + EXPECT_TRUE(cm["path"].notExistsOrMatches("some")); + EXPECT_FALSE(cm["path"].notExistsOrMatches("not_some")); + EXPECT_TRUE(cm["path"].exists()); + EXPECT_TRUE(cm["path"].existsAndMatches("some")); } -TEST_F(TablesTests, test_constraint_map_cast) { - ConstraintMap cm; +TEST_F(TablesTests, test_constraint_map_cast) +{ + ConstraintMap cm; - cm["num"].affinity = INTEGER_TYPE; - cm["num"].add(Constraint(EQUALS, "hello")); + cm["num"].affinity = INTEGER_TYPE; + cm["num"].add(Constraint(EQUALS, "hello")); - EXPECT_FALSE(cm["num"].existsAndMatches("hello")); + EXPECT_FALSE(cm["num"].existsAndMatches("hello")); } class TestTablePlugin : public TablePlugin { - public: - void testSetCache(size_t step, size_t interval) { - TableRows r; - QueryContext ctx; - ctx.useCache(true); - setCache(step, interval, ctx, r); - } - - bool testIsCached(size_t interval) { - QueryContext ctx; - ctx.useCache(true); - return isCached(interval, ctx); - } +public: + void testSetCache(size_t step, size_t interval) + { + TableRows r; + QueryContext ctx; + ctx.useCache(true); + setCache(step, interval, ctx, r); + } + + bool testIsCached(size_t interval) + { + QueryContext ctx; + ctx.useCache(true); + return isCached(interval, ctx); + } }; -TEST_F(TablesTests, test_caching) { - TestTablePlugin test; - // By default the interval and step is 0, so a step of 5 will not be cached. - EXPECT_FALSE(test.testIsCached(5)); - - TablePlugin::kCacheInterval = 5; - TablePlugin::kCacheStep = 1; - EXPECT_FALSE(test.testIsCached(5)); - // Set the current time to 1, and the interval at 5. - test.testSetCache(TablePlugin::kCacheStep, TablePlugin::kCacheInterval); - // Time at 1 is cached for an interval of 5, so at time 5 the cache is fresh. - EXPECT_TRUE(test.testIsCached(5)); - // 6 is the end of the cache, it is not fresh. - EXPECT_FALSE(test.testIsCached(6)); - // 7 is past the cache, it is not fresh. - EXPECT_FALSE(test.testIsCached(7)); - - // Set the time at now to 2. - TablePlugin::kCacheStep = 2; - test.testSetCache(TablePlugin::kCacheStep, TablePlugin::kCacheInterval); - EXPECT_TRUE(test.testIsCached(5)); - // Now 6 is within the freshness of 2 + 5. - EXPECT_TRUE(test.testIsCached(6)); - EXPECT_FALSE(test.testIsCached(7)); +TEST_F(TablesTests, test_caching) +{ + TestTablePlugin test; + // By default the interval and step is 0, so a step of 5 will not be cached. + EXPECT_FALSE(test.testIsCached(5)); + + TablePlugin::kCacheInterval = 5; + TablePlugin::kCacheStep = 1; + EXPECT_FALSE(test.testIsCached(5)); + // Set the current time to 1, and the interval at 5. + test.testSetCache(TablePlugin::kCacheStep, TablePlugin::kCacheInterval); + // Time at 1 is cached for an interval of 5, so at time 5 the cache is fresh. + EXPECT_TRUE(test.testIsCached(5)); + // 6 is the end of the cache, it is not fresh. + EXPECT_FALSE(test.testIsCached(6)); + // 7 is past the cache, it is not fresh. + EXPECT_FALSE(test.testIsCached(7)); + + // Set the time at now to 2. + TablePlugin::kCacheStep = 2; + test.testSetCache(TablePlugin::kCacheStep, TablePlugin::kCacheInterval); + EXPECT_TRUE(test.testIsCached(5)); + // Now 6 is within the freshness of 2 + 5. + EXPECT_TRUE(test.testIsCached(6)); + EXPECT_FALSE(test.testIsCached(7)); } } diff --git a/src/osquery/include/osquery/plugins/logger.h b/src/osquery/include/osquery/plugins/logger.h deleted file mode 120000 index f298b6f..0000000 --- a/src/osquery/include/osquery/plugins/logger.h +++ /dev/null @@ -1 +0,0 @@ -../../../core/plugins/logger.h \ No newline at end of file diff --git a/src/osquery/include/osquery/query.h b/src/osquery/include/osquery/query.h index 05b6158..4dfa95c 100644 --- a/src/osquery/include/osquery/query.h +++ b/src/osquery/include/osquery/query.h @@ -32,185 +32,187 @@ class Status; * results in potentially-differential form for a logger. */ struct QueryLogItem { - public: - /// Differential results from the query. - DiffResults results; +public: + /// Differential results from the query. + DiffResults results; - /// Optional snapshot results, no differential applied. - QueryDataTyped snapshot_results; + /// Optional snapshot results, no differential applied. + QueryDataTyped snapshot_results; - /// The name of the scheduled query. - std::string name; + /// The name of the scheduled query. + std::string name; - /// The identifier (hostname, or uuid) of the host. - std::string identifier; + /// The identifier (hostname, or uuid) of the host. + std::string identifier; - /// The time that the query was executed, seconds as UNIX time. - size_t time{0}; + /// The time that the query was executed, seconds as UNIX time. + size_t time{0}; - /// The epoch at the time the query was executed - uint64_t epoch{}; + /// The epoch at the time the query was executed + uint64_t epoch{}; - /// Query execution counter for current epoch - uint64_t counter{0}; + /// Query execution counter for current epoch + uint64_t counter{0}; - /// The time that the query was executed, an ASCII string. - std::string calendar_time; + /// The time that the query was executed, an ASCII string. + std::string calendar_time; - /// A set of additional fields to emit with the log line. - std::map decorations; + /// A set of additional fields to emit with the log line. + std::map decorations; - /// equals operator - bool operator==(const QueryLogItem& comp) const { - return (comp.results == results) && (comp.name == name); - } + /// equals operator + bool operator==(const QueryLogItem& comp) const + { + return (comp.results == results) && (comp.name == name); + } - /// not equals operator - bool operator!=(const QueryLogItem& comp) const { - return !(*this == comp); - } + /// not equals operator + bool operator!=(const QueryLogItem& comp) const + { + return !(*this == comp); + } }; /** * @brief Interact with the historical on-disk storage for a given query. */ class Query { - public: - /** - * @brief Constructor which sets up necessary parameters of a Query object. - * - * Given a query, this constructor calculates the value of columnFamily_, - * which can be accessed via the getColumnFamilyName getter method. - * - * @param name The query name. - * @param q a SheduledQuery struct. - */ - explicit Query(std::string name, const ScheduledQuery& q) - : query_(q.query), name_(std::move(name)) {} - - /** - * @brief Serialize the data in RocksDB into a useful data structure - * - * This method retrieves the data from RocksDB and returns the data in a - * std::multiset, in-order to apply binary search in diff function. - * - * @param results the output QueryDataSet struct. - * - * @return the success or failure of the operation. - */ - Status getPreviousQueryResults(QueryDataSet& results) const; - - /** - * @brief Get the epoch associated with the previous query results. - * - * This method retrieves the epoch associated with the results data that was - * was stored in rocksdb. - * - * @return the epoch associated with the previous query results. - */ - uint64_t getPreviousEpoch() const; - - /** - * @brief Get the query invocation counter. - * - * This method returns query invocation counter. If the query is a new query, - * 0 is returned. Otherwise the counter associated with the query is retrieved - * from database and incremented by 1. - * - * @param new_query Whether or not the query is new. - * - * @return the query invocation counter. - */ - uint64_t getQueryCounter(bool new_query) const; - - /** - * @brief Check if a given scheduled query exists in the database. - * - * @return true if the scheduled query already exists in the database. - */ - bool isQueryNameInDatabase() const; - - /** - * @brief Check if a query (not query name) is 'new' or altered. - * - * @return true if the scheduled query has not been altered. - */ - bool isNewQuery() const; - - /** - * @brief Add a new set of results to the persistent storage. - * - * Given the results of the execution of a scheduled query, add the results - * to the database using addNewResults. - * - * @param qd the QueryDataTyped object, which has the results of the query. - * @param epoch the epoch associated with QueryData - * @param counter [output] the output that holds the query execution counter. - * - * @return the success or failure of the operation. - */ - Status addNewResults(QueryDataTyped qd, - uint64_t epoch, - uint64_t& counter) const; - - /** - * @brief Add a new set of results to the persistent storage and get back - * the differential results. - * - * Given the results of an execution of a scheduled query, add the results - * to the database using addNewResults and get back a data structure - * indicating what rows in the query's results have changed. - * - * @param qd the QueryDataTyped object containing query results to store. - * @param epoch the epoch associated with QueryData - * @param counter the output that holds the query execution counter. - * @param dr an output to a DiffResults object populated based on last run. - * @param calculate_diff default true to populate dr. - * - * @return the success or failure of the operation. - */ - Status addNewResults(QueryDataTyped qd, - uint64_t epoch, - uint64_t& counter, - DiffResults& dr, - bool calculate_diff = true) const; - - /** - * @brief The most recent result set for a scheduled query. - * - * @param qd the output QueryData object. - * - * @return the success or failure of the operation. - */ - Status getCurrentResults(QueryData& qd); - - public: - /** - * @brief Get the names of all historical queries. - * - * If you'd like to perform some database maintenance, getStoredQueryNames() - * allows you to get a vector of the names of all queries which are - * currently stored in RocksDB - * - * @return a vector containing the string names of all scheduled queries. - */ - static std::vector getStoredQueryNames(); - - private: - /// The scheduled query's query string. - std::string query_; - - /// The scheduled query name. - std::string name_; - - private: - FRIEND_TEST(QueryTests, test_private_members); - FRIEND_TEST(QueryTests, test_add_and_get_current_results); - FRIEND_TEST(QueryTests, test_is_query_name_in_database); - FRIEND_TEST(QueryTests, test_get_stored_query_names); - FRIEND_TEST(QueryTests, test_get_executions); - FRIEND_TEST(QueryTests, test_get_query_results); - FRIEND_TEST(QueryTests, test_query_name_not_found_in_db); +public: + /** + * @brief Constructor which sets up necessary parameters of a Query object. + * + * Given a query, this constructor calculates the value of columnFamily_, + * which can be accessed via the getColumnFamilyName getter method. + * + * @param name The query name. + * @param q a SheduledQuery struct. + */ + explicit Query(std::string name, const ScheduledQuery& q) + : query_(q.query), name_(std::move(name)) {} + + /** + * @brief Serialize the data in RocksDB into a useful data structure + * + * This method retrieves the data from RocksDB and returns the data in a + * std::multiset, in-order to apply binary search in diff function. + * + * @param results the output QueryDataSet struct. + * + * @return the success or failure of the operation. + */ + Status getPreviousQueryResults(QueryDataSet& results) const; + + /** + * @brief Get the epoch associated with the previous query results. + * + * This method retrieves the epoch associated with the results data that was + * was stored in rocksdb. + * + * @return the epoch associated with the previous query results. + */ + uint64_t getPreviousEpoch() const; + + /** + * @brief Get the query invocation counter. + * + * This method returns query invocation counter. If the query is a new query, + * 0 is returned. Otherwise the counter associated with the query is retrieved + * from database and incremented by 1. + * + * @param new_query Whether or not the query is new. + * + * @return the query invocation counter. + */ + uint64_t getQueryCounter(bool new_query) const; + + /** + * @brief Check if a given scheduled query exists in the database. + * + * @return true if the scheduled query already exists in the database. + */ + bool isQueryNameInDatabase() const; + + /** + * @brief Check if a query (not query name) is 'new' or altered. + * + * @return true if the scheduled query has not been altered. + */ + bool isNewQuery() const; + + /** + * @brief Add a new set of results to the persistent storage. + * + * Given the results of the execution of a scheduled query, add the results + * to the database using addNewResults. + * + * @param qd the QueryDataTyped object, which has the results of the query. + * @param epoch the epoch associated with QueryData + * @param counter [output] the output that holds the query execution counter. + * + * @return the success or failure of the operation. + */ + Status addNewResults(QueryDataTyped qd, + uint64_t epoch, + uint64_t& counter) const; + + /** + * @brief Add a new set of results to the persistent storage and get back + * the differential results. + * + * Given the results of an execution of a scheduled query, add the results + * to the database using addNewResults and get back a data structure + * indicating what rows in the query's results have changed. + * + * @param qd the QueryDataTyped object containing query results to store. + * @param epoch the epoch associated with QueryData + * @param counter the output that holds the query execution counter. + * @param dr an output to a DiffResults object populated based on last run. + * @param calculate_diff default true to populate dr. + * + * @return the success or failure of the operation. + */ + Status addNewResults(QueryDataTyped qd, + uint64_t epoch, + uint64_t& counter, + DiffResults& dr, + bool calculate_diff = true) const; + + /** + * @brief The most recent result set for a scheduled query. + * + * @param qd the output QueryData object. + * + * @return the success or failure of the operation. + */ + Status getCurrentResults(QueryData& qd); + +public: + /** + * @brief Get the names of all historical queries. + * + * If you'd like to perform some database maintenance, getStoredQueryNames() + * allows you to get a vector of the names of all queries which are + * currently stored in RocksDB + * + * @return a vector containing the string names of all scheduled queries. + */ + static std::vector getStoredQueryNames(); + +private: + /// The scheduled query's query string. + std::string query_; + + /// The scheduled query name. + std::string name_; + +private: + FRIEND_TEST(QueryTests, test_private_members); + FRIEND_TEST(QueryTests, test_add_and_get_current_results); + FRIEND_TEST(QueryTests, test_is_query_name_in_database); + FRIEND_TEST(QueryTests, test_get_stored_query_names); + FRIEND_TEST(QueryTests, test_get_executions); + FRIEND_TEST(QueryTests, test_get_query_results); + FRIEND_TEST(QueryTests, test_query_name_not_found_in_db); }; } // namespace osquery diff --git a/src/osquery/include/osquery/registry_factory.h b/src/osquery/include/osquery/registry_factory.h index c564700..d444e32 100644 --- a/src/osquery/include/osquery/registry_factory.h +++ b/src/osquery/include/osquery/registry_factory.h @@ -19,149 +19,155 @@ namespace osquery { class RegistryFactory : private boost::noncopyable { - public: - /// Singleton accessor. - static RegistryFactory& get(); - - /** - * @brief Call a registry item. - * - * Registry 'calling' is the primary interaction osquery has with the Plugin - * APIs, which register items. Each item is an instance of a specialized - * Plugin, whose life/scope is maintained by the specific registry identified - * by a unique name. - * - * The specialized plugin type will expose a `call` method that parses a - * PluginRequest then perform some action and return a PluginResponse. - * Each registry provides a `call` method that performs the registry item - * (Plugin instance) look up, and passes and retrieves the request and - * response. - * - * @param registry_name The unique registry name containing item_name, - * @param item_name The name of the plugin used to REGISTER. - * @param request The PluginRequest object handled by the Plugin item. - * @param response The output. - * @return A status from the Plugin. - */ - static Status call(const std::string& registry_name, - const std::string& item_name, - const PluginRequest& request, - PluginResponse& response); - - /// A helper call that does not return a response (only status). - static Status call(const std::string& registry_name, - const std::string& item_name, - const PluginRequest& request); - - /// A helper call that uses the active plugin (if the registry has one). - static Status call(const std::string& registry_name, - const PluginRequest& request, - PluginResponse& response); - - /// A helper call that uses the active plugin (if the registry has one). - static Status call(const std::string& registry_name, - const PluginRequest& request); - - /// Run `setUp` on every registry that is not marked 'lazy'. - static void setUp(); - - public: - /// Direct access to a registry instance. - RegistryInterfaceRef registry(const std::string& registry_name) const; - - void add(const std::string& name, RegistryInterfaceRef reg); - - /// Direct access to all registries. - std::map all() const; - - /// Direct access to all plugin instances for a given registry name. - std::map plugins( - const std::string& registry_name) const; - - /// Direct access to a plugin instance. - PluginRef plugin(const std::string& registry_name, - const std::string& item_name) const; - - /// Adds an alias for an internal registry item. This registry will only - /// broadcast the alias name. - Status addAlias(const std::string& registry_name, - const std::string& item_name, - const std::string& alias); - - /// Returns the item_name or the item alias if an alias exists. - std::string getAlias(const std::string& registry_name, - const std::string& alias) const; - - /// Set a registry's active plugin. - Status setActive(const std::string& registry_name, - const std::string& item_name); - - /// Get a registry's active plugin. - std::string getActive(const std::string& registry_nane) const; - - bool exists(const std::string& registry_name) const { - return (registries_.count(registry_name) > 0); - } - - /// Check if a registry item exists, optionally search only local registries. - bool exists(const std::string& registry_name, - const std::string& item_name, - bool local = false) const; - - /// Get a list of the registry names. - std::vector names() const; - - /// Get a list of the registry item names for a given registry. - std::vector names(const std::string& registry_name) const; - - /// Return the number of registries. - size_t count() const { - return registries_.size(); - } - - /// Return the number of registry items for a given registry name. - size_t count(const std::string& registry_name) const; - - /// Enable/disable duplicate registry item support using aliasing. - void allowDuplicates(bool allow) { - allow_duplicates_ = allow; - } - - /// Check if duplicate registry items using registry aliasing are allowed. - bool allowDuplicates() { - return allow_duplicates_; - } - - private: - /// Check if the registries are locked. - bool locked() { - return locked_; - } - - /// Set the registry locked status. - void locked(bool locked) { - locked_ = locked; - } - - protected: - RegistryFactory() = default; - virtual ~RegistryFactory() = default; - - private: - /// Track duplicate registry item support, used for testing. - bool allow_duplicates_{false}; - - /// Track registry "locking", while locked a registry cannot add/create. - bool locked_{false}; - - /// The primary storage for constructed registries. - std::map registries_; - - /// Protector for broadcast lookups and external registry mutations. - mutable Mutex mutex_; - - private: - friend class RegistryInterface; +public: + /// Singleton accessor. + static RegistryFactory& get(); + + /** + * @brief Call a registry item. + * + * Registry 'calling' is the primary interaction osquery has with the Plugin + * APIs, which register items. Each item is an instance of a specialized + * Plugin, whose life/scope is maintained by the specific registry identified + * by a unique name. + * + * The specialized plugin type will expose a `call` method that parses a + * PluginRequest then perform some action and return a PluginResponse. + * Each registry provides a `call` method that performs the registry item + * (Plugin instance) look up, and passes and retrieves the request and + * response. + * + * @param registry_name The unique registry name containing item_name, + * @param item_name The name of the plugin used to REGISTER. + * @param request The PluginRequest object handled by the Plugin item. + * @param response The output. + * @return A status from the Plugin. + */ + static Status call(const std::string& registry_name, + const std::string& item_name, + const PluginRequest& request, + PluginResponse& response); + + /// A helper call that does not return a response (only status). + static Status call(const std::string& registry_name, + const std::string& item_name, + const PluginRequest& request); + + /// A helper call that uses the active plugin (if the registry has one). + static Status call(const std::string& registry_name, + const PluginRequest& request, + PluginResponse& response); + + /// A helper call that uses the active plugin (if the registry has one). + static Status call(const std::string& registry_name, + const PluginRequest& request); + + /// Run `setUp` on every registry that is not marked 'lazy'. + static void setUp(); + +public: + /// Direct access to a registry instance. + RegistryInterfaceRef registry(const std::string& registry_name) const; + + void add(const std::string& name, RegistryInterfaceRef reg); + + /// Direct access to all registries. + std::map all() const; + + /// Direct access to all plugin instances for a given registry name. + std::map plugins( + const std::string& registry_name) const; + + /// Direct access to a plugin instance. + PluginRef plugin(const std::string& registry_name, + const std::string& item_name) const; + + /// Adds an alias for an internal registry item. This registry will only + /// broadcast the alias name. + Status addAlias(const std::string& registry_name, + const std::string& item_name, + const std::string& alias); + + /// Returns the item_name or the item alias if an alias exists. + std::string getAlias(const std::string& registry_name, + const std::string& alias) const; + + /// Set a registry's active plugin. + Status setActive(const std::string& registry_name, + const std::string& item_name); + + /// Get a registry's active plugin. + std::string getActive(const std::string& registry_nane) const; + + bool exists(const std::string& registry_name) const + { + return (registries_.count(registry_name) > 0); + } + + /// Check if a registry item exists, optionally search only local registries. + bool exists(const std::string& registry_name, + const std::string& item_name, + bool local = false) const; + + /// Get a list of the registry names. + std::vector names() const; + + /// Get a list of the registry item names for a given registry. + std::vector names(const std::string& registry_name) const; + + /// Return the number of registries. + size_t count() const + { + return registries_.size(); + } + + /// Return the number of registry items for a given registry name. + size_t count(const std::string& registry_name) const; + + /// Enable/disable duplicate registry item support using aliasing. + void allowDuplicates(bool allow) + { + allow_duplicates_ = allow; + } + + /// Check if duplicate registry items using registry aliasing are allowed. + bool allowDuplicates() + { + return allow_duplicates_; + } + +private: + /// Check if the registries are locked. + bool locked() + { + return locked_; + } + + /// Set the registry locked status. + void locked(bool locked) + { + locked_ = locked; + } + +protected: + RegistryFactory() = default; + virtual ~RegistryFactory() = default; + +private: + /// Track duplicate registry item support, used for testing. + bool allow_duplicates_{false}; + + /// Track registry "locking", while locked a registry cannot add/create. + bool locked_{false}; + + /// The primary storage for constructed registries. + std::map registries_; + + /// Protector for broadcast lookups and external registry mutations. + mutable Mutex mutex_; + +private: + friend class RegistryInterface; }; /** @@ -180,67 +186,71 @@ namespace registries { template class AR : public AutoRegisterInterface { - public: - AR(const char* t, const char* n, bool optional) - : AutoRegisterInterface(t, n, optional) {} - - void run() override { - RegistryFactory::get().add( - type_, std::make_shared>(name_, optional_)); - } +public: + AR(const char* t, const char* n, bool optional) + : AutoRegisterInterface(t, n, optional) {} + + void run() override + { + RegistryFactory::get().add( + type_, std::make_shared>(name_, optional_)); + } }; template class AP : public AutoRegisterInterface { - public: - AP(const char* t, const char* n, bool optional) - : AutoRegisterInterface(t, n, optional) {} - - void run() override { - auto registry = RegistryFactory::get().registry(type_); - registry->add(name_, std::make_shared

(), optional_); - } +public: + AP(const char* t, const char* n, bool optional) + : AutoRegisterInterface(t, n, optional) {} + + void run() override + { + auto registry = RegistryFactory::get().registry(type_); + registry->add(name_, std::make_shared

(), optional_); + } }; template struct RI { - RI(const char* class_name, - const char* registry_name, - bool is_optional = false) { - AutoRegisterInterface::autoloadRegistry( - std::make_unique>(class_name, registry_name, is_optional)); - } + RI(const char* class_name, + const char* registry_name, + bool is_optional = false) + { + AutoRegisterInterface::autoloadRegistry( + std::make_unique>(class_name, registry_name, is_optional)); + } }; template struct PI { - PI(const char* registry_name, - const char* plugin_name, - bool is_optional = false) { - AutoRegisterInterface::autoloadPlugin( - std::make_unique>(registry_name, plugin_name, is_optional)); - } + PI(const char* registry_name, + const char* plugin_name, + bool is_optional = false) + { + AutoRegisterInterface::autoloadPlugin( + std::make_unique>(registry_name, plugin_name, is_optional)); + } }; } // namespace registries #define CREATE_REGISTRY(class_name, registry_name) \ - namespace registries { \ - const RI k##class_name(registry_name, registry_name, false); \ - } + namespace registries { \ + const RI k##class_name(registry_name, registry_name, false); \ + } #define CREATE_LAZY_REGISTRY(class_name, registry_name) \ - namespace registries { \ - const RI k##class_name(registry_name, registry_name, true); \ - } + namespace registries { \ + const RI k##class_name(registry_name, registry_name, true); \ + } #define REGISTER(class_name, registry_name, plugin_name) \ - namespace registries { \ - const PI k##class_name(registry_name, plugin_name, false); \ - } + namespace registries { \ + const PI k##class_name(registry_name, plugin_name, false); \ + } #define REGISTER_INTERNAL(class_name, registry_name, plugin_name) \ - namespace registries { \ - const PI k##class_name(registry_name, plugin_name, true); \ - } + namespace registries { \ + const PI k##class_name(registry_name, plugin_name, true); \ + } } // namespace osquery diff --git a/src/osquery/include/osquery/registry_interface.h b/src/osquery/include/osquery/registry_interface.h index 7f9f335..0d9fdf5 100644 --- a/src/osquery/include/osquery/registry_interface.h +++ b/src/osquery/include/osquery/registry_interface.h @@ -29,144 +29,144 @@ class Status; * @brief This is the registry interface. */ class RegistryInterface : private boost::noncopyable { - public: - explicit RegistryInterface(std::string name, bool auto_setup = false) - : name_(std::move(name)), auto_setup_(auto_setup) {} - virtual ~RegistryInterface() = default; - - /** - * @brief This is the only way to add plugins to a registry. - * - * It must be implemented by the templated child, which knows the type of - * registry and which can downcast the input plugin. - * - * @param plugin_name An indexable name for the plugin. - * @param plugin_item A type-specific plugin reference. - * @param internal true if this is internal to the osquery SDK. - */ - virtual Status add(const std::string& plugin_name, - const PluginRef& plugin_item, - bool internal = false) = 0; - - /** - * @brief Remove a registry item by its identifier. - * - * @param item_name An identifier for this registry plugin. - */ - void remove(const std::string& item_name); - - /// Allow a registry type to react to configuration updates. - virtual void configure(); - - /// Check if a given plugin name is considered internal. - bool isInternal(const std::string& item_name) const; - - /// Get the 'active' plugin, return success with the active plugin name. - std::string getActive() const; - - /// Allow others to introspect into the registered name (for reporting). - virtual std::string getName() const; - - /// Facility method to check if a registry item exists. - bool exists(const std::string& item_name, bool local = false) const; - - /// Facility method to count the number of items in this registry. - size_t count() const; - - /// Facility method to list the registry item identifiers. - std::vector names() const; - - /** - * @brief Allow a plugin to perform some setup functions when osquery starts. - * - * Doing work in a plugin constructor has unknown behavior. Plugins may - * be constructed at anytime during osquery's life, including global variable - * instantiation. To have a reliable state (aka, flags have been parsed, - * and logs are ready to stream), do construction work in Plugin::setUp. - * - * The registry `setUp` will iterate over all of its registry items and call - * their setup unless the registry is lazy (see CREATE_REGISTRY). - */ - virtual void setUp(); - - virtual PluginRef plugin(const std::string& plugin_name) const = 0; - - /// Construct and return a map of plugin names to their implementation. - std::map plugins(); - - protected: - /** - * @brief The only method a plugin user should call. - * - * Registry plugins are used internally and externally. They may belong - * to the process making the call or to an external process via a thrift - * transport. - * - * All plugin input and output must be serializable. The plugin types - * RegistryType usually exposes protected serialization methods for the - * data structures used by plugins (registry items). - * - * @param item_name The plugin identifier to call. - * @param request The plugin request, usually containing an action request. - * @param response If successful, the requested information. - * @return Success if the plugin was called, and response was filled. - */ - virtual Status call(const std::string& item_name, - const PluginRequest& request, - PluginResponse& response); - - /// Allow the registry to introspect into the registered name (for logging). - void setname(const std::string& name); - - /** - * @brief The implementation adder will call addPlugin. - * - * Once a downcast is completed the work for adding internal/external - * indexes is provided here. - */ - Status addPlugin(const std::string& plugin_name, - const PluginRef& plugin_item, - bool internal); - - /// Set an 'active' plugin to receive registry calls when no item name given. - Status setActive(const std::string& item_name); - - /// Create a registry item alias for a given item name. - Status addAlias(const std::string& item_name, const std::string& alias); - - /// Get the registry item name for a given alias. - std::string getAlias(const std::string& alias) const; - - protected: - /// The identifier for this registry, used to register items. - std::string name_; - - /// Does this registry run setUp on each registry item at initialization. - bool auto_setup_; - - protected: - /// A map of registered plugin instances to their registered identifier. - std::map items_; - - /// If aliases are used, a map of alias to item name. - std::map aliases_; - - /// Keep a lookup of registry items that are blacklisted from broadcast. - std::vector internal_; - - /// Support an 'active' mode where calls without a specific item name will - /// be directed to the 'active' plugin. - std::string active_; - - /// Protect concurrent accesses to object's data - mutable Mutex mutex_; - - private: - friend class RegistryFactory; - - bool isInternal_(const std::string& item_name) const; - - bool exists_(const std::string& item_name, bool local) const; +public: + explicit RegistryInterface(std::string name, bool auto_setup = false) + : name_(std::move(name)), auto_setup_(auto_setup) {} + virtual ~RegistryInterface() = default; + + /** + * @brief This is the only way to add plugins to a registry. + * + * It must be implemented by the templated child, which knows the type of + * registry and which can downcast the input plugin. + * + * @param plugin_name An indexable name for the plugin. + * @param plugin_item A type-specific plugin reference. + * @param internal true if this is internal to the osquery SDK. + */ + virtual Status add(const std::string& plugin_name, + const PluginRef& plugin_item, + bool internal = false) = 0; + + /** + * @brief Remove a registry item by its identifier. + * + * @param item_name An identifier for this registry plugin. + */ + void remove(const std::string& item_name); + + /// Allow a registry type to react to configuration updates. + virtual void configure(); + + /// Check if a given plugin name is considered internal. + bool isInternal(const std::string& item_name) const; + + /// Get the 'active' plugin, return success with the active plugin name. + std::string getActive() const; + + /// Allow others to introspect into the registered name (for reporting). + virtual std::string getName() const; + + /// Facility method to check if a registry item exists. + bool exists(const std::string& item_name, bool local = false) const; + + /// Facility method to count the number of items in this registry. + size_t count() const; + + /// Facility method to list the registry item identifiers. + std::vector names() const; + + /** + * @brief Allow a plugin to perform some setup functions when osquery starts. + * + * Doing work in a plugin constructor has unknown behavior. Plugins may + * be constructed at anytime during osquery's life, including global variable + * instantiation. To have a reliable state (aka, flags have been parsed, + * and logs are ready to stream), do construction work in Plugin::setUp. + * + * The registry `setUp` will iterate over all of its registry items and call + * their setup unless the registry is lazy (see CREATE_REGISTRY). + */ + virtual void setUp(); + + virtual PluginRef plugin(const std::string& plugin_name) const = 0; + + /// Construct and return a map of plugin names to their implementation. + std::map plugins(); + +protected: + /** + * @brief The only method a plugin user should call. + * + * Registry plugins are used internally and externally. They may belong + * to the process making the call or to an external process via a thrift + * transport. + * + * All plugin input and output must be serializable. The plugin types + * RegistryType usually exposes protected serialization methods for the + * data structures used by plugins (registry items). + * + * @param item_name The plugin identifier to call. + * @param request The plugin request, usually containing an action request. + * @param response If successful, the requested information. + * @return Success if the plugin was called, and response was filled. + */ + virtual Status call(const std::string& item_name, + const PluginRequest& request, + PluginResponse& response); + + /// Allow the registry to introspect into the registered name (for logging). + void setname(const std::string& name); + + /** + * @brief The implementation adder will call addPlugin. + * + * Once a downcast is completed the work for adding internal/external + * indexes is provided here. + */ + Status addPlugin(const std::string& plugin_name, + const PluginRef& plugin_item, + bool internal); + + /// Set an 'active' plugin to receive registry calls when no item name given. + Status setActive(const std::string& item_name); + + /// Create a registry item alias for a given item name. + Status addAlias(const std::string& item_name, const std::string& alias); + + /// Get the registry item name for a given alias. + std::string getAlias(const std::string& alias) const; + +protected: + /// The identifier for this registry, used to register items. + std::string name_; + + /// Does this registry run setUp on each registry item at initialization. + bool auto_setup_; + +protected: + /// A map of registered plugin instances to their registered identifier. + std::map items_; + + /// If aliases are used, a map of alias to item name. + std::map aliases_; + + /// Keep a lookup of registry items that are blacklisted from broadcast. + std::vector internal_; + + /// Support an 'active' mode where calls without a specific item name will + /// be directed to the 'active' plugin. + std::string active_; + + /// Protect concurrent accesses to object's data + mutable Mutex mutex_; + +private: + friend class RegistryFactory; + + bool isInternal_(const std::string& item_name) const; + + bool exists_(const std::string& item_name, bool local) const; }; /** @@ -178,45 +178,47 @@ class RegistryInterface : private boost::noncopyable { */ template class RegistryType : public RegistryInterface { - protected: - using PluginTypeRef = std::shared_ptr; - - public: - explicit RegistryType(const std::string& name, bool auto_setup = false) - : RegistryInterface(name, auto_setup) {} - ~RegistryType() override = default; - - Status add(const std::string& plugin_name, - const PluginRef& plugin_item, - bool internal = false) override { - if (nullptr == std::dynamic_pointer_cast(plugin_item)) { - throw std::runtime_error("Cannot add foreign plugin type: " + - plugin_name); - } - return addPlugin(plugin_name, plugin_item, internal); - } - - /** - * @brief A raw accessor for a registry plugin. - * - * If there is no plugin with an item_name identifier this will throw - * and out_of_range exception. - * - * @param plugin_name An identifier for this registry plugin. - * @return A std::shared_ptr of type RegistryType. - */ - PluginRef plugin(const std::string& plugin_name) const override { - ReadLock lock(mutex_); - - if (items_.count(plugin_name) == 0) { - return nullptr; - } - return items_.at(plugin_name); - } - - private: - FRIEND_TEST(EventsTests, test_event_subscriber_configure); - FRIEND_TEST(VirtualTableTests, test_indexing_costs); +protected: + using PluginTypeRef = std::shared_ptr; + +public: + explicit RegistryType(const std::string& name, bool auto_setup = false) + : RegistryInterface(name, auto_setup) {} + ~RegistryType() override = default; + + Status add(const std::string& plugin_name, + const PluginRef& plugin_item, + bool internal = false) override + { + if (nullptr == std::dynamic_pointer_cast(plugin_item)) { + throw std::runtime_error("Cannot add foreign plugin type: " + + plugin_name); + } + return addPlugin(plugin_name, plugin_item, internal); + } + + /** + * @brief A raw accessor for a registry plugin. + * + * If there is no plugin with an item_name identifier this will throw + * and out_of_range exception. + * + * @param plugin_name An identifier for this registry plugin. + * @return A std::shared_ptr of type RegistryType. + */ + PluginRef plugin(const std::string& plugin_name) const override + { + ReadLock lock(mutex_); + + if (items_.count(plugin_name) == 0) { + return nullptr; + } + return items_.at(plugin_name); + } + +private: + FRIEND_TEST(EventsTests, test_event_subscriber_configure); + FRIEND_TEST(VirtualTableTests, test_indexing_costs); }; /// Helper definitions for a shared pointer to the basic Registry type. @@ -226,34 +228,34 @@ class AutoRegisterInterface; using AutoRegisterSet = std::vector>; class AutoRegisterInterface { - public: - /// The registry name, or type identifier. - std::string type_; +public: + /// The registry name, or type identifier. + std::string type_; - /// The registry or plugin name. - std::string name_; + /// The registry or plugin name. + std::string name_; - /// Either autoload a registry, or create an internal plugin. - bool optional_; + /// Either autoload a registry, or create an internal plugin. + bool optional_; - AutoRegisterInterface(const char* _type, const char* _name, bool optional); - virtual ~AutoRegisterInterface() = default; + AutoRegisterInterface(const char* _type, const char* _name, bool optional); + virtual ~AutoRegisterInterface() = default; - /// A call-in for the iterator. - virtual void run() = 0; + /// A call-in for the iterator. + virtual void run() = 0; - public: - /// Access all registries. - static AutoRegisterSet& registries(); +public: + /// Access all registries. + static AutoRegisterSet& registries(); - /// Insert a new registry. - static void autoloadRegistry(std::unique_ptr ar_); + /// Insert a new registry. + static void autoloadRegistry(std::unique_ptr ar_); - /// Access all plugins. - static AutoRegisterSet& plugins(); + /// Access all plugins. + static AutoRegisterSet& plugins(); - /// Insert a new plugin. - static void autoloadPlugin(std::unique_ptr ar_); + /// Insert a new plugin. + static void autoloadPlugin(std::unique_ptr ar_); }; void registryAndPluginInit(); diff --git a/src/osquery/include/osquery/sql.h b/src/osquery/include/osquery/sql.h index d3bf6b0..aa2ea5e 100644 --- a/src/osquery/include/osquery/sql.h +++ b/src/osquery/include/osquery/sql.h @@ -36,123 +36,123 @@ namespace osquery { * @endcode */ class SQL : private only_movable { - public: - /** - * @brief Instantiate an instance of the class with a query. - * - * @param query An osquery SQL query. - * @param use_cache [optional] Set true to use the query cache. - */ - explicit SQL(const std::string& query, bool use_cache = false); - - /// Allow moving. - SQL(SQL&&) noexcept = default; - - /// Allow move assignment. - SQL& operator=(SQL&&) = default; - - public: - /** - * @brief Const accessor for the rows returned by the query. - * - * @return A QueryData object of the query results. - */ - const QueryData& rows() const; - - /** - * @brief Accessor for the rows returned by the query. - * - * @return A QueryData object of the query results. - */ - QueryData& rows(); - - /** - * @brief Column information for the query - * - * @return A ColumnNames object for the query - */ - const ColumnNames& columns() const; - - /** - * @brief Accessor to switch off of when checking the success of a query. - * - * @return A bool indicating the success or failure of the operation. - */ - bool ok() const; - - /** - * @brief Get the status returned by the query. - * - * @return The query status. - */ - const Status& getStatus() const; - - /** - * @brief Accessor for the message string indicating the status of the query. - * - * @return The message string indicating the status of the query. - */ - std::string getMessageString() const; - - - public: - /** - * @brief Get all, 'SELECT * ...', results given a virtual table name. - * - * @param table The name of the virtual table. - * @return A QueryData object of the 'SELECT *...' query results. - */ - static QueryData selectAllFrom(const std::string& table); - - /** - * @brief Get all with constraint, 'SELECT * ... where', results given - * a virtual table name and single constraint. - * - * @param table The name of the virtual table. - * @param column Table column name to apply constraint. - * @param op The SQL comparative operator. - * @param expr The constraint expression. - * @return A QueryData object of the 'SELECT *...' query results. - */ - static QueryData selectAllFrom(const std::string& table, - const std::string& column, - ConstraintOperator op, - const std::string& expr); - - /** - * @brief Get columns with constraint, 'SELECT [columns] ... where', results - * given a virtual table name, column names, and single constraint. - * - * @param columns the columns to return - * @param table The name of the virtual table. - * @param column Table column name to apply constraint. - * @param op The SQL comparative operator. - * @param expr The constraint expression. - * @return A QueryData object of the 'SELECT [columns] ...' query results. - */ - static QueryData selectFrom(const std::initializer_list& columns, - const std::string& table, - const std::string& column, - ConstraintOperator op, - const std::string& expr); - - protected: - /** - * @brief Private default constructor. - * - * The osquery::SQL class should only ever be instantiated with a query. - */ - SQL() = default; - - protected: - /// The internal member which holds the results of the query. - QueryData results_; - - /// The internal member which holds the status of the query. - Status status_; - - /// The internal member which holds the column names and order for the query - ColumnNames columns_; +public: + /** + * @brief Instantiate an instance of the class with a query. + * + * @param query An osquery SQL query. + * @param use_cache [optional] Set true to use the query cache. + */ + explicit SQL(const std::string& query, bool use_cache = false); + + /// Allow moving. + SQL(SQL&&) noexcept = default; + + /// Allow move assignment. + SQL& operator=(SQL&&) = default; + +public: + /** + * @brief Const accessor for the rows returned by the query. + * + * @return A QueryData object of the query results. + */ + const QueryData& rows() const; + + /** + * @brief Accessor for the rows returned by the query. + * + * @return A QueryData object of the query results. + */ + QueryData& rows(); + + /** + * @brief Column information for the query + * + * @return A ColumnNames object for the query + */ + const ColumnNames& columns() const; + + /** + * @brief Accessor to switch off of when checking the success of a query. + * + * @return A bool indicating the success or failure of the operation. + */ + bool ok() const; + + /** + * @brief Get the status returned by the query. + * + * @return The query status. + */ + const Status& getStatus() const; + + /** + * @brief Accessor for the message string indicating the status of the query. + * + * @return The message string indicating the status of the query. + */ + std::string getMessageString() const; + + +public: + /** + * @brief Get all, 'SELECT * ...', results given a virtual table name. + * + * @param table The name of the virtual table. + * @return A QueryData object of the 'SELECT *...' query results. + */ + static QueryData selectAllFrom(const std::string& table); + + /** + * @brief Get all with constraint, 'SELECT * ... where', results given + * a virtual table name and single constraint. + * + * @param table The name of the virtual table. + * @param column Table column name to apply constraint. + * @param op The SQL comparative operator. + * @param expr The constraint expression. + * @return A QueryData object of the 'SELECT *...' query results. + */ + static QueryData selectAllFrom(const std::string& table, + const std::string& column, + ConstraintOperator op, + const std::string& expr); + + /** + * @brief Get columns with constraint, 'SELECT [columns] ... where', results + * given a virtual table name, column names, and single constraint. + * + * @param columns the columns to return + * @param table The name of the virtual table. + * @param column Table column name to apply constraint. + * @param op The SQL comparative operator. + * @param expr The constraint expression. + * @return A QueryData object of the 'SELECT [columns] ...' query results. + */ + static QueryData selectFrom(const std::initializer_list& columns, + const std::string& table, + const std::string& column, + ConstraintOperator op, + const std::string& expr); + +protected: + /** + * @brief Private default constructor. + * + * The osquery::SQL class should only ever be instantiated with a query. + */ + SQL() = default; + +protected: + /// The internal member which holds the results of the query. + QueryData results_; + + /// The internal member which holds the status of the query. + Status status_; + + /// The internal member which holds the column names and order for the query + ColumnNames columns_; }; @@ -182,8 +182,8 @@ class SQL : private only_movable { * @return A status indicating query success. */ Status query(const std::string& query, - QueryData& results, - bool use_cache = false); + QueryData& results, + bool use_cache = false); /** * @brief Analyze a query, providing information about the result columns. diff --git a/src/osquery/include/osquery/tables.h b/src/osquery/include/osquery/tables.h index 232d8d6..091cb65 100644 --- a/src/osquery/include/osquery/tables.h +++ b/src/osquery/include/osquery/tables.h @@ -52,38 +52,46 @@ class Status; * time. */ template -inline std::string __sqliteField(const Type& source) noexcept { - return std::to_string(source); +inline std::string __sqliteField(const Type& source) noexcept +{ + return std::to_string(source); } template -inline std::string __sqliteField(const char (&source)[N]) noexcept { - return std::string(source, N - 1U); +inline std::string __sqliteField(const char (&source)[N]) noexcept +{ + return std::string(source, N - 1U); } template -inline std::string __sqliteField(const unsigned char (&source)[N]) noexcept { - return std::string(reinterpret_cast(source), N - 1U); +inline std::string __sqliteField(const unsigned char (&source)[N]) noexcept +{ + return std::string(reinterpret_cast(source), N - 1U); } -inline std::string __sqliteField(const char* source) noexcept { - return std::string(source); +inline std::string __sqliteField(const char* source) noexcept +{ + return std::string(source); } -inline std::string __sqliteField(char* const source) noexcept { - return std::string(source); +inline std::string __sqliteField(char* const source) noexcept +{ + return std::string(source); } -inline std::string __sqliteField(const unsigned char* source) noexcept { - return std::string(reinterpret_cast(source)); +inline std::string __sqliteField(const unsigned char* source) noexcept +{ + return std::string(reinterpret_cast(source)); } -inline std::string __sqliteField(unsigned char* const source) noexcept { - return std::string(reinterpret_cast(source)); +inline std::string __sqliteField(unsigned char* const source) noexcept +{ + return std::string(reinterpret_cast(source)); } -inline std::string __sqliteField(const std::string& source) noexcept { - return source; +inline std::string __sqliteField(const std::string& source) noexcept +{ + return source; } #ifdef WIN32 @@ -128,16 +136,16 @@ inline std::string __sqliteField(const std::string& source) noexcept { * expression the table generator may limit the data appropriately. */ enum ConstraintOperator : unsigned char { - EQUALS = 2, - GREATER_THAN = 4, - LESS_THAN_OR_EQUALS = 8, - LESS_THAN = 16, - GREATER_THAN_OR_EQUALS = 32, - MATCH = 64, - LIKE = 65, - GLOB = 66, - REGEXP = 67, - UNIQUE = 1, + EQUALS = 2, + GREATER_THAN = 4, + LESS_THAN_OR_EQUALS = 8, + LESS_THAN = 16, + GREATER_THAN_OR_EQUALS = 32, + MATCH = 64, + LIKE = 65, + GLOB = 66, + REGEXP = 67, + UNIQUE = 1, }; /// Type for flags for what constraint operators are admissible. @@ -152,17 +160,18 @@ using ConstraintOperatorFlag = unsigned char; * The constraint is applied to columns which have literal and affinity types. */ struct Constraint { - unsigned char op; - std::string expr; - - /// Construct a Constraint with the most-basic information, the operator. - explicit Constraint(unsigned char _op) { - op = _op; - } - - // A constraint list in a context knows only the operator at creation. - explicit Constraint(unsigned char _op, std::string _expr) - : op(_op), expr(std::move(_expr)) {} + unsigned char op; + std::string expr; + + /// Construct a Constraint with the most-basic information, the operator. + explicit Constraint(unsigned char _op) + { + op = _op; + } + + // A constraint list in a context knows only the operator at creation. + explicit Constraint(unsigned char _op, std::string _expr) + : op(_op), expr(std::move(_expr)) {} }; @@ -170,41 +179,43 @@ struct Constraint { * @brief Attributes about a Table implementation. */ enum class TableAttributes { - /// Default no-op attribute. - NONE = 0, + /// Default no-op attribute. + NONE = 0, - /// This table is a 'utility' and is always available locally. - UTILITY = 1, + /// This table is a 'utility' and is always available locally. + UTILITY = 1, - /// The results from this table may be cached within a schedule. - CACHEABLE = 2, + /// The results from this table may be cached within a schedule. + CACHEABLE = 2, - /// The results are backed by a set time-indexed, always growing, events. - EVENT_BASED = 4, + /// The results are backed by a set time-indexed, always growing, events. + EVENT_BASED = 4, - /// This table inspect items relative to each user, a JOIN may be required. - USER_BASED = 8, + /// This table inspect items relative to each user, a JOIN may be required. + USER_BASED = 8, - /// This table's data requires an osquery kernel extension/module. - KERNEL_REQUIRED = 16, + /// This table's data requires an osquery kernel extension/module. + KERNEL_REQUIRED = 16, }; /// Treat table attributes as a set of flags. -inline TableAttributes operator|(TableAttributes a, TableAttributes b) { - return static_cast(static_cast(a) | - static_cast(b)); +inline TableAttributes operator|(TableAttributes a, TableAttributes b) +{ + return static_cast(static_cast(a) | + static_cast(b)); } /// Treat column options as a set of flags. -inline size_t operator&(TableAttributes a, TableAttributes b) { - return static_cast(a) & static_cast(b); +inline size_t operator&(TableAttributes a, TableAttributes b) +{ + return static_cast(a) & static_cast(b); } /// Alias for an ordered list of column name and corresponding SQL type. using TableColumns = - std::vector>; + std::vector>; /// Alias for map of column alias sets. using ColumnAliasSet = std::map>; @@ -226,140 +237,145 @@ struct QueryContext; * A constraint list supports all AS_LITERAL types, and all ConstraintOperators. */ struct ConstraintList : private boost::noncopyable { - public: - /// The SQLite affinity type. - ColumnType affinity{TEXT_TYPE}; - - /** - * @brief Check if an expression matches the query constraints. - * - * Evaluate ALL constraints in this ConstraintList against the string - * expression. The affinity of the constraint will be used as the affinite - * and lexical type of the expression and set of constraint expressions. - * If there are no predicate constraints in this list, all expression will - * match. Constraints are limitations. - * - * @param expr a SQL type expression of the column literal type to check. - * @return If the expression matched all constraints. - */ - bool matches(const std::string& expr) const; - - /** - * @brief Check if an expression matches the query constraints. - * - * `matches` also supports the set of SQL affinite types. - * The expression expr will be evaluated as a string and compared using - * the affinity of the constraint. - * - * @param expr a SQL type expression of the column literal type to check. - * @return If the expression matched all constraints. - */ - template - bool matches(const T& expr) const { - return matches(SQL_TEXT(expr)); - } - - /** - * @brief Check and return if there are constraints on this column. - * - * A ConstraintList is used in a ConstraintMap with a column name as the - * map index. Tables that act on optional constraints should check if any - * constraint was provided. The ops parameter serves to specify which - * operators we want to check existence for. - * - * @param ops (Optional: default ANY_OP) The operators types to look for. - * @return true if any constraint exists. - */ - bool exists(ConstraintOperatorFlag ops = ANY_OP) const; - - /** - * @brief Check if a constraint exists AND matches the type expression. - * - * See ConstraintList::exists and ConstraintList::matches. - * - * @param expr The expression to match. - * @return true if any constraint exists AND matches the type expression. - */ - template - bool existsAndMatches(const T& expr) const { - return (exists() && matches(expr)); - } - - /** - * @brief Check if a constraint is missing or matches a type expression. - * - * A ConstraintList is used in a ConstraintMap with a column name as the - * map index. Tables that act on required constraints can make decisions - * on missing constraints or a constraint match. - * - * @param expr The expression to match. - * @return true if constraint is missing or matches the type expression. - */ - template - bool notExistsOrMatches(const T& expr) const { - return (!exists() || matches(expr)); - } - - /** - * @brief Helper templated function for ConstraintList::matches. - */ - template - bool literal_matches(const T& base_expr) const; - - /** - * @brief Get all expressions for a given ConstraintOperator. - * - * This is most useful if the table generation requires as column. - * The generator may `getAll(EQUALS)` then iterate. - * - * @param op the ConstraintOperator. - * @return A list of TEXT%-represented types matching the operator. - */ - std::set getAll(ConstraintOperator op) const; - - /// See ConstraintList::getAll, but as a selected literal type. - template - std::set getAll(ConstraintOperator op) const; - - /// Constraint list accessor, types and operator. - const std::vector& getAll() const { - return constraints_; - } - - /** - * @brief Add a new Constraint to the list of constraints. - * - * @param constraint a new operator/expression to constrain. - */ - void add(const struct Constraint& constraint) { - constraints_.push_back(constraint); - } - - /** - * @brief Serialize a ConstraintList into a property tree. - * - * The property tree will use the format: - * { - * "affinity": affinity, - * "list": [ - * {"op": op, "expr": expr}, ... - * ] - * } - */ - vist::json::Object serialize() const; - - void deserialize(vist::json::Object& obj); - - - private: - /// List of constraint operator/expressions. - std::vector constraints_; - - private: - friend struct QueryContext; - - private: - FRIEND_TEST(TablesTests, test_constraint_list); +public: + /// The SQLite affinity type. + ColumnType affinity{TEXT_TYPE}; + + /** + * @brief Check if an expression matches the query constraints. + * + * Evaluate ALL constraints in this ConstraintList against the string + * expression. The affinity of the constraint will be used as the affinite + * and lexical type of the expression and set of constraint expressions. + * If there are no predicate constraints in this list, all expression will + * match. Constraints are limitations. + * + * @param expr a SQL type expression of the column literal type to check. + * @return If the expression matched all constraints. + */ + bool matches(const std::string& expr) const; + + /** + * @brief Check if an expression matches the query constraints. + * + * `matches` also supports the set of SQL affinite types. + * The expression expr will be evaluated as a string and compared using + * the affinity of the constraint. + * + * @param expr a SQL type expression of the column literal type to check. + * @return If the expression matched all constraints. + */ + template + bool matches(const T& expr) const + { + return matches(SQL_TEXT(expr)); + } + + /** + * @brief Check and return if there are constraints on this column. + * + * A ConstraintList is used in a ConstraintMap with a column name as the + * map index. Tables that act on optional constraints should check if any + * constraint was provided. The ops parameter serves to specify which + * operators we want to check existence for. + * + * @param ops (Optional: default ANY_OP) The operators types to look for. + * @return true if any constraint exists. + */ + bool exists(ConstraintOperatorFlag ops = ANY_OP) const; + + /** + * @brief Check if a constraint exists AND matches the type expression. + * + * See ConstraintList::exists and ConstraintList::matches. + * + * @param expr The expression to match. + * @return true if any constraint exists AND matches the type expression. + */ + template + bool existsAndMatches(const T& expr) const + { + return (exists() && matches(expr)); + } + + /** + * @brief Check if a constraint is missing or matches a type expression. + * + * A ConstraintList is used in a ConstraintMap with a column name as the + * map index. Tables that act on required constraints can make decisions + * on missing constraints or a constraint match. + * + * @param expr The expression to match. + * @return true if constraint is missing or matches the type expression. + */ + template + bool notExistsOrMatches(const T& expr) const + { + return (!exists() || matches(expr)); + } + + /** + * @brief Helper templated function for ConstraintList::matches. + */ + template + bool literal_matches(const T& base_expr) const; + + /** + * @brief Get all expressions for a given ConstraintOperator. + * + * This is most useful if the table generation requires as column. + * The generator may `getAll(EQUALS)` then iterate. + * + * @param op the ConstraintOperator. + * @return A list of TEXT%-represented types matching the operator. + */ + std::set getAll(ConstraintOperator op) const; + + /// See ConstraintList::getAll, but as a selected literal type. + template + std::set getAll(ConstraintOperator op) const; + + /// Constraint list accessor, types and operator. + const std::vector& getAll() const + { + return constraints_; + } + + /** + * @brief Add a new Constraint to the list of constraints. + * + * @param constraint a new operator/expression to constrain. + */ + void add(const struct Constraint& constraint) + { + constraints_.push_back(constraint); + } + + /** + * @brief Serialize a ConstraintList into a property tree. + * + * The property tree will use the format: + * { + * "affinity": affinity, + * "list": [ + * {"op": op, "expr": expr}, ... + * ] + * } + */ + vist::json::Object serialize() const; + + void deserialize(vist::json::Object& obj); + + +private: + /// List of constraint operator/expressions. + std::vector constraints_; + +private: + friend struct QueryContext; + +private: + FRIEND_TEST(TablesTests, test_constraint_list); }; /// Pass a constraint map to the query request. @@ -372,8 +388,8 @@ using ConstraintSet = std::vector>; using UsedColumns = std::unordered_set; /// Keep track of which columns are used, as a bitset -using UsedColumnsBitset = std::bitset< - std::numeric_limits::digits>; +using UsedColumnsBitset = std::bitset < + std::numeric_limits::digits >; /** * @brief osquery table content descriptor. @@ -390,50 +406,50 @@ using UsedColumnsBitset = std::bitset< * a cache or choose not to generate certain columns. */ struct VirtualTableContent { - /// Friendly name for the table. - TableName name; - - /// Table column structure, retrieved once via the TablePlugin call API. - TableColumns columns; - - /// Attributes are copied into the content such that they can be quickly - /// passed to the SQL and optional Query for inspection. - TableAttributes attributes{TableAttributes::NONE}; - - /** - * @brief Table column aliases structure. - * - * This is used within xColumn to move content from special HIDDEN columns - * that act as aliases. If these columns are requested the content is moved - * from the new non-deprecated name. - */ - std::map aliases; - - /// Transient set of virtual table access constraints. - std::unordered_map constraints; - - /// Transient set of virtual table used columns - std::unordered_map colsUsed; - - /// Transient set of virtual table used columns (as bitmasks) - std::unordered_map colsUsedBitsets; - - /* - * @brief A table implementation specific query result cache. - * - * Virtual tables may 'cache' information between filter requests. This is - * intended to provide optimization for very latent/expensive tables where - * complex joins may result in duplicate filter requests. - * - * The cache is implemented as a map of row data. The cache concept - * should utilize a primary key as an index, and may store arbitrary data. - * More intense caching may use the backing store though the general database - * set and get calls. - * - * The in-memory, non-backing store, cache is expired after each query run. - * This caching does not affect or use the schedule results cache. - */ - std::map cache; + /// Friendly name for the table. + TableName name; + + /// Table column structure, retrieved once via the TablePlugin call API. + TableColumns columns; + + /// Attributes are copied into the content such that they can be quickly + /// passed to the SQL and optional Query for inspection. + TableAttributes attributes{TableAttributes::NONE}; + + /** + * @brief Table column aliases structure. + * + * This is used within xColumn to move content from special HIDDEN columns + * that act as aliases. If these columns are requested the content is moved + * from the new non-deprecated name. + */ + std::map aliases; + + /// Transient set of virtual table access constraints. + std::unordered_map constraints; + + /// Transient set of virtual table used columns + std::unordered_map colsUsed; + + /// Transient set of virtual table used columns (as bitmasks) + std::unordered_map colsUsedBitsets; + + /* + * @brief A table implementation specific query result cache. + * + * Virtual tables may 'cache' information between filter requests. This is + * intended to provide optimization for very latent/expensive tables where + * complex joins may result in duplicate filter requests. + * + * The cache is implemented as a map of row data. The cache concept + * should utilize a primary key as an index, and may store arbitrary data. + * More intense caching may use the backing store though the general database + * set and get calls. + * + * The in-memory, non-backing store, cache is expired after each query run. + * This caching does not affect or use the schedule results cache. + */ + std::map cache; }; /** @@ -441,206 +457,216 @@ struct VirtualTableContent { * on query components like predicate constraints and limits. */ struct QueryContext { - /// Construct a context without cache support. - QueryContext() { - table_ = std::make_shared(); - } - - /// If the context was created without content, it is ephemeral. - ~QueryContext() = default; - - /// Construct a context and set the table content for caching. - explicit QueryContext(std::shared_ptr content) - : enable_cache_(true), table_(std::move(content)) {} - - /// Disallow copying - QueryContext(const QueryContext&) = delete; - - /// Disallow copy assignment. - QueryContext& operator=(const QueryContext&) = delete; - - /// Allow moving. - QueryContext(QueryContext&& other) - : constraints(std::move(other.constraints)), - colsUsed(std::move(other.colsUsed)), - enable_cache_(other.enable_cache_), - use_cache_(other.use_cache_), - table_(other.table_) { - other.enable_cache_ = false; - other.table_ = nullptr; - } - - /// Allow move assignment. - QueryContext& operator=(QueryContext&& other) { - std::swap(constraints, other.constraints); - std::swap(colsUsed, other.colsUsed); - std::swap(enable_cache_, other.enable_cache_); - std::swap(use_cache_, other.use_cache_); - std::swap(table_, other.table_); - - return *this; - } - - /** - * @brief Check if a constraint exists for a given column operator pair. - * - * Operator and expression existence and matching occurs on the constraint - * list for a given column name. The query context maintains a map of columns - * to potentially empty constraint lists. Check if a constraint exists with - * any operator or for a specific operator, usually equality (EQUALS). - * - * @param column The name of a column within this table. - * @param op Check for a specific constraint operator (default EQUALS). - * @return true if a constraint exists, false if empty or no operator match. - */ - bool hasConstraint(const std::string& column, - ConstraintOperator op = EQUALS) const; - - /** - * @brief Apply a predicate function to each expression in a constraint list. - * - * Most constraint sets are use to extract expressions or perform a row - * generation for each expressions (given an operator). - * - * This prevents the caller (table implementation) from extracting the set - * and iterating separately on potentially duplicate and copied data. The - * predicate function is provided two arguments: - * - An iterating reference to each expression for the given operator. - * - * @param column The name of a column within this table. - * @param op The comparison or expression operator (e.g., EQUALS). - * @param predicate A predicate receiving each expression. - */ - template - void iteritems(const std::string& column, - ConstraintOperator op, - std::function predicate) const { - if (constraints.count(column) > 0) { - const auto& list = constraints.at(column); - if (list.affinity == TEXT_TYPE) { - for (const auto& constraint : list.constraints_) { - if (constraint.op == op) { - predicate(constraint.expr); - } - } - } else { - auto constraint_set = list.getAll(op); - for (const auto& constraint : constraint_set) { - predicate(constraint); - } - } - } - } - - /// Helper for string type (most all types are TEXT/VARCHAR). - void iteritems(const std::string& column, - ConstraintOperator op, - std::function predicate) const { - return iteritems(column, op, std::move(predicate)); - } - - /** - * @brief Expand a list of constraints into a set of values. - * - * This is most (perhaps only) helpful with filesystem globbing inputs. - * The requirement is a constraint column that takes an expandable input. - * This method will accept an expand predicate and return the aggregate set of - * expanded items. - * - * In the future this will be a templated type that restricts the predicate - * to act on the column's affinite type and returns a similar-typed set. - * - * @param column The name of a column within this table. - * @param op An operator to retrieve from the constraint list. - * @param output The output parameter, a set of expanded values. - * @param predicate A predicate lambda to apply to each constraint. - * @return An aggregate status, if any predicate fails the operation fails. - */ - Status expandConstraints( - const std::string& column, - ConstraintOperator op, - std::set& output, - std::function& output)> predicate); - - /// Check if the given column is used by the query - bool isColumnUsed(const std::string& colName) const; - - /// Check if any of the given columns is used by the query - bool isAnyColumnUsed(std::initializer_list colNames) const; - - inline bool isAnyColumnUsed(UsedColumnsBitset desiredBitset) const { - return !colsUsedBitset || (*colsUsedBitset & desiredBitset).any(); - } - - template - inline void setTextColumnIfUsed(Row& r, - const std::string& colName, - const Type& value) const { - if (isColumnUsed(colName)) { - r[colName] = TEXT(value); - } - } - - template - inline void setIntegerColumnIfUsed(Row& r, - const std::string& colName, - const Type& value) const { - if (isColumnUsed(colName)) { - r[colName] = INTEGER(value); - } - } - - template - inline void setBigIntColumnIfUsed(Row& r, - const std::string& colName, - const Type& value) const { - if (isColumnUsed(colName)) { - r[colName] = BIGINT(value); - } - } - - inline void setColumnIfUsed(Row& r, - const std::string& colName, - const std::string& value) const { - if (isColumnUsed(colName)) { - r[colName] = value; - } - } - - /// Check if a table-defined index exists within the query cache. - bool isCached(const std::string& index) const; - - /// Retrieve an index within the query cache. - TableRowHolder getCache(const std::string& index); - - /// Request the context use the warm query cache. - void useCache(bool use_cache); - - /// Check if the query requested use of the warm query cache. - bool useCache() const; - - /// Set the entire cache for an index. - void setCache(const std::string& index, const TableRowHolder& _cache); - - /// The map of column name to constraint list. - ConstraintMap constraints; - - boost::optional colsUsed; - boost::optional colsUsedBitset; - - private: - /// If false then the context is maintaining an ephemeral cache. - bool enable_cache_{false}; - - /// If the context is allowed to use the warm query cache. - bool use_cache_{false}; - - /// Persistent table content for table caching. - std::shared_ptr table_; - - private: - friend class TablePlugin; + /// Construct a context without cache support. + QueryContext() + { + table_ = std::make_shared(); + } + + /// If the context was created without content, it is ephemeral. + ~QueryContext() = default; + + /// Construct a context and set the table content for caching. + explicit QueryContext(std::shared_ptr content) + : enable_cache_(true), table_(std::move(content)) {} + + /// Disallow copying + QueryContext(const QueryContext&) = delete; + + /// Disallow copy assignment. + QueryContext& operator=(const QueryContext&) = delete; + + /// Allow moving. + QueryContext(QueryContext&& other) + : constraints(std::move(other.constraints)), + colsUsed(std::move(other.colsUsed)), + enable_cache_(other.enable_cache_), + use_cache_(other.use_cache_), + table_(other.table_) + { + other.enable_cache_ = false; + other.table_ = nullptr; + } + + /// Allow move assignment. + QueryContext& operator=(QueryContext&& other) + { + std::swap(constraints, other.constraints); + std::swap(colsUsed, other.colsUsed); + std::swap(enable_cache_, other.enable_cache_); + std::swap(use_cache_, other.use_cache_); + std::swap(table_, other.table_); + + return *this; + } + + /** + * @brief Check if a constraint exists for a given column operator pair. + * + * Operator and expression existence and matching occurs on the constraint + * list for a given column name. The query context maintains a map of columns + * to potentially empty constraint lists. Check if a constraint exists with + * any operator or for a specific operator, usually equality (EQUALS). + * + * @param column The name of a column within this table. + * @param op Check for a specific constraint operator (default EQUALS). + * @return true if a constraint exists, false if empty or no operator match. + */ + bool hasConstraint(const std::string& column, + ConstraintOperator op = EQUALS) const; + + /** + * @brief Apply a predicate function to each expression in a constraint list. + * + * Most constraint sets are use to extract expressions or perform a row + * generation for each expressions (given an operator). + * + * This prevents the caller (table implementation) from extracting the set + * and iterating separately on potentially duplicate and copied data. The + * predicate function is provided two arguments: + * - An iterating reference to each expression for the given operator. + * + * @param column The name of a column within this table. + * @param op The comparison or expression operator (e.g., EQUALS). + * @param predicate A predicate receiving each expression. + */ + template + void iteritems(const std::string& column, + ConstraintOperator op, + std::function predicate) const + { + if (constraints.count(column) > 0) { + const auto& list = constraints.at(column); + if (list.affinity == TEXT_TYPE) { + for (const auto& constraint : list.constraints_) { + if (constraint.op == op) { + predicate(constraint.expr); + } + } + } else { + auto constraint_set = list.getAll(op); + for (const auto& constraint : constraint_set) { + predicate(constraint); + } + } + } + } + + /// Helper for string type (most all types are TEXT/VARCHAR). + void iteritems(const std::string& column, + ConstraintOperator op, + std::function predicate) const + { + return iteritems(column, op, std::move(predicate)); + } + + /** + * @brief Expand a list of constraints into a set of values. + * + * This is most (perhaps only) helpful with filesystem globbing inputs. + * The requirement is a constraint column that takes an expandable input. + * This method will accept an expand predicate and return the aggregate set of + * expanded items. + * + * In the future this will be a templated type that restricts the predicate + * to act on the column's affinite type and returns a similar-typed set. + * + * @param column The name of a column within this table. + * @param op An operator to retrieve from the constraint list. + * @param output The output parameter, a set of expanded values. + * @param predicate A predicate lambda to apply to each constraint. + * @return An aggregate status, if any predicate fails the operation fails. + */ + Status expandConstraints( + const std::string& column, + ConstraintOperator op, + std::set& output, + std::function& output)> predicate); + + /// Check if the given column is used by the query + bool isColumnUsed(const std::string& colName) const; + + /// Check if any of the given columns is used by the query + bool isAnyColumnUsed(std::initializer_list colNames) const; + + inline bool isAnyColumnUsed(UsedColumnsBitset desiredBitset) const + { + return !colsUsedBitset || (*colsUsedBitset & desiredBitset).any(); + } + + template + inline void setTextColumnIfUsed(Row& r, + const std::string& colName, + const Type& value) const + { + if (isColumnUsed(colName)) { + r[colName] = TEXT(value); + } + } + + template + inline void setIntegerColumnIfUsed(Row& r, + const std::string& colName, + const Type& value) const + { + if (isColumnUsed(colName)) { + r[colName] = INTEGER(value); + } + } + + template + inline void setBigIntColumnIfUsed(Row& r, + const std::string& colName, + const Type& value) const + { + if (isColumnUsed(colName)) { + r[colName] = BIGINT(value); + } + } + + inline void setColumnIfUsed(Row& r, + const std::string& colName, + const std::string& value) const + { + if (isColumnUsed(colName)) { + r[colName] = value; + } + } + + /// Check if a table-defined index exists within the query cache. + bool isCached(const std::string& index) const; + + /// Retrieve an index within the query cache. + TableRowHolder getCache(const std::string& index); + + /// Request the context use the warm query cache. + void useCache(bool use_cache); + + /// Check if the query requested use of the warm query cache. + bool useCache() const; + + /// Set the entire cache for an index. + void setCache(const std::string& index, const TableRowHolder& _cache); + + /// The map of column name to constraint list. + ConstraintMap constraints; + + boost::optional colsUsed; + boost::optional colsUsedBitset; + +private: + /// If false then the context is maintaining an ephemeral cache. + bool enable_cache_{false}; + + /// If the context is allowed to use the warm query cache. + bool use_cache_{false}; + + /// Persistent table content for table caching. + std::shared_ptr table_; + +private: + friend class TablePlugin; }; using QueryContext = struct QueryContext; @@ -657,235 +683,245 @@ using Constraint = struct Constraint; * in osquery/tables/templates/default.cpp.in */ class TablePlugin : public Plugin { - public: - /** - * @brief Table name aliases create full-scan VIEWs for tables. - * - * Aliases allow table names to be changed/deprecated without breaking - * existing deployments and scheduled queries. - * - * @return A string vector of qtable name aliases. - */ - virtual std::vector aliases() const { - return {}; - } - - /// Return the table's column name and type pairs. - virtual TableColumns columns() const { - return TableColumns(); - } - - /// Define a map of target columns to optional aliases. - virtual ColumnAliasSet columnAliases() const { - return ColumnAliasSet(); - } - - /// Define a map of aliases to canoical columns - virtual AliasColumnMap aliasedColumns() const { - AliasColumnMap result; - ColumnAliasSet aliases = columnAliases(); - - for (const auto& columnAliases : aliases) { - const auto& columnName = columnAliases.first; - for (const auto& alias : columnAliases.second) { - result[alias] = columnName; - } - } - - return AliasColumnMap(); - } - - /// Return a set of attribute flags. - virtual TableAttributes attributes() const { - return TableAttributes::NONE; - } - - /** - * @brief Generate a complete table representation. - * - * The TablePlugin::generate method is the most important part of the table. - * This should return a best-effort match of the expected results for a - * query. In common cases, this returns all rows for a virtual table. - * For EventSubscriber tables this will perform database lookups for events - * matching several conditions such as time within the SQL query or the last - * time the EventSubscriber was called. - * - * The context input is filled in "as best possible" by SQLite's - * virtual table APIs. In the best case this context include a limit or - * constraints organized by each possible column. - * - * @param context A query context filled in by SQLite's virtual table API. - * @return The result rows for this table, given the query context. - */ - virtual TableRows generate(QueryContext& context) { - (void)context; - return TableRows(); - } - - /// Callback for DELETE statements - virtual QueryData delete_(QueryContext& context, - const PluginRequest& request) { - boost::ignore_unused(context); - boost::ignore_unused(request); - - return {{std::make_pair("status", "readonly")}}; - } - - /// Callback for INSERT statements - virtual QueryData insert(QueryContext& context, - const PluginRequest& request) { - boost::ignore_unused(context); - boost::ignore_unused(request); - - return {{std::make_pair("status", "readonly")}}; - } - - /// Callback for UPDATE statements - virtual QueryData update(QueryContext& context, - const PluginRequest& request) { - boost::ignore_unused(context); - boost::ignore_unused(request); - - return {{std::make_pair("status", "readonly")}}; - } - - protected: - /// An SQL table containing the table definition/syntax. - std::string columnDefinition(bool is_extension = false) const; - - /// Return the name and column pairs for attaching virtual tables. - PluginResponse routeInfo() const override; - - /** - * @brief Check if there are fresh cache results for this table. - * - * Table results are considered fresh when evaluated against a given interval. - * The interval is the expected rate for which this data should be generated. - * Caching and cache freshness only applies to queries acting on tables - * within a schedule. If two queries "one" and "two" both inspect the - * table "processes" at the interval 60. The first executed will cache results - * and the second will use the cached results. - * - * Table results are not cached if a QueryContext contains constraints or - * provides HOB (hand-off blocks) to additional tables within a query. - * Currently, the query scheduler cannot communicate to table implementations. - * An interval is set globally by the scheduler and passed to the table - * implementation as a future-proof API. There is no "shortcut" for caching - * when used in external tables. A cache lookup within an extension means - * a database call API and re-serialization to the virtual table APIs. In - * practice this does not perform well and is explicitly disabled. - * - * @param interval The interval this query expects the tables results. - * @param ctx The query context. - * @return True if the cache contains fresh results, otherwise false. - */ - bool isCached(size_t interval, const QueryContext& ctx) const; - - /** - * @brief Perform a database lookup of cached results and deserialize. - * - * If a query determined the table's cached results are fresh, it may ask the - * table to retrieve results from the database and deserialized them into - * table row data. - * - * @return The deserialized row data of cached results. - */ - TableRows getCache() const; - - /** - * @brief Similar to getCache, stores the results from generate. - * - * Set will serialize and save the results as JSON to be retrieved later. - * It will inspect the query context, if any required/indexed/optimized or - * additional columns are used then the cache will not be saved. - */ - void setCache(size_t step, - size_t interval, - const QueryContext& ctx, - const TableRows& results); - - private: - /// The last time in seconds the table data results were saved to cache. - size_t last_cached_{0}; - - /// The last interval in seconds when the table data was cached. - size_t last_interval_{0}; - - public: - /** - * @brief The scheduled interval for the executing query. - * - * Scheduled queries execute within a pseudo-mutex, and each may communicate - * their scheduled interval to internal TablePlugin implementations. If the - * table is cachable then the interval can be used to calculate freshness. - */ - static size_t kCacheInterval; - - /// The schedule step, this is the current position of the schedule. - static size_t kCacheStep; - - public: - /** - * @brief The registry call "router". - * - * Like all of osquery's Plugin%s, the TablePlugin uses a "call" router to - * handle requests and responses from extensions. The TablePlugin uses an - * "action" key, which can be: - * - generate: call the plugin's row generate method (defined in spec). - * - columns: return a list of column name and SQLite types. - * - definition: return an SQL statement for table creation. - * - * @param request The plugin request, must include an action key. - * @param response A plugin response, for generation this contains the rows. - */ - Status call(const PluginRequest& request, PluginResponse& response) override; - - public: - /// Helper data structure transformation methods. - static void setRequestFromContext(const QueryContext& context, - PluginRequest& request); - - public: - /** - * @brief Add a virtual table that exists in an extension. - * - * When external table plugins are registered the core will attach them - * as virtual tables to the SQL internal implementation. - * - * @param name The table name. - * @param info The route info (column name and type pairs). - */ - static Status addExternal(const std::string& name, - const PluginResponse& info); - - /// Remove an extension's table from the SQL virtual database. - static void removeExternal(const std::string& name); - - private: - /// Helper data structure transformation methods. - QueryContext getContextFromRequest(const PluginRequest& request) const; - - UsedColumnsBitset usedColumnsToBitset(const UsedColumns usedColumns) const; - friend class RegistryFactory; - FRIEND_TEST(VirtualTableTests, test_tableplugin_columndefinition); - FRIEND_TEST(VirtualTableTests, test_tableplugin_statement); - FRIEND_TEST(VirtualTableTests, test_indexing_costs); - FRIEND_TEST(VirtualTableTests, test_table_results_cache); - FRIEND_TEST(VirtualTableTests, test_yield_generator); +public: + /** + * @brief Table name aliases create full-scan VIEWs for tables. + * + * Aliases allow table names to be changed/deprecated without breaking + * existing deployments and scheduled queries. + * + * @return A string vector of qtable name aliases. + */ + virtual std::vector aliases() const + { + return {}; + } + + /// Return the table's column name and type pairs. + virtual TableColumns columns() const + { + return TableColumns(); + } + + /// Define a map of target columns to optional aliases. + virtual ColumnAliasSet columnAliases() const + { + return ColumnAliasSet(); + } + + /// Define a map of aliases to canoical columns + virtual AliasColumnMap aliasedColumns() const + { + AliasColumnMap result; + ColumnAliasSet aliases = columnAliases(); + + for (const auto& columnAliases : aliases) { + const auto& columnName = columnAliases.first; + for (const auto& alias : columnAliases.second) { + result[alias] = columnName; + } + } + + return AliasColumnMap(); + } + + /// Return a set of attribute flags. + virtual TableAttributes attributes() const + { + return TableAttributes::NONE; + } + + /** + * @brief Generate a complete table representation. + * + * The TablePlugin::generate method is the most important part of the table. + * This should return a best-effort match of the expected results for a + * query. In common cases, this returns all rows for a virtual table. + * For EventSubscriber tables this will perform database lookups for events + * matching several conditions such as time within the SQL query or the last + * time the EventSubscriber was called. + * + * The context input is filled in "as best possible" by SQLite's + * virtual table APIs. In the best case this context include a limit or + * constraints organized by each possible column. + * + * @param context A query context filled in by SQLite's virtual table API. + * @return The result rows for this table, given the query context. + */ + virtual TableRows generate(QueryContext& context) + { + (void)context; + return TableRows(); + } + + /// Callback for DELETE statements + virtual QueryData delete_(QueryContext& context, + const PluginRequest& request) + { + boost::ignore_unused(context); + boost::ignore_unused(request); + + return {{std::make_pair("status", "readonly")}}; + } + + /// Callback for INSERT statements + virtual QueryData insert(QueryContext& context, + const PluginRequest& request) + { + boost::ignore_unused(context); + boost::ignore_unused(request); + + return {{std::make_pair("status", "readonly")}}; + } + + /// Callback for UPDATE statements + virtual QueryData update(QueryContext& context, + const PluginRequest& request) + { + boost::ignore_unused(context); + boost::ignore_unused(request); + + return {{std::make_pair("status", "readonly")}}; + } + +protected: + /// An SQL table containing the table definition/syntax. + std::string columnDefinition(bool is_extension = false) const; + + /// Return the name and column pairs for attaching virtual tables. + PluginResponse routeInfo() const override; + + /** + * @brief Check if there are fresh cache results for this table. + * + * Table results are considered fresh when evaluated against a given interval. + * The interval is the expected rate for which this data should be generated. + * Caching and cache freshness only applies to queries acting on tables + * within a schedule. If two queries "one" and "two" both inspect the + * table "processes" at the interval 60. The first executed will cache results + * and the second will use the cached results. + * + * Table results are not cached if a QueryContext contains constraints or + * provides HOB (hand-off blocks) to additional tables within a query. + * Currently, the query scheduler cannot communicate to table implementations. + * An interval is set globally by the scheduler and passed to the table + * implementation as a future-proof API. There is no "shortcut" for caching + * when used in external tables. A cache lookup within an extension means + * a database call API and re-serialization to the virtual table APIs. In + * practice this does not perform well and is explicitly disabled. + * + * @param interval The interval this query expects the tables results. + * @param ctx The query context. + * @return True if the cache contains fresh results, otherwise false. + */ + bool isCached(size_t interval, const QueryContext& ctx) const; + + /** + * @brief Perform a database lookup of cached results and deserialize. + * + * If a query determined the table's cached results are fresh, it may ask the + * table to retrieve results from the database and deserialized them into + * table row data. + * + * @return The deserialized row data of cached results. + */ + TableRows getCache() const; + + /** + * @brief Similar to getCache, stores the results from generate. + * + * Set will serialize and save the results as JSON to be retrieved later. + * It will inspect the query context, if any required/indexed/optimized or + * additional columns are used then the cache will not be saved. + */ + void setCache(size_t step, + size_t interval, + const QueryContext& ctx, + const TableRows& results); + +private: + /// The last time in seconds the table data results were saved to cache. + size_t last_cached_{0}; + + /// The last interval in seconds when the table data was cached. + size_t last_interval_{0}; + +public: + /** + * @brief The scheduled interval for the executing query. + * + * Scheduled queries execute within a pseudo-mutex, and each may communicate + * their scheduled interval to internal TablePlugin implementations. If the + * table is cachable then the interval can be used to calculate freshness. + */ + static size_t kCacheInterval; + + /// The schedule step, this is the current position of the schedule. + static size_t kCacheStep; + +public: + /** + * @brief The registry call "router". + * + * Like all of osquery's Plugin%s, the TablePlugin uses a "call" router to + * handle requests and responses from extensions. The TablePlugin uses an + * "action" key, which can be: + * - generate: call the plugin's row generate method (defined in spec). + * - columns: return a list of column name and SQLite types. + * - definition: return an SQL statement for table creation. + * + * @param request The plugin request, must include an action key. + * @param response A plugin response, for generation this contains the rows. + */ + Status call(const PluginRequest& request, PluginResponse& response) override; + +public: + /// Helper data structure transformation methods. + static void setRequestFromContext(const QueryContext& context, + PluginRequest& request); + +public: + /** + * @brief Add a virtual table that exists in an extension. + * + * When external table plugins are registered the core will attach them + * as virtual tables to the SQL internal implementation. + * + * @param name The table name. + * @param info The route info (column name and type pairs). + */ + static Status addExternal(const std::string& name, + const PluginResponse& info); + + /// Remove an extension's table from the SQL virtual database. + static void removeExternal(const std::string& name); + +private: + /// Helper data structure transformation methods. + QueryContext getContextFromRequest(const PluginRequest& request) const; + + UsedColumnsBitset usedColumnsToBitset(const UsedColumns usedColumns) const; + friend class RegistryFactory; + FRIEND_TEST(VirtualTableTests, test_tableplugin_columndefinition); + FRIEND_TEST(VirtualTableTests, test_tableplugin_statement); + FRIEND_TEST(VirtualTableTests, test_indexing_costs); + FRIEND_TEST(VirtualTableTests, test_table_results_cache); + FRIEND_TEST(VirtualTableTests, test_yield_generator); }; /// Helper method to generate the virtual table CREATE statement. std::string columnDefinition(const TableColumns& columns, - bool is_extension = false); + bool is_extension = false); /// Helper method to generate the virtual table CREATE statement. std::string columnDefinition(const PluginResponse& response, - bool aliases = false, - bool is_extension = false); + bool aliases = false, + bool is_extension = false); /// Get the string representation for an SQLite column type. -inline const std::string& columnTypeName(ColumnType type) { - return kColumnTypeNames.at(type); +inline const std::string& columnTypeName(ColumnType type) +{ + return kColumnTypeNames.at(type); } /// Get the column type from the string representation. diff --git a/src/osquery/registry/registry_factory.cpp b/src/osquery/registry/registry_factory.cpp index fb2acff..8b64ece 100644 --- a/src/osquery/registry/registry_factory.cpp +++ b/src/osquery/registry/registry_factory.cpp @@ -18,155 +18,174 @@ namespace osquery { -RegistryFactory& RegistryFactory::get() { - static RegistryFactory instance; - return instance; +RegistryFactory& RegistryFactory::get() +{ + static RegistryFactory instance; + return instance; } -void RegistryFactory::add(const std::string& name, RegistryInterfaceRef reg) { - if (exists(name)) { - throw std::runtime_error("Cannot add duplicate registry: " + name); - } - registries_[name] = std::move(reg); +void RegistryFactory::add(const std::string& name, RegistryInterfaceRef reg) +{ + if (exists(name)) { + throw std::runtime_error("Cannot add duplicate registry: " + name); + } + registries_[name] = std::move(reg); } -RegistryInterfaceRef RegistryFactory::registry(const std::string& t) const { - if (!exists(t)) { - throw std::runtime_error("Unknown registry requested: " + t); - } - return registries_.at(t); +RegistryInterfaceRef RegistryFactory::registry(const std::string& t) const +{ + if (!exists(t)) { + throw std::runtime_error("Unknown registry requested: " + t); + } + return registries_.at(t); } -std::map RegistryFactory::all() const { - return registries_; +std::map RegistryFactory::all() const +{ + return registries_; } std::map RegistryFactory::plugins( - const std::string& registry_name) const { - return registry(registry_name)->plugins(); + const std::string& registry_name) const +{ + return registry(registry_name)->plugins(); } PluginRef RegistryFactory::plugin(const std::string& registry_name, - const std::string& item_name) const { - return registry(registry_name)->plugin(item_name); + const std::string& item_name) const +{ + return registry(registry_name)->plugin(item_name); } /// Adds an alias for an internal registry item. This registry will only /// broadcast the alias name. Status RegistryFactory::addAlias(const std::string& registry_name, - const std::string& item_name, - const std::string& alias) { - if (!exists(registry_name)) { - return Status(1, "Unknown registry: " + registry_name); - } - return registries_.at(registry_name)->addAlias(item_name, alias); + const std::string& item_name, + const std::string& alias) +{ + if (!exists(registry_name)) { + return Status(1, "Unknown registry: " + registry_name); + } + return registries_.at(registry_name)->addAlias(item_name, alias); } /// Returns the item_name or the item alias if an alias exists. std::string RegistryFactory::getAlias(const std::string& registry_name, - const std::string& alias) const { - if (!exists(registry_name)) { - return alias; - } - return registries_.at(registry_name)->getAlias(alias); + const std::string& alias) const +{ + if (!exists(registry_name)) { + return alias; + } + return registries_.at(registry_name)->getAlias(alias); } Status RegistryFactory::call(const std::string& registry_name, - const std::string& item_name, - const PluginRequest& request, - PluginResponse& response) { - // Forward factory call to the registry. - try { - if (item_name.find(',') != std::string::npos) { - // Call is multiplexing plugins (usually for multiple loggers). - for (const auto& item : osquery::split(item_name, ",")) { - get().registry(registry_name)->call(item, request, response); - } - // All multiplexed items are called without regard for statuses. - return Status(0); - } - return get().registry(registry_name)->call(item_name, request, response); - } catch (const std::exception& e) { - ERROR(OSQUERY) << registry_name << " registry " << item_name - << " plugin caused exception: " << e.what(); - return Status(1, e.what()); - } catch (...) { - ERROR(OSQUERY) << registry_name << " registry " << item_name - << " plugin caused unknown exception"; - return Status(2, "Unknown exception"); - } + const std::string& item_name, + const PluginRequest& request, + PluginResponse& response) +{ + // Forward factory call to the registry. + try { + if (item_name.find(',') != std::string::npos) { + // Call is multiplexing plugins (usually for multiple loggers). + for (const auto& item : osquery::split(item_name, ",")) { + get().registry(registry_name)->call(item, request, response); + } + // All multiplexed items are called without regard for statuses. + return Status(0); + } + return get().registry(registry_name)->call(item_name, request, response); + } catch (const std::exception& e) { + ERROR(OSQUERY) << registry_name << " registry " << item_name + << " plugin caused exception: " << e.what(); + return Status(1, e.what()); + } catch (...) { + ERROR(OSQUERY) << registry_name << " registry " << item_name + << " plugin caused unknown exception"; + return Status(2, "Unknown exception"); + } } Status RegistryFactory::call(const std::string& registry_name, - const std::string& item_name, - const PluginRequest& request) { - PluginResponse response; - // Wrapper around a call expecting a response. - return call(registry_name, item_name, request, response); + const std::string& item_name, + const PluginRequest& request) +{ + PluginResponse response; + // Wrapper around a call expecting a response. + return call(registry_name, item_name, request, response); } Status RegistryFactory::call(const std::string& registry_name, - const PluginRequest& request, - PluginResponse& response) { - auto plugin = get().registry(registry_name)->getActive(); - return call(registry_name, plugin, request, response); + const PluginRequest& request, + PluginResponse& response) +{ + auto plugin = get().registry(registry_name)->getActive(); + return call(registry_name, plugin, request, response); } Status RegistryFactory::call(const std::string& registry_name, - const PluginRequest& request) { - PluginResponse response; - return call(registry_name, request, response); + const PluginRequest& request) +{ + PluginResponse response; + return call(registry_name, request, response); } Status RegistryFactory::setActive(const std::string& registry_name, - const std::string& item_name) { - WriteLock lock(mutex_); - return registry(registry_name)->setActive(item_name); + const std::string& item_name) +{ + WriteLock lock(mutex_); + return registry(registry_name)->setActive(item_name); } -std::string RegistryFactory::getActive(const std::string& registry_name) const { - return registry(registry_name)->getActive(); +std::string RegistryFactory::getActive(const std::string& registry_name) const +{ + return registry(registry_name)->getActive(); } -void RegistryFactory::setUp() { - for (const auto& registry : get().all()) { - registry.second->setUp(); - } +void RegistryFactory::setUp() +{ + for (const auto& registry : get().all()) { + registry.second->setUp(); + } } bool RegistryFactory::exists(const std::string& registry_name, - const std::string& item_name, - bool local) const { - if (!exists(registry_name)) { - return false; - } + const std::string& item_name, + bool local) const +{ + if (!exists(registry_name)) { + return false; + } - // Check the registry. - return registry(registry_name)->exists(item_name, local); + // Check the registry. + return registry(registry_name)->exists(item_name, local); } -std::vector RegistryFactory::names() const { - std::vector names; - for (const auto& registry : all()) { - names.push_back(registry.second->getName()); - } - return names; +std::vector RegistryFactory::names() const +{ + std::vector names; + for (const auto& registry : all()) { + names.push_back(registry.second->getName()); + } + return names; } std::vector RegistryFactory::names( - const std::string& registry_name) const { - if (registries_.at(registry_name) == nullptr) { - std::vector names; - return names; - } - return registry(registry_name)->names(); -} - -size_t RegistryFactory::count(const std::string& registry_name) const { - if (!exists(registry_name)) { - return 0; - } - return registry(registry_name)->count(); + const std::string& registry_name) const +{ + if (registries_.at(registry_name) == nullptr) { + std::vector names; + return names; + } + return registry(registry_name)->names(); +} + +size_t RegistryFactory::count(const std::string& registry_name) const +{ + if (!exists(registry_name)) { + return 0; + } + return registry(registry_name)->count(); } } // namespace osquery diff --git a/src/osquery/registry/registry_interface.cpp b/src/osquery/registry/registry_interface.cpp index ddacdee..8205aa7 100644 --- a/src/osquery/registry/registry_interface.cpp +++ b/src/osquery/registry/registry_interface.cpp @@ -11,263 +11,286 @@ #include namespace osquery { -void RegistryInterface::remove(const std::string& item_name) { - if (items_.count(item_name) > 0) { - items_[item_name]->tearDown(); - items_.erase(item_name); - } - - // Populate list of aliases to remove (those that mask item_name). - std::vector removed_aliases; - for (const auto& alias : aliases_) { - if (alias.second == item_name) { - removed_aliases.push_back(alias.first); - } - } - - for (const auto& alias : removed_aliases) { - aliases_.erase(alias); - } +void RegistryInterface::remove(const std::string& item_name) +{ + if (items_.count(item_name) > 0) { + items_[item_name]->tearDown(); + items_.erase(item_name); + } + + // Populate list of aliases to remove (those that mask item_name). + std::vector removed_aliases; + for (const auto& alias : aliases_) { + if (alias.second == item_name) { + removed_aliases.push_back(alias.first); + } + } + + for (const auto& alias : removed_aliases) { + aliases_.erase(alias); + } } -bool RegistryInterface::isInternal(const std::string& item_name) const { - ReadLock lock(mutex_); +bool RegistryInterface::isInternal(const std::string& item_name) const +{ + ReadLock lock(mutex_); - return isInternal_(item_name); + return isInternal_(item_name); } -std::string RegistryInterface::getActive() const { - ReadLock lock(mutex_); +std::string RegistryInterface::getActive() const +{ + ReadLock lock(mutex_); - return active_; + return active_; } -std::string RegistryInterface::getName() const { - ReadLock lock(mutex_); +std::string RegistryInterface::getName() const +{ + ReadLock lock(mutex_); - return name_; + return name_; } -size_t RegistryInterface::count() const { - ReadLock lock(mutex_); +size_t RegistryInterface::count() const +{ + ReadLock lock(mutex_); - return items_.size(); + return items_.size(); } -Status RegistryInterface::setActive(const std::string& item_name) { -// FIXME -// UpgradeLock lock(mutex_); - - // Default support multiple active plugins. - for (const auto& item : osquery::split(item_name, ",")) { - if (items_.count(item) == 0) { - return Status::failure("Unknown registry plugin: " + item); - } - } - - Status status; - { -// FIXME -// WriteUpgradeLock wlock(lock); - active_ = item_name; - } - - // The active plugin is setup when initialized. - for (const auto& item : osquery::split(item_name, ",")) { - if (exists_(item, true)) { - status = RegistryFactory::get().plugin(name_, item)->setUp(); - } - - if (!status.ok()) { - break; - } - } - return status; +Status RegistryInterface::setActive(const std::string& item_name) +{ + // FIXME + // UpgradeLock lock(mutex_); + + // Default support multiple active plugins. + for (const auto& item : osquery::split(item_name, ",")) { + if (items_.count(item) == 0) { + return Status::failure("Unknown registry plugin: " + item); + } + } + + Status status; + { + // FIXME + // WriteUpgradeLock wlock(lock); + active_ = item_name; + } + + // The active plugin is setup when initialized. + for (const auto& item : osquery::split(item_name, ",")) { + if (exists_(item, true)) { + status = RegistryFactory::get().plugin(name_, item)->setUp(); + } + + if (!status.ok()) { + break; + } + } + return status; } Status RegistryInterface::call(const std::string& item_name, - const PluginRequest& request, - PluginResponse& response) { - if (item_name.empty()) { - return Status::failure("No registry item name specified"); - } - PluginRef plugin; - { - ReadLock lock(mutex_); - - // Search local plugins (items) for the plugin. - if (items_.count(item_name) > 0) { - plugin = items_.at(item_name); - } - } - if (plugin) { - return plugin->call(request, response); - } - - return Status::failure("Unknown registry name: " + item_name); + const PluginRequest& request, + PluginResponse& response) +{ + if (item_name.empty()) { + return Status::failure("No registry item name specified"); + } + PluginRef plugin; + { + ReadLock lock(mutex_); + + // Search local plugins (items) for the plugin. + if (items_.count(item_name) > 0) { + plugin = items_.at(item_name); + } + } + if (plugin) { + return plugin->call(request, response); + } + + return Status::failure("Unknown registry name: " + item_name); } Status RegistryInterface::addAlias(const std::string& item_name, - const std::string& alias) { - WriteLock lock(mutex_); - - if (aliases_.count(alias) > 0) { - return Status::failure("Duplicate alias: " + alias); - } - aliases_[alias] = item_name; - return Status::success(); + const std::string& alias) +{ + WriteLock lock(mutex_); + + if (aliases_.count(alias) > 0) { + return Status::failure("Duplicate alias: " + alias); + } + aliases_[alias] = item_name; + return Status::success(); } -std::string RegistryInterface::getAlias(const std::string& alias) const { - ReadLock lock(mutex_); +std::string RegistryInterface::getAlias(const std::string& alias) const +{ + ReadLock lock(mutex_); - if (aliases_.count(alias) == 0) { - return alias; - } - return aliases_.at(alias); + if (aliases_.count(alias) == 0) { + return alias; + } + return aliases_.at(alias); } Status RegistryInterface::addPlugin(const std::string& plugin_name, - const PluginRef& plugin_item, - bool internal) { - WriteLock lock(mutex_); + const PluginRef& plugin_item, + bool internal) +{ + WriteLock lock(mutex_); - if (items_.count(plugin_name) > 0) { - return Status::failure("Duplicate registry item exists: " + plugin_name); - } + if (items_.count(plugin_name) > 0) { + return Status::failure("Duplicate registry item exists: " + plugin_name); + } - plugin_item->setName(plugin_name); - items_.emplace(std::make_pair(plugin_name, plugin_item)); + plugin_item->setName(plugin_name); + items_.emplace(std::make_pair(plugin_name, plugin_item)); - // The item can be listed as internal, meaning it does not broadcast. - if (internal) { - internal_.push_back(plugin_name); - } + // The item can be listed as internal, meaning it does not broadcast. + if (internal) { + internal_.push_back(plugin_name); + } - return Status::success(); + return Status::success(); } -void RegistryInterface::setUp() { - ReadLock lock(mutex_); - - // If this registry does not auto-setup do NOT setup the registry items. - if (!auto_setup_) { - return; - } - - // If the registry is using a single 'active' plugin, setUp that plugin. - // For config and logger, only setUp the selected plugin. - if (active_.size() != 0 && exists_(active_, true)) { - items_.at(active_)->setUp(); - return; - } - - // Try to set up each of the registry items. - // If they fail, remove them from the registry. - std::vector failed; - for (auto& item : items_) { - if (!item.second->setUp().ok()) { - failed.push_back(item.first); - } - } - - for (const auto& failed_item : failed) { - remove(failed_item); - } +void RegistryInterface::setUp() +{ + ReadLock lock(mutex_); + + // If this registry does not auto-setup do NOT setup the registry items. + if (!auto_setup_) { + return; + } + + // If the registry is using a single 'active' plugin, setUp that plugin. + // For config and logger, only setUp the selected plugin. + if (active_.size() != 0 && exists_(active_, true)) { + items_.at(active_)->setUp(); + return; + } + + // Try to set up each of the registry items. + // If they fail, remove them from the registry. + std::vector failed; + for (auto& item : items_) { + if (!item.second->setUp().ok()) { + failed.push_back(item.first); + } + } + + for (const auto& failed_item : failed) { + remove(failed_item); + } } -void RegistryInterface::configure() { - ReadLock lock(mutex_); - - if (!active_.empty() && exists_(active_, true)) { - items_.at(active_)->configure(); - } else { - for (auto& item : items_) { - item.second->configure(); - } - } +void RegistryInterface::configure() +{ + ReadLock lock(mutex_); + + if (!active_.empty() && exists_(active_, true)) { + items_.at(active_)->configure(); + } else { + for (auto& item : items_) { + item.second->configure(); + } + } } /// Facility method to check if a registry item exists. -bool RegistryInterface::exists(const std::string& item_name, bool local) const { - ReadLock lock(mutex_); +bool RegistryInterface::exists(const std::string& item_name, bool local) const +{ + ReadLock lock(mutex_); - return exists_(item_name, local); + return exists_(item_name, local); } /// Facility method to list the registry item identifiers. -std::vector RegistryInterface::names() const { - ReadLock lock(mutex_); +std::vector RegistryInterface::names() const +{ + ReadLock lock(mutex_); - std::vector names; - for (const auto& item : items_) { - names.push_back(item.first); - } + std::vector names; + for (const auto& item : items_) { + names.push_back(item.first); + } - return names; + return names; } -std::map RegistryInterface::plugins() { - ReadLock lock(mutex_); +std::map RegistryInterface::plugins() +{ + ReadLock lock(mutex_); - return items_; + return items_; } -void RegistryInterface::setname(const std::string& name) { - WriteLock lock(mutex_); +void RegistryInterface::setname(const std::string& name) +{ + WriteLock lock(mutex_); - name_ = name; + name_ = name; } -bool RegistryInterface::isInternal_(const std::string& item_name) const { - if (std::find(internal_.begin(), internal_.end(), item_name) == - internal_.end()) { - return false; - } - return true; +bool RegistryInterface::isInternal_(const std::string& item_name) const +{ + if (std::find(internal_.begin(), internal_.end(), item_name) == + internal_.end()) { + return false; + } + return true; } bool RegistryInterface::exists_(const std::string& item_name, - bool local) const { - return (items_.count(item_name) > 0); + bool local) const +{ + return (items_.count(item_name) > 0); } AutoRegisterInterface::AutoRegisterInterface(const char* _type, - const char* _name, - bool optional) - : type_(_type), name_(_name), optional_(optional) {} - -AutoRegisterSet& AutoRegisterInterface::registries() { - static AutoRegisterSet registries_; - return registries_; + const char* _name, + bool optional) + : type_(_type), name_(_name), optional_(optional) {} + +AutoRegisterSet& AutoRegisterInterface::registries() +{ + static AutoRegisterSet registries_; + return registries_; } -AutoRegisterSet& AutoRegisterInterface::plugins() { - static AutoRegisterSet plugins_; - return plugins_; +AutoRegisterSet& AutoRegisterInterface::plugins() +{ + static AutoRegisterSet plugins_; + return plugins_; } void AutoRegisterInterface::autoloadRegistry( - std::unique_ptr ar_) { - registries().push_back(std::move(ar_)); + std::unique_ptr ar_) +{ + registries().push_back(std::move(ar_)); } void AutoRegisterInterface::autoloadPlugin( - std::unique_ptr ar_) { - plugins().push_back(std::move(ar_)); + std::unique_ptr ar_) +{ + plugins().push_back(std::move(ar_)); } -void registryAndPluginInit() { - for (const auto& it : AutoRegisterInterface::registries()) { - it->run(); - } +void registryAndPluginInit() +{ + for (const auto& it : AutoRegisterInterface::registries()) { + it->run(); + } - for (const auto& it : AutoRegisterInterface::plugins()) { - it->run(); - } + for (const auto& it : AutoRegisterInterface::plugins()) { + it->run(); + } - AutoRegisterSet().swap(AutoRegisterInterface::registries()); - AutoRegisterSet().swap(AutoRegisterInterface::plugins()); + AutoRegisterSet().swap(AutoRegisterInterface::registries()); + AutoRegisterSet().swap(AutoRegisterInterface::plugins()); } } // namespace osquery diff --git a/src/osquery/registry/tests/registry.cpp b/src/osquery/registry/tests/registry.cpp index 9e643e4..81a6ef4 100644 --- a/src/osquery/registry/tests/registry.cpp +++ b/src/osquery/registry/tests/registry.cpp @@ -18,222 +18,238 @@ namespace osquery { class TestCoreRegistry : public RegistryFactory {}; class CatPlugin : public Plugin { - public: - CatPlugin() : some_value_(0) {} +public: + CatPlugin() : some_value_(0) {} - Status call(const PluginRequest&, PluginResponse&) override { - return Status(0); - } + Status call(const PluginRequest&, PluginResponse&) override + { + return Status(0); + } - protected: - int some_value_; +protected: + int some_value_; }; class DogPlugin : public Plugin { - public: - DogPlugin() : some_value_(10000) {} +public: + DogPlugin() : some_value_(10000) {} - Status call(const PluginRequest&, PluginResponse&) override { - return Status(0); - } + Status call(const PluginRequest&, PluginResponse&) override + { + return Status(0); + } - protected: - int some_value_; +protected: + int some_value_; }; class RegistryTests : public testing::Test { - public: - void SetUp() override { - if (!kSetUp) { - TestCoreRegistry::get().add( - "cat", std::make_shared>("cat")); - TestCoreRegistry::get().add( - "dog", std::make_shared>("dog")); - kSetUp = true; - } - } - - static bool kSetUp; +public: + void SetUp() override + { + if (!kSetUp) { + TestCoreRegistry::get().add( + "cat", std::make_shared>("cat")); + TestCoreRegistry::get().add( + "dog", std::make_shared>("dog")); + kSetUp = true; + } + } + + static bool kSetUp; }; bool RegistryTests::kSetUp{false}; class HouseCat : public CatPlugin { - public: - Status setUp() { - // Make sure the Plugin implementation's init is called. - some_value_ = 9000; - return Status::success(); - } +public: + Status setUp() + { + // Make sure the Plugin implementation's init is called. + some_value_ = 9000; + return Status::success(); + } }; /// This is a manual registry type without a name, so we cannot broadcast /// this registry type and it does NOT need to conform to a registry API. class CatRegistry : public RegistryType { - public: - CatRegistry(const std::string& name) : RegistryType(name) {} +public: + CatRegistry(const std::string& name) : RegistryType(name) {} }; -TEST_F(RegistryTests, test_registry) { - CatRegistry cats("cats"); +TEST_F(RegistryTests, test_registry) +{ + CatRegistry cats("cats"); - /// Add a CatRegistry item (a plugin) called "house". - cats.add("house", std::make_shared()); - EXPECT_EQ(cats.count(), 1U); + /// Add a CatRegistry item (a plugin) called "house". + cats.add("house", std::make_shared()); + EXPECT_EQ(cats.count(), 1U); - /// Try to add the same plugin with the same name, this is meaningless. - cats.add("house", std::make_shared()); + /// Try to add the same plugin with the same name, this is meaningless. + cats.add("house", std::make_shared()); - /// Now add the same plugin with a different name, a new plugin instance - /// will be created and registered. - cats.add("house2", std::make_shared()); - EXPECT_EQ(cats.count(), 2U); + /// Now add the same plugin with a different name, a new plugin instance + /// will be created and registered. + cats.add("house2", std::make_shared()); + EXPECT_EQ(cats.count(), 2U); - /// Request a plugin to call an API method. - auto cat = cats.plugin("house"); - cats.setUp(); + /// Request a plugin to call an API method. + auto cat = cats.plugin("house"); + cats.setUp(); - /// Now let's iterate over every registered Cat plugin. - EXPECT_EQ(cats.plugins().size(), 2U); + /// Now let's iterate over every registered Cat plugin. + EXPECT_EQ(cats.plugins().size(), 2U); } -TEST_F(RegistryTests, test_auto_factory) { - /// Using the registry, and a registry type by name, we can register a - /// plugin HouseCat called "house" like above. - auto cat_registry = TestCoreRegistry::get().registry("cat"); - cat_registry->add("auto_house", std::make_shared()); - cat_registry->setUp(); - - /// When acting on registries by name we can check the broadcasted - /// registry name of other plugin processes (via Thrift) as well as - /// internally registered plugins like HouseCat. - EXPECT_EQ(TestCoreRegistry::get().registry("cat")->count(), 1U); - EXPECT_EQ(TestCoreRegistry::get().count("cat"), 1U); - - /// And we can call an API method, since we guarantee CatPlugins conform - /// to the "TestCoreRegistry"'s "TestPluginAPI". - auto cat = TestCoreRegistry::get().plugin("cat", "auto_house"); - auto same_cat = TestCoreRegistry::get().plugin("cat", "auto_house"); - EXPECT_EQ(cat, same_cat); +TEST_F(RegistryTests, test_auto_factory) +{ + /// Using the registry, and a registry type by name, we can register a + /// plugin HouseCat called "house" like above. + auto cat_registry = TestCoreRegistry::get().registry("cat"); + cat_registry->add("auto_house", std::make_shared()); + cat_registry->setUp(); + + /// When acting on registries by name we can check the broadcasted + /// registry name of other plugin processes (via Thrift) as well as + /// internally registered plugins like HouseCat. + EXPECT_EQ(TestCoreRegistry::get().registry("cat")->count(), 1U); + EXPECT_EQ(TestCoreRegistry::get().count("cat"), 1U); + + /// And we can call an API method, since we guarantee CatPlugins conform + /// to the "TestCoreRegistry"'s "TestPluginAPI". + auto cat = TestCoreRegistry::get().plugin("cat", "auto_house"); + auto same_cat = TestCoreRegistry::get().plugin("cat", "auto_house"); + EXPECT_EQ(cat, same_cat); } class Doge : public DogPlugin { - public: - Doge() { - some_value_ = 100000; - } +public: + Doge() + { + some_value_ = 100000; + } }; class BadDoge : public DogPlugin { - public: - Status setUp() { - return Status(1, "Expect error... this is a bad dog"); - } +public: + Status setUp() + { + return Status(1, "Expect error... this is a bad dog"); + } }; -TEST_F(RegistryTests, test_auto_registries) { - auto dog_registry = TestCoreRegistry::get().registry("dog"); - dog_registry->add("doge", std::make_shared()); - dog_registry->setUp(); +TEST_F(RegistryTests, test_auto_registries) +{ + auto dog_registry = TestCoreRegistry::get().registry("dog"); + dog_registry->add("doge", std::make_shared()); + dog_registry->setUp(); - EXPECT_EQ(TestCoreRegistry::get().count("dog"), 1U); + EXPECT_EQ(TestCoreRegistry::get().count("dog"), 1U); } -TEST_F(RegistryTests, test_persistent_registries) { - EXPECT_EQ(TestCoreRegistry::get().count("cat"), 1U); +TEST_F(RegistryTests, test_persistent_registries) +{ + EXPECT_EQ(TestCoreRegistry::get().count("cat"), 1U); } -TEST_F(RegistryTests, test_registry_exceptions) { - auto dog_registry = TestCoreRegistry::get().registry("dog"); - EXPECT_TRUE(dog_registry->add("doge2", std::make_shared()).ok()); - // Bad dog will be added fine. - EXPECT_TRUE(dog_registry->add("bad_doge", std::make_shared()).ok()); - dog_registry->setUp(); - // Make sure bad dog does exist. - EXPECT_TRUE(TestCoreRegistry::get().exists("dog", "bad_doge")); - EXPECT_EQ(TestCoreRegistry::get().count("dog"), 3U); - - unsigned int exception_count = 0; - try { - TestCoreRegistry::get().registry("does_not_exist"); - } catch (const std::runtime_error& /* e */) { - exception_count++; - } - - EXPECT_EQ(exception_count, 1U); +TEST_F(RegistryTests, test_registry_exceptions) +{ + auto dog_registry = TestCoreRegistry::get().registry("dog"); + EXPECT_TRUE(dog_registry->add("doge2", std::make_shared()).ok()); + // Bad dog will be added fine. + EXPECT_TRUE(dog_registry->add("bad_doge", std::make_shared()).ok()); + dog_registry->setUp(); + // Make sure bad dog does exist. + EXPECT_TRUE(TestCoreRegistry::get().exists("dog", "bad_doge")); + EXPECT_EQ(TestCoreRegistry::get().count("dog"), 3U); + + unsigned int exception_count = 0; + try { + TestCoreRegistry::get().registry("does_not_exist"); + } catch (const std::runtime_error& /* e */) { + exception_count++; + } + + EXPECT_EQ(exception_count, 1U); } class WidgetPlugin : public Plugin { - public: - /// The route information will usually be provided by the plugin type. - /// The plugin/registry item will set some structures for the plugin - /// to parse and format. BUT a plugin/registry item can also fill this - /// information in if the plugin type/registry type exposes routeInfo as - /// a virtual method. - PluginResponse routeInfo() const { - PluginResponse info; - info.push_back({{"name", name_}}); - return info; - } - - /// Plugin types should contain generic request/response formatters and - /// decorators. - std::string secretPower(const PluginRequest& request) const { - if (request.count("secret_power") > 0U) { - return request.at("secret_power"); - } - return "no_secret_power"; - } +public: + /// The route information will usually be provided by the plugin type. + /// The plugin/registry item will set some structures for the plugin + /// to parse and format. BUT a plugin/registry item can also fill this + /// information in if the plugin type/registry type exposes routeInfo as + /// a virtual method. + PluginResponse routeInfo() const + { + PluginResponse info; + info.push_back({{"name", name_}}); + return info; + } + + /// Plugin types should contain generic request/response formatters and + /// decorators. + std::string secretPower(const PluginRequest& request) const + { + if (request.count("secret_power") > 0U) { + return request.at("secret_power"); + } + return "no_secret_power"; + } }; class SpecialWidget : public WidgetPlugin { - public: - Status call(const PluginRequest& request, PluginResponse& response); +public: + Status call(const PluginRequest& request, PluginResponse& response); }; Status SpecialWidget::call(const PluginRequest& request, - PluginResponse& response) { - response.push_back(request); - response[0]["from"] = name_; - response[0]["secret_power"] = secretPower(request); - return Status::success(); + PluginResponse& response) +{ + response.push_back(request); + response[0]["from"] = name_; + response[0]["secret_power"] = secretPower(request); + return Status::success(); } #define UNUSED(x) (void)(x) -TEST_F(RegistryTests, test_registry_api) { - TestCoreRegistry::get().add( - "widgets", std::make_shared>("widgets")); +TEST_F(RegistryTests, test_registry_api) +{ + TestCoreRegistry::get().add( + "widgets", std::make_shared>("widgets")); - auto widgets = TestCoreRegistry::get().registry("widgets"); - widgets->add("special", std::make_shared()); + auto widgets = TestCoreRegistry::get().registry("widgets"); + widgets->add("special", std::make_shared()); - // Test route info propagation, from item to registry, to broadcast. - auto ri = TestCoreRegistry::get().plugin("widgets", "special")->routeInfo(); - EXPECT_EQ(ri[0].at("name"), "special"); + // Test route info propagation, from item to registry, to broadcast. + auto ri = TestCoreRegistry::get().plugin("widgets", "special")->routeInfo(); + EXPECT_EQ(ri[0].at("name"), "special"); - PluginResponse response; - PluginRequest request; - auto status = TestCoreRegistry::call("widgets", "special", request, response); - EXPECT_TRUE(status.ok()); - EXPECT_EQ(response[0].at("from"), "special"); - EXPECT_EQ(response[0].at("secret_power"), "no_secret_power"); + PluginResponse response; + PluginRequest request; + auto status = TestCoreRegistry::call("widgets", "special", request, response); + EXPECT_TRUE(status.ok()); + EXPECT_EQ(response[0].at("from"), "special"); + EXPECT_EQ(response[0].at("secret_power"), "no_secret_power"); - request["secret_power"] = "magic"; - status = TestCoreRegistry::call("widgets", "special", request, response); - EXPECT_EQ(response[0].at("secret_power"), "magic"); + request["secret_power"] = "magic"; + status = TestCoreRegistry::call("widgets", "special", request, response); + EXPECT_EQ(response[0].at("secret_power"), "magic"); } -TEST_F(RegistryTests, test_real_registry) { - EXPECT_TRUE(Registry::get().count() > 0U); - - bool has_one_registered = false; - for (const auto& registry : Registry::get().all()) { - if (Registry::get().count(registry.first) > 0) { - has_one_registered = true; - break; - } - } - EXPECT_TRUE(has_one_registered); +TEST_F(RegistryTests, test_real_registry) +{ + EXPECT_TRUE(Registry::get().count() > 0U); + + bool has_one_registered = false; + for (const auto& registry : Registry::get().all()) { + if (Registry::get().count(registry.first) > 0) { + has_one_registered = true; + break; + } + } + EXPECT_TRUE(has_one_registered); } } diff --git a/src/osquery/sql/dynamic_table_row.cpp b/src/osquery/sql/dynamic_table_row.cpp index c5285c8..60929bf 100644 --- a/src/osquery/sql/dynamic_table_row.cpp +++ b/src/osquery/sql/dynamic_table_row.cpp @@ -14,97 +14,101 @@ namespace osquery { -TableRows tableRowsFromQueryData(QueryData&& rows) { - TableRows result; +TableRows tableRowsFromQueryData(QueryData&& rows) +{ + TableRows result; - for (auto&& row : rows) { - result.push_back(TableRowHolder(new DynamicTableRow(std::move(row)))); - } + for (auto&& row : rows) { + result.push_back(TableRowHolder(new DynamicTableRow(std::move(row)))); + } - return result; + return result; } int DynamicTableRow::get_rowid(sqlite_int64 default_value, - sqlite_int64* pRowid) const { - auto& current_row = this->row; - auto rowid_it = current_row.find("rowid"); - if (rowid_it != current_row.end()) { - const auto& rowid_text_field = rowid_it->second; + sqlite_int64* pRowid) const +{ + auto& current_row = this->row; + auto rowid_it = current_row.find("rowid"); + if (rowid_it != current_row.end()) { + const auto& rowid_text_field = rowid_it->second; - auto exp = tryTo(rowid_text_field, 10); - if (exp.isError()) { - DEBUG(OSQUERY) << "Invalid rowid value returned " << exp.getError(); - return SQLITE_ERROR; - } - *pRowid = exp.take(); + auto exp = tryTo(rowid_text_field, 10); + if (exp.isError()) { + DEBUG(OSQUERY) << "Invalid rowid value returned " << exp.getError(); + return SQLITE_ERROR; + } + *pRowid = exp.take(); - } else { - *pRowid = default_value; - } - return SQLITE_OK; + } else { + *pRowid = default_value; + } + return SQLITE_OK; } int DynamicTableRow::get_column(sqlite3_context* ctx, - sqlite3_vtab* vtab, - int col) { - VirtualTable* pVtab = (VirtualTable*)vtab; - auto& column_name = std::get<0>(pVtab->content->columns[col]); - auto& type = std::get<1>(pVtab->content->columns[col]); - if (pVtab->content->aliases.count(column_name)) { - // Overwrite the aliased column with the type and name of the new column. - type = std::get<1>( - pVtab->content->columns[pVtab->content->aliases.at(column_name)]); - column_name = std::get<0>( - pVtab->content->columns[pVtab->content->aliases.at(column_name)]); - } + sqlite3_vtab* vtab, + int col) +{ + VirtualTable* pVtab = (VirtualTable*)vtab; + auto& column_name = std::get<0>(pVtab->content->columns[col]); + auto& type = std::get<1>(pVtab->content->columns[col]); + if (pVtab->content->aliases.count(column_name)) { + // Overwrite the aliased column with the type and name of the new column. + type = std::get<1>( + pVtab->content->columns[pVtab->content->aliases.at(column_name)]); + column_name = std::get<0>( + pVtab->content->columns[pVtab->content->aliases.at(column_name)]); + } - // Attempt to cast each xFilter-populated row/column to the SQLite type. - const auto& value = row[column_name]; - if (this->row.count(column_name) == 0) { - // Missing content. - DEBUG(OSQUERY) << "Error " << column_name << " is empty"; - sqlite3_result_null(ctx); - } else if (type == TEXT_TYPE || type == BLOB_TYPE) { - sqlite3_result_text( - ctx, value.c_str(), static_cast(value.size()), SQLITE_STATIC); - } else if (type == INTEGER_TYPE) { - auto afinite = tryTo(value, 0); - if (afinite.isError()) { - DEBUG(OSQUERY) << "Error casting " << column_name << " (" << value - << ") to INTEGER"; - sqlite3_result_null(ctx); - } else { - sqlite3_result_int(ctx, afinite.take()); - } - } else if (type == BIGINT_TYPE || type == UNSIGNED_BIGINT_TYPE) { - auto afinite = tryTo(value, 0); - if (afinite.isError()) { - DEBUG(OSQUERY) << "Error casting " << column_name << " (" << value - << ") to BIGINT"; - sqlite3_result_null(ctx); - } else { - sqlite3_result_int64(ctx, afinite.take()); - } - } else if (type == DOUBLE_TYPE) { - char* end = nullptr; - double afinite = strtod(value.c_str(), &end); - if (end == nullptr || end == value.c_str() || *end != '\0') { - DEBUG(OSQUERY) << "Error casting " << column_name << " (" << value - << ") to DOUBLE"; - sqlite3_result_null(ctx); - } else { - sqlite3_result_double(ctx, afinite); - } - } else { - ERROR(OSQUERY) << "Error unknown column type " << column_name; - } + // Attempt to cast each xFilter-populated row/column to the SQLite type. + const auto& value = row[column_name]; + if (this->row.count(column_name) == 0) { + // Missing content. + DEBUG(OSQUERY) << "Error " << column_name << " is empty"; + sqlite3_result_null(ctx); + } else if (type == TEXT_TYPE || type == BLOB_TYPE) { + sqlite3_result_text( + ctx, value.c_str(), static_cast(value.size()), SQLITE_STATIC); + } else if (type == INTEGER_TYPE) { + auto afinite = tryTo(value, 0); + if (afinite.isError()) { + DEBUG(OSQUERY) << "Error casting " << column_name << " (" << value + << ") to INTEGER"; + sqlite3_result_null(ctx); + } else { + sqlite3_result_int(ctx, afinite.take()); + } + } else if (type == BIGINT_TYPE || type == UNSIGNED_BIGINT_TYPE) { + auto afinite = tryTo(value, 0); + if (afinite.isError()) { + DEBUG(OSQUERY) << "Error casting " << column_name << " (" << value + << ") to BIGINT"; + sqlite3_result_null(ctx); + } else { + sqlite3_result_int64(ctx, afinite.take()); + } + } else if (type == DOUBLE_TYPE) { + char* end = nullptr; + double afinite = strtod(value.c_str(), &end); + if (end == nullptr || end == value.c_str() || *end != '\0') { + DEBUG(OSQUERY) << "Error casting " << column_name << " (" << value + << ") to DOUBLE"; + sqlite3_result_null(ctx); + } else { + sqlite3_result_double(ctx, afinite); + } + } else { + ERROR(OSQUERY) << "Error unknown column type " << column_name; + } - return SQLITE_OK; + return SQLITE_OK; } -TableRowHolder DynamicTableRow::clone() const { - Row new_row = row; - return TableRowHolder(new DynamicTableRow(std::move(new_row))); +TableRowHolder DynamicTableRow::clone() const +{ + Row new_row = row; + return TableRowHolder(new DynamicTableRow(std::move(new_row))); } } // namespace osquery diff --git a/src/osquery/sql/dynamic_table_row.h b/src/osquery/sql/dynamic_table_row.h index 7da1493..b8a5c34 100644 --- a/src/osquery/sql/dynamic_table_row.h +++ b/src/osquery/sql/dynamic_table_row.h @@ -15,64 +15,74 @@ namespace osquery { /** A TableRow backed by a string map. */ class DynamicTableRow : public TableRow { - public: - DynamicTableRow() : row() {} - DynamicTableRow(Row&& r) : row(std::move(r)) {} - DynamicTableRow( - std::initializer_list> init) - : row(init) {} - DynamicTableRow(const DynamicTableRow&) = delete; - DynamicTableRow& operator=(const DynamicTableRow&) = delete; - explicit operator Row() const { - return row; - } - virtual int get_rowid(sqlite_int64 default_value, sqlite_int64* pRowid) const; - virtual int get_column(sqlite3_context* ctx, sqlite3_vtab* pVtab, int col); - virtual TableRowHolder clone() const; - inline std::string& operator[](const std::string& key) { - return row[key]; - } - inline std::string& operator[](std::string&& key) { - return row[key]; - } - inline size_t count(const std::string& key) const { - return row.count(key); - } +public: + DynamicTableRow() : row() {} + DynamicTableRow(Row&& r) : row(std::move(r)) {} + DynamicTableRow( + std::initializer_list> init) + : row(init) {} + DynamicTableRow(const DynamicTableRow&) = delete; + DynamicTableRow& operator=(const DynamicTableRow&) = delete; + explicit operator Row() const + { + return row; + } + virtual int get_rowid(sqlite_int64 default_value, sqlite_int64* pRowid) const; + virtual int get_column(sqlite3_context* ctx, sqlite3_vtab* pVtab, int col); + virtual TableRowHolder clone() const; + inline std::string& operator[](const std::string& key) + { + return row[key]; + } + inline std::string& operator[](std::string&& key) + { + return row[key]; + } + inline size_t count(const std::string& key) const + { + return row.count(key); + } - private: - Row row; +private: + Row row; }; /// Syntactic sugar making DynamicRows inside of TableRowHolders easier to work /// with. This should go away once strongly typed rows are used everywhere. class DynamicTableRowHolder { - public: - DynamicTableRowHolder() : row(new DynamicTableRow()), ptr(row) {} - DynamicTableRowHolder( - std::initializer_list> init) - : row(new DynamicTableRow(init)), ptr(row) {} - inline operator TableRowHolder &&() { - return std::move(ptr); - } - inline std::string& operator[](const std::string& key) { - return (*row)[key]; - } - inline std::string& operator[](std::string&& key) { - return (*row)[key]; - } - inline size_t count(const std::string& key) { - return (*row).count(key); - } +public: + DynamicTableRowHolder() : row(new DynamicTableRow()), ptr(row) {} + DynamicTableRowHolder( + std::initializer_list> init) + : row(new DynamicTableRow(init)), ptr(row) {} + inline operator TableRowHolder&& () + { + return std::move(ptr); + } + inline std::string& operator[](const std::string& key) + { + return (*row)[key]; + } + inline std::string& operator[](std::string&& key) + { + return (*row)[key]; + } + inline size_t count(const std::string& key) + { + return (*row).count(key); + } - private: - DynamicTableRow* row; - TableRowHolder ptr; +private: + DynamicTableRow* row; + TableRowHolder ptr; }; -inline DynamicTableRowHolder make_table_row() { - return DynamicTableRowHolder(); +inline DynamicTableRowHolder make_table_row() +{ + return DynamicTableRowHolder(); } inline DynamicTableRowHolder make_table_row( - std::initializer_list> init) { - return DynamicTableRowHolder(init); + std::initializer_list> init) +{ + return DynamicTableRowHolder(init); } /// Converts a QueryData struct to TableRows. Intended for use only in diff --git a/src/osquery/sql/sql.cpp b/src/osquery/sql/sql.cpp index 905f931..6244e92 100644 --- a/src/osquery/sql/sql.cpp +++ b/src/osquery/sql/sql.cpp @@ -22,199 +22,216 @@ namespace osquery { CREATE_LAZY_REGISTRY(SQLPlugin, "sql"); -SQL::SQL(const std::string& query, bool use_cache) { - TableColumns table_columns; - status_ = getQueryColumns(query, table_columns); - if (status_.ok()) { - for (auto c : table_columns) { - columns_.push_back(std::get<0>(c)); - } - status_ = osquery::query(query, results_, use_cache); - } +SQL::SQL(const std::string& query, bool use_cache) +{ + TableColumns table_columns; + status_ = getQueryColumns(query, table_columns); + if (status_.ok()) { + for (auto c : table_columns) { + columns_.push_back(std::get<0>(c)); + } + status_ = osquery::query(query, results_, use_cache); + } } -const QueryData& SQL::rows() const { - return results_; +const QueryData& SQL::rows() const +{ + return results_; } -QueryData& SQL::rows() { - return results_; +QueryData& SQL::rows() +{ + return results_; } -const ColumnNames& SQL::columns() const { - return columns_; +const ColumnNames& SQL::columns() const +{ + return columns_; } -bool SQL::ok() const { - return status_.ok(); +bool SQL::ok() const +{ + return status_.ok(); } -const Status& SQL::getStatus() const { - return status_; +const Status& SQL::getStatus() const +{ + return status_; } -std::string SQL::getMessageString() const { - return status_.toString(); +std::string SQL::getMessageString() const +{ + return status_.toString(); } -static inline void escapeNonPrintableBytes(std::string& data) { - std::string escaped; - // clang-format off - char const hex_chars[16] = { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', - }; - // clang-format on +static inline void escapeNonPrintableBytes(std::string& data) +{ + std::string escaped; + // clang-format off + char const hex_chars[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + }; + // clang-format on - bool needs_replacement = false; - for (size_t i = 0; i < data.length(); i++) { - if (((unsigned char)data[i]) < 0x20 || ((unsigned char)data[i]) >= 0x80) { - needs_replacement = true; - escaped += "\\x"; - escaped += hex_chars[(((unsigned char)data[i])) >> 4]; - escaped += hex_chars[((unsigned char)data[i] & 0x0F) >> 0]; - } else { - escaped += data[i]; - } - } + bool needs_replacement = false; + for (size_t i = 0; i < data.length(); i++) { + if (((unsigned char)data[i]) < 0x20 || ((unsigned char)data[i]) >= 0x80) { + needs_replacement = true; + escaped += "\\x"; + escaped += hex_chars[(((unsigned char)data[i])) >> 4]; + escaped += hex_chars[((unsigned char)data[i] & 0x0F) >> 0]; + } else { + escaped += data[i]; + } + } - // Only replace if any escapes were made. - if (needs_replacement) { - data = std::move(escaped); - } + // Only replace if any escapes were made. + if (needs_replacement) { + data = std::move(escaped); + } } -void escapeNonPrintableBytesEx(std::string& data) { - return escapeNonPrintableBytes(data); +void escapeNonPrintableBytesEx(std::string& data) +{ + return escapeNonPrintableBytes(data); } -QueryData SQL::selectAllFrom(const std::string& table) { - PluginResponse response; - Registry::call("table", table, {{"action", "generate"}}, response); - return response; +QueryData SQL::selectAllFrom(const std::string& table) +{ + PluginResponse response; + Registry::call("table", table, {{"action", "generate"}}, response); + return response; } QueryData SQL::selectAllFrom(const std::string& table, - const std::string& column, - ConstraintOperator op, - const std::string& expr) { - return selectFrom({}, table, column, op, expr); + const std::string& column, + ConstraintOperator op, + const std::string& expr) +{ + return selectFrom({}, table, column, op, expr); } QueryData SQL::selectFrom(const std::initializer_list& columns, - const std::string& table, - const std::string& column, - ConstraintOperator op, - const std::string& expr) { - PluginRequest request = {{"action", "generate"}}; - // Create a fake content, there will be no caching. - QueryContext ctx; - ctx.constraints[column].add(Constraint(op, expr)); - if (columns.size() > 0) { - auto colsUsed = UsedColumns(columns); - colsUsed.insert(column); - ctx.colsUsed = colsUsed; - } - // We can't set colsUsedBitset here (because we don't know the column - // indexes). The plugin that handles the request will figure it out from the - // column names. - TablePlugin::setRequestFromContext(ctx, request); - - PluginResponse response; - Registry::call("table", table, request, response); - response.erase( - std::remove_if(response.begin(), - response.end(), - [&ctx, &column](const PluginRequest& row) -> bool { - return !ctx.constraints[column].matches(row.at(column)); - }), - response.end()); - return response; -} - -Status SQLPlugin::call(const PluginRequest& request, PluginResponse& response) { - response.clear(); - if (request.count("action") == 0) { - return Status(1, "SQL plugin must include a request action"); - } - - if (request.at("action") == "query") { - bool use_cache = (request.count("cache") && request.at("cache") == "1"); - return this->query(request.at("query"), response, use_cache); - } else if (request.at("action") == "columns") { - TableColumns columns; - auto status = this->getQueryColumns(request.at("query"), columns); - // Convert columns to response - for (const auto& column : columns) { - response.push_back( - {{"n", std::get<0>(column)}, - {"t", columnTypeName(std::get<1>(column))}, - {"o", INTEGER(static_cast(std::get<2>(column)))}}); - } - return status; - } else if (request.at("action") == "attach") { - // Attach a virtual table name using an optional included definition. - return this->attach(request.at("table")); - } else if (request.at("action") == "detach") { - this->detach(request.at("table")); - return Status::success(); - } else if (request.at("action") == "tables") { - std::vector tables; - auto status = this->getQueryTables(request.at("query"), tables); - if (status.ok()) { - for (const auto& table : tables) { - response.push_back({{"t", table}}); - } - } - return status; - } - return Status(1, "Unknown action"); -} - -Status query(const std::string& q, QueryData& results, bool use_cache) { - return Registry::call( - "sql", - "sql", - {{"action", "query"}, {"cache", (use_cache) ? "1" : "0"}, {"query", q}}, - results); -} - -Status getQueryColumns(const std::string& q, TableColumns& columns) { - PluginResponse response; - auto status = Registry::call( - "sql", "sql", {{"action", "columns"}, {"query", q}}, response); - - // Convert response to columns - for (const auto& item : response) { - columns.push_back(make_tuple( - item.at("n"), columnTypeName(item.at("t")), ColumnOptions::DEFAULT)); - } - return status; + const std::string& table, + const std::string& column, + ConstraintOperator op, + const std::string& expr) +{ + PluginRequest request = {{"action", "generate"}}; + // Create a fake content, there will be no caching. + QueryContext ctx; + ctx.constraints[column].add(Constraint(op, expr)); + if (columns.size() > 0) { + auto colsUsed = UsedColumns(columns); + colsUsed.insert(column); + ctx.colsUsed = colsUsed; + } + // We can't set colsUsedBitset here (because we don't know the column + // indexes). The plugin that handles the request will figure it out from the + // column names. + TablePlugin::setRequestFromContext(ctx, request); + + PluginResponse response; + Registry::call("table", table, request, response); + response.erase( + std::remove_if(response.begin(), + response.end(), + [&ctx, &column](const PluginRequest & row) -> bool { + return !ctx.constraints[column].matches(row.at(column)); + }), + response.end()); + return response; +} + +Status SQLPlugin::call(const PluginRequest& request, PluginResponse& response) +{ + response.clear(); + if (request.count("action") == 0) { + return Status(1, "SQL plugin must include a request action"); + } + + if (request.at("action") == "query") { + bool use_cache = (request.count("cache") && request.at("cache") == "1"); + return this->query(request.at("query"), response, use_cache); + } else if (request.at("action") == "columns") { + TableColumns columns; + auto status = this->getQueryColumns(request.at("query"), columns); + // Convert columns to response + for (const auto& column : columns) { + response.push_back({ + {"n", std::get<0>(column)}, + {"t", columnTypeName(std::get<1>(column))}, + {"o", INTEGER(static_cast(std::get<2>(column)))}}); + } + return status; + } else if (request.at("action") == "attach") { + // Attach a virtual table name using an optional included definition. + return this->attach(request.at("table")); + } else if (request.at("action") == "detach") { + this->detach(request.at("table")); + return Status::success(); + } else if (request.at("action") == "tables") { + std::vector tables; + auto status = this->getQueryTables(request.at("query"), tables); + if (status.ok()) { + for (const auto& table : tables) { + response.push_back({{"t", table}}); + } + } + return status; + } + return Status(1, "Unknown action"); +} + +Status query(const std::string& q, QueryData& results, bool use_cache) +{ + return Registry::call( + "sql", + "sql", + {{"action", "query"}, {"cache", (use_cache) ? "1" : "0"}, {"query", q}}, + results); +} + +Status getQueryColumns(const std::string& q, TableColumns& columns) +{ + PluginResponse response; + auto status = Registry::call( + "sql", "sql", {{"action", "columns"}, {"query", q}}, response); + + // Convert response to columns + for (const auto& item : response) { + columns.push_back(make_tuple( + item.at("n"), columnTypeName(item.at("t")), ColumnOptions::DEFAULT)); + } + return status; } Status mockGetQueryTables(std::string copy_q, - std::vector& tables) { - std::transform(copy_q.begin(), copy_q.end(), copy_q.begin(), ::tolower); - auto offset_from = copy_q.find("from "); - if (offset_from == std::string::npos) { - return Status(1); - } - - auto simple_tables = osquery::split(copy_q.substr(offset_from + 5), ","); - for (const auto& table : simple_tables) { - tables.push_back(table); - } - return Status(0); -} - -Status getQueryTables(const std::string& q, std::vector& tables) { - PluginResponse response; - auto status = Registry::call( - "sql", "sql", {{"action", "tables"}, {"query", q}}, response); - - for (const auto& table : response) { - tables.push_back(table.at("t")); - } - return status; + std::vector& tables) +{ + std::transform(copy_q.begin(), copy_q.end(), copy_q.begin(), ::tolower); + auto offset_from = copy_q.find("from "); + if (offset_from == std::string::npos) { + return Status(1); + } + + auto simple_tables = osquery::split(copy_q.substr(offset_from + 5), ","); + for (const auto& table : simple_tables) { + tables.push_back(table); + } + return Status(0); +} + +Status getQueryTables(const std::string& q, std::vector& tables) +{ + PluginResponse response; + auto status = Registry::call( + "sql", "sql", {{"action", "tables"}, {"query", q}}, response); + + for (const auto& table : response) { + tables.push_back(table.at("t")); + } + return status; } } diff --git a/src/osquery/sql/sqlite_util.cpp b/src/osquery/sql/sqlite_util.cpp index b38a839..9656324 100644 --- a/src/osquery/sql/sqlite_util.cpp +++ b/src/osquery/sql/sqlite_util.cpp @@ -35,31 +35,31 @@ using SQLiteDBInstanceRef = std::shared_ptr; */ // clang-format off const std::map kSQLiteReturnCodes = { - {0, "SQLITE_OK"}, {1, "SQLITE_ERROR"}, {2, "SQLITE_INTERNAL"}, - {3, "SQLITE_PERM"}, {4, "SQLITE_ABORT"}, {5, "SQLITE_BUSY"}, - {6, "SQLITE_LOCKED"}, {7, "SQLITE_NOMEM"}, {8, "SQLITE_READONLY"}, - {9, "SQLITE_INTERRUPT"}, {10, "SQLITE_IOERR"}, {11, "SQLITE_CORRUPT"}, - {12, "SQLITE_NOTFOUND"}, {13, "SQLITE_FULL"}, {14, "SQLITE_CANTOPEN"}, - {15, "SQLITE_PROTOCOL"}, {16, "SQLITE_EMPTY"}, {17, "SQLITE_SCHEMA"}, - {18, "SQLITE_TOOBIG"}, {19, "SQLITE_CONSTRAINT"}, {20, "SQLITE_MISMATCH"}, - {21, "SQLITE_MISUSE"}, {22, "SQLITE_NOLFS"}, {23, "SQLITE_AUTH"}, - {24, "SQLITE_FORMAT"}, {25, "SQLITE_RANGE"}, {26, "SQLITE_NOTADB"}, - {27, "SQLITE_NOTICE"}, {28, "SQLITE_WARNING"}, {100, "SQLITE_ROW"}, - {101, "SQLITE_DONE"}, + {0, "SQLITE_OK"}, {1, "SQLITE_ERROR"}, {2, "SQLITE_INTERNAL"}, + {3, "SQLITE_PERM"}, {4, "SQLITE_ABORT"}, {5, "SQLITE_BUSY"}, + {6, "SQLITE_LOCKED"}, {7, "SQLITE_NOMEM"}, {8, "SQLITE_READONLY"}, + {9, "SQLITE_INTERRUPT"}, {10, "SQLITE_IOERR"}, {11, "SQLITE_CORRUPT"}, + {12, "SQLITE_NOTFOUND"}, {13, "SQLITE_FULL"}, {14, "SQLITE_CANTOPEN"}, + {15, "SQLITE_PROTOCOL"}, {16, "SQLITE_EMPTY"}, {17, "SQLITE_SCHEMA"}, + {18, "SQLITE_TOOBIG"}, {19, "SQLITE_CONSTRAINT"}, {20, "SQLITE_MISMATCH"}, + {21, "SQLITE_MISUSE"}, {22, "SQLITE_NOLFS"}, {23, "SQLITE_AUTH"}, + {24, "SQLITE_FORMAT"}, {25, "SQLITE_RANGE"}, {26, "SQLITE_NOTADB"}, + {27, "SQLITE_NOTICE"}, {28, "SQLITE_WARNING"}, {100, "SQLITE_ROW"}, + {101, "SQLITE_DONE"}, }; const std::map kMemoryDBSettings = { - {"synchronous", "OFF"}, {"count_changes", "OFF"}, - {"default_temp_store", "0"}, {"auto_vacuum", "FULL"}, - {"journal_mode", "OFF"}, {"cache_size", "0"}, - {"page_count", "0"}, + {"synchronous", "OFF"}, {"count_changes", "OFF"}, + {"default_temp_store", "0"}, {"auto_vacuum", "FULL"}, + {"journal_mode", "OFF"}, {"cache_size", "0"}, + {"page_count", "0"}, }; // clang-format on #define OpComparator(x) \ - { x, QueryPlanner::Opcode(OpReg::P2, INTEGER_TYPE) } + { x, QueryPlanner::Opcode(OpReg::P2, INTEGER_TYPE) } #define Arithmetic(x) \ - { x, QueryPlanner::Opcode(OpReg::P3, BIGINT_TYPE) } + { x, QueryPlanner::Opcode(OpReg::P3, BIGINT_TYPE) } /** * @brief A map from opcode to pair of result register and resultant type. @@ -69,125 +69,133 @@ const std::map kMemoryDBSettings = { * comparators, aggregates, and copies. */ const std::map kSQLOpcodes = { - {"Concat", QueryPlanner::Opcode(OpReg::P3, TEXT_TYPE)}, - {"AggStep", QueryPlanner::Opcode(OpReg::P3, BIGINT_TYPE)}, - {"AggStep0", QueryPlanner::Opcode(OpReg::P3, BIGINT_TYPE)}, - {"Integer", QueryPlanner::Opcode(OpReg::P2, INTEGER_TYPE)}, - {"Int64", QueryPlanner::Opcode(OpReg::P2, BIGINT_TYPE)}, - {"String", QueryPlanner::Opcode(OpReg::P2, TEXT_TYPE)}, - {"String8", QueryPlanner::Opcode(OpReg::P2, TEXT_TYPE)}, - {"Or", QueryPlanner::Opcode(OpReg::P3, INTEGER_TYPE)}, - {"And", QueryPlanner::Opcode(OpReg::P3, INTEGER_TYPE)}, - - // Arithmetic yields a BIGINT for safety. - Arithmetic("BitAnd"), - Arithmetic("BitOr"), - Arithmetic("ShiftLeft"), - Arithmetic("ShiftRight"), - Arithmetic("Add"), - Arithmetic("Subtract"), - Arithmetic("Multiply"), - Arithmetic("Divide"), - Arithmetic("Remainder"), - - // Comparators result in booleans and are treated as INTEGERs. - OpComparator("Not"), - OpComparator("IsNull"), - OpComparator("NotNull"), - OpComparator("Ne"), - OpComparator("Eq"), - OpComparator("Gt"), - OpComparator("Le"), - OpComparator("Lt"), - OpComparator("Ge"), - OpComparator("IfNeg"), - OpComparator("IfNotZero"), + {"Concat", QueryPlanner::Opcode(OpReg::P3, TEXT_TYPE)}, + {"AggStep", QueryPlanner::Opcode(OpReg::P3, BIGINT_TYPE)}, + {"AggStep0", QueryPlanner::Opcode(OpReg::P3, BIGINT_TYPE)}, + {"Integer", QueryPlanner::Opcode(OpReg::P2, INTEGER_TYPE)}, + {"Int64", QueryPlanner::Opcode(OpReg::P2, BIGINT_TYPE)}, + {"String", QueryPlanner::Opcode(OpReg::P2, TEXT_TYPE)}, + {"String8", QueryPlanner::Opcode(OpReg::P2, TEXT_TYPE)}, + {"Or", QueryPlanner::Opcode(OpReg::P3, INTEGER_TYPE)}, + {"And", QueryPlanner::Opcode(OpReg::P3, INTEGER_TYPE)}, + + // Arithmetic yields a BIGINT for safety. + Arithmetic("BitAnd"), + Arithmetic("BitOr"), + Arithmetic("ShiftLeft"), + Arithmetic("ShiftRight"), + Arithmetic("Add"), + Arithmetic("Subtract"), + Arithmetic("Multiply"), + Arithmetic("Divide"), + Arithmetic("Remainder"), + + // Comparators result in booleans and are treated as INTEGERs. + OpComparator("Not"), + OpComparator("IsNull"), + OpComparator("NotNull"), + OpComparator("Ne"), + OpComparator("Eq"), + OpComparator("Gt"), + OpComparator("Le"), + OpComparator("Lt"), + OpComparator("Ge"), + OpComparator("IfNeg"), + OpComparator("IfNotZero"), }; RecursiveMutex SQLiteDBInstance::kPrimaryAttachMutex; /// The SQLiteSQLPlugin implements the "sql" registry for internal/core. class SQLiteSQLPlugin : public SQLPlugin { - public: - /// Execute SQL and store results. - Status query(const std::string& query, - QueryData& results, - bool use_cache) const override; +public: + /// Execute SQL and store results. + Status query(const std::string& query, + QueryData& results, + bool use_cache) const override; - /// Introspect, explain, the suspected types selected in an SQL statement. - Status getQueryColumns(const std::string& query, - TableColumns& columns) const override; + /// Introspect, explain, the suspected types selected in an SQL statement. + Status getQueryColumns(const std::string& query, + TableColumns& columns) const override; - /// Similar to getQueryColumns but return the scanned tables. - Status getQueryTables(const std::string& query, - std::vector& tables) const override; + /// Similar to getQueryColumns but return the scanned tables. + Status getQueryTables(const std::string& query, + std::vector& tables) const override; - /// Create a SQLite module and attach (CREATE). - Status attach(const std::string& name) override; + /// Create a SQLite module and attach (CREATE). + Status attach(const std::string& name) override; - /// Detach a virtual table (DROP). - void detach(const std::string& name) override; + /// Detach a virtual table (DROP). + void detach(const std::string& name) override; }; /// SQL provider for osquery internal/core. REGISTER_INTERNAL(SQLiteSQLPlugin, "sql", "sql"); -std::string getStringForSQLiteReturnCode(int code) { - if (kSQLiteReturnCodes.find(code) != kSQLiteReturnCodes.end()) { - return kSQLiteReturnCodes.at(code); - } else { - std::ostringstream s; - s << "Error: " << code << " is not a valid SQLite result code"; - return s.str(); - } +std::string getStringForSQLiteReturnCode(int code) +{ + if (kSQLiteReturnCodes.find(code) != kSQLiteReturnCodes.end()) { + return kSQLiteReturnCodes.at(code); + } else { + std::ostringstream s; + s << "Error: " << code << " is not a valid SQLite result code"; + return s.str(); + } } Status SQLiteSQLPlugin::query(const std::string& query, - QueryData& results, - bool use_cache) const { - auto dbc = SQLiteDBManager::get(); - dbc->useCache(use_cache); - auto result = queryInternal(query, results, dbc); - dbc->clearAffectedTables(); - return result; + QueryData& results, + bool use_cache) const +{ + auto dbc = SQLiteDBManager::get(); + dbc->useCache(use_cache); + auto result = queryInternal(query, results, dbc); + dbc->clearAffectedTables(); + return result; } Status SQLiteSQLPlugin::getQueryColumns(const std::string& query, - TableColumns& columns) const { - auto dbc = SQLiteDBManager::get(); - return getQueryColumnsInternal(query, columns, dbc); + TableColumns& columns) const +{ + auto dbc = SQLiteDBManager::get(); + return getQueryColumnsInternal(query, columns, dbc); } Status SQLiteSQLPlugin::getQueryTables(const std::string& query, - std::vector& tables) const { - auto dbc = SQLiteDBManager::get(); - QueryPlanner planner(query, dbc); - tables = planner.tables(); - return Status(0); + std::vector& tables) const +{ + auto dbc = SQLiteDBManager::get(); + QueryPlanner planner(query, dbc); + tables = planner.tables(); + return Status(0); } -SQLInternal::SQLInternal(const std::string& query, bool use_cache) { - auto dbc = SQLiteDBManager::get(); - dbc->useCache(use_cache); - status_ = queryInternal(query, resultsTyped_, dbc); +SQLInternal::SQLInternal(const std::string& query, bool use_cache) +{ + auto dbc = SQLiteDBManager::get(); + dbc->useCache(use_cache); + status_ = queryInternal(query, resultsTyped_, dbc); - // One of the advantages of using SQLInternal (aside from the Registry-bypass) - // is the ability to "deep-inspect" the table attributes and actions. - event_based_ = (dbc->getAttributes() & TableAttributes::EVENT_BASED) != 0; + // One of the advantages of using SQLInternal (aside from the Registry-bypass) + // is the ability to "deep-inspect" the table attributes and actions. + event_based_ = (dbc->getAttributes() & TableAttributes::EVENT_BASED) != 0; - dbc->clearAffectedTables(); + dbc->clearAffectedTables(); } -QueryDataTyped& SQLInternal::rowsTyped() { - return resultsTyped_; +QueryDataTyped& SQLInternal::rowsTyped() +{ + return resultsTyped_; } -const Status& SQLInternal::getStatus() const { - return status_; +const Status& SQLInternal::getStatus() const +{ + return status_; } -bool SQLInternal::eventBased() const { - return event_based_; +bool SQLInternal::eventBased() const +{ + return event_based_; } // Temporary: I'm going to move this from sql.cpp to here in change immediately @@ -196,452 +204,481 @@ bool SQLInternal::eventBased() const { extern void escapeNonPrintableBytesEx(std::string& str); class StringEscaperVisitor : public boost::static_visitor<> { - public: - void operator()(long long& i) const { // NO-OP - } - - void operator()(double& d) const { // NO-OP - } - - void operator()(std::string& str) const { - escapeNonPrintableBytesEx(str); - } +public: + void operator()(long long& i) const // NO-OP + { + } + + void operator()(double& d) const // NO-OP + { + } + + void operator()(std::string& str) const + { + escapeNonPrintableBytesEx(str); + } }; -void SQLInternal::escapeResults() { - StringEscaperVisitor visitor; - for (auto& rowTyped : resultsTyped_) { - for (auto& column : rowTyped) { - boost::apply_visitor(visitor, column.second); - } - } +void SQLInternal::escapeResults() +{ + StringEscaperVisitor visitor; + for (auto& rowTyped : resultsTyped_) { + for (auto& column : rowTyped) { + boost::apply_visitor(visitor, column.second); + } + } } -Status SQLiteSQLPlugin::attach(const std::string& name) { - PluginResponse response; - auto status = - Registry::call("table", name, {{"action", "columns"}}, response); - if (!status.ok()) { - return status; - } +Status SQLiteSQLPlugin::attach(const std::string& name) +{ + PluginResponse response; + auto status = + Registry::call("table", name, {{"action", "columns"}}, response); + if (!status.ok()) { + return status; + } - bool is_extension = true; - auto statement = columnDefinition(response, false, is_extension); + bool is_extension = true; + auto statement = columnDefinition(response, false, is_extension); - // Attach requests occurring via the plugin/registry APIs must act on the - // primary database. To allow this, getConnection can explicitly request the - // primary instance and avoid the contention decisions. - auto dbc = SQLiteDBManager::getConnection(true); + // Attach requests occurring via the plugin/registry APIs must act on the + // primary database. To allow this, getConnection can explicitly request the + // primary instance and avoid the contention decisions. + auto dbc = SQLiteDBManager::getConnection(true); - // Attach as an extension, allowing read/write tables - return attachTableInternal(name, statement, dbc, is_extension); + // Attach as an extension, allowing read/write tables + return attachTableInternal(name, statement, dbc, is_extension); } -void SQLiteSQLPlugin::detach(const std::string& name) { - auto dbc = SQLiteDBManager::get(); - if (!dbc->isPrimary()) { - return; - } - detachTableInternal(name, dbc); +void SQLiteSQLPlugin::detach(const std::string& name) +{ + auto dbc = SQLiteDBManager::get(); + if (!dbc->isPrimary()) { + return; + } + detachTableInternal(name, dbc); } SQLiteDBInstance::SQLiteDBInstance(sqlite3*& db, Mutex& mtx) - : db_(db), lock_(mtx, std::try_to_lock) { - if (lock_.owns_lock()) { - primary_ = true; - } else { - db_ = nullptr; - DEBUG(OSQUERY) << "DBManager contention: opening transient SQLite database"; - init(); - } + : db_(db), lock_(mtx, std::try_to_lock) +{ + if (lock_.owns_lock()) { + primary_ = true; + } else { + db_ = nullptr; + DEBUG(OSQUERY) << "DBManager contention: opening transient SQLite database"; + init(); + } } -static inline void openOptimized(sqlite3*& db) { - sqlite3_open(":memory:", &db); +static inline void openOptimized(sqlite3*& db) +{ + sqlite3_open(":memory:", &db); - std::string settings; - for (const auto& setting : kMemoryDBSettings) { - settings += "PRAGMA " + setting.first + "=" + setting.second + "; "; - } - sqlite3_exec(db, settings.c_str(), nullptr, nullptr, nullptr); + std::string settings; + for (const auto& setting : kMemoryDBSettings) { + settings += "PRAGMA " + setting.first + "=" + setting.second + "; "; + } + sqlite3_exec(db, settings.c_str(), nullptr, nullptr, nullptr); } -void SQLiteDBInstance::init() { - primary_ = false; - openOptimized(db_); +void SQLiteDBInstance::init() +{ + primary_ = false; + openOptimized(db_); } -void SQLiteDBInstance::useCache(bool use_cache) { - use_cache_ = use_cache; +void SQLiteDBInstance::useCache(bool use_cache) +{ + use_cache_ = use_cache; } -bool SQLiteDBInstance::useCache() const { - return use_cache_; +bool SQLiteDBInstance::useCache() const +{ + return use_cache_; } -RecursiveLock SQLiteDBInstance::attachLock() const { - if (isPrimary()) { - return RecursiveLock(kPrimaryAttachMutex); - } - return RecursiveLock(attach_mutex_); +RecursiveLock SQLiteDBInstance::attachLock() const +{ + if (isPrimary()) { + return RecursiveLock(kPrimaryAttachMutex); + } + return RecursiveLock(attach_mutex_); } void SQLiteDBInstance::addAffectedTable( - std::shared_ptr table) { - // An xFilter/scan was requested for this virtual table. - affected_tables_.insert(std::make_pair(table->name, std::move(table))); -} - -bool SQLiteDBInstance::tableCalled(VirtualTableContent const& table) { - return (affected_tables_.count(table.name) > 0); -} - -TableAttributes SQLiteDBInstance::getAttributes() const { - const SQLiteDBInstance* rdbc = this; - if (isPrimary() && !managed_) { - // Similarly to clearAffectedTables, the connection may be forwarded. - rdbc = SQLiteDBManager::getConnection(true).get(); - } - - TableAttributes attributes = TableAttributes::NONE; - for (const auto& table : rdbc->affected_tables_) { - attributes = table.second->attributes | attributes; - } - return attributes; -} - -void SQLiteDBInstance::clearAffectedTables() { - if (isPrimary() && !managed_) { - // A primary instance must forward clear requests to the DB manager's - // 'connection' instance. This is a temporary primary instance. - SQLiteDBManager::getConnection(true)->clearAffectedTables(); - return; - } - - for (const auto& table : affected_tables_) { - table.second->constraints.clear(); - table.second->cache.clear(); - table.second->colsUsed.clear(); - table.second->colsUsedBitsets.clear(); - } - // Since the affected tables are cleared, there are no more affected tables. - // There is no concept of compounding tables between queries. - affected_tables_.clear(); - use_cache_ = false; -} - -SQLiteDBInstance::~SQLiteDBInstance() { - if (!isPrimary() && db_ != nullptr) { - sqlite3_close(db_); - } else { - db_ = nullptr; - } -} - -SQLiteDBManager::SQLiteDBManager() : db_(nullptr) { - sqlite3_soft_heap_limit64(1); -} - -bool SQLiteDBManager::isDisabled(const std::string& table_name) { - const auto& element = instance().disabled_tables_.find(table_name); - return (element != instance().disabled_tables_.end()); -} - -void SQLiteDBManager::resetPrimary() { - auto& self = instance(); - - WriteLock connection_lock(self.mutex_); - self.connection_.reset(); - - { - WriteLock create_lock(self.create_mutex_); - sqlite3_close(self.db_); - self.db_ = nullptr; - } -} - -SQLiteDBInstanceRef SQLiteDBManager::getUnique() { - auto instance = std::make_shared(); - attachVirtualTables(instance); - return instance; -} - -SQLiteDBInstanceRef SQLiteDBManager::getConnection(bool primary) { - auto& self = instance(); - WriteLock lock(self.create_mutex_); - - if (self.db_ == nullptr) { - // Create primary SQLite DB instance. - openOptimized(self.db_); - self.connection_ = SQLiteDBInstanceRef(new SQLiteDBInstance(self.db_)); - attachVirtualTables(self.connection_); - } - - // Internal usage may request the primary connection explicitly. - if (primary) { - return self.connection_; - } - - // Create a 'database connection' for the managed database instance. - auto instance = std::make_shared(self.db_, self.mutex_); - if (!instance->isPrimary()) { - attachVirtualTables(instance); - } - return instance; -} + std::shared_ptr table) +{ + // An xFilter/scan was requested for this virtual table. + affected_tables_.insert(std::make_pair(table->name, std::move(table))); +} + +bool SQLiteDBInstance::tableCalled(VirtualTableContent const& table) +{ + return (affected_tables_.count(table.name) > 0); +} + +TableAttributes SQLiteDBInstance::getAttributes() const +{ + const SQLiteDBInstance* rdbc = this; + if (isPrimary() && !managed_) { + // Similarly to clearAffectedTables, the connection may be forwarded. + rdbc = SQLiteDBManager::getConnection(true).get(); + } + + TableAttributes attributes = TableAttributes::NONE; + for (const auto& table : rdbc->affected_tables_) { + attributes = table.second->attributes | attributes; + } + return attributes; +} + +void SQLiteDBInstance::clearAffectedTables() +{ + if (isPrimary() && !managed_) { + // A primary instance must forward clear requests to the DB manager's + // 'connection' instance. This is a temporary primary instance. + SQLiteDBManager::getConnection(true)->clearAffectedTables(); + return; + } + + for (const auto& table : affected_tables_) { + table.second->constraints.clear(); + table.second->cache.clear(); + table.second->colsUsed.clear(); + table.second->colsUsedBitsets.clear(); + } + // Since the affected tables are cleared, there are no more affected tables. + // There is no concept of compounding tables between queries. + affected_tables_.clear(); + use_cache_ = false; +} + +SQLiteDBInstance::~SQLiteDBInstance() +{ + if (!isPrimary() && db_ != nullptr) { + sqlite3_close(db_); + } else { + db_ = nullptr; + } +} + +SQLiteDBManager::SQLiteDBManager() : db_(nullptr) +{ + sqlite3_soft_heap_limit64(1); +} + +bool SQLiteDBManager::isDisabled(const std::string& table_name) +{ + const auto& element = instance().disabled_tables_.find(table_name); + return (element != instance().disabled_tables_.end()); +} + +void SQLiteDBManager::resetPrimary() +{ + auto& self = instance(); + + WriteLock connection_lock(self.mutex_); + self.connection_.reset(); + + { + WriteLock create_lock(self.create_mutex_); + sqlite3_close(self.db_); + self.db_ = nullptr; + } +} + +SQLiteDBInstanceRef SQLiteDBManager::getUnique() +{ + auto instance = std::make_shared(); + attachVirtualTables(instance); + return instance; +} + +SQLiteDBInstanceRef SQLiteDBManager::getConnection(bool primary) +{ + auto& self = instance(); + WriteLock lock(self.create_mutex_); + + if (self.db_ == nullptr) { + // Create primary SQLite DB instance. + openOptimized(self.db_); + self.connection_ = SQLiteDBInstanceRef(new SQLiteDBInstance(self.db_)); + attachVirtualTables(self.connection_); + } + + // Internal usage may request the primary connection explicitly. + if (primary) { + return self.connection_; + } -SQLiteDBManager::~SQLiteDBManager() { - connection_ = nullptr; - if (db_ != nullptr) { - sqlite3_close(db_); - db_ = nullptr; - } + // Create a 'database connection' for the managed database instance. + auto instance = std::make_shared(self.db_, self.mutex_); + if (!instance->isPrimary()) { + attachVirtualTables(instance); + } + return instance; +} + +SQLiteDBManager::~SQLiteDBManager() +{ + connection_ = nullptr; + if (db_ != nullptr) { + sqlite3_close(db_); + db_ = nullptr; + } } QueryPlanner::QueryPlanner(const std::string& query, - const SQLiteDBInstanceRef& instance) { - QueryData plan; - queryInternal("EXPLAIN QUERY PLAN " + query, plan, instance); - queryInternal("EXPLAIN " + query, program_, instance); - - for (const auto& row : plan) { - auto details = osquery::split(row.at("detail")); - if (details.size() > 2 && details[0] == "SCAN") { - tables_.push_back(details[2]); - } - } -} - -Status QueryPlanner::applyTypes(TableColumns& columns) { - std::map column_types; - for (const auto& row : program_) { - if (row.at("opcode") == "ResultRow") { - // The column parsing is finished. - auto k = boost::lexical_cast(row.at("p1")); - for (const auto& type : column_types) { - if (type.first - k < columns.size()) { - std::get<1>(columns[type.first - k]) = type.second; - } - } - } - - if (row.at("opcode") == "Copy") { - // Copy P1 -> P1 + P3 into P2 -> P2 + P3. - auto from = boost::lexical_cast(row.at("p1")); - auto to = boost::lexical_cast(row.at("p2")); - auto size = boost::lexical_cast(row.at("p3")); - for (size_t i = 0; i <= size; i++) { - if (column_types.count(from + i)) { - column_types[to + i] = std::move(column_types[from + i]); - column_types.erase(from + i); - } - } - } else if (row.at("opcode") == "Cast") { - auto value = boost::lexical_cast(row.at("p1")); - auto to = boost::lexical_cast(row.at("p2")); - switch (to) { - case 'A': // BLOB - column_types[value] = BLOB_TYPE; - break; - case 'B': // TEXT - column_types[value] = TEXT_TYPE; - break; - case 'C': // NUMERIC - // We don't exactly have an equivalent to NUMERIC (which includes such - // things as DATETIME and DECIMAL - column_types[value] = UNKNOWN_TYPE; - break; - case 'D': // INTEGER - column_types[value] = BIGINT_TYPE; - break; - case 'E': // REAL - column_types[value] = DOUBLE_TYPE; - break; - default: - column_types[value] = UNKNOWN_TYPE; - break; - } - } - - if (kSQLOpcodes.count(row.at("opcode"))) { - const auto& op = kSQLOpcodes.at(row.at("opcode")); - auto k = boost::lexical_cast(row.at(Opcode::regString(op.reg))); - column_types[k] = op.type; - } - } - - return Status(0); + const SQLiteDBInstanceRef& instance) +{ + QueryData plan; + queryInternal("EXPLAIN QUERY PLAN " + query, plan, instance); + queryInternal("EXPLAIN " + query, program_, instance); + + for (const auto& row : plan) { + auto details = osquery::split(row.at("detail")); + if (details.size() > 2 && details[0] == "SCAN") { + tables_.push_back(details[2]); + } + } +} + +Status QueryPlanner::applyTypes(TableColumns& columns) +{ + std::map column_types; + for (const auto& row : program_) { + if (row.at("opcode") == "ResultRow") { + // The column parsing is finished. + auto k = boost::lexical_cast(row.at("p1")); + for (const auto& type : column_types) { + if (type.first - k < columns.size()) { + std::get<1>(columns[type.first - k]) = type.second; + } + } + } + + if (row.at("opcode") == "Copy") { + // Copy P1 -> P1 + P3 into P2 -> P2 + P3. + auto from = boost::lexical_cast(row.at("p1")); + auto to = boost::lexical_cast(row.at("p2")); + auto size = boost::lexical_cast(row.at("p3")); + for (size_t i = 0; i <= size; i++) { + if (column_types.count(from + i)) { + column_types[to + i] = std::move(column_types[from + i]); + column_types.erase(from + i); + } + } + } else if (row.at("opcode") == "Cast") { + auto value = boost::lexical_cast(row.at("p1")); + auto to = boost::lexical_cast(row.at("p2")); + switch (to) { + case 'A': // BLOB + column_types[value] = BLOB_TYPE; + break; + case 'B': // TEXT + column_types[value] = TEXT_TYPE; + break; + case 'C': // NUMERIC + // We don't exactly have an equivalent to NUMERIC (which includes such + // things as DATETIME and DECIMAL + column_types[value] = UNKNOWN_TYPE; + break; + case 'D': // INTEGER + column_types[value] = BIGINT_TYPE; + break; + case 'E': // REAL + column_types[value] = DOUBLE_TYPE; + break; + default: + column_types[value] = UNKNOWN_TYPE; + break; + } + } + + if (kSQLOpcodes.count(row.at("opcode"))) { + const auto& op = kSQLOpcodes.at(row.at("opcode")); + auto k = boost::lexical_cast(row.at(Opcode::regString(op.reg))); + column_types[k] = op.type; + } + } + + return Status(0); } // Wrapper for legacy method until all uses can be replaced Status queryInternal(const std::string& query, - QueryData& results, - const SQLiteDBInstanceRef& instance) { - QueryDataTyped typedResults; - Status status = queryInternal(query, typedResults, instance); - if (status.ok()) { - results.reserve(typedResults.size()); - for (const auto& row : typedResults) { - Row r; - for (const auto& col : row) { - r[col.first] = castVariant(col.second); - } - results.push_back(std::move(r)); - } - } - return status; + QueryData& results, + const SQLiteDBInstanceRef& instance) +{ + QueryDataTyped typedResults; + Status status = queryInternal(query, typedResults, instance); + if (status.ok()) { + results.reserve(typedResults.size()); + for (const auto& row : typedResults) { + Row r; + for (const auto& col : row) { + r[col.first] = castVariant(col.second); + } + results.push_back(std::move(r)); + } + } + return status; } Status readRows(sqlite3_stmt* prepared_statement, - QueryDataTyped& results, - const SQLiteDBInstanceRef& instance) { - // Do nothing with a null prepared_statement (eg, if the sql was just - // whitespace) - if (prepared_statement == nullptr) { - return Status::success(); - } - int rc = sqlite3_step(prepared_statement); - /* if we have a result set row... */ - if (SQLITE_ROW == rc) { - // First collect the column names - int num_columns = sqlite3_column_count(prepared_statement); - std::vector colNames; - colNames.reserve(num_columns); - for (int i = 0; i < num_columns; i++) { - colNames.push_back(sqlite3_column_name(prepared_statement, i)); - } - - do { - RowTyped row; - for (int i = 0; i < num_columns; i++) { - switch (sqlite3_column_type(prepared_statement, i)) { - case SQLITE_INTEGER: - row[colNames[i]] = static_cast( - sqlite3_column_int64(prepared_statement, i)); - break; - case SQLITE_FLOAT: - row[colNames[i]] = sqlite3_column_double(prepared_statement, i); - break; - case SQLITE_NULL: - row[colNames[i]] = ""; - break; - default: - // Everything else (SQLITE_TEXT, SQLITE3_TEXT, SQLITE_BLOB) is - // obtained/conveyed as text/string - row[colNames[i]] = std::string(reinterpret_cast( - sqlite3_column_text(prepared_statement, i))); - } - } - results.push_back(std::move(row)); - rc = sqlite3_step(prepared_statement); - } while (SQLITE_ROW == rc); - } - if (rc != SQLITE_DONE) { - return Status::failure(sqlite3_errmsg(instance->db())); - } - - rc = sqlite3_finalize(prepared_statement); - if (rc != SQLITE_OK) { - return Status::failure(sqlite3_errmsg(instance->db())); - } - - return Status::success(); + QueryDataTyped& results, + const SQLiteDBInstanceRef& instance) +{ + // Do nothing with a null prepared_statement (eg, if the sql was just + // whitespace) + if (prepared_statement == nullptr) { + return Status::success(); + } + int rc = sqlite3_step(prepared_statement); + /* if we have a result set row... */ + if (SQLITE_ROW == rc) { + // First collect the column names + int num_columns = sqlite3_column_count(prepared_statement); + std::vector colNames; + colNames.reserve(num_columns); + for (int i = 0; i < num_columns; i++) { + colNames.push_back(sqlite3_column_name(prepared_statement, i)); + } + + do { + RowTyped row; + for (int i = 0; i < num_columns; i++) { + switch (sqlite3_column_type(prepared_statement, i)) { + case SQLITE_INTEGER: + row[colNames[i]] = static_cast( + sqlite3_column_int64(prepared_statement, i)); + break; + case SQLITE_FLOAT: + row[colNames[i]] = sqlite3_column_double(prepared_statement, i); + break; + case SQLITE_NULL: + row[colNames[i]] = ""; + break; + default: + // Everything else (SQLITE_TEXT, SQLITE3_TEXT, SQLITE_BLOB) is + // obtained/conveyed as text/string + row[colNames[i]] = std::string(reinterpret_cast( + sqlite3_column_text(prepared_statement, i))); + } + } + results.push_back(std::move(row)); + rc = sqlite3_step(prepared_statement); + } while (SQLITE_ROW == rc); + } + if (rc != SQLITE_DONE) { + return Status::failure(sqlite3_errmsg(instance->db())); + } + + rc = sqlite3_finalize(prepared_statement); + if (rc != SQLITE_OK) { + return Status::failure(sqlite3_errmsg(instance->db())); + } + + return Status::success(); } Status queryInternal(const std::string& query, - QueryDataTyped& results, - const SQLiteDBInstanceRef& instance) { - sqlite3_stmt* prepared_statement{nullptr}; /* Statement to execute. */ - - int rc = SQLITE_OK; /* Return Code */ - const char* leftover_sql = nullptr; /* Tail of unprocessed SQL */ - const char* sql = query.c_str(); /* SQL to be processed */ - - /* The big while loop. One iteration per statement */ - while ((sql[0] != '\0') && (SQLITE_OK == rc)) { - const auto lock = instance->attachLock(); - - // Trim leading whitespace - while (isspace(sql[0])) { - sql++; - } - rc = sqlite3_prepare_v2( - instance->db(), sql, -1, &prepared_statement, &leftover_sql); - if (rc != SQLITE_OK) { - Status s = Status::failure(sqlite3_errmsg(instance->db())); - sqlite3_finalize(prepared_statement); - return s; - } - - Status s = readRows(prepared_statement, results, instance); - if (!s.ok()) { - return s; - } - - sql = leftover_sql; - } /* end while */ - sqlite3_db_release_memory(instance->db()); - return Status::success(); + QueryDataTyped& results, + const SQLiteDBInstanceRef& instance) +{ + sqlite3_stmt* prepared_statement{nullptr}; /* Statement to execute. */ + + int rc = SQLITE_OK; /* Return Code */ + const char* leftover_sql = nullptr; /* Tail of unprocessed SQL */ + const char* sql = query.c_str(); /* SQL to be processed */ + + /* The big while loop. One iteration per statement */ + while ((sql[0] != '\0') && (SQLITE_OK == rc)) { + const auto lock = instance->attachLock(); + + // Trim leading whitespace + while (isspace(sql[0])) { + sql++; + } + rc = sqlite3_prepare_v2( + instance->db(), sql, -1, &prepared_statement, &leftover_sql); + if (rc != SQLITE_OK) { + Status s = Status::failure(sqlite3_errmsg(instance->db())); + sqlite3_finalize(prepared_statement); + return s; + } + + Status s = readRows(prepared_statement, results, instance); + if (!s.ok()) { + return s; + } + + sql = leftover_sql; + } /* end while */ + sqlite3_db_release_memory(instance->db()); + return Status::success(); } Status getQueryColumnsInternal(const std::string& q, - TableColumns& columns, - const SQLiteDBInstanceRef& instance) { - Status status = Status(); - TableColumns results; - { - auto lock = instance->attachLock(); - - // Turn the query into a prepared statement - sqlite3_stmt* stmt{nullptr}; - auto rc = sqlite3_prepare_v2(instance->db(), - q.c_str(), - static_cast(q.length() + 1), - &stmt, - nullptr); - if (rc != SQLITE_OK || stmt == nullptr) { - if (stmt != nullptr) { - sqlite3_finalize(stmt); - } - return Status(1, sqlite3_errmsg(instance->db())); - } - - // Get column count - auto num_columns = sqlite3_column_count(stmt); - results.reserve(num_columns); - - // Get column names and types - bool unknown_type = false; - for (int i = 0; i < num_columns; ++i) { - auto col_name = sqlite3_column_name(stmt, i); - auto col_type = sqlite3_column_decltype(stmt, i); - - if (col_name == nullptr) { - status = Status(1, "Could not get column type"); - break; - } - - if (col_type == nullptr) { - // Types are only returned for table columns (not expressions). - col_type = "UNKNOWN"; - unknown_type = true; - } - results.push_back(std::make_tuple( - col_name, columnTypeName(col_type), ColumnOptions::DEFAULT)); - } - - // An unknown type means we have to parse the plan and SQLite opcodes. - if (unknown_type) { - QueryPlanner planner(q, instance); - planner.applyTypes(results); - } - sqlite3_finalize(stmt); - } - - if (status.ok()) { - columns = std::move(results); - } - - return status; + TableColumns& columns, + const SQLiteDBInstanceRef& instance) +{ + Status status = Status(); + TableColumns results; + { + auto lock = instance->attachLock(); + + // Turn the query into a prepared statement + sqlite3_stmt* stmt{nullptr}; + auto rc = sqlite3_prepare_v2(instance->db(), + q.c_str(), + static_cast(q.length() + 1), + &stmt, + nullptr); + if (rc != SQLITE_OK || stmt == nullptr) { + if (stmt != nullptr) { + sqlite3_finalize(stmt); + } + return Status(1, sqlite3_errmsg(instance->db())); + } + + // Get column count + auto num_columns = sqlite3_column_count(stmt); + results.reserve(num_columns); + + // Get column names and types + bool unknown_type = false; + for (int i = 0; i < num_columns; ++i) { + auto col_name = sqlite3_column_name(stmt, i); + auto col_type = sqlite3_column_decltype(stmt, i); + + if (col_name == nullptr) { + status = Status(1, "Could not get column type"); + break; + } + + if (col_type == nullptr) { + // Types are only returned for table columns (not expressions). + col_type = "UNKNOWN"; + unknown_type = true; + } + results.push_back(std::make_tuple( + col_name, columnTypeName(col_type), ColumnOptions::DEFAULT)); + } + + // An unknown type means we have to parse the plan and SQLite opcodes. + if (unknown_type) { + QueryPlanner planner(q, instance); + planner.applyTypes(results); + } + sqlite3_finalize(stmt); + } + + if (status.ok()) { + columns = std::move(results); + } + + return status; } } // namespace osquery diff --git a/src/osquery/sql/sqlite_util.h b/src/osquery/sql/sqlite_util.h index a11a075..55ad02b 100644 --- a/src/osquery/sql/sqlite_util.h +++ b/src/osquery/sql/sqlite_util.h @@ -42,98 +42,101 @@ class SQLiteDBManager; * SQLiteDBInstance. */ class SQLiteDBInstance : private boost::noncopyable { - public: - SQLiteDBInstance() { - init(); - } - SQLiteDBInstance(sqlite3*& db, Mutex& mtx); - ~SQLiteDBInstance(); - - /// Check if the instance is the osquery primary. - bool isPrimary() const { - return primary_; - } - - /// Generate a new 'transient' connection. - void init(); - - /** - * @brief Accessor to the internal `sqlite3` object, do not store references - * to the object within osquery code. - */ - sqlite3* db() const { - return db_; - } - - /// Allow a virtual table implementation to record use/access of a table. - void addAffectedTable(std::shared_ptr table); - - /// Clear per-query state of a table affected by the use of this instance. - void clearAffectedTables(); - - /// Check if a virtual table had been called already. - bool tableCalled(VirtualTableContent const& table); - - /// Request that virtual tables use a warm cache for their results. - void useCache(bool use_cache); - - /// Check if the query requested use of the warm query cache. - bool useCache() const; - - /// Lock the database for attaching virtual tables. - RecursiveLock attachLock() const; - - private: - /// Handle the primary/forwarding requests for table attribute accesses. - TableAttributes getAttributes() const; - - private: - /// An opaque constructor only used by the DBManager. - explicit SQLiteDBInstance(sqlite3* db) - : primary_(true), managed_(true), db_(db) {} - - private: - /// Introspection into the database pointer, primary means managed. - bool primary_{false}; - - /// Track whether this instance is managed internally by the DB manager. - bool managed_{false}; - - /// True if this query should bypass table cache. - bool use_cache_{false}; - - /// Either the managed primary database or an ephemeral instance. - sqlite3* db_{nullptr}; - - /** - * @brief An attempted unique lock on the manager's primary database mutex. - * - * This lock is not always acquired. If it is then this instance has locked - * access to the 'primary' SQLite database. - */ - WriteLock lock_; - - /** - * @brief A mutex protecting access to this instance's SQLite database. - * - * Attaching, and other access, can occur async from the registry APIs. - * - * If a database is primary then the static attach mutex is used. - */ - mutable RecursiveMutex attach_mutex_; - - /// See attach_mutex_ but used for the primary database. - static RecursiveMutex kPrimaryAttachMutex; - - /// Vector of tables that need their constraints cleared after execution. - std::map> affected_tables_; - - private: - friend class SQLiteDBManager; - friend class SQLInternal; - - private: - FRIEND_TEST(SQLiteUtilTests, test_affected_tables); +public: + SQLiteDBInstance() + { + init(); + } + SQLiteDBInstance(sqlite3*& db, Mutex& mtx); + ~SQLiteDBInstance(); + + /// Check if the instance is the osquery primary. + bool isPrimary() const + { + return primary_; + } + + /// Generate a new 'transient' connection. + void init(); + + /** + * @brief Accessor to the internal `sqlite3` object, do not store references + * to the object within osquery code. + */ + sqlite3* db() const + { + return db_; + } + + /// Allow a virtual table implementation to record use/access of a table. + void addAffectedTable(std::shared_ptr table); + + /// Clear per-query state of a table affected by the use of this instance. + void clearAffectedTables(); + + /// Check if a virtual table had been called already. + bool tableCalled(VirtualTableContent const& table); + + /// Request that virtual tables use a warm cache for their results. + void useCache(bool use_cache); + + /// Check if the query requested use of the warm query cache. + bool useCache() const; + + /// Lock the database for attaching virtual tables. + RecursiveLock attachLock() const; + +private: + /// Handle the primary/forwarding requests for table attribute accesses. + TableAttributes getAttributes() const; + +private: + /// An opaque constructor only used by the DBManager. + explicit SQLiteDBInstance(sqlite3* db) + : primary_(true), managed_(true), db_(db) {} + +private: + /// Introspection into the database pointer, primary means managed. + bool primary_{false}; + + /// Track whether this instance is managed internally by the DB manager. + bool managed_{false}; + + /// True if this query should bypass table cache. + bool use_cache_{false}; + + /// Either the managed primary database or an ephemeral instance. + sqlite3* db_{nullptr}; + + /** + * @brief An attempted unique lock on the manager's primary database mutex. + * + * This lock is not always acquired. If it is then this instance has locked + * access to the 'primary' SQLite database. + */ + WriteLock lock_; + + /** + * @brief A mutex protecting access to this instance's SQLite database. + * + * Attaching, and other access, can occur async from the registry APIs. + * + * If a database is primary then the static attach mutex is used. + */ + mutable RecursiveMutex attach_mutex_; + + /// See attach_mutex_ but used for the primary database. + static RecursiveMutex kPrimaryAttachMutex; + + /// Vector of tables that need their constraints cleared after execution. + std::map> affected_tables_; + +private: + friend class SQLiteDBManager; + friend class SQLInternal; + +private: + FRIEND_TEST(SQLiteUtilTests, test_affected_tables); }; using SQLiteDBInstanceRef = std::shared_ptr; @@ -146,89 +149,91 @@ using SQLiteDBInstanceRef = std::shared_ptr; * resources as well as provide optimization around resource access. */ class SQLiteDBManager : private boost::noncopyable { - public: - static SQLiteDBManager& instance() { - static SQLiteDBManager instance; - return instance; - } - - /** - * @brief Return a fully configured `sqlite3` database object wrapper. - * - * An osquery database is basically just a SQLite3 database with several - * virtual tables attached. This method is the main abstraction for accessing - * SQLite3 databases within osquery. - * - * A RAII wrapper around the `sqlite3` database will manage attaching tables - * and freeing resources when the instance (connection per-say) goes out of - * scope. Using the SQLiteDBManager will also try to optimize the number of - * `sqlite3` databases in use by managing a single global instance and - * returning resource-safe transient databases if there's access contention. - * - * Note: osquery::initOsquery must be called before calling `get` in order - * for virtual tables to be registered. - * - * @return a SQLiteDBInstance with all virtual tables attached. - */ - static SQLiteDBInstanceRef get() { - return getConnection(); - } - - /// See `get` but always return a transient DB connection (for testing). - static SQLiteDBInstanceRef getUnique(); - - /** - * @brief Reset the primary database connection. - * - * Over time it may be helpful to remove SQLite's arena. - * We can periodically close and re-initialize and connect virtual tables. - */ - static void resetPrimary(); - - /** - * @brief Check if `table_name` is disabled. - * - * Check if `table_name` is in the list of tables passed in to the - * `--disable_tables` flag. - * - * @param The name of the Table to check. - * @return If `table_name` is disabled. - */ - static bool isDisabled(const std::string& table_name); - - protected: - SQLiteDBManager(); - virtual ~SQLiteDBManager(); - - public: - SQLiteDBManager(SQLiteDBManager const&) = delete; - SQLiteDBManager& operator=(SQLiteDBManager const&) = delete; - - private: - /// Primary (managed) sqlite3 database. - sqlite3* db_{nullptr}; - - /// The primary connection maintains an opaque instance. - SQLiteDBInstanceRef connection_{nullptr}; - - /// Mutex and lock around sqlite3 access. - Mutex mutex_; - - /// A write mutex for initializing the primary database. - Mutex create_mutex_; - - /// Member variable to hold set of disabled tables. - std::unordered_set disabled_tables_; - - /// Parse a comma-delimited set of tables names, passed in as a flag. - void setDisabledTables(const std::string& s); - - /// Request a connection, optionally request the primary connection. - static SQLiteDBInstanceRef getConnection(bool primary = false); - - private: - friend class SQLiteDBInstance; - friend class SQLiteSQLPlugin; +public: + static SQLiteDBManager& instance() + { + static SQLiteDBManager instance; + return instance; + } + + /** + * @brief Return a fully configured `sqlite3` database object wrapper. + * + * An osquery database is basically just a SQLite3 database with several + * virtual tables attached. This method is the main abstraction for accessing + * SQLite3 databases within osquery. + * + * A RAII wrapper around the `sqlite3` database will manage attaching tables + * and freeing resources when the instance (connection per-say) goes out of + * scope. Using the SQLiteDBManager will also try to optimize the number of + * `sqlite3` databases in use by managing a single global instance and + * returning resource-safe transient databases if there's access contention. + * + * Note: osquery::initOsquery must be called before calling `get` in order + * for virtual tables to be registered. + * + * @return a SQLiteDBInstance with all virtual tables attached. + */ + static SQLiteDBInstanceRef get() + { + return getConnection(); + } + + /// See `get` but always return a transient DB connection (for testing). + static SQLiteDBInstanceRef getUnique(); + + /** + * @brief Reset the primary database connection. + * + * Over time it may be helpful to remove SQLite's arena. + * We can periodically close and re-initialize and connect virtual tables. + */ + static void resetPrimary(); + + /** + * @brief Check if `table_name` is disabled. + * + * Check if `table_name` is in the list of tables passed in to the + * `--disable_tables` flag. + * + * @param The name of the Table to check. + * @return If `table_name` is disabled. + */ + static bool isDisabled(const std::string& table_name); + +protected: + SQLiteDBManager(); + virtual ~SQLiteDBManager(); + +public: + SQLiteDBManager(SQLiteDBManager const&) = delete; + SQLiteDBManager& operator=(SQLiteDBManager const&) = delete; + +private: + /// Primary (managed) sqlite3 database. + sqlite3* db_{nullptr}; + + /// The primary connection maintains an opaque instance. + SQLiteDBInstanceRef connection_{nullptr}; + + /// Mutex and lock around sqlite3 access. + Mutex mutex_; + + /// A write mutex for initializing the primary database. + Mutex create_mutex_; + + /// Member variable to hold set of disabled tables. + std::unordered_set disabled_tables_; + + /// Parse a comma-delimited set of tables names, passed in as a flag. + void setDisabledTables(const std::string& s); + + /// Request a connection, optionally request the primary connection. + static SQLiteDBInstanceRef getConnection(bool primary = false); + +private: + friend class SQLiteDBInstance; + friend class SQLiteSQLPlugin; }; /** @@ -242,61 +247,63 @@ class SQLiteDBManager : private boost::noncopyable { * once per new query and only when needed (aka an unusable expression). */ class QueryPlanner : private boost::noncopyable { - public: - explicit QueryPlanner(const std::string& query) - : QueryPlanner(query, SQLiteDBManager::get()) {} - QueryPlanner(const std::string& query, const SQLiteDBInstanceRef& instance); - ~QueryPlanner() {} - - public: - /** - * @brief Scan the plan and program for opcodes that infer types. - * - * This allows column type inference based on column expressions. The query - * column introspection may use a QueryPlanner to apply types to the unknown - * columns (which are usually expressions). - * - * @param column an ordered set of columns to fill in type information. - * @return success if all columns types were found, otherwise false. - */ - Status applyTypes(TableColumns& columns); - - /// Get the list of tables filtered by this query. - std::vector tables() const { - return tables_; - } - - /** - * @brief A helper structure to represent an opcode's result and type. - * - * An opcode can be defined by a register and type, for the sake of the - * only known use case of resultant type determination. - */ - struct Opcode { - enum Register { - P1 = 0, - P2, - P3, - }; - - Register reg; - ColumnType type; - - public: - Opcode(Register r, ColumnType t) : reg(r), type(t) {} - - /// Return a register as its column string name. - static std::string regString(Register r) { - static std::vector regs = {"p1", "p2", "p3"}; - return regs[r]; - } - }; - - private: - /// The results of EXPLAIN q. - QueryData program_; - /// The order of tables scanned. - std::vector tables_; +public: + explicit QueryPlanner(const std::string& query) + : QueryPlanner(query, SQLiteDBManager::get()) {} + QueryPlanner(const std::string& query, const SQLiteDBInstanceRef& instance); + ~QueryPlanner() {} + +public: + /** + * @brief Scan the plan and program for opcodes that infer types. + * + * This allows column type inference based on column expressions. The query + * column introspection may use a QueryPlanner to apply types to the unknown + * columns (which are usually expressions). + * + * @param column an ordered set of columns to fill in type information. + * @return success if all columns types were found, otherwise false. + */ + Status applyTypes(TableColumns& columns); + + /// Get the list of tables filtered by this query. + std::vector tables() const + { + return tables_; + } + + /** + * @brief A helper structure to represent an opcode's result and type. + * + * An opcode can be defined by a register and type, for the sake of the + * only known use case of resultant type determination. + */ + struct Opcode { + enum Register { + P1 = 0, + P2, + P3, + }; + + Register reg; + ColumnType type; + + public: + Opcode(Register r, ColumnType t) : reg(r), type(t) {} + + /// Return a register as its column string name. + static std::string regString(Register r) + { + static std::vector regs = {"p1", "p2", "p3"}; + return regs[r]; + } + }; + +private: + /// The results of EXPLAIN q. + QueryData program_; + /// The order of tables scanned. + std::vector tables_; }; /// Specific SQLite opcodes that change column/expression type. @@ -316,8 +323,8 @@ extern const std::map kSQLOpcodes; * @return A status indicating SQL query results. */ Status queryInternal(const std::string& q, - QueryDataTyped& results, - const SQLiteDBInstanceRef& instance); + QueryDataTyped& results, + const SQLiteDBInstanceRef& instance); /** * @brief SQLite Internal: Execute a query on a specific database @@ -333,8 +340,8 @@ Status queryInternal(const std::string& q, * @return A status indicating SQL query results. */ Status queryInternal(const std::string& q, - QueryData& results, - const SQLiteDBInstanceRef& instance); + QueryData& results, + const SQLiteDBInstanceRef& instance); /** * @brief SQLite Intern: Analyze a query, providing information about the @@ -352,57 +359,57 @@ Status queryInternal(const std::string& q, * @return status indicating success or failure of the operation */ Status getQueryColumnsInternal(const std::string& q, - TableColumns& columns, - const SQLiteDBInstanceRef& instance); + TableColumns& columns, + const SQLiteDBInstanceRef& instance); /** * @brief SQLInternal: like SQL, but backed by internal calls, and deals * with QueryDataTyped results. */ class SQLInternal : private only_movable { - public: - /** - * @brief Instantiate an instance of the class with an internal query. - * - * @param query An osquery SQL query. - * @param use_cache [optional] Set true to use the query cache. - */ - explicit SQLInternal(const std::string& query, bool use_cache = false); - - public: - /** - * @brief Const accessor for the rows returned by the query. - * - * @return A QueryDataTyped object of the query results. - */ - QueryDataTyped& rowsTyped(); - - const Status& getStatus() const; - - /** - * @brief Check if the SQL query's results use event-based tables. - * - * Higher level SQL facilities, like the scheduler, may act differently when - * the results of a query (including a JOIN) are event-based. For example, - * it does not make sense to perform set difference checks for an - * always-append result set. - * - * All the tables used in the query will be checked. The TableAttributes of - * each will be ORed and if any include EVENT_BASED, this will return true. - */ - bool eventBased() const; - - /// ASCII escape the results of the query. - void escapeResults(); - - private: - /// The internal member which holds the typed results of the query. - QueryDataTyped resultsTyped_; - - /// The internal member which holds the status of the query. - Status status_; - /// Before completing the execution, store a check for EVENT_BASED. - bool event_based_{false}; +public: + /** + * @brief Instantiate an instance of the class with an internal query. + * + * @param query An osquery SQL query. + * @param use_cache [optional] Set true to use the query cache. + */ + explicit SQLInternal(const std::string& query, bool use_cache = false); + +public: + /** + * @brief Const accessor for the rows returned by the query. + * + * @return A QueryDataTyped object of the query results. + */ + QueryDataTyped& rowsTyped(); + + const Status& getStatus() const; + + /** + * @brief Check if the SQL query's results use event-based tables. + * + * Higher level SQL facilities, like the scheduler, may act differently when + * the results of a query (including a JOIN) are event-based. For example, + * it does not make sense to perform set difference checks for an + * always-append result set. + * + * All the tables used in the query will be checked. The TableAttributes of + * each will be ORed and if any include EVENT_BASED, this will return true. + */ + bool eventBased() const; + + /// ASCII escape the results of the query. + void escapeResults(); + +private: + /// The internal member which holds the typed results of the query. + QueryDataTyped resultsTyped_; + + /// The internal member which holds the status of the query. + Status status_; + /// Before completing the execution, store a check for EVENT_BASED. + bool event_based_{false}; }; /** @@ -460,7 +467,7 @@ void registerFilesystemExtensions(sqlite3* db); * @param results The TableRows data structure that will hold the returned rows */ Status genTableRowsForSqliteTable(const boost::filesystem::path& sqlite_db, - const std::string& sqlite_query, - TableRows& results, - bool respect_locking = true); + const std::string& sqlite_query, + TableRows& results, + bool respect_locking = true); } // namespace osquery diff --git a/src/osquery/sql/tests/sql.cpp b/src/osquery/sql/tests/sql.cpp index 3438580..c520c45 100644 --- a/src/osquery/sql/tests/sql.cpp +++ b/src/osquery/sql/tests/sql.cpp @@ -20,91 +20,96 @@ namespace osquery { extern void escapeNonPrintableBytesEx(std::string& data); class SQLTests : public testing::Test { - public: - void SetUp() override { - registryAndPluginInit(); - } +public: + void SetUp() override + { + registryAndPluginInit(); + } }; class TestTablePlugin : public TablePlugin { - private: - TableColumns columns() const { - return { - std::make_tuple("test_int", INTEGER_TYPE, ColumnOptions::DEFAULT), - std::make_tuple("test_text", TEXT_TYPE, ColumnOptions::DEFAULT), - }; - } - - TableRows generate(QueryContext& ctx) { - TableRows results; - if (ctx.constraints["test_int"].existsAndMatches("1")) { - results.push_back( - make_table_row({{"test_int", "1"}, {"test_text", "0"}})); - } else { - results.push_back( - make_table_row({{"test_int", "0"}, {"test_text", "1"}})); - } - - auto ints = ctx.constraints["test_int"].getAll(EQUALS); - for (const auto& int_match : ints) { - results.push_back(make_table_row({{"test_int", INTEGER(int_match)}})); - } - - return results; - } +private: + TableColumns columns() const + { + return { + std::make_tuple("test_int", INTEGER_TYPE, ColumnOptions::DEFAULT), + std::make_tuple("test_text", TEXT_TYPE, ColumnOptions::DEFAULT), + }; + } + + TableRows generate(QueryContext& ctx) + { + TableRows results; + if (ctx.constraints["test_int"].existsAndMatches("1")) { + results.push_back( + make_table_row({{"test_int", "1"}, {"test_text", "0"}})); + } else { + results.push_back( + make_table_row({{"test_int", "0"}, {"test_text", "1"}})); + } + + auto ints = ctx.constraints["test_int"].getAll(EQUALS); + for (const auto& int_match : ints) { + results.push_back(make_table_row({{"test_int", INTEGER(int_match)}})); + } + + return results; + } }; -TEST_F(SQLTests, test_raw_access_context) { - auto tables = RegistryFactory::get().registry("table"); - tables->add("test", std::make_shared()); - auto results = SQL::selectAllFrom("test"); +TEST_F(SQLTests, test_raw_access_context) +{ + auto tables = RegistryFactory::get().registry("table"); + tables->add("test", std::make_shared()); + auto results = SQL::selectAllFrom("test"); - EXPECT_EQ(results.size(), 1U); - EXPECT_EQ(results[0]["test_text"], "1"); + EXPECT_EQ(results.size(), 1U); + EXPECT_EQ(results[0]["test_text"], "1"); - results = SQL::selectAllFrom("test", "test_int", EQUALS, "1"); - EXPECT_EQ(results.size(), 2U); + results = SQL::selectAllFrom("test", "test_int", EQUALS, "1"); + EXPECT_EQ(results.size(), 2U); - results = SQL::selectAllFrom("test", "test_int", EQUALS, "2"); - EXPECT_EQ(results.size(), 1U); - EXPECT_EQ(results[0]["test_int"], "2"); + results = SQL::selectAllFrom("test", "test_int", EQUALS, "2"); + EXPECT_EQ(results.size(), 1U); + EXPECT_EQ(results[0]["test_int"], "2"); } -TEST_F(SQLTests, test_sql_escape) { - std::string input = "しかたがない"; - escapeNonPrintableBytesEx(input); - EXPECT_EQ(input, - "\\xE3\\x81\\x97\\xE3\\x81\\x8B\\xE3\\x81\\x9F\\xE3\\x81\\x8C\\xE3" - "\\x81\\xAA\\xE3\\x81\\x84"); - - input = "悪因悪果"; - escapeNonPrintableBytesEx(input); - EXPECT_EQ(input, - "\\xE6\\x82\\xAA\\xE5\\x9B\\xA0\\xE6\\x82\\xAA\\xE6\\x9E\\x9C"); - - input = "モンスターハンター"; - escapeNonPrintableBytesEx(input); - EXPECT_EQ(input, - "\\xE3\\x83\\xA2\\xE3\\x83\\xB3\\xE3\\x82\\xB9\\xE3\\x82\\xBF\\xE3" - "\\x83\\xBC\\xE3\\x83\\x8F\\xE3\\x83\\xB3\\xE3\\x82\\xBF\\xE3\\x83" - "\\xBC"); - - input = "съешь же ещё этих мягких французских булок, да выпей чаю"; - escapeNonPrintableBytesEx(input); - EXPECT_EQ( - input, - "\\xD1\\x81\\xD1\\x8A\\xD0\\xB5\\xD1\\x88\\xD1\\x8C \\xD0\\xB6\\xD0\\xB5 " - "\\xD0\\xB5\\xD1\\x89\\xD1\\x91 \\xD1\\x8D\\xD1\\x82\\xD0\\xB8\\xD1\\x85 " - "\\xD0\\xBC\\xD1\\x8F\\xD0\\xB3\\xD0\\xBA\\xD0\\xB8\\xD1\\x85 " - "\\xD1\\x84\\xD1\\x80\\xD0\\xB0\\xD0\\xBD\\xD1\\x86\\xD1\\x83\\xD0\\xB7\\" - "xD1\\x81\\xD0\\xBA\\xD0\\xB8\\xD1\\x85 " - "\\xD0\\xB1\\xD1\\x83\\xD0\\xBB\\xD0\\xBE\\xD0\\xBA, " - "\\xD0\\xB4\\xD0\\xB0 \\xD0\\xB2\\xD1\\x8B\\xD0\\xBF\\xD0\\xB5\\xD0\\xB9 " - "\\xD1\\x87\\xD0\\xB0\\xD1\\x8E"); - - input = "The quick brown fox jumps over the lazy dog."; - escapeNonPrintableBytesEx(input); - EXPECT_EQ(input, "The quick brown fox jumps over the lazy dog."); +TEST_F(SQLTests, test_sql_escape) +{ + std::string input = "しかたがない"; + escapeNonPrintableBytesEx(input); + EXPECT_EQ(input, + "\\xE3\\x81\\x97\\xE3\\x81\\x8B\\xE3\\x81\\x9F\\xE3\\x81\\x8C\\xE3" + "\\x81\\xAA\\xE3\\x81\\x84"); + + input = "悪因悪果"; + escapeNonPrintableBytesEx(input); + EXPECT_EQ(input, + "\\xE6\\x82\\xAA\\xE5\\x9B\\xA0\\xE6\\x82\\xAA\\xE6\\x9E\\x9C"); + + input = "モンスターハンター"; + escapeNonPrintableBytesEx(input); + EXPECT_EQ(input, + "\\xE3\\x83\\xA2\\xE3\\x83\\xB3\\xE3\\x82\\xB9\\xE3\\x82\\xBF\\xE3" + "\\x83\\xBC\\xE3\\x83\\x8F\\xE3\\x83\\xB3\\xE3\\x82\\xBF\\xE3\\x83" + "\\xBC"); + + input = "съешь же ещё этих мягких французских булок, да выпей чаю"; + escapeNonPrintableBytesEx(input); + EXPECT_EQ( + input, + "\\xD1\\x81\\xD1\\x8A\\xD0\\xB5\\xD1\\x88\\xD1\\x8C \\xD0\\xB6\\xD0\\xB5 " + "\\xD0\\xB5\\xD1\\x89\\xD1\\x91 \\xD1\\x8D\\xD1\\x82\\xD0\\xB8\\xD1\\x85 " + "\\xD0\\xBC\\xD1\\x8F\\xD0\\xB3\\xD0\\xBA\\xD0\\xB8\\xD1\\x85 " + "\\xD1\\x84\\xD1\\x80\\xD0\\xB0\\xD0\\xBD\\xD1\\x86\\xD1\\x83\\xD0\\xB7\\" + "xD1\\x81\\xD0\\xBA\\xD0\\xB8\\xD1\\x85 " + "\\xD0\\xB1\\xD1\\x83\\xD0\\xBB\\xD0\\xBE\\xD0\\xBA, " + "\\xD0\\xB4\\xD0\\xB0 \\xD0\\xB2\\xD1\\x8B\\xD0\\xBF\\xD0\\xB5\\xD0\\xB9 " + "\\xD1\\x87\\xD0\\xB0\\xD1\\x8E"); + + input = "The quick brown fox jumps over the lazy dog."; + escapeNonPrintableBytesEx(input); + EXPECT_EQ(input, "The quick brown fox jumps over the lazy dog."); } } diff --git a/src/osquery/sql/tests/sql_test_utils.cpp b/src/osquery/sql/tests/sql_test_utils.cpp index e48adea..f722a79 100644 --- a/src/osquery/sql/tests/sql_test_utils.cpp +++ b/src/osquery/sql/tests/sql_test_utils.cpp @@ -3,81 +3,84 @@ namespace osquery { -QueryDataTyped getTestDBExpectedResults() { - QueryDataTyped d; - RowTyped row1; - row1["username"] = "mike"; - row1["age"] = 23LL; - d.push_back(row1); - RowTyped row2; - row2["username"] = "matt"; - row2["age"] = 24LL; - d.push_back(row2); - return d; +QueryDataTyped getTestDBExpectedResults() +{ + QueryDataTyped d; + RowTyped row1; + row1["username"] = "mike"; + row1["age"] = 23LL; + d.push_back(row1); + RowTyped row2; + row2["username"] = "matt"; + row2["age"] = 24LL; + d.push_back(row2); + return d; } -std::vector> getTestDBResultStream() { - std::vector> results; +std::vector> getTestDBResultStream() +{ + std::vector> results; - std::string q2 = - R"(INSERT INTO test_table (username, age) VALUES ("joe", 25))"; - QueryDataTyped d2; - RowTyped row2_1; - row2_1["username"] = "mike"; - row2_1["age"] = 23LL; - d2.push_back(row2_1); - RowTyped row2_2; - row2_2["username"] = "matt"; - row2_2["age"] = 24LL; - d2.push_back(row2_2); - RowTyped row2_3; - row2_3["username"] = "joe"; - row2_3["age"] = 25LL; - d2.push_back(row2_3); - results.push_back(std::make_pair(q2, d2)); + std::string q2 = + R"(INSERT INTO test_table (username, age) VALUES ("joe", 25))"; + QueryDataTyped d2; + RowTyped row2_1; + row2_1["username"] = "mike"; + row2_1["age"] = 23LL; + d2.push_back(row2_1); + RowTyped row2_2; + row2_2["username"] = "matt"; + row2_2["age"] = 24LL; + d2.push_back(row2_2); + RowTyped row2_3; + row2_3["username"] = "joe"; + row2_3["age"] = 25LL; + d2.push_back(row2_3); + results.push_back(std::make_pair(q2, d2)); - std::string q3 = R"(UPDATE test_table SET age = 27 WHERE username = "matt")"; - QueryDataTyped d3; - RowTyped row3_1; - row3_1["username"] = "mike"; - row3_1["age"] = 23LL; - d3.push_back(row3_1); - RowTyped row3_2; - row3_2["username"] = "matt"; - row3_2["age"] = 27LL; - d3.push_back(row3_2); - RowTyped row3_3; - row3_3["username"] = "joe"; - row3_3["age"] = 25LL; - d3.push_back(row3_3); - results.push_back(std::make_pair(q3, d3)); + std::string q3 = R"(UPDATE test_table SET age = 27 WHERE username = "matt")"; + QueryDataTyped d3; + RowTyped row3_1; + row3_1["username"] = "mike"; + row3_1["age"] = 23LL; + d3.push_back(row3_1); + RowTyped row3_2; + row3_2["username"] = "matt"; + row3_2["age"] = 27LL; + d3.push_back(row3_2); + RowTyped row3_3; + row3_3["username"] = "joe"; + row3_3["age"] = 25LL; + d3.push_back(row3_3); + results.push_back(std::make_pair(q3, d3)); - std::string q4 = - R"(DELETE FROM test_table WHERE username = "matt" AND age = 27)"; - QueryDataTyped d4; - RowTyped row4_1; - row4_1["username"] = "mike"; - row4_1["age"] = 23LL; - d4.push_back(row4_1); - RowTyped row4_2; - row4_2["username"] = "joe"; - row4_2["age"] = 25LL; - d4.push_back(row4_2); - results.push_back(std::make_pair(q4, d4)); + std::string q4 = + R"(DELETE FROM test_table WHERE username = "matt" AND age = 27)"; + QueryDataTyped d4; + RowTyped row4_1; + row4_1["username"] = "mike"; + row4_1["age"] = 23LL; + d4.push_back(row4_1); + RowTyped row4_2; + row4_2["username"] = "joe"; + row4_2["age"] = 25LL; + d4.push_back(row4_2); + results.push_back(std::make_pair(q4, d4)); - return results; + return results; } -ColumnNames getSerializedRowColumnNames(bool unordered_and_repeated) { - ColumnNames cn; - if (unordered_and_repeated) { - cn.push_back("repeated_column"); - } - cn.push_back("alphabetical"); - cn.push_back("foo"); - cn.push_back("meaning_of_life"); - cn.push_back("repeated_column"); - return cn; +ColumnNames getSerializedRowColumnNames(bool unordered_and_repeated) +{ + ColumnNames cn; + if (unordered_and_repeated) { + cn.push_back("repeated_column"); + } + cn.push_back("alphabetical"); + cn.push_back("foo"); + cn.push_back("meaning_of_life"); + cn.push_back("repeated_column"); + return cn; } } // namespace osquery diff --git a/src/osquery/sql/tests/sqlite_util_tests.cpp b/src/osquery/sql/tests/sqlite_util_tests.cpp index de26224..c577ae0 100644 --- a/src/osquery/sql/tests/sqlite_util_tests.cpp +++ b/src/osquery/sql/tests/sqlite_util_tests.cpp @@ -19,135 +19,148 @@ namespace osquery { class SQLiteUtilTests : public testing::Test { - public: - void SetUp() override { - registryAndPluginInit(); - } +public: + void SetUp() override + { + registryAndPluginInit(); + } }; -std::shared_ptr getTestDBC() { - auto dbc = SQLiteDBManager::getUnique(); - char* err = nullptr; - std::vector queries = { - "CREATE TABLE test_table (username varchar(30) primary key, age int)", - "INSERT INTO test_table VALUES (\"mike\", 23)", - "INSERT INTO test_table VALUES (\"matt\", 24)"}; - - for (auto q : queries) { - sqlite3_exec(dbc->db(), q.c_str(), nullptr, nullptr, &err); - if (err != nullptr) { - throw std::domain_error(std::string("Cannot create testing DBC's db: ") + - err); - } - } - - return dbc; +std::shared_ptr getTestDBC() +{ + auto dbc = SQLiteDBManager::getUnique(); + char* err = nullptr; + std::vector queries = { + "CREATE TABLE test_table (username varchar(30) primary key, age int)", + "INSERT INTO test_table VALUES (\"mike\", 23)", + "INSERT INTO test_table VALUES (\"matt\", 24)" + }; + + for (auto q : queries) { + sqlite3_exec(dbc->db(), q.c_str(), nullptr, nullptr, &err); + if (err != nullptr) { + throw std::domain_error(std::string("Cannot create testing DBC's db: ") + + err); + } + } + + return dbc; } -TEST_F(SQLiteUtilTests, test_zero_as_float_doesnt_convert_to_int) { - auto sql = SQL("SELECT 0.0 as zero"); - EXPECT_TRUE(sql.ok()); - EXPECT_EQ(sql.rows().size(), 1U); - Row r; - r["zero"] = "0.0"; - EXPECT_EQ(sql.rows()[0], r); +TEST_F(SQLiteUtilTests, test_zero_as_float_doesnt_convert_to_int) +{ + auto sql = SQL("SELECT 0.0 as zero"); + EXPECT_TRUE(sql.ok()); + EXPECT_EQ(sql.rows().size(), 1U); + Row r; + r["zero"] = "0.0"; + EXPECT_EQ(sql.rows()[0], r); } -TEST_F(SQLiteUtilTests, test_precision_is_maintained) { - auto sql = SQL("SELECT 0.123456789 as high_precision, 0.12 as low_precision"); - EXPECT_TRUE(sql.ok()); - EXPECT_EQ(sql.rows().size(), 1U); - Row r; - r["high_precision"] = "0.123456789"; - r["low_precision"] = "0.12"; - EXPECT_EQ(sql.rows()[0], r); +TEST_F(SQLiteUtilTests, test_precision_is_maintained) +{ + auto sql = SQL("SELECT 0.123456789 as high_precision, 0.12 as low_precision"); + EXPECT_TRUE(sql.ok()); + EXPECT_EQ(sql.rows().size(), 1U); + Row r; + r["high_precision"] = "0.123456789"; + r["low_precision"] = "0.12"; + EXPECT_EQ(sql.rows()[0], r); } -TEST_F(SQLiteUtilTests, test_sqlite_instance_manager) { - auto dbc1 = SQLiteDBManager::get(); - auto dbc2 = SQLiteDBManager::get(); - EXPECT_NE(dbc1->db(), dbc2->db()); - EXPECT_EQ(dbc1->db(), dbc1->db()); +TEST_F(SQLiteUtilTests, test_sqlite_instance_manager) +{ + auto dbc1 = SQLiteDBManager::get(); + auto dbc2 = SQLiteDBManager::get(); + EXPECT_NE(dbc1->db(), dbc2->db()); + EXPECT_EQ(dbc1->db(), dbc1->db()); } -TEST_F(SQLiteUtilTests, test_sqlite_instance) { - // Don't do this at home kids. - // Keep a copy of the internal DB and let the SQLiteDBInstance go oos. - auto internal_db = SQLiteDBManager::get()->db(); - // Compare the internal DB to another request with no SQLiteDBInstances - // in scope, meaning the primary will be returned. - EXPECT_EQ(internal_db, SQLiteDBManager::get()->db()); +TEST_F(SQLiteUtilTests, test_sqlite_instance) +{ + // Don't do this at home kids. + // Keep a copy of the internal DB and let the SQLiteDBInstance go oos. + auto internal_db = SQLiteDBManager::get()->db(); + // Compare the internal DB to another request with no SQLiteDBInstances + // in scope, meaning the primary will be returned. + EXPECT_EQ(internal_db, SQLiteDBManager::get()->db()); } -TEST_F(SQLiteUtilTests, test_reset) { - auto internal_db = SQLiteDBManager::get()->db(); - ASSERT_NE(nullptr, internal_db); +TEST_F(SQLiteUtilTests, test_reset) +{ + auto internal_db = SQLiteDBManager::get()->db(); + ASSERT_NE(nullptr, internal_db); - sqlite3_exec(internal_db, - "create view test_view as select 'test';", - nullptr, - nullptr, - nullptr); + sqlite3_exec(internal_db, + "create view test_view as select 'test';", + nullptr, + nullptr, + nullptr); - SQLiteDBManager::resetPrimary(); - auto instance = SQLiteDBManager::get(); + SQLiteDBManager::resetPrimary(); + auto instance = SQLiteDBManager::get(); - QueryDataTyped results; - queryInternal("select * from test_view", results, instance); + QueryDataTyped results; + queryInternal("select * from test_view", results, instance); - // Assume the internal (primary) database we reset and recreated. - EXPECT_EQ(results.size(), 0U); + // Assume the internal (primary) database we reset and recreated. + EXPECT_EQ(results.size(), 0U); } -TEST_F(SQLiteUtilTests, test_direct_query_execution) { - auto dbc = getTestDBC(); - QueryDataTyped results; - auto status = queryInternal(kTestQuery, results, dbc); - EXPECT_TRUE(status.ok()); - EXPECT_EQ(results, getTestDBExpectedResults()); +TEST_F(SQLiteUtilTests, test_direct_query_execution) +{ + auto dbc = getTestDBC(); + QueryDataTyped results; + auto status = queryInternal(kTestQuery, results, dbc); + EXPECT_TRUE(status.ok()); + EXPECT_EQ(results, getTestDBExpectedResults()); } -TEST_F(SQLiteUtilTests, test_aggregate_query) { - auto dbc = getTestDBC(); - QueryDataTyped results; - auto status = queryInternal(kTestQuery, results, dbc); - EXPECT_TRUE(status.ok()); - EXPECT_EQ(results, getTestDBExpectedResults()); +TEST_F(SQLiteUtilTests, test_aggregate_query) +{ + auto dbc = getTestDBC(); + QueryDataTyped results; + auto status = queryInternal(kTestQuery, results, dbc); + EXPECT_TRUE(status.ok()); + EXPECT_EQ(results, getTestDBExpectedResults()); } -TEST_F(SQLiteUtilTests, test_no_results_query) { - auto dbc = getTestDBC(); - QueryDataTyped results; - auto status = queryInternal( - "select * from test_table where username=\"A_NON_EXISTENT_NAME\"", - results, - dbc); - EXPECT_TRUE(status.ok()); +TEST_F(SQLiteUtilTests, test_no_results_query) +{ + auto dbc = getTestDBC(); + QueryDataTyped results; + auto status = queryInternal( + "select * from test_table where username=\"A_NON_EXISTENT_NAME\"", + results, + dbc); + EXPECT_TRUE(status.ok()); } -TEST_F(SQLiteUtilTests, test_whitespace_query) { - auto dbc = getTestDBC(); - QueryDataTyped results; - auto status = queryInternal(" ", results, dbc); - EXPECT_TRUE(status.ok()); +TEST_F(SQLiteUtilTests, test_whitespace_query) +{ + auto dbc = getTestDBC(); + QueryDataTyped results; + auto status = queryInternal(" ", results, dbc); + EXPECT_TRUE(status.ok()); } -TEST_F(SQLiteUtilTests, test_get_test_db_result_stream) { - auto dbc = getTestDBC(); - auto results = getTestDBResultStream(); - for (auto r : results) { - char* err_char = nullptr; - sqlite3_exec(dbc->db(), (r.first).c_str(), nullptr, nullptr, &err_char); - EXPECT_TRUE(err_char == nullptr); - if (err_char != nullptr) { - sqlite3_free(err_char); - ASSERT_TRUE(false); - } - - QueryDataTyped expected; - auto status = queryInternal(kTestQuery, expected, dbc); - EXPECT_EQ(expected, r.second); - } +TEST_F(SQLiteUtilTests, test_get_test_db_result_stream) +{ + auto dbc = getTestDBC(); + auto results = getTestDBResultStream(); + for (auto r : results) { + char* err_char = nullptr; + sqlite3_exec(dbc->db(), (r.first).c_str(), nullptr, nullptr, &err_char); + EXPECT_TRUE(err_char == nullptr); + if (err_char != nullptr) { + sqlite3_free(err_char); + ASSERT_TRUE(false); + } + + QueryDataTyped expected; + auto status = queryInternal(kTestQuery, expected, dbc); + EXPECT_EQ(expected, r.second); + } } } // namespace osquery diff --git a/src/osquery/sql/tests/virtual_table.cpp b/src/osquery/sql/tests/virtual_table.cpp index 1c28ac3..f7cef0d 100644 --- a/src/osquery/sql/tests/virtual_table.cpp +++ b/src/osquery/sql/tests/virtual_table.cpp @@ -19,866 +19,916 @@ namespace osquery { class VirtualTableTests : public testing::Test { - public: - void SetUp() override { - registryAndPluginInit(); - } +public: + void SetUp() override + { + registryAndPluginInit(); + } }; // sample plugin used on tests class sampleTablePlugin : public TablePlugin { - private: - TableColumns columns() const override { - return { - std::make_tuple("foo", INTEGER_TYPE, ColumnOptions::DEFAULT), - std::make_tuple("bar", TEXT_TYPE, ColumnOptions::DEFAULT), - }; - } +private: + TableColumns columns() const override + { + return { + std::make_tuple("foo", INTEGER_TYPE, ColumnOptions::DEFAULT), + std::make_tuple("bar", TEXT_TYPE, ColumnOptions::DEFAULT), + }; + } }; -TEST_F(VirtualTableTests, test_tableplugin_columndefinition) { - auto table = std::make_shared(); - EXPECT_EQ("(`foo` INTEGER, `bar` TEXT)", table->columnDefinition(false)); +TEST_F(VirtualTableTests, test_tableplugin_columndefinition) +{ + auto table = std::make_shared(); + EXPECT_EQ("(`foo` INTEGER, `bar` TEXT)", table->columnDefinition(false)); } class optionsTablePlugin : public TablePlugin { - private: - TableColumns columns() const override { - return { - std::make_tuple( - "id", INTEGER_TYPE, ColumnOptions::INDEX | ColumnOptions::REQUIRED), - std::make_tuple("username", TEXT_TYPE, ColumnOptions::OPTIMIZED), - std::make_tuple("name", TEXT_TYPE, ColumnOptions::DEFAULT), - }; - } - - private: - FRIEND_TEST(VirtualTableTests, test_tableplugin_options); +private: + TableColumns columns() const override + { + return { + std::make_tuple( + "id", INTEGER_TYPE, ColumnOptions::INDEX | ColumnOptions::REQUIRED), + std::make_tuple("username", TEXT_TYPE, ColumnOptions::OPTIMIZED), + std::make_tuple("name", TEXT_TYPE, ColumnOptions::DEFAULT), + }; + } + +private: + FRIEND_TEST(VirtualTableTests, test_tableplugin_options); }; -TEST_F(VirtualTableTests, test_tableplugin_options) { - auto table = std::make_shared(); - EXPECT_EQ(ColumnOptions::INDEX | ColumnOptions::REQUIRED, - std::get<2>(table->columns()[0])); - - PluginResponse response; - PluginRequest request = {{"action", "columns"}}; - EXPECT_TRUE(table->call(request, response).ok()); - auto index_required = - static_cast(ColumnOptions::INDEX | ColumnOptions::REQUIRED); - EXPECT_EQ(INTEGER(index_required), response[0]["op"]); - - response = table->routeInfo(); - EXPECT_EQ(INTEGER(index_required), response[0]["op"]); - - std::string expected_statement = - "(`id` INTEGER, `username` TEXT, `name` TEXT, PRIMARY KEY (`id`)) " - "WITHOUT ROWID"; - EXPECT_EQ(expected_statement, columnDefinition(response, true, false)); +TEST_F(VirtualTableTests, test_tableplugin_options) +{ + auto table = std::make_shared(); + EXPECT_EQ(ColumnOptions::INDEX | ColumnOptions::REQUIRED, + std::get<2>(table->columns()[0])); + + PluginResponse response; + PluginRequest request = {{"action", "columns"}}; + EXPECT_TRUE(table->call(request, response).ok()); + auto index_required = + static_cast(ColumnOptions::INDEX | ColumnOptions::REQUIRED); + EXPECT_EQ(INTEGER(index_required), response[0]["op"]); + + response = table->routeInfo(); + EXPECT_EQ(INTEGER(index_required), response[0]["op"]); + + std::string expected_statement = + "(`id` INTEGER, `username` TEXT, `name` TEXT, PRIMARY KEY (`id`)) " + "WITHOUT ROWID"; + EXPECT_EQ(expected_statement, columnDefinition(response, true, false)); } class moreOptionsTablePlugin : public TablePlugin { - private: - TableColumns columns() const override { - return { - std::make_tuple("id", INTEGER_TYPE, ColumnOptions::INDEX), - std::make_tuple("username", TEXT_TYPE, ColumnOptions::ADDITIONAL), - std::make_tuple("name", TEXT_TYPE, ColumnOptions::DEFAULT), - }; - } - - private: - FRIEND_TEST(VirtualTableTests, test_tableplugin_moreoptions); +private: + TableColumns columns() const override + { + return { + std::make_tuple("id", INTEGER_TYPE, ColumnOptions::INDEX), + std::make_tuple("username", TEXT_TYPE, ColumnOptions::ADDITIONAL), + std::make_tuple("name", TEXT_TYPE, ColumnOptions::DEFAULT), + }; + } + +private: + FRIEND_TEST(VirtualTableTests, test_tableplugin_moreoptions); }; -TEST_F(VirtualTableTests, test_tableplugin_moreoptions) { - auto table = std::make_shared(); +TEST_F(VirtualTableTests, test_tableplugin_moreoptions) +{ + auto table = std::make_shared(); - PluginResponse response; - PluginRequest request = {{"action", "columns"}}; - EXPECT_TRUE(table->call(request, response).ok()); + PluginResponse response; + PluginRequest request = {{"action", "columns"}}; + EXPECT_TRUE(table->call(request, response).ok()); - std::string expected_statement = - "(`id` INTEGER, `username` TEXT, `name` TEXT, PRIMARY KEY (`id`, " - "`username`)) WITHOUT ROWID"; - EXPECT_EQ(expected_statement, columnDefinition(response, true, false)); + std::string expected_statement = + "(`id` INTEGER, `username` TEXT, `name` TEXT, PRIMARY KEY (`id`, " + "`username`)) WITHOUT ROWID"; + EXPECT_EQ(expected_statement, columnDefinition(response, true, false)); } class aliasesTablePlugin : public TablePlugin { - private: - TableColumns columns() const override { - return { - std::make_tuple("username", TEXT_TYPE, ColumnOptions::DEFAULT), - std::make_tuple("name", TEXT_TYPE, ColumnOptions::DEFAULT), - }; - } - - std::vector aliases() const override { - return {"aliases1", "aliases2"}; - } - - ColumnAliasSet columnAliases() const override { - return { - {"username", {"user_name"}}, - {"name", {"name1", "name2"}}, - }; - } - - private: - FRIEND_TEST(VirtualTableTests, test_tableplugin_aliases); +private: + TableColumns columns() const override + { + return { + std::make_tuple("username", TEXT_TYPE, ColumnOptions::DEFAULT), + std::make_tuple("name", TEXT_TYPE, ColumnOptions::DEFAULT), + }; + } + + std::vector aliases() const override + { + return {"aliases1", "aliases2"}; + } + + ColumnAliasSet columnAliases() const override + { + return { + {"username", {"user_name"}}, + {"name", {"name1", "name2"}}, + }; + } + +private: + FRIEND_TEST(VirtualTableTests, test_tableplugin_aliases); }; -TEST_F(VirtualTableTests, test_tableplugin_aliases) { - auto table = std::make_shared(); - std::vector expected_aliases = {"aliases1", "aliases2"}; - EXPECT_EQ(expected_aliases, table->aliases()); - - PluginResponse response; - PluginRequest request = {{"action", "columns"}}; - EXPECT_TRUE(table->call(request, response).ok()); - - auto default_option = static_cast(ColumnOptions::DEFAULT); - PluginResponse expected_response = { - {{"id", "column"}, - {"name", "username"}, - {"type", "TEXT"}, - {"op", INTEGER(default_option)}}, - {{"id", "column"}, - {"name", "name"}, - {"type", "TEXT"}, - {"op", INTEGER(default_option)}}, - {{"alias", "aliases1"}, {"id", "alias"}}, - {{"alias", "aliases2"}, {"id", "alias"}}, - {{"id", "columnAlias"}, {"name", "name1"}, {"target", "name"}}, - {{"id", "columnAlias"}, {"name", "name2"}, {"target", "name"}}, - {{"id", "columnAlias"}, {"name", "user_name"}, {"target", "username"}}, - {{"attributes", "0"}, {"id", "attributes"}}, - }; - EXPECT_EQ(response, expected_response); - - // Compare the expected table definitions. - std::string expected_statement = - "(`username` TEXT, `name` TEXT, `name1` TEXT HIDDEN, `name2` TEXT HIDDEN," - " `user_name` TEXT HIDDEN)"; - EXPECT_EQ(expected_statement, columnDefinition(response, true, false)); - expected_statement = "(`username` TEXT, `name` TEXT)"; - EXPECT_EQ(expected_statement, columnDefinition(response, false, false)); +TEST_F(VirtualTableTests, test_tableplugin_aliases) +{ + auto table = std::make_shared(); + std::vector expected_aliases = {"aliases1", "aliases2"}; + EXPECT_EQ(expected_aliases, table->aliases()); + + PluginResponse response; + PluginRequest request = {{"action", "columns"}}; + EXPECT_TRUE(table->call(request, response).ok()); + + auto default_option = static_cast(ColumnOptions::DEFAULT); + PluginResponse expected_response = { + { {"id", "column"}, + {"name", "username"}, + {"type", "TEXT"}, + {"op", INTEGER(default_option)} + }, + { {"id", "column"}, + {"name", "name"}, + {"type", "TEXT"}, + {"op", INTEGER(default_option)} + }, + {{"alias", "aliases1"}, {"id", "alias"}}, + {{"alias", "aliases2"}, {"id", "alias"}}, + {{"id", "columnAlias"}, {"name", "name1"}, {"target", "name"}}, + {{"id", "columnAlias"}, {"name", "name2"}, {"target", "name"}}, + {{"id", "columnAlias"}, {"name", "user_name"}, {"target", "username"}}, + {{"attributes", "0"}, {"id", "attributes"}}, + }; + EXPECT_EQ(response, expected_response); + + // Compare the expected table definitions. + std::string expected_statement = + "(`username` TEXT, `name` TEXT, `name1` TEXT HIDDEN, `name2` TEXT HIDDEN," + " `user_name` TEXT HIDDEN)"; + EXPECT_EQ(expected_statement, columnDefinition(response, true, false)); + expected_statement = "(`username` TEXT, `name` TEXT)"; + EXPECT_EQ(expected_statement, columnDefinition(response, false, false)); } -TEST_F(VirtualTableTests, test_sqlite3_attach_vtable) { - auto table = std::make_shared(); - table->setName("sample"); - - // Request a managed "connection". - // This will be a single (potentially locked) instance or a transient - // SQLite database if there is contention and a lock was not requested. - auto dbc = SQLiteDBManager::get(); - - // Virtual tables require the registry/plugin API to query tables. - auto status = - attachTableInternal("failed_sample", "(foo INTEGER)", dbc, false); - EXPECT_EQ(status.getCode(), SQLITE_ERROR); - - // The table attach will complete only when the table name is registered. - auto tables = RegistryFactory::get().registry("table"); - tables->add("sample", std::make_shared()); - - PluginResponse response; - status = Registry::call("table", "sample", {{"action", "columns"}}, response); - ASSERT_TRUE(status.ok()); - - // Use the table name, plugin-generated schema to attach. - status = attachTableInternal( - "sample", columnDefinition(response, false, false), dbc, false); - EXPECT_EQ(status.getCode(), SQLITE_OK); - - std::string const q = "SELECT sql FROM sqlite_temp_master WHERE tbl_name='sample';"; - QueryData results; - status = queryInternal(q, results, dbc); - EXPECT_EQ( - "CREATE VIRTUAL TABLE sample USING sample(`foo` INTEGER, `bar` TEXT)", - results[0]["sql"]); +TEST_F(VirtualTableTests, test_sqlite3_attach_vtable) +{ + auto table = std::make_shared(); + table->setName("sample"); + + // Request a managed "connection". + // This will be a single (potentially locked) instance or a transient + // SQLite database if there is contention and a lock was not requested. + auto dbc = SQLiteDBManager::get(); + + // Virtual tables require the registry/plugin API to query tables. + auto status = + attachTableInternal("failed_sample", "(foo INTEGER)", dbc, false); + EXPECT_EQ(status.getCode(), SQLITE_ERROR); + + // The table attach will complete only when the table name is registered. + auto tables = RegistryFactory::get().registry("table"); + tables->add("sample", std::make_shared()); + + PluginResponse response; + status = Registry::call("table", "sample", {{"action", "columns"}}, response); + ASSERT_TRUE(status.ok()); + + // Use the table name, plugin-generated schema to attach. + status = attachTableInternal( + "sample", columnDefinition(response, false, false), dbc, false); + EXPECT_EQ(status.getCode(), SQLITE_OK); + + std::string const q = "SELECT sql FROM sqlite_temp_master WHERE tbl_name='sample';"; + QueryData results; + status = queryInternal(q, results, dbc); + EXPECT_EQ( + "CREATE VIRTUAL TABLE sample USING sample(`foo` INTEGER, `bar` TEXT)", + results[0]["sql"]); } class pTablePlugin : public TablePlugin { - private: - TableColumns columns() const override { - return { - std::make_tuple("x", INTEGER_TYPE, ColumnOptions::DEFAULT), - std::make_tuple("y", INTEGER_TYPE, ColumnOptions::DEFAULT), - }; - } - - public: - TableRows generate(QueryContext&) override { - TableRows tr; - tr.push_back(make_table_row({{"x", "1"}, {"y", "2"}})); - tr.push_back(make_table_row({{"x", "2"}, {"y", "1"}})); - return tr; - } - - private: - FRIEND_TEST(VirtualTableTests, test_constraints_stacking); +private: + TableColumns columns() const override + { + return { + std::make_tuple("x", INTEGER_TYPE, ColumnOptions::DEFAULT), + std::make_tuple("y", INTEGER_TYPE, ColumnOptions::DEFAULT), + }; + } + +public: + TableRows generate(QueryContext&) override + { + TableRows tr; + tr.push_back(make_table_row({{"x", "1"}, {"y", "2"}})); + tr.push_back(make_table_row({{"x", "2"}, {"y", "1"}})); + return tr; + } + +private: + FRIEND_TEST(VirtualTableTests, test_constraints_stacking); }; class kTablePlugin : public TablePlugin { - private: - TableColumns columns() const override { - return { - std::make_tuple("x", INTEGER_TYPE, ColumnOptions::DEFAULT), - std::make_tuple("z", INTEGER_TYPE, ColumnOptions::DEFAULT), - }; - } - - public: - TableRows generate(QueryContext&) override { - TableRows tr; - tr.push_back(make_table_row({{"x", "1"}, {"z", "2"}})); - tr.push_back(make_table_row({{"x", "2"}, {"z", "1"}})); - return tr; - } - - private: - FRIEND_TEST(VirtualTableTests, test_constraints_stacking); +private: + TableColumns columns() const override + { + return { + std::make_tuple("x", INTEGER_TYPE, ColumnOptions::DEFAULT), + std::make_tuple("z", INTEGER_TYPE, ColumnOptions::DEFAULT), + }; + } + +public: + TableRows generate(QueryContext&) override + { + TableRows tr; + tr.push_back(make_table_row({{"x", "1"}, {"z", "2"}})); + tr.push_back(make_table_row({{"x", "2"}, {"z", "1"}})); + return tr; + } + +private: + FRIEND_TEST(VirtualTableTests, test_constraints_stacking); }; static QueryData makeResult(const std::string& col, - const std::vector& values) { - QueryData results; - for (const auto& value : values) { - Row r; - r[col] = value; - results.push_back(r); - } - return results; + const std::vector& values) +{ + QueryData results; + for (const auto& value : values) { + Row r; + r[col] = value; + results.push_back(r); + } + return results; } #define MP std::make_pair -TEST_F(VirtualTableTests, test_constraints_stacking) { - // Add two testing tables to the registry. - auto tables = RegistryFactory::get().registry("table"); - tables->add("p", std::make_shared()); - tables->add("k", std::make_shared()); - auto dbc = SQLiteDBManager::getUnique(); - - { - // To simplify the attach, just access the column definition directly. - auto p = std::make_shared(); - attachTableInternal("p", p->columnDefinition(false), dbc, false); - auto k = std::make_shared(); - attachTableInternal("k", k->columnDefinition(false), dbc, false); - } - - std::vector> constraint_tests = { - MP("select k.x from p, k", makeResult("x", {"1", "2", "1", "2"})), - MP("select k.x from (select * from k) k2, p, k where k.x = p.x", - makeResult("x", {"1", "1", "2", "2"})), - MP("select k.x from (select * from k where z = 1) k2, p, k where k.x = " - "p.x", - makeResult("x", {"1", "2"})), - MP("select k.x from k k1, (select * from p) p1, k where k.x = p1.x", - makeResult("x", {"1", "1", "2", "2"})), - MP("select k.x from (select * from p) p1, k, (select * from k) k2 where " - "k.x = p1.x", - makeResult("x", {"1", "1", "2", "2"})), - MP("select k.x from (select * from p) p1, k, (select * from k where z = " - "2) k2 where k.x = p1.x", - makeResult("x", {"1", "2"})), - MP("select k.x from k, (select * from p) p1, k k2, (select * from k " - "where z = 1) k3 where k.x = p1.x", - makeResult("x", {"1", "1", "2", "2"})), - MP("select p.x from (select * from k where z = 1) k1, (select * from k " - "where z != 1) k2, p where p.x = k2.x", - makeResult("x", {"1"})), - MP("select p.x from (select * from k, (select x as xx from k where x = " - "1) k2 where z = 1) k1, (select * from k where z != 1) k2, p, k as k3 " - "where p.x = k2.x", - makeResult("x", {"1", "1"})), - }; - - for (const auto& test : constraint_tests) { - QueryData results; - queryInternal(test.first, results, dbc); - EXPECT_EQ(results, test.second) << "Unexpected result for the query: " << test.first; - } - - std::vector union_results = { - makeResult("x", {"1", "2"}), - makeResult("x", {"1", "2"}), - makeResult("x", {"1", "2"}), - makeResult("x", {"1", "2"}), - makeResult("x", {"1", "2"}), - makeResult("x", {"1", "2"}), - makeResult("x", {"1", "2"}), - makeResult("x", {"1"}), - makeResult("x", {"1"}), - }; - - size_t index = 0; - for (const auto& test : constraint_tests) { - QueryData results; - queryInternal(test.first + " union " + test.first, results, dbc); - EXPECT_EQ(results, union_results[index++]); - } +TEST_F(VirtualTableTests, test_constraints_stacking) +{ + // Add two testing tables to the registry. + auto tables = RegistryFactory::get().registry("table"); + tables->add("p", std::make_shared()); + tables->add("k", std::make_shared()); + auto dbc = SQLiteDBManager::getUnique(); + + { + // To simplify the attach, just access the column definition directly. + auto p = std::make_shared(); + attachTableInternal("p", p->columnDefinition(false), dbc, false); + auto k = std::make_shared(); + attachTableInternal("k", k->columnDefinition(false), dbc, false); + } + + std::vector> constraint_tests = { + MP("select k.x from p, k", makeResult("x", {"1", "2", "1", "2"})), + MP("select k.x from (select * from k) k2, p, k where k.x = p.x", + makeResult("x", {"1", "1", "2", "2"})), + MP("select k.x from (select * from k where z = 1) k2, p, k where k.x = " + "p.x", + makeResult("x", {"1", "2"})), + MP("select k.x from k k1, (select * from p) p1, k where k.x = p1.x", + makeResult("x", {"1", "1", "2", "2"})), + MP("select k.x from (select * from p) p1, k, (select * from k) k2 where " + "k.x = p1.x", + makeResult("x", {"1", "1", "2", "2"})), + MP("select k.x from (select * from p) p1, k, (select * from k where z = " + "2) k2 where k.x = p1.x", + makeResult("x", {"1", "2"})), + MP("select k.x from k, (select * from p) p1, k k2, (select * from k " + "where z = 1) k3 where k.x = p1.x", + makeResult("x", {"1", "1", "2", "2"})), + MP("select p.x from (select * from k where z = 1) k1, (select * from k " + "where z != 1) k2, p where p.x = k2.x", + makeResult("x", {"1"})), + MP("select p.x from (select * from k, (select x as xx from k where x = " + "1) k2 where z = 1) k1, (select * from k where z != 1) k2, p, k as k3 " + "where p.x = k2.x", + makeResult("x", {"1", "1"})), + }; + + for (const auto& test : constraint_tests) { + QueryData results; + queryInternal(test.first, results, dbc); + EXPECT_EQ(results, test.second) << "Unexpected result for the query: " << test.first; + } + + std::vector union_results = { + makeResult("x", {"1", "2"}), + makeResult("x", {"1", "2"}), + makeResult("x", {"1", "2"}), + makeResult("x", {"1", "2"}), + makeResult("x", {"1", "2"}), + makeResult("x", {"1", "2"}), + makeResult("x", {"1", "2"}), + makeResult("x", {"1"}), + makeResult("x", {"1"}), + }; + + size_t index = 0; + for (const auto& test : constraint_tests) { + QueryData results; + queryInternal(test.first + " union " + test.first, results, dbc); + EXPECT_EQ(results, union_results[index++]); + } } class jsonTablePlugin : public TablePlugin { - private: - TableColumns columns() const override { - return { - std::make_tuple("data", TEXT_TYPE, ColumnOptions::DEFAULT), - }; - } - - public: - TableRows generate(QueryContext&) override { - TableRows results; - results.push_back(make_table_row({{"data", "{\"test\": 1}"}})); - return results; - } - - private: - FRIEND_TEST(VirtualTableTests, test_json_extract); +private: + TableColumns columns() const override + { + return { + std::make_tuple("data", TEXT_TYPE, ColumnOptions::DEFAULT), + }; + } + +public: + TableRows generate(QueryContext&) override + { + TableRows results; + results.push_back(make_table_row({{"data", "{\"test\": 1}"}})); + return results; + } + +private: + FRIEND_TEST(VirtualTableTests, test_json_extract); }; -TEST_F(VirtualTableTests, test_json_extract) { - // Get a database connection. - auto tables = RegistryFactory::get().registry("table"); - auto json = std::make_shared(); - tables->add("json", json); - - auto dbc = SQLiteDBManager::getUnique(); - attachTableInternal("json", json->columnDefinition(false), dbc, false); - - QueryData results; - // Run a query with a join within. - std::string statement = - "SELECT JSON_EXTRACT(data, '$.test') AS test FROM json;"; - auto status = queryInternal(statement, results, dbc); - ASSERT_TRUE(status.ok()); - ASSERT_EQ(results.size(), 1U); - EXPECT_EQ(results[0]["test"], "1"); +TEST_F(VirtualTableTests, test_json_extract) +{ + // Get a database connection. + auto tables = RegistryFactory::get().registry("table"); + auto json = std::make_shared(); + tables->add("json", json); + + auto dbc = SQLiteDBManager::getUnique(); + attachTableInternal("json", json->columnDefinition(false), dbc, false); + + QueryData results; + // Run a query with a join within. + std::string statement = + "SELECT JSON_EXTRACT(data, '$.test') AS test FROM json;"; + auto status = queryInternal(statement, results, dbc); + ASSERT_TRUE(status.ok()); + ASSERT_EQ(results.size(), 1U); + EXPECT_EQ(results[0]["test"], "1"); } -TEST_F(VirtualTableTests, test_null_values) { - auto dbc = SQLiteDBManager::getUnique(); - - std::string statement = "SELECT NULL as null_value;"; - { - QueryData results; - auto status = queryInternal(statement, results, dbc); - ASSERT_TRUE(status.ok()); - EXPECT_EQ(results[0]["null_value"], ""); - } - - // Try INTEGER. - { - QueryData results; - statement = "SELECT CAST(NULL as INTEGER) as null_value;"; - queryInternal(statement, results, dbc); - EXPECT_EQ(results[0]["null_value"], ""); - } - - // BIGINT. - { - QueryData results; - statement = "SELECT CAST(NULL as BIGINT) as null_value;"; - queryInternal(statement, results, dbc); - EXPECT_EQ(results[0]["null_value"], ""); - } - - // Try DOUBLE. - { - QueryData results; - statement = "SELECT CAST(NULL as DOUBLE) as null_value;"; - queryInternal(statement, results, dbc); - EXPECT_EQ(results[0]["null_value"], ""); - } +TEST_F(VirtualTableTests, test_null_values) +{ + auto dbc = SQLiteDBManager::getUnique(); + + std::string statement = "SELECT NULL as null_value;"; + { + QueryData results; + auto status = queryInternal(statement, results, dbc); + ASSERT_TRUE(status.ok()); + EXPECT_EQ(results[0]["null_value"], ""); + } + + // Try INTEGER. + { + QueryData results; + statement = "SELECT CAST(NULL as INTEGER) as null_value;"; + queryInternal(statement, results, dbc); + EXPECT_EQ(results[0]["null_value"], ""); + } + + // BIGINT. + { + QueryData results; + statement = "SELECT CAST(NULL as BIGINT) as null_value;"; + queryInternal(statement, results, dbc); + EXPECT_EQ(results[0]["null_value"], ""); + } + + // Try DOUBLE. + { + QueryData results; + statement = "SELECT CAST(NULL as DOUBLE) as null_value;"; + queryInternal(statement, results, dbc); + EXPECT_EQ(results[0]["null_value"], ""); + } } class cacheTablePlugin : public TablePlugin { - private: - TableColumns columns() const override { - return { - std::make_tuple("data", TEXT_TYPE, ColumnOptions::DEFAULT), - }; - } - - public: - TableRows generate(QueryContext& context) override { - TableRows results; - if (context.isCached("awesome_data")) { - // There is cache entry for awesome data. - results.push_back(make_table_row({{"data", "more_awesome_data"}})); - } else { - auto tr = make_table_row({{"data", "awesome_data"}}); - context.setCache("awesome_data", static_cast(tr)); - results.push_back(std::move(tr)); - } - return results; - } - - private: - FRIEND_TEST(VirtualTableTests, test_table_cache); +private: + TableColumns columns() const override + { + return { + std::make_tuple("data", TEXT_TYPE, ColumnOptions::DEFAULT), + }; + } + +public: + TableRows generate(QueryContext& context) override + { + TableRows results; + if (context.isCached("awesome_data")) { + // There is cache entry for awesome data. + results.push_back(make_table_row({{"data", "more_awesome_data"}})); + } else { + auto tr = make_table_row({{"data", "awesome_data"}}); + context.setCache("awesome_data", static_cast < TableRowHolder && >(tr)); + results.push_back(std::move(tr)); + } + return results; + } + +private: + FRIEND_TEST(VirtualTableTests, test_table_cache); }; -TEST_F(VirtualTableTests, test_table_cache) { - // Get a database connection. - auto tables = RegistryFactory::get().registry("table"); - auto cache = std::make_shared(); - tables->add("cache", cache); - auto dbc = SQLiteDBManager::getUnique(); - attachTableInternal("cache", cache->columnDefinition(false), dbc, false); - - QueryData results; - // Run a query with a join within. - std::string statement = "SELECT c2.data as data FROM cache c1, cache c2;"; - auto status = queryInternal(statement, results, dbc); - dbc->clearAffectedTables(); - ASSERT_TRUE(status.ok()); - ASSERT_EQ(results.size(), 1U); - EXPECT_EQ(results[0]["data"], "more_awesome_data"); - - // Run the query again, the virtual table cache should have been expired. - results.clear(); - statement = "SELECT data from cache c1"; - queryInternal(statement, results, dbc); - ASSERT_EQ(results.size(), 1U); - ASSERT_EQ(results[0]["data"], "awesome_data"); +TEST_F(VirtualTableTests, test_table_cache) +{ + // Get a database connection. + auto tables = RegistryFactory::get().registry("table"); + auto cache = std::make_shared(); + tables->add("cache", cache); + auto dbc = SQLiteDBManager::getUnique(); + attachTableInternal("cache", cache->columnDefinition(false), dbc, false); + + QueryData results; + // Run a query with a join within. + std::string statement = "SELECT c2.data as data FROM cache c1, cache c2;"; + auto status = queryInternal(statement, results, dbc); + dbc->clearAffectedTables(); + ASSERT_TRUE(status.ok()); + ASSERT_EQ(results.size(), 1U); + EXPECT_EQ(results[0]["data"], "more_awesome_data"); + + // Run the query again, the virtual table cache should have been expired. + results.clear(); + statement = "SELECT data from cache c1"; + queryInternal(statement, results, dbc); + ASSERT_EQ(results.size(), 1U); + ASSERT_EQ(results[0]["data"], "awesome_data"); } class tableCacheTablePlugin : public TablePlugin { - public: - TableColumns columns() const override { - return { - std::make_tuple("i", TEXT_TYPE, ColumnOptions::INDEX), - std::make_tuple("d", TEXT_TYPE, ColumnOptions::DEFAULT), - }; - } - - TableAttributes attributes() const override { - return TableAttributes::CACHEABLE; - } - - TableRows generate(QueryContext& ctx) override { - if (isCached(60, ctx)) { - return getCache(); - } - - generates_++; - auto r = make_table_row(); - r["i"] = "1"; - TableRows result; - result.push_back(std::move(r)); - setCache(60, 1, ctx, result); - return result; - } - - size_t generates_{0}; +public: + TableColumns columns() const override + { + return { + std::make_tuple("i", TEXT_TYPE, ColumnOptions::INDEX), + std::make_tuple("d", TEXT_TYPE, ColumnOptions::DEFAULT), + }; + } + + TableAttributes attributes() const override + { + return TableAttributes::CACHEABLE; + } + + TableRows generate(QueryContext& ctx) override + { + if (isCached(60, ctx)) { + return getCache(); + } + + generates_++; + auto r = make_table_row(); + r["i"] = "1"; + TableRows result; + result.push_back(std::move(r)); + setCache(60, 1, ctx, result); + return result; + } + + size_t generates_{0}; }; -TEST_F(VirtualTableTests, test_table_results_cache) { - // Get a database connection. - auto tables = RegistryFactory::get().registry("table"); - auto cache = std::make_shared(); - tables->add("table_cache", cache); - auto dbc = SQLiteDBManager::getUnique(); - attachTableInternal( - "table_cache", cache->columnDefinition(false), dbc, false); - - QueryData results; - std::string statement = "SELECT * from table_cache;"; - auto status = queryInternal(statement, results, dbc); - dbc->clearAffectedTables(); - - ASSERT_TRUE(status.ok()); - EXPECT_EQ(results.size(), 1U); - EXPECT_EQ(cache->generates_, 1U); - - // Run the query again, the virtual table cache was not requested. - results.clear(); - statement = "SELECT * from table_cache;"; - queryInternal(statement, results, dbc); - EXPECT_EQ(results.size(), 1U); - - // The table should have used the cache. - EXPECT_EQ(cache->generates_, 2U); - - // Now request that caching be used. - dbc->useCache(true); - - // Run the query again, the virtual table cache will be populated. - results.clear(); - statement = "SELECT * from table_cache;"; - queryInternal(statement, results, dbc); - EXPECT_EQ(results.size(), 1U); - EXPECT_EQ(cache->generates_, 3U); - - // Run the query again, the virtual table cache will be returned. - results.clear(); - statement = "SELECT * from table_cache;"; - queryInternal(statement, results, dbc); - EXPECT_EQ(results.size(), 1U); - EXPECT_EQ(cache->generates_, 3U); - - // Once last time with constraints that invalidate the cache results. - results.clear(); - statement = "SELECT * from table_cache where i = '1';"; - queryInternal(statement, results, dbc); - EXPECT_EQ(results.size(), 1U); - - // The table should NOT have used the cache. - EXPECT_EQ(cache->generates_, 4U); +TEST_F(VirtualTableTests, test_table_results_cache) +{ + // Get a database connection. + auto tables = RegistryFactory::get().registry("table"); + auto cache = std::make_shared(); + tables->add("table_cache", cache); + auto dbc = SQLiteDBManager::getUnique(); + attachTableInternal( + "table_cache", cache->columnDefinition(false), dbc, false); + + QueryData results; + std::string statement = "SELECT * from table_cache;"; + auto status = queryInternal(statement, results, dbc); + dbc->clearAffectedTables(); + + ASSERT_TRUE(status.ok()); + EXPECT_EQ(results.size(), 1U); + EXPECT_EQ(cache->generates_, 1U); + + // Run the query again, the virtual table cache was not requested. + results.clear(); + statement = "SELECT * from table_cache;"; + queryInternal(statement, results, dbc); + EXPECT_EQ(results.size(), 1U); + + // The table should have used the cache. + EXPECT_EQ(cache->generates_, 2U); + + // Now request that caching be used. + dbc->useCache(true); + + // Run the query again, the virtual table cache will be populated. + results.clear(); + statement = "SELECT * from table_cache;"; + queryInternal(statement, results, dbc); + EXPECT_EQ(results.size(), 1U); + EXPECT_EQ(cache->generates_, 3U); + + // Run the query again, the virtual table cache will be returned. + results.clear(); + statement = "SELECT * from table_cache;"; + queryInternal(statement, results, dbc); + EXPECT_EQ(results.size(), 1U); + EXPECT_EQ(cache->generates_, 3U); + + // Once last time with constraints that invalidate the cache results. + results.clear(); + statement = "SELECT * from table_cache where i = '1';"; + queryInternal(statement, results, dbc); + EXPECT_EQ(results.size(), 1U); + + // The table should NOT have used the cache. + EXPECT_EQ(cache->generates_, 4U); } class likeTablePlugin : public TablePlugin { - private: - TableColumns columns() const override { - return { - std::make_tuple("i", TEXT_TYPE, ColumnOptions::INDEX), - std::make_tuple("op", TEXT_TYPE, ColumnOptions::DEFAULT), - }; - } - - public: - TableRows generate(QueryContext& context) override { - TableRows results; - - // To test, we'll move all predicate constraints into the result set. - // First we'll move constrains for the column `i` using operands =, LIKE. - auto i = context.constraints["i"].getAll(EQUALS); - for (const auto& constraint : i) { - auto r = make_table_row(); - r["i"] = constraint; - r["op"] = "EQUALS"; - results.push_back(std::move(r)); - } - - i = context.constraints["i"].getAll(LIKE); - for (const auto& constraint : i) { - auto r = make_table_row(); - r["i"] = constraint; - r["op"] = "LIKE"; - results.push_back(std::move(r)); - } - - return results; - } - - private: - FRIEND_TEST(VirtualTableTests, test_like_constraints); +private: + TableColumns columns() const override + { + return { + std::make_tuple("i", TEXT_TYPE, ColumnOptions::INDEX), + std::make_tuple("op", TEXT_TYPE, ColumnOptions::DEFAULT), + }; + } + +public: + TableRows generate(QueryContext& context) override + { + TableRows results; + + // To test, we'll move all predicate constraints into the result set. + // First we'll move constrains for the column `i` using operands =, LIKE. + auto i = context.constraints["i"].getAll(EQUALS); + for (const auto& constraint : i) { + auto r = make_table_row(); + r["i"] = constraint; + r["op"] = "EQUALS"; + results.push_back(std::move(r)); + } + + i = context.constraints["i"].getAll(LIKE); + for (const auto& constraint : i) { + auto r = make_table_row(); + r["i"] = constraint; + r["op"] = "LIKE"; + results.push_back(std::move(r)); + } + + return results; + } + +private: + FRIEND_TEST(VirtualTableTests, test_like_constraints); }; -TEST_F(VirtualTableTests, test_like_constraints) { - auto table = std::make_shared(); - auto table_registry = RegistryFactory::get().registry("table"); - table_registry->add("like_table", table); - - auto dbc = SQLiteDBManager::getUnique(); - attachTableInternal("like_table", table->columnDefinition(false), dbc, false); - - // Base case, without constrains this table has no results. - QueryData results; - queryInternal("SELECT * FROM like_table", results, dbc); - dbc->clearAffectedTables(); - ASSERT_EQ(results.size(), 0U); - - // A single EQUAL constraint's value is emitted. - queryInternal("SELECT * FROM like_table WHERE i = '1'", results, dbc); - dbc->clearAffectedTables(); - ASSERT_EQ(results.size(), 1U); - EXPECT_EQ(results[0]["i"], "1"); - EXPECT_EQ(results[0]["op"], "EQUALS"); - - // When using OR, both values should be added. - results.clear(); - queryInternal( - "SELECT * FROM like_table WHERE i = '1' OR i = '2'", results, dbc); - dbc->clearAffectedTables(); - ASSERT_EQ(results.size(), 2U); - EXPECT_EQ(results[0]["i"], "1"); - EXPECT_EQ(results[0]["op"], "EQUALS"); - EXPECT_EQ(results[1]["i"], "2"); - EXPECT_EQ(results[1]["op"], "EQUALS"); - - // When using a LIKE, the value (with substitution character) is emitted. - results.clear(); - queryInternal( - "SELECT * FROM like_table WHERE i LIKE '/test/1/%'", results, dbc); - dbc->clearAffectedTables(); - ASSERT_EQ(results.size(), 1U); - EXPECT_EQ(results[0]["i"], "/test/1/%"); - EXPECT_EQ(results[0]["op"], "LIKE"); - - // As with EQUAL, multiple LIKEs mean multiple values. - results.clear(); - queryInternal( - "SELECT * FROM like_table WHERE i LIKE '/test/1/%' OR i LIKE '/test/2/%'", - results, - dbc); - dbc->clearAffectedTables(); - ASSERT_EQ(results.size(), 2U); - EXPECT_EQ(results[0]["i"], "/test/1/%"); - EXPECT_EQ(results[0]["op"], "LIKE"); - EXPECT_EQ(results[1]["i"], "/test/2/%"); - EXPECT_EQ(results[1]["op"], "LIKE"); - - // As with EQUAL, multiple LIKEs mean multiple values. - results.clear(); - queryInternal( - "SELECT * FROM like_table WHERE i LIKE '/home/%/downloads' OR i LIKE " - "'/home/%/documents'", - results, - dbc); - dbc->clearAffectedTables(); - ASSERT_EQ(results.size(), 2U); - EXPECT_EQ(results[0]["i"], "/home/%/downloads"); - EXPECT_EQ(results[0]["op"], "LIKE"); - EXPECT_EQ(results[1]["i"], "/home/%/documents"); - EXPECT_EQ(results[1]["op"], "LIKE"); +TEST_F(VirtualTableTests, test_like_constraints) +{ + auto table = std::make_shared(); + auto table_registry = RegistryFactory::get().registry("table"); + table_registry->add("like_table", table); + + auto dbc = SQLiteDBManager::getUnique(); + attachTableInternal("like_table", table->columnDefinition(false), dbc, false); + + // Base case, without constrains this table has no results. + QueryData results; + queryInternal("SELECT * FROM like_table", results, dbc); + dbc->clearAffectedTables(); + ASSERT_EQ(results.size(), 0U); + + // A single EQUAL constraint's value is emitted. + queryInternal("SELECT * FROM like_table WHERE i = '1'", results, dbc); + dbc->clearAffectedTables(); + ASSERT_EQ(results.size(), 1U); + EXPECT_EQ(results[0]["i"], "1"); + EXPECT_EQ(results[0]["op"], "EQUALS"); + + // When using OR, both values should be added. + results.clear(); + queryInternal( + "SELECT * FROM like_table WHERE i = '1' OR i = '2'", results, dbc); + dbc->clearAffectedTables(); + ASSERT_EQ(results.size(), 2U); + EXPECT_EQ(results[0]["i"], "1"); + EXPECT_EQ(results[0]["op"], "EQUALS"); + EXPECT_EQ(results[1]["i"], "2"); + EXPECT_EQ(results[1]["op"], "EQUALS"); + + // When using a LIKE, the value (with substitution character) is emitted. + results.clear(); + queryInternal( + "SELECT * FROM like_table WHERE i LIKE '/test/1/%'", results, dbc); + dbc->clearAffectedTables(); + ASSERT_EQ(results.size(), 1U); + EXPECT_EQ(results[0]["i"], "/test/1/%"); + EXPECT_EQ(results[0]["op"], "LIKE"); + + // As with EQUAL, multiple LIKEs mean multiple values. + results.clear(); + queryInternal( + "SELECT * FROM like_table WHERE i LIKE '/test/1/%' OR i LIKE '/test/2/%'", + results, + dbc); + dbc->clearAffectedTables(); + ASSERT_EQ(results.size(), 2U); + EXPECT_EQ(results[0]["i"], "/test/1/%"); + EXPECT_EQ(results[0]["op"], "LIKE"); + EXPECT_EQ(results[1]["i"], "/test/2/%"); + EXPECT_EQ(results[1]["op"], "LIKE"); + + // As with EQUAL, multiple LIKEs mean multiple values. + results.clear(); + queryInternal( + "SELECT * FROM like_table WHERE i LIKE '/home/%/downloads' OR i LIKE " + "'/home/%/documents'", + results, + dbc); + dbc->clearAffectedTables(); + ASSERT_EQ(results.size(), 2U); + EXPECT_EQ(results[0]["i"], "/home/%/downloads"); + EXPECT_EQ(results[0]["op"], "LIKE"); + EXPECT_EQ(results[1]["i"], "/home/%/documents"); + EXPECT_EQ(results[1]["op"], "LIKE"); } class indexIOptimizedTablePlugin : public TablePlugin { - private: - TableColumns columns() const override { - return { - std::make_tuple("i", INTEGER_TYPE, ColumnOptions::INDEX), - std::make_tuple("j", INTEGER_TYPE, ColumnOptions::DEFAULT), - std::make_tuple("text", INTEGER_TYPE, ColumnOptions::DEFAULT), - }; - } - - public: - TableRows generate(QueryContext& context) override { - scans++; - - TableRows results; - auto indexes = context.constraints["i"].getAll(EQUALS); - for (const auto& i : indexes) { - results.push_back(make_table_row( - {{"i", INTEGER(i)}, {"j", INTEGER(i * 10)}, {"text", "none"}})); - } - if (indexes.empty()) { - for (size_t i = 0; i < 100; i++) { - results.push_back(make_table_row( - {{"i", INTEGER(i)}, {"j", INTEGER(i * 10)}, {"text", "some"}})); - } - } - return results; - } - - // Here the goal is to expect/assume the number of scans. - size_t scans{0}; +private: + TableColumns columns() const override + { + return { + std::make_tuple("i", INTEGER_TYPE, ColumnOptions::INDEX), + std::make_tuple("j", INTEGER_TYPE, ColumnOptions::DEFAULT), + std::make_tuple("text", INTEGER_TYPE, ColumnOptions::DEFAULT), + }; + } + +public: + TableRows generate(QueryContext& context) override + { + scans++; + + TableRows results; + auto indexes = context.constraints["i"].getAll(EQUALS); + for (const auto& i : indexes) { + results.push_back(make_table_row( + {{"i", INTEGER(i)}, {"j", INTEGER(i * 10)}, {"text", "none"}})); + } + if (indexes.empty()) { + for (size_t i = 0; i < 100; i++) { + results.push_back(make_table_row( + {{"i", INTEGER(i)}, {"j", INTEGER(i * 10)}, {"text", "some"}})); + } + } + return results; + } + + // Here the goal is to expect/assume the number of scans. + size_t scans{0}; }; class indexJOptimizedTablePlugin : public TablePlugin { - private: - TableColumns columns() const override { - return { - std::make_tuple("j", INTEGER_TYPE, ColumnOptions::INDEX), - std::make_tuple("text", INTEGER_TYPE, ColumnOptions::DEFAULT), - }; - } - - public: - TableRows generate(QueryContext& context) override { - scans++; - - TableRows results; - auto indexes = context.constraints["j"].getAll(EQUALS); - for (const auto& j : indexes) { - results.push_back(make_table_row({{"j", INTEGER(j)}, {"text", "none"}})); - } - if (indexes.empty()) { - for (size_t j = 0; j < 100; j++) { - results.push_back( - make_table_row({{"j", INTEGER(j)}, {"text", "some"}})); - } - } - return results; - } - - // Here the goal is to expect/assume the number of scans. - size_t scans{0}; +private: + TableColumns columns() const override + { + return { + std::make_tuple("j", INTEGER_TYPE, ColumnOptions::INDEX), + std::make_tuple("text", INTEGER_TYPE, ColumnOptions::DEFAULT), + }; + } + +public: + TableRows generate(QueryContext& context) override + { + scans++; + + TableRows results; + auto indexes = context.constraints["j"].getAll(EQUALS); + for (const auto& j : indexes) { + results.push_back(make_table_row({{"j", INTEGER(j)}, {"text", "none"}})); + } + if (indexes.empty()) { + for (size_t j = 0; j < 100; j++) { + results.push_back( + make_table_row({{"j", INTEGER(j)}, {"text", "some"}})); + } + } + return results; + } + + // Here the goal is to expect/assume the number of scans. + size_t scans{0}; }; class defaultScanTablePlugin : public TablePlugin { - private: - TableColumns columns() const override { - return { - std::make_tuple("i", INTEGER_TYPE, ColumnOptions::DEFAULT), - std::make_tuple("text", INTEGER_TYPE, ColumnOptions::DEFAULT), - }; - } - - public: - TableRows generate(QueryContext& context) override { - scans++; - - TableRows results; - for (size_t i = 0; i < 10; i++) { - results.push_back(make_table_row({{"i", INTEGER(i)}, {"text", "some"}})); - } - return results; - } - - // Here the goal is to expect/assume the number of scans. - size_t scans{0}; +private: + TableColumns columns() const override + { + return { + std::make_tuple("i", INTEGER_TYPE, ColumnOptions::DEFAULT), + std::make_tuple("text", INTEGER_TYPE, ColumnOptions::DEFAULT), + }; + } + +public: + TableRows generate(QueryContext& context) override + { + scans++; + + TableRows results; + for (size_t i = 0; i < 10; i++) { + results.push_back(make_table_row({{"i", INTEGER(i)}, {"text", "some"}})); + } + return results; + } + + // Here the goal is to expect/assume the number of scans. + size_t scans{0}; }; class colsUsedTablePlugin : public TablePlugin { - private: - TableColumns columns() const override { - return { - std::make_tuple("col1", TEXT_TYPE, ColumnOptions::DEFAULT), - std::make_tuple("col2", TEXT_TYPE, ColumnOptions::DEFAULT), - std::make_tuple("col3", TEXT_TYPE, ColumnOptions::DEFAULT), - }; - } - - ColumnAliasSet columnAliases() const override { - return { - {"col2", {"aliasToCol2"}}, - }; - } - - public: - TableRows generate(QueryContext& context) override { - auto r = make_table_row(); - if (context.isColumnUsed("col1")) { - r["col1"] = "value1"; - } - if (context.isColumnUsed("col2")) { - r["col2"] = "value2"; - } - if (context.isColumnUsed("col3")) { - r["col3"] = "value3"; - } - TableRows result; - result.push_back(std::move(r)); - return result; - } - - private: - FRIEND_TEST(VirtualTableTests, test_used_columns); - FRIEND_TEST(VirtualTableTests, test_used_columns_with_alias); +private: + TableColumns columns() const override + { + return { + std::make_tuple("col1", TEXT_TYPE, ColumnOptions::DEFAULT), + std::make_tuple("col2", TEXT_TYPE, ColumnOptions::DEFAULT), + std::make_tuple("col3", TEXT_TYPE, ColumnOptions::DEFAULT), + }; + } + + ColumnAliasSet columnAliases() const override + { + return { + {"col2", {"aliasToCol2"}}, + }; + } + +public: + TableRows generate(QueryContext& context) override + { + auto r = make_table_row(); + if (context.isColumnUsed("col1")) { + r["col1"] = "value1"; + } + if (context.isColumnUsed("col2")) { + r["col2"] = "value2"; + } + if (context.isColumnUsed("col3")) { + r["col3"] = "value3"; + } + TableRows result; + result.push_back(std::move(r)); + return result; + } + +private: + FRIEND_TEST(VirtualTableTests, test_used_columns); + FRIEND_TEST(VirtualTableTests, test_used_columns_with_alias); }; -TEST_F(VirtualTableTests, test_used_columns) { - // Add testing table to the registry. - auto tables = RegistryFactory::get().registry("table"); - auto colsUsed = std::make_shared(); - tables->add("colsUsed1", colsUsed); - auto dbc = SQLiteDBManager::getUnique(); - attachTableInternal( - "colsUsed1", colsUsed->columnDefinition(false), dbc, false); - - QueryData results; - auto status = queryInternal("SELECT col1, col3 FROM colsUsed1", results, dbc); - EXPECT_TRUE(status.ok()); - ASSERT_EQ(results.size(), 1U); - EXPECT_EQ(results[0]["col1"], "value1"); - EXPECT_EQ(results[0].find("col2"), results[0].end()); - EXPECT_EQ(results[0]["col3"], "value3"); - EXPECT_EQ(results[0].find("aliasToCol2"), results[0].end()); +TEST_F(VirtualTableTests, test_used_columns) +{ + // Add testing table to the registry. + auto tables = RegistryFactory::get().registry("table"); + auto colsUsed = std::make_shared(); + tables->add("colsUsed1", colsUsed); + auto dbc = SQLiteDBManager::getUnique(); + attachTableInternal( + "colsUsed1", colsUsed->columnDefinition(false), dbc, false); + + QueryData results; + auto status = queryInternal("SELECT col1, col3 FROM colsUsed1", results, dbc); + EXPECT_TRUE(status.ok()); + ASSERT_EQ(results.size(), 1U); + EXPECT_EQ(results[0]["col1"], "value1"); + EXPECT_EQ(results[0].find("col2"), results[0].end()); + EXPECT_EQ(results[0]["col3"], "value3"); + EXPECT_EQ(results[0].find("aliasToCol2"), results[0].end()); } -TEST_F(VirtualTableTests, test_used_columns_with_alias) { - // Add testing table to the registry. - auto tables = RegistryFactory::get().registry("table"); - auto colsUsed = std::make_shared(); - tables->add("colsUsed2", colsUsed); - auto dbc = SQLiteDBManager::getUnique(); - attachTableInternal( - "colsUsed2", colsUsed->columnDefinition(false), dbc, false); - - QueryData results; - auto status = - queryInternal("SELECT aliasToCol2 FROM colsUsed2", results, dbc); - EXPECT_TRUE(status.ok()); - ASSERT_EQ(results.size(), 1U); - EXPECT_EQ(results[0].find("col1"), results[0].end()); - EXPECT_EQ(results[0].find("col2"), results[0].end()); - EXPECT_EQ(results[0].find("col3"), results[0].end()); - EXPECT_EQ(results[0]["aliasToCol2"], "value2"); +TEST_F(VirtualTableTests, test_used_columns_with_alias) +{ + // Add testing table to the registry. + auto tables = RegistryFactory::get().registry("table"); + auto colsUsed = std::make_shared(); + tables->add("colsUsed2", colsUsed); + auto dbc = SQLiteDBManager::getUnique(); + attachTableInternal( + "colsUsed2", colsUsed->columnDefinition(false), dbc, false); + + QueryData results; + auto status = + queryInternal("SELECT aliasToCol2 FROM colsUsed2", results, dbc); + EXPECT_TRUE(status.ok()); + ASSERT_EQ(results.size(), 1U); + EXPECT_EQ(results[0].find("col1"), results[0].end()); + EXPECT_EQ(results[0].find("col2"), results[0].end()); + EXPECT_EQ(results[0].find("col3"), results[0].end()); + EXPECT_EQ(results[0]["aliasToCol2"], "value2"); } class colsUsedBitsetTablePlugin : public TablePlugin { - private: - TableColumns columns() const override { - return { - std::make_tuple("col1", TEXT_TYPE, ColumnOptions::DEFAULT), - std::make_tuple("col2", TEXT_TYPE, ColumnOptions::DEFAULT), - std::make_tuple("col3", TEXT_TYPE, ColumnOptions::DEFAULT), - }; - } - - ColumnAliasSet columnAliases() const override { - return { - {"col2", {"aliasToCol2"}}, - }; - } - - public: - TableRows generate(QueryContext& context) override { - TableRows results; - auto r = make_table_row(); - if (context.isAnyColumnUsed(UsedColumnsBitset(0x1))) { - r["col1"] = "value1"; - } - if (context.isAnyColumnUsed(UsedColumnsBitset(0x2))) { - r["col2"] = "value2"; - } - if (context.isAnyColumnUsed(UsedColumnsBitset(0x4))) { - r["col3"] = "value3"; - } - results.push_back(std::move(r)); - return results; - } - - private: - FRIEND_TEST(VirtualTableTests, test_used_columns_bitset); - FRIEND_TEST(VirtualTableTests, test_used_columns_bitset_with_alias); +private: + TableColumns columns() const override + { + return { + std::make_tuple("col1", TEXT_TYPE, ColumnOptions::DEFAULT), + std::make_tuple("col2", TEXT_TYPE, ColumnOptions::DEFAULT), + std::make_tuple("col3", TEXT_TYPE, ColumnOptions::DEFAULT), + }; + } + + ColumnAliasSet columnAliases() const override + { + return { + {"col2", {"aliasToCol2"}}, + }; + } + +public: + TableRows generate(QueryContext& context) override + { + TableRows results; + auto r = make_table_row(); + if (context.isAnyColumnUsed(UsedColumnsBitset(0x1))) { + r["col1"] = "value1"; + } + if (context.isAnyColumnUsed(UsedColumnsBitset(0x2))) { + r["col2"] = "value2"; + } + if (context.isAnyColumnUsed(UsedColumnsBitset(0x4))) { + r["col3"] = "value3"; + } + results.push_back(std::move(r)); + return results; + } + +private: + FRIEND_TEST(VirtualTableTests, test_used_columns_bitset); + FRIEND_TEST(VirtualTableTests, test_used_columns_bitset_with_alias); }; -TEST_F(VirtualTableTests, test_used_columns_bitset) { - // Add testing table to the registry. - auto tables = RegistryFactory::get().registry("table"); - auto colsUsed = std::make_shared(); - tables->add("colsUsedBitset1", colsUsed); - auto dbc = SQLiteDBManager::getUnique(); - attachTableInternal( - "colsUsedBitset1", colsUsed->columnDefinition(false), dbc, false); - - QueryData results; - auto status = - queryInternal("SELECT col1, col3 FROM colsUsedBitset1", results, dbc); - EXPECT_TRUE(status.ok()); - ASSERT_EQ(results.size(), 1U); - EXPECT_EQ(results[0]["col1"], "value1"); - EXPECT_EQ(results[0].find("col2"), results[0].end()); - EXPECT_EQ(results[0]["col3"], "value3"); - EXPECT_EQ(results[0].find("aliasToCol2"), results[0].end()); +TEST_F(VirtualTableTests, test_used_columns_bitset) +{ + // Add testing table to the registry. + auto tables = RegistryFactory::get().registry("table"); + auto colsUsed = std::make_shared(); + tables->add("colsUsedBitset1", colsUsed); + auto dbc = SQLiteDBManager::getUnique(); + attachTableInternal( + "colsUsedBitset1", colsUsed->columnDefinition(false), dbc, false); + + QueryData results; + auto status = + queryInternal("SELECT col1, col3 FROM colsUsedBitset1", results, dbc); + EXPECT_TRUE(status.ok()); + ASSERT_EQ(results.size(), 1U); + EXPECT_EQ(results[0]["col1"], "value1"); + EXPECT_EQ(results[0].find("col2"), results[0].end()); + EXPECT_EQ(results[0]["col3"], "value3"); + EXPECT_EQ(results[0].find("aliasToCol2"), results[0].end()); } -TEST_F(VirtualTableTests, test_used_columns_bitset_with_alias) { - // Add testing table to the registry. - auto tables = RegistryFactory::get().registry("table"); - auto colsUsed = std::make_shared(); - tables->add("colsUsedBitset2", colsUsed); - auto dbc = SQLiteDBManager::getUnique(); - attachTableInternal( - "colsUsedBitset2", colsUsed->columnDefinition(false), dbc, false); - - QueryData results; - auto status = - queryInternal("SELECT aliasToCol2 FROM colsUsedBitset2", results, dbc); - EXPECT_TRUE(status.ok()); - ASSERT_EQ(results.size(), 1U); - EXPECT_EQ(results[0].find("col1"), results[0].end()); - EXPECT_EQ(results[0].find("col2"), results[0].end()); - EXPECT_EQ(results[0].find("col3"), results[0].end()); - EXPECT_EQ(results[0]["aliasToCol2"], "value2"); +TEST_F(VirtualTableTests, test_used_columns_bitset_with_alias) +{ + // Add testing table to the registry. + auto tables = RegistryFactory::get().registry("table"); + auto colsUsed = std::make_shared(); + tables->add("colsUsedBitset2", colsUsed); + auto dbc = SQLiteDBManager::getUnique(); + attachTableInternal( + "colsUsedBitset2", colsUsed->columnDefinition(false), dbc, false); + + QueryData results; + auto status = + queryInternal("SELECT aliasToCol2 FROM colsUsedBitset2", results, dbc); + EXPECT_TRUE(status.ok()); + ASSERT_EQ(results.size(), 1U); + EXPECT_EQ(results[0].find("col1"), results[0].end()); + EXPECT_EQ(results[0].find("col2"), results[0].end()); + EXPECT_EQ(results[0].find("col3"), results[0].end()); + EXPECT_EQ(results[0]["aliasToCol2"], "value2"); } } // namespace osquery diff --git a/src/osquery/sql/virtual_sqlite_table.cpp b/src/osquery/sql/virtual_sqlite_table.cpp index 57fc4c3..b01567d 100644 --- a/src/osquery/sql/virtual_sqlite_table.cpp +++ b/src/osquery/sql/virtual_sqlite_table.cpp @@ -20,94 +20,97 @@ namespace errc = boost::system::errc; namespace osquery { -const char* getSystemVFS(bool respect_locking) { - return "unix-none"; +const char* getSystemVFS(bool respect_locking) +{ + return "unix-none"; } Status genSqliteTableRow(sqlite3_stmt* stmt, - TableRows& qd, - const fs::path& sqlite_db) { - auto r = make_table_row(); - for (int i = 0; i < sqlite3_column_count(stmt); ++i) { - auto column_name = std::string(sqlite3_column_name(stmt, i)); - auto column_type = sqlite3_column_type(stmt, i); - switch (column_type) { - case SQLITE_TEXT: { - auto text_value = sqlite3_column_text(stmt, i); - if (text_value != nullptr) { - r[column_name] = std::string(reinterpret_cast(text_value)); - } - break; - } - case SQLITE_FLOAT: { - auto float_value = sqlite3_column_double(stmt, i); - r[column_name] = DOUBLE(float_value); - break; - } - case SQLITE_INTEGER: { - auto int_value = sqlite3_column_int(stmt, i); - r[column_name] = INTEGER(int_value); - break; - } - } - } - if (r.count("path") > 0) { - WARN(OSQUERY) << "Row contains a path key, refusing to overwrite"; - } else { - r["path"] = sqlite_db.string(); - } - qd.push_back(std::move(r)); - return Status::success(); + TableRows& qd, + const fs::path& sqlite_db) +{ + auto r = make_table_row(); + for (int i = 0; i < sqlite3_column_count(stmt); ++i) { + auto column_name = std::string(sqlite3_column_name(stmt, i)); + auto column_type = sqlite3_column_type(stmt, i); + switch (column_type) { + case SQLITE_TEXT: { + auto text_value = sqlite3_column_text(stmt, i); + if (text_value != nullptr) { + r[column_name] = std::string(reinterpret_cast(text_value)); + } + break; + } + case SQLITE_FLOAT: { + auto float_value = sqlite3_column_double(stmt, i); + r[column_name] = DOUBLE(float_value); + break; + } + case SQLITE_INTEGER: { + auto int_value = sqlite3_column_int(stmt, i); + r[column_name] = INTEGER(int_value); + break; + } + } + } + if (r.count("path") > 0) { + WARN(OSQUERY) << "Row contains a path key, refusing to overwrite"; + } else { + r["path"] = sqlite_db.string(); + } + qd.push_back(std::move(r)); + return Status::success(); } Status genTableRowsForSqliteTable(const fs::path& sqlite_db, - const std::string& sqlite_query, - TableRows& results, - bool respect_locking) { - sqlite3* db = nullptr; - boost::system::error_code ec; - if (sqlite_db.empty()) { - return Status(1, "Database path does not exist"); - } + const std::string& sqlite_query, + TableRows& results, + bool respect_locking) +{ + sqlite3* db = nullptr; + boost::system::error_code ec; + if (sqlite_db.empty()) { + return Status(1, "Database path does not exist"); + } - // A tri-state determination of presence - if (!fs::exists(sqlite_db, ec) || ec.value() != errc::success) { - return Status(1, ec.message()); - } + // A tri-state determination of presence + if (!fs::exists(sqlite_db, ec) || ec.value() != errc::success) { + return Status(1, ec.message()); + } - auto rc = sqlite3_open_v2( - sqlite_db.string().c_str(), - &db, - (SQLITE_OPEN_READONLY | SQLITE_OPEN_PRIVATECACHE | SQLITE_OPEN_NOMUTEX), - getSystemVFS(respect_locking)); - if (rc != SQLITE_OK || db == nullptr) { - DEBUG(OSQUERY) << "Cannot open specified database: " - << getStringForSQLiteReturnCode(rc); - if (db != nullptr) { - sqlite3_close(db); - } - return Status(1, "Could not open database"); - } + auto rc = sqlite3_open_v2( + sqlite_db.string().c_str(), + &db, + (SQLITE_OPEN_READONLY | SQLITE_OPEN_PRIVATECACHE | SQLITE_OPEN_NOMUTEX), + getSystemVFS(respect_locking)); + if (rc != SQLITE_OK || db == nullptr) { + DEBUG(OSQUERY) << "Cannot open specified database: " + << getStringForSQLiteReturnCode(rc); + if (db != nullptr) { + sqlite3_close(db); + } + return Status(1, "Could not open database"); + } - sqlite3_stmt* stmt = nullptr; - rc = sqlite3_prepare_v2(db, sqlite_query.c_str(), -1, &stmt, nullptr); - if (rc != SQLITE_OK) { - sqlite3_close(db); - DEBUG(OSQUERY) << "Could not prepare database at path: " << sqlite_db; - return Status(rc, "Could not prepare database"); - } + sqlite3_stmt* stmt = nullptr; + rc = sqlite3_prepare_v2(db, sqlite_query.c_str(), -1, &stmt, nullptr); + if (rc != SQLITE_OK) { + sqlite3_close(db); + DEBUG(OSQUERY) << "Could not prepare database at path: " << sqlite_db; + return Status(rc, "Could not prepare database"); + } - while ((sqlite3_step(stmt)) == SQLITE_ROW) { - auto s = genSqliteTableRow(stmt, results, sqlite_db); - if (!s.ok()) { - break; - } - } + while ((sqlite3_step(stmt)) == SQLITE_ROW) { + auto s = genSqliteTableRow(stmt, results, sqlite_db); + if (!s.ok()) { + break; + } + } - // Close handles and free memory - sqlite3_finalize(stmt); - sqlite3_close(db); + // Close handles and free memory + sqlite3_finalize(stmt); + sqlite3_close(db); - return Status{}; + return Status{}; } } // namespace osquery diff --git a/src/osquery/sql/virtual_table.cpp b/src/osquery/sql/virtual_table.cpp index ecf0252..9221842 100644 --- a/src/osquery/sql/virtual_table.cpp +++ b/src/osquery/sql/virtual_table.cpp @@ -37,30 +37,31 @@ static std::atomic kPlannerCursorID{0}; */ static std::atomic kConstraintIndexID{0}; -static inline std::string opString(unsigned char op) { - switch (op) { - case EQUALS: - return "="; - case GREATER_THAN: - return ">"; - case LESS_THAN_OR_EQUALS: - return "<="; - case LESS_THAN: - return "<"; - case GREATER_THAN_OR_EQUALS: - return ">="; - case LIKE: - return "LIKE"; - case MATCH: - return "MATCH"; - case GLOB: - return "GLOB"; - case REGEXP: - return "REGEX"; - case UNIQUE: - return "UNIQUE"; - } - return "?"; +static inline std::string opString(unsigned char op) +{ + switch (op) { + case EQUALS: + return "="; + case GREATER_THAN: + return ">"; + case LESS_THAN_OR_EQUALS: + return "<="; + case LESS_THAN: + return "<"; + case GREATER_THAN_OR_EQUALS: + return ">="; + case LIKE: + return "LIKE"; + case MATCH: + return "MATCH"; + case GLOB: + return "GLOB"; + case REGEXP: + return "REGEX"; + case UNIQUE: + return "UNIQUE"; + } + return "?"; } namespace { @@ -70,53 +71,55 @@ std::unordered_map sqlite_module_map; Mutex sqlite_module_map_mutex; bool getColumnValue(std::string& value, - size_t index, - size_t argc, - sqlite3_value** argv) { - value.clear(); - - if (index >= argc) { - return false; - } - - auto sqlite_value = argv[index]; - switch (sqlite3_value_type(sqlite_value)) { - case SQLITE_INTEGER: { - auto temp = sqlite3_value_int64(sqlite_value); - value = std::to_string(temp); - break; - } - - case SQLITE_FLOAT: { - auto temp = sqlite3_value_double(sqlite_value); - value = std::to_string(temp); - break; - } - - case SQLITE_BLOB: - case SQLITE3_TEXT: { - auto data_ptr = static_cast(sqlite3_value_blob(sqlite_value)); - auto buffer_size = static_cast(sqlite3_value_bytes(sqlite_value)); - - value.assign(data_ptr, buffer_size); - break; - } - - case SQLITE_NULL: { - break; - } - - default: { - ERROR(OSQUERY) << "Invalid column type returned by sqlite"; - return false; - } - } - - return true; + size_t index, + size_t argc, + sqlite3_value** argv) +{ + value.clear(); + + if (index >= argc) { + return false; + } + + auto sqlite_value = argv[index]; + switch (sqlite3_value_type(sqlite_value)) { + case SQLITE_INTEGER: { + auto temp = sqlite3_value_int64(sqlite_value); + value = std::to_string(temp); + break; + } + + case SQLITE_FLOAT: { + auto temp = sqlite3_value_double(sqlite_value); + value = std::to_string(temp); + break; + } + + case SQLITE_BLOB: + case SQLITE3_TEXT: { + auto data_ptr = static_cast(sqlite3_value_blob(sqlite_value)); + auto buffer_size = static_cast(sqlite3_value_bytes(sqlite_value)); + + value.assign(data_ptr, buffer_size); + break; + } + + case SQLITE_NULL: { + break; + } + + default: { + ERROR(OSQUERY) << "Invalid column type returned by sqlite"; + return false; + } + } + + return true; } /// PATCH START ////////////////////////////////////////////////////////////// -int serializeDeleteParameters(std::string& serialized, VirtualTable* pVtab) { +int serializeDeleteParameters(std::string& serialized, VirtualTable* pVtab) +{ auto content = pVtab->content; if (content->constraints.size() <= 0) { ERROR(OSQUERY) << "Invalid constraints arguments"; @@ -149,7 +152,8 @@ int serializeDeleteParameters(std::string& serialized, VirtualTable* pVtab) { int serializeUpdateParameters(std::string& serialized, size_t argc, sqlite3_value** argv, - const TableColumns& columnDescriptors) { + const TableColumns& columnDescriptors) +{ if (columnDescriptors.size() != argc - 2U) { DEBUG(VIST) << "Wrong column count: " << argc - 2U << ". Expected: " << columnDescriptors.size(); @@ -172,7 +176,7 @@ int serializeUpdateParameters(std::string& serialized, switch (receivedValueType) { case SQLITE_INTEGER: { if (columnType != INTEGER_TYPE && columnType != BIGINT_TYPE && - columnType != UNSIGNED_BIGINT_TYPE) { + columnType != UNSIGNED_BIGINT_TYPE) { return SQLITE_MISMATCH; } @@ -197,7 +201,7 @@ int serializeUpdateParameters(std::string& serialized, else typeMismatch = columnType != TEXT_TYPE; - if (typeMismatch) + if (typeMismatch) return SQLITE_MISMATCH; auto data_pointer = sqlite3_value_blob(sqlite_value); @@ -238,803 +242,823 @@ int serializeUpdateParameters(std::string& serialized, } void setTableErrorMessage(sqlite3_vtab* vtable, - const std::string& error_message) { - // We are required to always replace the pointer with memory - // allocated with sqlite3_malloc. This buffer is freed automatically - // by sqlite3 on exit - // - // Documentation: https://www.sqlite.org/vtab.html section 1.2 - - if (vtable->zErrMsg != nullptr) { - sqlite3_free(vtable->zErrMsg); - } - - auto buffer_size = static_cast(error_message.size() + 1); - - vtable->zErrMsg = static_cast(sqlite3_malloc(buffer_size)); - if (vtable->zErrMsg != nullptr) { - memcpy(vtable->zErrMsg, error_message.c_str(), buffer_size); - } + const std::string& error_message) +{ + // We are required to always replace the pointer with memory + // allocated with sqlite3_malloc. This buffer is freed automatically + // by sqlite3 on exit + // + // Documentation: https://www.sqlite.org/vtab.html section 1.2 + + if (vtable->zErrMsg != nullptr) { + sqlite3_free(vtable->zErrMsg); + } + + auto buffer_size = static_cast(error_message.size() + 1); + + vtable->zErrMsg = static_cast(sqlite3_malloc(buffer_size)); + if (vtable->zErrMsg != nullptr) { + memcpy(vtable->zErrMsg, error_message.c_str(), buffer_size); + } } } // namespace -inline std::string table_doc(const std::string& name) { - return "https://osquery.io/schema/#" + name; +inline std::string table_doc(const std::string& name) +{ + return "https://osquery.io/schema/#" + name; } -static void plan(const std::string& output) { +static void plan(const std::string& output) +{ } -int xOpen(sqlite3_vtab* tab, sqlite3_vtab_cursor** ppCursor) { - auto* pCur = new BaseCursor; - auto* pVtab = (VirtualTable*)tab; - plan("Opening cursor (" + std::to_string(kPlannerCursorID) + - ") for table: " + pVtab->content->name); - pCur->id = kPlannerCursorID++; - pCur->base.pVtab = tab; - *ppCursor = (sqlite3_vtab_cursor*)pCur; +int xOpen(sqlite3_vtab* tab, sqlite3_vtab_cursor** ppCursor) +{ + auto* pCur = new BaseCursor; + auto* pVtab = (VirtualTable*)tab; + plan("Opening cursor (" + std::to_string(kPlannerCursorID) + + ") for table: " + pVtab->content->name); + pCur->id = kPlannerCursorID++; + pCur->base.pVtab = tab; + *ppCursor = (sqlite3_vtab_cursor*)pCur; - return SQLITE_OK; + return SQLITE_OK; } -int xClose(sqlite3_vtab_cursor* cur) { - BaseCursor* pCur = (BaseCursor*)cur; - plan("Closing cursor (" + std::to_string(pCur->id) + ")"); - delete pCur; - return SQLITE_OK; +int xClose(sqlite3_vtab_cursor* cur) +{ + BaseCursor* pCur = (BaseCursor*)cur; + plan("Closing cursor (" + std::to_string(pCur->id) + ")"); + delete pCur; + return SQLITE_OK; } -int xEof(sqlite3_vtab_cursor* cur) { - BaseCursor* pCur = (BaseCursor*)cur; +int xEof(sqlite3_vtab_cursor* cur) +{ + BaseCursor* pCur = (BaseCursor*)cur; - if (pCur->row >= pCur->n) { - // If the requested row exceeds the size of the row set then all rows - // have been visited, clear the data container. - return true; - } - return false; + if (pCur->row >= pCur->n) { + // If the requested row exceeds the size of the row set then all rows + // have been visited, clear the data container. + return true; + } + return false; } -int xDestroy(sqlite3_vtab* p) { - auto* pVtab = (VirtualTable*)p; - delete pVtab; - return SQLITE_OK; +int xDestroy(sqlite3_vtab* p) +{ + auto* pVtab = (VirtualTable*)p; + delete pVtab; + return SQLITE_OK; } -int xNext(sqlite3_vtab_cursor* cur) { - BaseCursor* pCur = (BaseCursor*)cur; - pCur->row++; - return SQLITE_OK; +int xNext(sqlite3_vtab_cursor* cur) +{ + BaseCursor* pCur = (BaseCursor*)cur; + pCur->row++; + return SQLITE_OK; } -int xRowid(sqlite3_vtab_cursor* cur, sqlite_int64* pRowid) { - *pRowid = 0; +int xRowid(sqlite3_vtab_cursor* cur, sqlite_int64* pRowid) +{ + *pRowid = 0; - const BaseCursor* pCur = (BaseCursor*)cur; - auto data_it = std::next(pCur->rows.begin(), pCur->row); - if (data_it >= pCur->rows.end()) { - return SQLITE_ERROR; - } + const BaseCursor* pCur = (BaseCursor*)cur; + auto data_it = std::next(pCur->rows.begin(), pCur->row); + if (data_it >= pCur->rows.end()) { + return SQLITE_ERROR; + } - // Use the rowid returned by the extension, if available; most likely, this - // will only be used by extensions providing read/write tables - const auto& current_row = *data_it; - return current_row->get_rowid(pCur->row, pRowid); + // Use the rowid returned by the extension, if available; most likely, this + // will only be used by extensions providing read/write tables + const auto& current_row = *data_it; + return current_row->get_rowid(pCur->row, pRowid); } int xUpdate(sqlite3_vtab* p, - int argc, - sqlite3_value** argv, - sqlite3_int64* pRowid) { - auto argument_count = static_cast(argc); - auto* pVtab = (VirtualTable*)p; + int argc, + sqlite3_value** argv, + sqlite3_int64* pRowid) +{ + auto argument_count = static_cast(argc); + auto* pVtab = (VirtualTable*)p; - auto content = pVtab->content; - const auto& columnDescriptors = content->columns; + auto content = pVtab->content; + const auto& columnDescriptors = content->columns; + + std::string table_name = pVtab->content->name; + + // The SQLite instance communicates to the TablePlugin via the context. + QueryContext context(content); + PluginRequest plugin_request; + + if (argument_count == 1U) { + // This is a simple delete operation + plugin_request = {{"action", "delete"}}; + + auto row_to_delete = sqlite3_value_int64(argv[0]); + plugin_request.insert({"id", std::to_string(row_to_delete)}); + + /// PATCH START ////////////////////////////////////////////////////////////// + std::string json_values; + serializeDeleteParameters(json_values, reinterpret_cast(p)); + plugin_request.insert({"json_values", json_values}); + /// PATCH END //////////////////////////////////////////////////////////////// + } else if (sqlite3_value_type(argv[0]) == SQLITE_NULL) { + // This is an INSERT query; if the rowid has been generated for us, we'll + // find it inside argv[1] + plugin_request = {{"action", "insert"}}; + + // Add the values to insert; we should have a value for each column present + // in the table, even if the user did not specify a value (in which case + // we will find a nullptr) + std::string json_values; + auto serializerError = serializeUpdateParameters( + json_values, argument_count, argv, columnDescriptors); + if (serializerError != SQLITE_OK) { + DEBUG(OSQUERY) << "Failed to serialize the UPDATE request"; + return serializerError; + } - std::string table_name = pVtab->content->name; + plugin_request.insert({"json_values", json_values}); - // The SQLite instance communicates to the TablePlugin via the context. - QueryContext context(content); - PluginRequest plugin_request; + if (sqlite3_value_type(argv[1]) != SQLITE_NULL) { + plugin_request.insert({"auto_rowid", "true"}); - if (argument_count == 1U) { - // This is a simple delete operation - plugin_request = {{"action", "delete"}}; + std::string auto_generated_rowid; + if (!getColumnValue(auto_generated_rowid, 1U, argument_count, argv)) { + DEBUG(OSQUERY) << "Failed to retrieve the column value"; + return SQLITE_ERROR; + } - auto row_to_delete = sqlite3_value_int64(argv[0]); - plugin_request.insert({"id", std::to_string(row_to_delete)}); + plugin_request.insert({"id", auto_generated_rowid}); -/// PATCH START ////////////////////////////////////////////////////////////// - std::string json_values; - serializeDeleteParameters(json_values, reinterpret_cast(p)); - plugin_request.insert({"json_values", json_values}); -/// PATCH END //////////////////////////////////////////////////////////////// - } else if (sqlite3_value_type(argv[0]) == SQLITE_NULL) { - // This is an INSERT query; if the rowid has been generated for us, we'll - // find it inside argv[1] - plugin_request = {{"action", "insert"}}; - - // Add the values to insert; we should have a value for each column present - // in the table, even if the user did not specify a value (in which case - // we will find a nullptr) - std::string json_values; - auto serializerError = serializeUpdateParameters( - json_values, argument_count, argv, columnDescriptors); - if (serializerError != SQLITE_OK) { - DEBUG(OSQUERY) << "Failed to serialize the UPDATE request"; - return serializerError; + } else { + plugin_request.insert({"auto_rowid", "false"}); + } + + } else if (sqlite3_value_type(argv[0]) == SQLITE_INTEGER) { + // This is an UPDATE query; we have to update the rowid value in some + // cases (if argv[1] is populated) + plugin_request = {{"action", "update"}}; + + std::string current_rowid; + if (!getColumnValue(current_rowid, 0U, argument_count, argv)) { + DEBUG(OSQUERY) << "Failed to retrieve the column value"; + return SQLITE_ERROR; + } + + plugin_request.insert({"id", current_rowid}); + + // Get the new rowid, if any + if (sqlite3_value_type(argv[1]) == SQLITE_INTEGER) { + std::string new_rowid; + if (!getColumnValue(new_rowid, 1U, argument_count, argv)) { + DEBUG(OSQUERY) << "Failed to retrieve the column value"; + return SQLITE_ERROR; + } + + if (new_rowid != plugin_request.at("id")) { + plugin_request.insert({"new_id", new_rowid}); + } + } + + // Get the values to update + std::string json_values; + auto serializerError = serializeUpdateParameters( + json_values, argument_count, argv, columnDescriptors); + if (serializerError != SQLITE_OK) { + DEBUG(OSQUERY) << "Failed to serialize the UPDATE request"; + return serializerError; + } + + plugin_request.insert({"json_values", json_values}); + + } else { + DEBUG(OSQUERY) << "Invalid xUpdate call"; + return SQLITE_ERROR; } - plugin_request.insert({"json_values", json_values}); - - if (sqlite3_value_type(argv[1]) != SQLITE_NULL) { - plugin_request.insert({"auto_rowid", "true"}); - - std::string auto_generated_rowid; - if (!getColumnValue(auto_generated_rowid, 1U, argument_count, argv)) { - DEBUG(OSQUERY) << "Failed to retrieve the column value"; - return SQLITE_ERROR; - } - - plugin_request.insert({"id", auto_generated_rowid}); - - } else { - plugin_request.insert({"auto_rowid", "false"}); - } - - } else if (sqlite3_value_type(argv[0]) == SQLITE_INTEGER) { - // This is an UPDATE query; we have to update the rowid value in some - // cases (if argv[1] is populated) - plugin_request = {{"action", "update"}}; - - std::string current_rowid; - if (!getColumnValue(current_rowid, 0U, argument_count, argv)) { - DEBUG(OSQUERY) << "Failed to retrieve the column value"; - return SQLITE_ERROR; - } - - plugin_request.insert({"id", current_rowid}); - - // Get the new rowid, if any - if (sqlite3_value_type(argv[1]) == SQLITE_INTEGER) { - std::string new_rowid; - if (!getColumnValue(new_rowid, 1U, argument_count, argv)) { - DEBUG(OSQUERY) << "Failed to retrieve the column value"; - return SQLITE_ERROR; - } - - if (new_rowid != plugin_request.at("id")) { - plugin_request.insert({"new_id", new_rowid}); - } - } - - // Get the values to update - std::string json_values; - auto serializerError = serializeUpdateParameters( - json_values, argument_count, argv, columnDescriptors); - if (serializerError != SQLITE_OK) { - DEBUG(OSQUERY) << "Failed to serialize the UPDATE request"; - return serializerError; + TablePlugin::setRequestFromContext(context, plugin_request); + + // Forward the query to the table extension + PluginResponse response_list; + Registry::call("table", table_name, plugin_request, response_list); + + // Validate the response + if (response_list.size() != 1) { + DEBUG(OSQUERY) << "Invalid response from the extension table"; + return SQLITE_ERROR; + } + + const auto& response = response_list.at(0); + if (response.count("status") == 0) { + DEBUG(OSQUERY) << "Invalid response from the extension table; the status field is " + "missing"; + + return SQLITE_ERROR; + } + + const auto& status_value = response.at("status"); + if (status_value == "readonly") { + auto error_message = + "table " + pVtab->content->name + " may not be modified"; + + setTableErrorMessage(p, error_message); + return SQLITE_READONLY; + + } else if (status_value == "failure") { + auto custom_error_message_it = response.find("message"); + if (custom_error_message_it == response.end()) { + return SQLITE_ERROR; + } + + const auto& custom_error_message = custom_error_message_it->second; + setTableErrorMessage(p, custom_error_message); + return SQLITE_ERROR; + + } else if (status_value == "constraint") { + return SQLITE_CONSTRAINT; + + } else if (status_value != "success") { + DEBUG(OSQUERY) << "Invalid response from the extension table; the status field " + "could not be recognized"; + + return SQLITE_ERROR; } - plugin_request.insert({"json_values", json_values}); - - } else { - DEBUG(OSQUERY) << "Invalid xUpdate call"; - return SQLITE_ERROR; - } - - TablePlugin::setRequestFromContext(context, plugin_request); - - // Forward the query to the table extension - PluginResponse response_list; - Registry::call("table", table_name, plugin_request, response_list); - - // Validate the response - if (response_list.size() != 1) { - DEBUG(OSQUERY) << "Invalid response from the extension table"; - return SQLITE_ERROR; - } - - const auto& response = response_list.at(0); - if (response.count("status") == 0) { - DEBUG(OSQUERY) << "Invalid response from the extension table; the status field is " - "missing"; - - return SQLITE_ERROR; - } - - const auto& status_value = response.at("status"); - if (status_value == "readonly") { - auto error_message = - "table " + pVtab->content->name + " may not be modified"; - - setTableErrorMessage(p, error_message); - return SQLITE_READONLY; - - } else if (status_value == "failure") { - auto custom_error_message_it = response.find("message"); - if (custom_error_message_it == response.end()) { - return SQLITE_ERROR; - } - - const auto& custom_error_message = custom_error_message_it->second; - setTableErrorMessage(p, custom_error_message); - return SQLITE_ERROR; - - } else if (status_value == "constraint") { - return SQLITE_CONSTRAINT; - - } else if (status_value != "success") { - DEBUG(OSQUERY) << "Invalid response from the extension table; the status field " - "could not be recognized"; - - return SQLITE_ERROR; - } - -/* - // INSERT actions must always return a valid rowid to sqlite - if (plugin_request.at("action") == "insert") { - std::string rowid; - - if (plugin_request.at("auto_rowid") == "true") { - if (!getColumnValue(rowid, 1U, argument_count, argv)) { - DEBUG(OSQUERY) << "Failed to retrieve the rowid value"; - return SQLITE_ERROR; - } - - } else { - auto id_it = response.find("id"); - if (id_it == response.end()) { - DEBUG(OSQUERY) << "The plugin did not return a row id"; - return SQLITE_ERROR; - } - - rowid = id_it->second; - } - - auto exp = tryTo(rowid); - if (exp.isError()) { - DEBUG(OSQUERY) << "The plugin did not return a valid row id"; - return SQLITE_ERROR; - } - *pRowid = exp.take(); - } -*/ - return SQLITE_OK; + /* + // INSERT actions must always return a valid rowid to sqlite + if (plugin_request.at("action") == "insert") { + std::string rowid; + + if (plugin_request.at("auto_rowid") == "true") { + if (!getColumnValue(rowid, 1U, argument_count, argv)) { + DEBUG(OSQUERY) << "Failed to retrieve the rowid value"; + return SQLITE_ERROR; + } + + } else { + auto id_it = response.find("id"); + if (id_it == response.end()) { + DEBUG(OSQUERY) << "The plugin did not return a row id"; + return SQLITE_ERROR; + } + + rowid = id_it->second; + } + + auto exp = tryTo(rowid); + if (exp.isError()) { + DEBUG(OSQUERY) << "The plugin did not return a valid row id"; + return SQLITE_ERROR; + } + *pRowid = exp.take(); + } + */ + return SQLITE_OK; } int xCreate(sqlite3* db, - void* pAux, - int argc, - const char* const* argv, - sqlite3_vtab** ppVtab, - char** pzErr) { - auto* pVtab = new VirtualTable; - if (argc == 0 || argv[0] == nullptr) { - delete pVtab; - return SQLITE_NOMEM; - } - - memset(pVtab, 0, sizeof(VirtualTable)); - pVtab->content = std::make_shared(); - pVtab->instance = (SQLiteDBInstance*)pAux; - - // Create a TablePlugin Registry call, expect column details as the response. - PluginResponse response; - pVtab->content->name = std::string(argv[0]); - const auto& name = pVtab->content->name; - - // Get the table column information. - auto status = - Registry::call("table", name, {{"action", "columns"}}, response); - if (!status.ok() || response.size() == 0) { - delete pVtab; - return SQLITE_ERROR; - } - - bool is_extension = false; - - // Generate an SQL create table statement from the retrieved column details. - // This call to columnDefinition requests column aliases (as HIDDEN columns). - auto statement = - "CREATE TABLE " + name + columnDefinition(response, true, is_extension); - - int rc = sqlite3_declare_vtab(db, statement.c_str()); - if (rc != SQLITE_OK || !status.ok() || response.size() == 0) { - ERROR(OSQUERY) << "Error creating virtual table: " << name << " (" << rc - << "): " << getStringForSQLiteReturnCode(rc); - - DEBUG(OSQUERY) << "Cannot create virtual table using: " << statement; - delete pVtab; - return (rc != SQLITE_OK) ? rc : SQLITE_ERROR; - } - - // Tables may request aliases as views. - std::set views; - - // Keep a local copy of the column details in the VirtualTableContent struct. - // This allows introspection into the column type without additional calls. - for (const auto& column : response) { - auto cid = column.find("id"); - if (cid == column.end()) { - // This does not define a column type. - continue; - } - - auto cname = column.find("name"); - auto ctype = column.find("type"); - if (cid->second == "column" && cname != column.end() && - ctype != column.end()) { - // This is a malformed column definition. - // Populate the virtual table specific persistent column information. - auto options = ColumnOptions::DEFAULT; - auto cop = column.find("op"); - if (cop != column.end()) { - auto op = tryTo(cop->second); - if (op) { - options = static_cast(op.take()); - } - } - - pVtab->content->columns.push_back(std::make_tuple( - cname->second, columnTypeName(ctype->second), options)); - } else if (cid->second == "alias") { - // Create associated views for table aliases. - auto calias = column.find("alias"); - if (calias != column.end()) { - views.insert(calias->second); - } - } else if (cid->second == "columnAlias" && cname != column.end()) { - auto ctarget = column.find("target"); - if (ctarget == column.end()) { - continue; - } - - // Record the column in the set of columns. - // This is required because SQLITE uses indexes to identify columns. - // Use an UNKNOWN_TYPE as a pseudo-mask, since the type does not matter. - pVtab->content->columns.push_back( - std::make_tuple(cname->second, UNKNOWN_TYPE, ColumnOptions::HIDDEN)); - // Record a mapping of the requested column alias name. - size_t target_index = 0; - for (size_t i = 0; i < pVtab->content->columns.size(); i++) { - const auto& target_column = pVtab->content->columns[i]; - if (std::get<0>(target_column) == ctarget->second) { - target_index = i; - break; - } - } - pVtab->content->aliases[cname->second] = target_index; - } else if (cid->second == "attributes") { - auto cattr = column.find("attributes"); - // Store the attributes locally so they may be passed to the SQL object. - if (cattr != column.end()) { - auto attr = tryTo(cattr->second); - if (attr) { - pVtab->content->attributes = - static_cast(attr.take()); - } - } - } - } - - // Create the requested 'aliases'. - for (const auto& view : views) { - statement = "CREATE VIEW " + view + " AS SELECT * FROM " + name; - sqlite3_exec(db, statement.c_str(), nullptr, nullptr, nullptr); - } - - *ppVtab = (sqlite3_vtab*)pVtab; - return rc; + void* pAux, + int argc, + const char* const* argv, + sqlite3_vtab** ppVtab, + char** pzErr) +{ + auto* pVtab = new VirtualTable; + if (argc == 0 || argv[0] == nullptr) { + delete pVtab; + return SQLITE_NOMEM; + } + + memset(pVtab, 0, sizeof(VirtualTable)); + pVtab->content = std::make_shared(); + pVtab->instance = (SQLiteDBInstance*)pAux; + + // Create a TablePlugin Registry call, expect column details as the response. + PluginResponse response; + pVtab->content->name = std::string(argv[0]); + const auto& name = pVtab->content->name; + + // Get the table column information. + auto status = + Registry::call("table", name, {{"action", "columns"}}, response); + if (!status.ok() || response.size() == 0) { + delete pVtab; + return SQLITE_ERROR; + } + + bool is_extension = false; + + // Generate an SQL create table statement from the retrieved column details. + // This call to columnDefinition requests column aliases (as HIDDEN columns). + auto statement = + "CREATE TABLE " + name + columnDefinition(response, true, is_extension); + + int rc = sqlite3_declare_vtab(db, statement.c_str()); + if (rc != SQLITE_OK || !status.ok() || response.size() == 0) { + ERROR(OSQUERY) << "Error creating virtual table: " << name << " (" << rc + << "): " << getStringForSQLiteReturnCode(rc); + + DEBUG(OSQUERY) << "Cannot create virtual table using: " << statement; + delete pVtab; + return (rc != SQLITE_OK) ? rc : SQLITE_ERROR; + } + + // Tables may request aliases as views. + std::set views; + + // Keep a local copy of the column details in the VirtualTableContent struct. + // This allows introspection into the column type without additional calls. + for (const auto& column : response) { + auto cid = column.find("id"); + if (cid == column.end()) { + // This does not define a column type. + continue; + } + + auto cname = column.find("name"); + auto ctype = column.find("type"); + if (cid->second == "column" && cname != column.end() && + ctype != column.end()) { + // This is a malformed column definition. + // Populate the virtual table specific persistent column information. + auto options = ColumnOptions::DEFAULT; + auto cop = column.find("op"); + if (cop != column.end()) { + auto op = tryTo(cop->second); + if (op) { + options = static_cast(op.take()); + } + } + + pVtab->content->columns.push_back(std::make_tuple( + cname->second, columnTypeName(ctype->second), options)); + } else if (cid->second == "alias") { + // Create associated views for table aliases. + auto calias = column.find("alias"); + if (calias != column.end()) { + views.insert(calias->second); + } + } else if (cid->second == "columnAlias" && cname != column.end()) { + auto ctarget = column.find("target"); + if (ctarget == column.end()) { + continue; + } + + // Record the column in the set of columns. + // This is required because SQLITE uses indexes to identify columns. + // Use an UNKNOWN_TYPE as a pseudo-mask, since the type does not matter. + pVtab->content->columns.push_back( + std::make_tuple(cname->second, UNKNOWN_TYPE, ColumnOptions::HIDDEN)); + // Record a mapping of the requested column alias name. + size_t target_index = 0; + for (size_t i = 0; i < pVtab->content->columns.size(); i++) { + const auto& target_column = pVtab->content->columns[i]; + if (std::get<0>(target_column) == ctarget->second) { + target_index = i; + break; + } + } + pVtab->content->aliases[cname->second] = target_index; + } else if (cid->second == "attributes") { + auto cattr = column.find("attributes"); + // Store the attributes locally so they may be passed to the SQL object. + if (cattr != column.end()) { + auto attr = tryTo(cattr->second); + if (attr) { + pVtab->content->attributes = + static_cast(attr.take()); + } + } + } + } + + // Create the requested 'aliases'. + for (const auto& view : views) { + statement = "CREATE VIEW " + view + " AS SELECT * FROM " + name; + sqlite3_exec(db, statement.c_str(), nullptr, nullptr, nullptr); + } + + *ppVtab = (sqlite3_vtab*)pVtab; + return rc; } -int xColumn(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int col) { - BaseCursor* pCur = (BaseCursor*)cur; - const auto* pVtab = (VirtualTable*)cur->pVtab; - if (col >= static_cast(pVtab->content->columns.size())) { - // Requested column index greater than column set size. - return SQLITE_ERROR; - } +int xColumn(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int col) +{ + BaseCursor* pCur = (BaseCursor*)cur; + const auto* pVtab = (VirtualTable*)cur->pVtab; + if (col >= static_cast(pVtab->content->columns.size())) { + // Requested column index greater than column set size. + return SQLITE_ERROR; + } - TableRowHolder& row = pCur->rows[pCur->row]; - return row->get_column(ctx, cur->pVtab, col); + TableRowHolder& row = pCur->rows[pCur->row]; + return row->get_column(ctx, cur->pVtab, col); } -static inline bool sensibleComparison(ColumnType type, unsigned char op) { - if (type == TEXT_TYPE) { - if (op == GREATER_THAN || op == GREATER_THAN_OR_EQUALS || op == LESS_THAN || - op == LESS_THAN_OR_EQUALS) { - return false; - } - } - return true; +static inline bool sensibleComparison(ColumnType type, unsigned char op) +{ + if (type == TEXT_TYPE) { + if (op == GREATER_THAN || op == GREATER_THAN_OR_EQUALS || op == LESS_THAN || + op == LESS_THAN_OR_EQUALS) { + return false; + } + } + return true; } -static int xBestIndex(sqlite3_vtab* tab, sqlite3_index_info* pIdxInfo) { - auto* pVtab = (VirtualTable*)tab; - const auto& columns = pVtab->content->columns; - - ConstraintSet constraints; - // Keep track of the index used for each valid constraint. - // Expect this index to correspond with argv within xFilter. - size_t expr_index = 0; - // If any constraints are unusable increment the cost of the index. - double cost = 1; - - // Tables may have requirements or use indexes. - bool required_satisfied = false; - bool index_used = false; - - // Expressions operating on the same virtual table are loosely identified by - // the consecutive sets of terms each of the constraint sets are applied onto. - // Subsequent attempts from failed (unusable) constraints replace the set, - // while new sets of terms append. - if (pIdxInfo->nConstraint > 0) { - for (size_t i = 0; i < static_cast(pIdxInfo->nConstraint); ++i) { - // Record the term index (this index exists across all expressions). - const auto& constraint_info = pIdxInfo->aConstraint[i]; +static int xBestIndex(sqlite3_vtab* tab, sqlite3_index_info* pIdxInfo) +{ + auto* pVtab = (VirtualTable*)tab; + const auto& columns = pVtab->content->columns; + + ConstraintSet constraints; + // Keep track of the index used for each valid constraint. + // Expect this index to correspond with argv within xFilter. + size_t expr_index = 0; + // If any constraints are unusable increment the cost of the index. + double cost = 1; + + // Tables may have requirements or use indexes. + bool required_satisfied = false; + bool index_used = false; + + // Expressions operating on the same virtual table are loosely identified by + // the consecutive sets of terms each of the constraint sets are applied onto. + // Subsequent attempts from failed (unusable) constraints replace the set, + // while new sets of terms append. + if (pIdxInfo->nConstraint > 0) { + for (size_t i = 0; i < static_cast(pIdxInfo->nConstraint); ++i) { + // Record the term index (this index exists across all expressions). + const auto& constraint_info = pIdxInfo->aConstraint[i]; #if defined(DEBUG) - plan("Evaluating constraints for table: " + pVtab->content->name + - " [index=" + std::to_string(i) + - " column=" + std::to_string(constraint_info.iColumn) + - " term=" + std::to_string((int)constraint_info.iTermOffset) + - " usable=" + std::to_string((int)constraint_info.usable) + "]"); + plan("Evaluating constraints for table: " + pVtab->content->name + + " [index=" + std::to_string(i) + + " column=" + std::to_string(constraint_info.iColumn) + + " term=" + std::to_string((int)constraint_info.iTermOffset) + + " usable=" + std::to_string((int)constraint_info.usable) + "]"); #endif - if (!constraint_info.usable) { - // A higher cost less priority, prefer more usable query constraints. - cost += 10; - continue; - } - - // Lookup the column name given an index into the table column set. - if (constraint_info.iColumn < 0 || - static_cast(constraint_info.iColumn) >= - pVtab->content->columns.size()) { - cost += 10; - continue; - } - const auto& name = std::get<0>(columns[constraint_info.iColumn]); - const auto& type = std::get<1>(columns[constraint_info.iColumn]); - if (!sensibleComparison(type, constraint_info.op)) { - cost += 10; - continue; - } - - // Check if this constraint is on an index or required column. - const auto& options = std::get<2>(columns[constraint_info.iColumn]); - if (options & ColumnOptions::REQUIRED) { - index_used = true; - required_satisfied = true; - } else if (options & (ColumnOptions::INDEX | ColumnOptions::ADDITIONAL)) { - index_used = true; - } - - // Save a pair of the name and the constraint operator. - // Use this constraint during xFilter by performing a scan and column - // name lookup through out all cursor constraint lists. - constraints.push_back( - std::make_pair(name, Constraint(constraint_info.op))); - pIdxInfo->aConstraintUsage[i].argvIndex = static_cast(++expr_index); + if (!constraint_info.usable) { + // A higher cost less priority, prefer more usable query constraints. + cost += 10; + continue; + } + + // Lookup the column name given an index into the table column set. + if (constraint_info.iColumn < 0 || + static_cast(constraint_info.iColumn) >= + pVtab->content->columns.size()) { + cost += 10; + continue; + } + const auto& name = std::get<0>(columns[constraint_info.iColumn]); + const auto& type = std::get<1>(columns[constraint_info.iColumn]); + if (!sensibleComparison(type, constraint_info.op)) { + cost += 10; + continue; + } + + // Check if this constraint is on an index or required column. + const auto& options = std::get<2>(columns[constraint_info.iColumn]); + if (options & ColumnOptions::REQUIRED) { + index_used = true; + required_satisfied = true; + } else if (options & (ColumnOptions::INDEX | ColumnOptions::ADDITIONAL)) { + index_used = true; + } + + // Save a pair of the name and the constraint operator. + // Use this constraint during xFilter by performing a scan and column + // name lookup through out all cursor constraint lists. + constraints.push_back( + std::make_pair(name, Constraint(constraint_info.op))); + pIdxInfo->aConstraintUsage[i].argvIndex = static_cast(++expr_index); #if defined(DEBUG) - plan("Adding constraint for table: " + pVtab->content->name + - " [column=" + name + " arg_index=" + std::to_string(expr_index) + - " op=" + std::to_string(constraint_info.op) + "]"); + plan("Adding constraint for table: " + pVtab->content->name + + " [column=" + name + " arg_index=" + std::to_string(expr_index) + + " op=" + std::to_string(constraint_info.op) + "]"); #endif - } - } - - // Check the table for a required column. - for (const auto& column : columns) { - auto& options = std::get<2>(column); - if ((options & ColumnOptions::REQUIRED) && !required_satisfied) { - // A column is marked required, but no constraint satisfies. - return SQLITE_CONSTRAINT; - } - } - - if (!index_used) { - // A column is marked index, but no index constraint was provided. - cost += 200; - } - - UsedColumns colsUsed; - UsedColumnsBitset colsUsedBitset(pIdxInfo->colUsed); - if (colsUsedBitset.any()) { - for (size_t i = 0; i < columns.size(); i++) { - // Check whether the column is used. colUsed has one bit for each of the - // first 63 columns, and the 64th bit indicates that at least one other - // column is used. - auto bit = i < 63 ? i : 63U; - if (colsUsedBitset[bit]) { - auto column_name = std::get<0>(columns[i]); - - if (pVtab->content->aliases.count(column_name)) { - colsUsedBitset.reset(bit); - auto real_column_index = pVtab->content->aliases[column_name]; - bit = real_column_index < 63 ? real_column_index : 63U; - colsUsedBitset.set(bit); - column_name = std::get<0>(columns[real_column_index]); - } - colsUsed.insert(column_name); - } - } - } - - pIdxInfo->idxNum = static_cast(kConstraintIndexID++); + } + } + + // Check the table for a required column. + for (const auto& column : columns) { + auto& options = std::get<2>(column); + if ((options & ColumnOptions::REQUIRED) && !required_satisfied) { + // A column is marked required, but no constraint satisfies. + return SQLITE_CONSTRAINT; + } + } + + if (!index_used) { + // A column is marked index, but no index constraint was provided. + cost += 200; + } + + UsedColumns colsUsed; + UsedColumnsBitset colsUsedBitset(pIdxInfo->colUsed); + if (colsUsedBitset.any()) { + for (size_t i = 0; i < columns.size(); i++) { + // Check whether the column is used. colUsed has one bit for each of the + // first 63 columns, and the 64th bit indicates that at least one other + // column is used. + auto bit = i < 63 ? i : 63U; + if (colsUsedBitset[bit]) { + auto column_name = std::get<0>(columns[i]); + + if (pVtab->content->aliases.count(column_name)) { + colsUsedBitset.reset(bit); + auto real_column_index = pVtab->content->aliases[column_name]; + bit = real_column_index < 63 ? real_column_index : 63U; + colsUsedBitset.set(bit); + column_name = std::get<0>(columns[real_column_index]); + } + colsUsed.insert(column_name); + } + } + } + + pIdxInfo->idxNum = static_cast(kConstraintIndexID++); #if defined(DEBUG) - plan("Recording constraint set for table: " + pVtab->content->name + - " [cost=" + std::to_string(cost) + - " size=" + std::to_string(constraints.size()) + - " idx=" + std::to_string(pIdxInfo->idxNum) + "]"); + plan("Recording constraint set for table: " + pVtab->content->name + + " [cost=" + std::to_string(cost) + + " size=" + std::to_string(constraints.size()) + + " idx=" + std::to_string(pIdxInfo->idxNum) + "]"); #endif - // Add the constraint set to the table's tracked constraints. - pVtab->content->constraints[pIdxInfo->idxNum] = std::move(constraints); - pVtab->content->colsUsed[pIdxInfo->idxNum] = std::move(colsUsed); - pVtab->content->colsUsedBitsets[pIdxInfo->idxNum] = colsUsedBitset; - pIdxInfo->estimatedCost = cost; - return SQLITE_OK; + // Add the constraint set to the table's tracked constraints. + pVtab->content->constraints[pIdxInfo->idxNum] = std::move(constraints); + pVtab->content->colsUsed[pIdxInfo->idxNum] = std::move(colsUsed); + pVtab->content->colsUsedBitsets[pIdxInfo->idxNum] = colsUsedBitset; + pIdxInfo->estimatedCost = cost; + return SQLITE_OK; } static int xFilter(sqlite3_vtab_cursor* pVtabCursor, - int idxNum, - const char* idxStr, - int argc, - sqlite3_value** argv) { - BaseCursor* pCur = (BaseCursor*)pVtabCursor; - auto* pVtab = (VirtualTable*)pVtabCursor->pVtab; - auto content = pVtab->content; - pVtab->instance->addAffectedTable(content); - - pCur->row = 0; - pCur->n = 0; - QueryContext context(content); - - // The SQLite instance communicates to the TablePlugin via the context. - context.useCache(pVtab->instance->useCache()); - - // Track required columns, this is different than the requirements check - // that occurs within BestIndex because this scan includes a cursor. - // For each cursor used, if a requirement exists, we need to scan the - // selected set of constraints for a match. - bool required_satisfied = true; - - // The specialized table attribute USER_BASED imposes a special requirement - // for UID. This may be represented in the requirements, but otherwise - // would benefit from specific notification to the caller. - bool user_based_satisfied = !( - (content->attributes & TableAttributes::USER_BASED) > 0); - - // For event-based tables, help the caller if events are disabled. - bool events_satisfied = - ((content->attributes & TableAttributes::EVENT_BASED) == 0); - - std::map options; - for (size_t i = 0; i < content->columns.size(); ++i) { - // Set the column affinity for each optional constraint list. - // There is a separate list for each column name. - auto column_name = std::get<0>(content->columns[i]); - context.constraints[column_name].affinity = - std::get<1>(content->columns[i]); - // Save the column options for comparison within constraints enumeration. - options[column_name] = std::get<2>(content->columns[i]); - if (options[column_name] & ColumnOptions::REQUIRED) { - required_satisfied = false; - } - } - -// Filtering between cursors happens iteratively, not consecutively. -// If there are multiple sets of constraints, they apply to each cursor. + int idxNum, + const char* idxStr, + int argc, + sqlite3_value** argv) +{ + BaseCursor* pCur = (BaseCursor*)pVtabCursor; + auto* pVtab = (VirtualTable*)pVtabCursor->pVtab; + auto content = pVtab->content; + pVtab->instance->addAffectedTable(content); + + pCur->row = 0; + pCur->n = 0; + QueryContext context(content); + + // The SQLite instance communicates to the TablePlugin via the context. + context.useCache(pVtab->instance->useCache()); + + // Track required columns, this is different than the requirements check + // that occurs within BestIndex because this scan includes a cursor. + // For each cursor used, if a requirement exists, we need to scan the + // selected set of constraints for a match. + bool required_satisfied = true; + + // The specialized table attribute USER_BASED imposes a special requirement + // for UID. This may be represented in the requirements, but otherwise + // would benefit from specific notification to the caller. + bool user_based_satisfied = !( + (content->attributes & TableAttributes::USER_BASED) > 0); + + // For event-based tables, help the caller if events are disabled. + bool events_satisfied = + ((content->attributes & TableAttributes::EVENT_BASED) == 0); + + std::map options; + for (size_t i = 0; i < content->columns.size(); ++i) { + // Set the column affinity for each optional constraint list. + // There is a separate list for each column name. + auto column_name = std::get<0>(content->columns[i]); + context.constraints[column_name].affinity = + std::get<1>(content->columns[i]); + // Save the column options for comparison within constraints enumeration. + options[column_name] = std::get<2>(content->columns[i]); + if (options[column_name] & ColumnOptions::REQUIRED) { + required_satisfied = false; + } + } + + // Filtering between cursors happens iteratively, not consecutively. + // If there are multiple sets of constraints, they apply to each cursor. #if defined(DEBUG) - plan("Filtering called for table: " + content->name + - " [constraint_count=" + std::to_string(content->constraints.size()) + - " argc=" + std::to_string(argc) + " idx=" + std::to_string(idxNum) + - "]"); + plan("Filtering called for table: " + content->name + + " [constraint_count=" + std::to_string(content->constraints.size()) + + " argc=" + std::to_string(argc) + " idx=" + std::to_string(idxNum) + + "]"); #endif - // Iterate over every argument to xFilter, filling in constraint values. - if (content->constraints.size() > 0) { - auto& constraints = content->constraints[idxNum]; - if (argc > 0) { - for (size_t i = 0; i < static_cast(argc); ++i) { - auto expr = (const char*)sqlite3_value_text(argv[i]); - if (expr == nullptr || expr[0] == 0) { - // SQLite did not expose the expression value. - continue; - } - // Set the expression from SQLite's now-populated argv. - auto& constraint = constraints[i]; - constraint.second.expr = std::string(expr); - plan("Adding constraint to cursor (" + std::to_string(pCur->id) + - "): " + constraint.first + " " + opString(constraint.second.op) + - " " + constraint.second.expr); - // Add the constraint to the column-sorted query request map. - context.constraints[constraint.first].add(constraint.second); - } - } else if (constraints.size() > 0) { - // Constraints failed. - } - - // Evaluate index and optimized constraint requirements. - // These are satisfied regardless of expression content availability. - for (const auto& constraint : constraints) { - if (options[constraint.first] & ColumnOptions::REQUIRED) { - // A required option exists in the constraints. - required_satisfied = true; - } - - if (!user_based_satisfied && - (constraint.first == "uid" || constraint.first == "username")) { - // UID was required and exists in the constraints. - user_based_satisfied = true; - } - } - } - - if (!content->colsUsedBitsets.empty()) { - context.colsUsedBitset = content->colsUsedBitsets[idxNum]; - } else { - // Unspecified; have to assume all columns are used - context.colsUsedBitset->set(); - } - if (content->colsUsed.size() > 0) { - context.colsUsed = content->colsUsed[idxNum]; - } - - if (!user_based_satisfied) { - WARN(OSQUERY) << "The " << pVtab->content->name - << " table returns data based on the current user by default, " - "consider JOINing against the users table"; - } else if (!required_satisfied) { - WARN(OSQUERY) - << "Table " << pVtab->content->name - << " was queried without a required column in the WHERE clause"; - } else if (!events_satisfied) { - WARN(OSQUERY) << "Table " << pVtab->content->name - << " is event-based but events are disabled"; - } - - // Reset the virtual table contents. - pCur->rows.clear(); - options.clear(); - - // Generate the row data set. - plan("Scanning rows for cursor (" + std::to_string(pCur->id) + ")"); - if (Registry::get().exists("table", pVtab->content->name, true)) { - auto plugin = Registry::get().plugin("table", pVtab->content->name); - auto table = std::dynamic_pointer_cast(plugin); - pCur->rows = table->generate(context); - } else { - PluginRequest request = {{"action", "generate"}}; - TablePlugin::setRequestFromContext(context, request); - QueryData qd; - Registry::call("table", pVtab->content->name, request, qd); - pCur->rows = tableRowsFromQueryData(std::move(qd)); - } - - // Set the number of rows. - pCur->n = pCur->rows.size(); - return SQLITE_OK; + // Iterate over every argument to xFilter, filling in constraint values. + if (content->constraints.size() > 0) { + auto& constraints = content->constraints[idxNum]; + if (argc > 0) { + for (size_t i = 0; i < static_cast(argc); ++i) { + auto expr = (const char*)sqlite3_value_text(argv[i]); + if (expr == nullptr || expr[0] == 0) { + // SQLite did not expose the expression value. + continue; + } + // Set the expression from SQLite's now-populated argv. + auto& constraint = constraints[i]; + constraint.second.expr = std::string(expr); + plan("Adding constraint to cursor (" + std::to_string(pCur->id) + + "): " + constraint.first + " " + opString(constraint.second.op) + + " " + constraint.second.expr); + // Add the constraint to the column-sorted query request map. + context.constraints[constraint.first].add(constraint.second); + } + } else if (constraints.size() > 0) { + // Constraints failed. + } + + // Evaluate index and optimized constraint requirements. + // These are satisfied regardless of expression content availability. + for (const auto& constraint : constraints) { + if (options[constraint.first] & ColumnOptions::REQUIRED) { + // A required option exists in the constraints. + required_satisfied = true; + } + + if (!user_based_satisfied && + (constraint.first == "uid" || constraint.first == "username")) { + // UID was required and exists in the constraints. + user_based_satisfied = true; + } + } + } + + if (!content->colsUsedBitsets.empty()) { + context.colsUsedBitset = content->colsUsedBitsets[idxNum]; + } else { + // Unspecified; have to assume all columns are used + context.colsUsedBitset->set(); + } + if (content->colsUsed.size() > 0) { + context.colsUsed = content->colsUsed[idxNum]; + } + + if (!user_based_satisfied) { + WARN(OSQUERY) << "The " << pVtab->content->name + << " table returns data based on the current user by default, " + "consider JOINing against the users table"; + } else if (!required_satisfied) { + WARN(OSQUERY) + << "Table " << pVtab->content->name + << " was queried without a required column in the WHERE clause"; + } else if (!events_satisfied) { + WARN(OSQUERY) << "Table " << pVtab->content->name + << " is event-based but events are disabled"; + } + + // Reset the virtual table contents. + pCur->rows.clear(); + options.clear(); + + // Generate the row data set. + plan("Scanning rows for cursor (" + std::to_string(pCur->id) + ")"); + if (Registry::get().exists("table", pVtab->content->name, true)) { + auto plugin = Registry::get().plugin("table", pVtab->content->name); + auto table = std::dynamic_pointer_cast(plugin); + pCur->rows = table->generate(context); + } else { + PluginRequest request = {{"action", "generate"}}; + TablePlugin::setRequestFromContext(context, request); + QueryData qd; + Registry::call("table", pVtab->content->name, request, qd); + pCur->rows = tableRowsFromQueryData(std::move(qd)); + } + + // Set the number of rows. + pCur->n = pCur->rows.size(); + return SQLITE_OK; } struct sqlite3_module* getVirtualTableModule(const std::string& table_name, - bool extension) { -// FIXME -// UpgradeLock lock(sqlite_module_map_mutex); - - if (sqlite_module_map.find(table_name) != sqlite_module_map.end()) { - return &sqlite_module_map[table_name]; - } - -// WriteUpgradeLock wlock(lock); - - sqlite_module_map[table_name] = {}; - sqlite_module_map[table_name].xCreate = tables::sqlite::xCreate; - sqlite_module_map[table_name].xConnect = tables::sqlite::xCreate; - sqlite_module_map[table_name].xBestIndex = tables::sqlite::xBestIndex; - sqlite_module_map[table_name].xDisconnect = tables::sqlite::xDestroy; - sqlite_module_map[table_name].xDestroy = tables::sqlite::xDestroy; - sqlite_module_map[table_name].xOpen = tables::sqlite::xOpen; - sqlite_module_map[table_name].xClose = tables::sqlite::xClose; - sqlite_module_map[table_name].xFilter = tables::sqlite::xFilter; - sqlite_module_map[table_name].xNext = tables::sqlite::xNext; - sqlite_module_map[table_name].xEof = tables::sqlite::xEof; - sqlite_module_map[table_name].xColumn = tables::sqlite::xColumn; - sqlite_module_map[table_name].xRowid = tables::sqlite::xRowid; - sqlite_module_map[table_name].xUpdate = tables::sqlite::xUpdate; - - // Allow the table to receive INSERT/UPDATE/DROP events if it is - // implemented from an extension and is overwriting the right methods - // in the TablePlugin class - - return &sqlite_module_map[table_name]; + bool extension) +{ + // FIXME + // UpgradeLock lock(sqlite_module_map_mutex); + + if (sqlite_module_map.find(table_name) != sqlite_module_map.end()) { + return &sqlite_module_map[table_name]; + } + + // WriteUpgradeLock wlock(lock); + + sqlite_module_map[table_name] = {}; + sqlite_module_map[table_name].xCreate = tables::sqlite::xCreate; + sqlite_module_map[table_name].xConnect = tables::sqlite::xCreate; + sqlite_module_map[table_name].xBestIndex = tables::sqlite::xBestIndex; + sqlite_module_map[table_name].xDisconnect = tables::sqlite::xDestroy; + sqlite_module_map[table_name].xDestroy = tables::sqlite::xDestroy; + sqlite_module_map[table_name].xOpen = tables::sqlite::xOpen; + sqlite_module_map[table_name].xClose = tables::sqlite::xClose; + sqlite_module_map[table_name].xFilter = tables::sqlite::xFilter; + sqlite_module_map[table_name].xNext = tables::sqlite::xNext; + sqlite_module_map[table_name].xEof = tables::sqlite::xEof; + sqlite_module_map[table_name].xColumn = tables::sqlite::xColumn; + sqlite_module_map[table_name].xRowid = tables::sqlite::xRowid; + sqlite_module_map[table_name].xUpdate = tables::sqlite::xUpdate; + + // Allow the table to receive INSERT/UPDATE/DROP events if it is + // implemented from an extension and is overwriting the right methods + // in the TablePlugin class + + return &sqlite_module_map[table_name]; } } // namespace sqlite } // namespace tables Status attachTableInternal(const std::string& name, - const std::string& statement, - const SQLiteDBInstanceRef& instance, - bool is_extension) { - if (SQLiteDBManager::isDisabled(name)) { - DEBUG(OSQUERY) << "Table " << name << " is disabled, not attaching"; - return Status(0, getStringForSQLiteReturnCode(0)); - } - - struct sqlite3_module* module = - tables::sqlite::getVirtualTableModule(name, is_extension); - if (module == nullptr) { - DEBUG(OSQUERY) << "Failed to retrieve the virtual table module for \"" << name - << "\""; - return Status(1); - } - - // Note, if the clientData API is used then this will save a registry call - // within xCreate. - auto lock(instance->attachLock()); - - int rc = sqlite3_create_module( - instance->db(), name.c_str(), module, (void*)&(*instance)); - - if (rc == SQLITE_OK || rc == SQLITE_MISUSE) { - auto format = - "CREATE VIRTUAL TABLE temp." + name + " USING " + name + statement; - - rc = - sqlite3_exec(instance->db(), format.c_str(), nullptr, nullptr, nullptr); - - } else { - ERROR(OSQUERY) << "Error attaching table: " << name << " (" << rc << ")"; - } - - return Status(rc, getStringForSQLiteReturnCode(rc)); + const std::string& statement, + const SQLiteDBInstanceRef& instance, + bool is_extension) +{ + if (SQLiteDBManager::isDisabled(name)) { + DEBUG(OSQUERY) << "Table " << name << " is disabled, not attaching"; + return Status(0, getStringForSQLiteReturnCode(0)); + } + + struct sqlite3_module* module = + tables::sqlite::getVirtualTableModule(name, is_extension); + if (module == nullptr) { + DEBUG(OSQUERY) << "Failed to retrieve the virtual table module for \"" << name + << "\""; + return Status(1); + } + + // Note, if the clientData API is used then this will save a registry call + // within xCreate. + auto lock(instance->attachLock()); + + int rc = sqlite3_create_module( + instance->db(), name.c_str(), module, (void*) & (*instance)); + + if (rc == SQLITE_OK || rc == SQLITE_MISUSE) { + auto format = + "CREATE VIRTUAL TABLE temp." + name + " USING " + name + statement; + + rc = + sqlite3_exec(instance->db(), format.c_str(), nullptr, nullptr, nullptr); + + } else { + ERROR(OSQUERY) << "Error attaching table: " << name << " (" << rc << ")"; + } + + return Status(rc, getStringForSQLiteReturnCode(rc)); } Status detachTableInternal(const std::string& name, - const SQLiteDBInstanceRef& instance) { - auto lock(instance->attachLock()); - auto format = "DROP TABLE IF EXISTS temp." + name; - int rc = sqlite3_exec(instance->db(), format.c_str(), nullptr, nullptr, 0); - if (rc != SQLITE_OK) { - ERROR(OSQUERY) << "Error detaching table: " << name << " (" << rc << ")"; - } - - return Status(rc, getStringForSQLiteReturnCode(rc)); + const SQLiteDBInstanceRef& instance) +{ + auto lock(instance->attachLock()); + auto format = "DROP TABLE IF EXISTS temp." + name; + int rc = sqlite3_exec(instance->db(), format.c_str(), nullptr, nullptr, 0); + if (rc != SQLITE_OK) { + ERROR(OSQUERY) << "Error detaching table: " << name << " (" << rc << ")"; + } + + return Status(rc, getStringForSQLiteReturnCode(rc)); } Status attachFunctionInternal( - const std::string& name, - std::function< - void(sqlite3_context* context, int argc, sqlite3_value** argv)> func) { - // Hold the manager connection instance again in callbacks. - auto dbc = SQLiteDBManager::get(); - // Add some shell-specific functions to the instance. - auto lock(dbc->attachLock()); - int rc = sqlite3_create_function( - dbc->db(), - name.c_str(), - 0, - SQLITE_UTF8, - nullptr, - *func.target(), - nullptr, - nullptr); - return Status(rc); + const std::string& name, + std::function < + void(sqlite3_context* context, int argc, sqlite3_value** argv) > func) +{ + // Hold the manager connection instance again in callbacks. + auto dbc = SQLiteDBManager::get(); + // Add some shell-specific functions to the instance. + auto lock(dbc->attachLock()); + int rc = sqlite3_create_function( + dbc->db(), + name.c_str(), + 0, + SQLITE_UTF8, + nullptr, + *func.target(), + nullptr, + nullptr); + return Status(rc); } -void attachVirtualTables(const SQLiteDBInstanceRef& instance) { - PluginResponse response; - bool is_extension = false; - - for (const auto& name : RegistryFactory::get().names("table")) { - // Column information is nice for virtual table create call. - auto status = - Registry::call("table", name, {{"action", "columns"}}, response); - if (status.ok()) { - auto statement = columnDefinition(response, true, is_extension); - attachTableInternal(name, statement, instance, is_extension); - } - } +void attachVirtualTables(const SQLiteDBInstanceRef& instance) +{ + PluginResponse response; + bool is_extension = false; + + for (const auto& name : RegistryFactory::get().names("table")) { + // Column information is nice for virtual table create call. + auto status = + Registry::call("table", name, {{"action", "columns"}}, response); + if (status.ok()) { + auto statement = columnDefinition(response, true, is_extension); + attachTableInternal(name, statement, instance, is_extension); + } + } } } // namespace osquery diff --git a/src/osquery/sql/virtual_table.h b/src/osquery/sql/virtual_table.h index b9636c8..af09402 100644 --- a/src/osquery/sql/virtual_table.h +++ b/src/osquery/sql/virtual_table.h @@ -33,24 +33,24 @@ extern RecursiveMutex kAttachMutex; * Only used in the SQLite virtual table module methods. */ struct BaseCursor : private boost::noncopyable { - public: - /// SQLite virtual table cursor. - sqlite3_vtab_cursor base; +public: + /// SQLite virtual table cursor. + sqlite3_vtab_cursor base; - /// Track cursors for optional planner output. - size_t id{0}; + /// Track cursors for optional planner output. + size_t id{0}; - /// Table data generated from last access. - TableRows rows; + /// Table data generated from last access. + TableRows rows; - /// Results of current call. - TableRowHolder current; + /// Results of current call. + TableRowHolder current; - /// Current cursor position. - size_t row{0}; + /// Current cursor position. + size_t row{0}; - /// Total number of rows. - size_t n{0}; + /// Total number of rows. + size_t n{0}; }; /** @@ -60,30 +60,30 @@ struct BaseCursor : private boost::noncopyable { * This adds each table plugin class to the state tracking in SQLite. */ struct VirtualTable : private boost::noncopyable { - /// The SQLite-provided virtual table structure. - sqlite3_vtab base; + /// The SQLite-provided virtual table structure. + sqlite3_vtab base; - /// Added structure: A content structure with metadata about the table. - std::shared_ptr content; + /// Added structure: A content structure with metadata about the table. + std::shared_ptr content; - /// Added structure: The thread-local DB instance associated with the query. - SQLiteDBInstance* instance{nullptr}; + /// Added structure: The thread-local DB instance associated with the query. + SQLiteDBInstance* instance{nullptr}; }; /// Attach a table plugin name to an in-memory SQLite database. Status attachTableInternal(const std::string& name, - const std::string& statement, - const SQLiteDBInstanceRef& instance, - bool is_extension); + const std::string& statement, + const SQLiteDBInstanceRef& instance, + bool is_extension); /// Detach (drop) a table. Status detachTableInternal(const std::string& name, - const SQLiteDBInstanceRef& instance); + const SQLiteDBInstanceRef& instance); Status attachFunctionInternal( - const std::string& name, - std::function< - void(sqlite3_context* context, int argc, sqlite3_value** argv)> func); + const std::string& name, + std::function < + void(sqlite3_context* context, int argc, sqlite3_value** argv) > func); /// Attach all table plugins to an in-memory SQLite database. void attachVirtualTables(const SQLiteDBInstanceRef& instance); diff --git a/src/osquery/utils/base64.cpp b/src/osquery/utils/base64.cpp index ac2cde2..b5e3154 100644 --- a/src/osquery/utils/base64.cpp +++ b/src/osquery/utils/base64.cpp @@ -30,39 +30,41 @@ typedef bai::base64_from_binary it_base64; } // namespace -std::string decode(std::string encoded) { - boost::erase_all(encoded, "\r\n"); - boost::erase_all(encoded, "\n"); - boost::trim_right_if(encoded, boost::is_any_of("=")); +std::string decode(std::string encoded) +{ + boost::erase_all(encoded, "\r\n"); + boost::erase_all(encoded, "\n"); + boost::trim_right_if(encoded, boost::is_any_of("=")); - if (encoded.empty()) { - return encoded; - } + if (encoded.empty()) { + return encoded; + } - try { - return std::string(base64_dec(encoded.data()), - base64_dec(encoded.data() + encoded.size())); - } catch (const boost::archive::iterators::dataflow_exception& e) { - INFO(OSQUERY) << "Could not base64 decode string: " << e.what(); - return ""; - } + try { + return std::string(base64_dec(encoded.data()), + base64_dec(encoded.data() + encoded.size())); + } catch (const boost::archive::iterators::dataflow_exception& e) { + INFO(OSQUERY) << "Could not base64 decode string: " << e.what(); + return ""; + } } -std::string encode(const std::string& unencoded) { - if (unencoded.empty()) { - return unencoded; - } +std::string encode(const std::string& unencoded) +{ + if (unencoded.empty()) { + return unencoded; + } - size_t writePaddChars = (3U - unencoded.length() % 3U) % 3U; - try { - auto encoded = - std::string(it_base64(unencoded.begin()), it_base64(unencoded.end())); - encoded.append(std::string(writePaddChars, '=')); - return encoded; - } catch (const boost::archive::iterators::dataflow_exception& e) { - INFO(OSQUERY) << "Could not base64 decode string: " << e.what(); - return ""; - } + size_t writePaddChars = (3U - unencoded.length() % 3U) % 3U; + try { + auto encoded = + std::string(it_base64(unencoded.begin()), it_base64(unencoded.end())); + encoded.append(std::string(writePaddChars, '=')); + return encoded; + } catch (const boost::archive::iterators::dataflow_exception& e) { + INFO(OSQUERY) << "Could not base64 decode string: " << e.what(); + return ""; + } } } // namespace base64 diff --git a/src/osquery/utils/chars.cpp b/src/osquery/utils/chars.cpp index be53fe9..85d2fda 100644 --- a/src/osquery/utils/chars.cpp +++ b/src/osquery/utils/chars.cpp @@ -16,58 +16,61 @@ namespace osquery { -bool isPrintable(const std::string& check) { - for (const unsigned char ch : check) { - if (ch >= 0x7F || ch <= 0x1F) { - return false; - } - } - return true; +bool isPrintable(const std::string& check) +{ + for (const unsigned char ch : check) { + if (ch >= 0x7F || ch <= 0x1F) { + return false; + } + } + return true; } -size_t utf8StringSize(const std::string& str) { - size_t res = 0; - std::string::const_iterator it = str.begin(); - for (; it != str.end(); incUtf8StringIterator(it, str.end())) { - res++; - } +size_t utf8StringSize(const std::string& str) +{ + size_t res = 0; + std::string::const_iterator it = str.begin(); + for (; it != str.end(); incUtf8StringIterator(it, str.end())) { + res++; + } - return res; + return res; } -std::string unescapeUnicode(const std::string& escaped) { - if (escaped.size() < 6) { - return escaped; - } +std::string unescapeUnicode(const std::string& escaped) +{ + if (escaped.size() < 6) { + return escaped; + } - std::string unescaped; - unescaped.reserve(escaped.size()); - for (size_t i = 0; i < escaped.size(); ++i) { - if (i < escaped.size() - 5 && '\\' == escaped[i] && 'u' == escaped[i + 1]) { - // Assume 2-byte wide unicode. - auto const exp = tryTo(escaped.substr(i + 2, 4), 16); - if (exp.isError()) { - WARN(OSQUERY) << "Unescaping a string with length: " << escaped.size() - << " failed at: " << i; - return ""; - } - long const value = exp.get(); - if (value < 255) { - unescaped += static_cast(value); - i += 5; - continue; - } - } else if (i < escaped.size() - 1 && '\\' == escaped[i] && - '\\' == escaped[i + 1]) { - // In the case of \\users 'sers' is not a unicode character - // If we see \\ we should skip them and we do this by adding - // an extra jump forward. - unescaped += escaped[i]; - ++i; - } - unescaped += escaped[i]; - } - return unescaped; + std::string unescaped; + unescaped.reserve(escaped.size()); + for (size_t i = 0; i < escaped.size(); ++i) { + if (i < escaped.size() - 5 && '\\' == escaped[i] && 'u' == escaped[i + 1]) { + // Assume 2-byte wide unicode. + auto const exp = tryTo(escaped.substr(i + 2, 4), 16); + if (exp.isError()) { + WARN(OSQUERY) << "Unescaping a string with length: " << escaped.size() + << " failed at: " << i; + return ""; + } + long const value = exp.get(); + if (value < 255) { + unescaped += static_cast(value); + i += 5; + continue; + } + } else if (i < escaped.size() - 1 && '\\' == escaped[i] && + '\\' == escaped[i + 1]) { + // In the case of \\users 'sers' is not a unicode character + // If we see \\ we should skip them and we do this by adding + // an extra jump forward. + unescaped += escaped[i]; + ++i; + } + unescaped += escaped[i]; + } + return unescaped; } } // namespace osquery diff --git a/src/osquery/utils/chars.h b/src/osquery/utils/chars.h index af9437e..0df4433 100644 --- a/src/osquery/utils/chars.h +++ b/src/osquery/utils/chars.h @@ -24,20 +24,21 @@ bool isPrintable(const std::string& check); * @brief In-line helper function for use with utf8StringSize */ template -size_t incUtf8StringIterator(_Iterator1& it, const _Iterator2& last) { - if (it == last) { - return 0; - } - - size_t res = 1; - for (++it; last != it; ++it, ++res) { - unsigned char c = *it; - if (!(c & 0x80) || ((c & 0xC0) == 0xC0)) { - break; - } - } - - return res; +size_t incUtf8StringIterator(_Iterator1& it, const _Iterator2& last) +{ + if (it == last) { + return 0; + } + + size_t res = 1; + for (++it; last != it; ++it, ++res) { + unsigned char c = *it; + if (!(c & 0x80) || ((c & 0xC0) == 0xC0)) { + break; + } + } + + return res; } /** diff --git a/src/osquery/utils/conversions/castvariant.h b/src/osquery/utils/conversions/castvariant.h index 9960dbe..2f660f7 100644 --- a/src/osquery/utils/conversions/castvariant.h +++ b/src/osquery/utils/conversions/castvariant.h @@ -16,27 +16,31 @@ namespace osquery { /* We do this so that we get '0.0' from double 0.0 instead of '0' */ class CastVisitor : public boost::static_visitor { - public: - std::string operator()(const long long& i) const { - return std::to_string(i); - } +public: + std::string operator()(const long long& i) const + { + return std::to_string(i); + } - std::string operator()(const double& d) const { - std::string s{boost::lexical_cast(d)}; - if (s.find('.') == std::string::npos) { - s += ".0"; - } - return s; - } + std::string operator()(const double& d) const + { + std::string s{boost::lexical_cast(d)}; + if (s.find('.') == std::string::npos) { + s += ".0"; + } + return s; + } - std::string operator()(const std::string& str) const { - return str; - } + std::string operator()(const std::string& str) const + { + return str; + } }; inline std::string castVariant( - const boost::variant& var) { - static const CastVisitor visitor; - return boost::apply_visitor(visitor, var); + const boost::variant& var) +{ + static const CastVisitor visitor; + return boost::apply_visitor(visitor, var); } } // namespace osquery diff --git a/src/osquery/utils/conversions/join.h b/src/osquery/utils/conversions/join.h index 57a6b3f..7e73613 100644 --- a/src/osquery/utils/conversions/join.h +++ b/src/osquery/utils/conversions/join.h @@ -23,8 +23,9 @@ namespace osquery { * @return the joined string. */ template -inline std::string join(const SequenceType& s, const std::string& tok) { - return boost::algorithm::join(s, tok); +inline std::string join(const SequenceType& s, const std::string& tok) +{ + return boost::algorithm::join(s, tok); } } // namespace osquery diff --git a/src/osquery/utils/conversions/split.cpp b/src/osquery/utils/conversions/split.cpp index c5a0152..153c807 100644 --- a/src/osquery/utils/conversions/split.cpp +++ b/src/osquery/utils/conversions/split.cpp @@ -12,41 +12,43 @@ namespace osquery { -std::vector split(const std::string& s, const std::string& delim) { - std::vector elems; - boost::split(elems, s, boost::is_any_of(delim)); - auto start = - std::remove_if(elems.begin(), elems.end(), [](const std::string& t) { - return t.size() == 0; - }); - elems.erase(start, elems.end()); - for (auto& each : elems) { - boost::algorithm::trim(each); - } - return elems; +std::vector split(const std::string& s, const std::string& delim) +{ + std::vector elems; + boost::split(elems, s, boost::is_any_of(delim)); + auto start = + std::remove_if(elems.begin(), elems.end(), [](const std::string & t) { + return t.size() == 0; + }); + elems.erase(start, elems.end()); + for (auto& each : elems) { + boost::algorithm::trim(each); + } + return elems; } std::vector split(const std::string& s, - char delim, - size_t occurrences) { - auto delims = std::string(1, delim); - // Split the string normally with the required delimiter. - auto content = split(s, delims); - // While the result split exceeds the number of requested occurrences, join. - std::vector accumulator; - std::vector elems; - for (size_t i = 0; i < content.size(); i++) { - if (i < occurrences) { - elems.push_back(content.at(i)); - } else { - accumulator.push_back(content.at(i)); - } - } - // Join the optional accumulator. - if (accumulator.size() > 0) { - elems.push_back(boost::algorithm::join(accumulator, delims)); - } - return elems; + char delim, + size_t occurrences) +{ + auto delims = std::string(1, delim); + // Split the string normally with the required delimiter. + auto content = split(s, delims); + // While the result split exceeds the number of requested occurrences, join. + std::vector accumulator; + std::vector elems; + for (size_t i = 0; i < content.size(); i++) { + if (i < occurrences) { + elems.push_back(content.at(i)); + } else { + accumulator.push_back(content.at(i)); + } + } + // Join the optional accumulator. + if (accumulator.size() > 0) { + elems.push_back(boost::algorithm::join(accumulator, delims)); + } + return elems; } } diff --git a/src/osquery/utils/conversions/split.h b/src/osquery/utils/conversions/split.h index eaec899..3d15e0e 100644 --- a/src/osquery/utils/conversions/split.h +++ b/src/osquery/utils/conversions/split.h @@ -24,7 +24,7 @@ namespace osquery { * @return a vector of strings split by delim. */ std::vector split(const std::string& s, - const std::string& delim = "\t "); + const std::string& delim = "\t "); /** * @brief Split a given string based on an delimiter. @@ -36,7 +36,7 @@ std::vector split(const std::string& s, * @return a vector of strings split by delim for occurrences. */ std::vector split(const std::string& s, - char delim, - size_t occurrences); + char delim, + size_t occurrences); } diff --git a/src/osquery/utils/conversions/tests/join.cpp b/src/osquery/utils/conversions/tests/join.cpp index 8000927..0cb5f3d 100644 --- a/src/osquery/utils/conversions/tests/join.cpp +++ b/src/osquery/utils/conversions/tests/join.cpp @@ -12,15 +12,16 @@ #include -namespace osquery{ +namespace osquery { class ConversionsTests : public testing::Test {}; -TEST_F(ConversionsTests, test_join) { - std::vector content = { - "one", "two", "three", - }; - EXPECT_EQ(join(content, ", "), "one, two, three"); +TEST_F(ConversionsTests, test_join) +{ + std::vector content = { + "one", "two", "three", + }; + EXPECT_EQ(join(content, ", "), "one, two, three"); } } diff --git a/src/osquery/utils/conversions/tests/split.cpp b/src/osquery/utils/conversions/tests/split.cpp index e1e9e6d..a342cdd 100644 --- a/src/osquery/utils/conversions/tests/split.cpp +++ b/src/osquery/utils/conversions/tests/split.cpp @@ -17,39 +17,42 @@ namespace osquery { class ConversionsTests : public testing::Test {}; struct SplitStringTestData { - std::string test_string; - std::string delim; - std::vector test_vector; + std::string test_string; + std::string delim; + std::vector test_vector; }; -std::vector generateSplitStringTestData() { - SplitStringTestData s1; - s1.test_string = "a b\tc"; - s1.test_vector = {"a", "b", "c"}; +std::vector generateSplitStringTestData() +{ + SplitStringTestData s1; + s1.test_string = "a b\tc"; + s1.test_vector = {"a", "b", "c"}; - SplitStringTestData s2; - s2.test_string = " a b c"; - s2.test_vector = {"a", "b", "c"}; + SplitStringTestData s2; + s2.test_string = " a b c"; + s2.test_vector = {"a", "b", "c"}; - SplitStringTestData s3; - s3.test_string = " a b c"; - s3.test_vector = {"a", "b", "c"}; + SplitStringTestData s3; + s3.test_string = " a b c"; + s3.test_vector = {"a", "b", "c"}; - return {s1, s2, s3}; + return {s1, s2, s3}; } -TEST_F(ConversionsTests, test_split) { - for (const auto& i : generateSplitStringTestData()) { - EXPECT_EQ(split(i.test_string), i.test_vector); - } +TEST_F(ConversionsTests, test_split) +{ + for (const auto& i : generateSplitStringTestData()) { + EXPECT_EQ(split(i.test_string), i.test_vector); + } } -TEST_F(ConversionsTests, test_split_occurrences) { - std::string content = "T: 'S:S'"; - std::vector expected = { - "T", "'S:S'", - }; - EXPECT_EQ(split(content, ':', 1), expected); +TEST_F(ConversionsTests, test_split_occurrences) +{ + std::string content = "T: 'S:S'"; + std::vector expected = { + "T", "'S:S'", + }; + EXPECT_EQ(split(content, ':', 1), expected); } } // namespace osquery diff --git a/src/osquery/utils/conversions/tests/to.cpp b/src/osquery/utils/conversions/tests/to.cpp index 064831a..598ce96 100644 --- a/src/osquery/utils/conversions/tests/to.cpp +++ b/src/osquery/utils/conversions/tests/to.cpp @@ -18,38 +18,40 @@ namespace osquery { class NonFailingConversionsTests : public testing::Test {}; enum class TestGreenColor { - Green, - Pine, - Fern, - Olive, + Green, + Pine, + Fern, + Olive, }; -TEST_F(NonFailingConversionsTests, to_string_from_enum_class) { - EXPECT_NE(std::string::npos, - to(TestGreenColor::Green).find("TestGreenColor[0]")); - EXPECT_NE(std::string::npos, - to(TestGreenColor::Pine).find("TestGreenColor[1]")); - EXPECT_NE(std::string::npos, - to(TestGreenColor::Fern).find("TestGreenColor[2]")); +TEST_F(NonFailingConversionsTests, to_string_from_enum_class) +{ + EXPECT_NE(std::string::npos, + to(TestGreenColor::Green).find("TestGreenColor[0]")); + EXPECT_NE(std::string::npos, + to(TestGreenColor::Pine).find("TestGreenColor[1]")); + EXPECT_NE(std::string::npos, + to(TestGreenColor::Fern).find("TestGreenColor[2]")); } enum class TestOrangeColor { - Orange, - Fire, - Clay, - Cider, + Orange, + Fire, + Clay, + Cider, }; -TEST_F(NonFailingConversionsTests, to_string_from_old_enum) { - EXPECT_NE( - std::string::npos, - to(TestOrangeColor::Orange).find("TestOrangeColor[0]")); - EXPECT_NE(std::string::npos, - to(TestOrangeColor::Fire).find("TestOrangeColor[1]")); - EXPECT_NE(std::string::npos, - to(TestOrangeColor::Clay).find("TestOrangeColor[2]")); - EXPECT_NE(std::string::npos, - to(TestOrangeColor::Cider).find("TestOrangeColor[3]")); +TEST_F(NonFailingConversionsTests, to_string_from_old_enum) +{ + EXPECT_NE( + std::string::npos, + to(TestOrangeColor::Orange).find("TestOrangeColor[0]")); + EXPECT_NE(std::string::npos, + to(TestOrangeColor::Fire).find("TestOrangeColor[1]")); + EXPECT_NE(std::string::npos, + to(TestOrangeColor::Clay).find("TestOrangeColor[2]")); + EXPECT_NE(std::string::npos, + to(TestOrangeColor::Cider).find("TestOrangeColor[3]")); } } // namespace osquery diff --git a/src/osquery/utils/conversions/tests/tryto.cpp b/src/osquery/utils/conversions/tests/tryto.cpp index 6b2dce0..bb867da 100644 --- a/src/osquery/utils/conversions/tests/tryto.cpp +++ b/src/osquery/utils/conversions/tests/tryto.cpp @@ -18,312 +18,335 @@ namespace osquery { class ConversionsTests : public testing::Test {}; -TEST_F(ConversionsTests, tryTo_same_type) { - class First {}; - // rvalue - auto ret0 = tryTo(First{}); - ASSERT_FALSE(ret0.isError()); - - auto test_lvalue = First{}; - auto ret1 = tryTo(test_lvalue); - ASSERT_FALSE(ret1.isError()); - - const auto const_test_lvalue = First{}; - auto ret2 = tryTo(const_test_lvalue); - ASSERT_FALSE(ret2.isError()); +TEST_F(ConversionsTests, tryTo_same_type) +{ + class First {}; + // rvalue + auto ret0 = tryTo(First{}); + ASSERT_FALSE(ret0.isError()); + + auto test_lvalue = First{}; + auto ret1 = tryTo(test_lvalue); + ASSERT_FALSE(ret1.isError()); + + const auto const_test_lvalue = First{}; + auto ret2 = tryTo(const_test_lvalue); + ASSERT_FALSE(ret2.isError()); } template -void testTryToForRvalue(ValueType value, const StrType& str) { - auto ret = tryTo(StrType{str}); - ASSERT_FALSE(ret.isError()); - ASSERT_EQ(ret.get(), value); +void testTryToForRvalue(ValueType value, const StrType& str) +{ + auto ret = tryTo(StrType{str}); + ASSERT_FALSE(ret.isError()); + ASSERT_EQ(ret.get(), value); } template -void testTryToForLValue(ValueType value, StrType str) { - auto ret = tryTo(str); - ASSERT_FALSE(ret.isError()); - ASSERT_EQ(ret.get(), value); +void testTryToForLValue(ValueType value, StrType str) +{ + auto ret = tryTo(str); + ASSERT_FALSE(ret.isError()); + ASSERT_EQ(ret.get(), value); } template -void testTryToForConstLValue(ValueType value, const StrType str) { - auto ret = tryTo(str); - ASSERT_FALSE(ret.isError()); - ASSERT_EQ(ret.get(), value); +void testTryToForConstLValue(ValueType value, const StrType str) +{ + auto ret = tryTo(str); + ASSERT_FALSE(ret.isError()); + ASSERT_EQ(ret.get(), value); } template -void testTryToForString(ValueType value, const StrType str) { - testTryToForRvalue(value, str); - testTryToForLValue(value, str); - testTryToForConstLValue(value, str); +void testTryToForString(ValueType value, const StrType str) +{ + testTryToForRvalue(value, str); + testTryToForLValue(value, str); + testTryToForConstLValue(value, str); } template -void testTryToForValue(ValueType value) { - testTryToForString(value, std::to_string(value)); - testTryToForString(value, std::to_wstring(value)); +void testTryToForValue(ValueType value) +{ + testTryToForString(value, std::to_string(value)); + testTryToForString(value, std::to_wstring(value)); } template -void testTryToForUnsignedInt() { - testTryToForValue(119); - testTryToForValue(std::numeric_limits::max()); - testTryToForValue(std::numeric_limits::min()); - testTryToForValue(std::numeric_limits::lowest()); - { - auto ret = tryTo(std::string{"0xfb"}, 16); - ASSERT_FALSE(ret.isError()); - ASSERT_EQ(ret.get(), 251); - } - { - auto ret = tryTo(std::string{"FB"}, 16); - ASSERT_FALSE(ret.isError()); - ASSERT_EQ(ret.get(), 251); - } - { - auto ret = tryTo(std::string{"0xFb"}, 16); - ASSERT_FALSE(ret.isError()); - ASSERT_EQ(ret.get(), 251); - } - { - auto ret = tryTo(std::string{"E1bC2"}, 16); - ASSERT_FALSE(ret.isError()); - ASSERT_EQ(ret.get(), 924610); - } - { - auto ret = tryTo(std::string{"10101"}, 2); - ASSERT_FALSE(ret.isError()); - ASSERT_EQ(ret.get(), 21); - } - { - auto ret = tryTo(std::string{"035"}, 8); - ASSERT_FALSE(ret.isError()); - ASSERT_EQ(ret.get(), 29); - } - { - auto ret = tryTo(std::string{"47"}, 8); - ASSERT_FALSE(ret.isError()); - ASSERT_EQ(ret.get(), 39); - } - { - auto ret = tryTo(std::string{"+15"}); - ASSERT_FALSE(ret.isError()); - ASSERT_EQ(ret.get(), 15); - } - { - auto ret = tryTo(std::string{"+1A"}, 16); - ASSERT_FALSE(ret.isError()); - ASSERT_EQ(ret.get(), 26); - } - // failure tests - { - auto ret = tryTo(std::string{""}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"x"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"xor"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{".1"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"(10)"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"O"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"lO0"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"IV"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"s1"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"u1"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"#12"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"%99"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"*483"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"/488"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"\\493"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"+ 19"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string(2, '\0')); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } +void testTryToForUnsignedInt() +{ + testTryToForValue(119); + testTryToForValue(std::numeric_limits::max()); + testTryToForValue(std::numeric_limits::min()); + testTryToForValue(std::numeric_limits::lowest()); + { + auto ret = tryTo(std::string{"0xfb"}, 16); + ASSERT_FALSE(ret.isError()); + ASSERT_EQ(ret.get(), 251); + } + { + auto ret = tryTo(std::string{"FB"}, 16); + ASSERT_FALSE(ret.isError()); + ASSERT_EQ(ret.get(), 251); + } + { + auto ret = tryTo(std::string{"0xFb"}, 16); + ASSERT_FALSE(ret.isError()); + ASSERT_EQ(ret.get(), 251); + } + { + auto ret = tryTo(std::string{"E1bC2"}, 16); + ASSERT_FALSE(ret.isError()); + ASSERT_EQ(ret.get(), 924610); + } + { + auto ret = tryTo(std::string{"10101"}, 2); + ASSERT_FALSE(ret.isError()); + ASSERT_EQ(ret.get(), 21); + } + { + auto ret = tryTo(std::string{"035"}, 8); + ASSERT_FALSE(ret.isError()); + ASSERT_EQ(ret.get(), 29); + } + { + auto ret = tryTo(std::string{"47"}, 8); + ASSERT_FALSE(ret.isError()); + ASSERT_EQ(ret.get(), 39); + } + { + auto ret = tryTo(std::string{"+15"}); + ASSERT_FALSE(ret.isError()); + ASSERT_EQ(ret.get(), 15); + } + { + auto ret = tryTo(std::string{"+1A"}, 16); + ASSERT_FALSE(ret.isError()); + ASSERT_EQ(ret.get(), 26); + } + // failure tests + { + auto ret = tryTo(std::string{""}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"x"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"xor"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{".1"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"(10)"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"O"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"lO0"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"IV"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"s1"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"u1"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"#12"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"%99"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"*483"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"/488"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"\\493"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"+ 19"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string(2, '\0')); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } } template -void testTryToForSignedInt() { - testTryToForUnsignedInt(); - testTryToForValue(-126); - { - auto ret = tryTo(std::string{"-7A"}, 16); - ASSERT_FALSE(ret.isError()); - ASSERT_EQ(ret.get(), -122); - } - // failure tests - { - auto ret = tryTo(std::string{"--14779"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"+-1813"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } - { - auto ret = tryTo(std::string{"- 3"}); - ASSERT_TRUE(ret.isError()); - ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); - } +void testTryToForSignedInt() +{ + testTryToForUnsignedInt(); + testTryToForValue(-126); + { + auto ret = tryTo(std::string{"-7A"}, 16); + ASSERT_FALSE(ret.isError()); + ASSERT_EQ(ret.get(), -122); + } + // failure tests + { + auto ret = tryTo(std::string{"--14779"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"+-1813"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } + { + auto ret = tryTo(std::string{"- 3"}); + ASSERT_TRUE(ret.isError()); + ASSERT_EQ(ret.getErrorCode(), ConversionError::InvalidArgument); + } } -TEST_F(ConversionsTests, try_i_to_string_and_back) { - testTryToForSignedInt(); +TEST_F(ConversionsTests, try_i_to_string_and_back) +{ + testTryToForSignedInt(); } -TEST_F(ConversionsTests, try_l_to_string_and_back) { - testTryToForSignedInt(); +TEST_F(ConversionsTests, try_l_to_string_and_back) +{ + testTryToForSignedInt(); } -TEST_F(ConversionsTests, try_ll_to_string_and_back) { - testTryToForSignedInt(); +TEST_F(ConversionsTests, try_ll_to_string_and_back) +{ + testTryToForSignedInt(); } -TEST_F(ConversionsTests, try_i32_to_string_and_back) { - testTryToForSignedInt(); +TEST_F(ConversionsTests, try_i32_to_string_and_back) +{ + testTryToForSignedInt(); } -TEST_F(ConversionsTests, try_i64_to_string_and_back) { - testTryToForSignedInt(); +TEST_F(ConversionsTests, try_i64_to_string_and_back) +{ + testTryToForSignedInt(); } -TEST_F(ConversionsTests, try_imax_to_string_and_back) { - testTryToForSignedInt(); +TEST_F(ConversionsTests, try_imax_to_string_and_back) +{ + testTryToForSignedInt(); } -TEST_F(ConversionsTests, try_u_to_string_and_back) { - testTryToForUnsignedInt(); +TEST_F(ConversionsTests, try_u_to_string_and_back) +{ + testTryToForUnsignedInt(); } -TEST_F(ConversionsTests, try_ul_to_string_and_back) { - testTryToForUnsignedInt(); +TEST_F(ConversionsTests, try_ul_to_string_and_back) +{ + testTryToForUnsignedInt(); } -TEST_F(ConversionsTests, try_ull_to_string_and_back) { - testTryToForUnsignedInt(); +TEST_F(ConversionsTests, try_ull_to_string_and_back) +{ + testTryToForUnsignedInt(); } -TEST_F(ConversionsTests, try_u32_to_string_and_back) { - testTryToForUnsignedInt(); +TEST_F(ConversionsTests, try_u32_to_string_and_back) +{ + testTryToForUnsignedInt(); } -TEST_F(ConversionsTests, try_u64_to_string_and_back) { - testTryToForUnsignedInt(); +TEST_F(ConversionsTests, try_u64_to_string_and_back) +{ + testTryToForUnsignedInt(); } -TEST_F(ConversionsTests, try_umax_to_string_and_back) { - testTryToForUnsignedInt(); +TEST_F(ConversionsTests, try_umax_to_string_and_back) +{ + testTryToForUnsignedInt(); } -TEST_F(ConversionsTests, try_size_t_to_string_and_back) { - testTryToForUnsignedInt(); +TEST_F(ConversionsTests, try_size_t_to_string_and_back) +{ + testTryToForUnsignedInt(); } -TEST_F(ConversionsTests, tryTo_string_to_boolean_valid_args) { - const auto test_table = std::unordered_map{ - {"1", true}, {"0", false}, {"y", true}, - {"n", false}, {"yes", true}, {"yEs", true}, - {"Yes", true}, {"no", false}, {"No", false}, - {"t", true}, {"T", true}, {"f", false}, - {"F", false}, {"true", true}, {"True", true}, - {"tRUE", true}, {"false", false}, {"fALse", false}, - {"ok", true}, {"OK", true}, {"Ok", true}, - {"enable", true}, {"Enable", true}, {"ENABLE", true}, - {"disable", false}, {"Disable", false}, {"DISABLE", false}, - }; - for (const auto& argAndAnswer : test_table) { - auto exp = tryTo(argAndAnswer.first); - ASSERT_FALSE(exp.isError()); - EXPECT_EQ(argAndAnswer.second, exp.get()); - } +TEST_F(ConversionsTests, tryTo_string_to_boolean_valid_args) +{ + const auto test_table = std::unordered_map { + {"1", true}, {"0", false}, {"y", true}, + {"n", false}, {"yes", true}, {"yEs", true}, + {"Yes", true}, {"no", false}, {"No", false}, + {"t", true}, {"T", true}, {"f", false}, + {"F", false}, {"true", true}, {"True", true}, + {"tRUE", true}, {"false", false}, {"fALse", false}, + {"ok", true}, {"OK", true}, {"Ok", true}, + {"enable", true}, {"Enable", true}, {"ENABLE", true}, + {"disable", false}, {"Disable", false}, {"DISABLE", false}, + }; + for (const auto& argAndAnswer : test_table) { + auto exp = tryTo(argAndAnswer.first); + ASSERT_FALSE(exp.isError()); + EXPECT_EQ(argAndAnswer.second, exp.get()); + } } -TEST_F(ConversionsTests, tryTo_string_to_boolean_invalid_args) { - const auto test_table = std::vector{ - "", "\0", "\n", "\x06", "\x15", "\x27", "ADS", - "7251", "20.09", "M0V+K7V", "+", "-", ".", "@", - "1.0", "11", "00", " 0", "1 ", "2", "10", - "100%", "_0", "1_", "1.", "2.", "E", "a", - "b", "d", "e", "o", "p", "uh", "nix", - "nixie", "nixy", "nixey", "nay", "nah", "no way", "veto", - "yea", "yeah", "yep", "okey", "aye", "roger", "uh-huh", - "righto", "yup", "yuppers", "ja", "surely", "amen", "totally", - "sure", "yessir", "true.", "tru", "tr", "tr.", "ff", - "yy", "nn", "nope", "null", "nil", "dis", "able", - "pos", "neg", "ack", "ACK", "NAK", "enabled", "disabled", - "valid", "invalid", "void", "allow", "permit", "positive", "negative", - }; - for (const auto& wrong : test_table) { - auto exp = tryTo(wrong); - ASSERT_TRUE(exp.isError()); - EXPECT_EQ(ConversionError::InvalidArgument, exp.getErrorCode()); - } +TEST_F(ConversionsTests, tryTo_string_to_boolean_invalid_args) +{ + const auto test_table = std::vector { + "", "\0", "\n", "\x06", "\x15", "\x27", "ADS", + "7251", "20.09", "M0V+K7V", "+", "-", ".", "@", + "1.0", "11", "00", " 0", "1 ", "2", "10", + "100%", "_0", "1_", "1.", "2.", "E", "a", + "b", "d", "e", "o", "p", "uh", "nix", + "nixie", "nixy", "nixey", "nay", "nah", "no way", "veto", + "yea", "yeah", "yep", "okey", "aye", "roger", "uh-huh", + "righto", "yup", "yuppers", "ja", "surely", "amen", "totally", + "sure", "yessir", "true.", "tru", "tr", "tr.", "ff", + "yy", "nn", "nope", "null", "nil", "dis", "able", + "pos", "neg", "ack", "ACK", "NAK", "enabled", "disabled", + "valid", "invalid", "void", "allow", "permit", "positive", "negative", + }; + for (const auto& wrong : test_table) { + auto exp = tryTo(wrong); + ASSERT_TRUE(exp.isError()); + EXPECT_EQ(ConversionError::InvalidArgument, exp.getErrorCode()); + } } } // namespace osquery diff --git a/src/osquery/utils/conversions/to.h b/src/osquery/utils/conversions/to.h index 712ee68..b12a443 100644 --- a/src/osquery/utils/conversions/to.h +++ b/src/osquery/utils/conversions/to.h @@ -26,16 +26,17 @@ namespace osquery { * to(En::First) -> "En::First[1]" */ template -inline typename std::enable_if::value && - std::is_same::value, - ToType>::type -to(FromType from) noexcept { - auto str = ToType{boost::core::demangle(typeid(from).name())}; - str.append("["); - str.append(std::to_string( - static_cast::type>(from))); - str.append("]"); - return str; +inline typename std::enable_if < std::is_enum::value&& +std::is_same::value, + ToType >::type + to(FromType from) noexcept +{ + auto str = ToType{boost::core::demangle(typeid(from).name())}; + str.append("["); + str.append(std::to_string( + static_cast::type>(from))); + str.append("]"); + return str; } } // namespace osquery diff --git a/src/osquery/utils/conversions/tryto.cpp b/src/osquery/utils/conversions/tryto.cpp index 7f3ab31..5745688 100644 --- a/src/osquery/utils/conversions/tryto.cpp +++ b/src/osquery/utils/conversions/tryto.cpp @@ -16,37 +16,38 @@ namespace osquery { namespace impl { -Expected stringToBool(std::string from) { - static const auto table = std::unordered_map{ - {"1", true}, - {"0", false}, - {"y", true}, - {"yes", true}, - {"n", false}, - {"no", false}, - {"t", true}, - {"true", true}, - {"f", false}, - {"false", false}, - {"ok", true}, - {"disable", false}, - {"enable", true}, - }; - using CharType = std::string::value_type; - // Classic locale could be used here because all available string - // representations of boolean have ascii encoding. It must be a bit faster. - static const auto& ctype = - std::use_facet>(std::locale::classic()); - for (auto& ch : from) { - ch = ctype.tolower(ch); - } - const auto it = table.find(from); - if (it == table.end()) { - return createError(ConversionError::InvalidArgument) - << "Wrong string representation of boolean " - << boost::io::quoted(from); - } - return it->second; +Expected stringToBool(std::string from) +{ + static const auto table = std::unordered_map { + {"1", true}, + {"0", false}, + {"y", true}, + {"yes", true}, + {"n", false}, + {"no", false}, + {"t", true}, + {"true", true}, + {"f", false}, + {"false", false}, + {"ok", true}, + {"disable", false}, + {"enable", true}, + }; + using CharType = std::string::value_type; + // Classic locale could be used here because all available string + // representations of boolean have ascii encoding. It must be a bit faster. + static const auto& ctype = + std::use_facet>(std::locale::classic()); + for (auto& ch : from) { + ch = ctype.tolower(ch); + } + const auto it = table.find(from); + if (it == table.end()) { + return createError(ConversionError::InvalidArgument) + << "Wrong string representation of boolean " + << boost::io::quoted(from); + } + return it->second; } } // namespace impl diff --git a/src/osquery/utils/conversions/tryto.h b/src/osquery/utils/conversions/tryto.h index 2cc81c1..7dc0141 100644 --- a/src/osquery/utils/conversions/tryto.h +++ b/src/osquery/utils/conversions/tryto.h @@ -15,98 +15,105 @@ namespace osquery { enum class ConversionError { - InvalidArgument, - OutOfRange, - Unknown, + InvalidArgument, + OutOfRange, + Unknown, }; template -inline typename std::enable_if< - std::is_same::type>::type>::value, - Expected>::type -tryTo(FromType&& from) { - return std::forward(from); +inline typename std::enable_if < +std::is_same::type>::type>::value, + Expected>::type + tryTo(FromType&& from) +{ + return std::forward(from); } namespace impl { template struct IsStlString { - static constexpr bool value = std::is_same::value || - std::is_same::value; + static constexpr bool value = std::is_same::value || + std::is_same::value; }; template struct IsInteger { - static constexpr bool value = - std::is_integral::value && !std::is_same::value; + static constexpr bool value = + std::is_integral::value && !std::is_same::value; }; -template ::value && - IsStlString::value, - IntType>::type> +template < typename FromType, + typename ToType, + typename IntType, + typename = + typename std::enable_if < std::is_same::value && + IsStlString::value, + IntType >::type > struct IsConversionFromStringToIntEnabledFor { - using type = IntType; + using type = IntType; }; template inline - typename IsConversionFromStringToIntEnabledFor::type - throwingStringToInt(const FromType& from, const int base) { - auto pos = std::size_t{}; - return std::stoi(from, &pos, base); +typename IsConversionFromStringToIntEnabledFor::type +throwingStringToInt(const FromType& from, const int base) +{ + auto pos = std::size_t{}; + return std::stoi(from, &pos, base); } template inline typename IsConversionFromStringToIntEnabledFor::type -throwingStringToInt(const FromType& from, const int base) { - auto pos = std::size_t{}; - return std::stol(from, &pos, base); + ToType, + long int>::type + throwingStringToInt(const FromType& from, const int base) +{ + auto pos = std::size_t{}; + return std::stol(from, &pos, base); } template inline typename IsConversionFromStringToIntEnabledFor::type -throwingStringToInt(const FromType& from, const int base) { - auto pos = std::size_t{}; - return std::stoll(from, &pos, base); + ToType, + long long int>::type + throwingStringToInt(const FromType& from, const int base) +{ + auto pos = std::size_t{}; + return std::stoll(from, &pos, base); } template inline typename IsConversionFromStringToIntEnabledFor::type -throwingStringToInt(const FromType& from, const int base) { - auto pos = std::size_t{}; - return std::stoul(from, &pos, base); + ToType, + unsigned int>::type + throwingStringToInt(const FromType& from, const int base) +{ + auto pos = std::size_t{}; + return std::stoul(from, &pos, base); } template inline typename IsConversionFromStringToIntEnabledFor::type -throwingStringToInt(const FromType& from, const int base) { - auto pos = std::size_t{}; - return std::stoul(from, &pos, base); + ToType, + unsigned long int>::type + throwingStringToInt(const FromType& from, const int base) +{ + auto pos = std::size_t{}; + return std::stoul(from, &pos, base); } template inline - typename IsConversionFromStringToIntEnabledFor::type - throwingStringToInt(const FromType& from, const int base) { - auto pos = std::size_t{}; - return std::stoull(from, &pos, base); +typename IsConversionFromStringToIntEnabledFor::type + throwingStringToInt(const FromType& from, const int base) +{ + auto pos = std::size_t{}; + return std::stoull(from, &pos, base); } Expected stringToBool(std::string from); @@ -117,26 +124,27 @@ Expected stringToBool(std::string from); * Template tryTo for [w]string to integer conversion */ template -inline typename std::enable_if::value && - impl::IsStlString::value, - Expected>::type -tryTo(const FromType& from, const int base = 10) noexcept { - try { - return impl::throwingStringToInt(from, base); - } catch (const std::invalid_argument& ia) { - return createError(ConversionError::InvalidArgument) - << "If no conversion could be performed. " << ia.what(); - } catch (const std::out_of_range& oor) { - return createError(ConversionError::OutOfRange) - << "Value read is out of the range of representable values by an " - "int. " - << oor.what(); - } catch (...) { - return createError(ConversionError::Unknown) - << "Unknown error during conversion " - << boost::core::demangle(typeid(FromType).name()) << " to " - << boost::core::demangle(typeid(ToType).name()) << " base " << base; - } +inline typename std::enable_if < impl::IsInteger::value&& +impl::IsStlString::value, + Expected>::type + tryTo(const FromType& from, const int base = 10) noexcept +{ + try { + return impl::throwingStringToInt(from, base); + } catch (const std::invalid_argument& ia) { + return createError(ConversionError::InvalidArgument) + << "If no conversion could be performed. " << ia.what(); + } catch (const std::out_of_range& oor) { + return createError(ConversionError::OutOfRange) + << "Value read is out of the range of representable values by an " + "int. " + << oor.what(); + } catch (...) { + return createError(ConversionError::Unknown) + << "Unknown error during conversion " + << boost::core::demangle(typeid(FromType).name()) << " to " + << boost::core::demangle(typeid(ToType).name()) << " base " << base; + } } /** @@ -152,13 +160,15 @@ tryTo(const FromType& from, const int base = 10) noexcept { */ template inline typename std::enable_if::value, - Expected>::type -tryTo(std::string from) { - return impl::stringToBool(std::move(from)); + Expected>::type + tryTo(std::string from) +{ + return impl::stringToBool(std::move(from)); } -inline size_t operator"" _sz(unsigned long long int x) { - return x; +inline size_t operator"" _sz(unsigned long long int x) +{ + return x; } } diff --git a/src/osquery/utils/debug/debug_only.h b/src/osquery/utils/debug/debug_only.h index 3e784a2..7d9e0e0 100644 --- a/src/osquery/utils/debug/debug_only.h +++ b/src/osquery/utils/debug/debug_only.h @@ -21,12 +21,13 @@ namespace debug_only { /** * Use it for unconditional abort with message only in debug mode */ -inline void fail(const char* msg) { +inline void fail(const char* msg) +{ #ifndef NDEBUG - std::cerr << "Failure in debug mode: \"" << msg << "\"\n"; - assert(false && "Failure in debug mode"); + std::cerr << "Failure in debug mode: \"" << msg << "\"\n"; + assert(false && "Failure in debug mode"); #endif - boost::ignore_unused(msg); + boost::ignore_unused(msg); } /** @@ -34,27 +35,29 @@ inline void fail(const char* msg) { * See examples of usage in tests osquery/debug/tests/debug_only_tests.cpp */ template -inline void verify(FunctionType checker, const char* msg) { +inline void verify(FunctionType checker, const char* msg) +{ #ifndef NDEBUG - if (!checker()) { - fail(msg); - } + if (!checker()) { + fail(msg); + } #endif - boost::ignore_unused(checker); - boost::ignore_unused(msg); + boost::ignore_unused(checker); + boost::ignore_unused(msg); } /** * Pretty much the same as verify, but for the simple boolean condition */ -inline void verifyTrue(bool expected_true, const char* msg) { +inline void verifyTrue(bool expected_true, const char* msg) +{ #ifndef NDEBUG - if (!expected_true) { - fail(msg); - } + if (!expected_true) { + fail(msg); + } #endif - boost::ignore_unused(expected_true); - boost::ignore_unused(msg); + boost::ignore_unused(expected_true); + boost::ignore_unused(msg); } /** @@ -67,69 +70,74 @@ inline void verifyTrue(bool expected_true, const char* msg) { */ template class Var final { - public: - explicit Var() +public: + explicit Var() #ifndef NDEBUG - : value_(VarType{}) + : value_(VarType {}) #endif - { - } + { + } - Var(VarType value) + Var(VarType value) #ifndef NDEBUG - : value_(std::move(value)) + : value_(std::move(value)) #endif - { - boost::ignore_unused(value); - } + { + boost::ignore_unused(value); + } - inline void verify(const char* msg) const { + inline void verify(const char* msg) const + { #ifndef NDEBUG - if (!value_) { - fail(msg); - } + if (!value_) { + fail(msg); + } #endif - boost::ignore_unused(msg); - } + boost::ignore_unused(msg); + } - template - inline void verify(FunctionType checker, const char* msg) const { + template + inline void verify(FunctionType checker, const char* msg) const + { #ifndef NDEBUG - if (!checker(value_)) { - fail(msg); - } + if (!checker(value_)) { + fail(msg); + } #endif - boost::ignore_unused(checker); - boost::ignore_unused(msg); - } + boost::ignore_unused(checker); + boost::ignore_unused(msg); + } - inline void verifyEqual(const VarType& other, const char* msg) const { + inline void verifyEqual(const VarType& other, const char* msg) const + { #ifndef NDEBUG - if (value_ != other) { - fail(msg); - } + if (value_ != other) { + fail(msg); + } #endif - boost::ignore_unused(other); - boost::ignore_unused(msg); - } + boost::ignore_unused(other); + boost::ignore_unused(msg); + } - inline void set(const VarType& newValue) const { + inline void set(const VarType& newValue) const + { #ifndef NDEBUG - value_ = newValue; + value_ = newValue; #endif - boost::ignore_unused(newValue); - } + boost::ignore_unused(newValue); + } - template - inline void update(FunctionType modifier) const { + template + inline void update(FunctionType modifier) const + { #ifndef NDEBUG - value_ = modifier(value_); + value_ = modifier(value_); #endif - boost::ignore_unused(modifier); - } + boost::ignore_unused(modifier); + } #ifndef NDEBUG - mutable VarType value_; + mutable VarType value_; #endif }; diff --git a/src/osquery/utils/debug/tests/debug_only.cpp b/src/osquery/utils/debug/tests/debug_only.cpp index a88f4b3..74c36ce 100644 --- a/src/osquery/utils/debug/tests/debug_only.cpp +++ b/src/osquery/utils/debug/tests/debug_only.cpp @@ -22,157 +22,186 @@ class TestEmptyClass {}; } // namespace -GTEST_TEST(DebugOnly, fail) { +GTEST_TEST(DebugOnly, fail) +{ #ifndef NDEBUG - ASSERT_DEATH(debug_only::fail("This code should fail"), - "debug.*This code should fail"); + ASSERT_DEATH(debug_only::fail("This code should fail"), + "debug.*This code should fail"); #endif } -GTEST_TEST(DebugOnly, verify) { +GTEST_TEST(DebugOnly, verify) +{ #ifndef NDEBUG - ASSERT_DEATH( - debug_only::verify([]() { return false; }, - "This code should fail because lambda return false"), - "debug.*This code should fail because lambda return false"); + ASSERT_DEATH( + debug_only::verify([]() { + return false; + }, + "This code should fail because lambda return false"), + "debug.*This code should fail because lambda return false"); #endif } -GTEST_TEST(DebugOnly, verifyTrue) { - debug_only::verifyTrue(true, "This code must not fail"); +GTEST_TEST(DebugOnly, verifyTrue) +{ + debug_only::verifyTrue(true, "This code must not fail"); #ifndef NDEBUG - ASSERT_DEATH(debug_only::verifyTrue(false, "This code should fail"), - "debug.*This code should fail"); + ASSERT_DEATH(debug_only::verifyTrue(false, "This code should fail"), + "debug.*This code should fail"); #endif } -GTEST_TEST(DebugOnly, verify_do_nothing_in_non_debug_mode) { +GTEST_TEST(DebugOnly, verify_do_nothing_in_non_debug_mode) +{ #ifdef NDEBUG - // This code will be compiled and run only in release mode according to macro - // conditions around. That means code inside lambda is not going to be run. - // Let's check it here. - debug_only::verify( - []() { - EXPECT_TRUE(false); - return false; - }, - "This check should not fail"); + // This code will be compiled and run only in release mode according to macro + // conditions around. That means code inside lambda is not going to be run. + // Let's check it here. + debug_only::verify( + []() { + EXPECT_TRUE(false); + return false; + }, + "This check should not fail"); #endif } -GTEST_TEST(DebugOnlyVar, size) { +GTEST_TEST(DebugOnlyVar, size) +{ #ifndef NDEBUG - ASSERT_EQ(sizeof(long long), sizeof(debug_only::Var)); + ASSERT_EQ(sizeof(long long), sizeof(debug_only::Var)); #else - ASSERT_EQ(sizeof(TestEmptyClass), sizeof(debug_only::Var)); + ASSERT_EQ(sizeof(TestEmptyClass), sizeof(debug_only::Var)); #endif } -GTEST_TEST(DebugOnlyVar, verify) { - auto var = debug_only::Var{0}; - var.verify([](auto v) { return v == 0; }, "This should be fine"); - var.verifyEqual(0, "This also should be fine"); +GTEST_TEST(DebugOnlyVar, verify) +{ + auto var = debug_only::Var {0}; + var.verify([](auto v) { + return v == 0; + }, "This should be fine"); + var.verifyEqual(0, "This also should be fine"); #ifndef NDEBUG - ASSERT_DEATH(var.verify([](auto v) { return v == 9; }, - "There is some funny joke supposed to be here"), - "debug.*There is some funny joke supposed to be here"); - ASSERT_DEATH(var.verifyEqual(12, "One more hilarious joke, have a fun"), - "debug.*One more hilarious joke, have a fun"); - ASSERT_DEATH(var.verify("And one more, don't worry this is the last one"), - "debug.*And one more, don't worry this is the last one"); + ASSERT_DEATH(var.verify([](auto v) { + return v == 9; + }, + "There is some funny joke supposed to be here"), + "debug.*There is some funny joke supposed to be here"); + ASSERT_DEATH(var.verifyEqual(12, "One more hilarious joke, have a fun"), + "debug.*One more hilarious joke, have a fun"); + ASSERT_DEATH(var.verify("And one more, don't worry this is the last one"), + "debug.*And one more, don't worry this is the last one"); #endif } -GTEST_TEST(DebugOnlyVar, implicit_constructor) { - // object can be created from underlying value - debug_only::Var dbg_var = 12; - dbg_var.verifyEqual(12, "This check should not fail"); +GTEST_TEST(DebugOnlyVar, implicit_constructor) +{ + // object can be created from underlying value + debug_only::Var dbg_var = 12; + dbg_var.verifyEqual(12, "This check should not fail"); } -GTEST_TEST(DebugOnlyVar, set) { - auto var = debug_only::Var{12}; - var.verify( - [](auto value) { - EXPECT_EQ(12, value); - return true; - }, - "This check should not fail"); - var.set(291); - var.verify( - [](auto value) { - EXPECT_EQ(291, value); - return true; - }, - "This check should not fail"); +GTEST_TEST(DebugOnlyVar, set) +{ + auto var = debug_only::Var {12}; + var.verify( + [](auto value) { + EXPECT_EQ(12, value); + return true; + }, + "This check should not fail"); + var.set(291); + var.verify( + [](auto value) { + EXPECT_EQ(291, value); + return true; + }, + "This check should not fail"); } -GTEST_TEST(DebugOnlyVar, update) { - auto var = debug_only::Var{12}; - var.verify( - [](auto value) { - EXPECT_EQ(12, value); - return true; - }, - "This check should not fail"); - // where - var.update([](auto old) { return old + 17; }); - // let's verify update was successful - var.verify( - [](auto value) { - EXPECT_EQ(12 + 17, value); - return true; - }, - "This check should not fail"); +GTEST_TEST(DebugOnlyVar, update) +{ + auto var = debug_only::Var {12}; + var.verify( + [](auto value) { + EXPECT_EQ(12, value); + return true; + }, + "This check should not fail"); + // where + var.update([](auto old) { + return old + 17; + }); + // let's verify update was successful + var.verify( + [](auto value) { + EXPECT_EQ(12 + 17, value); + return true; + }, + "This check should not fail"); } -GTEST_TEST(DebugOnlyVar, verify_in_non_debug_mode_should_not_be_run) { - auto var = debug_only::Var{12}; - var.verify( - [](auto value) { - EXPECT_EQ(12, value); - return true; - }, - "This check should not fail"); +GTEST_TEST(DebugOnlyVar, verify_in_non_debug_mode_should_not_be_run) +{ + auto var = debug_only::Var {12}; + var.verify( + [](auto value) { + EXPECT_EQ(12, value); + return true; + }, + "This check should not fail"); #ifdef NDEBUG - // This code will be compiled and run only in release mode according to macro - // conditions around. That means code inside lambda is not going to be run. - // Let's check it here. - var.verify( - [](auto value) { - boost::ignore_unused(value); - EXPECT_TRUE(false); - return false; - }, - "This check should not fail"); + // This code will be compiled and run only in release mode according to macro + // conditions around. That means code inside lambda is not going to be run. + // Let's check it here. + var.verify( + [](auto value) { + boost::ignore_unused(value); + EXPECT_TRUE(false); + return false; + }, + "This check should not fail"); #endif } struct Gun { - explicit Gun(int bullets) : bullets_(bullets) {} - - void shot() { - dbg.update([this](auto v) { return bullets_ == 0 ? v + 1 : v; }); - --bullets_; - } - - int bullets_; - debug_only::Var dbg = 0; + explicit Gun(int bullets) : bullets_(bullets) {} + + void shot() + { + dbg.update([this](auto v) { + return bullets_ == 0 ? v + 1 : v; + }); + --bullets_; + } + + int bullets_; + debug_only::Var dbg = 0; }; -GTEST_TEST(DebugOnlyVar, example_debug_check_watchdog) { - auto gun = Gun(2); - gun.shot(); - gun.shot(); - gun.dbg.verify([](auto v) { return v == 0; }, - "There is not supposed to have a failure, just an example"); +GTEST_TEST(DebugOnlyVar, example_debug_check_watchdog) +{ + auto gun = Gun(2); + gun.shot(); + gun.shot(); + gun.dbg.verify([](auto v) { + return v == 0; + }, + "There is not supposed to have a failure, just an example"); } -GTEST_TEST(DebugOnlyVar, example_debug_check_return_value) { - auto test_function = [](int i) { return std::to_string(i); }; - debug_only::Var dbg = test_function(11); - dbg.verify([](const auto& str) { return !str.empty(); }, - "The return value is not supposed to be empty string. But for " - "performance reasons let's check it only in debug mode."); +GTEST_TEST(DebugOnlyVar, example_debug_check_return_value) +{ + auto test_function = [](int i) { + return std::to_string(i); + }; + debug_only::Var dbg = test_function(11); + dbg.verify([](const auto & str) { + return !str.empty(); + }, + "The return value is not supposed to be empty string. But for " + "performance reasons let's check it only in debug mode."); } } // namespace osquery diff --git a/src/osquery/utils/error/error.h b/src/osquery/utils/error/error.h index 2fbe303..5ef5533 100644 --- a/src/osquery/utils/error/error.h +++ b/src/osquery/utils/error/error.h @@ -20,135 +20,150 @@ namespace osquery { class ErrorBase { - public: - virtual std::string getNonRecursiveMessage() const = 0; - virtual std::string getMessage() const = 0; - virtual ~ErrorBase() = default; - ErrorBase() = default; - ErrorBase(const ErrorBase& other) = default; +public: + virtual std::string getNonRecursiveMessage() const = 0; + virtual std::string getMessage() const = 0; + virtual ~ErrorBase() = default; + ErrorBase() = default; + ErrorBase(const ErrorBase& other) = default; }; template class Error final : public ErrorBase { - public: - using SelfType = Error; - - explicit Error(ErrorCodeEnumType error_code, - std::string message, - std::unique_ptr underlying_error = nullptr) - : errorCode_(error_code), - message_(std::move(message)), - underlyingError_(std::move(underlying_error)) {} - - explicit Error(ErrorCodeEnumType error_code, - std::unique_ptr underlying_error = nullptr) - : errorCode_(error_code), underlyingError_(std::move(underlying_error)) {} - - virtual ~Error() = default; - - Error(Error&& other) = default; - Error(const Error& other) = delete; - - Error& operator=(Error&& other) = default; - Error& operator=(const Error& other) = delete; - - ErrorCodeEnumType getErrorCode() const { - return errorCode_; - } - - bool hasUnderlyingError() const { - return underlyingError_ != nullptr; - } - - const ErrorBase& getUnderlyingError() const { - return *underlyingError_; - } - - std::unique_ptr takeUnderlyingError() const { - return std::move(underlyingError_); - } - - std::string getNonRecursiveMessage() const override { - std::string full_message = to(errorCode_); - if (message_.size() > 0) { - full_message += " (" + message_ + ")"; - } - return full_message; - } - - std::string getMessage() const override { - std::string full_message = getNonRecursiveMessage(); - if (underlyingError_) { - full_message += " <- " + underlyingError_->getMessage(); - } - return full_message; - } - - void appendToMessage(const std::string& text) { - message_.append(text); - } - - private: - ErrorCodeEnumType errorCode_; - std::string message_; - std::unique_ptr underlyingError_; +public: + using SelfType = Error; + + explicit Error(ErrorCodeEnumType error_code, + std::string message, + std::unique_ptr underlying_error = nullptr) + : errorCode_(error_code), + message_(std::move(message)), + underlyingError_(std::move(underlying_error)) {} + + explicit Error(ErrorCodeEnumType error_code, + std::unique_ptr underlying_error = nullptr) + : errorCode_(error_code), underlyingError_(std::move(underlying_error)) {} + + virtual ~Error() = default; + + Error(Error&& other) = default; + Error(const Error& other) = delete; + + Error& operator=(Error&& other) = default; + Error& operator=(const Error& other) = delete; + + ErrorCodeEnumType getErrorCode() const + { + return errorCode_; + } + + bool hasUnderlyingError() const + { + return underlyingError_ != nullptr; + } + + const ErrorBase& getUnderlyingError() const + { + return *underlyingError_; + } + + std::unique_ptr takeUnderlyingError() const + { + return std::move(underlyingError_); + } + + std::string getNonRecursiveMessage() const override + { + std::string full_message = to(errorCode_); + if (message_.size() > 0) { + full_message += " (" + message_ + ")"; + } + return full_message; + } + + std::string getMessage() const override + { + std::string full_message = getNonRecursiveMessage(); + if (underlyingError_) { + full_message += " <- " + underlyingError_->getMessage(); + } + return full_message; + } + + void appendToMessage(const std::string& text) + { + message_.append(text); + } + +private: + ErrorCodeEnumType errorCode_; + std::string message_; + std::unique_ptr underlyingError_; }; template -inline bool operator==(const Error& lhs, const Error& rhs) { - return lhs.getErrorCode() == rhs.getErrorCode(); +inline bool operator==(const Error& lhs, const Error& rhs) +{ + return lhs.getErrorCode() == rhs.getErrorCode(); } template -inline bool operator==(const Error* lhs, const T rhs) { - return lhs->getErrorCode() == rhs; +inline bool operator==(const Error* lhs, const T rhs) +{ + return lhs->getErrorCode() == rhs; } template -inline bool operator==(const Error& lhs, const T rhs) { - return lhs.getErrorCode() == rhs; +inline bool operator==(const Error& lhs, const T rhs) +{ + return lhs.getErrorCode() == rhs; } template -inline bool operator==(const ErrorBase& lhs, const T rhs) { - try { - const Error& casted_lhs = dynamic_cast&>(lhs); - return casted_lhs == rhs; - } catch (std::bad_cast _) { - return false; - } +inline bool operator==(const ErrorBase& lhs, const T rhs) +{ + try { + const Error& casted_lhs = dynamic_cast&>(lhs); + return casted_lhs == rhs; + } catch (std::bad_cast _) { + return false; + } } -inline std::ostream& operator<<(std::ostream& out, const ErrorBase& error) { - out << error.getMessage(); - return out; +inline std::ostream& operator<<(std::ostream& out, const ErrorBase& error) +{ + out << error.getMessage(); + return out; } template OSQUERY_NODISCARD Error createError( - ErrorCodeEnumType error_code, - Error underlying_error) { - return Error( - error_code, - std::make_unique>( - std::move(underlying_error))); + ErrorCodeEnumType error_code, + Error underlying_error) +{ + return Error( + error_code, + std::make_unique>( + std::move(underlying_error))); } template OSQUERY_NODISCARD Error createError( - ErrorCodeEnumType error_code) { - return Error(error_code); + ErrorCodeEnumType error_code) +{ + return Error(error_code); } template ::value>::type> -inline ErrorType operator<<(ErrorType&& error, const ValueType& value) { - std::ostringstream ostr{}; - ostr << value; - error.appendToMessage(ostr.str()); - return std::forward(error); + typename ValueType, + typename = typename std::enable_if< + std::is_base_of::value>::type> +inline ErrorType operator<<(ErrorType && error, const ValueType& value) +{ + std::ostringstream ostr{}; + ostr << value; + error.appendToMessage(ostr.str()); + return std::forward(error); } } // namespace osquery diff --git a/src/osquery/utils/error/tests/error.cpp b/src/osquery/utils/error/tests/error.cpp index 4af8024..2115701 100644 --- a/src/osquery/utils/error/tests/error.cpp +++ b/src/osquery/utils/error/tests/error.cpp @@ -14,90 +14,96 @@ #include enum class TestError { - SomeError = 1, - AnotherError = 2, - MusicError, + SomeError = 1, + AnotherError = 2, + MusicError, }; -GTEST_TEST(ErrorTest, initialization) { - auto error = osquery::Error(TestError::SomeError, "TestMessage"); - EXPECT_FALSE(error.hasUnderlyingError()); - EXPECT_TRUE(error == TestError::SomeError); +GTEST_TEST(ErrorTest, initialization) +{ + auto error = osquery::Error(TestError::SomeError, "TestMessage"); + EXPECT_FALSE(error.hasUnderlyingError()); + EXPECT_TRUE(error == TestError::SomeError); - auto shortMsg = error.getNonRecursiveMessage(); - EXPECT_NE(std::string::npos, shortMsg.find("TestError[1]")); + auto shortMsg = error.getNonRecursiveMessage(); + EXPECT_NE(std::string::npos, shortMsg.find("TestError[1]")); - auto fullMsg = error.getMessage(); - EXPECT_NE(std::string::npos, fullMsg.find("TestError[1]")); - EXPECT_NE(std::string::npos, fullMsg.find("TestMessage")); + auto fullMsg = error.getMessage(); + EXPECT_NE(std::string::npos, fullMsg.find("TestError[1]")); + EXPECT_NE(std::string::npos, fullMsg.find("TestMessage")); } -GTEST_TEST(ErrorTest, recursive) { - auto orignalError = std::make_unique>( - TestError::SomeError, "SuperTestMessage"); - auto error = osquery::Error( - TestError::AnotherError, "TestMessage", std::move(orignalError)); - EXPECT_TRUE(error.hasUnderlyingError()); - - auto shortMsg = error.getNonRecursiveMessage(); - EXPECT_EQ(std::string::npos, shortMsg.find("TestError[1]")); - EXPECT_NE(std::string::npos, shortMsg.find("TestError[2]")); - - auto fullMsg = error.getMessage(); - EXPECT_NE(std::string::npos, fullMsg.find("TestError[1]")); - EXPECT_NE(std::string::npos, fullMsg.find("SuperTestMessage")); - EXPECT_NE(std::string::npos, fullMsg.find("TestError[2]")); - EXPECT_NE(std::string::npos, fullMsg.find("TestMessage")); +GTEST_TEST(ErrorTest, recursive) +{ + auto orignalError = std::make_unique>( + TestError::SomeError, "SuperTestMessage"); + auto error = osquery::Error( + TestError::AnotherError, "TestMessage", std::move(orignalError)); + EXPECT_TRUE(error.hasUnderlyingError()); + + auto shortMsg = error.getNonRecursiveMessage(); + EXPECT_EQ(std::string::npos, shortMsg.find("TestError[1]")); + EXPECT_NE(std::string::npos, shortMsg.find("TestError[2]")); + + auto fullMsg = error.getMessage(); + EXPECT_NE(std::string::npos, fullMsg.find("TestError[1]")); + EXPECT_NE(std::string::npos, fullMsg.find("SuperTestMessage")); + EXPECT_NE(std::string::npos, fullMsg.find("TestError[2]")); + EXPECT_NE(std::string::npos, fullMsg.find("TestMessage")); } -bool stringContains(const std::string& where, const std::string& what) { - return boost::contains(where, what); +bool stringContains(const std::string& where, const std::string& what) +{ + return boost::contains(where, what); }; -GTEST_TEST(ErrorTest, createErrorSimple) { - const auto msg = std::string{ - "\"!ab#c$d%e&f'g(h)i*j+k,l-m.n/o\" this is not a human readable text"}; - auto err = osquery::createError(TestError::AnotherError) << msg; - EXPECT_EQ(TestError::AnotherError, err.getErrorCode()); - EXPECT_FALSE(err.hasUnderlyingError()); - - auto shortMsg = err.getMessage(); - EXPECT_PRED2(stringContains, shortMsg, "TestError"); - EXPECT_PRED2(stringContains, shortMsg, msg); +GTEST_TEST(ErrorTest, createErrorSimple) +{ + const auto msg = std::string{ + "\"!ab#c$d%e&f'g(h)i*j+k,l-m.n/o\" this is not a human readable text"}; + auto err = osquery::createError(TestError::AnotherError) << msg; + EXPECT_EQ(TestError::AnotherError, err.getErrorCode()); + EXPECT_FALSE(err.hasUnderlyingError()); + + auto shortMsg = err.getMessage(); + EXPECT_PRED2(stringContains, shortMsg, "TestError"); + EXPECT_PRED2(stringContains, shortMsg, msg); } -GTEST_TEST(ErrorTest, createErrorFromOtherError) { - const auto firstMsg = std::string{"2018-06-28 08:13 451014"}; - - auto firstErr = osquery::createError(TestError::SomeError) << firstMsg; - EXPECT_EQ(TestError::SomeError, firstErr.getErrorCode()); - EXPECT_FALSE(firstErr.hasUnderlyingError()); - - EXPECT_PRED2(stringContains, firstErr.getMessage(), firstMsg); - - const auto secondMsg = std::string{"what's wrong with the first message?!"}; - auto secondErr = - osquery::createError(TestError::AnotherError, std::move(firstErr)) - << secondMsg; - EXPECT_EQ(TestError::AnotherError, secondErr.getErrorCode()); - EXPECT_TRUE(secondErr.hasUnderlyingError()); - auto secondShortMsg = secondErr.getMessage(); - EXPECT_PRED2(stringContains, secondShortMsg, "TestError"); - EXPECT_PRED2(stringContains, secondShortMsg, firstMsg); - EXPECT_PRED2(stringContains, secondShortMsg, secondMsg); +GTEST_TEST(ErrorTest, createErrorFromOtherError) +{ + const auto firstMsg = std::string{"2018-06-28 08:13 451014"}; + + auto firstErr = osquery::createError(TestError::SomeError) << firstMsg; + EXPECT_EQ(TestError::SomeError, firstErr.getErrorCode()); + EXPECT_FALSE(firstErr.hasUnderlyingError()); + + EXPECT_PRED2(stringContains, firstErr.getMessage(), firstMsg); + + const auto secondMsg = std::string{"what's wrong with the first message?!"}; + auto secondErr = + osquery::createError(TestError::AnotherError, std::move(firstErr)) + << secondMsg; + EXPECT_EQ(TestError::AnotherError, secondErr.getErrorCode()); + EXPECT_TRUE(secondErr.hasUnderlyingError()); + auto secondShortMsg = secondErr.getMessage(); + EXPECT_PRED2(stringContains, secondShortMsg, "TestError"); + EXPECT_PRED2(stringContains, secondShortMsg, firstMsg); + EXPECT_PRED2(stringContains, secondShortMsg, secondMsg); } -GTEST_TEST(ErrorTest, createErrorAndStreamToIt) { - const auto a4 = std::string{"A4"}; - const auto err = osquery::createError(TestError::MusicError) - << "Do" << '-' << "Re" - << "-Mi" - << "-Fa" - << "-Sol" - << "-La" - << "-Si La" << boost::io::quoted(a4) << ' ' << 440 << " Hz"; - EXPECT_EQ(TestError::MusicError, err.getErrorCode()); - auto fullMsg = err.getMessage(); - EXPECT_PRED2( - stringContains, fullMsg, "Do-Re-Mi-Fa-Sol-La-Si La\"A4\" 440 Hz"); +GTEST_TEST(ErrorTest, createErrorAndStreamToIt) +{ + const auto a4 = std::string{"A4"}; + const auto err = osquery::createError(TestError::MusicError) + << "Do" << '-' << "Re" + << "-Mi" + << "-Fa" + << "-Sol" + << "-La" + << "-Si La" << boost::io::quoted(a4) << ' ' << 440 << " Hz"; + EXPECT_EQ(TestError::MusicError, err.getErrorCode()); + auto fullMsg = err.getMessage(); + EXPECT_PRED2( + stringContains, fullMsg, "Do-Re-Mi-Fa-Sol-La-Si La\"A4\" 440 Hz"); } diff --git a/src/osquery/utils/expected/expected.h b/src/osquery/utils/expected/expected.h index a3252ce..664cd87 100644 --- a/src/osquery/utils/expected/expected.h +++ b/src/osquery/utils/expected/expected.h @@ -72,170 +72,192 @@ namespace osquery { template class Expected final { - public: - using ValueType = ValueType_; - using ErrorType = Error; - using SelfType = Expected; - - static_assert( - !std::is_pointer::value, - "Please do not use raw pointers as expected value, " - "use smart pointers instead. See CppCoreGuidelines for explanation. " - "https://github.com/isocpp/CppCoreGuidelines/blob/master/" - "CppCoreGuidelines.md#Rf-unique_ptr"); - static_assert(!std::is_reference::value, - "Expected does not support reference as a value type"); - static_assert(std::is_enum::value, - "ErrorCodeEnumType template parameter must be enum"); - - public: - Expected(ValueType value) : object_{std::move(value)} {} - - Expected(ErrorType error) : object_{std::move(error)} {} - - explicit Expected(ErrorCodeEnumType code, std::string message) - : object_{ErrorType(code, message)} {} - - Expected() = delete; - Expected(ErrorBase* error) = delete; - - Expected(Expected&& other) - : object_(std::move(other.object_)), errorChecked_(other.errorChecked_) { - other.errorChecked_.set(true); - } - - Expected& operator=(Expected&& other) { - if (this != &other) { - errorChecked_.verify("Expected was not checked before assigning"); - - object_ = std::move(other.object_); - errorChecked_ = other.errorChecked_; - other.errorChecked_.set(true); - } - return *this; - } - - Expected(const Expected&) = delete; - Expected& operator=(const Expected& other) = delete; - - ~Expected() { - errorChecked_.verify("Expected was not checked before destruction"); - } - - static SelfType success(ValueType value) { - return SelfType{std::move(value)}; - } - - static SelfType failure(std::string message) { - auto defaultCode = ErrorCodeEnumType{}; - return SelfType(defaultCode, std::move(message)); - } - - static SelfType failure(ErrorCodeEnumType code, std::string message) { - return SelfType(code, std::move(message)); - } - - ErrorType takeError() && = delete; - ErrorType takeError() & { - verifyIsError(); - return std::move(boost::get(object_)); - } - - const ErrorType& getError() const&& = delete; - const ErrorType& getError() const& { - verifyIsError(); - return boost::get(object_); - } - - ErrorCodeEnumType getErrorCode() const&& = delete; - ErrorCodeEnumType getErrorCode() const& { - return getError().getErrorCode(); - } - - bool isError() const noexcept { - errorChecked_.set(true); - return object_.which() == kErrorType_; - } - - void ignoreResult() const noexcept { - errorChecked_.set(true); - } - - bool isValue() const noexcept { - return !isError(); - } - - explicit operator bool() const noexcept { - return isValue(); - } - - ValueType& get() && = delete; - ValueType& get() & { - verifyIsValue(); - return boost::get(object_); - } - - const ValueType& get() const&& = delete; - const ValueType& get() const& { - verifyIsValue(); - return boost::get(object_); - } - - ValueType take() && = delete; - ValueType take() & { - return std::move(get()); - } - - template - typename std::enable_if< - std::is_same::type, - ValueType>::value, - ValueType>::type - takeOr(ValueTypeUniversal&& defaultValue) { - if (isError()) { - return std::forward(defaultValue); - } - return std::move(get()); - } - - ValueType* operator->() && = delete; - ValueType* operator->() & { - return &get(); - } - - const ValueType* operator->() const&& = delete; - const ValueType* operator->() const& { - return &get(); - } - - ValueType& operator*() && = delete; - ValueType& operator*() & { - return get(); - } - - const ValueType& operator*() const&& = delete; - const ValueType& operator*() const& { - return get(); - } - - private: - inline void verifyIsError() const { - debug_only::verify([this]() { return object_.which() == kErrorType_; }, - "Do not try to get error from Expected with value"); - } - - inline void verifyIsValue() const { - debug_only::verify([this]() { return object_.which() == kValueType_; }, - "Do not try to get value from Expected with error"); - } - - private: - boost::variant object_; - enum ETypeId { - kValueType_ = 0, - kErrorType_ = 1, - }; - debug_only::Var errorChecked_ = false; +public: + using ValueType = ValueType_; + using ErrorType = Error; + using SelfType = Expected; + + static_assert( + !std::is_pointer::value, + "Please do not use raw pointers as expected value, " + "use smart pointers instead. See CppCoreGuidelines for explanation. " + "https://github.com/isocpp/CppCoreGuidelines/blob/master/" + "CppCoreGuidelines.md#Rf-unique_ptr"); + static_assert(!std::is_reference::value, + "Expected does not support reference as a value type"); + static_assert(std::is_enum::value, + "ErrorCodeEnumType template parameter must be enum"); + +public: + Expected(ValueType value) : object_{std::move(value)} {} + + Expected(ErrorType error) : object_{std::move(error)} {} + + explicit Expected(ErrorCodeEnumType code, std::string message) + : object_{ErrorType(code, message)} {} + + Expected() = delete; + Expected(ErrorBase* error) = delete; + + Expected(Expected&& other) + : object_(std::move(other.object_)), errorChecked_(other.errorChecked_) + { + other.errorChecked_.set(true); + } + + Expected& operator=(Expected&& other) + { + if (this != &other) { + errorChecked_.verify("Expected was not checked before assigning"); + + object_ = std::move(other.object_); + errorChecked_ = other.errorChecked_; + other.errorChecked_.set(true); + } + return *this; + } + + Expected(const Expected&) = delete; + Expected& operator=(const Expected& other) = delete; + + ~Expected() + { + errorChecked_.verify("Expected was not checked before destruction"); + } + + static SelfType success(ValueType value) + { + return SelfType{std::move(value)}; + } + + static SelfType failure(std::string message) + { + auto defaultCode = ErrorCodeEnumType{}; + return SelfType(defaultCode, std::move(message)); + } + + static SelfType failure(ErrorCodeEnumType code, std::string message) + { + return SelfType(code, std::move(message)); + } + + ErrorType takeError()&& = delete; + ErrorType takeError()& { + verifyIsError(); + return std::move(boost::get(object_)); + } + + const ErrorType& getError() const&& = delete; + const ErrorType& getError() const& + { + verifyIsError(); + return boost::get(object_); + } + + ErrorCodeEnumType getErrorCode() const&& = delete; + ErrorCodeEnumType getErrorCode() const& + { + return getError().getErrorCode(); + } + + bool isError() const noexcept + { + errorChecked_.set(true); + return object_.which() == kErrorType_; + } + + void ignoreResult() const noexcept + { + errorChecked_.set(true); + } + + bool isValue() const noexcept + { + return !isError(); + } + + explicit operator bool() const noexcept + { + return isValue(); + } + + ValueType& get()&& = delete; + ValueType& get()& { + verifyIsValue(); + return boost::get(object_); + } + + const ValueType& get() const&& = delete; + const ValueType& get() const& + { + verifyIsValue(); + return boost::get(object_); + } + + ValueType take()&& = delete; + ValueType take()& { + return std::move(get()); + } + + template + typename std::enable_if < + std::is_same::type, + ValueType>::value, + ValueType >::type + takeOr(ValueTypeUniversal && defaultValue) + { + if (isError()) { + return std::forward(defaultValue); + } + return std::move(get()); + } + + ValueType* operator->()&& = delete; + ValueType* operator->()& { + return &get(); + } + + const ValueType* operator->() const&& = delete; + const ValueType* operator->() const& + { + return &get(); + } + + ValueType& operator*()&& = delete; + ValueType& operator*()& { + return get(); + } + + const ValueType& operator*() const&& = delete; + const ValueType& operator*() const& + { + return get(); + } + +private: + inline void verifyIsError() const + { + debug_only::verify([this]() { + return object_.which() == kErrorType_; + }, + "Do not try to get error from Expected with value"); + } + + inline void verifyIsValue() const + { + debug_only::verify([this]() { + return object_.which() == kValueType_; + }, + "Do not try to get value from Expected with error"); + } + +private: + boost::variant object_; + enum ETypeId { + kValueType_ = 0, + kErrorType_ = 1, + }; + debug_only::Var errorChecked_ = false; }; template diff --git a/src/osquery/utils/expected/tests/expected.cpp b/src/osquery/utils/expected/tests/expected.cpp index 24d62a3..168c0a8 100644 --- a/src/osquery/utils/expected/tests/expected.cpp +++ b/src/osquery/utils/expected/tests/expected.cpp @@ -17,301 +17,331 @@ namespace osquery { enum class TestError { - Some, - Another, - Semantic, - Logical, - Runtime, + Some, + Another, + Semantic, + Logical, + Runtime, }; -GTEST_TEST(ExpectedTest, success_contructor_initialization) { - Expected value = std::string("Test"); - EXPECT_TRUE(value); - EXPECT_TRUE(value.isValue()); - EXPECT_FALSE(value.isError()); - EXPECT_EQ(value.get(), "Test"); +GTEST_TEST(ExpectedTest, success_contructor_initialization) +{ + Expected value = std::string("Test"); + EXPECT_TRUE(value); + EXPECT_TRUE(value.isValue()); + EXPECT_FALSE(value.isError()); + EXPECT_EQ(value.get(), "Test"); } -GTEST_TEST(ExpectedTest, failure_error_contructor_initialization) { - Expected error = - Error(TestError::Some, "Please try again"); - EXPECT_FALSE(error); - EXPECT_FALSE(error.isValue()); - EXPECT_TRUE(error.isError()); - EXPECT_EQ(error.getErrorCode(), TestError::Some); +GTEST_TEST(ExpectedTest, failure_error_contructor_initialization) +{ + Expected error = + Error(TestError::Some, "Please try again"); + EXPECT_FALSE(error); + EXPECT_FALSE(error.isValue()); + EXPECT_TRUE(error.isError()); + EXPECT_EQ(error.getErrorCode(), TestError::Some); } -bool stringContains(const std::string& what, const std::string& where) { - return boost::contains(what, where); +bool stringContains(const std::string& what, const std::string& where) +{ + return boost::contains(what, where); }; -GTEST_TEST(ExpectedTest, failure_error_str_contructor_initialization) { - const auto msg = - std::string{"\"#$%&'()*+,-./089:;<[=]>\" is it a valid error message?"}; - auto expected = Expected::failure(msg); - EXPECT_FALSE(expected); - EXPECT_TRUE(expected.isError()); - EXPECT_EQ(expected.getErrorCode(), TestError::Some); - auto fullMsg = expected.getError().getMessage(); - EXPECT_PRED2(stringContains, fullMsg, msg); +GTEST_TEST(ExpectedTest, failure_error_str_contructor_initialization) +{ + const auto msg = + std::string{"\"#$%&'()*+,-./089:;<[=]>\" is it a valid error message?"}; + auto expected = Expected::failure(msg); + EXPECT_FALSE(expected); + EXPECT_TRUE(expected.isError()); + EXPECT_EQ(expected.getErrorCode(), TestError::Some); + auto fullMsg = expected.getError().getMessage(); + EXPECT_PRED2(stringContains, fullMsg, msg); } -osquery::ExpectedUnique testFunction() { - return std::make_unique("Test"); +osquery::ExpectedUnique testFunction() +{ + return std::make_unique("Test"); } -GTEST_TEST(ExpectedTest, ExpectedSharedTestFunction) { - osquery::Expected, TestError> sharedPointer = - std::make_shared("Test"); - EXPECT_TRUE(sharedPointer); - EXPECT_EQ(**sharedPointer, "Test"); - - osquery::ExpectedShared sharedPointer2 = - std::make_shared("Test"); - EXPECT_TRUE(sharedPointer2); - EXPECT_EQ(**sharedPointer2, "Test"); +GTEST_TEST(ExpectedTest, ExpectedSharedTestFunction) +{ + osquery::Expected, TestError> sharedPointer = + std::make_shared("Test"); + EXPECT_TRUE(sharedPointer); + EXPECT_EQ(**sharedPointer, "Test"); + + osquery::ExpectedShared sharedPointer2 = + std::make_shared("Test"); + EXPECT_TRUE(sharedPointer2); + EXPECT_EQ(**sharedPointer2, "Test"); } -GTEST_TEST(ExpectedTest, ExpectedUniqueTestFunction) { - auto uniquePointer = testFunction(); - EXPECT_TRUE(uniquePointer); - EXPECT_EQ(**uniquePointer, "Test"); +GTEST_TEST(ExpectedTest, ExpectedUniqueTestFunction) +{ + auto uniquePointer = testFunction(); + EXPECT_TRUE(uniquePointer); + EXPECT_EQ(**uniquePointer, "Test"); } -GTEST_TEST(ExpectedTest, ExpectedSharedWithError) { - osquery::ExpectedShared error = - Error(TestError::Another, "Some message"); - EXPECT_FALSE(error); - EXPECT_EQ(error.getErrorCode(), TestError::Another); +GTEST_TEST(ExpectedTest, ExpectedSharedWithError) +{ + osquery::ExpectedShared error = + Error(TestError::Another, "Some message"); + EXPECT_FALSE(error); + EXPECT_EQ(error.getErrorCode(), TestError::Another); } -GTEST_TEST(ExpectedTest, ExpectedOptional) { - boost::optional optional = std::string("123"); - osquery::Expected, TestError> optionalExpected = - optional; - EXPECT_TRUE(optionalExpected); - EXPECT_EQ(**optionalExpected, "123"); +GTEST_TEST(ExpectedTest, ExpectedOptional) +{ + boost::optional optional = std::string("123"); + osquery::Expected, TestError> optionalExpected = + optional; + EXPECT_TRUE(optionalExpected); + EXPECT_EQ(**optionalExpected, "123"); } template using LocalExpected = Expected; -GTEST_TEST(ExpectedTest, createError_example) { - auto giveMeDozen = [](bool valid) -> LocalExpected { - if (valid) { - return 50011971; - } - return createError(TestError::Logical) - << "an error message is supposed to be here"; - }; - auto v = giveMeDozen(true); - EXPECT_TRUE(v); - ASSERT_FALSE(v.isError()); - EXPECT_EQ(*v, 50011971); - - auto errV = giveMeDozen(false); - EXPECT_FALSE(errV); - ASSERT_TRUE(errV.isError()); - EXPECT_EQ(errV.getErrorCode(), TestError::Logical); +GTEST_TEST(ExpectedTest, createError_example) +{ + auto giveMeDozen = [](bool valid) -> LocalExpected { + if (valid) + { + return 50011971; + } + return createError(TestError::Logical) + << "an error message is supposed to be here"; + }; + auto v = giveMeDozen(true); + EXPECT_TRUE(v); + ASSERT_FALSE(v.isError()); + EXPECT_EQ(*v, 50011971); + + auto errV = giveMeDozen(false); + EXPECT_FALSE(errV); + ASSERT_TRUE(errV.isError()); + EXPECT_EQ(errV.getErrorCode(), TestError::Logical); } -GTEST_TEST(ExpectedTest, ExpectedSuccess_example) { - auto giveMeStatus = [](bool valid) -> ExpectedSuccess { - if (valid) { - return Success{}; - } - return Error(TestError::Runtime, - "an error message is supposed to be here"); - }; - auto s = giveMeStatus(true); - EXPECT_TRUE(s); - ASSERT_FALSE(s.isError()); - - auto errS = giveMeStatus(false); - EXPECT_FALSE(errS); - ASSERT_TRUE(errS.isError()); +GTEST_TEST(ExpectedTest, ExpectedSuccess_example) +{ + auto giveMeStatus = [](bool valid) -> ExpectedSuccess { + if (valid) + { + return Success{}; + } + return Error(TestError::Runtime, + "an error message is supposed to be here"); + }; + auto s = giveMeStatus(true); + EXPECT_TRUE(s); + ASSERT_FALSE(s.isError()); + + auto errS = giveMeStatus(false); + EXPECT_FALSE(errS); + ASSERT_TRUE(errS.isError()); } -GTEST_TEST(ExpectedTest, nested_errors_example) { - const auto msg = std::string{"Write a good error message"}; - auto firstFailureSource = [&msg]() -> Expected, TestError> { - return createError(TestError::Semantic) << msg; - }; - auto giveMeNestedError = [&]() -> Expected, TestError> { - auto ret = firstFailureSource(); - ret.isError(); - return createError(TestError::Runtime, ret.takeError()) << msg; - }; - auto ret = giveMeNestedError(); - EXPECT_FALSE(ret); - ASSERT_TRUE(ret.isError()); - EXPECT_EQ(ret.getErrorCode(), TestError::Runtime); - ASSERT_TRUE(ret.getError().hasUnderlyingError()); - EXPECT_PRED2(stringContains, ret.getError().getMessage(), msg); +GTEST_TEST(ExpectedTest, nested_errors_example) +{ + const auto msg = std::string{"Write a good error message"}; + auto firstFailureSource = [&msg]() -> Expected, TestError> { + return createError(TestError::Semantic) << msg; + }; + auto giveMeNestedError = [&]() -> Expected, TestError> { + auto ret = firstFailureSource(); + ret.isError(); + return createError(TestError::Runtime, ret.takeError()) << msg; + }; + auto ret = giveMeNestedError(); + EXPECT_FALSE(ret); + ASSERT_TRUE(ret.isError()); + EXPECT_EQ(ret.getErrorCode(), TestError::Runtime); + ASSERT_TRUE(ret.getError().hasUnderlyingError()); + EXPECT_PRED2(stringContains, ret.getError().getMessage(), msg); } -GTEST_TEST(ExpectedTest, error_handling_example) { - auto failureSource = []() -> Expected, TestError> { - return createError(TestError::Runtime) << "Test error message ()*+,-."; - }; - auto ret = failureSource(); - if (ret.isError()) { - switch (ret.getErrorCode()) { - case TestError::Some: - case TestError::Another: - case TestError::Semantic: - case TestError::Logical: - FAIL() << "There is must be a Runtime type of error"; - case TestError::Runtime: - SUCCEED(); - } - } else { - FAIL() << "There is must be an error"; - } +GTEST_TEST(ExpectedTest, error_handling_example) +{ + auto failureSource = []() -> Expected, TestError> { + return createError(TestError::Runtime) << "Test error message ()*+,-."; + }; + auto ret = failureSource(); + if (ret.isError()) { + switch (ret.getErrorCode()) { + case TestError::Some: + case TestError::Another: + case TestError::Semantic: + case TestError::Logical: + FAIL() << "There is must be a Runtime type of error"; + case TestError::Runtime: + SUCCEED(); + } + } else { + FAIL() << "There is must be an error"; + } } -GTEST_TEST(ExpectedTest, expected_was_not_checked_before_destruction_failure) { - auto action = []() { auto expected = ExpectedSuccess{Success()}; }; +GTEST_TEST(ExpectedTest, expected_was_not_checked_before_destruction_failure) +{ + auto action = []() { + auto expected = ExpectedSuccess {Success()}; + }; #ifndef NDEBUG - ASSERT_DEATH(action(), "Expected was not checked before destruction"); + ASSERT_DEATH(action(), "Expected was not checked before destruction"); #else - boost::ignore_unused(action); + boost::ignore_unused(action); #endif } -GTEST_TEST(ExpectedTest, expected_was_not_checked_before_assigning_failure) { - auto action = []() { - auto expected = ExpectedSuccess{Success()}; - expected = ExpectedSuccess{Success()}; - expected.isValue(); - }; +GTEST_TEST(ExpectedTest, expected_was_not_checked_before_assigning_failure) +{ + auto action = []() { + auto expected = ExpectedSuccess {Success()}; + expected = ExpectedSuccess {Success()}; + expected.isValue(); + }; #ifndef NDEBUG - ASSERT_DEATH(action(), "Expected was not checked before assigning"); + ASSERT_DEATH(action(), "Expected was not checked before assigning"); #else - boost::ignore_unused(action); + boost::ignore_unused(action); #endif } -GTEST_TEST(ExpectedTest, expected_move_is_safe) { - auto expected = ExpectedSuccess{Success()}; - expected.isValue(); - expected = ExpectedSuccess{Success()}; - expected.isValue(); +GTEST_TEST(ExpectedTest, expected_move_is_safe) +{ + auto expected = ExpectedSuccess {Success()}; + expected.isValue(); + expected = ExpectedSuccess {Success()}; + expected.isValue(); } -GTEST_TEST(ExpectedTest, get_value_from_expected_with_error) { - auto action = []() { - auto expected = Expected(TestError::Logical, - "Test error message @#$0k+Qh"); - auto value = expected.get(); - boost::ignore_unused(value); - }; +GTEST_TEST(ExpectedTest, get_value_from_expected_with_error) +{ + auto action = []() { + auto expected = Expected(TestError::Logical, + "Test error message @#$0k+Qh"); + auto value = expected.get(); + boost::ignore_unused(value); + }; #ifndef NDEBUG - ASSERT_DEATH(action(), "Do not try to get value from Expected with error"); + ASSERT_DEATH(action(), "Do not try to get value from Expected with error"); #else - boost::ignore_unused(action); + boost::ignore_unused(action); #endif } -GTEST_TEST(ExpectedTest, const_get_value_from_expected_with_error) { - auto action = []() { - const auto expected = Expected( - TestError::Semantic, "Test error message {}()[]."); - auto value = expected.get(); - boost::ignore_unused(value); - }; +GTEST_TEST(ExpectedTest, const_get_value_from_expected_with_error) +{ + auto action = []() { + const auto expected = Expected( + TestError::Semantic, "Test error message {}()[]."); + auto value = expected.get(); + boost::ignore_unused(value); + }; #ifndef NDEBUG - ASSERT_DEATH(action(), "Do not try to get value from Expected with error"); + ASSERT_DEATH(action(), "Do not try to get value from Expected with error"); #else - boost::ignore_unused(action); + boost::ignore_unused(action); #endif } -GTEST_TEST(ExpectedTest, take_value_from_expected_with_error) { - auto action = []() { - auto expected = Expected(TestError::Semantic, - "Test error message !&^?<>."); - auto value = expected.take(); - boost::ignore_unused(value); - }; +GTEST_TEST(ExpectedTest, take_value_from_expected_with_error) +{ + auto action = []() { + auto expected = Expected(TestError::Semantic, + "Test error message !&^?<>."); + auto value = expected.take(); + boost::ignore_unused(value); + }; #ifndef NDEBUG - ASSERT_DEATH(action(), "Do not try to get value from Expected with error"); + ASSERT_DEATH(action(), "Do not try to get value from Expected with error"); #else - boost::ignore_unused(action); + boost::ignore_unused(action); #endif } -GTEST_TEST(ExpectedTest, get_error_from_expected_with_value) { - auto action = []() { - auto expected = Expected(228); - const auto& error = expected.getError(); - boost::ignore_unused(error); - }; +GTEST_TEST(ExpectedTest, get_error_from_expected_with_value) +{ + auto action = []() { + auto expected = Expected(228); + const auto& error = expected.getError(); + boost::ignore_unused(error); + }; #ifndef NDEBUG - ASSERT_DEATH(action(), "Do not try to get error from Expected with value"); + ASSERT_DEATH(action(), "Do not try to get error from Expected with value"); #else - boost::ignore_unused(action); + boost::ignore_unused(action); #endif } -GTEST_TEST(ExpectedTest, take_error_from_expected_with_value) { - auto action = []() { - auto expected = Expected(228); - return expected.takeError(); - }; +GTEST_TEST(ExpectedTest, take_error_from_expected_with_value) +{ + auto action = []() { + auto expected = Expected(228); + return expected.takeError(); + }; #ifndef NDEBUG - ASSERT_DEATH(action(), "Do not try to get error from Expected with value"); + ASSERT_DEATH(action(), "Do not try to get error from Expected with value"); #else - boost::ignore_unused(action); + boost::ignore_unused(action); #endif } -GTEST_TEST(ExpectedTest, value__takeOr) { - const auto text = std::string{"some text"}; - auto callable = [&text]() -> Expected { - return text; - }; - auto expected = callable(); - EXPECT_EQ(expected ? expected.take() : std::string{"default text"}, text); +GTEST_TEST(ExpectedTest, value__takeOr) +{ + const auto text = std::string{"some text"}; + auto callable = [&text]() -> Expected { + return text; + }; + auto expected = callable(); + EXPECT_EQ(expected ? expected.take() : std::string{"default text"}, text); - EXPECT_EQ(callable().takeOr(std::string{"default text"}), text); + EXPECT_EQ(callable().takeOr(std::string{"default text"}), text); } -GTEST_TEST(ExpectedTest, error__takeOr) { - auto expected = - Expected(TestError::Semantic, "error message"); - EXPECT_EQ(expected.takeOr(std::string{"default text"}), "default text"); +GTEST_TEST(ExpectedTest, error__takeOr) +{ + auto expected = + Expected(TestError::Semantic, "error message"); + EXPECT_EQ(expected.takeOr(std::string{"default text"}), "default text"); } -GTEST_TEST(ExpectedTest, error__takeOr_with_user_defined_class) { - class SomeTestClass { - public: - explicit SomeTestClass(const std::string& prefix, const std::string& sufix) - : text{prefix + " - " + sufix} {} - - std::string text; - }; - auto callable = []() -> Expected { - return createError(TestError::Semantic) << "error message"; - }; - EXPECT_EQ(callable().takeOr(SomeTestClass("427 BC", "347 BC")).text, - "427 BC - 347 BC"); +GTEST_TEST(ExpectedTest, error__takeOr_with_user_defined_class) +{ + class SomeTestClass { + public: + explicit SomeTestClass(const std::string& prefix, const std::string& sufix) + : text{prefix + " - " + sufix} {} + + std::string text; + }; + auto callable = []() -> Expected { + return createError(TestError::Semantic) << "error message"; + }; + EXPECT_EQ(callable().takeOr(SomeTestClass("427 BC", "347 BC")).text, + "427 BC - 347 BC"); } -GTEST_TEST(ExpectedTest, value_takeOr_with_rvalue_as_an_argument) { - auto value = int{312}; - auto callable = []() -> Expected { return 306; }; - value = callable().takeOr(value); - EXPECT_EQ(value, 306); +GTEST_TEST(ExpectedTest, value_takeOr_with_rvalue_as_an_argument) +{ + auto value = int{312}; + auto callable = []() -> Expected { return 306; }; + value = callable().takeOr(value); + EXPECT_EQ(value, 306); } -GTEST_TEST(ExpectedTest, error_takeOr_with_rvalue_as_an_argument) { - auto value = int{312}; - auto callable = []() -> Expected { - return createError(TestError::Logical) << "error message"; - }; - value = callable().takeOr(value); - EXPECT_EQ(value, 312); +GTEST_TEST(ExpectedTest, error_takeOr_with_rvalue_as_an_argument) +{ + auto value = int{312}; + auto callable = []() -> Expected { + return createError(TestError::Logical) << "error message"; + }; + value = callable().takeOr(value); + EXPECT_EQ(value, 312); } } // namespace osquery diff --git a/src/osquery/utils/only_movable.h b/src/osquery/utils/only_movable.h index 55f3f4d..72bd550 100644 --- a/src/osquery/utils/only_movable.h +++ b/src/osquery/utils/only_movable.h @@ -17,25 +17,25 @@ namespace osquery { * their's as default. */ class only_movable { - protected: - /// Boilerplate self default constructor. - only_movable() = default; +protected: + /// Boilerplate self default constructor. + only_movable() = default; - /// Boilerplate self destructor. - ~only_movable() = default; + /// Boilerplate self destructor. + ~only_movable() = default; - /// Boilerplate move constructor. - only_movable(only_movable&&) noexcept = default; + /// Boilerplate move constructor. + only_movable(only_movable&&) noexcept = default; - /// Boilerplate move assignment. - only_movable& operator=(only_movable&&) = default; + /// Boilerplate move assignment. + only_movable& operator=(only_movable&&) = default; - public: - /// Important, a private copy constructor prevents copying. - only_movable(const only_movable&) = delete; +public: + /// Important, a private copy constructor prevents copying. + only_movable(const only_movable&) = delete; - /// Important, a private copy assignment constructor prevents copying. - only_movable& operator=(const only_movable&) = delete; + /// Important, a private copy assignment constructor prevents copying. + only_movable& operator=(const only_movable&) = delete; }; } // namespace osquery diff --git a/src/osquery/utils/status/status.cpp b/src/osquery/utils/status/status.cpp index 725f5d1..fa27967 100644 --- a/src/osquery/utils/status/status.cpp +++ b/src/osquery/utils/status/status.cpp @@ -14,15 +14,17 @@ namespace osquery { constexpr int Status::kSuccessCode; -Status Status::failure(int code, std::string message) { - assert(code != Status::kSuccessCode && - "Using 'failure' to create Status object with a kSuccessCode"); - return Status(code, std::move(message)); +Status Status::failure(int code, std::string message) +{ + assert(code != Status::kSuccessCode && + "Using 'failure' to create Status object with a kSuccessCode"); + return Status(code, std::move(message)); } -::std::ostream& operator<<(::std::ostream& os, const Status& s) { - return os << "Status(" << s.getCode() << R"(, ")" << s.getMessage() - << R"("))"; +::std::ostream& operator<<(::std::ostream& os, const Status& s) +{ + return os << "Status(" << s.getCode() << R"(, ")" << s.getMessage() + << R"("))"; } } // namespace osquery diff --git a/src/osquery/utils/status/status.h b/src/osquery/utils/status/status.h index f78f0c1..d0bcb26 100644 --- a/src/osquery/utils/status/status.h +++ b/src/osquery/utils/status/status.h @@ -31,141 +31,152 @@ namespace osquery { */ class Status { - public: - static constexpr int kSuccessCode = 0; - /** - * @brief Default constructor - * - * Note that the default constructor initialized an osquery::Status instance - * to a state such that a successful operation is indicated. - */ - explicit Status(int c = Status::kSuccessCode) : code_(c), message_("OK") {} - - /** - * @brief A constructor which can be used to concisely express the status of - * an operation. - * - * @param c a status code. The idiom is that a zero status code indicates a - * successful operation and a non-zero status code indicates a failed - * operation. - * @param m a message indicating some extra detail regarding the operation. - * If all operations were successful, this message should be "OK". - * Otherwise, it doesn't matter what the string is, as long as both the - * setter and caller agree. - */ - Status(int c, std::string m) : code_(c), message_(std::move(m)) {} - - Status(const ErrorBase& error) : code_(1), message_(error.getMessage()) {} - - public: - /** - * @brief A getter for the status code property - * - * @return an integer representing the status code of the operation. - */ - int getCode() const { - return code_; - } - - /** - * @brief A getter for the message property - * - * @return a string representing arbitrary additional information about the - * success or failure of an operation. On successful operations, the idiom - * is for the message to be "OK" - */ - std::string getMessage() const { - return message_; - } - - /** - * @brief A convenience method to check if the return code is - * Status::kSuccessCode - * - * @code{.cpp} - * auto s = doSomething(); - * if (s.ok()) { - * LOG(INFO) << "doing work"; - * } else { - * LOG(ERROR) << s.toString(); - * } - * @endcode - * - * @return a boolean which is true if the status code is Status::kSuccessCode, - * false otherwise. - */ - bool ok() const { - return getCode() == Status::kSuccessCode; - } - - /** - * @brief A synonym for osquery::Status::getMessage() - * - * @see getMessage() - */ - std::string toString() const { - return getMessage(); - } - std::string what() const { - return getMessage(); - } - - /** - * @brief implicit conversion to bool - * - * Allows easy use of Status in an if statement, as below: - * - * @code{.cpp} - * if (doSomethingThatReturnsStatus()) { - * LOG(INFO) << "Success!"; - * } - * @endcode - */ - /* explicit */ explicit operator bool() const { - return ok(); - } - - static Status success() { - return Status(kSuccessCode); - } - - static Status failure(std::string message) { - return Status(1, std::move(message)); - } - - static Status failure(int code, std::string message); - - // Below operator implementations useful for testing with gtest - - // Enables use of gtest (ASSERT|EXPECT)_EQ - bool operator==(const Status& rhs) const { - return (code_ == rhs.getCode()) && (message_ == rhs.getMessage()); - } - - // Enables use of gtest (ASSERT|EXPECT)_NE - bool operator!=(const Status& rhs) const { - return !operator==(rhs); - } - - // Enables pretty-printing in gtest (ASSERT|EXPECT)_(EQ|NE) - friend ::std::ostream& operator<<(::std::ostream& os, const Status& s); - - private: - /// the internal storage of the status code - int code_; - - /// the internal storage of the status message - std::string message_; +public: + static constexpr int kSuccessCode = 0; + /** + * @brief Default constructor + * + * Note that the default constructor initialized an osquery::Status instance + * to a state such that a successful operation is indicated. + */ + explicit Status(int c = Status::kSuccessCode) : code_(c), message_("OK") {} + + /** + * @brief A constructor which can be used to concisely express the status of + * an operation. + * + * @param c a status code. The idiom is that a zero status code indicates a + * successful operation and a non-zero status code indicates a failed + * operation. + * @param m a message indicating some extra detail regarding the operation. + * If all operations were successful, this message should be "OK". + * Otherwise, it doesn't matter what the string is, as long as both the + * setter and caller agree. + */ + Status(int c, std::string m) : code_(c), message_(std::move(m)) {} + + Status(const ErrorBase& error) : code_(1), message_(error.getMessage()) {} + +public: + /** + * @brief A getter for the status code property + * + * @return an integer representing the status code of the operation. + */ + int getCode() const + { + return code_; + } + + /** + * @brief A getter for the message property + * + * @return a string representing arbitrary additional information about the + * success or failure of an operation. On successful operations, the idiom + * is for the message to be "OK" + */ + std::string getMessage() const + { + return message_; + } + + /** + * @brief A convenience method to check if the return code is + * Status::kSuccessCode + * + * @code{.cpp} + * auto s = doSomething(); + * if (s.ok()) { + * LOG(INFO) << "doing work"; + * } else { + * LOG(ERROR) << s.toString(); + * } + * @endcode + * + * @return a boolean which is true if the status code is Status::kSuccessCode, + * false otherwise. + */ + bool ok() const + { + return getCode() == Status::kSuccessCode; + } + + /** + * @brief A synonym for osquery::Status::getMessage() + * + * @see getMessage() + */ + std::string toString() const + { + return getMessage(); + } + std::string what() const + { + return getMessage(); + } + + /** + * @brief implicit conversion to bool + * + * Allows easy use of Status in an if statement, as below: + * + * @code{.cpp} + * if (doSomethingThatReturnsStatus()) { + * LOG(INFO) << "Success!"; + * } + * @endcode + */ + /* explicit */ explicit operator bool() const + { + return ok(); + } + + static Status success() + { + return Status(kSuccessCode); + } + + static Status failure(std::string message) + { + return Status(1, std::move(message)); + } + + static Status failure(int code, std::string message); + + // Below operator implementations useful for testing with gtest + + // Enables use of gtest (ASSERT|EXPECT)_EQ + bool operator==(const Status& rhs) const + { + return (code_ == rhs.getCode()) && (message_ == rhs.getMessage()); + } + + // Enables use of gtest (ASSERT|EXPECT)_NE + bool operator!=(const Status& rhs) const + { + return !operator==(rhs); + } + + // Enables pretty-printing in gtest (ASSERT|EXPECT)_(EQ|NE) + friend ::std::ostream& operator<<(::std::ostream& os, const Status& s); + +private: + /// the internal storage of the status code + int code_; + + /// the internal storage of the status message + std::string message_; }; ::std::ostream& operator<<(::std::ostream& os, const Status& s); template inline - typename std::enable_if::value, Status>::type - to(const Expected& expected) { - return expected ? Status::success() - : Status::failure(expected.getError().getMessage()); +typename std::enable_if::value, Status>::type +to(const Expected& expected) +{ + return expected ? Status::success() + : Status::failure(expected.getError().getMessage()); } } // namespace osquery diff --git a/src/osquery/utils/status/tests/status.cpp b/src/osquery/utils/status/tests/status.cpp index 99f599c..5b4211e 100644 --- a/src/osquery/utils/status/tests/status.cpp +++ b/src/osquery/utils/status/tests/status.cpp @@ -16,89 +16,102 @@ namespace osquery { class StatusTests : public testing::Test {}; -TEST_F(StatusTests, test_constructor) { - auto s = Status(5, "message"); - EXPECT_EQ(s.getCode(), 5); - EXPECT_EQ(s.getMessage(), "message"); +TEST_F(StatusTests, test_constructor) +{ + auto s = Status(5, "message"); + EXPECT_EQ(s.getCode(), 5); + EXPECT_EQ(s.getMessage(), "message"); } -TEST_F(StatusTests, test_constructor_2) { - Status s; - EXPECT_EQ(s.getCode(), 0); - EXPECT_EQ(s.getMessage(), "OK"); +TEST_F(StatusTests, test_constructor_2) +{ + Status s; + EXPECT_EQ(s.getCode(), 0); + EXPECT_EQ(s.getMessage(), "OK"); } -TEST_F(StatusTests, test_ok) { - auto s1 = Status(5, "message"); - EXPECT_FALSE(s1.ok()); - auto s2 = Status(0, "message"); - EXPECT_TRUE(s2.ok()); +TEST_F(StatusTests, test_ok) +{ + auto s1 = Status(5, "message"); + EXPECT_FALSE(s1.ok()); + auto s2 = Status(0, "message"); + EXPECT_TRUE(s2.ok()); } -TEST_F(StatusTests, test_to_string) { - auto s = Status(0, "foobar"); - EXPECT_EQ(s.toString(), "foobar"); +TEST_F(StatusTests, test_to_string) +{ + auto s = Status(0, "foobar"); + EXPECT_EQ(s.toString(), "foobar"); } -TEST_F(StatusTests, test_default_constructor) { - auto s = Status{}; - EXPECT_TRUE(s.ok()); +TEST_F(StatusTests, test_default_constructor) +{ + auto s = Status{}; + EXPECT_TRUE(s.ok()); } -TEST_F(StatusTests, test_success_code) { - auto s = Status(Status::kSuccessCode); - EXPECT_TRUE(s.ok()); +TEST_F(StatusTests, test_success_code) +{ + auto s = Status(Status::kSuccessCode); + EXPECT_TRUE(s.ok()); } -TEST_F(StatusTests, test_success) { - auto s = Status::success(); - EXPECT_TRUE(s.ok()); +TEST_F(StatusTests, test_success) +{ + auto s = Status::success(); + EXPECT_TRUE(s.ok()); } -TEST_F(StatusTests, test_failure_single_arg) { - auto s = Status::failure("Some proper error message."); - EXPECT_EQ(s.toString(), "Some proper error message."); - EXPECT_FALSE(s.ok()); +TEST_F(StatusTests, test_failure_single_arg) +{ + auto s = Status::failure("Some proper error message."); + EXPECT_EQ(s.toString(), "Some proper error message."); + EXPECT_FALSE(s.ok()); } -TEST_F(StatusTests, test_failure_double_arg) { - auto s = Status::failure(105, "One more proper error message!"); - EXPECT_EQ(s.toString(), "One more proper error message!"); - EXPECT_FALSE(s.ok()); +TEST_F(StatusTests, test_failure_double_arg) +{ + auto s = Status::failure(105, "One more proper error message!"); + EXPECT_EQ(s.toString(), "One more proper error message!"); + EXPECT_FALSE(s.ok()); } -TEST_F(StatusTests, test_failure_with_success_code) { +TEST_F(StatusTests, test_failure_with_success_code) +{ #ifndef NDEBUG - ASSERT_DEATH(Status::failure(Status::kSuccessCode, "message"), - "Using 'failure' to create Status object with a kSuccessCode"); + ASSERT_DEATH(Status::failure(Status::kSuccessCode, "message"), + "Using 'failure' to create Status object with a kSuccessCode"); #endif } namespace { enum class TestError { - Semantic = 1, + Semantic = 1, }; -bool stringContains(const std::string& where, const std::string& what) { - return boost::contains(where, what); +bool stringContains(const std::string& where, const std::string& what) +{ + return boost::contains(where, what); }; } // namespace -TEST_F(StatusTests, test_expected_to_status_failure) { - const auto expected = Expected( - TestError::Semantic, "The ultimate failure reason"); - auto s = to(expected); - EXPECT_FALSE(s.ok()); - EXPECT_PRED2(stringContains, s.toString(), "The ultimate failure reason"); +TEST_F(StatusTests, test_expected_to_status_failure) +{ + const auto expected = Expected( + TestError::Semantic, "The ultimate failure reason"); + auto s = to(expected); + EXPECT_FALSE(s.ok()); + EXPECT_PRED2(stringContains, s.toString(), "The ultimate failure reason"); } -TEST_F(StatusTests, test_expected_to_status_success) { - const auto expected = - Expected("This is not a failure"); - auto s = to(expected); - EXPECT_TRUE(s.ok()); - EXPECT_EQ(s, Status::success()); +TEST_F(StatusTests, test_expected_to_status_success) +{ + const auto expected = + Expected("This is not a failure"); + auto s = to(expected); + EXPECT_TRUE(s.ok()); + EXPECT_EQ(s, Status::success()); } } diff --git a/src/osquery/utils/system/posix/env.cpp b/src/osquery/utils/system/posix/env.cpp index c86b502..6e878bc 100644 --- a/src/osquery/utils/system/posix/env.cpp +++ b/src/osquery/utils/system/posix/env.cpp @@ -16,22 +16,25 @@ namespace osquery { -bool setEnvVar(const std::string& name, const std::string& value) { - auto ret = ::setenv(name.c_str(), value.c_str(), 1); - return (ret == 0); +bool setEnvVar(const std::string& name, const std::string& value) +{ + auto ret = ::setenv(name.c_str(), value.c_str(), 1); + return (ret == 0); } -bool unsetEnvVar(const std::string& name) { - auto ret = ::unsetenv(name.c_str()); - return (ret == 0); +bool unsetEnvVar(const std::string& name) +{ + auto ret = ::unsetenv(name.c_str()); + return (ret == 0); } -boost::optional getEnvVar(const std::string& name) { - char* value = ::getenv(name.c_str()); - if (value) { - return std::string(value); - } - return boost::none; +boost::optional getEnvVar(const std::string& name) +{ + char* value = ::getenv(name.c_str()); + if (value) { + return std::string(value); + } + return boost::none; } } // namespace osquery diff --git a/src/osquery/utils/system/posix/errno.cpp b/src/osquery/utils/system/posix/errno.cpp index aa82abd..d86ce2e 100644 --- a/src/osquery/utils/system/posix/errno.cpp +++ b/src/osquery/utils/system/posix/errno.cpp @@ -15,37 +15,39 @@ namespace osquery { -std::string platformStrerr(int errnum) { - return ::strerror(errnum); +std::string platformStrerr(int errnum) +{ + return ::strerror(errnum); } namespace impl { -PosixError toPosixSystemError(int from_errno) { - static auto const table = std::unordered_map{ - {EPERM, PosixError::PERM}, {ENOENT, PosixError::NOENT}, - {ESRCH, PosixError::SRCH}, {EINTR, PosixError::INTR}, - {EIO, PosixError::IO}, {ENXIO, PosixError::NXIO}, - {E2BIG, PosixError::T_BIG}, {ENOEXEC, PosixError::NOEXEC}, - {EBADF, PosixError::BADF}, {ECHILD, PosixError::CHILD}, - {EAGAIN, PosixError::AGAIN}, {ENOMEM, PosixError::NOMEM}, - {EACCES, PosixError::ACCES}, {EFAULT, PosixError::FAULT}, - {ENOTBLK, PosixError::NOTBLK}, {EBUSY, PosixError::BUSY}, - {EEXIST, PosixError::EXIST}, {EXDEV, PosixError::XDEV}, - {ENODEV, PosixError::NODEV}, {ENOTDIR, PosixError::NOTDIR}, - {EISDIR, PosixError::ISDIR}, {EINVAL, PosixError::INVAL}, - {ENFILE, PosixError::NFILE}, {EMFILE, PosixError::MFILE}, - {ENOTTY, PosixError::NOTTY}, {ETXTBSY, PosixError::TXTBSY}, - {EFBIG, PosixError::FBIG}, {ENOSPC, PosixError::NOSPC}, - {ESPIPE, PosixError::SPIPE}, {EROFS, PosixError::ROFS}, - {EMLINK, PosixError::MLINK}, {EPIPE, PosixError::PIPE}, - {EDOM, PosixError::DOM}, {ERANGE, PosixError::RANGE}, - }; - auto const it = table.find(from_errno); - if (it == table.end()) { - return PosixError::Unknown; - } - return it->second; +PosixError toPosixSystemError(int from_errno) +{ + static auto const table = std::unordered_map { + {EPERM, PosixError::PERM}, {ENOENT, PosixError::NOENT}, + {ESRCH, PosixError::SRCH}, {EINTR, PosixError::INTR}, + {EIO, PosixError::IO}, {ENXIO, PosixError::NXIO}, + {E2BIG, PosixError::T_BIG}, {ENOEXEC, PosixError::NOEXEC}, + {EBADF, PosixError::BADF}, {ECHILD, PosixError::CHILD}, + {EAGAIN, PosixError::AGAIN}, {ENOMEM, PosixError::NOMEM}, + {EACCES, PosixError::ACCES}, {EFAULT, PosixError::FAULT}, + {ENOTBLK, PosixError::NOTBLK}, {EBUSY, PosixError::BUSY}, + {EEXIST, PosixError::EXIST}, {EXDEV, PosixError::XDEV}, + {ENODEV, PosixError::NODEV}, {ENOTDIR, PosixError::NOTDIR}, + {EISDIR, PosixError::ISDIR}, {EINVAL, PosixError::INVAL}, + {ENFILE, PosixError::NFILE}, {EMFILE, PosixError::MFILE}, + {ENOTTY, PosixError::NOTTY}, {ETXTBSY, PosixError::TXTBSY}, + {EFBIG, PosixError::FBIG}, {ENOSPC, PosixError::NOSPC}, + {ESPIPE, PosixError::SPIPE}, {EROFS, PosixError::ROFS}, + {EMLINK, PosixError::MLINK}, {EPIPE, PosixError::PIPE}, + {EDOM, PosixError::DOM}, {ERANGE, PosixError::RANGE}, + }; + auto const it = table.find(from_errno); + if (it == table.end()) { + return PosixError::Unknown; + } + return it->second; } } // namespace impl diff --git a/src/osquery/utils/system/posix/errno.h b/src/osquery/utils/system/posix/errno.h index f5c285d..43eb0af 100644 --- a/src/osquery/utils/system/posix/errno.h +++ b/src/osquery/utils/system/posix/errno.h @@ -15,41 +15,41 @@ namespace osquery { enum class PosixError { - Unknown = 0, - PERM = EPERM, - NOENT = ENOENT, - SRCH = ESRCH, - INTR = EINTR, - IO = EIO, - NXIO = ENXIO, - T_BIG = E2BIG, - NOEXEC = ENOEXEC, - BADF = EBADF, - CHILD = ECHILD, - AGAIN = EAGAIN, - NOMEM = ENOMEM, - ACCES = EACCES, - FAULT = EFAULT, - NOTBLK = ENOTBLK, - BUSY = EBUSY, - EXIST = EEXIST, - XDEV = EXDEV, - NODEV = ENODEV, - NOTDIR = ENOTDIR, - ISDIR = EISDIR, - INVAL = EINVAL, - NFILE = ENFILE, - MFILE = EMFILE, - NOTTY = ENOTTY, - TXTBSY = ETXTBSY, - FBIG = EFBIG, - NOSPC = ENOSPC, - SPIPE = ESPIPE, - ROFS = EROFS, - MLINK = EMLINK, - PIPE = EPIPE, - DOM = EDOM, - RANGE = ERANGE, + Unknown = 0, + PERM = EPERM, + NOENT = ENOENT, + SRCH = ESRCH, + INTR = EINTR, + IO = EIO, + NXIO = ENXIO, + T_BIG = E2BIG, + NOEXEC = ENOEXEC, + BADF = EBADF, + CHILD = ECHILD, + AGAIN = EAGAIN, + NOMEM = ENOMEM, + ACCES = EACCES, + FAULT = EFAULT, + NOTBLK = ENOTBLK, + BUSY = EBUSY, + EXIST = EEXIST, + XDEV = EXDEV, + NODEV = ENODEV, + NOTDIR = ENOTDIR, + ISDIR = EISDIR, + INVAL = EINVAL, + NFILE = ENFILE, + MFILE = EMFILE, + NOTTY = ENOTTY, + TXTBSY = ETXTBSY, + FBIG = EFBIG, + NOSPC = ENOSPC, + SPIPE = ESPIPE, + ROFS = EROFS, + MLINK = EMLINK, + PIPE = EPIPE, + DOM = EDOM, + RANGE = ERANGE, }; namespace impl { @@ -60,9 +60,10 @@ PosixError toPosixSystemError(int from_errno); template inline typename std::enable_if::value, - PosixError>::type -to(int from_errno) { - return impl::toPosixSystemError(from_errno); + PosixError>::type + to(int from_errno) +{ + return impl::toPosixSystemError(from_errno); } } // namespace osquery diff --git a/src/osquery/utils/system/posix/filepath.cpp b/src/osquery/utils/system/posix/filepath.cpp index d017dcb..0a8c51a 100644 --- a/src/osquery/utils/system/posix/filepath.cpp +++ b/src/osquery/utils/system/posix/filepath.cpp @@ -15,32 +15,33 @@ namespace osquery { -const std::string canonicalize_file_name(const char* name) { +const std::string canonicalize_file_name(const char* name) +{ #ifdef PATH_MAX - // On supported platforms where PATH_MAX is defined we can pass null - // as buffer, and allow libc to alloced space - // On centos/ubuntu libc will use realloc so we will be able to resolve - // any path - // On darwin libc will allocate PATH_MAX buffer for us - char* resolved = realpath(name, nullptr); - std::string result = (resolved == nullptr) ? name : resolved; - free(resolved); + // On supported platforms where PATH_MAX is defined we can pass null + // as buffer, and allow libc to alloced space + // On centos/ubuntu libc will use realloc so we will be able to resolve + // any path + // On darwin libc will allocate PATH_MAX buffer for us + char* resolved = realpath(name, nullptr); + std::string result = (resolved == nullptr) ? name : resolved; + free(resolved); #else #warning PATH_MAX is undefined, please read comment below - // PATH_MAX is not defined, very likely it's not officially supported - // os, our best guess is _PC_PATH_MAX if available - // In case of failure fallback to "safe" buffer of 8K + // PATH_MAX is not defined, very likely it's not officially supported + // os, our best guess is _PC_PATH_MAX if available + // In case of failure fallback to "safe" buffer of 8K - long int path_max = pathconf(name, _PC_PATH_MAX); - if (path_max <= 0) { - path_max = 8 * 1024; - } - char* buffer = static_cast(malloc(path_max)); - char* resolved = realpath(name, buffer); - std::string result = (resolved == nullptr) ? name : resolved; - free(buffer); + long int path_max = pathconf(name, _PC_PATH_MAX); + if (path_max <= 0) { + path_max = 8 * 1024; + } + char* buffer = static_cast(malloc(path_max)); + char* resolved = realpath(name, buffer); + std::string result = (resolved == nullptr) ? name : resolved; + free(buffer); #endif - return result; + return result; } } // namespace osquery diff --git a/src/osquery/utils/system/posix/tests/errno.cpp b/src/osquery/utils/system/posix/tests/errno.cpp index af2ce82..c08d045 100644 --- a/src/osquery/utils/system/posix/tests/errno.cpp +++ b/src/osquery/utils/system/posix/tests/errno.cpp @@ -17,16 +17,17 @@ namespace { class PosixErrnoTests : public testing::Test {}; -TEST_F(PosixErrnoTests, to) { - EXPECT_EQ(PosixError::Unknown, to(0)); - EXPECT_EQ(PosixError::Unknown, to(-1)); - EXPECT_EQ(PosixError::Unknown, to(98765)); - EXPECT_EQ(PosixError::Unknown, to(987654)); - - EXPECT_EQ(PosixError::PIPE, to(EPIPE)); - EXPECT_EQ(PosixError::DOM, to(EDOM)); - EXPECT_EQ(PosixError::RANGE, to(ERANGE)); - EXPECT_EQ(PosixError::T_BIG, to(E2BIG)); +TEST_F(PosixErrnoTests, to) +{ + EXPECT_EQ(PosixError::Unknown, to(0)); + EXPECT_EQ(PosixError::Unknown, to(-1)); + EXPECT_EQ(PosixError::Unknown, to(98765)); + EXPECT_EQ(PosixError::Unknown, to(987654)); + + EXPECT_EQ(PosixError::PIPE, to(EPIPE)); + EXPECT_EQ(PosixError::DOM, to(EDOM)); + EXPECT_EQ(PosixError::RANGE, to(ERANGE)); + EXPECT_EQ(PosixError::T_BIG, to(E2BIG)); } } // namespace diff --git a/src/osquery/utils/system/posix/time.cpp b/src/osquery/utils/system/posix/time.cpp index fa3d889..24fbfa1 100644 --- a/src/osquery/utils/system/posix/time.cpp +++ b/src/osquery/utils/system/posix/time.cpp @@ -12,14 +12,15 @@ namespace osquery { -std::string platformAsctime(const struct tm* timeptr) { - if (timeptr == nullptr) { - return ""; - } +std::string platformAsctime(const struct tm* timeptr) +{ + if (timeptr == nullptr) { + return ""; + } - // Manual says at least 26 characters. - char buffer[32] = {0}; - return ::asctime_r(timeptr, buffer); + // Manual says at least 26 characters. + char buffer[32] = {0}; + return ::asctime_r(timeptr, buffer); } } // namespace osquery diff --git a/src/osquery/utils/system/tests/time.cpp b/src/osquery/utils/system/tests/time.cpp index c5f366d..c668362 100644 --- a/src/osquery/utils/system/tests/time.cpp +++ b/src/osquery/utils/system/tests/time.cpp @@ -14,21 +14,23 @@ namespace osquery { class TimeTests : public testing::Test {}; -TEST_F(TimeTests, test_asciitime) { - const std::time_t epoch = 1491518994; - const std::string expected = "Thu Apr 6 22:49:54 2017 UTC"; +TEST_F(TimeTests, test_asciitime) +{ + const std::time_t epoch = 1491518994; + const std::string expected = "Thu Apr 6 22:49:54 2017 UTC"; - auto const result = std::gmtime(&epoch); + auto const result = std::gmtime(&epoch); - EXPECT_EQ(expected, toAsciiTime(result)); + EXPECT_EQ(expected, toAsciiTime(result)); } -TEST_F(TimeTests, test_asciitimeutc) { - const std::time_t epoch = 1491518994; - const std::string expected = "Thu Apr 6 22:49:54 2017 UTC"; +TEST_F(TimeTests, test_asciitimeutc) +{ + const std::time_t epoch = 1491518994; + const std::string expected = "Thu Apr 6 22:49:54 2017 UTC"; - auto const result = std::localtime(&epoch); + auto const result = std::localtime(&epoch); - EXPECT_EQ(expected, toAsciiTimeUTC(result)); + EXPECT_EQ(expected, toAsciiTimeUTC(result)); } } diff --git a/src/osquery/utils/system/time.cpp b/src/osquery/utils/system/time.cpp index 73abaa2..f45ae5c 100644 --- a/src/osquery/utils/system/time.cpp +++ b/src/osquery/utils/system/time.cpp @@ -15,58 +15,63 @@ namespace osquery { -std::string toAsciiTime(const struct tm* tm_time) { - if (tm_time == nullptr) { - return ""; - } - - auto time_str = platformAsctime(tm_time); - boost::algorithm::trim(time_str); - return time_str + " UTC"; +std::string toAsciiTime(const struct tm* tm_time) +{ + if (tm_time == nullptr) { + return ""; + } + + auto time_str = platformAsctime(tm_time); + boost::algorithm::trim(time_str); + return time_str + " UTC"; } -std::string toAsciiTimeUTC(const struct tm* tm_time) { - size_t epoch = toUnixTime(tm_time); - struct tm tptr; +std::string toAsciiTimeUTC(const struct tm* tm_time) +{ + size_t epoch = toUnixTime(tm_time); + struct tm tptr; - std::memset(&tptr, 0, sizeof(tptr)); + std::memset(&tptr, 0, sizeof(tptr)); - if (epoch == (size_t)-1) { - return ""; - } + if (epoch == (size_t) -1) { + return ""; + } #ifdef OSQUERY_WINDOWS - _gmtime64_s(&tptr, (time_t*)&epoch); + _gmtime64_s(&tptr, (time_t*)&epoch); #else - gmtime_r((time_t*)&epoch, &tptr); + gmtime_r((time_t*)&epoch, &tptr); #endif - return toAsciiTime(&tptr); + return toAsciiTime(&tptr); } -std::string getAsciiTime() { - auto result = std::time(nullptr); +std::string getAsciiTime() +{ + auto result = std::time(nullptr); - struct tm now; + struct tm now; #ifdef OSQUERY_WINDOWS - _gmtime64_s(&now, &result); + _gmtime64_s(&now, &result); #else - gmtime_r(&result, &now); + gmtime_r(&result, &now); #endif - return toAsciiTime(&now); + return toAsciiTime(&now); } -size_t toUnixTime(const struct tm* tm_time) { - struct tm result; - std::memset(&result, 0, sizeof(result)); +size_t toUnixTime(const struct tm* tm_time) +{ + struct tm result; + std::memset(&result, 0, sizeof(result)); - std::memcpy(&result, tm_time, sizeof(result)); - return mktime(&result); + std::memcpy(&result, tm_time, sizeof(result)); + return mktime(&result); } -size_t getUnixTime() { - std::time_t ut = std::time(nullptr); - return ut < 0 ? 0 : ut; +size_t getUnixTime() +{ + std::time_t ut = std::time(nullptr); + return ut < 0 ? 0 : ut; } } // namespace osquery