mirror of
https://github.com/valitydev/osquery-1.git
synced 2024-11-07 01:55:20 +00:00
Introduce generator/yield-style virtual tables (#3060)
This commit is contained in:
parent
cea5981182
commit
1e71f4aab8
@ -516,11 +516,6 @@ class RegistryFactory : private boost::noncopyable {
|
||||
static Status call(const std::string& registry_name,
|
||||
const PluginRequest& request);
|
||||
|
||||
/// A helper call optimized for table data generation.
|
||||
static Status callTable(const std::string& table_name,
|
||||
QueryContext& context,
|
||||
PluginResponse& response);
|
||||
|
||||
/// Run `setUp` on every registry that is not marked 'lazy'.
|
||||
static void setUp();
|
||||
|
||||
|
@ -23,31 +23,6 @@ namespace osquery {
|
||||
|
||||
DECLARE_int32(value_max);
|
||||
|
||||
/**
|
||||
* @brief An abstract similar to boost's noncopyable that defines moves.
|
||||
*
|
||||
* By defining protected move constructors we allow the children to assign
|
||||
* their's as default.
|
||||
*/
|
||||
class only_movable {
|
||||
protected:
|
||||
/// Boilerplate self default constructor.
|
||||
only_movable() {}
|
||||
|
||||
/// Boilerplate self destructor.
|
||||
~only_movable() {}
|
||||
|
||||
/// Important, existance of a move constructor.
|
||||
only_movable(only_movable&&) {}
|
||||
|
||||
private:
|
||||
/// Important, a private copy constructor prevents copying.
|
||||
only_movable(const only_movable&);
|
||||
|
||||
/// Important, a private copy assignment constructor prevents copying.
|
||||
only_movable& operator=(const only_movable&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The core interface to executing osquery SQL commands.
|
||||
*
|
||||
|
@ -17,6 +17,12 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#ifdef WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <boost/coroutine2/all.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
@ -36,6 +42,31 @@
|
||||
|
||||
namespace osquery {
|
||||
|
||||
/**
|
||||
* @brief An abstract similar to boost's noncopyable that defines moves.
|
||||
*
|
||||
* By defining protected move constructors we allow the children to assign
|
||||
* their's as default.
|
||||
*/
|
||||
class only_movable {
|
||||
protected:
|
||||
/// Boilerplate self default constructor.
|
||||
only_movable() {}
|
||||
|
||||
/// Boilerplate self destructor.
|
||||
~only_movable() {}
|
||||
|
||||
/// Important, existance of a move constructor.
|
||||
only_movable(only_movable&&) {}
|
||||
|
||||
private:
|
||||
/// Important, a private copy constructor prevents copying.
|
||||
only_movable(const only_movable&);
|
||||
|
||||
/// Important, a private copy assignment constructor prevents copying.
|
||||
only_movable& operator=(const only_movable&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief osquery does not yet use a NULL type.
|
||||
*
|
||||
@ -492,11 +523,14 @@ struct VirtualTableContent {
|
||||
std::map<std::string, Row> cache;
|
||||
};
|
||||
|
||||
using RowGenerator = boost::coroutines2::coroutine<Row&>;
|
||||
using RowYield = RowGenerator::push_type;
|
||||
|
||||
/**
|
||||
* @brief A QueryContext is provided to every table generator for optimization
|
||||
* on query components like predicate constraints and limits.
|
||||
*/
|
||||
struct QueryContext : private boost::noncopyable {
|
||||
struct QueryContext : private only_movable {
|
||||
/// Construct a context without cache support.
|
||||
QueryContext() : enable_cache_(false), table_(new VirtualTableContent()) {}
|
||||
|
||||
@ -511,6 +545,12 @@ struct QueryContext : private boost::noncopyable {
|
||||
explicit QueryContext(VirtualTableContent* content)
|
||||
: enable_cache_(true), table_(content) {}
|
||||
|
||||
/// Allow moving.
|
||||
QueryContext(QueryContext&&) = default;
|
||||
|
||||
/// Allow move assignment.
|
||||
QueryContext& operator=(QueryContext&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Check if a constraint exists for a given column operator pair.
|
||||
*
|
||||
@ -542,9 +582,9 @@ struct QueryContext : private boost::noncopyable {
|
||||
* @param predicate A predicate receiving each expression.
|
||||
*/
|
||||
template <typename T>
|
||||
void forEachConstraint(const std::string& column,
|
||||
ConstraintOperator op,
|
||||
std::function<void(const T& expr)> predicate) const {
|
||||
void iteritems(const std::string& column,
|
||||
ConstraintOperator op,
|
||||
std::function<void(const T& expr)> predicate) const {
|
||||
if (constraints.count(column) > 0) {
|
||||
const auto& list = constraints.at(column);
|
||||
if (list.affinity == TEXT_TYPE) {
|
||||
@ -563,11 +603,10 @@ struct QueryContext : private boost::noncopyable {
|
||||
}
|
||||
|
||||
/// Helper for string type (most all types are TEXT/VARCHAR).
|
||||
void forEachConstraint(
|
||||
const std::string& column,
|
||||
ConstraintOperator op,
|
||||
std::function<void(const std::string& expr)> predicate) const {
|
||||
return forEachConstraint<std::string>(column, op, predicate);
|
||||
void iteritems(const std::string& column,
|
||||
ConstraintOperator op,
|
||||
std::function<void(const std::string& expr)> predicate) const {
|
||||
return iteritems<std::string>(column, op, predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -650,7 +689,7 @@ using Constraint = struct Constraint;
|
||||
* in osquery/tables/templates/default.cpp.in
|
||||
*/
|
||||
class TablePlugin : public Plugin {
|
||||
protected:
|
||||
public:
|
||||
/**
|
||||
* @brief Table name aliases create full-scan VIEWs for tables.
|
||||
*
|
||||
@ -695,10 +734,37 @@ class TablePlugin : public Plugin {
|
||||
* @param request A query context filled in by SQLite's virtual table API.
|
||||
* @return The result rows for this table, given the query context.
|
||||
*/
|
||||
virtual QueryData generate(QueryContext& request) {
|
||||
virtual QueryData generate(QueryContext& context) {
|
||||
return QueryData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate a table representation by yielding each row.
|
||||
*
|
||||
* For tables that set generator=True in their spec's implementation, this
|
||||
* generator will be bound to an asymmetric coroutine. It should call the
|
||||
* provided yield function for each Row returned. Treat this like Python's
|
||||
* generator-type methods where the only difference is yield is not reserved
|
||||
* but rather provided with some boilerplate syntax.
|
||||
*
|
||||
* This implementation uses nearly %5 more cycles than the generate method
|
||||
* when the table content is small (less than 100 rows) and has a disadvantage
|
||||
* of not being cachable since the entire contents are not available before
|
||||
* post-filter aggregations. This implementation prevents the need for
|
||||
* multiple representations of table content existing simultaneously and is
|
||||
* always more memory efficient. It can be more compute efficient for tables
|
||||
* with over 1000 rows.
|
||||
*
|
||||
* @param yield a callable that takes a single Row as input.
|
||||
* @param context a query context filled in by SQLite's virtual table API.
|
||||
*/
|
||||
virtual void generator(RowYield& yield, QueryContext& context) {}
|
||||
|
||||
/// Override and return true to use the generator and yield method.
|
||||
virtual bool usesGenerator() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
/// An SQL table containing the table definition/syntax.
|
||||
std::string columnDefinition() const;
|
||||
@ -810,6 +876,7 @@ class TablePlugin : public Plugin {
|
||||
FRIEND_TEST(VirtualTableTests, test_tableplugin_columndefinition);
|
||||
FRIEND_TEST(VirtualTableTests, test_tableplugin_statement);
|
||||
FRIEND_TEST(VirtualTableTests, test_indexing_costs);
|
||||
FRIEND_TEST(VirtualTableTests, test_yield_generator);
|
||||
};
|
||||
|
||||
/// Helper method to generate the virtual table CREATE statement.
|
||||
|
@ -47,6 +47,7 @@ if(WINDOWS)
|
||||
ADD_OSQUERY_LINK_CORE("libboost_system-vc140-mt-s-1_63")
|
||||
ADD_OSQUERY_LINK_CORE("libboost_regex-vc140-mt-s-1_63")
|
||||
ADD_OSQUERY_LINK_CORE("libboost_filesystem-vc140-mt-s-1_63")
|
||||
ADD_OSQUERY_LINK_CORE("libboost_context-vc140-mt-s-1_63")
|
||||
ADD_OSQUERY_LINK_CORE("rocksdblib")
|
||||
ADD_OSQUERY_LINK_CORE("snappy64")
|
||||
ADD_OSQUERY_LINK_CORE("gflags_static")
|
||||
@ -71,6 +72,8 @@ if(APPLE OR LINUX)
|
||||
ADD_OSQUERY_LINK_CORE("libdl")
|
||||
ADD_OSQUERY_LINK_CORE("boost_system-mt")
|
||||
ADD_OSQUERY_LINK_CORE("boost_filesystem-mt")
|
||||
ADD_OSQUERY_LINK_CORE("boost_context-mt")
|
||||
|
||||
ADD_OSQUERY_LINK_ADDITIONAL("rocksdb_lite")
|
||||
ADD_OSQUERY_LINK_ADDITIONAL("boost_regex-mt")
|
||||
elseif(FREEBSD)
|
||||
@ -78,6 +81,8 @@ elseif(FREEBSD)
|
||||
ADD_OSQUERY_LINK_CORE("boost_system")
|
||||
ADD_OSQUERY_LINK_CORE("boost_filesystem")
|
||||
ADD_OSQUERY_LINK_CORE("boost_thread")
|
||||
ADD_OSQUERY_LINK_CORE("boost_context")
|
||||
|
||||
ADD_OSQUERY_LINK_ADDITIONAL("rocksdb")
|
||||
ADD_OSQUERY_LINK_ADDITIONAL("boost_regex")
|
||||
endif()
|
||||
|
@ -447,23 +447,6 @@ Status RegistryFactory::call(const std::string& registry_name,
|
||||
return call(registry_name, request, response);
|
||||
}
|
||||
|
||||
Status RegistryFactory::callTable(const std::string& table_name,
|
||||
QueryContext& context,
|
||||
PluginResponse& response) {
|
||||
auto& tables = get().registry("table")->items_;
|
||||
// This only works for local tables.
|
||||
if (tables.count(table_name) > 0) {
|
||||
auto plugin = std::dynamic_pointer_cast<TablePlugin>(tables.at(table_name));
|
||||
response = plugin->generate(context);
|
||||
return Status(0);
|
||||
} else {
|
||||
// If the table is not local then it does not benefit from complex contexts.
|
||||
PluginRequest request = {{"action", "generate"}};
|
||||
TablePlugin::setRequestFromContext(context, request);
|
||||
return call("table", table_name, request, response);
|
||||
}
|
||||
}
|
||||
|
||||
Status RegistryFactory::setActive(const std::string& registry_name,
|
||||
const std::string& item_name) {
|
||||
WriteLock lock(mutex_);
|
||||
|
@ -20,7 +20,7 @@
|
||||
namespace osquery {
|
||||
|
||||
class BenchmarkTablePlugin : public TablePlugin {
|
||||
private:
|
||||
protected:
|
||||
TableColumns columns() const {
|
||||
return {
|
||||
std::make_tuple("test_int", INTEGER_TYPE, ColumnOptions::DEFAULT),
|
||||
@ -36,6 +36,28 @@ class BenchmarkTablePlugin : public TablePlugin {
|
||||
}
|
||||
};
|
||||
|
||||
class BenchmarkTableYieldPlugin : public BenchmarkTablePlugin {
|
||||
public:
|
||||
bool usesGenerator() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void generator(RowYield& yield, QueryContext& ctx) override {
|
||||
{
|
||||
Row r;
|
||||
r["test_int"] = "0";
|
||||
yield(r);
|
||||
}
|
||||
|
||||
{
|
||||
Row r;
|
||||
r["test_int"] = "0";
|
||||
r["test_text"] = "hello";
|
||||
yield(r);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void SQL_virtual_table_registry(benchmark::State& state) {
|
||||
// Add a sample virtual table plugin.
|
||||
// Profile calling the plugin's column data.
|
||||
@ -57,17 +79,38 @@ static void SQL_virtual_table_internal(benchmark::State& state) {
|
||||
Registry::call("table", "benchmark", {{"action", "columns"}}, res);
|
||||
|
||||
// Attach a sample virtual table.
|
||||
auto dbc = SQLiteDBManager::get();
|
||||
auto dbc = SQLiteDBManager::getUnique();
|
||||
attachTableInternal("benchmark", columnDefinition(res), dbc);
|
||||
|
||||
while (state.KeepRunning()) {
|
||||
QueryData results;
|
||||
queryInternal("select * from benchmark", results, dbc->db());
|
||||
dbc->clearAffectedTables();
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(SQL_virtual_table_internal);
|
||||
|
||||
static void SQL_virtual_table_internal_yield(benchmark::State& state) {
|
||||
auto tables = RegistryFactory::get().registry("table");
|
||||
tables->add("benchmark_yield", std::make_shared<BenchmarkTableYieldPlugin>());
|
||||
|
||||
PluginResponse res;
|
||||
Registry::call("table", "benchmark_yield", {{"action", "columns"}}, res);
|
||||
|
||||
// Attach a sample virtual table.
|
||||
auto dbc = SQLiteDBManager::getUnique();
|
||||
attachTableInternal("benchmark_yield", columnDefinition(res), dbc);
|
||||
|
||||
while (state.KeepRunning()) {
|
||||
QueryData results;
|
||||
queryInternal("select * from benchmark_yield", results, dbc->db());
|
||||
dbc->clearAffectedTables();
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(SQL_virtual_table_internal_yield);
|
||||
|
||||
static void SQL_virtual_table_internal_global(benchmark::State& state) {
|
||||
auto tables = RegistryFactory::get().registry("table");
|
||||
tables->add("benchmark", std::make_shared<BenchmarkTablePlugin>());
|
||||
@ -82,6 +125,7 @@ static void SQL_virtual_table_internal_global(benchmark::State& state) {
|
||||
|
||||
QueryData results;
|
||||
queryInternal("select * from benchmark", results, dbc->db());
|
||||
dbc->clearAffectedTables();
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,6 +145,7 @@ static void SQL_virtual_table_internal_unique(benchmark::State& state) {
|
||||
|
||||
QueryData results;
|
||||
queryInternal("select * from benchmark", results, dbc->db());
|
||||
dbc->clearAffectedTables();
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,13 +183,16 @@ static void SQL_virtual_table_internal_long(benchmark::State& state) {
|
||||
while (state.KeepRunning()) {
|
||||
QueryData results;
|
||||
queryInternal("select * from long_benchmark", results, dbc->db());
|
||||
dbc->clearAffectedTables();
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(SQL_virtual_table_internal_long);
|
||||
|
||||
size_t kWideCount{0};
|
||||
|
||||
class BenchmarkWideTablePlugin : public TablePlugin {
|
||||
private:
|
||||
protected:
|
||||
TableColumns columns() const override {
|
||||
TableColumns cols;
|
||||
for (int i = 0; i < 20; i++) {
|
||||
@ -156,7 +204,7 @@ class BenchmarkWideTablePlugin : public TablePlugin {
|
||||
|
||||
QueryData generate(QueryContext& ctx) override {
|
||||
QueryData results;
|
||||
for (int k = 0; k < 50; k++) {
|
||||
for (int k = 0; k < kWideCount; k++) {
|
||||
Row r;
|
||||
for (int i = 0; i < 20; i++) {
|
||||
r["test_" + std::to_string(i)] = "0";
|
||||
@ -167,6 +215,23 @@ class BenchmarkWideTablePlugin : public TablePlugin {
|
||||
}
|
||||
};
|
||||
|
||||
class BenchmarkWideTableYieldPlugin : public BenchmarkWideTablePlugin {
|
||||
public:
|
||||
bool usesGenerator() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void generator(RowYield& yield, QueryContext& ctx) override {
|
||||
for (int k = 0; k < kWideCount; k++) {
|
||||
Row r;
|
||||
for (int i = 0; i < 20; i++) {
|
||||
r["test_" + std::to_string(i)] = "0";
|
||||
}
|
||||
yield(r);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void SQL_virtual_table_internal_wide(benchmark::State& state) {
|
||||
auto tables = RegistryFactory::get().registry("table");
|
||||
tables->add("wide_benchmark", std::make_shared<BenchmarkWideTablePlugin>());
|
||||
@ -178,20 +243,53 @@ static void SQL_virtual_table_internal_wide(benchmark::State& state) {
|
||||
auto dbc = SQLiteDBManager::getUnique();
|
||||
attachTableInternal("wide_benchmark", columnDefinition(res), dbc);
|
||||
|
||||
kWideCount = state.range_y();
|
||||
while (state.KeepRunning()) {
|
||||
QueryData results;
|
||||
queryInternal("select * from wide_benchmark", results, dbc->db());
|
||||
dbc->clearAffectedTables();
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(SQL_virtual_table_internal_wide);
|
||||
BENCHMARK(SQL_virtual_table_internal_wide)
|
||||
->ArgPair(0, 1)
|
||||
->ArgPair(0, 10)
|
||||
->ArgPair(0, 100)
|
||||
->ArgPair(0, 1000);
|
||||
|
||||
static void SQL_virtual_table_internal_wide_yield(benchmark::State& state) {
|
||||
auto tables = RegistryFactory::get().registry("table");
|
||||
tables->add("wide_benchmark_yield",
|
||||
std::make_shared<BenchmarkWideTableYieldPlugin>());
|
||||
|
||||
PluginResponse res;
|
||||
Registry::call("table", "wide_benchmark_yield", {{"action", "columns"}}, res);
|
||||
|
||||
// Attach a sample virtual table.
|
||||
auto dbc = SQLiteDBManager::getUnique();
|
||||
attachTableInternal("wide_benchmark_yield", columnDefinition(res), dbc);
|
||||
|
||||
kWideCount = state.range_y();
|
||||
while (state.KeepRunning()) {
|
||||
QueryData results;
|
||||
queryInternal("select * from wide_benchmark_yield", results, dbc->db());
|
||||
dbc->clearAffectedTables();
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(SQL_virtual_table_internal_wide_yield)
|
||||
->ArgPair(0, 1)
|
||||
->ArgPair(0, 10)
|
||||
->ArgPair(0, 100)
|
||||
->ArgPair(0, 1000);
|
||||
|
||||
static void SQL_select_metadata(benchmark::State& state) {
|
||||
auto dbc = SQLiteDBManager::get();
|
||||
auto dbc = SQLiteDBManager::getUnique();
|
||||
while (state.KeepRunning()) {
|
||||
QueryData results;
|
||||
queryInternal(
|
||||
"select count(*) from sqlite_temp_master;", results, dbc->db());
|
||||
dbc->clearAffectedTables();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -421,6 +421,51 @@ TEST_F(VirtualTableTests, test_table_cache) {
|
||||
ASSERT_EQ(results[0]["data"], "awesome_data");
|
||||
}
|
||||
|
||||
class yieldTablePlugin : public TablePlugin {
|
||||
private:
|
||||
TableColumns columns() const override {
|
||||
return {
|
||||
std::make_tuple("index", INTEGER_TYPE, ColumnOptions::DEFAULT),
|
||||
};
|
||||
}
|
||||
|
||||
public:
|
||||
bool usesGenerator() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void generator(RowYield& yield, QueryContext& qc) override {
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
Row r;
|
||||
r["index"] = std::to_string(index_++);
|
||||
yield(r);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
size_t index_{0};
|
||||
};
|
||||
|
||||
TEST_F(VirtualTableTests, test_yield_generator) {
|
||||
auto table = std::make_shared<yieldTablePlugin>();
|
||||
auto table_registry = RegistryFactory::get().registry("table");
|
||||
table_registry->add("yield", table);
|
||||
|
||||
auto dbc = SQLiteDBManager::getUnique();
|
||||
attachTableInternal("yield", table->columnDefinition(), dbc);
|
||||
|
||||
QueryData results;
|
||||
queryInternal("SELECT * from yield", results, dbc->db());
|
||||
dbc->clearAffectedTables();
|
||||
EXPECT_EQ(results.size(), 10U);
|
||||
EXPECT_EQ(results[0]["index"], "0");
|
||||
|
||||
results.clear();
|
||||
queryInternal("SELECT * from yield", results, dbc->db());
|
||||
dbc->clearAffectedTables();
|
||||
EXPECT_EQ(results[0]["index"], "10");
|
||||
}
|
||||
|
||||
class indexIOptimizedTablePlugin : public TablePlugin {
|
||||
private:
|
||||
TableColumns columns() const override {
|
||||
@ -528,6 +573,8 @@ TEST_F(VirtualTableTests, test_indexing_costs) {
|
||||
QueryData results;
|
||||
queryInternal(
|
||||
"SELECT * from default_scan JOIN index_i using (i);", results, dbc->db());
|
||||
dbc->clearAffectedTables();
|
||||
|
||||
// We expect index_i to optimize, meaning the constraint evaluation
|
||||
// understood the marked columns and returned a low cost.
|
||||
ASSERT_EQ(1U, default_scan->scans);
|
||||
@ -540,6 +587,7 @@ TEST_F(VirtualTableTests, test_indexing_costs) {
|
||||
// The inverse should also hold, all cost evaluations will be high.
|
||||
queryInternal(
|
||||
"SELECT * from index_i JOIN default_scan using (i);", results, dbc->db());
|
||||
dbc->clearAffectedTables();
|
||||
EXPECT_EQ(10U, i->scans);
|
||||
EXPECT_EQ(1U, default_scan->scans);
|
||||
|
||||
@ -552,6 +600,7 @@ TEST_F(VirtualTableTests, test_indexing_costs) {
|
||||
"(j);",
|
||||
results,
|
||||
dbc->db());
|
||||
dbc->clearAffectedTables();
|
||||
ASSERT_EQ(1U, default_scan->scans);
|
||||
EXPECT_EQ(10U, i->scans);
|
||||
EXPECT_EQ(10U, j->scans);
|
||||
|
@ -102,6 +102,14 @@ int xClose(sqlite3_vtab_cursor* cur) {
|
||||
|
||||
int xEof(sqlite3_vtab_cursor* cur) {
|
||||
BaseCursor* pCur = (BaseCursor*)cur;
|
||||
if (pCur->uses_generator) {
|
||||
if (*pCur->generator) {
|
||||
return false;
|
||||
}
|
||||
pCur->generator = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
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.
|
||||
@ -119,6 +127,12 @@ int xDestroy(sqlite3_vtab* p) {
|
||||
|
||||
int xNext(sqlite3_vtab_cursor* cur) {
|
||||
BaseCursor* pCur = (BaseCursor*)cur;
|
||||
if (pCur->uses_generator) {
|
||||
pCur->generator->operator()();
|
||||
if (*pCur->generator) {
|
||||
pCur->current = pCur->generator->get();
|
||||
}
|
||||
}
|
||||
pCur->row++;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
@ -233,7 +247,7 @@ int xColumn(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int col) {
|
||||
// Requested column index greater than column set size.
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
if (pCur->row >= pCur->data.size()) {
|
||||
if (!pCur->uses_generator && pCur->row >= pCur->data.size()) {
|
||||
// Request row index greater than row set size.
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
@ -248,9 +262,16 @@ int xColumn(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int col) {
|
||||
pVtab->content->columns[pVtab->content->aliases.at(column_name)]);
|
||||
}
|
||||
|
||||
Row* row = nullptr;
|
||||
if (pCur->uses_generator) {
|
||||
row = &pCur->current;
|
||||
} else {
|
||||
row = &pCur->data[pCur->row];
|
||||
}
|
||||
|
||||
// Attempt to cast each xFilter-populated row/column to the SQLite type.
|
||||
const auto& value = pCur->data[pCur->row][column_name];
|
||||
if (pCur->data[pCur->row].count(column_name) == 0) {
|
||||
const auto& value = (*row)[column_name];
|
||||
if (row->count(column_name) == 0) {
|
||||
// Missing content.
|
||||
VLOG(1) << "Error " << column_name << " is empty";
|
||||
sqlite3_result_null(ctx);
|
||||
@ -465,8 +486,8 @@ static int xFilter(sqlite3_vtab_cursor* pVtabCursor,
|
||||
// Constraints failed.
|
||||
}
|
||||
|
||||
// Evaluate index and optimized constratint requirements.
|
||||
// These are satisfied regarless of expression content availability.
|
||||
// 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.
|
||||
@ -506,7 +527,27 @@ static int xFilter(sqlite3_vtab_cursor* pVtabCursor,
|
||||
|
||||
// Generate the row data set.
|
||||
plan("Scanning rows for cursor (" + std::to_string(pCur->id) + ")");
|
||||
Registry::callTable(pVtab->content->name, context, pCur->data);
|
||||
if (Registry::get().exists("table", pVtab->content->name, true)) {
|
||||
auto plugin = Registry::get().plugin("table", pVtab->content->name);
|
||||
auto table = std::dynamic_pointer_cast<TablePlugin>(plugin);
|
||||
if (table->usesGenerator()) {
|
||||
pCur->uses_generator = true;
|
||||
pCur->generator = std::make_unique<RowGenerator::pull_type>(
|
||||
std::bind(&TablePlugin::generator,
|
||||
table,
|
||||
std::placeholders::_1,
|
||||
std::move(context)));
|
||||
if (*pCur->generator) {
|
||||
pCur->current = pCur->generator->get();
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
pCur->data = table->generate(context);
|
||||
} else {
|
||||
PluginRequest request = {{"action", "generate"}};
|
||||
TablePlugin::setRequestFromContext(context, request);
|
||||
Registry::call("table", pVtab->content->name, request, pCur->data);
|
||||
}
|
||||
|
||||
// Set the number of rows.
|
||||
pCur->n = pCur->data.size();
|
||||
|
@ -35,6 +35,7 @@ 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;
|
||||
|
||||
@ -44,6 +45,15 @@ struct BaseCursor : private boost::noncopyable {
|
||||
/// Table data generated from last access.
|
||||
QueryData data;
|
||||
|
||||
/// Callable generator.
|
||||
std::unique_ptr<RowGenerator::pull_type> generator{nullptr};
|
||||
|
||||
/// Results of current call.
|
||||
Row current;
|
||||
|
||||
/// Does the backing local table use a generator type.
|
||||
bool uses_generator{false};
|
||||
|
||||
/// Current cursor position.
|
||||
size_t row{0};
|
||||
|
||||
|
@ -526,12 +526,11 @@ QueryData genSMCKeys(QueryContext &context) {
|
||||
|
||||
// If the query is requesting an SMC key by name within the predicate.
|
||||
if (context.hasConstraint("key", EQUALS)) {
|
||||
context.forEachConstraint("key",
|
||||
EQUALS,
|
||||
([&smc, &results](const std::string &expr) {
|
||||
bool hidden = (kSMCHiddenKeys.count(expr) > 0);
|
||||
genSMCKey(expr, smc, results, hidden);
|
||||
}));
|
||||
context.iteritems(
|
||||
"key", EQUALS, ([&smc, &results](const std::string& expr) {
|
||||
bool hidden = (kSMCHiddenKeys.count(expr) > 0);
|
||||
genSMCKey(expr, smc, results, hidden);
|
||||
}));
|
||||
return results;
|
||||
}
|
||||
|
||||
@ -575,7 +574,7 @@ inline QueryData getSMCKeysUsingPredicate(
|
||||
});
|
||||
|
||||
if (context.hasConstraint("key", EQUALS)) {
|
||||
context.forEachConstraint("key", EQUALS, wrapped);
|
||||
context.iteritems("key", EQUALS, wrapped);
|
||||
} else {
|
||||
// Perform a full scan of the keys category.
|
||||
for (const auto &key : keys) {
|
||||
|
@ -18,11 +18,11 @@ namespace tables {
|
||||
QueryData usersFromContext(const QueryContext& context, bool all) {
|
||||
QueryData users;
|
||||
if (context.hasConstraint("uid", EQUALS)) {
|
||||
context.forEachConstraint(
|
||||
"uid", EQUALS, ([&users](const std::string& expr) {
|
||||
auto user = SQL::selectAllFrom("users", "uid", EQUALS, expr);
|
||||
users.insert(users.end(), user.begin(), user.end());
|
||||
}));
|
||||
context.iteritems("uid", EQUALS, ([&users](const std::string& expr) {
|
||||
auto user =
|
||||
SQL::selectAllFrom("users", "uid", EQUALS, expr);
|
||||
users.insert(users.end(), user.begin(), user.end());
|
||||
}));
|
||||
} else if (!all) {
|
||||
users = SQL::selectAllFrom(
|
||||
"users", "uid", EQUALS, std::to_string(platformGetUid()));
|
||||
@ -35,11 +35,11 @@ QueryData usersFromContext(const QueryContext& context, bool all) {
|
||||
QueryData pidsFromContext(const QueryContext& context, bool all) {
|
||||
QueryData procs;
|
||||
if (context.hasConstraint("pid", EQUALS)) {
|
||||
context.forEachConstraint(
|
||||
"pid", EQUALS, ([&procs](const std::string& expr) {
|
||||
auto proc = SQL::selectAllFrom("processes", "pid", EQUALS, expr);
|
||||
procs.insert(procs.end(), procs.begin(), procs.end());
|
||||
}));
|
||||
context.iteritems("pid", EQUALS, ([&procs](const std::string& expr) {
|
||||
auto proc = SQL::selectAllFrom(
|
||||
"processes", "pid", EQUALS, expr);
|
||||
procs.insert(procs.end(), procs.begin(), procs.end());
|
||||
}));
|
||||
} else if (!all) {
|
||||
procs = SQL::selectAllFrom(
|
||||
"processes", "pid", EQUALS, std::to_string(platformGetPid()));
|
||||
|
@ -183,6 +183,7 @@ class TableState(Singleton):
|
||||
self.fuzz_paths = []
|
||||
self.has_options = False
|
||||
self.has_column_aliases = False
|
||||
self.generator = False
|
||||
|
||||
def columns(self):
|
||||
return [i for i in self.schema if isinstance(i, Column)]
|
||||
@ -212,6 +213,10 @@ class TableState(Singleton):
|
||||
if len(set(all_options).intersection(NON_CACHEABLE)) > 0:
|
||||
print(lightred("Table cannot be marked cacheable: %s" % (path)))
|
||||
exit(1)
|
||||
if self.generator:
|
||||
print(lightred(
|
||||
"Table cannot use a generator and be marked cacheable: %s" % (path)))
|
||||
exit(1)
|
||||
if self.table_name == "" or self.function == "":
|
||||
print(lightred("Invalid table spec: %s" % (path)))
|
||||
exit(1)
|
||||
@ -249,6 +254,7 @@ class TableState(Singleton):
|
||||
aliases=self.aliases,
|
||||
has_options=self.has_options,
|
||||
has_column_aliases=self.has_column_aliases,
|
||||
generator=self.generator,
|
||||
attribute_set=[TABLE_ATTRIBUTES[attr] for attr in self.attributes],
|
||||
)
|
||||
|
||||
@ -339,7 +345,7 @@ def fuzz_paths(paths):
|
||||
table.fuzz_paths = paths
|
||||
|
||||
|
||||
def implementation(impl_string):
|
||||
def implementation(impl_string, generator=False):
|
||||
"""
|
||||
define the path to the implementation file and the function which
|
||||
implements the virtual table. You should use the following format:
|
||||
@ -360,6 +366,7 @@ def implementation(impl_string):
|
||||
table.impl = impl
|
||||
table.function = function
|
||||
table.class_name = class_name
|
||||
table.generator = generator
|
||||
|
||||
'''Check if the table has a subscriber attribute, if so, enforce time.'''
|
||||
if "event_subscriber" in table.attributes:
|
||||
|
@ -21,11 +21,15 @@ namespace osquery {
|
||||
/// BEGIN[GENTABLE]
|
||||
namespace tables {
|
||||
{% if class_name == "" %}\
|
||||
osquery::QueryData {{function}}(QueryContext& request);
|
||||
{% if generator %}\
|
||||
void {{function}}(RowYield& yield, QueryContext& context);
|
||||
{% else %}\
|
||||
osquery::QueryData {{function}}(QueryContext& context);
|
||||
{% endif %}\
|
||||
{% else %}
|
||||
class {{class_name}} {
|
||||
public:
|
||||
osquery::QueryData {{function}}(QueryContext& request);
|
||||
osquery::QueryData {{function}}(QueryContext& context);
|
||||
};
|
||||
{% endif %}\
|
||||
}
|
||||
@ -78,11 +82,18 @@ class {{table_name_cc}}TablePlugin : public TablePlugin {
|
||||
TableAttributes::NONE;
|
||||
}
|
||||
|
||||
QueryData generate(QueryContext& request) override {
|
||||
{% if generator %}\
|
||||
bool usesGenerator() const override { return true; }
|
||||
|
||||
void generator(RowYield& yield, QueryContext& context) override {
|
||||
tables::{{function}}(yield, context);
|
||||
}
|
||||
{% else %}\
|
||||
QueryData generate(QueryContext& context) override {
|
||||
{% if class_name != "" %}\
|
||||
if (EventFactory::exists(getName())) {
|
||||
auto subscriber = EventFactory::getEventSubscriber(getName());
|
||||
return subscriber->{{function}}(request);
|
||||
return subscriber->{{function}}(context);
|
||||
} else {
|
||||
LOG(ERROR) << "Subscriber table missing: " << getName();
|
||||
return QueryData();
|
||||
@ -93,13 +104,15 @@ class {{table_name_cc}}TablePlugin : public TablePlugin {
|
||||
return getCache();
|
||||
}
|
||||
{% endif %}\
|
||||
auto results = tables::{{function}}(request);
|
||||
auto results = tables::{{function}}(context);
|
||||
{% if attributes.cacheable %}\
|
||||
setCache(kCacheStep, kCacheInterval, results);
|
||||
{% endif %}
|
||||
return results;
|
||||
{% endif %}\
|
||||
}
|
||||
{% endif %}\
|
||||
|
||||
};
|
||||
|
||||
{% if attributes.utility %}
|
||||
|
@ -6,7 +6,7 @@ class Boost < AbstractOsqueryFormula
|
||||
url "https://downloads.sourceforge.net/project/boost/boost/1.63.0/boost_1_63_0.tar.bz2"
|
||||
sha256 "beae2529f759f6b3bf3f4969a19c2e9d6f0c503edcb2de4a61d1428519fcb3b0"
|
||||
head "https://github.com/boostorg/boost.git"
|
||||
revision 4
|
||||
revision 7
|
||||
|
||||
bottle do
|
||||
root_url "https://osquery-packages.s3.amazonaws.com/bottles"
|
||||
@ -15,8 +15,6 @@ class Boost < AbstractOsqueryFormula
|
||||
sha256 "429a64c15405fcd9c1d5f8712da9834859a8cb5695660d83d8f0d4b380add1c7" => :x86_64_linux
|
||||
end
|
||||
|
||||
patch :DATA
|
||||
|
||||
env :userpaths
|
||||
|
||||
option :universal
|
||||
@ -60,6 +58,8 @@ class Boost < AbstractOsqueryFormula
|
||||
"--with-regex",
|
||||
"--with-system",
|
||||
"--with-thread",
|
||||
"--with-coroutine2",
|
||||
"--with-context",
|
||||
"threading=multi",
|
||||
"link=static",
|
||||
"optimization=space",
|
||||
|
Loading…
Reference in New Issue
Block a user