diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:34:54 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:34:54 +0000 |
commit | 0915b3ef56dfac3113cce55a59a5765dc94976be (patch) | |
tree | a8fea11d50b4f083e1bf0f90025ece7f0824784a /tools | |
parent | Initial commit. (diff) | |
download | icinga2-upstream.tar.xz icinga2-upstream.zip |
Adding upstream version 2.13.6.upstream/2.13.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools')
32 files changed, 4506 insertions, 0 deletions
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..e3a822e --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,6 @@ +# Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ + +add_subdirectory(mkclass) +add_subdirectory(mkembedconfig) +add_subdirectory(mkunity) + diff --git a/tools/debug/gdb/.gitignore b/tools/debug/gdb/.gitignore new file mode 100644 index 0000000..1c9c744 --- /dev/null +++ b/tools/debug/gdb/.gitignore @@ -0,0 +1 @@ +icingadbg.pyc diff --git a/tools/debug/gdb/README.md b/tools/debug/gdb/README.md new file mode 100644 index 0000000..b00f81a --- /dev/null +++ b/tools/debug/gdb/README.md @@ -0,0 +1,40 @@ +# Pretty Printer Installation + +Requirements: +* icinga2 debug symbols +* boost, gcc, etc debug symbols + +Install the `boost`, `python` and `icinga2` pretty printers. Absolute paths are required, +so please make sure to update the installation paths accordingly (`pwd`). + +Boost Pretty Printers: + + $ mkdir ~/.gdb_printers && cd ~/.gdb_printers + $ git clone https://github.com/ruediger/Boost-Pretty-Printer.git && cd Boost-Pretty-Printer + $ pwd + /home/michi/.gdb_printers/Boost-Pretty-Printer + +Python Pretty Printers: + + $ cd ~/.gdb_printers + $ svn co svn://gcc.gnu.org/svn/gcc/trunk/libstdc++-v3/python + +Icinga 2 Pretty Printers: + + $ mkdir -p ~/.gdb_printers/icinga2 && ~/.gdb_printers/icinga2 + $ wget https://raw.githubusercontent.com/Icinga/icinga2/master/tools/debug/gdb/icingadbg.py + +Now you'll need to modify/setup your `~/.gdbinit` configuration file. +You can download the one from Icinga 2 and modify all paths. + +> **Note** +> +> The path to the `pthread` library varies on distributions. Use +> `find /usr/lib* -type f -name '*libpthread.so*'` to get the proper +> path. + + $ wget https://raw.githubusercontent.com/Icinga/icinga2/master/tools/debug/gdb/gdbinit -O ~/.gdbinit + $ vim ~/.gdbinit + + +More details in the [troubleshooting debug documentation](https://docs.icinga.com/icinga2/latest/doc/module/icinga2/chapter/troubleshooting#debug). diff --git a/tools/debug/gdb/gdbinit b/tools/debug/gdb/gdbinit new file mode 100644 index 0000000..a7e6b87 --- /dev/null +++ b/tools/debug/gdb/gdbinit @@ -0,0 +1,25 @@ +set print pretty on + +python +import sys +sys.path.insert(0, '/home/gbeutner/icinga2/tools/debug/gdb') +from icingadbg import register_icinga_printers +register_icinga_printers() +end + +python +import sys +sys.path.insert(0, '/home/gbeutner/gdb_printers/python') +from libstdcxx.v6.printers import register_libstdcxx_printers +try: + register_libstdcxx_printers(None) +except: + pass +end + +python +import sys +sys.path.insert(0, '/home/gbeutner/Boost-Pretty-Printer') +from boost.printers import register_printer_gen +register_printer_gen(None) +end diff --git a/tools/debug/gdb/icingadbg.py b/tools/debug/gdb/icingadbg.py new file mode 100644 index 0000000..d1e1c59 --- /dev/null +++ b/tools/debug/gdb/icingadbg.py @@ -0,0 +1,64 @@ +import gdb +import re + +class IcingaStringPrinter: + def __init__(self, val): + self.val = val + + def to_string(self): + return '"' + self.val['m_Data']['_M_dataplus']['_M_p'].string() + '"' + +class IcingaValuePrinter: + def __init__(self, val): + self.val = val + + def to_string(self): + which = self.val['m_Value']['which_'] + + if which == 0: + return 'Empty' + elif which == 1: + return self.val['m_Value']['storage_']['data_']['buf'].cast(gdb.lookup_type('double').pointer()).dereference() + elif which == 2: + return self.val['m_Value']['storage_']['data_']['buf'].cast(gdb.lookup_type('bool').pointer()).dereference() + elif which == 3: + return self.val['m_Value']['storage_']['data_']['buf'].cast(gdb.lookup_type('icinga::String').pointer()).dereference() + elif which == 4: + return self.val['m_Value']['storage_']['data_']['buf'].cast(gdb.lookup_type('icinga::Object').pointer()).dereference() + else: + return '<INVALID>' + +class IcingaSignalPrinter: + def __init__(self, val): + self.val = val + + def to_string(self): + return '<SIGNAL>' + +class IcingaMutexPrinter: + def __init__(self, val): + self.val = val + + def to_string(self): + owner = self.val['__data']['__owner'] + + if owner == 0: + return '<unlocked>' + else: + return '<locked by #' + str(owner) + '>' + +def lookup_icinga_type(val): + t = val.type.unqualified() + if str(t) == 'icinga::String': + return IcingaStringPrinter(val) + elif str(t) == 'icinga::Value': + return IcingaValuePrinter(val) + elif re.match('^boost::signals2::signal.*<.*>$', str(t)): + return IcingaSignalPrinter(val) + elif str(t) == 'pthread_mutex_t': + return IcingaMutexPrinter(val) + + return None + +def register_icinga_printers(): + gdb.pretty_printers.append(lookup_icinga_type) diff --git a/tools/debug/natvis/Visualizers/icinga2.natstepfilter b/tools/debug/natvis/Visualizers/icinga2.natstepfilter new file mode 100644 index 0000000..f53f002 --- /dev/null +++ b/tools/debug/natvis/Visualizers/icinga2.natstepfilter @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<StepFilter xmlns="http://schemas.microsoft.com/vstudio/debugger/natstepfilter/2010"> + <Function><Name>icinga::String::.*</Name><Action>NoStepInto</Action></Function> + <Function><Name>icinga::Value::.*</Name><Action>NoStepInto</Action></Function> + <Function><Name>icinga::Array::.*</Name><Action>NoStepInto</Action></Function> + <Function><Name>icinga::Dictionary::.*</Name><Action>NoStepInto</Action></Function> + <Function><Name>icinga::Object::.*</Name><Action>NoStepInto</Action></Function> + <Function><Name>icinga::ObjectImpl<.*</Name><Action>NoStepInto</Action></Function> +</StepFilter> diff --git a/tools/debug/natvis/Visualizers/icinga2.natvis b/tools/debug/natvis/Visualizers/icinga2.natvis new file mode 100644 index 0000000..0ec4b78 --- /dev/null +++ b/tools/debug/natvis/Visualizers/icinga2.natvis @@ -0,0 +1,32 @@ +<?xml version='1.0' encoding='utf-8'?> +<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> + <Type Name="icinga::String"> + <DisplayString>{m_Data}</DisplayString> + </Type> + + <Type Name="icinga::Value"> + <DisplayString Condition="m_Value.which_ == 0">Empty</DisplayString> + <DisplayString Condition="m_Value.which_ == 1">{*(double *)m_Value.storage_.data_.buf}</DisplayString> + <DisplayString Condition="m_Value.which_ == 2">{*(double *)m_Value.storage_.data_.buf}</DisplayString> + <DisplayString Condition="m_Value.which_ == 3">{*(icinga::String *)m_Value.storage_.data_.buf}</DisplayString> + <DisplayString Condition="m_Value.which_ == 4">{*(boost::intrusive_ptr<icinga::Object> *)m_Value.storage_.data_.buf}</DisplayString> + </Type> + + <Type Name="icinga::Array"> + <DisplayString>{m_Data}</DisplayString> + <Expand> + <ExpandedItem>m_Data</ExpandedItem> + </Expand> + </Type> + + <Type Name="icinga::Dictionary"> + <DisplayString>{m_Data}</DisplayString> + <Expand> + <ExpandedItem>m_Data</ExpandedItem> + </Expand> + </Type> + + <Type Name="icinga::ObjectLock"> + <DisplayString>{m_Lock}</DisplayString> + </Type> +</AutoVisualizer> diff --git a/tools/debug/natvis/[Content_Types].xml b/tools/debug/natvis/[Content_Types].xml new file mode 100644 index 0000000..b1c9cf9 --- /dev/null +++ b/tools/debug/natvis/[Content_Types].xml @@ -0,0 +1 @@ +<?xml version="1.0" encoding="utf-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="vsixmanifest" ContentType="text/xml" /><Default Extension="natstepfilter" ContentType="application/octet-stream" /><Default Extension="natvis" ContentType="application/octet-stream" /></Types>
\ No newline at end of file diff --git a/tools/debug/natvis/extension.vsixmanifest b/tools/debug/natvis/extension.vsixmanifest new file mode 100644 index 0000000..d870e89 --- /dev/null +++ b/tools/debug/natvis/extension.vsixmanifest @@ -0,0 +1,18 @@ +<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011"> + <Metadata> + <Identity Id="Icinga2Visualizers.VS2013.D1DFF2F5-FB30-41FE-8EEF-0CEB97ABBC6B" Version="2.0.0" Language="en-US" Publisher="Icinga GmbH" /> + <DisplayName>Icinga 2 Debugger Visualizers for Visual Studio</DisplayName> + <Description xml:space="preserve">Icinga 2 Debugger Visualizers</Description> + </Metadata> + <Installation> + <InstallationTarget Version="[12.0,13.0)" Id="Microsoft.VisualStudio.Premium" /> + <InstallationTarget Version="[12.0,13.0)" Id="Microsoft.VisualStudio.Pro" /> + <InstallationTarget Version="[12.0,13.0)" Id="Microsoft.VisualStudio.Ultimate" /> + <InstallationTarget Version="[12.0,13.0)" Id="Microsoft.VisualStudio.VSWinDesktopExpress" /> + </Installation> + <Dependencies></Dependencies> + <Assets> + <Asset Type="NativeVisualizer" Path="Visualizers\icinga2.natvis" /> + <Asset Type="StepFilter" Path="Visualizers\icinga2.natstepfilter" /> + </Assets> +</PackageManifest> diff --git a/tools/mkclass/CMakeLists.txt b/tools/mkclass/CMakeLists.txt new file mode 100644 index 0000000..1b97bda --- /dev/null +++ b/tools/mkclass/CMakeLists.txt @@ -0,0 +1,43 @@ +# Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ + +find_package(BISON 2.3.0 REQUIRED) +find_package(FLEX 2.5.31 REQUIRED) + +bison_target(class_parser class_parser.yy ${CMAKE_CURRENT_BINARY_DIR}/class_parser.cc) +flex_target(class_lexer class_lexer.ll ${CMAKE_CURRENT_BINARY_DIR}/class_lexer.cc) +add_flex_bison_dependency(class_lexer class_parser) + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/class_parser.cc PROPERTY COMPILE_FLAGS "-Wno-deprecated-register") + set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/class_lexer.cc PROPERTY COMPILE_FLAGS "-Wno-deprecated-register -Wno-null-conversion") +endif() + +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + +set(mkclass_SOURCES + mkclass.cpp + classcompiler.cpp classcompiler.hpp + ${FLEX_class_lexer_OUTPUTS} + ${BISON_class_parser_OUTPUTS} +) + +add_executable(mkclass ${mkclass_SOURCES}) + +if(WIN32) + target_link_libraries(mkclass shlwapi) +endif() + +set_target_properties ( + mkclass PROPERTIES + FOLDER Bin +) + +macro(MKCLASS_TARGET ClassInput ClassImplOutput ClassHeaderOutput) + add_custom_command( + OUTPUT ${ClassImplOutput} ${ClassHeaderOutput} + COMMAND mkclass + ARGS ${ClassInput} ${CMAKE_CURRENT_BINARY_DIR}/${ClassImplOutput} ${CMAKE_CURRENT_BINARY_DIR}/${ClassHeaderOutput} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS mkclass ${ClassInput} + ) +endmacro() diff --git a/tools/mkclass/class_lexer.ll b/tools/mkclass/class_lexer.ll new file mode 100644 index 0000000..217ca49 --- /dev/null +++ b/tools/mkclass/class_lexer.ll @@ -0,0 +1,167 @@ +%{ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "classcompiler.hpp" + +using namespace icinga; + +#define YYLTYPE icinga::ClassDebugInfo + +#include "class_parser.hh" + +#define YY_EXTRA_TYPE ClassCompiler * +#define YY_USER_ACTION \ +do { \ + yylloc->path = yyextra->GetPath(); \ + yylloc->first_line = yylineno; \ + yylloc->first_column = yycolumn; \ + yylloc->last_line = yylineno; \ + yylloc->last_column = yycolumn + yyleng - 1; \ + yycolumn += yyleng; \ +} while (0); + +#define YY_INPUT(buf, result, max_size) \ +do { \ + result = yyextra->ReadInput(buf, max_size); \ +} while (0) + +struct lex_buf { + char *buf; + size_t size; +}; + +static void lb_init(lex_buf *lb) +{ + lb->buf = NULL; + lb->size = 0; +} + +/*static void lb_cleanup(lex_buf *lb) +{ + free(lb->buf); +}*/ + +static void lb_append_char(lex_buf *lb, char new_char) +{ + const size_t block_size = 64; + + size_t old_blocks = (lb->size + (block_size - 1)) / block_size; + size_t new_blocks = ((lb->size + 1) + (block_size - 1)) / block_size; + + if (old_blocks != new_blocks) { + char *new_buf = (char *)realloc(lb->buf, new_blocks * block_size); + + if (new_buf == NULL && new_blocks > 0) + throw std::bad_alloc(); + + lb->buf = new_buf; + } + + lb->size++; + lb->buf[lb->size - 1] = new_char; +} + +static char *lb_steal(lex_buf *lb) +{ + lb_append_char(lb, '\0'); + + char *buf = lb->buf; + lb->buf = NULL; + lb->size = 0; + return buf; +} +%} + +%option reentrant noyywrap yylineno +%option bison-bridge bison-locations +%option never-interactive nounistd +%option noinput nounput + +%x HEREDOC +%x C_COMMENT + +%% + lex_buf string_buf; + +\{\{\{ { lb_init(&string_buf); BEGIN(HEREDOC); } + +<HEREDOC>\}\}\} { + BEGIN(INITIAL); + + lb_append_char(&string_buf, '\0'); + + yylval->text = lb_steal(&string_buf); + + return T_STRING; + } + +<HEREDOC>(.|\n) { lb_append_char(&string_buf, yytext[0]); } + +"/*" { BEGIN(C_COMMENT); } + +<C_COMMENT>{ +"*/" { BEGIN(INITIAL); } +[^*] /* ignore comment */ +"*" /* ignore star */ +} + +<C_COMMENT><<EOF>> { + fprintf(stderr, "End-of-file while in comment.\n"); + yyterminate(); + } +\/\/[^\n]* /* ignore C++-style comments */ +[ \t\r\n] /* ignore whitespace */ + +#include { return T_INCLUDE; } +#impl_include { return T_IMPL_INCLUDE; } +class { return T_CLASS; } +namespace { return T_NAMESPACE; } +code { return T_CODE; } +load_after { return T_LOAD_AFTER; } +activation_priority { return T_ACTIVATION_PRIORITY; } +library { return T_LIBRARY; } +abstract { yylval->num = TAAbstract; return T_CLASS_ATTRIBUTE; } +vararg_constructor { yylval->num = TAVarArgConstructor; return T_CLASS_ATTRIBUTE; } +config { yylval->num = FAConfig; return T_FIELD_ATTRIBUTE; } +state { yylval->num = FAState; return T_FIELD_ATTRIBUTE; } +enum { yylval->num = FAEnum; return T_FIELD_ATTRIBUTE; } +get_protected { yylval->num = FAGetProtected; return T_FIELD_ATTRIBUTE; } +set_protected { yylval->num = FASetProtected; return T_FIELD_ATTRIBUTE; } +protected { yylval->num = FAGetProtected | FASetProtected; return T_FIELD_ATTRIBUTE; } +no_storage { yylval->num = FANoStorage; return T_FIELD_ATTRIBUTE; } +no_user_modify { yylval->num = FANoUserModify; return T_FIELD_ATTRIBUTE; } +no_user_view { yylval->num = FANoUserView; return T_FIELD_ATTRIBUTE; } +deprecated { yylval->num = FADeprecated; return T_FIELD_ATTRIBUTE; } +get_virtual { yylval->num = FAGetVirtual; return T_FIELD_ATTRIBUTE; } +set_virtual { yylval->num = FASetVirtual; return T_FIELD_ATTRIBUTE; } +signal_with_old_value { yylval->num = FASignalWithOldValue; return T_FIELD_ATTRIBUTE; } +virtual { yylval->num = FAGetVirtual | FASetVirtual; return T_FIELD_ATTRIBUTE; } +navigation { return T_NAVIGATION; } +validator { return T_VALIDATOR; } +required { return T_REQUIRED; } +name { return T_NAME; } +array { return T_ARRAY; } +default { yylval->num = FTDefault; return T_FIELD_ACCESSOR_TYPE; } +get { yylval->num = FTGet; return T_FIELD_ACCESSOR_TYPE; } +set { yylval->num = FTSet; return T_FIELD_ACCESSOR_TYPE; } +track { yylval->num = FTTrack; return T_FIELD_ACCESSOR_TYPE; } +navigate { yylval->num = FTNavigate; return T_FIELD_ACCESSOR_TYPE; } +\"[^\"]+\" { yylval->text = strdup(yytext + 1); yylval->text[strlen(yylval->text) - 1] = '\0'; return T_STRING; } +\<[^ \>]*\> { yylval->text = strdup(yytext + 1); yylval->text[strlen(yylval->text) - 1] = '\0'; return T_ANGLE_STRING; } +[a-zA-Z_][:a-zA-Z0-9\-_]* { yylval->text = strdup(yytext); return T_IDENTIFIER; } +-?[0-9]+(\.[0-9]+)? { yylval->num = strtod(yytext, NULL); return T_NUMBER; } + +. return yytext[0]; + +%% + +void ClassCompiler::InitializeScanner(void) +{ + yylex_init(&m_Scanner); + yyset_extra(this, m_Scanner); +} + +void ClassCompiler::DestroyScanner(void) +{ + yylex_destroy(m_Scanner); +} diff --git a/tools/mkclass/class_parser.yy b/tools/mkclass/class_parser.yy new file mode 100644 index 0000000..0524b2d --- /dev/null +++ b/tools/mkclass/class_parser.yy @@ -0,0 +1,558 @@ +%{ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "classcompiler.hpp" +#include <iostream> +#include <vector> +#include <cstring> + +using std::malloc; +using std::free; +using std::exit; + +using namespace icinga; + +#define YYLTYPE icinga::ClassDebugInfo + +%} + +%pure-parser + +%locations +%defines +%error-verbose + +%parse-param { ClassCompiler *context } +%lex-param { void *scanner } + +%union { + char *text; + int num; + FieldType *type; + Field *field; + std::vector<Field> *fields; + Klass *klass; + FieldAccessor *fieldaccessor; + std::vector<FieldAccessor> *fieldaccessors; + Rule *rule; + std::vector<Rule> *rules; + Validator *validator; +} + +%token T_INCLUDE "#include (T_INCLUDE)" +%token T_IMPL_INCLUDE "#impl_include (T_IMPL_INCLUDE)" +%token T_CLASS "class (T_CLASS)" +%token T_CODE "code (T_CODE)" +%token T_LOAD_AFTER "load_after (T_LOAD_AFTER)" +%token T_ACTIVATION_PRIORITY "activation_priority (T_ACTIVATION_PRIORITY)" +%token T_LIBRARY "library (T_LIBRARY)" +%token T_NAMESPACE "namespace (T_NAMESPACE)" +%token T_VALIDATOR "validator (T_VALIDATOR)" +%token T_REQUIRED "required (T_REQUIRED)" +%token T_NAVIGATION "navigation (T_NAVIGATION)" +%token T_NAME "name (T_NAME)" +%token T_ARRAY "array (T_ARRAY)" +%token T_STRING "string (T_STRING)" +%token T_ANGLE_STRING "angle_string (T_ANGLE_STRING)" +%token T_FIELD_ATTRIBUTE "field_attribute (T_FIELD_ATTRIBUTE)" +%token T_CLASS_ATTRIBUTE "class_attribute (T_CLASS_ATTRIBUTE)" +%token T_IDENTIFIER "identifier (T_IDENTIFIER)" +%token T_GET "get (T_GET)" +%token T_SET "set (T_SET)" +%token T_DEFAULT "default (T_DEFAULT)" +%token T_FIELD_ACCESSOR_TYPE "field_accessor_type (T_FIELD_ACCESSOR_TYPE)" +%token T_NUMBER "number (T_NUMBER)" +%type <text> T_IDENTIFIER +%type <text> T_STRING +%type <text> T_ANGLE_STRING +%type <text> identifier +%type <text> alternative_name_specifier +%type <text> inherits_specifier +%type <text> type_base_specifier +%type <text> include +%type <text> angle_include +%type <text> impl_include +%type <text> angle_impl_include +%type <text> code +%type <num> T_FIELD_ATTRIBUTE +%type <field> field_attribute +%type <field> field_attributes +%type <field> field_attribute_list +%type <num> T_FIELD_ACCESSOR_TYPE +%type <num> T_CLASS_ATTRIBUTE +%type <num> class_attribute_list +%type <type> field_type +%type <field> class_field +%type <fields> class_fields +%type <klass> class +%type <fieldaccessors> field_accessor_list +%type <fieldaccessors> field_accessors +%type <fieldaccessor> field_accessor +%type <rule> validator_rule +%type <rules> validator_rules +%type <validator> validator +%type <num> T_NUMBER + +%{ + +int yylex(YYSTYPE *lvalp, YYLTYPE *llocp, void *scanner); + +void yyerror(YYLTYPE *locp, ClassCompiler *, const char *err) +{ + std::cerr << "in " << locp->path << " at " << locp->first_line << ":" << locp->first_column << "-" + << locp->last_line << ":" << locp->last_column << ": " << err << std::endl; + std::exit(1); +} + +int yyparse(ClassCompiler *context); + +void ClassCompiler::Compile(void) +{ + try { + yyparse(this); + } catch (const std::exception& ex) { + std::cerr << "Exception: " << ex.what(); + } + + HandleMissingValidators(); +} + +#define scanner (context->GetScanner()) + +%} + +%% + +statements: /* empty */ + | statements statement + ; + +statement: include + { + context->HandleInclude($1, yylloc); + std::free($1); + } + | angle_include + { + context->HandleAngleInclude($1, yylloc); + std::free($1); + } + | impl_include + { + context->HandleImplInclude($1, yylloc); + std::free($1); + } + | angle_impl_include + { + context->HandleAngleImplInclude($1, yylloc); + std::free($1); + } + | class + { + context->HandleClass(*$1, yylloc); + delete $1; + } + | validator + { + context->HandleValidator(*$1, yylloc); + delete $1; + } + | namespace + | code + { + context->HandleCode($1, yylloc); + std::free($1); + } + | library + ; + +include: T_INCLUDE T_STRING + { + $$ = $2; + } + ; + +angle_include: T_INCLUDE T_ANGLE_STRING + { + $$ = $2; + } + ; + +impl_include: T_IMPL_INCLUDE T_STRING + { + $$ = $2; + } + ; + +angle_impl_include: T_IMPL_INCLUDE T_ANGLE_STRING + { + $$ = $2; + } + ; + +namespace: T_NAMESPACE identifier '{' + { + context->HandleNamespaceBegin($2, yylloc); + std::free($2); + } + statements '}' + { + context->HandleNamespaceEnd(yylloc); + } + ; + +code: T_CODE T_STRING + { + $$ = $2; + } + ; + +library: T_LIBRARY T_IDENTIFIER ';' + { + context->HandleLibrary($2, yylloc); + free($2); + } + ; + +class: class_attribute_list T_CLASS T_IDENTIFIER inherits_specifier type_base_specifier '{' class_fields '}' ';' + { + $$ = new Klass(); + + $$->Name = $3; + std::free($3); + + if ($4) { + $$->Parent = $4; + std::free($4); + } + + if ($5) { + $$->TypeBase = $5; + std::free($5); + } + + $$->Attributes = $1; + + for (const Field& field : *$7) { + if (field.Attributes & FALoadDependency) { + $$->LoadDependencies.push_back(field.Name); + } else if (field.Attributes & FAActivationPriority) { + $$->ActivationPriority = field.Priority; + } else + $$->Fields.push_back(field); + } + + delete $7; + + ClassCompiler::OptimizeStructLayout($$->Fields); + } + ; + +class_attribute_list: /* empty */ + { + $$ = 0; + } + | T_CLASS_ATTRIBUTE + { + $$ = $1; + } + | class_attribute_list T_CLASS_ATTRIBUTE + { + $$ = $1 | $2; + } + +inherits_specifier: /* empty */ + { + $$ = NULL; + } + | ':' identifier + { + $$ = $2; + } + ; + +type_base_specifier: /* empty */ + { + $$ = NULL; + } + | '<' identifier + { + $$ = $2; + } + ; + +class_fields: /* empty */ + { + $$ = new std::vector<Field>(); + } + | class_fields class_field + { + $$->push_back(*$2); + delete $2; + } + ; + +field_type: identifier + { + $$ = new FieldType(); + $$->IsName = false; + $$->TypeName = $1; + free($1); + } + | T_NAME '(' identifier ')' + { + $$ = new FieldType(); + $$->IsName = true; + $$->TypeName = $3; + $$->ArrayRank = 0; + free($3); + } + | T_ARRAY '(' field_type ')' + { + $$ = $3; + $$->ArrayRank++; + } + ; + +class_field: field_attribute_list field_type identifier alternative_name_specifier field_accessor_list ';' + { + Field *field = $1; + + if ((field->Attributes & (FAConfig | FAState)) == 0) + field->Attributes |= FAEphemeral; + + field->Type = *$2; + delete $2; + + field->Name = $3; + std::free($3); + + if ($4) { + field->AlternativeName = $4; + std::free($4); + } + + std::vector<FieldAccessor>::const_iterator it; + for (it = $5->begin(); it != $5->end(); it++) { + switch (it->Type) { + case FTGet: + field->GetAccessor = it->Accessor; + field->PureGetAccessor = it->Pure; + break; + case FTSet: + field->SetAccessor = it->Accessor; + field->PureSetAccessor = it->Pure; + break; + case FTDefault: + field->DefaultAccessor = it->Accessor; + break; + case FTTrack: + field->TrackAccessor = it->Accessor; + break; + case FTNavigate: + field->NavigateAccessor = it->Accessor; + field->PureNavigateAccessor = it->Pure; + break; + } + } + + delete $5; + + $$ = field; + } + | T_LOAD_AFTER identifier ';' + { + auto *field = new Field(); + field->Attributes = FALoadDependency; + field->Name = $2; + std::free($2); + $$ = field; + } + | T_ACTIVATION_PRIORITY T_NUMBER ';' + { + auto *field = new Field(); + field->Attributes = FAActivationPriority; + field->Priority = $2; + $$ = field; + } + ; + +alternative_name_specifier: /* empty */ + { + $$ = NULL; + } + | '(' identifier ')' + { + $$ = $2; + } + ; + +field_attribute_list: /* empty */ + { + $$ = new Field(); + } + | '[' field_attributes ']' + { + $$ = $2; + } + ; + +field_attribute: T_FIELD_ATTRIBUTE + { + $$ = new Field(); + $$->Attributes = $1; + } + | T_REQUIRED + { + $$ = new Field(); + $$->Attributes = FARequired; + } + | T_NAVIGATION '(' identifier ')' + { + $$ = new Field(); + $$->Attributes = FANavigation; + $$->NavigationName = $3; + std::free($3); + } + | T_NAVIGATION + { + $$ = new Field(); + $$->Attributes = FANavigation; + } + ; + +field_attributes: /* empty */ + { + $$ = new Field(); + } + | field_attributes ',' field_attribute + { + $$ = $1; + $$->Attributes |= $3->Attributes; + if (!$3->NavigationName.empty()) + $$->NavigationName = $3->NavigationName; + delete $3; + } + | field_attribute + { + $$ = $1; + } + ; + +field_accessor_list: /* empty */ + { + $$ = new std::vector<FieldAccessor>(); + } + | '{' field_accessors '}' + { + $$ = $2; + } + ; + +field_accessors: /* empty */ + { + $$ = new std::vector<FieldAccessor>(); + } + | field_accessors field_accessor + { + $$ = $1; + $$->push_back(*$2); + delete $2; + } + ; + +field_accessor: T_FIELD_ACCESSOR_TYPE T_STRING + { + $$ = new FieldAccessor(static_cast<FieldAccessorType>($1), $2, false); + std::free($2); + } + | T_FIELD_ACCESSOR_TYPE ';' + { + $$ = new FieldAccessor(static_cast<FieldAccessorType>($1), "", true); + } + ; + +validator_rules: /* empty */ + { + $$ = new std::vector<Rule>(); + } + | validator_rules validator_rule + { + $$->push_back(*$2); + delete $2; + } + ; + +validator_rule: T_NAME '(' T_IDENTIFIER ')' identifier ';' + { + $$ = new Rule(); + $$->Attributes = 0; + $$->IsName = true; + $$->Type = $3; + std::free($3); + $$->Pattern = $5; + std::free($5); + } + | T_IDENTIFIER identifier ';' + { + $$ = new Rule(); + $$->Attributes = 0; + $$->IsName = false; + $$->Type = $1; + std::free($1); + $$->Pattern = $2; + std::free($2); + } + | T_NAME '(' T_IDENTIFIER ')' identifier '{' validator_rules '}' ';' + { + $$ = new Rule(); + $$->Attributes = 0; + $$->IsName = true; + $$->Type = $3; + std::free($3); + $$->Pattern = $5; + std::free($5); + $$->Rules = *$7; + delete $7; + } + | T_IDENTIFIER identifier '{' validator_rules '}' ';' + { + $$ = new Rule(); + $$->Attributes = 0; + $$->IsName = false; + $$->Type = $1; + std::free($1); + $$->Pattern = $2; + std::free($2); + $$->Rules = *$4; + delete $4; + } + | T_REQUIRED identifier ';' + { + $$ = new Rule(); + $$->Attributes = RARequired; + $$->IsName = false; + $$->Type = ""; + $$->Pattern = $2; + std::free($2); + } + ; + +validator: T_VALIDATOR T_IDENTIFIER '{' validator_rules '}' ';' + { + $$ = new Validator(); + + $$->Name = $2; + std::free($2); + + $$->Rules = *$4; + delete $4; + } + ; + +identifier: T_IDENTIFIER + | T_STRING + { + $$ = $1; + } + ; diff --git a/tools/mkclass/classcompiler.cpp b/tools/mkclass/classcompiler.cpp new file mode 100644 index 0000000..d1d2750 --- /dev/null +++ b/tools/mkclass/classcompiler.cpp @@ -0,0 +1,1476 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "classcompiler.hpp" +#include <iostream> +#include <sstream> +#include <fstream> +#include <stdexcept> +#include <map> +#include <set> +#include <utility> +#include <vector> +#include <cstring> +#include <locale> +#ifndef _WIN32 +#include <libgen.h> +#else /* _WIN32 */ +#include <shlwapi.h> +#endif /* _WIN32 */ + +using namespace icinga; + +ClassCompiler::ClassCompiler(std::string path, std::istream& input, + std::ostream& oimpl, std::ostream& oheader) + : m_Path(std::move(path)), m_Input(input), m_Impl(oimpl), m_Header(oheader) +{ + InitializeScanner(); +} + +ClassCompiler::~ClassCompiler() +{ + DestroyScanner(); +} + +std::string ClassCompiler::GetPath() const +{ + return m_Path; +} + +void *ClassCompiler::GetScanner() +{ + return m_Scanner; +} + +size_t ClassCompiler::ReadInput(char *buffer, size_t max_size) +{ + m_Input.read(buffer, max_size); + return static_cast<size_t>(m_Input.gcount()); +} + +void ClassCompiler::HandleInclude(const std::string& path, const ClassDebugInfo&) +{ + m_Header << "#include \"" << path << "\"" << std::endl << std::endl; +} + +void ClassCompiler::HandleAngleInclude(const std::string& path, const ClassDebugInfo&) +{ + m_Header << "#include <" << path << ">" << std::endl << std::endl; +} + +void ClassCompiler::HandleImplInclude(const std::string& path, const ClassDebugInfo&) +{ + m_Impl << "#include \"" << path << "\"" << std::endl << std::endl; +} + +void ClassCompiler::HandleAngleImplInclude(const std::string& path, const ClassDebugInfo&) +{ + m_Impl << "#include <" << path << ">" << std::endl << std::endl; +} + +void ClassCompiler::HandleNamespaceBegin(const std::string& name, const ClassDebugInfo&) +{ + m_Header << "namespace " << name << std::endl + << "{" << std::endl << std::endl; + + m_Impl << "namespace " << name << std::endl + << "{" << std::endl << std::endl; +} + +void ClassCompiler::HandleNamespaceEnd(const ClassDebugInfo&) +{ + HandleMissingValidators(); + + m_Header << "}" << std::endl; + + m_Impl << "}" << std::endl; +} + +void ClassCompiler::HandleCode(const std::string& code, const ClassDebugInfo&) +{ + m_Header << code << std::endl; +} + +void ClassCompiler::HandleLibrary(const std::string& library, const ClassDebugInfo&) +{ + m_Library = library; +} + +unsigned long ClassCompiler::SDBM(const std::string& str, size_t len = std::string::npos) +{ + unsigned long hash = 0; + size_t current = 0; + + for (const char& ch : str) { + if (current >= len) + break; + + hash = ch + (hash << 6) + (hash << 16) - hash; + + current++; + } + + return hash; +} + +static int TypePreference(const std::string& type) +{ + if (type == "Value") + return 0; + else if (type == "String") + return 1; + else if (type == "double") + return 2; + else if (type.find("::Ptr") != std::string::npos) + return 3; + else if (type == "int") + return 4; + else + return 5; +} + +static bool FieldLayoutCmp(const Field& a, const Field& b) +{ + return TypePreference(a.Type.GetRealType()) < TypePreference(b.Type.GetRealType()); +} + +static bool FieldTypeCmp(const Field& a, const Field& b) +{ + return a.Type.GetRealType() < b.Type.GetRealType(); +} + +static std::string FieldTypeToIcingaName(const Field& field, bool inner) +{ + std::string ftype = field.Type.TypeName; + + if (!inner && field.Type.ArrayRank > 0) + return "Array"; + + if (field.Type.IsName) + return "String"; + + if (field.Attributes & FAEnum) + return "Number"; + + if (ftype == "bool" || ftype == "int" || ftype == "double") + return "Number"; + + if (ftype == "int" || ftype == "double") + return "Number"; + else if (ftype == "bool") + return "Boolean"; + + if (ftype.find("::Ptr") != std::string::npos) + return ftype.substr(0, ftype.size() - strlen("::Ptr")); + + return ftype; +} + +void ClassCompiler::OptimizeStructLayout(std::vector<Field>& fields) +{ + std::sort(fields.begin(), fields.end(), FieldTypeCmp); + std::stable_sort(fields.begin(), fields.end(), FieldLayoutCmp); +} + +void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&) +{ + /* forward declaration */ + if (klass.Name.find_first_of(':') == std::string::npos) + m_Header << "class " << klass.Name << ";" << std::endl << std::endl; + + /* TypeHelper */ + if (klass.Attributes & TAAbstract) { + m_Header << "template<>" << std::endl + << "struct TypeHelper<" << klass.Name << ", " << ((klass.Attributes & TAVarArgConstructor) ? "true" : "false") << ">" << std::endl + << "{" << std::endl + << "\t" << "static ObjectFactory GetFactory()" << std::endl + << "\t" << "{" << std::endl + << "\t\t" << "return nullptr;" << std::endl + << "\t" << "}" << std::endl + << "};" << std::endl << std::endl; + } + + /* TypeImpl */ + m_Header << "template<>" << std::endl + << "class TypeImpl<" << klass.Name << ">" + << " : public Type"; + + if (!klass.Parent.empty()) + m_Header << "Impl<" << klass.Parent << ">"; + + if (!klass.TypeBase.empty()) + m_Header << ", public " + klass.TypeBase; + + m_Header << std::endl + << "{" << std::endl + << "public:" << std::endl + << "\t" << "DECLARE_PTR_TYPEDEFS(TypeImpl<" << klass.Name << ">);" << std::endl << std::endl + << "\t" << "TypeImpl();" << std::endl + << "\t" << "~TypeImpl() override;" << std::endl << std::endl; + + m_Impl << "TypeImpl<" << klass.Name << ">::TypeImpl()" << std::endl + << "{ }" << std::endl << std::endl + << "TypeImpl<" << klass.Name << ">::~TypeImpl()" << std::endl + << "{ }" << std::endl << std::endl; + + /* GetName */ + m_Header << "\t" << "String GetName() const override;" << std::endl; + + m_Impl << "String TypeImpl<" << klass.Name << ">::GetName() const" << std::endl + << "{" << std::endl + << "\t" << "return \"" << klass.Name << "\";" << std::endl + << "}" << std::endl << std::endl; + + /* GetAttributes */ + m_Header << "\t" << "int GetAttributes() const override;" << std::endl; + + m_Impl << "int TypeImpl<" << klass.Name << ">::GetAttributes() const" << std::endl + << "{" << std::endl + << "\t" << "return " << klass.Attributes << ";" << std::endl + << "}" << std::endl << std::endl; + + /* GetBaseType */ + m_Header << "\t" << "Type::Ptr GetBaseType() const override;" << std::endl; + + m_Impl << "Type::Ptr TypeImpl<" << klass.Name << ">::GetBaseType() const" << std::endl + << "{" << std::endl + << "\t" << "return "; + + if (!klass.Parent.empty()) + m_Impl << klass.Parent << "::TypeInstance"; + else + m_Impl << "Object::TypeInstance"; + + m_Impl << ";" << std::endl + << "}" << std::endl << std::endl; + + /* GetFieldId */ + m_Header << "\t" << "int GetFieldId(const String& name) const override;" << std::endl; + + m_Impl << "int TypeImpl<" << klass.Name << ">::GetFieldId(const String& name) const" << std::endl + << "{" << std::endl; + + if (!klass.Fields.empty()) { + m_Impl << "\t" << "int offset = "; + + if (!klass.Parent.empty()) + m_Impl << klass.Parent << "::TypeInstance->GetFieldCount()"; + else + m_Impl << "0"; + + m_Impl << ";" << std::endl << std::endl; + } + + std::map<int, std::vector<std::pair<int, std::string> > > jumptable; + + int hlen = 0, collisions = 0; + + do { + int num = 0; + + hlen++; + jumptable.clear(); + collisions = 0; + + for (const Field& field : klass.Fields) { + auto hash = static_cast<int>(SDBM(field.Name, hlen)); + jumptable[hash].emplace_back(num, field.Name); + num++; + + if (jumptable[hash].size() > 1) + collisions++; + } + } while (collisions >= 5 && hlen < 8); + + if (!klass.Fields.empty()) { + m_Impl << "\tswitch (static_cast<int>(Utility::SDBM(name, " << hlen << "))) {" << std::endl; + + for (const auto& itj : jumptable) { + m_Impl << "\t\tcase " << itj.first << ":" << std::endl; + + for (const auto& itf : itj.second) { + m_Impl << "\t\t\t" << "if (name == \"" << itf.second << "\")" << std::endl + << "\t\t\t\t" << "return offset + " << itf.first << ";" << std::endl; + } + + m_Impl << std::endl + << "\t\t\tbreak;" << std::endl; + } + + m_Impl << "\t}" << std::endl; + } + + m_Impl << std::endl + << "\t" << "return "; + + if (!klass.Parent.empty()) + m_Impl << klass.Parent << "::TypeInstance->GetFieldId(name)"; + else + m_Impl << "-1"; + + m_Impl << ";" << std::endl + << "}" << std::endl << std::endl; + + /* GetFieldInfo */ + m_Header << "\t" << "Field GetFieldInfo(int id) const override;" << std::endl; + + m_Impl << "Field TypeImpl<" << klass.Name << ">::GetFieldInfo(int id) const" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << "int real_id = id - " << klass.Parent << "::TypeInstance->GetFieldCount();" << std::endl + << "\t" << "if (real_id < 0) { return " << klass.Parent << "::TypeInstance->GetFieldInfo(id); }" << std::endl; + + if (!klass.Fields.empty()) { + m_Impl << "\t" << "switch ("; + + if (!klass.Parent.empty()) + m_Impl << "real_id"; + else + m_Impl << "id"; + + m_Impl << ") {" << std::endl; + + size_t num = 0; + for (const Field& field : klass.Fields) { + std::string ftype = FieldTypeToIcingaName(field, false); + + std::string nameref; + + if (field.Type.IsName) + nameref = "\"" + field.Type.TypeName + "\""; + else + nameref = "nullptr"; + + m_Impl << "\t\t" << "case " << num << ":" << std::endl + << "\t\t\t" << "return {" << num << ", \"" << ftype << "\", \"" << field.Name << "\", \"" << (field.NavigationName.empty() ? field.Name : field.NavigationName) << "\", " << nameref << ", " << field.Attributes << ", " << field.Type.ArrayRank << "};" << std::endl; + num++; + } + + m_Impl << "\t\t" << "default:" << std::endl + << "\t\t"; + } + + m_Impl << "\t" << "throw std::runtime_error(\"Invalid field ID.\");" << std::endl; + + if (!klass.Fields.empty()) + m_Impl << "\t" << "}" << std::endl; + + m_Impl << "}" << std::endl << std::endl; + + /* GetFieldCount */ + m_Header << "\t" << "int GetFieldCount() const override;" << std::endl; + + m_Impl << "int TypeImpl<" << klass.Name << ">::GetFieldCount() const" << std::endl + << "{" << std::endl + << "\t" << "return " << klass.Fields.size(); + + if (!klass.Parent.empty()) + m_Impl << " + " << klass.Parent << "::TypeInstance->GetFieldCount()"; + + m_Impl << ";" << std::endl + << "}" << std::endl << std::endl; + + /* GetFactory */ + m_Header << "\t" << "ObjectFactory GetFactory() const override;" << std::endl; + + m_Impl << "ObjectFactory TypeImpl<" << klass.Name << ">::GetFactory() const" << std::endl + << "{" << std::endl + << "\t" << "return TypeHelper<" << klass.Name << ", " << ((klass.Attributes & TAVarArgConstructor) ? "true" : "false") << ">::GetFactory();" << std::endl + << "}" << std::endl << std::endl; + + /* GetLoadDependencies */ + m_Header << "\t" << "std::vector<String> GetLoadDependencies() const override;" << std::endl; + + m_Impl << "std::vector<String> TypeImpl<" << klass.Name << ">::GetLoadDependencies() const" << std::endl + << "{" << std::endl + << "\t" << "std::vector<String> deps;" << std::endl; + + for (const std::string& dep : klass.LoadDependencies) + m_Impl << "\t" << "deps.emplace_back(\"" << dep << "\");" << std::endl; + + m_Impl << "\t" << "return deps;" << std::endl + << "}" << std::endl << std::endl; + + /* GetActivationPriority */ + m_Header << "\t" << "int GetActivationPriority() const override;" << std::endl; + + m_Impl << "int TypeImpl<" << klass.Name << ">::GetActivationPriority() const" << std::endl + << "{" << std::endl + << "\t" << "return " << klass.ActivationPriority << ";" << std::endl + << "}" << std::endl << std::endl; + + /* RegisterAttributeHandler */ + m_Header << "public:" << std::endl + << "\t" << "void RegisterAttributeHandler(int fieldId, const Type::AttributeHandler& callback) override;" << std::endl; + + m_Impl << "void TypeImpl<" << klass.Name << ">::RegisterAttributeHandler(int fieldId, const Type::AttributeHandler& callback)" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << "int real_id = fieldId - " << klass.Parent << "::TypeInstance->GetFieldCount(); " << std::endl + << "\t" << "if (real_id < 0) { " << klass.Parent << "::TypeInstance->RegisterAttributeHandler(fieldId, callback); return; }" << std::endl; + + if (!klass.Fields.empty()) { + m_Impl << "\t" << "switch ("; + + if (!klass.Parent.empty()) + m_Impl << "real_id"; + else + m_Impl << "fieldId"; + + m_Impl << ") {" << std::endl; + + int num = 0; + for (const Field& field : klass.Fields) { + m_Impl << "\t\t" << "case " << num << ":" << std::endl + << "\t\t\t" << "ObjectImpl<" << klass.Name << ">::On" << field.GetFriendlyName() << "Changed.connect(callback);" << std::endl + << "\t\t\t" << "break;" << std::endl; + num++; + } + + m_Impl << "\t\t" << "default:" << std::endl + << "\t\t"; + } + m_Impl << "\t" << "throw std::runtime_error(\"Invalid field ID.\");" << std::endl; + + if (!klass.Fields.empty()) + m_Impl << "\t" << "}" << std::endl; + + m_Impl << "}" << std::endl << std::endl; + + m_Header << "};" << std::endl << std::endl; + + m_Header << std::endl; + + /* ObjectImpl */ + m_Header << "template<>" << std::endl + << "class ObjectImpl<" << klass.Name << ">" + << " : public " << (klass.Parent.empty() ? "Object" : klass.Parent) << std::endl + << "{" << std::endl + << "public:" << std::endl + << "\t" << "DECLARE_PTR_TYPEDEFS(ObjectImpl<" << klass.Name << ">);" << std::endl << std::endl; + + /* Validate */ + m_Header << "\t" << "void Validate(int types, const ValidationUtils& utils) override;" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::Validate(int types, const ValidationUtils& utils)" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << klass.Parent << "::Validate(types, utils);" << std::endl << std::endl; + + for (const Field& field : klass.Fields) { + m_Impl << "\t" << "if (" << (field.Attributes & (FAEphemeral|FAConfig|FAState)) << " & types)" << std::endl + << "\t\t" << "Validate" << field.GetFriendlyName() << "(Lazy<" << field.Type.GetRealType() << ">([this]() { return Get" << field.GetFriendlyName() << "(); }), utils);" << std::endl; + } + + m_Impl << "}" << std::endl << std::endl; + + for (const Field& field : klass.Fields) { + std::string argName, valName; + + if (field.Type.ArrayRank > 0) { + argName = "avalue"; + valName = "value"; + } else { + argName = "value"; + valName = "value()"; + } + + m_Header << "\t" << "void SimpleValidate" << field.GetFriendlyName() << "(const Lazy<" << field.Type.GetRealType() << ">& " << argName << ", const ValidationUtils& utils);" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::SimpleValidate" << field.GetFriendlyName() << "(const Lazy<" << field.Type.GetRealType() << ">& " << argName << ", const ValidationUtils& utils)" << std::endl + << "{" << std::endl; + + if (field.Attributes & FARequired) { + if (field.Type.GetRealType().find("::Ptr") != std::string::npos) + m_Impl << "\t" << "if (!" << argName << "())" << std::endl; + else + m_Impl << "\t" << "if (" << argName << "().IsEmpty())" << std::endl; + + m_Impl << "\t\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), { \"" << field.Name << R"(" }, "Attribute must not be empty."));)" << std::endl << std::endl; + } + + if (field.Attributes & FADeprecated) { + if (field.Type.GetRealType().find("::Ptr") != std::string::npos) + m_Impl << "\t" << "if (" << argName << "())" << std::endl; + else + m_Impl << "\t" << "if (" << argName << "() != GetDefault" << field.GetFriendlyName() << "())" << std::endl; + + m_Impl << "\t\t" << "Log(LogWarning, \"" << klass.Name << "\") << \"Attribute '" << field.Name << R"(' for object '" << dynamic_cast<ConfigObject *>(this)->GetName() << "' of type '" << dynamic_cast<ConfigObject *>(this)->GetReflectionType()->GetName() << "' is deprecated and should not be used.";)" << std::endl; + } + + if (field.Type.ArrayRank > 0) { + m_Impl << "\t" << "if (avalue()) {" << std::endl + << "\t\t" << "ObjectLock olock(avalue());" << std::endl + << "\t\t" << "for (const Value& value : avalue()) {" << std::endl; + } + + std::string ftype = FieldTypeToIcingaName(field, true); + + if (ftype == "Value") { + m_Impl << "\t" << "if (" << valName << ".IsObjectType<Function>()) {" << std::endl + << "\t\t" << "Function::Ptr func = " << valName << ";" << std::endl + << "\t\t" << "if (func->IsDeprecated())" << std::endl + << "\t\t\t" << "Log(LogWarning, \"" << klass.Name << "\") << \"Attribute '" << field.Name << R"(' for object '" << dynamic_cast<ConfigObject *>(this)->GetName() << "' of type '" << dynamic_cast<ConfigObject *>(this)->GetReflectionType()->GetName() << "' is set to a deprecated function: " << func->GetName();)" << std::endl + << "\t" << "}" << std::endl << std::endl; + } + + if (field.Type.IsName) { + m_Impl << "\t" << "if ("; + + if (field.Type.ArrayRank > 0) + m_Impl << valName << ".IsEmpty() || "; + else + m_Impl << "!" << valName << ".IsEmpty() && "; + + m_Impl << "!utils.ValidateName(\"" << field.Type.TypeName << "\", " << valName << "))" << std::endl + << "\t\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), { \"" << field.Name << R"(" }, "Object '" + )" << valName << R"( + "' of type ')" << field.Type.TypeName + << "' does not exist.\"));" << std::endl; + } else if (field.Type.ArrayRank > 0 && (ftype == "Number" || ftype == "Boolean")) { + m_Impl << "\t" << "try {" << std::endl + << "\t\t" << "Convert::ToDouble(" << valName << ");" << std::endl + << "\t" << "} catch (const std::invalid_argument&) {" << std::endl + << "\t\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), { \"" << field.Name << R"(", "Array element '" + " << valName << " + "' of type '" + " << valName << ".GetReflectionType()->GetName() + "' is not valid here; expected type ')" << ftype << "'.\"));" << std::endl + << "\t" << "}" << std::endl; + } + + if (field.Type.ArrayRank > 0) { + m_Impl << "\t\t" << "}" << std::endl + << "\t" << "}" << std::endl; + } + + m_Impl << "}" << std::endl << std::endl; + } + + /* constructor */ + m_Header << "public:" << std::endl + << "\t" << "ObjectImpl<" << klass.Name << ">();" << std::endl; + + m_Impl << "ObjectImpl<" << klass.Name << ">::ObjectImpl()" << std::endl + << "{" << std::endl; + + for (const Field& field : klass.Fields) { + if (!field.PureSetAccessor) + m_Impl << "\t" << "Set" << field.GetFriendlyName() << "(" << "GetDefault" << field.GetFriendlyName() << "(), true);" << std::endl; + } + + m_Impl << "}" << std::endl << std::endl; + + /* destructor */ + m_Header << "public:" << std::endl + << "\t" << "~ObjectImpl<" << klass.Name << ">() override;" << std::endl; + + m_Impl << "ObjectImpl<" << klass.Name << ">::~ObjectImpl()" << std::endl + << "{ }" << std::endl << std::endl; + + if (!klass.Fields.empty()) { + /* SetField */ + m_Header << "public:" << std::endl + << "\t" << "void SetField(int id, const Value& value, bool suppress_events = false, const Value& cookie = Empty) override;" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::SetField(int id, const Value& value, bool suppress_events, const Value& cookie)" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << "int real_id = id - " << klass.Parent << "::TypeInstance->GetFieldCount(); " << std::endl + << "\t" << "if (real_id < 0) { " << klass.Parent << "::SetField(id, value, suppress_events, cookie); return; }" << std::endl; + + m_Impl << "\t" << "switch ("; + + if (!klass.Parent.empty()) + m_Impl << "real_id"; + else + m_Impl << "id"; + + m_Impl << ") {" << std::endl; + + size_t num = 0; + for (const Field& field : klass.Fields) { + m_Impl << "\t\t" << "case " << num << ":" << std::endl + << "\t\t\t" << "Set" << field.GetFriendlyName() << "("; + + if (field.Attributes & FAEnum) + m_Impl << "static_cast<" << field.Type.GetRealType() << ">(static_cast<int>("; + + m_Impl << "value"; + + if (field.Attributes & FAEnum) + m_Impl << "))"; + + m_Impl << ", suppress_events, cookie);" << std::endl + << "\t\t\t" << "break;" << std::endl; + num++; + } + + m_Impl << "\t\t" << "default:" << std::endl + << "\t\t\t" << "throw std::runtime_error(\"Invalid field ID.\");" << std::endl + << "\t" << "}" << std::endl; + + m_Impl << "}" << std::endl << std::endl; + + /* GetField */ + m_Header << "public:" << std::endl + << "\t" << "Value GetField(int id) const override;" << std::endl; + + m_Impl << "Value ObjectImpl<" << klass.Name << ">::GetField(int id) const" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << "int real_id = id - " << klass.Parent << "::TypeInstance->GetFieldCount(); " << std::endl + << "\t" << "if (real_id < 0) { return " << klass.Parent << "::GetField(id); }" << std::endl; + + m_Impl << "\t" << "switch ("; + + if (!klass.Parent.empty()) + m_Impl << "real_id"; + else + m_Impl << "id"; + + m_Impl << ") {" << std::endl; + + num = 0; + for (const Field& field : klass.Fields) { + m_Impl << "\t\t" << "case " << num << ":" << std::endl + << "\t\t\t" << "return Get" << field.GetFriendlyName() << "();" << std::endl; + num++; + } + + m_Impl << "\t\t" << "default:" << std::endl + << "\t\t\t" << "throw std::runtime_error(\"Invalid field ID.\");" << std::endl + << "\t" << "}" << std::endl; + + m_Impl << "}" << std::endl << std::endl; + + /* ValidateField */ + m_Header << "public:" << std::endl + << "\t" << "void ValidateField(int id, const Lazy<Value>& lvalue, const ValidationUtils& utils) override;" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::ValidateField(int id, const Lazy<Value>& lvalue, const ValidationUtils& utils)" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << "int real_id = id - " << klass.Parent << "::TypeInstance->GetFieldCount(); " << std::endl + << "\t" << "if (real_id < 0) { " << klass.Parent << "::ValidateField(id, lvalue, utils); return; }" << std::endl; + + m_Impl << "\t" << "switch ("; + + if (!klass.Parent.empty()) + m_Impl << "real_id"; + else + m_Impl << "id"; + + m_Impl << ") {" << std::endl; + + num = 0; + for (const Field& field : klass.Fields) { + m_Impl << "\t\t" << "case " << num << ":" << std::endl + << "\t\t\t" << "Validate" << field.GetFriendlyName() << "("; + + if (field.Attributes & FAEnum) + m_Impl << "static_cast<Lazy<" << field.Type.GetRealType() << "> >(static_cast<Lazy<int> >("; + + m_Impl << "lvalue"; + + if (field.Attributes & FAEnum) + m_Impl << "))"; + + m_Impl << ", utils);" << std::endl + << "\t\t\t" << "break;" << std::endl; + num++; + } + + m_Impl << "\t\t" << "default:" << std::endl + << "\t\t\t" << "throw std::runtime_error(\"Invalid field ID.\");" << std::endl + << "\t" << "}" << std::endl; + + m_Impl << "}" << std::endl << std::endl; + + /* NotifyField */ + m_Header << "public:" << std::endl + << "\t" << "void NotifyField(int id, const Value& cookie = Empty) override;" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::NotifyField(int id, const Value& cookie)" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << "int real_id = id - " << klass.Parent << "::TypeInstance->GetFieldCount(); " << std::endl + << "\t" << "if (real_id < 0) { " << klass.Parent << "::NotifyField(id, cookie); return; }" << std::endl; + + m_Impl << "\t" << "switch ("; + + if (!klass.Parent.empty()) + m_Impl << "real_id"; + else + m_Impl << "id"; + + m_Impl << ") {" << std::endl; + + num = 0; + for (const Field& field : klass.Fields) { + m_Impl << "\t\t" << "case " << num << ":" << std::endl + << "\t\t\t" << "Notify" << field.GetFriendlyName() << "(cookie);" << std::endl + << "\t\t\t" << "break;" << std::endl; + num++; + } + + m_Impl << "\t\t" << "default:" << std::endl + << "\t\t\t" << "throw std::runtime_error(\"Invalid field ID.\");" << std::endl + << "\t" << "}" << std::endl; + + m_Impl << "}" << std::endl << std::endl; + + /* NavigateField */ + m_Header << "public:" << std::endl + << "\t" << "Object::Ptr NavigateField(int id) const override;" << std::endl; + + m_Impl << "Object::Ptr ObjectImpl<" << klass.Name << ">::NavigateField(int id) const" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << "int real_id = id - " << klass.Parent << "::TypeInstance->GetFieldCount(); " << std::endl + << "\t" << "if (real_id < 0) { return " << klass.Parent << "::NavigateField(id); }" << std::endl; + + bool haveNavigationFields = false; + + for (const Field& field : klass.Fields) { + if (field.Attributes & FANavigation) { + haveNavigationFields = true; + break; + } + } + + if (haveNavigationFields) { + m_Impl << "\t" << "switch ("; + + if (!klass.Parent.empty()) + m_Impl << "real_id"; + else + m_Impl << "id"; + + m_Impl << ") {" << std::endl; + + num = 0; + for (const Field& field : klass.Fields) { + if (field.Attributes & FANavigation) { + m_Impl << "\t\t" << "case " << num << ":" << std::endl + << "\t\t\t" << "return Navigate" << field.GetFriendlyName() << "();" << std::endl; + } + + num++; + } + + m_Impl << "\t\t" << "default:" << std::endl + << "\t\t"; + } + + m_Impl << "\t" << "throw std::runtime_error(\"Invalid field ID.\");" << std::endl; + + if (haveNavigationFields) + m_Impl << "\t" << "}" << std::endl; + + m_Impl << "}" << std::endl << std::endl; + + /* getters */ + for (const Field& field : klass.Fields) { + std::string prot; + + if (field.Attributes & FAGetProtected) + prot = "protected"; + else + prot = "public"; + + m_Header << prot << ":" << std::endl + << "\t"; + + if (field.Attributes & FAGetVirtual || field.PureGetAccessor) + m_Header << "virtual "; + + m_Header << field.Type.GetRealType() << " Get" << field.GetFriendlyName() << "() const"; + + if (field.PureGetAccessor) { + m_Header << " = 0;" << std::endl; + } else { + m_Header << ";" << std::endl; + + m_Impl << field.Type.GetRealType() << " ObjectImpl<" << klass.Name << ">::Get" << field.GetFriendlyName() << "() const" << std::endl + << "{" << std::endl; + + if (field.GetAccessor.empty() && !(field.Attributes & FANoStorage)) + m_Impl << "\t" << "return m_" << field.GetFriendlyName() << ".load();" << std::endl; + else + m_Impl << field.GetAccessor << std::endl; + + m_Impl << "}" << std::endl << std::endl; + } + } + + /* setters */ + for (const Field& field : klass.Fields) { + std::string prot; + + if (field.Attributes & FASetProtected) + prot = "protected"; + else + prot = "public"; + + m_Header << prot << ":" << std::endl + << "\t"; + + if (field.Attributes & FASetVirtual || field.PureSetAccessor) + m_Header << "virtual "; + + m_Header << "void Set" << field.GetFriendlyName() << "(" << field.Type.GetArgumentType() << " value, bool suppress_events = false, const Value& cookie = Empty)"; + + if (field.PureSetAccessor) { + m_Header << " = 0;" << std::endl; + } else { + m_Header << ";" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::Set" << field.GetFriendlyName() << "(" << field.Type.GetArgumentType() << " value, bool suppress_events, const Value& cookie)" << std::endl + << "{" << std::endl; + + if (field.Type.IsName || !field.TrackAccessor.empty() || field.Attributes & FASignalWithOldValue) + m_Impl << "\t" << "Value oldValue = Get" << field.GetFriendlyName() << "();" << std::endl + << "\t" << "auto *dobj = dynamic_cast<ConfigObject *>(this);" << std::endl; + + if (field.SetAccessor.empty() && !(field.Attributes & FANoStorage)) + m_Impl << "\t" << "m_" << field.GetFriendlyName() << ".store(value);" << std::endl; + else + m_Impl << field.SetAccessor << std::endl << std::endl; + + if (field.Type.IsName || !field.TrackAccessor.empty()) { + if (field.Name != "active") { + m_Impl << "\t" << "if (!dobj || dobj->IsActive())" << std::endl + << "\t"; + } + + m_Impl << "\t" << "Track" << field.GetFriendlyName() << "(oldValue, value);" << std::endl; + } + + m_Impl << "\t" << "if (!suppress_events) {" << std::endl + << "\t\t" << "Notify" << field.GetFriendlyName() << "(cookie);" << std::endl; + + if (field.Attributes & FASignalWithOldValue) { + m_Impl << "\t\t" << "if (!dobj || dobj->IsActive())" << std::endl + << "\t\t\t" << "On" << field.GetFriendlyName() << "ChangedWithOldValue(static_cast<" << klass.Name << " *>(this), oldValue, value);" << std::endl; + } + + m_Impl << "\t" "}" << std::endl << std::endl + << "}" << std::endl << std::endl; + } + } + + m_Header << "protected:" << std::endl; + + bool needs_tracking = false; + + /* tracking */ + for (const Field& field : klass.Fields) { + if (!field.Type.IsName && field.TrackAccessor.empty()) + continue; + + needs_tracking = true; + + m_Header << "\t" << "void Track" << field.GetFriendlyName() << "(" << field.Type.GetArgumentType() << " oldValue, " << field.Type.GetArgumentType() << " newValue);" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::Track" << field.GetFriendlyName() << "(" << field.Type.GetArgumentType() << " oldValue, " << field.Type.GetArgumentType() << " newValue)" << std::endl + << "{" << std::endl; + + if (!field.TrackAccessor.empty()) + m_Impl << "\t" << field.TrackAccessor << std::endl; + + if (field.Type.TypeName != "String") { + if (field.Type.ArrayRank > 0) { + m_Impl << "\t" << "if (oldValue) {" << std::endl + << "\t\t" << "ObjectLock olock(oldValue);" << std::endl + << "\t\t" << "for (const String& ref : oldValue) {" << std::endl + << "\t\t\t" << "DependencyGraph::RemoveDependency(this, ConfigObject::GetObject"; + + /* Ew */ + if (field.Type.TypeName == "Zone" && m_Library == "base") + m_Impl << "(\"Zone\", "; + else + m_Impl << "<" << field.Type.TypeName << ">("; + + m_Impl << "ref).get());" << std::endl + << "\t\t" << "}" << std::endl + << "\t" << "}" << std::endl + << "\t" << "if (newValue) {" << std::endl + << "\t\t" << "ObjectLock olock(newValue);" << std::endl + << "\t\t" << "for (const String& ref : newValue) {" << std::endl + << "\t\t\t" << "DependencyGraph::AddDependency(this, ConfigObject::GetObject"; + + /* Ew */ + if (field.Type.TypeName == "Zone" && m_Library == "base") + m_Impl << "(\"Zone\", "; + else + m_Impl << "<" << field.Type.TypeName << ">("; + + m_Impl << "ref).get());" << std::endl + << "\t\t" << "}" << std::endl + << "\t" << "}" << std::endl; + } else { + m_Impl << "\t" << "if (!oldValue.IsEmpty())" << std::endl + << "\t\t" << "DependencyGraph::RemoveDependency(this, ConfigObject::GetObject"; + + /* Ew */ + if (field.Type.TypeName == "Zone" && m_Library == "base") + m_Impl << "(\"Zone\", "; + else + m_Impl << "<" << field.Type.TypeName << ">("; + + m_Impl << "oldValue).get());" << std::endl + << "\t" << "if (!newValue.IsEmpty())" << std::endl + << "\t\t" << "DependencyGraph::AddDependency(this, ConfigObject::GetObject"; + + /* Ew */ + if (field.Type.TypeName == "Zone" && m_Library == "base") + m_Impl << "(\"Zone\", "; + else + m_Impl << "<" << field.Type.TypeName << ">("; + + m_Impl << "newValue).get());" << std::endl; + } + } + + m_Impl << "}" << std::endl << std::endl; + } + + /* navigation */ + for (const Field& field : klass.Fields) { + if ((field.Attributes & FANavigation) == 0) + continue; + + m_Header << "public:" << std::endl + << "\t" << "Object::Ptr Navigate" << field.GetFriendlyName() << "() const"; + + if (field.PureNavigateAccessor) { + m_Header << " = 0;" << std::endl; + } else { + m_Header << ";" << std::endl; + + m_Impl << "Object::Ptr ObjectImpl<" << klass.Name << ">::Navigate" << field.GetFriendlyName() << "() const" << std::endl + << "{" << std::endl; + + if (field.NavigateAccessor.empty()) + m_Impl << "\t" << "return Get" << field.GetFriendlyName() << "();" << std::endl; + else + m_Impl << "\t" << field.NavigateAccessor << std::endl; + + m_Impl << "}" << std::endl << std::endl; + } + } + + /* start/stop */ + if (needs_tracking) { + m_Header << "protected:" << std::endl + << "\tvoid Start(bool runtimeCreated = false) override;" << std::endl + << "\tvoid Stop(bool runtimeRemoved = false) override;" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::Start(bool runtimeCreated)" << std::endl + << "{" << std::endl + << "\t" << klass.Parent << "::Start(runtimeCreated);" << std::endl << std::endl; + + for (const Field& field : klass.Fields) { + if (!field.Type.IsName && field.TrackAccessor.empty()) + continue; + + m_Impl << "\t" << "Track" << field.GetFriendlyName() << "(Empty, Get" << field.GetFriendlyName() << "());" << std::endl; + } + + m_Impl << "}" << std::endl << std::endl + << "void ObjectImpl<" << klass.Name << ">::Stop(bool runtimeRemoved)" << std::endl + << "{" << std::endl + << "\t" << klass.Parent << "::Stop(runtimeRemoved);" << std::endl << std::endl; + + for (const Field& field : klass.Fields) { + if (!field.Type.IsName && field.TrackAccessor.empty()) + continue; + + m_Impl << "\t" << "Track" << field.GetFriendlyName() << "(Get" << field.GetFriendlyName() << "(), Empty);" << std::endl; + } + + m_Impl << "}" << std::endl << std::endl; + } + + /* notify */ + for (const Field& field : klass.Fields) { + std::string prot; + + if (field.Attributes & FASetProtected) + prot = "protected"; + else + prot = "public"; + + m_Header << prot << ":" << std::endl + << "\t" << "virtual void Notify" << field.GetFriendlyName() << "(const Value& cookie = Empty);" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::Notify" << field.GetFriendlyName() << "(const Value& cookie)" << std::endl + << "{" << std::endl; + + if (field.Name != "active") { + m_Impl << "\t" << "auto *dobj = dynamic_cast<ConfigObject *>(this);" << std::endl + << "\t" << "if (!dobj || dobj->IsActive())" << std::endl + << "\t"; + } + + m_Impl << "\t" << "On" << field.GetFriendlyName() << "Changed(static_cast<" << klass.Name << " *>(this), cookie);" << std::endl + << "}" << std::endl << std::endl; + } + + /* default */ + for (const Field& field : klass.Fields) { + std::string realType = field.Type.GetRealType(); + + m_Header << "private:" << std::endl + << "\t" << "inline " << realType << " GetDefault" << field.GetFriendlyName() << "() const;" << std::endl; + + m_Impl << realType << " ObjectImpl<" << klass.Name << ">::GetDefault" << field.GetFriendlyName() << "() const" << std::endl + << "{" << std::endl; + + if (field.DefaultAccessor.empty()) + m_Impl << "\t" << "return " << realType << "();" << std::endl; + else + m_Impl << "\t" << field.DefaultAccessor << std::endl; + + m_Impl << "}" << std::endl << std::endl; + } + + /* validators */ + for (const Field& field : klass.Fields) { + m_Header << "protected:" << std::endl + << "\t" << "virtual void Validate" << field.GetFriendlyName() << "(const Lazy<" << field.Type.GetRealType() << ">& lvalue, const ValidationUtils& utils);" << std::endl; + } + + /* instance variables */ + m_Header << "private:" << std::endl; + + for (const Field& field : klass.Fields) { + if (field.Attributes & FANoStorage) + continue; + + m_Header << "\tAtomicOrLocked<" << field.Type.GetRealType() << "> m_" << field.GetFriendlyName() << ";" << std::endl; + } + + /* signal */ + m_Header << "public:" << std::endl; + + for (const Field& field : klass.Fields) { + m_Header << "\t" << "static boost::signals2::signal<void (const intrusive_ptr<" << klass.Name << ">&, const Value&)> On" << field.GetFriendlyName() << "Changed;" << std::endl; + m_Impl << std::endl << "boost::signals2::signal<void (const intrusive_ptr<" << klass.Name << ">&, const Value&)> ObjectImpl<" << klass.Name << ">::On" << field.GetFriendlyName() << "Changed;" << std::endl << std::endl; + + if (field.Attributes & FASignalWithOldValue) { + m_Header << "\t" << "static boost::signals2::signal<void (const intrusive_ptr<" << klass.Name + << ">&, const Value&, const Value&)> On" << field.GetFriendlyName() << "ChangedWithOldValue;" + << std::endl; + m_Impl << std::endl << "boost::signals2::signal<void (const intrusive_ptr<" << klass.Name + << ">&, const Value&, const Value&)> ObjectImpl<" << klass.Name << ">::On" + << field.GetFriendlyName() << "ChangedWithOldValue;" << std::endl << std::endl; + } + } + } + + if (klass.Name == "ConfigObject") + m_Header << "\t" << "friend class ConfigItem;" << std::endl; + + if (!klass.TypeBase.empty()) + m_Header << "\t" << "friend class " << klass.TypeBase << ";" << std::endl; + + m_Header << "};" << std::endl << std::endl; + + for (const Field& field : klass.Fields) { + m_MissingValidators[std::make_pair(klass.Name, field.GetFriendlyName())] = field; + } +} + +void ClassCompiler::CodeGenValidator(const std::string& name, const std::string& klass, const std::vector<Rule>& rules, const std::string& field, const FieldType& fieldType, ValidatorType validatorType) +{ + m_Impl << "static void TIValidate" << name << "(const intrusive_ptr<ObjectImpl<" << klass << "> >& object, "; + + if (validatorType != ValidatorField) + m_Impl << "const String& key, "; + + m_Impl << fieldType.GetArgumentType() << " value, std::vector<String>& location, const ValidationUtils& utils)" << std::endl + << "{" << std::endl; + + if (validatorType == ValidatorField) { + bool required = false; + + for (const Rule& rule : rules) { + if ((rule.Attributes & RARequired) && rule.Pattern == field) { + required = true; + break; + } + } + + if (fieldType.GetRealType() != "int" && fieldType.GetRealType() != "double") { + if (fieldType.GetRealType() == "Value" || fieldType.GetRealType() == "String") + m_Impl << "\t" << "if (value.IsEmpty())" << std::endl; + else + m_Impl << "\t" << "if (!value)" << std::endl; + + if (required) + m_Impl << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), location, \"This attribute must not be empty.\"));" << std::endl; + else + m_Impl << "\t\t" << "return;" << std::endl; + + m_Impl << std::endl; + } + } + + if (validatorType != ValidatorField) + m_Impl << "\t" << "bool known_attribute = false;" << std::endl; + + bool type_check = false; + int i = 0; + + for (const Rule& rule : rules) { + if (rule.Attributes & RARequired) + continue; + + i++; + + if (validatorType == ValidatorField && rule.Pattern != field) + continue; + + m_Impl << "\t" << "do {" << std::endl; + + if (validatorType != ValidatorField) { + if (rule.Pattern != "*") { + if (rule.Pattern.find_first_of("*?") != std::string::npos) + m_Impl << "\t\t" << "if (!Utility::Match(\"" << rule.Pattern << "\", key))" << std::endl; + else + m_Impl << "\t\t" << "if (key != \"" << rule.Pattern << "\")" << std::endl; + + m_Impl << "\t\t\t" << "break;" << std::endl; + } + + m_Impl << "\t\t" << "known_attribute = true;" << std::endl; + } + + if (rule.IsName) { + m_Impl << "\t\t" << "if (value.IsScalar()) {" << std::endl + << "\t\t\t" << "if (utils.ValidateName(\"" << rule.Type << "\", value))" << std::endl + << "\t\t\t\t" << "return;" << std::endl + << "\t\t\t" << "else" << std::endl + << "\t\t\t\t" << R"(BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, "Object '" + ")" << "xxx" << R"( + "' of type ')" << rule.Type << "' does not exist.\"));" << std::endl + << "\t\t" << "}" << std::endl; + } + + if (fieldType.GetRealType() == "Value") { + if (rule.Type == "String") + m_Impl << "\t\t" << "if (value.IsEmpty() || value.IsScalar())" << std::endl + << "\t\t\t" << "return;" << std::endl; + else if (rule.Type == "Number") { + m_Impl << "\t\t" << "try {" << std::endl + << "\t\t\t" << "Convert::ToDouble(value);" << std::endl + << "\t\t\t" << "return;" << std::endl + << "\t\t" << "} catch (...) { }" << std::endl; + } + } + + if (rule.Type == "Dictionary" || rule.Type == "Array" || rule.Type == "Function") { + if (fieldType.GetRealType() == "Value") { + m_Impl << "\t\t" << "if (value.IsObjectType<" << rule.Type << ">()) {" << std::endl; + type_check = true; + } else if (fieldType.GetRealType() != rule.Type + "::Ptr") { + m_Impl << "\t\t" << "if (dynamic_pointer_cast<" << rule.Type << ">(value)) {" << std::endl; + type_check = true; + } + + if (!rule.Rules.empty()) { + bool indent = false; + + if (rule.Type == "Dictionary") { + if (type_check) + m_Impl << "\t\t\t" << "Dictionary::Ptr dict = value;" << std::endl; + else + m_Impl << "\t\t" << "const Dictionary::Ptr& dict = value;" << std::endl; + + m_Impl << (type_check ? "\t" : "") << "\t\t" << "{" << std::endl + << (type_check ? "\t" : "") << "\t\t\t" << "ObjectLock olock(dict);" << std::endl + << (type_check ? "\t" : "") << "\t\t\t" << "for (const Dictionary::Pair& kv : dict) {" << std::endl + << (type_check ? "\t" : "") << "\t\t\t\t" << "const String& akey = kv.first;" << std::endl + << (type_check ? "\t" : "") << "\t\t\t\t" << "const Value& avalue = kv.second;" << std::endl; + indent = true; + } else if (rule.Type == "Array") { + if (type_check) + m_Impl << "\t\t\t" << "Array::Ptr arr = value;" << std::endl; + else + m_Impl << "\t\t" << "const Array::Ptr& arr = value;" << std::endl; + + m_Impl << (type_check ? "\t" : "") << "\t\t" << "Array::SizeType anum = 0;" << std::endl + << (type_check ? "\t" : "") << "\t\t" << "{" << std::endl + << (type_check ? "\t" : "") << "\t\t\t" << "ObjectLock olock(arr);" << std::endl + << (type_check ? "\t" : "") << "\t\t\t" << "for (const Value& avalue : arr) {" << std::endl + << (type_check ? "\t" : "") << "\t\t\t\t" << "String akey = Convert::ToString(anum);" << std::endl; + indent = true; + } else { + m_Impl << (type_check ? "\t" : "") << "\t\t" << "String akey = \"\";" << std::endl + << (type_check ? "\t" : "") << "\t\t" << "const Value& avalue = value;" << std::endl; + } + + std::string subvalidator_prefix; + + if (validatorType == ValidatorField) + subvalidator_prefix = klass; + else + subvalidator_prefix = name; + + m_Impl << (type_check ? "\t" : "") << (indent ? "\t\t" : "") << "\t\t" << "location.emplace_back(akey);" << std::endl + << (type_check ? "\t" : "") << (indent ? "\t\t" : "") << "\t\t" << "TIValidate" << subvalidator_prefix << "_" << i << "(object, akey, avalue, location, utils);" << std::endl + << (type_check ? "\t" : "") << (indent ? "\t\t" : "") << "\t\t" << "location.pop_back();" << std::endl; + + if (rule.Type == "Array") + m_Impl << (type_check ? "\t" : "") << "\t\t\t\t" << "anum++;" << std::endl; + + if (rule.Type == "Dictionary" || rule.Type == "Array") { + m_Impl << (type_check ? "\t" : "") << "\t\t\t" << "}" << std::endl + << (type_check ? "\t" : "") << "\t\t" << "}" << std::endl; + } + + for (const Rule& srule : rule.Rules) { + if ((srule.Attributes & RARequired) == 0) + continue; + + if (rule.Type == "Dictionary") { + m_Impl << (type_check ? "\t" : "") << "\t\t" << "if (dict->Get(\"" << srule.Pattern << "\").IsEmpty())" << std::endl + << (type_check ? "\t" : "") << "\t\t\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, \"Required dictionary item '" << srule.Pattern << "' is not set.\"));" << std::endl; + } else if (rule.Type == "Array") { + int index = -1; + std::stringstream idxbuf; + idxbuf << srule.Pattern; + idxbuf >> index; + + if (index == -1) { + std::cerr << "Invalid index for 'required' keyword: " << srule.Pattern; + std::exit(1); + } + + m_Impl << (type_check ? "\t" : "") << "\t\t" << "if (arr.GetLength() < " << (index + 1) << ")" << std::endl + << (type_check ? "\t" : "") << "\t\t\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), location, \"Required index '" << index << "' is not set.\"));" << std::endl; + } + } + } + + m_Impl << (type_check ? "\t" : "") << "\t\t" << "return;" << std::endl; + + if (fieldType.GetRealType() == "Value" || fieldType.GetRealType() != rule.Type + "::Ptr") + m_Impl << "\t\t" << "}" << std::endl; + } + + m_Impl << "\t" << "} while (0);" << std::endl << std::endl; + } + + if (type_check || validatorType != ValidatorField) { + if (validatorType != ValidatorField) { + m_Impl << "\t" << "if (!known_attribute)" << std::endl + << "\t\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, \"Invalid attribute: \" + key));" << std::endl + << "\t" << "else" << std::endl; + } + + m_Impl << (validatorType != ValidatorField ? "\t" : "") << "\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, \"Invalid type.\"));" << std::endl; + } + + m_Impl << "}" << std::endl << std::endl; +} + +void ClassCompiler::CodeGenValidatorSubrules(const std::string& name, const std::string& klass, const std::vector<Rule>& rules) +{ + int i = 0; + + for (const Rule& rule : rules) { + if (rule.Attributes & RARequired) + continue; + + i++; + + if (!rule.Rules.empty()) { + ValidatorType subtype; + + if (rule.Type == "Array") + subtype = ValidatorArray; + else if (rule.Type == "Dictionary") + subtype = ValidatorDictionary; + else { + std::cerr << "Invalid sub-validator type: " << rule.Type << std::endl; + std::exit(EXIT_FAILURE); + } + + std::ostringstream namebuf; + namebuf << name << "_" << i; + + CodeGenValidatorSubrules(namebuf.str(), klass, rule.Rules); + + FieldType ftype; + ftype.IsName = false; + ftype.TypeName = "Value"; + CodeGenValidator(namebuf.str(), klass, rule.Rules, rule.Pattern, ftype, subtype); + } + } +} + +void ClassCompiler::HandleValidator(const Validator& validator, const ClassDebugInfo&) +{ + CodeGenValidatorSubrules(validator.Name, validator.Name, validator.Rules); + + for (const auto& it : m_MissingValidators) + CodeGenValidator(it.first.first + it.first.second, it.first.first, validator.Rules, it.second.Name, it.second.Type, ValidatorField); + + for (const auto& it : m_MissingValidators) { + m_Impl << "void ObjectImpl<" << it.first.first << ">::Validate" << it.first.second << "(const Lazy<" << it.second.Type.GetRealType() << ">& lvalue, const ValidationUtils& utils)" << std::endl + << "{" << std::endl + << "\t" << "SimpleValidate" << it.first.second << "(lvalue, utils);" << std::endl + << "\t" << "std::vector<String> location;" << std::endl + << "\t" << "location.emplace_back(\"" << it.second.Name << "\");" << std::endl + << "\t" << "TIValidate" << it.first.first << it.first.second << "(this, lvalue(), location, utils);" << std::endl + << "\t" << "location.pop_back();" << std::endl + << "}" << std::endl << std::endl; + } + + m_MissingValidators.clear(); +} + +void ClassCompiler::HandleMissingValidators() +{ + for (const auto& it : m_MissingValidators) { + m_Impl << "void ObjectImpl<" << it.first.first << ">::Validate" << it.first.second << "(const Lazy<" << it.second.Type.GetRealType() << ">& lvalue, const ValidationUtils& utils)" << std::endl + << "{" << std::endl + << "\t" << "SimpleValidate" << it.first.second << "(lvalue, utils);" << std::endl + << "}" << std::endl << std::endl; + } + + m_MissingValidators.clear(); +} + +void ClassCompiler::CompileFile(const std::string& inputpath, + const std::string& implpath, const std::string& headerpath) +{ + std::ifstream input; + input.open(inputpath.c_str(), std::ifstream::in); + + if (!input) { + std::cerr << "Could not open input file: " << inputpath << std::endl; + std::exit(EXIT_FAILURE); + } + + std::string tmpheaderpath = headerpath + ".tmp"; + std::ofstream oheader; + oheader.open(tmpheaderpath.c_str(), std::ofstream::out); + + if (!oheader) { + std::cerr << "Could not open header file: " << tmpheaderpath << std::endl; + std::exit(EXIT_FAILURE); + } + + std::string tmpimplpath = implpath + ".tmp"; + std::ofstream oimpl; + oimpl.open(tmpimplpath.c_str(), std::ofstream::out); + + if (!oimpl) { + std::cerr << "Could not open implementation file: " << tmpimplpath << std::endl; + std::exit(EXIT_FAILURE); + } + + CompileStream(inputpath, input, oimpl, oheader); + + input.close(); + oimpl.close(); + oheader.close(); + +#ifdef _WIN32 + _unlink(headerpath.c_str()); +#endif /* _WIN32 */ + + if (rename(tmpheaderpath.c_str(), headerpath.c_str()) < 0) { + std::cerr << "Could not rename header file: " << tmpheaderpath << " -> " << headerpath << std::endl; + std::exit(EXIT_FAILURE); + } + +#ifdef _WIN32 + _unlink(implpath.c_str()); +#endif /* _WIN32 */ + + if (rename(tmpimplpath.c_str(), implpath.c_str()) < 0) { + std::cerr << "Could not rename implementation file: " << tmpimplpath << " -> " << implpath << std::endl; + std::exit(EXIT_FAILURE); + } +} + +std::string ClassCompiler::BaseName(const std::string& path) +{ + char *dir = strdup(path.c_str()); + std::string result; + + if (!dir) + throw std::bad_alloc(); + +#ifndef _WIN32 + result = basename(dir); +#else /* _WIN32 */ + result = PathFindFileName(dir); +#endif /* _WIN32 */ + + free(dir); + + return result; +} + +std::string ClassCompiler::FileNameToGuardName(const std::string& fname) +{ + std::string result = fname; + std::locale locale; + + for (auto& ch : result) { + ch = std::toupper(ch, locale); + + if (ch == '.') + ch = '_'; + } + + return result; +} + +void ClassCompiler::CompileStream(const std::string& path, std::istream& input, + std::ostream& oimpl, std::ostream& oheader) +{ + input.exceptions(std::istream::badbit); + + std::string guard_name = FileNameToGuardName(BaseName(path)); + + oheader << "#ifndef " << guard_name << std::endl + << "#define " << guard_name << std::endl << std::endl; + + oheader << "#include \"base/object.hpp\"" << std::endl + << "#include \"base/type.hpp\"" << std::endl + << "#include \"base/value.hpp\"" << std::endl + << "#include \"base/array.hpp\"" << std::endl + << "#include \"base/atomic.hpp\"" << std::endl + << "#include \"base/dictionary.hpp\"" << std::endl + << "#include <boost/signals2.hpp>" << std::endl << std::endl; + + oimpl << "#include \"base/exception.hpp\"" << std::endl + << "#include \"base/objectlock.hpp\"" << std::endl + << "#include \"base/utility.hpp\"" << std::endl + << "#include \"base/convert.hpp\"" << std::endl + << "#include \"base/dependencygraph.hpp\"" << std::endl + << "#include \"base/logger.hpp\"" << std::endl + << "#include \"base/function.hpp\"" << std::endl + << "#include \"base/configtype.hpp\"" << std::endl + << "#ifdef _MSC_VER" << std::endl + << "#pragma warning( push )" << std::endl + << "#pragma warning( disable : 4244 )" << std::endl + << "#pragma warning( disable : 4800 )" << std::endl + << "#endif /* _MSC_VER */" << std::endl << std::endl; + + + ClassCompiler ctx(path, input, oimpl, oheader); + ctx.Compile(); + + oheader << "#endif /* " << guard_name << " */" << std::endl; + + oimpl << "#ifdef _MSC_VER" << std::endl + << "#pragma warning ( pop )" << std::endl + << "#endif /* _MSC_VER */" << std::endl; +} diff --git a/tools/mkclass/classcompiler.hpp b/tools/mkclass/classcompiler.hpp new file mode 100644 index 0000000..0bd789d --- /dev/null +++ b/tools/mkclass/classcompiler.hpp @@ -0,0 +1,245 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef CLASSCOMPILER_H +#define CLASSCOMPILER_H + +#include <string> +#include <istream> +#include <utility> +#include <vector> +#include <algorithm> +#include <map> + +namespace icinga +{ + +struct ClassDebugInfo +{ + std::string path; + int first_line; + int first_column; + int last_line; + int last_column; +}; + +enum FieldAccessorType +{ + FTGet, + FTSet, + FTDefault, + FTTrack, + FTNavigate +}; + +struct FieldAccessor +{ + FieldAccessorType Type; + std::string Accessor; + bool Pure; + + FieldAccessor(FieldAccessorType type, std::string accessor, bool pure) + : Type(type), Accessor(std::move(accessor)), Pure(pure) + { } +}; + +/* keep this in sync with lib/base/type.hpp */ +enum FieldAttribute +{ + FAEphemeral = 1, + FAConfig = 2, + FAState = 4, + FAEnum = 8, + FAGetProtected = 16, + FASetProtected = 32, + FANoStorage = 64, + FALoadDependency = 128, + FARequired = 256, + FANavigation = 512, + FANoUserModify = 1024, + FANoUserView = 2048, + FADeprecated = 4096, + FAGetVirtual = 8192, + FASetVirtual = 16384, + FAActivationPriority = 32768, + FASignalWithOldValue = 65536, +}; + +struct FieldType +{ + bool IsName{false}; + std::string TypeName; + int ArrayRank{0}; + + inline std::string GetRealType() const + { + if (ArrayRank > 0) + return "Array::Ptr"; + + if (IsName) + return "String"; + + return TypeName; + } + + inline std::string GetArgumentType() const + { + std::string realType = GetRealType(); + + if (realType == "bool" || realType == "double" || realType == "int") + return realType; + else + return "const " + realType + "&"; + } +}; + +struct Field +{ + int Attributes{0}; + FieldType Type; + std::string Name; + std::string AlternativeName; + std::string GetAccessor; + bool PureGetAccessor{false}; + std::string SetAccessor; + bool PureSetAccessor{false}; + std::string DefaultAccessor; + std::string TrackAccessor; + std::string NavigationName; + std::string NavigateAccessor; + bool PureNavigateAccessor{false}; + int Priority{0}; + + inline std::string GetFriendlyName() const + { + if (!AlternativeName.empty()) + return AlternativeName; + + bool cap = true; + std::string name = Name; + + for (char& ch : name) { + if (ch == '_') { + cap = true; + continue; + } + + if (cap) { + ch = toupper(ch); + cap = false; + } + } + + name.erase( + std::remove(name.begin(), name.end(), '_'), + name.end() + ); + + /* TODO: figure out name */ + return name; + } +}; + +enum TypeAttribute +{ + TAAbstract = 1, + TAVarArgConstructor = 2 +}; + +struct Klass +{ + std::string Name; + std::string Parent; + std::string TypeBase; + int Attributes; + std::vector<Field> Fields; + std::vector<std::string> LoadDependencies; + int ActivationPriority{0}; +}; + +enum RuleAttribute +{ + RARequired = 1 +}; + +struct Rule +{ + int Attributes; + bool IsName; + std::string Type; + std::string Pattern; + + std::vector<Rule> Rules; +}; + +enum ValidatorType +{ + ValidatorField, + ValidatorArray, + ValidatorDictionary +}; + +struct Validator +{ + std::string Name; + std::vector<Rule> Rules; +}; + +class ClassCompiler +{ +public: + ClassCompiler(std::string path, std::istream& input, std::ostream& oimpl, std::ostream& oheader); + ~ClassCompiler(); + + void Compile(); + + std::string GetPath() const; + + void InitializeScanner(); + void DestroyScanner(); + + void *GetScanner(); + + size_t ReadInput(char *buffer, size_t max_size); + + void HandleInclude(const std::string& path, const ClassDebugInfo& locp); + void HandleAngleInclude(const std::string& path, const ClassDebugInfo& locp); + void HandleImplInclude(const std::string& path, const ClassDebugInfo& locp); + void HandleAngleImplInclude(const std::string& path, const ClassDebugInfo& locp); + void HandleClass(const Klass& klass, const ClassDebugInfo& locp); + void HandleValidator(const Validator& validator, const ClassDebugInfo& locp); + void HandleNamespaceBegin(const std::string& name, const ClassDebugInfo& locp); + void HandleNamespaceEnd(const ClassDebugInfo& locp); + void HandleCode(const std::string& code, const ClassDebugInfo& locp); + void HandleLibrary(const std::string& library, const ClassDebugInfo& locp); + void HandleMissingValidators(); + + void CodeGenValidator(const std::string& name, const std::string& klass, const std::vector<Rule>& rules, const std::string& field, const FieldType& fieldType, ValidatorType validatorType); + void CodeGenValidatorSubrules(const std::string& name, const std::string& klass, const std::vector<Rule>& rules); + + static void CompileFile(const std::string& inputpath, const std::string& implpath, + const std::string& headerpath); + static void CompileStream(const std::string& path, std::istream& input, + std::ostream& oimpl, std::ostream& oheader); + + static void OptimizeStructLayout(std::vector<Field>& fields); + +private: + std::string m_Path; + std::istream& m_Input; + std::ostream& m_Impl; + std::ostream& m_Header; + void *m_Scanner; + + std::string m_Library; + + std::map<std::pair<std::string, std::string>, Field> m_MissingValidators; + + static unsigned long SDBM(const std::string& str, size_t len); + static std::string BaseName(const std::string& path); + static std::string FileNameToGuardName(const std::string& path); +}; + +} + +#endif /* CLASSCOMPILER_H */ + diff --git a/tools/mkclass/mkclass.cpp b/tools/mkclass/mkclass.cpp new file mode 100644 index 0000000..eb0d8f8 --- /dev/null +++ b/tools/mkclass/mkclass.cpp @@ -0,0 +1,16 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "classcompiler.hpp" +#include <iostream> + +using namespace icinga; + +int main(int argc, char **argv) +{ + if (argc < 4) { + std::cerr << "Syntax: " << argv[0] << " <input> <output-impl> <output-header>" << std::endl; + return 1; + } + + ClassCompiler::CompileFile(argv[1], argv[2], argv[3]); +} diff --git a/tools/mkembedconfig/CMakeLists.txt b/tools/mkembedconfig/CMakeLists.txt new file mode 100644 index 0000000..90fe0ce --- /dev/null +++ b/tools/mkembedconfig/CMakeLists.txt @@ -0,0 +1,24 @@ +# Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ + +set(mkembedconfig_SOURCES + mkembedconfig.c +) + +add_executable(mkembedconfig ${mkembedconfig_SOURCES}) + +set_target_properties ( + mkembedconfig PROPERTIES + FOLDER Bin +) + +macro(MKEMBEDCONFIG_TARGET EmbedInput EmbedOutput) + add_custom_command( + OUTPUT ${EmbedOutput} + COMMAND mkembedconfig + ARGS ${EmbedInput} ${CMAKE_CURRENT_BINARY_DIR}/${EmbedOutput} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS mkembedconfig ${EmbedInput} + ) + set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/${EmbedOutput} PROPERTY EXCLUDE_UNITY_BUILD TRUE) +endmacro() + diff --git a/tools/mkembedconfig/mkembedconfig.c b/tools/mkembedconfig/mkembedconfig.c new file mode 100644 index 0000000..ae23087 --- /dev/null +++ b/tools/mkembedconfig/mkembedconfig.c @@ -0,0 +1,51 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +int main(int argc, char **argv) +{ + FILE *infp, *outfp; + + if (argc < 3) { + fprintf(stderr, "Syntax: %s <in-file> <out-file>\n", argv[0]); + return EXIT_FAILURE; + } + + infp = fopen(argv[1], "r"); + + if (!infp) { + perror("fopen"); + return EXIT_FAILURE; + } + + outfp = fopen(argv[2], "w"); + + if (!outfp) { + fclose(infp); + perror("fopen"); + return EXIT_FAILURE; + } + + fprintf(outfp, "/* This file has been automatically generated\n" + " from the input file \"%s\". */\n\n", argv[1]); + fputs("#include \"config/configfragment.hpp\"\n\nnamespace {\n\nconst char *fragment = R\"CONFIG_FRAGMENT(", outfp); + + while (!feof(infp)) { + char buf[1024]; + size_t rc = fread(buf, 1, sizeof(buf), infp); + + if (rc == 0) + break; + + fwrite(buf, rc, 1, outfp); + } + + fprintf(outfp, ")CONFIG_FRAGMENT\";\n\nREGISTER_CONFIG_FRAGMENT(\"%s\", fragment);\n\n}", argv[1]); + + fclose(outfp); + fclose(infp); + + return EXIT_SUCCESS; +} diff --git a/tools/mkunity/CMakeLists.txt b/tools/mkunity/CMakeLists.txt new file mode 100644 index 0000000..8fa0f20 --- /dev/null +++ b/tools/mkunity/CMakeLists.txt @@ -0,0 +1,47 @@ +# Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ + +define_property( + SOURCE + PROPERTY EXCLUDE_UNITY_BUILD + BRIEF_DOCS "Whether to exclude the source file from unity builds" + FULL_DOCS "Specified whether a source file should be excluded from unity builds and should be built separately" +) + +if(ICINGA2_UNITY_BUILD) + set(mkunity_SOURCES + mkunity.c + ) + + add_executable(mkunity ${mkunity_SOURCES}) + + set_target_properties ( + mkunity PROPERTIES + FOLDER Bin + ) + + function(MKUNITY_TARGET Target Prefix UnityInputRef) + set(UnityInput ${${UnityInputRef}}) + set(UnityOutput ${CMAKE_CURRENT_BINARY_DIR}/${Target}_unity.cpp) + set(RealSources "") + set(UnitySources "") + foreach(UnitySource ${UnityInput}) + get_property(SourceExcluded SOURCE ${UnitySource} PROPERTY EXCLUDE_UNITY_BUILD) + if(SourceExcluded MATCHES TRUE OR NOT ${UnitySource} MATCHES "\\.(cpp|cxx|cc)\$") + list(APPEND RealSources ${UnitySource}) + else() + list(APPEND UnitySources ${UnitySource}) + endif() + endforeach() + add_custom_command( + OUTPUT ${UnityOutput} + COMMAND mkunity + ARGS ${Prefix} ${UnitySources} > ${UnityOutput}.tmp + COMMAND ${CMAKE_COMMAND} + ARGS -E copy ${UnityOutput}.tmp ${UnityOutput} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS mkunity ${UnitySources} + ) + list(APPEND RealSources ${UnityOutput}) + set(${UnityInputRef} ${RealSources} PARENT_SCOPE) + endfunction() +endif() diff --git a/tools/mkunity/mkunity.c b/tools/mkunity/mkunity.c new file mode 100644 index 0000000..cf36f60 --- /dev/null +++ b/tools/mkunity/mkunity.c @@ -0,0 +1,20 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include <stdlib.h> +#include <stdio.h> + +int main(int argc, char **argv) +{ + int i; + + if (argc < 3) { + fprintf(stderr, "Syntax: %s <prefix> [<file> ...]\n", argv[0]); + return EXIT_FAILURE; + } + + for (i = 2; i < argc; i++) { + printf("#include \"%s/%s\"\n", argv[1], argv[i]); + } + + return EXIT_SUCCESS; +} diff --git a/tools/selinux/icinga2.fc b/tools/selinux/icinga2.fc new file mode 100644 index 0000000..325728d --- /dev/null +++ b/tools/selinux/icinga2.fc @@ -0,0 +1,23 @@ +/etc/rc\.d/init\.d/icinga2 -- gen_context(system_u:object_r:icinga2_initrc_exec_t,s0) + +/usr/lib/systemd/system/icinga2.* -- gen_context(system_u:object_r:icinga2_unit_file_t,s0) + +/etc/icinga2(/.*)? gen_context(system_u:object_r:icinga2_etc_t,s0) + +/etc/icinga2/scripts(/.*)? -- gen_context(system_u:object_r:nagios_notification_plugin_exec_t,s0) + +/usr/sbin/icinga2 -- gen_context(system_u:object_r:icinga2_exec_t,s0) +/usr/lib/icinga2/sbin/icinga2 -- gen_context(system_u:object_r:icinga2_exec_t,s0) +/usr/lib/icinga2/safe-reload -- gen_context(system_u:object_r:icinga2_exec_t,s0) + +/var/lib/icinga2(/.*)? gen_context(system_u:object_r:icinga2_var_lib_t,s0) + +/var/log/icinga2(/.*)? gen_context(system_u:object_r:icinga2_log_t,s0) + +/var/run/icinga2(/.*)? gen_context(system_u:object_r:icinga2_var_run_t,s0) + +/var/run/icinga2/cmd(/.*)? gen_context(system_u:object_r:icinga2_command_t,s0) + +/var/spool/icinga2(/.*)? gen_context(system_u:object_r:icinga2_spool_t,s0) + +/var/cache/icinga2(/.*)? gen_context(system_u:object_r:icinga2_cache_t,s0) diff --git a/tools/selinux/icinga2.if b/tools/selinux/icinga2.if new file mode 100644 index 0000000..15fb034 --- /dev/null +++ b/tools/selinux/icinga2.if @@ -0,0 +1,453 @@ + +## <summary>policy for icinga2</summary> + +######################################## +## <summary> +## Execute TEMPLATE in the icinga2 domin. +## </summary> +## <param name="domain"> +## <summary> +## Domain allowed to transition. +## </summary> +## </param> +# +interface(`icinga2_domtrans',` + gen_require(` + type icinga2_t, icinga2_exec_t; + ') + + corecmd_search_bin($1) + domtrans_pattern($1, icinga2_exec_t, icinga2_t) +') + +######################################## +## <summary> +## Execute icinga2 server in the icinga2 domain. +## </summary> +## <param name="domain"> +## <summary> +## Domain allowed access. +## </summary> +## </param> +# +interface(`icinga2_initrc_domtrans',` + gen_require(` + type icinga2_initrc_exec_t; + ') + + init_labeled_script_domtrans($1, icinga2_initrc_exec_t) +') + +######################################## +## <summary> +## Execute icinga2 daemon in the icinga2 domain. +## </summary> +## <param name="domain"> +## <summary> +## Domain allowed to transition. +## </summary> +## </param> +# +interface(`icinga2_systemctl',` + gen_require(` + type icinga2_t; + type icinga2_unit_file_t; + ') + + systemd_exec_systemctl($1) + allow $1 icinga2_unit_file_t:file read_file_perms; + allow $1 icinga2_unit_file_t:service manage_service_perms; + + ps_process_pattern($1, icinga2_t) + init_dbus_chat($1) +') + +######################################## +## <summary> +## Allow the specified domain to read +## icinga2 configuration files. +## </summary> +## <param name="domain"> +## <summary> +## Domain allowed access. +## </summary> +## </param> +## <rolecap/> +# +interface(`icinga2_read_config',` + gen_require(` + type icinga2_etc_t; + ') + + files_search_etc($1) + list_dirs_pattern($1, icinga2_etc_t, icinga2_etc_t) + read_files_pattern($1, icinga2_etc_t, icinga2_etc_t) +') + +######################################## +## <summary> +## Allow the specified domain to read +## and write icinga2 configuration files. +## </summary> +## <param name="domain"> +## <summary> +## Domain allowed access. +## </summary> +## </param> +## <rolecap/> +# +interface(`icinga2_manage_config',` + gen_require(` + type icinga2_etc_t; + ') + + files_search_etc($1) + manage_dirs_pattern($1, icinga2_etc_t, icinga2_etc_t) + manage_files_pattern($1, icinga2_etc_t, icinga2_etc_t) +') + +######################################## +## <summary> +## Read icinga2's log files. +## </summary> +## <param name="domain"> +## <summary> +## Domain allowed access. +## </summary> +## </param> +## <rolecap/> +# +interface(`icinga2_read_log',` + gen_require(` + type icinga2_log_t; + ') + + logging_search_logs($1) + read_files_pattern($1, icinga2_log_t, icinga2_log_t) +') + +######################################## +## <summary> +## Append to icinga2 log files. +## </summary> +## <param name="domain"> +## <summary> +## Domain allowed access. +## </summary> +## </param> +# +interface(`icinga2_append_log',` + gen_require(` + type icinga2_log_t; + ') + + logging_search_logs($1) + append_files_pattern($1, icinga2_log_t, icinga2_log_t) +') + +######################################## +## <summary> +## Manage icinga2 log files +## </summary> +## <param name="domain"> +## <summary> +## Domain allowed access. +## </summary> +## </param> +# +interface(`icinga2_manage_log',` + gen_require(` + type icinga2_log_t; + ') + + logging_search_logs($1) + manage_dirs_pattern($1, icinga2_log_t, icinga2_log_t) + manage_files_pattern($1, icinga2_log_t, icinga2_log_t) + manage_lnk_files_pattern($1, icinga2_log_t, icinga2_log_t) +') + +######################################## +## <summary> +## Search icinga2 lib directories. +## </summary> +## <param name="domain"> +## <summary> +## Domain allowed access. +## </summary> +## </param> +# +interface(`icinga2_search_lib',` + gen_require(` + type icinga2_var_lib_t; + ') + + allow $1 icinga2_var_lib_t:dir search_dir_perms; + files_search_var_lib($1) +') + +######################################## +## <summary> +## Read icinga2 lib files. +## </summary> +## <param name="domain"> +## <summary> +## Domain allowed access. +## </summary> +## </param> +# +interface(`icinga2_read_lib_files',` + gen_require(` + type icinga2_var_lib_t; + ') + + files_search_var_lib($1) + read_files_pattern($1, icinga2_var_lib_t, icinga2_var_lib_t) +') + +######################################## +## <summary> +## Manage icinga2 lib files. +## </summary> +## <param name="domain"> +## <summary> +## Domain allowed access. +## </summary> +## </param> +# +interface(`icinga2_manage_lib_files',` + gen_require(` + type icinga2_var_lib_t; + ') + + files_search_var_lib($1) + manage_files_pattern($1, icinga2_var_lib_t, icinga2_var_lib_t) +') + +######################################## +## <summary> +## Manage icinga2 lib directories. +## </summary> +## <param name="domain"> +## <summary> +## Domain allowed access. +## </summary> +## </param> +# +interface(`icinga2_manage_lib_dirs',` + gen_require(` + type icinga2_var_lib_t; + ') + + files_search_var_lib($1) + manage_dirs_pattern($1, icinga2_var_lib_t, icinga2_var_lib_t) +') + +######################################## +## <summary> +## Manage icinga2 spool files. +## </summary> +## <param name="domain"> +## <summary> +## Domain allowed access. +## </summary> +## </param> +# +interface(`icinga2_manage_spool_files',` + gen_require(` + type icinga2_spool_t; + ') + + files_search_var_lib($1) + manage_files_pattern($1, icinga2_spool_t, icinga2_spool_t) +') + +######################################## +## <summary> +## All of the rules required to administrate +## an icinga2 environment +## </summary> +## <param name="domain"> +## <summary> +## Domain allowed access. +## </summary> +## </param> +## <param name="role"> +## <summary> +## Role allowed access. +## </summary> +## </param> +## <rolecap/> +# +interface(`icinga2_admin',` + gen_require(` + type icinga2_t; + type icinga2_initrc_exec_t; + type icinga2_log_t; + type icinga2_var_lib_t; + ') + + allow $1 icinga2_t:process { signal_perms }; + ps_process_pattern($1, icinga2_t) + + tunable_policy(`deny_ptrace',`',` + allow $1 icinga2_t:process ptrace; + ') + + icinga2_initrc_domtrans($1) + domain_system_change_exemption($1) + role_transition $2 icinga2_initrc_exec_t system_r; + allow $2 system_r; + + files_list_etc($1) + admin_pattern($1, icinga2_etc_t) + + logging_search_logs($1) + admin_pattern($1, icinga2_log_t) + + files_search_var_lib($1) + admin_pattern($1, icinga2_var_lib_t) + + admin_pattern($1, icinga2_var_run_t) + admin_pattern($1, icinga2_command_t) + admin_pattern($1, icinga2_spool_t) + admin_pattern($1, icinga2_cache_t) + + icinga2_systemctl($1) + admin_pattern($1, icinga2_unit_file_t) + allow $1 icinga2_unit_file_t:service all_service_perms; + + optional_policy(` + systemd_passwd_agent_exec($1) + systemd_read_fifo_file_passwd_run($1) + ') +') + +######################################## +### <summary> +### Send icinga2 commands through pipe +### </summary> +### <param name="domain"> +### <summary> +### Domain allowed to send commands. +### </summary> +### </param> +# +interface(`icinga2_send_commands',` + gen_require(` + type icinga2_var_run_t; + ') + + files_search_pids($1) + read_files_pattern($1, icinga2_var_run_t, icinga2_var_run_t) + read_files_pattern($1, icinga2_command_t, icinga2_command_t) + write_fifo_files_pattern($1, icinga2_command_t, icinga2_command_t) +') + +######################################## +## <summary> +## For domains icinga2 should transition to (e.g. Plugins). +## </summary> +## <param name="executable"> +## <summary> +## Context of the executable. +## </summary> +## </param> +## <param name="domain"> +## <summary> +## Domain icinga should transition to. +## </summary> +## </param> +# +interface(`icinga2_execstrans',` + gen_require(` + type icinga2_t; + ') + + domtrans_pattern(icinga2_t, $1, $2) + allow icinga2_t $2:process sigkill; +') + +###################################### +## <summary> +## Dontaudit read and write an leaked file descriptors +## </summary> +## <param name="domain"> +## <summary> +## Domain to not audit. +## </summary> +## </param> +# +interface(`icinga2_dontaudit_leaks_fifo',` + gen_require(` + type icinga2_t; + ') + + dontaudit $1 icinga2_t:fifo_file write; +') + +## <summary>Icinga2 administrator role.</summary> + +######################################## +## <summary> +## Change to the Icinga2 administrator role. +## </summary> +## <param name="role"> +## <summary> +## Role allowed access. +## </summary> +## </param> +## <rolecap/> +# +interface(`icinga2adm_role_change',` + gen_require(` + role icinga2adm_r; + ') + + allow $1 icinga2adm_r; +') + +######################################## +## <summary> +## For domains icinga2adm should transition to (e.g. Plugins). +## </summary> +## <param name="executable"> +## <summary> +## Context of the executable. +## </summary> +## </param> +## <param name="domain"> +## <summary> +## Domain icinga should transition to. +## </summary> +## </param> +# +interface(`icinga2adm_execstrans',` + gen_require(` + type icinga2adm_t; + ') + + role icinga2adm_r types $2; + allow icinga2adm_r system_r; + type_transition icinga2adm_t $1:process $2; + allow icinga2adm_t $2:process transition; + allow $2 icinga2adm_t:process sigchld; + role_transition icinga2adm_r $1 system_r; +') + +######################################## +## <summary> +## Make a TCP connection to the icinga2 port. +## </summary> +## <param name="domain"> +## <summary> +## Domain allowed access. +## </summary> +## </param> +# +interface(`corenet_tcp_connect_icinga2_port',` + gen_require(` + type icinga2_port_t; + ') + + allow $1 icinga2_port_t:tcp_socket name_connect; +') diff --git a/tools/selinux/icinga2.sh b/tools/selinux/icinga2.sh new file mode 100755 index 0000000..9d5dd59 --- /dev/null +++ b/tools/selinux/icinga2.sh @@ -0,0 +1,74 @@ +#!/bin/sh -e + +DIRNAME=`dirname $0` +cd $DIRNAME +USAGE="$0 [ --update ]" +if [ `id -u` != 0 ]; then +echo 'You must be root to run this script' +exit 1 +fi + +if [ $# -eq 1 ]; then + if [ "$1" = "--update" ] ; then + time=`ls -l --time-style="+%x %X" icinga2.te | awk '{ printf "%s %s", $6, $7 }'` + rules=`ausearch --start $time -m avc --raw -se icinga2` + if [ x"$rules" != "x" ] ; then + echo "Found avc's to update policy with" + echo -e "$rules" | audit2allow -R + echo "Do you want these changes added to policy [y/n]?" + read ANS + if [ "$ANS" = "y" -o "$ANS" = "Y" ] ; then + echo "Updating policy" + echo -e "$rules" | audit2allow -R >> icinga2.te + # Fall though and rebuild policy + else + exit 0 + fi + else + echo "No new avcs found" + exit 0 + fi + else + echo -e $USAGE + exit 1 + fi +elif [ $# -ge 2 ] ; then + echo -e $USAGE + exit 1 +fi + +echo "Building and Loading Policy" +set -x +make -f /usr/share/selinux/devel/Makefile icinga2.pp || exit +/usr/sbin/semodule -i icinga2.pp + +# Generate a man page off the installed module +sepolicy manpage -p . -d icinga2_t +# Fixing the file context on /usr/sbin/icinga2 +/sbin/restorecon -F -R -v /usr/sbin/icinga2 +/sbin/restorecon -F -R -v /usr/lib64/icinga2/sbin/icinga2 +/sbin/restorecon -F -R -v /usr/lib/icinga2/safe-reload +# Fixing the file context on /etc/rc\.d/init\.d/icinga2 +#/sbin/restorecon -F -R -v /etc/rc\.d/init\.d/icinga2 +# Fixing the file context on /usr/lib/systemd/system/icinga2.* +/sbin/restorecon -F -R -v /usr/lib/systemd/system/icinga2.* +# Fixing the file context on /etc/icinga2 +/sbin/restorecon -F -R -v /etc/icinga2 +# Fixing the file context on /var/log/icinga2 +/sbin/restorecon -F -R -v /var/log/icinga2 +# Fixing the file context on /var/lib/icinga2 +/sbin/restorecon -F -R -v /var/lib/icinga2 +# Fixing the file context on /var/run/icinga2 +/sbin/restorecon -F -R -v /var/run/icinga2 +# Fixing the file context on /var/cache/icinga2 +/sbin/restorecon -F -R -v /var/cache/icinga2 +# Fixing the file context on /var/spool/icinga2 +/sbin/restorecon -F -R -v /var/spool/icinga2 + +# Label the port 5665 +/sbin/semanage port -a -t icinga2_port_t -p tcp 5665 +/sbin/semanage port -a -t redis_port_t -p tcp 6380 + +# Generate a rpm package for the newly generated policy +pwd=$(pwd) +#rpmbuild --define "_sourcedir ${pwd}" --define "_specdir ${pwd}" --define "_builddir ${pwd}" --define "_srcrpmdir ${pwd}" --define "_rpmdir ${pwd}" --define "_buildrootdir ${pwd}/.build" -ba icinga2_selinux.spec diff --git a/tools/selinux/icinga2.te b/tools/selinux/icinga2.te new file mode 100644 index 0000000..1573256 --- /dev/null +++ b/tools/selinux/icinga2.te @@ -0,0 +1,288 @@ +policy_module(icinga2, 0.2.2) + +######################################## +# +# Declarations +# + +## <desc> +## <p> +## Allow Icinga 2 to connect to all ports +## </p> +## </desc> +gen_tunable(icinga2_can_connect_all, false) + +## <desc> +## <p> +## Allow Apache to connect to Icinga 2 API +## </p> +## </desc> +gen_tunable(httpd_can_connect_icinga2_api, true) + +## <desc> +## <p> +## Allow Apache to write into Icinga 2 Commandpipe +## </p> +## </desc> +gen_tunable(httpd_can_write_icinga2_command, true) + +## <desc> +## <p> +## Allow Icinga 2 to run plugins via sudo +## </p> +## </desc> +gen_tunable(icinga2_run_sudo, false) + +require { + type nagios_admin_plugin_t; type nagios_admin_plugin_exec_t; + type nagios_checkdisk_plugin_t; type nagios_checkdisk_plugin_exec_t; + type nagios_mail_plugin_t; type nagios_mail_plugin_exec_t; + type nagios_services_plugin_t; type nagios_services_plugin_exec_t; + type nagios_system_plugin_t; type nagios_system_plugin_exec_t; + type nagios_unconfined_plugin_t; type nagios_unconfined_plugin_exec_t; + type nagios_eventhandler_plugin_t; type nagios_eventhandler_plugin_exec_t; + type nagios_openshift_plugin_t; type nagios_openshift_plugin_exec_t; + type httpd_t; type system_mail_t; + type redis_t; type redis_var_run_t; type redis_port_t; + type devlog_t; + role staff_r; + attribute unreserved_port_type; +} + +type icinga2_t; +type icinga2_exec_t; +init_daemon_domain(icinga2_t, icinga2_exec_t) + +#permissive icinga2_t; + +type icinga2_initrc_exec_t; +init_script_file(icinga2_initrc_exec_t) + +type icinga2_unit_file_t; +systemd_unit_file(icinga2_unit_file_t) + +type icinga2_etc_t; +files_config_file(icinga2_etc_t) + +type icinga2_log_t; +logging_log_file(icinga2_log_t) + +type icinga2_var_lib_t; +files_type(icinga2_var_lib_t) + +type icinga2_var_run_t; +files_pid_file(icinga2_var_run_t) + +type icinga2_command_t; +files_type(icinga2_command_t) + +type icinga2_spool_t; +files_type(icinga2_spool_t) + +type icinga2_cache_t; +files_type(icinga2_cache_t) + +type icinga2_tmp_t; +files_tmp_file(icinga2_tmp_t) + +type icinga2_port_t; +# There is no interface for unreserved_port_type +typeattribute icinga2_port_t unreserved_port_type; +corenet_port(icinga2_port_t) + +######################################## +# +# icinga2 local policy +# +allow icinga2_t self:capability { setgid setuid sys_resource kill }; +allow icinga2_t self:process { setsched signal setrlimit }; +allow icinga2_t self:fifo_file rw_fifo_file_perms; +allow icinga2_t self:unix_dgram_socket create_socket_perms; +allow icinga2_t self:unix_stream_socket create_stream_socket_perms; + +allow icinga2_t icinga2_exec_t:file execute_no_trans; + +list_dirs_pattern(icinga2_t, icinga2_etc_t, icinga2_etc_t) +read_files_pattern(icinga2_t, icinga2_etc_t, icinga2_etc_t) +read_lnk_files_pattern(icinga2_t, icinga2_etc_t, icinga2_etc_t) + +manage_dirs_pattern(icinga2_t, icinga2_log_t, icinga2_log_t) +manage_files_pattern(icinga2_t, icinga2_log_t, icinga2_log_t) +manage_lnk_files_pattern(icinga2_t, icinga2_log_t, icinga2_log_t) +logging_log_filetrans(icinga2_t, icinga2_log_t, { dir file lnk_file }) + +manage_dirs_pattern(icinga2_t, icinga2_var_lib_t, icinga2_var_lib_t) +manage_files_pattern(icinga2_t, icinga2_var_lib_t, icinga2_var_lib_t) +manage_lnk_files_pattern(icinga2_t, icinga2_var_lib_t, icinga2_var_lib_t) +files_var_lib_filetrans(icinga2_t, icinga2_var_lib_t, { dir file lnk_file }) + +manage_dirs_pattern(icinga2_t, icinga2_var_run_t, icinga2_var_run_t) +manage_files_pattern(icinga2_t, icinga2_var_run_t, icinga2_var_run_t) +files_pid_filetrans(icinga2_t, icinga2_var_run_t, { dir file }) + +manage_dirs_pattern(icinga2_t, icinga2_command_t, icinga2_command_t) +manage_files_pattern(icinga2_t, icinga2_command_t, icinga2_command_t) +manage_fifo_files_pattern(icinga2_t, icinga2_command_t, icinga2_command_t) + +manage_dirs_pattern(icinga2_t, icinga2_spool_t, icinga2_spool_t) +manage_files_pattern(icinga2_t, icinga2_spool_t, icinga2_spool_t) +files_spool_filetrans(icinga2_t, icinga2_spool_t, { dir file }) + +manage_dirs_pattern(icinga2_t, icinga2_cache_t, icinga2_cache_t) +manage_files_pattern(icinga2_t, icinga2_cache_t, icinga2_cache_t) + +manage_files_pattern(icinga2_t, icinga2_tmp_t, icinga2_tmp_t) +manage_dirs_pattern(icinga2_t, icinga2_tmp_t, icinga2_tmp_t) +files_tmp_filetrans(icinga2_t, icinga2_tmp_t, { dir file }) + +domain_use_interactive_fds(icinga2_t) + +files_read_etc_files(icinga2_t) + +auth_use_nsswitch(icinga2_t) + +miscfiles_read_localization(icinga2_t) + +corecmd_exec_shell(icinga2_t) +corecmd_exec_bin(icinga2_t) + +kernel_read_system_state(icinga2_t) +kernel_read_network_state(icinga2_t) +kernel_dgram_send(icinga2_t) + +# should be moved to nagios_plugin_template in nagios.if +icinga2_execstrans(nagios_admin_plugin_exec_t, nagios_admin_plugin_t) +icinga2_execstrans(nagios_checkdisk_plugin_exec_t, nagios_checkdisk_plugin_t) +icinga2_execstrans(nagios_mail_plugin_exec_t, nagios_mail_plugin_t) +icinga2_execstrans(nagios_services_plugin_exec_t, nagios_services_plugin_t) +icinga2_execstrans(nagios_system_plugin_exec_t, nagios_system_plugin_t) +icinga2_execstrans(nagios_unconfined_plugin_exec_t, nagios_unconfined_plugin_t) +icinga2_execstrans(nagios_eventhandler_plugin_exec_t, nagios_eventhandler_plugin_t) +icinga2_execstrans(nagios_openshift_plugin_exec_t, nagios_openshift_plugin_t) + +# should be moved nagios.te +nagios_plugin_template(notification) +icinga2_execstrans(nagios_notification_plugin_exec_t, nagios_notification_plugin_t) +allow nagios_notification_plugin_t icinga2_etc_t:dir search; +allow nagios_notification_plugin_t nagios_notification_plugin_exec_t:dir search; +#permissive nagios_notification_plugin_t; +corecmd_exec_bin(nagios_notification_plugin_t) +hostname_exec(nagios_notification_plugin_t) +type nagios_notification_plugin_tmp_t; +files_tmp_file(nagios_notification_plugin_tmp_t) +manage_files_pattern(nagios_notification_plugin_t, nagios_notification_plugin_tmp_t, nagios_notification_plugin_tmp_t) +manage_dirs_pattern(nagios_notification_plugin_t, nagios_notification_plugin_tmp_t, nagios_notification_plugin_tmp_t) +files_tmp_filetrans(nagios_notification_plugin_t, nagios_notification_plugin_tmp_t, { dir file }) +fs_dontaudit_getattr_xattr_fs(nagios_notification_plugin_t) +optional_policy(` + mta_send_mail(nagios_notification_plugin_t) +') +icinga2_dontaudit_leaks_fifo(system_mail_t) +# direct smtp notification +corenet_tcp_connect_smtp_port(nagios_notification_plugin_t) +# hipsaint notification +auth_read_passwd(nagios_notification_plugin_t) +sysnet_read_config(nagios_notification_plugin_t) +allow nagios_notification_plugin_t self:udp_socket create_stream_socket_perms; +allow nagios_notification_plugin_t self:tcp_socket create_stream_socket_perms; +allow nagios_notification_plugin_t self:netlink_route_socket create_netlink_socket_perms; +corenet_tcp_connect_http_port(nagios_notification_plugin_t) +miscfiles_read_generic_certs(nagios_notification_plugin_t) + +allow icinga2_t icinga2_port_t:tcp_socket name_bind; +allow icinga2_t self:tcp_socket create_stream_socket_perms; +corenet_tcp_connect_icinga2_port(icinga2_t) + +mysql_stream_connect(icinga2_t) +mysql_tcp_connect(icinga2_t) +postgresql_stream_connect(icinga2_t) +postgresql_tcp_connect(icinga2_t) + +# graphite is using port 2003 which is lmtp_port_t +corenet_tcp_connect_lmtp_port(icinga2_t) + +# Allow icinga2 to connect to redis using unix domain sockets +stream_connect_pattern(icinga2_t, redis_var_run_t, redis_var_run_t, redis_t) + +# Just like `redis_tcp_connect(icinga2_t)`, though this interface does not exist on centos7 +corenet_tcp_recvfrom_labeled(icinga2_t, redis_t) +corenet_tcp_sendrecv_redis_port(icinga2_t) +corenet_tcp_connect_redis_port(icinga2_t) + +# This is for other feature that do not use a confined port +# or if you run one one with a non standard port. +tunable_policy(`icinga2_can_connect_all',` + corenet_tcp_connect_all_ports(icinga2_t) +') + +# This is for plugins requiring to be executed via sudo +tunable_policy(`icinga2_run_sudo',` + allow icinga2_t self:capability { audit_write net_admin }; + allow icinga2_t self:netlink_audit_socket { create_netlink_socket_perms nlmsg_relay }; + allow icinga2_t devlog_t:sock_file write; + + init_read_utmp(icinga2_t) + + auth_domtrans_chkpwd(icinga2_t) + allow icinga2_t chkpwd_t:process { noatsecure rlimitinh siginh }; + + selinux_compute_access_vector(icinga2_t) + + dbus_send_system_bus(icinga2_t) + dbus_stream_connect_system_dbusd(icinga2_t) + systemd_dbus_chat_logind(icinga2_t) + # Without this it works but is very slow + systemd_write_inherited_logind_sessions_pipes(icinga2_t) +') + +optional_policy(` + tunable_policy(`icinga2_run_sudo',` + sudo_exec(icinga2_t) + ') +') + + + +######################################## +# +# Icinga Webinterfaces +# + +optional_policy(` + # should be a boolean in apache-policy + tunable_policy(`httpd_can_write_icinga2_command',` + icinga2_send_commands(httpd_t) + ') +') + +optional_policy(` + # should be a boolean in apache-policy + tunable_policy(`httpd_can_connect_icinga2_api',` + corenet_tcp_connect_icinga2_port(httpd_t) + ') +') + +######################################## +# +# Icinga2 Admin Role +# + +userdom_unpriv_user_template(icinga2adm) + +icinga2_admin(icinga2adm_t, icinga2adm_r) + +allow icinga2adm_t self:capability { dac_read_search dac_override }; + +# should be moved to staff.te +icinga2adm_role_change(staff_r) + +# should be moved to nagios_plugin_template in nagios.if +icinga2adm_execstrans(nagios_admin_plugin_exec_t, nagios_admin_plugin_t) +icinga2adm_execstrans(nagios_checkdisk_plugin_exec_t, nagios_checkdisk_plugin_t) +icinga2adm_execstrans(nagios_mail_plugin_exec_t, nagios_mail_plugin_t) +icinga2adm_execstrans(nagios_services_plugin_exec_t, nagios_services_plugin_t) +icinga2adm_execstrans(nagios_system_plugin_exec_t, nagios_system_plugin_t) +icinga2adm_execstrans(nagios_unconfined_plugin_exec_t, nagios_unconfined_plugin_t) +icinga2adm_execstrans(nagios_eventhandler_plugin_exec_t, nagios_eventhandler_plugin_t) +icinga2adm_execstrans(nagios_openshift_plugin_exec_t, nagios_openshift_plugin_t) +icinga2adm_execstrans(nagios_notification_plugin_exec_t, nagios_notification_plugin_t) diff --git a/tools/syntax/nano/icinga2.nanorc b/tools/syntax/nano/icinga2.nanorc new file mode 100644 index 0000000..cbc743b --- /dev/null +++ b/tools/syntax/nano/icinga2.nanorc @@ -0,0 +1,157 @@ +### Nano synteax file +### Icinga2 object configuration file + +syntax "icinga2" "/etc/icinga2/.*\.conf$" "/usr/share/icinga2/include/(plugin|itl|.*\.conf$)" + +## objects types +icolor brightgreen "object[ \t]+(host|hostgroup|service|servicegroup|user|usergroup)" +icolor brightgreen "object[ \t]+(checkcommand|notificationcommand|eventcommand|notification)" +icolor brightgreen "object[ \t]+(timeperiod|scheduleddowntime|dependency|perfdatawriter)" +icolor brightgreen "object[ \t]+(graphitewriter|idomysqlconnection|idomysqlconnection)" +icolor brightgreen "object[ \t]+(livestatuslistener|statusdatawriter|externalcommandlistener)" +icolor brightgreen "object[ \t]+(compatlogger|checkresultreader|checkcomponent|notificationcomponent)" +icolor brightgreen "object[ \t]+(filelogger|sysloglogger|apilistener|endpoint|zone)" + +## apply def +icolor brightgreen "apply[ \t]+(Service|Dependency|Notification|ScheduledDowntime)" + + +## objects attributes +icolor red "(^|^\s+)(accept_commands|accept_config|action_url|address|address6|arguments|author|bind_host)" +icolor red "(^|^\s+)(bind_port|ca_path|categories|cert_path|check_command|check_interval)" +icolor red "(^|^\s+)(check_period|child_host_name|child_service_name|cleanup|command|command_endpoint|command_path)" +icolor red "(^|^\s+)(comment|compat_log_path|crl_path|database|disable_checks|disable_notifications)" +icolor red "(^|^\s+)(display_name|duration|email|enable_active_checks|enable_event_handler)" +icolor red "(^|^\s+)(enable_flapping|enable_ha|enable_notifications|enable_passive_checks|enable_perfdata)" +icolor red "(^|^\s+)(endpoints|env|event_command|failover_timeout|fixed|flapping_threshold|groups|host)" +icolor red "(^|^\s+)(host_format_template|host_name|host_name_template|host_perfdata_path|host_temp_path|icon_image)" +icolor red "(^|^\s+)(icon_image_alt|instance_description|instance_name|interval|key_path|log_dir)" +icolor red "(^|^\s+)(log_duration|max_check_attempts|methods|name|notes|notes_url|objects_path)" +icolor red "(^|^\s+)(pager|parent|parent_host_name|parent_service_name|password|path|period)" +icolor red "(^|^\s+)(port|ranges|retry_interval|rotation_interval|rotation_method)" +icolor red "(^|^\s+)(service_format_template|service_name|service_name_template|service_perfdata_path|service_temp_path)" +icolor red "(^|^\s+)(severity|socket_path|socket_type|spool_dir|states|status_path|table_prefix)" +icolor red "(^|^\s+)(timeout|times|types|update_interval|user|user_groups|users|volatile|zone)" +icolor red "(^|^\s+)(vars\.\w+)" + + +## keywords +icolor red "(^|^\s+)|(icinga2Keyword|template|const|import|include|include_recursive|var|function|return|to|use|locals|globals|this)\s+" + +## Assign conditions +icolor magenta "(assign|ignore)[ \t]+where" + +## Global functions +icolor white "(regex|match|len|union|intersection|keys|string|number|bool|random|log|typeof|get_time|exit)" + +## Accessor Functions +icolor white "(get_host|get_service|get_user|get_check_command|get_event_command|get_notification_command)" +icolor white "(get_host_group|get_service_group|get_user_group|get_time_period)" + + +## Math functions +icolor white "(Math.E|Math.LN2|Math.LN10|Math.LOG2E|Math.PI|Math.SQRT1_2|Math.SQRT2)" +icolor white "(Math.abs|Math.acos|Math.asin|Math.atan|Math.atan2|Math.ceil|Math.cos)" +icolor white "(Math.exp|Math.floor|Math.isinf|Math.isnan|Math.log|Math.max|Math.min)" +icolor white "(Math.pow|Math.random|Math.round|Math.sign|Math.sin|Math.sqrt|Math.tan)" + +## Json functions +icolor white "(Json.encode|Json.decode)" + +## String functions +icolor white "(\.to_string)" +icolor white "(\.find)" +icolor white "(\.contains)" +icolor white "(\.len)" +icolor white "(\.lower)" +icolor white "(\.upper)" +icolor white "(\.replace)" +icolor white "(\.split)" +icolor white "(\.substr)" + +## Array and Dict Functions +icolor white "(\.add)" +icolor white "(\.clear)" +icolor white "(\.clone)" +icolor white "(\.contains)" +icolor white "(\.len)" +icolor white "(\.remove)" +icolor white "(\.set)" +icolor white "(\.remove)" +icolor white "(\.sort)" +icolor white "(\.join)" +icolor white "(\.clone)" +icolor white "(\.call)" +icolor white "(\.callv)" + + +## Conditional statements +icolor white "(if|else)" + +## Loops +icolor white "(while|for|break|continue)" + +## Operators +icolor green "\s(\.)\s" +icolor green "\s(!)\s" +icolor green "\s(\~)\s" +icolor green "\s(\+)\s" +icolor green "\s(-)\s" +icolor green "\s(\*)\s" +icolor green "\s(/)\s" +icolor green "\s(%)\s" +icolor green "\s(=)\s" +icolor green "\s(<)\s" +icolor green "\s(>)\s" +icolor green "\s(<<)\s" +icolor green "\s(>>)\s" +icolor green "\s(<=)\s" +icolor green "\s(>=)\s" +icolor green "\s(in)\s" +icolor green "\s(!in)\s" +icolor green "\s(==)\s" +icolor green "\s(!=)\s" +icolor green "\s(&)\s" +icolor green "\s(\^)\s" +icolor green "\s(|)\s" +icolor green "\s(&&)\s" +icolor green "\s(||)\s" +icolor green "\s(=>)\s" +icolor green "\s(\+=)\s" +icolor green "\s(-=)\s" +icolor green "\s(\*=)\s" +icolor green "\s(/=)\s" + +## Global constats +icolor yellow "(PrefixDir|SysconfDir|ZonesDir|LocalStateDir|RunDir|PkgDataDir|StatePath|ObjectsPath)" +icolor yellow "(PidPath|NodeName|ApplicationType|EnableNotifications|EnableEventHandlers|EnableFlapping)" +icolor yellow "(EnableHostChecks|EnableServiceChecks|EnablePerfdata|UseVfork|RunAsUser|RunAsGroup|PluginDir)" +icolor yellow "(Vars\s+)" + +## Boolean +icolor blue "(true|false)" + +# Null +icolor blue "(null)" + + +## comments +color brightblue "\/\/.*" +color brightblue "^[ \t]*\*\($\|[ \t]\+\)" +color brightblue start="/\*" end="\*/" + +## Braces and Parens definition +# - Braces are used in dictionary definition + +color magenta "(\(|\))" +color magenta "(\[|\])" +color magenta "(\{|\})" + +## type definitions +# - double quotes " +# - single quotes ' +# - brackets <> + +color brightyellow "'" +color brightyellow """ +color brightyellow start="<" end=">" diff --git a/tools/syntax/vim/ftdetect/icinga2.vim b/tools/syntax/vim/ftdetect/icinga2.vim new file mode 100644 index 0000000..5a78d0a --- /dev/null +++ b/tools/syntax/vim/ftdetect/icinga2.vim @@ -0,0 +1,2 @@ +" Modify +au BufNewFile,BufRead /*etc/icinga2/*.conf,/*usr/share/icinga2/include/{itl,plugins,*.conf} set filetype=icinga2 diff --git a/tools/syntax/vim/syntax/icinga2.vim b/tools/syntax/vim/syntax/icinga2.vim new file mode 100644 index 0000000..3cbb8e6 --- /dev/null +++ b/tools/syntax/vim/syntax/icinga2.vim @@ -0,0 +1,360 @@ +" Vim syntax file +" Filename: icinga2.vim +" Language: Icinga2 object configuration file +" Author: Carlos Cesario <carloscesario@gmail.com>, Michael Friedrich <michael.friedrich@icinga.com> +" Version: 1.0.0 +" Based: javascript.vim / nagios.vim + +" For version 5.x: Clear all syntax items +" For version 6.x: Quit when a syntax file was already loaded +if !exists("main_syntax") + if version < 600 + syntax clear + elseif exists("b:current_syntax") + finish + endif + let main_syntax = 'icinga2' +endif + +" case off +syntax case ignore + +" ######################################## +" ### General settings + +" comments +syn keyword icinga2CommentTodo TODO FIXME XXX TBD contained +syn match icinga2LineComment "\/\/.*" contains=icinga2CommentTodo +syn match icinga2LineComment "#.*" contains=icinga2CommentTodo +syn match icinga2CommentSkip "^[ \t]*\*\($\|[ \t]\+\)" +syn region icinga2Comment start="/\*" end="\*/" contains=icinga2CommentTodo + +" type definitions +" - double quotes " +" - single quotes ' +" - brackets <> + +syn match angleBrackets "<.*>" +syn region macro start=+\$+ end=+\$+ oneline +syn region StringD start=+"+ end=+"\|$+ contains=macro +syn region StringS start=+'+ end=+'\|$+ contains=macro + + +" Braces and Parens definition +" Braces are used in dictionary definition + +syn match Braces "[{}\[\]]" +syn match Parens "[()]" +syn match Lambda "{{}}" + + +" ######################################## +" ### Match objects, attributes and keywords + +" Object types +syn keyword icinga2ObjType ApiListener ApiUser CheckCommand CheckerComponent +syn keyword icinga2ObjType Comment Dependency Downtime ElasticsearchWriter +syn keyword icinga2ObjType Endpoint EventCommand ExternalCommandListener +syn keyword icinga2ObjType FileLogger GelfWriter GraphiteWriter Host HostGroup +syn keyword icinga2ObjType IcingaApplication IdoMysqlConnection IdoPgsqlConnection +syn keyword icinga2ObjType InfluxdbWriter Influxdb2Writer LivestatusListener Notification NotificationCommand +syn keyword icinga2ObjType NotificationComponent OpenTsdbWriter PerfdataWriter +syn keyword icinga2ObjType ScheduledDowntime Service ServiceGroup SyslogLogger +syn keyword icinga2ObjType TimePeriod User UserGroup WindowsEventLogLogger Zone + +" Object/Template marker (simplified) +syn match icinga2ObjDef "\(object\|template\)[ \t]\+.*" + +" Apply rules +syn match icinga2ApplyDef "apply[ \t]\+\(Service\|Dependency\|Notification\|ScheduledDowntime\)" + + +" Objects attributes +" +" find . -type f -name '*.ti' -exec sh -c 'grep config {}' \; +" Don't add 'host', etc. from apply rules here, they should match icinga2ObjType instead. +syn keyword icinga2ObjAttr contained accept_commands accept_config access_control_allow_origin action_url address address6 arguments author bind_host +syn keyword icinga2ObjAttr contained bind_port ca_path categories cert_path check_command check_interval +syn keyword icinga2ObjAttr contained check_period check_timeout child_host_name child_options child_service_name cipher_list +syn keyword icinga2ObjAttr contained cleanup client_cn command command_endpoint command_path +syn keyword icinga2ObjAttr contained comment compat_log_path concurrent_checks crl_path database disable_checks disable_notifications +syn keyword icinga2ObjAttr contained display_name duration email enable_active_checks enable_event_handlers enable_event_handler +syn keyword icinga2ObjAttr contained enable_flapping enable_ha enable_host_checks enable_notifications enable_passive_checks enable_perfdata +syn keyword icinga2ObjAttr contained enable_send_metadata enable_send_perfdata enable_send_thresholds enable_tls enable_service_checks +syn keyword icinga2ObjAttr contained endpoints env event_command excludes failover_timeout fixed flapping_threshold_low flapping_threshold_high +syn keyword icinga2ObjAttr contained flush_interval flush_threshold global groups +syn keyword icinga2ObjAttr contained host_format_template host_name host_name_template host_perfdata_path host_temp_path host_template icon_image +syn keyword icinga2ObjAttr contained icon_image_alt ignore_soft_states includes index instance_description instance_name interval key_path log_dir +syn keyword icinga2ObjAttr contained log_duration max_anonymous_clients max_check_attempts methods name notes notes_url objects_path +syn keyword icinga2ObjAttr contained pager parent parent_host_name parent_service_name password path period permissions +syn keyword icinga2ObjAttr contained port prefer_includes ranges retry_interval rotation_interval rotation_method +syn keyword icinga2ObjAttr contained service_format_template service_name service_name_template service_perfdata_path service_temp_path service_template +syn keyword icinga2ObjAttr contained severity socket_path socket_type source spool_dir +syn keyword icinga2ObjAttr contained ssl_ca ssl_capath ssl_ca_cert ssl_cert ssl_cipher ssl_enable ssl_mode ssl_key +syn keyword icinga2ObjAttr contained states status_path table_prefix ticket_salt +syn keyword icinga2ObjAttr contained timeout times tls_handshake_timeout tls_protocolmin +syn keyword icinga2ObjAttr contained types update_interval user user_groups username users volatile zone +syn match icinga2ObjAttr contained "\(vars.\w\+\)" + +" keywords: https://icinga.com/docs/icinga2/latest/doc/17-language-reference/#reserved-keywords +syn keyword icinga2Keyword object template include include_recursive include_zones library +syn keyword icinga2Keyword const var this globals locals use default ignore_on_error +syn keyword icinga2Keyword current_filename current_line apply to where import assign +syn keyword icinga2Keyword ignore function return in + + +" Assign conditions +syn match icinga2AssignCond contained "\(assign[ \t]\+\where\|ignore[ \t]\+\where\)" + + +" Documentation reference: https://icinga.com/docs/icinga2/latest/doc/18-library-reference/ + +" Global functions +syn keyword icinga2GFunction contained regex match cidr_match range len union intersection keys string +syn keyword icinga2GFunction contained number bool random log typeof get_time parse_performance_data dirname +syn keyword icinga2GFunction contained basename path_exists glob glob_recursive +syn keyword icinga2GFunction contained escape_shell_arg escape_shell_cmd escape_create_process_arg sleep exit +syn keyword icinga2GFunction contained macro + + +" Accessor Functions +syn keyword icinga2AFunction contained get_check_command get_event_command get_notification_command +syn keyword icinga2AFunction contained get_host get_service get_services get_user +syn keyword icinga2AFunction contained get_host_group get_service_group get_user_group +syn keyword icinga2AFunction contained get_timeperiod +syn keyword icinga2AFunction contained get_object get_objects + +" Math functions +syn match icinga2MathFunction contained "\(Math.E\|Math.LN2\|Math.LN10\|Math.LOG2E\|Math.PI\|Math.SQRT1_2\|Math.SQRT2\)" +syn match icinga2MathFunction contained "\(Math.abs\|Math.acos\|Math.asin\|Math.atan\|Math.atan2\|Math.ceil\|Math.cos\)" +syn match icinga2MathFunction contained "\(Math.exp\|Math.floor\|Math.isinf\|Math.isnan\|Math.log\|Math.max\|Math.min\)" +syn match icinga2MathFunction contained "\(Math.pow\|Math.random\|Math.round\|Math.sign\|Math.sin\|Math.sqrt\|Math.tan\)" + +" Json functions +syn match icinga2JsonFunction contained "\(Json.encode\|Json.decode\)" + +" Number functions +syn match icinga2NumberFunction contained "\(\.to_string\)" + +" Boolean functions +syn match icinga2BoolFunction contained "\(\.to_string\)" + +" String functions +syn match icinga2StrFunction contained "\(\.find\)" +syn match icinga2StrFunction contained "\(\.contains\)" +syn match icinga2StrFunction contained "\(\.len\)" +syn match icinga2StrFunction contained "\(\.lower\)" +syn match icinga2StrFunction contained "\(\.upper\)" +syn match icinga2StrFunction contained "\(\.replace\)" +syn match icinga2StrFunction contained "\(\.split\)" +syn match icinga2StrFunction contained "\(\.substr\)" +syn match icinga2StrFunction contained "\(\.to_string\)" +syn match icinga2StrFunction contained "\(\.reverse\)" +syn match icinga2StrFunction contained "\(\.trim\)" + +" Object functions +syn match icinga2ObjectFunction contained "\(\.clone\)" +syn match icinga2ObjectFunction contained "\(\.to_string\)" +syn match icinga2ObjectFunction contained "\(\.type\)" + +" Type functions +syn match icinga2TypeFunction contained "\(\.base\)" +" needs an exception for 'vars' +syn match icinga2TypeFunction contained "\(^[vars]\.name\)" +syn match icinga2TypeFunction contained "\(\.prototype\)" + +" Array functions +syn match icinga2ArrFunction contained "\(\.add(\)" +syn match icinga2ArrFunction contained "\(\.clear\)" +syn match icinga2ArrFunction contained "\(\.shallow_clone\)" +syn match icinga2ArrFunction contained "\(\.contains\)" +syn match icinga2ArrFunction contained "\(\.freeze\)" +syn match icinga2ArrFunction contained "\(\.len\)" +syn match icinga2ArrFunction contained "\(\.remove\)" +syn match icinga2ArrFunction contained "\(\.set\)" +syn match icinga2ArrFunction contained "\(\.get\)" +syn match icinga2ArrFunction contained "\(\.sort\)" +syn match icinga2ArrFunction contained "\(\.join\)" +syn match icinga2ArrFunction contained "\(\.reverse\)" +syn match icinga2ArrFunction contained "\(\.map\)" +syn match icinga2ArrFunction contained "\(\.reduce\)" +syn match icinga2ArrFunction contained "\(\.filter\)" +syn match icinga2ArrFunction contained "\(\.any\)" +syn match icinga2ArrFunction contained "\(\.all\)" + +" Dictionary functions +syn match icinga2DictFunction contained "\(\.shallow_clone\)" +syn match icinga2DictFunction contained "\(\.contains\)" +syn match icinga2DictFunction contained "\(\.freeze\)" +syn match icinga2DictFunction contained "\(\.len\)" +syn match icinga2DictFunction contained "\(\.remove\)" +syn match icinga2DictFunction contained "\(\.set\)" +syn match icinga2DictFunction contained "\(\.get\)" +syn match icinga2DictFunction contained "\(\.keys\)" +syn match icinga2DictFunction contained "\(\.values\)" + +" Function functions +syn match icinga2FuncFunction contained "\(\.call\)" +syn match icinga2FuncFunction contained "\(\.callv\)" + +" DateTime functions +syn match icinga2DTFunction contained "\(DateTime\)" +syn match icinga2DTFunction contained "\(\.format\)" +syn match icinga2DTFunction contained "\(\.to_string\)" + +" Conditional statements +syn keyword icinga2Cond if else + +" Loops +syn keyword icinga2Loop while for break continue + +" Exceptions +syn keyword icinga2Exception throw try except + +" Debugger +syn keyword icinga2Debugger debugger + +" References +syn keyword icinga2Reference & * +" Namespace +syn keyword icinga2Namespace namespace using + +" Operators +syn match icinga2Operators "[ \t]\+\(\.\)\+" +syn match icinga2Operators "[ \t]\+\(!\)\+" +syn match icinga2Operators "[ \t]\+\(\~\)\+" +syn match icinga2Operators "[ \t]\+\(+\)\+" +syn match icinga2Operators "[ \t]\+\(-\)\+" +syn match icinga2Operators "[ \t]\+\(*\)\+" +syn match icinga2Operators "[ \t]\+\(/[^/\*]\)\+" +syn match icinga2Operators "[ \t]\+\(%\)\+" +syn match icinga2Operators "[ \t]\+\(+\)\+" +syn match icinga2Operators "[ \t]\+\(-\)\+" +syn match icinga2Operators "[ \t]\+\(=\)\+" +syn match icinga2Operators "[ \t]\+\(<\)[ \t]\+" +syn match icinga2Operators "[ \t]\+\(>\)[ \t]\+" +syn match icinga2Operators "[ \t]\+\(<<\)\+" +syn match icinga2Operators "[ \t]\+\(>>\)\+" +syn match icinga2Operators "[ \t]\+\(<=\)\+" +syn match icinga2Operators "[ \t]\+\(>=\)\+" +syn match icinga2Operators "[ \t]\+\(in\)\+" +syn match icinga2Operators "[ \t]\+\(!in\)\+" +syn match icinga2Operators "[ \t]\+\(==\)\+" +syn match icinga2Operators "[ \t]\+\(!=\)\+" +syn match icinga2Operators "[ \t]\+\(&\)\+" +syn match icinga2Operators "[ \t]\+\(\^\)\+" +syn match icinga2Operators "[ \t]\+\(|\)\+" +syn match icinga2Operators "[ \t]\+\(&&\)\+" +syn match icinga2Operators "[ \t]\+\(||\)\+" +syn match icinga2Operators "[ \t]\+\(=>\)\+" +syn match icinga2Operators "[ \t]\+\(+=\)\+" +syn match icinga2Operators "[ \t]\+\(-=\)\+" +syn match icinga2Operators "[ \t]\+\(*=\)\+" +syn match icinga2Operators "[ \t]\+\(/=\)\+" + + +" ######################################## +" ### Global settings + +" Global constants +" https://icinga.com/docs/icinga2/snapshot/doc/17-language-reference/#icinga-2-specific-constants + +" Path specific constants +syn keyword icinga2PathConstant CacheDir ConfigDir DataDir IncludeConfDir InitRunDir LocalStateDir LogDir ModAttrPath +syn keyword icinga2PathConstant ObjectsPath PidPath PkgDataDir PrefixDir ProgramData RunDir SpoolDir StatePath SysconfDir +syn keyword icinga2PathConstant VarsPath ZonesDir + +" Global constants +syn keyword icinga2GlobalConstant Vars NodeName Environment RunAsUser RunAsGroup MaxConcurrentChecks ApiBindHost ApiBindPort EventEngine AttachDebugger + +" Application runtime constants +syn keyword icinga2GlobalConstant PlatformName PlatformVersion PlatformKernel PlatformKernelVersion BuildCompilerName BuildCompilerVersion BuildHostName +syn keyword icinga2GlobalConstant ApplicationVersion + +" User proposed constants +syn keyword icinga2UserConstant PluginDir ContribPluginContribDir ManubulonPluginDir TicketSalt NodeName ZoneName + +" Global types +syn keyword icinga2GlobalType Number String Boolean Array Dictionary Value Object ConfigObject Command CheckResult +syn keyword icinga2GlobalType Checkable CustomVarObject DbConnection Type PerfdataValue Comment Downtime Logger Application + +" Built-in Namespaces +syn match icinga2Namespace contained "\(Icinga\.\|Internal\.\|System\.Configuration\.\|System\.\)" + +" Additional constants from Namespaces +syn keyword icinga2GlobalConstant LogCritical LogDebug LogInformation LogNotice LogWarning MatchAll MatchAny +" icinga2 console; keys(Icinga).join(" ") +syn keyword icinga2GlobalConstant Acknowledgement Critical Custom DbCatAcknowledgement DbCatCheck DbCatComment DbCatConfig DbCatDowntime DbCatEventHandler DbCatEverything DbCatExternalCommand DbCatFlapping DbCatLog DbCatNotification DbCatProgramStatus DbCatRetention DbCatState DbCatStateHistory Down DowntimeEnd DowntimeNoChildren DowntimeNonTriggeredChildren DowntimeRemoved DowntimeStart DowntimeTriggeredChildren FlappingEnd FlappingStart HostDown HostUp OK Problem Recovery ServiceCritical ServiceOK ServiceUnknown ServiceWarning Unknown Up Warning + +" Value types +"syn match valueNumber "[0-9]*" +syn keyword valueBoolean contained true false +syn keyword valueNull contained null + + +" ######################################## +" ### Where to apply + +syn region icingaDefBody start='{' end='}' + \ contains=icinga2Comment, icinga2LineComment, StringS, StringD, macro, Braces, Parens, Lambda, icinga2ObjType, icinga2ObjDef, + \ icinga2ApplyDef, icinga2ObjAttr, icinga2Keyword, icinga2AssignCond, + \ icinga2Cond, icinga2Loop, icinga2Exception, icinga2Debugger, icinga2Operators, icinga2GFunction, icinga2AFunction, + \ icinga2MathFunction, icinga2GlobalConstant, icinga2PathConstant, icinga2UserConstant, icinga2Gconst, icinga2Namespace, + \ icinga2JsonFunction, icinga2NumberFunction, icinga2BoolFunction, + \ icinga2StrFunction, icinga2ObjectFunction, icinga2TypeFunction, icinga2ArrFunction, icinga2DictFunction, + \ icinga2DTFunction, valueNumber, valueBoolean, valueNull + + +" ######################################## +" ### Highlighting +hi link icinga2Comment Comment +hi link icinga2LineComment Comment +hi link icinga2CommentTodo Todo + +hi link Braces Function +hi link Parens Function +hi link Lambda Function + +hi link macro Underlined +hi link StringS String +hi link StringD String +hi link angleBrackets String + +hi link icinga2ObjType Type +hi link icinga2ObjDef Statement + +hi link icinga2ApplyDef Statement +hi link icinga2ObjAttr Define +hi link icinga2Keyword Keyword + +hi link icinga2AssignCond Conditional + +hi link icinga2Cond Conditional +hi link icinga2Loop Repeat +hi link icinga2Exception Conditional +hi link icinga2Debugger Debug + +hi link icinga2Operators Operator + +hi link icinga2AFunction Function +hi link icinga2MathFunction Function +hi link icinga2GFunction Function +hi link icinga2JsonFunction Function +hi link icinga2NumberFunction Function +hi link icinga2BoolFunction Function +hi link icinga2StrFunction Function +hi link icinga2ObjectFunction Function +hi link icinga2TypeFunction Function +hi link icinga2ArrFunction Function +hi link icinga2DictFunction Function +hi link icinga2DTFunction Function + +hi link icinga2GlobalConstant Statement +hi link icinga2PathConstant Statement +hi link icinga2UserConstant Statement +hi link icinga2Gconst Statement +hi link icinga2Namespace Statement + +hi link valueNumber Number +hi link valueBoolean Boolean +hi link valueNull Special diff --git a/tools/win32/build-choco.ps1 b/tools/win32/build-choco.ps1 new file mode 100644 index 0000000..32138bd --- /dev/null +++ b/tools/win32/build-choco.ps1 @@ -0,0 +1,42 @@ +Set-PsDebug -Trace 1 + +if(-not (Test-Path "$($env:ProgramData)\chocolatey\choco.exe")) { + throw "Could not find Choco executable. Abort." +} + +if (-not (Test-Path env:ICINGA2_BUILDPATH)) { + $env:ICINGA2_BUILDPATH = '.\build' +} + +if(-not (Test-Path "$($env:ICINGA2_BUILDPATH)\choco\chocolateyInstall.ps1.template")) { + throw "Could not find Chocolatey install script template. Abort." +} + +$chocoInstallScriptTemplatePath = "$($env:ICINGA2_BUILDPATH)\choco\chocolateyInstall.ps1.template" +$chocoInstallScript = Get-Content $chocoInstallScriptTemplatePath + +if(-not (Test-Path "$($env:ICINGA2_BUILDPATH)\*-x86.msi")) { + throw "Could not find Icinga 2 32 bit MSI package. Abort." +} + +$hashMSIpackage32 = Get-FileHash "$($env:ICINGA2_BUILDPATH)\*-x86.msi" +Write-Output "File Hash for 32 bit MSI package: $($hashMSIpackage32.Hash)." + +if(-not (Test-Path "$($env:ICINGA2_BUILDPATH)\*-x86_64.msi")) { + throw "Could not find Icinga 2 64 bit MSI package. Abort." +} + +$hashMSIpackage64 = Get-FileHash "$($env:ICINGA2_BUILDPATH)\*-x86_64.msi" +Write-Output "File Hash for 32 bit MSI package: $($hashMSIpackage64.Hash)" + +$chocoInstallScript = $chocoInstallScript.Replace("%CHOCO_32BIT_CHECKSUM%", "$($hashMSIpackage32.Hash)") +$chocoInstallScript = $chocoInstallScript.Replace("%CHOCO_64BIT_CHECKSUM%", "$($hashMSIpackage64.Hash)") +Write-Output $chocoInstallScript + +Set-Content -Path "$($env:ICINGA2_BUILDPATH)\choco\chocolateyInstall.ps1" -Value $chocoInstallScript + +cd "$($env:ICINGA2_BUILDPATH)\choco" +& "$($env:ProgramData)\chocolatey\choco.exe" "pack" +cd "..\.." + +Move-Item -Path "$($env:ICINGA2_BUILDPATH)\choco\*.nupkg" -Destination "$($env:ICINGA2_BUILDPATH)"
\ No newline at end of file diff --git a/tools/win32/build.ps1 b/tools/win32/build.ps1 new file mode 100644 index 0000000..33346a9 --- /dev/null +++ b/tools/win32/build.ps1 @@ -0,0 +1,27 @@ +Set-PsDebug -Trace 1 + +if (-not (Test-Path env:ICINGA2_BUILDPATH)) { + $env:ICINGA2_BUILDPATH = '.\build' +} + +if (-not (Test-Path env:CMAKE_BUILD_TYPE)) { + $env:CMAKE_BUILD_TYPE = 'RelWithDebInfo' +} + +if (-not (Test-Path $env:ICINGA2_BUILDPATH)) { + Write-Host "Path '$env:ICINGA2_BUILDPATH' does not exist!" + exit 1 +} + +if (-not (Test-Path env:CMAKE_PATH)) { + $env:CMAKE_PATH = 'C:\Program Files\CMake\bin' +} +if (-not ($env:PATH -contains $env:CMAKE_PATH)) { + $env:PATH = $env:CMAKE_PATH + ';' + $env:PATH +} + +cmake.exe --build "$env:ICINGA2_BUILDPATH" --target ALL_BUILD --config $env:CMAKE_BUILD_TYPE +if ($lastexitcode -ne 0) { exit $lastexitcode } + +cmake.exe --build "$env:ICINGA2_BUILDPATH" --target PACKAGE --config $env:CMAKE_BUILD_TYPE +if ($lastexitcode -ne 0) { exit $lastexitcode } diff --git a/tools/win32/configure-dev.ps1 b/tools/win32/configure-dev.ps1 new file mode 100644 index 0000000..c3bb836 --- /dev/null +++ b/tools/win32/configure-dev.ps1 @@ -0,0 +1,71 @@ +Set-PsDebug -Trace 1 + +# Specify default targets for VS 2019 for developers. + +if (-not (Test-Path env:ICINGA2_BUILDPATH)) { + $env:ICINGA2_BUILDPATH = '.\debug' +} +if (-not (Test-Path "$env:ICINGA2_BUILDPATH")) { + mkdir "$env:ICINGA2_BUILDPATH" | out-null +} + +if (-not (Test-Path env:CMAKE_BUILD_TYPE)) { + $env:CMAKE_BUILD_TYPE = 'Debug' +} +if (-not (Test-Path env:ICINGA2_INSTALLPATH)) { + $env:ICINGA2_INSTALLPATH = 'C:\Program Files\Icinga2-debug' +} +if (-not (Test-Path env:CMAKE_PATH)) { + $env:CMAKE_PATH = 'C:\Program Files\CMake\bin' +} +if (-not ($env:PATH -contains $env:CMAKE_PATH)) { + $env:PATH = $env:CMAKE_PATH + ';' + $env:PATH +} +if (-not (Test-Path env:CMAKE_GENERATOR)) { + $env:CMAKE_GENERATOR = 'Visual Studio 16 2019' +} +if (-not (Test-Path env:CMAKE_GENERATOR_PLATFORM)) { + $env:CMAKE_GENERATOR_PLATFORM = 'x64' +} +if (-not (Test-Path env:OPENSSL_ROOT_DIR)) { + $env:OPENSSL_ROOT_DIR = 'c:\local\OpenSSL-Win64' +} +if (-not (Test-Path env:BOOST_ROOT)) { + $env:BOOST_ROOT = 'c:\local\boost_1_80_0' +} +if (-not (Test-Path env:BOOST_LIBRARYDIR)) { + $env:BOOST_LIBRARYDIR = 'c:\local\boost_1_80_0\lib64-msvc-14.2' +} +if (-not (Test-Path env:FLEX_BINARY)) { + $env:FLEX_BINARY = 'C:\ProgramData\chocolatey\bin\win_flex.exe' +} +if (-not (Test-Path env:BISON_BINARY)) { + $env:BISON_BINARY = 'C:\ProgramData\chocolatey\bin\win_bison.exe' +} + +$sourcePath = Get-Location + +cd "$env:ICINGA2_BUILDPATH" + +# Invalidate cache in case something in the build environment changed +if (Test-Path CMakeCache.txt) { + Remove-Item -Force CMakeCache.txt | Out-Null +} + +& cmake.exe "$sourcePath" ` + -DCMAKE_BUILD_TYPE="$env:CMAKE_BUILD_TYPE" ` + -G "$env:CMAKE_GENERATOR" -A "$env:CMAKE_GENERATOR_PLATFORM" -DCPACK_GENERATOR=WIX ` + -DCMAKE_INSTALL_PREFIX="$env:ICINGA2_INSTALLPATH" ` + -DICINGA2_WITH_MYSQL=OFF -DICINGA2_WITH_PGSQL=OFF ` + -DICINGA2_WITH_LIVESTATUS=OFF -DICINGA2_WITH_COMPAT=OFF ` + -DOPENSSL_ROOT_DIR="$env:OPENSSL_ROOT_DIR" ` + -DBOOST_LIBRARYDIR="$env:BOOST_LIBRARYDIR" ` + -DBOOST_INCLUDEDIR="$env:BOOST_ROOT" ` + -DFLEX_EXECUTABLE="$env:FLEX_BINARY" ` + -DBISON_EXECUTABLE="$env:BISON_BINARY" + +cd "$sourcePath" + +if ($lastexitcode -ne 0) { + exit $lastexitcode +} diff --git a/tools/win32/configure.ps1 b/tools/win32/configure.ps1 new file mode 100644 index 0000000..295436e --- /dev/null +++ b/tools/win32/configure.ps1 @@ -0,0 +1,74 @@ +Set-PsDebug -Trace 1 + +if (-not (Test-Path env:ICINGA2_BUILDPATH)) { + $env:ICINGA2_BUILDPATH = '.\build' +} + +if (-not (Test-Path env:CMAKE_BUILD_TYPE)) { + $env:CMAKE_BUILD_TYPE = 'RelWithDebInfo' +} +if (-not (Test-Path "$env:ICINGA2_BUILDPATH")) { + mkdir "$env:ICINGA2_BUILDPATH" | out-null +} +if (-not (Test-Path env:CMAKE_PATH)) { + $env:CMAKE_PATH = 'C:\Program Files\CMake\bin' +} +if (-not ($env:PATH -contains $env:CMAKE_PATH)) { + $env:PATH = $env:CMAKE_PATH + ';' + $env:PATH +} +if (-not (Test-Path env:CMAKE_GENERATOR)) { + $env:CMAKE_GENERATOR = 'Visual Studio 16 2019' +} +if (-not (Test-Path env:BITS)) { + $env:BITS = 64 +} +if (-not (Test-Path env:CMAKE_GENERATOR_PLATFORM)) { + if ($env:BITS -eq 32) { + $env:CMAKE_GENERATOR_PLATFORM = 'Win32' + } else { + $env:CMAKE_GENERATOR_PLATFORM = 'x64' + } +} +if (-not (Test-Path env:OPENSSL_ROOT_DIR)) { + $env:OPENSSL_ROOT_DIR = "c:\local\OpenSSL_1_1_1s-Win${env:BITS}" +} +if (-not (Test-Path env:BOOST_ROOT)) { + $env:BOOST_ROOT = "c:\local\boost_1_80_0-Win${env:BITS}" +} +if (-not (Test-Path env:BOOST_LIBRARYDIR)) { + $env:BOOST_LIBRARYDIR = "c:\local\boost_1_80_0-Win${env:BITS}\lib${env:BITS}-msvc-14.2" +} +if (-not (Test-Path env:FLEX_BINARY)) { + $env:FLEX_BINARY = 'C:\ProgramData\chocolatey\bin\win_flex.exe' +} +if (-not (Test-Path env:BISON_BINARY)) { + $env:BISON_BINARY = 'C:\ProgramData\chocolatey\bin\win_bison.exe' +} + +$sourcePath = Get-Location + +cd "$env:ICINGA2_BUILDPATH" + +#-DCMAKE_INSTALL_PREFIX="C:\Program Files\Icinga2" ` + +# Invalidate cache in case something in the build environment changed +if (Test-Path CMakeCache.txt) { + Remove-Item -Force CMakeCache.txt | Out-Null +} + +& cmake.exe "$sourcePath" ` + -DCMAKE_BUILD_TYPE="$env:CMAKE_BUILD_TYPE" ` + -G "$env:CMAKE_GENERATOR" -A "$env:CMAKE_GENERATOR_PLATFORM" -DCPACK_GENERATOR=WIX ` + -DICINGA2_WITH_MYSQL=OFF -DICINGA2_WITH_PGSQL=OFF ` + -DICINGA2_WITH_LIVESTATUS=OFF -DICINGA2_WITH_COMPAT=OFF ` + -DOPENSSL_ROOT_DIR="$env:OPENSSL_ROOT_DIR" ` + -DBOOST_LIBRARYDIR="$env:BOOST_LIBRARYDIR" ` + -DBOOST_INCLUDEDIR="$env:BOOST_ROOT" ` + -DFLEX_EXECUTABLE="$env:FLEX_BINARY" ` + -DBISON_EXECUTABLE="$env:BISON_BINARY" + +cd "$sourcePath" + +if ($lastexitcode -ne 0) { + exit $lastexitcode +} diff --git a/tools/win32/load-vsenv.ps1 b/tools/win32/load-vsenv.ps1 new file mode 100644 index 0000000..c5323dc --- /dev/null +++ b/tools/win32/load-vsenv.ps1 @@ -0,0 +1,59 @@ +# why that env handling, see +# https://help.appveyor.com/discussions/questions/18777-how-to-use-vcvars64bat-from-powershell#comment_44999171 + +Set-PsDebug -Trace 1 + +$SOURCE = Get-Location + +if (Test-Path env:ICINGA2_BUILDPATH) { + $BUILD = $env:ICINGA2_BUILDPATH +} else { + $BUILD = "${SOURCE}\Build" +} + +if (-not (Test-Path $BUILD)) { + mkdir $BUILD | Out-Null +} + +if (Test-Path env:VS_INSTALL_PATH) { + $VSBASE = $env:VS_INSTALL_PATH +} else { + $VSBASE = "C:\Program Files (x86)\Microsoft Visual Studio\2019" +} + +if (Test-Path env:BITS) { + $bits = $env:BITS +} else { + $bits = 64 +} + +# Execute vcvars in cmd and store env +$vcvars_locations = @( + "${VSBASE}\BuildTools\VC\Auxiliary\Build\vcvars${bits}.bat" + "${VSBASE}\Community\VC\Auxiliary\Build\vcvars${bits}.bat" + "${VSBASE}\Enterprise\VC\Auxiliary\Build\vcvars${bits}.bat" +) + +$vcvars = $null +foreach ($file in $vcvars_locations) { + if (Test-Path $file) { + $vcvars = $file + break + } +} + +if ($vcvars -eq $null) { + throw "Could not get Build environment script at locations: ${vcvars_locations}" +} + +cmd.exe /c "call `"${vcvars}`" && set > `"${BUILD}\vcvars.txt`"" +if ($LastExitCode -ne 0) { + throw "Could not load Build environment from: ${vcvars}" +} + +# Load environment for PowerShell +Get-Content "${BUILD}\vcvars.txt" | Foreach-Object { + if ($_ -match "^(VSCMD.*?)=(.*)$") { + Set-Content ("env:" + $matches[1]) $matches[2] + } +} diff --git a/tools/win32/test.ps1 b/tools/win32/test.ps1 new file mode 100644 index 0000000..d7ad90c --- /dev/null +++ b/tools/win32/test.ps1 @@ -0,0 +1,33 @@ +Set-PsDebug -Trace 1 + +if (-not (Test-Path env:ICINGA2_BUILDPATH)) { + $env:ICINGA2_BUILDPATH = 'build' +} + +if (-not (Test-Path env:CMAKE_BUILD_TYPE)) { + $env:CMAKE_BUILD_TYPE = 'RelWithDebInfo' +} + +[string]$pwd = Get-Location + +if (-not (Test-Path $env:ICINGA2_BUILDPATH)) { + Write-Host "Path '$pwd\$env:ICINGA2_BUILDPATH' does not exist!" + exit 1 +} + +if (-not (Test-Path env:CMAKE_PATH)) { + $env:CMAKE_PATH = 'C:\Program Files\CMake\bin' +} +if (-not ($env:PATH -contains $env:CMAKE_PATH)) { + $env:PATH = $env:CMAKE_PATH + ';' + $env:PATH +} + +cd "$env:ICINGA2_BUILDPATH" + +ctest.exe -C "${env:CMAKE_BUILD_TYPE}" -T test -O $env:ICINGA2_BUILDPATH/Test.xml --output-on-failure --log_level=all +if ($lastexitcode -ne 0) { + cd .. + exit $lastexitcode +} + +cd .. |