osquery-1/tools/gentable.py

351 lines
8.9 KiB
Python
Raw Normal View History

#!/usr/bin/env python
2014-07-31 00:35:19 +00:00
# Copyright 2004-present Facebook. All Rights Reserved.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import ast
import jinja2
import logging
import os
import sys
# set DEVELOPING to True for debug statements
DEVELOPING = False
# the log format for the logging module
LOG_FORMAT = "%(levelname)s [Line %(lineno)d]: %(message)s"
# IMPL_TEMPLATE is the jinja template used to generate the virtual table
# implementation file
IMPL_TEMPLATE = """// Copyright 2004-present Facebook. All Rights Reserved.
/*
** This file is generated. Do not modify it manually!
*/
#include <cstring>
2014-07-31 00:35:19 +00:00
#include <string>
#include <vector>
#include <boost/lexical_cast.hpp>
2014-08-07 20:14:06 +00:00
#include "osquery/database.h"
2014-07-31 00:35:19 +00:00
#include "osquery/tables/base.h"
2014-09-06 01:12:37 +00:00
#include "osquery/registry/registry.h"
2014-07-31 00:35:19 +00:00
namespace osquery { namespace tables {
{% if class_name == "" %}
2014-09-21 21:27:09 +00:00
osquery::QueryData {{function}}();
{% else %}
class {{class_name}} {
public:
static osquery::QueryData {{function}}();
};
{% endif %}
2014-08-07 20:14:06 +00:00
2014-08-07 20:19:56 +00:00
struct sqlite3_{{table_name}} {
int n;
{% for col in schema %}\
std::vector<{{col.type}}> {{col.name}};
{% endfor %}\
};
2014-07-31 00:35:19 +00:00
const std::string
sqlite3_{{table_name}}_create_table_statement =
"CREATE TABLE {{table_name}}("
{% for col in schema %}\
"{{col.name}} \
{% if col.type == "std::string" %}VARCHAR{% endif %}\
{% if col.type == "int" %}INTEGER{% endif %}\
{% if col.type == "long long int" %}BIGINT{% endif %}\
2014-07-31 00:35:19 +00:00
{% if not loop.last %}, {% endif %}"
{% endfor %}\
")";
2014-07-31 00:35:19 +00:00
int {{table_name_cc}}Create(
2014-07-31 00:35:19 +00:00
sqlite3 *db,
void *pAux,
int argc,
const char *const *argv,
sqlite3_vtab **ppVtab,
char **pzErr
) {
return xCreate<
x_vtab<sqlite3_{{table_name}}>,
sqlite3_{{table_name}}
>(
db, pAux, argc, argv, ppVtab, pzErr,
sqlite3_{{table_name}}_create_table_statement.c_str()
);
}
int {{table_name_cc}}Column(
2014-07-31 00:35:19 +00:00
sqlite3_vtab_cursor *cur,
sqlite3_context *ctx,
int col
) {
base_cursor *pCur = (base_cursor*)cur;
x_vtab<sqlite3_{{table_name}}> *pVtab =
(x_vtab<sqlite3_{{table_name}}>*)cur->pVtab;
if(pCur->row >= 0 && pCur->row < pVtab->pContent->n) {
switch (col) {
{% for col in schema %}\
// {{ col.name }}
case {{ loop.index0 }}:
{% if col.type == "std::string" %}\
sqlite3_result_text(
ctx,
(pVtab->pContent->{{col.name}}[pCur->row]).c_str(),
-1,
nullptr
);
{% endif %}\
{% if col.type == "int" %}\
sqlite3_result_int(
ctx,
(int)pVtab->pContent->{{col.name}}[pCur->row]
);
{% endif %}\
{% if col.type == "long long int" %}\
sqlite3_result_int64(
ctx,
(long long int)pVtab->pContent->{{col.name}}[pCur->row]
);
2014-07-31 00:35:19 +00:00
{% endif %}\
break;
{% endfor %}\
}
}
return SQLITE_OK;
}
int {{table_name_cc}}Filter(
2014-07-31 00:35:19 +00:00
sqlite3_vtab_cursor *pVtabCursor,
int idxNum,
const char *idxStr,
int argc,
sqlite3_value **argv
) {
base_cursor *pCur = (base_cursor *)pVtabCursor;
x_vtab<sqlite3_{{table_name}}> *pVtab =
(x_vtab<sqlite3_{{table_name}}>*)pVtabCursor->pVtab;
pCur->row = 0;
2014-08-04 23:08:49 +00:00
{% for col in schema %}\
pVtab->pContent->{{col.name}}.clear();
2014-08-04 23:08:49 +00:00
{% endfor %}\
2014-07-31 00:35:19 +00:00
{% if class_name != "" %}
for (auto& row : osquery::tables::{{class_name}}::{{function}}()) {
{% else %}
2014-07-31 00:35:19 +00:00
for (auto& row : osquery::tables::{{function}}()) {
{% endif %}
2014-07-31 00:35:19 +00:00
{% for col in schema %}\
{% if col.type == "std::string" %}\
pVtab->pContent->{{col.name}}.push_back(row["{{col.name}}"]);
{% endif %}\
{% if col.type == "int" %}\
try {
pVtab->pContent->{{col.name}}\
2014-07-31 00:35:19 +00:00
.push_back(boost::lexical_cast<int>(row["{{col.name}}"]));
} catch (const boost::bad_lexical_cast& e) {
2014-09-25 19:25:20 +00:00
LOG(WARNING) << "Error casting " << row["{{col.name}}"] << " to int";
pVtab->pContent->{{col.name}}.push_back(-1);
}
2014-07-31 00:35:19 +00:00
{% endif %}\
{% if col.type == "long long int" %}\
try {
pVtab->pContent->{{col.name}}\
.push_back(boost::lexical_cast<long long>(row["{{col.name}}"]));
} catch (const boost::bad_lexical_cast& e) {
LOG(WARNING) << "Error casting " << row["{{col.name}}"] << " to long long int";
pVtab->pContent->{{col.name}}.push_back(-1);
}
{% endif %}\
2014-07-31 00:35:19 +00:00
{% endfor %}\
}
pVtab->pContent->n = pVtab->pContent->{{schema[0].name}}.size();
return SQLITE_OK;
}
static sqlite3_module {{table_name_cc}}Module = {
2014-08-07 20:19:56 +00:00
0,
{{table_name_cc}}Create,
{{table_name_cc}}Create,
2014-08-07 20:19:56 +00:00
xBestIndex,
xDestroy<x_vtab<sqlite3_{{table_name}}>>,
xDestroy<x_vtab<sqlite3_{{table_name}}>>,
xOpen<base_cursor>,
xClose<base_cursor>,
{{table_name_cc}}Filter,
2014-08-07 20:19:56 +00:00
xNext<base_cursor>,
xEof<base_cursor, x_vtab<sqlite3_{{table_name}}>>,
{{table_name_cc}}Column,
2014-08-07 20:19:56 +00:00
xRowid<base_cursor>,
0,
0,
0,
0,
0,
0,
0,
};
class {{table_name_cc}}TablePlugin : public TablePlugin {
2014-08-05 08:21:28 +00:00
public:
{{table_name_cc}}TablePlugin() {}
2014-08-05 08:21:28 +00:00
int attachVtable(sqlite3 *db) {
return sqlite3_attach_vtable<sqlite3_{{table_name}}>(
db, "{{table_name}}", &{{table_name_cc}}Module);
2014-08-05 08:21:28 +00:00
}
virtual ~{{table_name_cc}}TablePlugin() {}
2014-08-05 08:21:28 +00:00
};
REGISTER_TABLE(
"{{table_name}}",
std::make_shared<{{table_name_cc}}TablePlugin>()
2014-08-05 08:21:28 +00:00
);
2014-07-31 00:35:19 +00:00
}}
"""
def usage():
""" print program usage """
print("Usage: %s <spec.table> <file.cpp>" % sys.argv[0])
2014-07-31 00:35:19 +00:00
def to_camel_case(snake_case):
""" convert a snake_case string to camelCase """
components = snake_case.split('_')
return components[0] + "".join(x.title() for x in components[1:])
2014-07-31 00:35:19 +00:00
class Singleton(object):
"""
Make sure that anything that subclasses Singleton can only be instantiated
once
"""
_instance = None
def __new__(self, *args, **kwargs):
if not self._instance:
self._instance = super(Singleton, self).__new__(
self, *args, **kwargs)
return self._instance
class TableState(Singleton):
"""
Maintain the state of of the table commands during the execution of
the config file
"""
def __init__(self):
self.table_name = ""
self.schema = []
self.header = ""
self.impl = ""
self.function = ""
self.class_name = ""
2014-07-31 00:35:19 +00:00
def generate(self, path):
2014-07-31 00:35:19 +00:00
"""Generate the virtual table files"""
logging.debug("TableState.generate")
self.impl_content = jinja2.Template(IMPL_TEMPLATE).render(
table_name=self.table_name,
table_name_cc=to_camel_case(self.table_name),
2014-07-31 00:35:19 +00:00
schema=self.schema,
header=self.header,
impl=self.impl,
function=self.function,
class_name=self.class_name
2014-07-31 00:35:19 +00:00
)
path_bits = path.split("/")
for i in range(1, len(path_bits)):
dir_path = ""
for j in range(i):
dir_path += "%s/" % path_bits[j]
if not os.path.exists(dir_path):
os.mkdir(dir_path)
logging.debug("generating %s" % path)
with open(path, "w+") as file_h:
2014-07-31 00:35:19 +00:00
file_h.write(self.impl_content)
table = TableState()
class Column(object):
"""
A Column object to get around that fact that list literals in Python are
ordered but dictionaries aren't
"""
def __init__(self, **kwargs):
self.name = kwargs.get("name", "")
self.type = kwargs.get("type", "")
def table_name(name):
"""define the virtual table name"""
logging.debug("- table_name")
logging.debug(" - called with: %s" % name)
table.table_name = name
def schema(schema_list):
"""
define a list of Column object which represent the columns of your virtual
table
"""
logging.debug("- schema")
for col in schema_list:
logging.debug(" - %s (%s)" % (col.name, col.type))
table.schema = schema_list
def implementation(impl_string):
"""
define the path to the implementation file and the function which
implements the virtual table. You should use the following format:
# the path is "osquery/table/implementations/foo.cpp"
# the function is "QueryData genFoo();"
implementation("foo@genFoo")
2014-07-31 00:35:19 +00:00
"""
logging.debug("- implementation")
filename, function = impl_string.split("@")
class_parts = function.split("::")[::-1]
function = class_parts[0]
class_name = class_parts[1] if len(class_parts) > 1 else ""
impl = "%s.cpp" % filename
2014-07-31 00:35:19 +00:00
logging.debug(" - impl => %s" % impl)
logging.debug(" - function => %s" % function)
logging.debug(" - class_name => %s" % class_name)
2014-07-31 00:35:19 +00:00
table.impl = impl
table.function = function
table.class_name = class_name
2014-07-31 00:35:19 +00:00
def main(argc, argv):
2014-07-31 00:35:19 +00:00
if DEVELOPING:
logging.basicConfig(format=LOG_FORMAT, level=logging.DEBUG)
else:
logging.basicConfig(format=LOG_FORMAT, level=logging.INFO)
if argc < 3:
2014-07-31 00:35:19 +00:00
usage()
sys.exit(1)
filename = argv[1]
output = argv[2]
2014-07-31 00:35:19 +00:00
with open(filename, "rU") as file_handle:
tree = ast.parse(file_handle.read())
exec(compile(tree, "<string>", "exec"))
table.generate(output)
2014-07-31 00:35:19 +00:00
if __name__ == "__main__":
main(len(sys.argv), sys.argv)