diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 08d237d8b..ae6efaa56 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -163,7 +163,7 @@ jobs: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Configure run: | - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_SHARED_LIBS=OFF . + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_SHARED_LIBS=OFF -DENABLE_JOURNALD_TARGETS=OFF -DENABLE_SQLITE3_TARGETS=OFF . - name: Build run: | make all @@ -277,7 +277,7 @@ jobs: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Configure run: | - cmake -DCOVERAGE=ON -DCMAKE_BUILD_TYPE=Debug -DENABLE_JOURNALD_TARGETS=OFF -DENABLE_NETWORK_TARGETS=OFF -DENABLE_SOCKET_TARGETS=OFF -DENABLE_WINDOWS_EVENT_LOG_TARGETS=OFF -DENABLE_THREAD_SAFETY=OFF . + cmake -DCOVERAGE=ON -DCMAKE_BUILD_TYPE=Debug -DENABLE_JOURNALD_TARGETS=OFF -DENABLE_NETWORK_TARGETS=OFF -DENABLE_SOCKET_TARGETS=OFF -DENABLE_SQLITE3_TARGETS=OFF -DENABLE_WINDOWS_EVENT_LOG_TARGETS=OFF -DENABLE_THREAD_SAFETY=OFF . - name: Build run: | make all @@ -303,7 +303,7 @@ jobs: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Configure run: | - cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_JOURNALD_TARGETS=OFF -DENABLE_NETWORK_TARGETS=OFF -DENABLE_SOCKET_TARGETS=OFF -DENABLE_WINDOWS_EVENT_LOG_TARGETS=OFF -DENABLE_THREAD_SAFETY=OFF . + cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_JOURNALD_TARGETS=OFF -DENABLE_NETWORK_TARGETS=OFF -DENABLE_SOCKET_TARGETS=OFF -DENABLE_SQLITE3_TARGETS=OFF -DENABLE_WINDOWS_EVENT_LOG_TARGETS=OFF -DENABLE_THREAD_SAFETY=OFF . - name: Build run: | make all diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index fd8ccf2a6..e4722bf49 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -152,7 +152,7 @@ jobs: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Configure run: | - cmake -DCOVERAGE=ON -DENABLE_JOURNALD_TARGETS=OFF -DENABLE_NETWORK_TARGETS=OFF -DENABLE_SOCKET_TARGETS=OFF -DENABLE_WINDOWS_EVENT_LOG_TARGETS=OFF -DENABLE_THREAD_SAFETY=OFF . + cmake -DCOVERAGE=ON -DENABLE_JOURNALD_TARGETS=OFF -DENABLE_NETWORK_TARGETS=OFF -DENABLE_SOCKET_TARGETS=OFF -DENABLE_SQLITE3_TARGETS=OFF -DENABLE_WINDOWS_EVENT_LOG_TARGETS=OFF -DENABLE_THREAD_SAFETY=OFF . - name: Build run: | make all diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 126e9f876..97067dcd9 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -87,6 +87,28 @@ jobs: - name: Run Examples run: | cmake --build . --config Release --target examples-single-file + windows-sqlite3: + name: "windows, release, sqlite3 source" + runs-on: "windows-2019" + steps: + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + - name: Download SQLite3 Amalgamation + run: | + Invoke-WebRequest -Uri https://sqlite.org/2023/sqlite-amalgamation-3440200.zip -OutFile sqlite.zip + Expand-Archive -Path sqlite.zip + shell: pwsh + - name: Configure + run: | + cmake -G "Visual Studio 16 2019" -DCMAKE_BUILD_TYPE=Release -DSQLITE3_SRC_PATH=D:\a\stumpless\stumpless\sqlite\sqlite-amalgamation-3440200\sqlite3.c -DSQLITE3_INCLUDE_PATH=D:\a\stumpless\stumpless\sqlite\sqlite-amalgamation-3440200\sqlite3.h . + - name: Build + run: | + cmake --build . --config Release + - name: Test + run: | + cmake --build . --config Release --target check + - name: Thread Safety Tests + run: | + cmake --build . --config Release --target check-thread-safety windows-all-disabled: name: "windows, all features disabled" runs-on: "windows-2019" @@ -94,7 +116,7 @@ jobs: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Configure run: | - cmake -G "Visual Studio 16 2019" -DENABLE_JOURNALD_TARGETS=OFF -DENABLE_NETWORK_TARGETS=OFF -DENABLE_SOCKET_TARGETS=OFF -DCMAKE_BUILD_TYPE=Release -DENABLE_THREAD_SAFETY=OFF . + cmake -G "Visual Studio 16 2019" -DENABLE_JOURNALD_TARGETS=OFF -DENABLE_NETWORK_TARGETS=OFF -DENABLE_SOCKET_TARGETS=OFF -DENABLE_SQLITE3_TARGETS=OFF -DCMAKE_BUILD_TYPE=Release -DENABLE_THREAD_SAFETY=OFF . - name: Build run: | cmake --build . --config Release diff --git a/CMakeLists.txt b/CMakeLists.txt index c9a7c6989..07db36a5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ option(ENABLE_THREAD_SAFETY "support thread-safe functionality" ON) option(ENABLE_JOURNALD_TARGETS "support systemd journald service targets" ON) option(ENABLE_NETWORK_TARGETS "support network targets" ON) option(ENABLE_SOCKET_TARGETS "support unix domain socket targets" ON) +option(ENABLE_SQLITE3_TARGETS "support sqlite3 targets" ON) option(ENABLE_WINDOWS_EVENT_LOG_TARGETS "support windows event log targets" ON) option(INSTALL_EXAMPLES "install examples" ON) @@ -55,6 +56,32 @@ set(FALLBACK_PAGESIZE 4096 CACHE STRING "the memory page size to use if it cannot be detected at runtime" ) +string(CONCAT sqlite3_src_path_help_string + "The path to a SQLite3 source file, referred to as an 'amalgamation' in " + "SQLite documentation. If this variable is set, it is used to link SQLite3 " + "statically with Stumpless, instead of the default dynamic linking." +) +set(SQLITE3_SRC_PATH "" + CACHE FILEPATH ${sqlite3_src_path_help_string} +) + +string(CONCAT sqlite3_include_path_help_string + "The path to the sqlite3.h header matching the source file provided in " + "SQLITE3_SRC_PATH. If the header is already in the include directores, this " + "does not need to be specified." +) +set(SQLITE3_INCLUDE_PATH "" + CACHE FILEPATH ${sqlite3_include_path_help_string} +) + +set(SQLITE3_DEFAULT_TABLE_NAME "logs" + CACHE STRING "the name of the table used by default for SQLite3 targets" +) + +set(SQLITE3_RETRY_MAX 3 + CACHE STRING "the maximum number of retries on SQLite3 operations" +) + string(CONCAT benchmark_path_help_string "A directory with a build of google benchmark that can be used instead of " "downloading and building the library during build. " @@ -120,8 +147,16 @@ include(tools/cmake/test.cmake) # building configuration +if(EXISTS ${SQLITE3_INCLUDE_PATH}) + file(COPY + "${SQLITE3_INCLUDE_PATH}" + DESTINATION "${PROJECT_BINARY_DIR}/include" + ) +endif() + check_include_files(pthread.h HAVE_PTHREAD_H) check_include_files(stdatomic.h HAVE_STDATOMIC_H) +check_include_files("sqlite3.h" HAVE_SQLITE3_H) check_include_files(sys/socket.h HAVE_SYS_SOCKET_H) check_include_files(syslog.h STUMPLESS_SYSLOG_H_COMPATIBLE) check_include_files(systemd/sd-journal.h HAVE_SYSTEMD_SD_JOURNAL_H) @@ -458,6 +493,45 @@ else() endif() +# sqlite3 target support +if(NOT ENABLE_SQLITE3_TARGETS) + set(STUMPLESS_SQLITE3_TARGETS_SUPPORTED FALSE) +elseif(SQLITE3_SRC_PATH) + if(NOT EXISTS "${SQLITE3_SRC_PATH}") + message("the specified sqlite3 source file does not exist") + set(STUMPLESS_SQLITE3_TARGETS_SUPPORTED FALSE) + else() + list(APPEND STUMPLESS_SOURCES "${SQLITE3_SRC_PATH}") + set(STUMPLESS_SQLITE3_TARGETS_SUPPORTED TRUE) + endif() +elseif(NOT HAVE_SQLITE3_H) + message("sqlite3 targets are not supported without sqlite3.h") + set(STUMPLESS_SQLITE3_TARGETS_SUPPORTED FALSE) +else() + find_library(LIBSQLITE3_FOUND sqlite3) + if(LIBSQLITE3_FOUND) + set(STUMPLESS_SQLITE3_TARGETS_SUPPORTED TRUE) + else() + message("sqlite3 targets are not supported without libsqlite3") + set(STUMPLESS_SQLITE3_TARGETS_SUPPORTED FALSE) + endif() +endif() + +if(STUMPLESS_SQLITE3_TARGETS_SUPPORTED) + include(tools/cmake/sqlite3.cmake) +else() + list(APPEND + STUMPLESS_SOURCES "${PROJECT_SOURCE_DIR}/src/config/sqlite3_unsupported.c" + ) + + add_function_test(sqlite3_unsupported + SOURCES + ${PROJECT_SOURCE_DIR}/test/function/config/sqlite3_unsupported.cpp + $ + ) +endif() + + # windows event log target support if(NOT ENABLE_WINDOWS_EVENT_LOG_TARGETS) set(STUMPLESS_WINDOWS_EVENT_LOG_TARGETS_SUPPORTED FALSE) @@ -546,17 +620,15 @@ endif() add_library(stumpless ${STUMPLESS_SOURCES}) set_target_properties(stumpless PROPERTIES VERSION ${PROJECT_VERSION}) set_target_properties(stumpless PROPERTIES PUBLIC_HEADER include/stumpless.h) + +target_link_libraries(stumpless PRIVATE ${STUMPLESS_LINK_LIBRARIES}) + if(MINGW) target_compile_options(stumpless PRIVATE -D__USE_MINGW_ANSI_STDIO) set_target_properties(stumpless PROPERTIES PREFIX "") endif() -if(STUMPLESS_JOURNALD_TARGETS_SUPPORTED) - target_link_libraries(stumpless PRIVATE systemd) -endif() - if(STUMPLESS_WINDOWS_EVENT_LOG_TARGETS_SUPPORTED) - target_link_libraries(stumpless PRIVATE KtmW32) add_dependencies(stumpless default_events) endif() @@ -584,7 +656,7 @@ endif(FUZZ) target_include_directories(stumpless PRIVATE ${PROJECT_SOURCE_DIR}/include - ${CMAKE_BINARY_DIR}/include + ${PROJECT_BINARY_DIR}/include ) @@ -676,6 +748,7 @@ install(FILES ${PROJECT_SOURCE_DIR}/include/stumpless/target/buffer.h ${PROJECT_SOURCE_DIR}/include/stumpless/target/file.h ${PROJECT_SOURCE_DIR}/include/stumpless/target/function.h + ${PROJECT_SOURCE_DIR}/include/stumpless/target/sqlite3.h ${PROJECT_SOURCE_DIR}/include/stumpless/target/stream.h DESTINATION "include/stumpless/target" ) diff --git a/ChangeLog.md b/ChangeLog.md index 4e0646a33..11af69f13 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -9,10 +9,11 @@ fixes, check out the [roadmap](https://github.com/goatshriek/stumpless/blob/master/docs/roadmap.md). -## [2.2.0] - 2023-10-14 +## [2.2.0] - 2023-12-10 ### Added - @since format check enforcement in CI pipeline. - `single-file` target for rollup `.c` and `.h` files. + - SQLite3 logging targets. ### Fixed - Deadlock potential in `stumpless_set_entry_hostname` and `stumpless_set_entry_procid` when validation fails. diff --git a/docs/dependencies.md b/docs/dependencies.md index 02621f985..7f0de1b6c 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -54,7 +54,6 @@ additional tools: * `ruby` for some of the development scripts. If you have bundler, you can use the Gemfile (run `bundle install` from the project root) to install all the gems you might need at once. - * `indent` to format sources according to the project standard ## Simplified Wrapper Interface Generator (SWIG) The SWIG project is used to expose the functionality of Stumpless to languages diff --git a/docs/examples/sqlite3/README.md b/docs/examples/sqlite3/README.md new file mode 100644 index 000000000..4c01b54c2 --- /dev/null +++ b/docs/examples/sqlite3/README.md @@ -0,0 +1,302 @@ +# SQLite Targets +SQLite targets provide a way to send logs into a SQLite database, if you'd like +to store your logs there. A structured database provides a convenient place to +store data like logs, as well as a way to perform searches without setting up a +more complex storage and indexing system. + +You can work with SQLite databases at three levels of abstraction in Stumpless, +each providing more control but requiring more effort in return. We'll go +through each option in order of increasing complexity. + + +# The `logs` Table +In the most basic SQLite target, logs are put into a table with the name given +in `STUMPLESS_DEFAULT_SQLITE3_TABLE_NAME_STRING`, which defaults to `logs`. If +you open a target and immediately send an entry to it, this is the table that +it will go into. If the database doesn't exist, Stumpless can ask SQLite to +create it first. + +The entry will be added using the insert statement given by +`STUMPLESS_DEFAULT_SQLITE3_INSERT_SQL`, which is effectively something like +this: + +```sql +INSERT INTO logs ( prival, version, timestamp, hostname, app_name, procid, + msgid, structured_data, message ) +VALUES ( $prival, 1, $timestamp, $hostname, $app_name, $procid, $msgid, + "$structured_data, $message ); +``` + +Each variable is bound to the effective value held in the entry at the time that +it's logged. + +If you don't want to bother creating the logs table, you can ask Stumpless to do +it for you by invoking `stumpless_create_default_sqlite3_table`. This will +create a table with the following schema: + +```sql +CREATE TABLE logs ( + log_id INTEGER PRIMARY KEY, + prival INTEGER NOT NULL, + version INTEGER NOT NULL, + timestamp TEXT, + hostname TEXT, + app_name TEXT, + procid TEXT, + msgid TEXT, + structured_data TEXT, + message TEXT +); +``` + +So, let's say we want to create a new database with the default logs table, and +log a few events. This would be done like so: + +```c +struct stumpless_target *db_target; + +// create the new database (or open the existing one) +db_target = stumpless_open_sqlite3_target( "stumpless_example.sqlite3" ); + +// create the default logs table (if it doesn't exist) +stumpless_create_default_sqlite3_table( db_target ); + +// send a simple message to our new logs table +stumpless_add_message( db_target, "cards are on the table" ); +``` + +If we peeked into the database at this point, we would see an entry like +this for the message we added: + +``` +$ sqlite3 -header -column stumpless_example.sqlite3 +SQLite version 3.22.0 2018-01-22 18:45:57 +Enter ".help" for usage hints. +sqlite> SELECT * FROM logs; +log_id prival version timestamp hostname app_name procid msgid structured_data message +---------- ---------- ---------- --------------------------- ---------- ---------- ---------- ---------- --------------- ---------------------- +1 14 1 2023-11-22T04:35:02.909888Z Angus 3090 cards are on the table +``` + +If you simply need to get entries into a database, then this usage might be all +that you need. Notice that you don't even need to use any SQLite3 functions or +headers: you can rely on Stumpless for that! But, if you need to customize this +a bit more, then you'll need to do a little more work. + + +# Custom `INSERT` Statements +If you only need to adjust the default behavior a little bit, you might be able +to accomplish this without much extra effort. The +`stumpless_set_sqlite3_insert_sql` function allows you to provide an alternate +SQL statement to run each time a log entry needs to be made. This allows you to +make changes like adjust the table name, fields included, or hard-code certain +values. + +You might have noticed that the default SQL statement uses several named SQL +parameters for the entry values. You can use these in your custom SQL as well! +They are resolved by name (position is ignored) and if a name is not present +in the SQL then it is simply left out. In addition to the parameters used in +the default statement, there are a few more available if you need them: + + * `$facility` is the facility portion of the entry as an integer. This is + equivalent to the integer value of `stumpless_get_entry_facility` of the + entry. + * `$severity` is the severity portion of the entry as an integer. This is + equivalent to the integer value of `stumpless_get_entry_severity` of the + entry. + +For this example, lets say that you have your own log table with the following +schema, and you want entries to go into it instead. + +```sql +CREATE TABLE card_logs ( + log_id INTEGER PRIMARY KEY, + facility INTEGER NOT NULL, + severity INTEGER NOT NULL, + timestamp TEXT, + structured_data TEXT, + message TEXT +); +``` + +We've made a few adjustments to the default schema here. Of course, there is a +different table name to be more descriptive about the type of logs that are in +the table. We've also broken the prival into its separate parts, so that it is +easier to filter entries using SQL without needing to parse the prival first. +Finally, we've cut the columns down to specific things that our application +cares about. + +The default insert statement won't work here of course, so we'll need to write +a new one to fit. This is pretty straightforward: + +```sql +INSERT INTO card_logs ( facility, severity, timestamp, structured_data, + message ) +VALUES ( $facility, $severity, $timestamp, $structured_data, $message ) +``` + +Assuming this SQL is in a variable named `card_logs_insert`, all we need to do +is set this as our target's insert SQL: + +```c +stumpless_set_sqlite3_insert_sql( db_target, card_logs_insert ); +``` + +Now we can start putting logs into our new table! This time, we'll include some +structured data in our entry as well. + +```c +entry = stumpless_new_entry( STUMPLESS_FACILITY_USER, + STUMPLESS_SEVERITY_INFO, + "card-counter", + "card-played", + "a card was played" ); + +stumpless_add_new_param_to_entry( entry, "card", "suit", "hearts" ); +stumpless_add_new_param_to_entry( entry, "card", "rank", "5" ); +stumpless_add_new_param_to_entry( entry, "player", "name", "bill" ); + +stumpless_add_entry( db_target, entry ); +``` + +Let's take a look in the database and see this looks like. + +``` +$ sqlite3 -header -column stumpless_example.sqlite3 +SQLite version 3.22.0 2018-01-22 18:45:57 +Enter ".help" for usage hints. +sqlite> SELECT * FROM card_logs; +log_id facility severity timestamp structured_data message +---------- ---------- ---------- --------------------------- ------------------------------------------------- ----------------- +1 8 6 2023-11-23T19:05:33.234523Z [card suit="hearts" rank="5"][player name="bill"] a card was played +``` + +Great! We got our logs into our own table with the fields we needed. But what +if we need even MORE control over the insertion? There's one more level of +customization that you can use to get Stumpless to suit your needs. + + +# Custom Prepared SQL Statements +There are some scenarios where just adjusting the insert SQL statement isn't +enough. If you need to execute multiple statements or perform your own logic on +the entry's data before making the insertion, you'll need more control over what +happens to make the insertion. + +`stumpless_set_sqlite3_prepare` gives you a way to do this within a SQLite +target. You provide a function that take the entry and returns a number of +prepared statements to execute. When setting up, you can also provide a pointer +to anything you'd like, since you might need some extra information to create +the statements. + +For this example, we'll break our relational database into a couple of tables. +When new entries come in, we'll pull out the structured data and put it into the +appropriate columns, so that it's easier to work with using standard SQL +methods. We'll also add a table that maintains some game state, and update this +whenever a card is played. + +Our two new tables look like this: + +```sql +CREATE TABLE played_cards ( + played_card_id INTEGER PRIMARY KEY, + suit TEXT, + rank TEXT +); + +CREATE TABLE taken_turns ( + taken_turn_id INTEGER PRIMARY KEY, + player_name TEXT +); +``` + +Custom prepare functions take three parameters: the entry to log, a pointer to +a data structure supplied when the function is set, and a pointer to a counter +where the number of prepared statements is written. The function returns a +pointer to an array of prepared statement pointers, or NULL if something went +wrong. + +Our custom function will prepare two insert statements, one for each of our +tables. An abbreviated version looks like this (see `sqlite3_example.c` for +the full version). + +```c +static sqlite3_stmt *card_stmts[2] = { NULL, NULL }; + +void * +card_played_prepare( const struct stumpless_entry *entry, + void *data, + size_t *count ) { + sqlite3 *db = data; + const char *card_insert_sql = "INSERT INTO played_cards ( suit, rank ) " + "VALUES ( $suite, $rank )"; + const char *player_insert_sql = "INSERT INTO taken_turns ( player_name ) " + "VALUES ( $name )"; + const char *suit; + const char *rank; + const char *name; + + sqlite3_prepare_v2( db, card_insert_sql, -1, &card_stmts[0], NULL ); + sqlite3_prepare_v2( db, player_insert_sql, -1, &card_stmts[1], NULL ); + + suit = stumpless_get_entry_param_value_by_name( entry, "card", "suit" ); + rank = stumpless_get_entry_param_value_by_name( entry, "card", "rank" ); + name = stumpless_get_entry_param_value_by_name( entry, "player", "name" ); + + sqlite3_bind_text( card_stmts[0], 1, suit, -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( card_stmts[0], 2, rank, -1, SQLITE_TRANSIENT ); + sqlite3_bind_text( card_stmts[1], 1, name, -1, SQLITE_TRANSIENT ); + + free( ( char * ) suit ); + free( ( char * ) rank ); + free( ( char * ) name ); + + *count = 2; + return &card_stmts; +} +``` + +Setting this function to be used is almost trivial. Let's also send the same +entry as before so that we can see the new behavior. + +```c +stumpless_set_sqlite3_prepare( db_target, &card_played_prepare, db ); +stumpless_add_entry( db_target, entry ); +``` + +And finally, a peek into the database to make sure everything is as we expect. + +``` +$ sqlite3 -header -column stumpless_example.sqlite3 +SQLite version 3.22.0 2018-01-22 18:45:57 +Enter ".help" for usage hints. +sqlite> SELECT * FROM played_cards; +played_card_id suit rank +-------------- ---------- ---------- +1 hearts 5 +sqlite> SELECT * FROM taken_turns; +taken_turn_id player_name +------------- ----------- +1 bill +``` + +That's it! + +One important thing to note about this last style of insertion is that it +requires you to have the SQLite headers and library linked against your own +executable. You'll want to make sure that you have the same version of SQLite +that Stumpless is configured with, or you could run into strange issues! + +This is especially relevant if you've compiled Stumpless by directly embedding +SQLite into it via the `SQLITE3_SRC_PATH` CMake variable. Doing this and then +writing a separate prepare function in your own code will mean that SQLite will +be built in two separate places: in Stumpless itself and in your code. This +means that there will also be different static variable locations, and could +cause serious problems if a single database handle is shared between these two +SQLite instances. A good rule of thumb to avoid these issues is to dynamically +link SQLite to Stumpless and your own code if you're going this route. + +If you need more control than this, you're probably better off writing your own +SQLite code to do insertions, and handing this to a function target to invoke it +when entries are added. Check out the [function target](../function/README.md) +example if you want to see what setting up a custom function target is like +(spoiler: it's easy). diff --git a/docs/examples/sqlite3/sqlite3_example.c b/docs/examples/sqlite3/sqlite3_example.c new file mode 100644 index 000000000..b6ba21c73 --- /dev/null +++ b/docs/examples/sqlite3/sqlite3_example.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2023 Joel E. Anderson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +// an array of prepared statements to use in our custom prepare function +static sqlite3_stmt *card_stmts[2] = { NULL, NULL }; + +/** + * A custom prepare function for a Stumpless SQLite3 target. + * + * Extracts structured data from the entry, and builds two insert statements + * to put this data into custom tables. + * + * @param entry The entry to get the structured data from. + * + * @param data The SQLite3 database handle (sqlite3 *). + * + * @param count A pointer to an output variable where the number of valid + * prepared statements will be written to. This will always be 2 unless an + * error occurred. + * + * @return A pointer to an array of sqlite3_stmt pointers on success, or NULL + * on failure. + */ +void * +card_played_prepare( const struct stumpless_entry *entry, + void *data, + size_t *count ) { + sqlite3 *db = data; + const char *card_insert_sql = "INSERT INTO played_cards ( suit, rank ) " + "VALUES ( $suite, $rank )"; + int sql_result; + const char *player_insert_sql = "INSERT INTO taken_turns ( player_name ) " + "VALUES ( $name )"; + const char *suit; + const char *rank; + const char *name; + + if( !card_stmts[0] ) { + sql_result = sqlite3_prepare_v2( db, + card_insert_sql, + -1, + &card_stmts[0], + NULL ); + if( sql_result != SQLITE_OK ) { + printf( "couldn't prepare the played_cards insert stmt: %s\n", + sqlite3_errmsg( db ) ); + return NULL; + } + } else { + sqlite3_reset( card_stmts[0] ); + } + + if( !card_stmts[1] ) { + sql_result = sqlite3_prepare_v2( db, + player_insert_sql, + -1, + &card_stmts[1], + NULL ); + if( sql_result != SQLITE_OK ) { + printf( "couldn't prepare the taken_turns insert stmt: %s\n", + sqlite3_errmsg( db ) ); + return NULL; + } + } else { + sqlite3_reset( card_stmts[1] ); + } + + suit = stumpless_get_entry_param_value_by_name( entry, "card", "suit" ); + rank = stumpless_get_entry_param_value_by_name( entry, "card", "rank" ); + name = stumpless_get_entry_param_value_by_name( entry, "player", "name" ); + + sql_result = sqlite3_bind_text( card_stmts[0], + 1, + suit, + -1, + SQLITE_TRANSIENT ); + if( sql_result != SQLITE_OK ) { + printf( "couldn't bind the card suit: %s\n", + sqlite3_errmsg( db ) ); + return NULL; + } + + sql_result = sqlite3_bind_text( card_stmts[0], + 2, + rank, + -1, + SQLITE_TRANSIENT ); + if( sql_result != SQLITE_OK ) { + printf( "couldn't bind the card rank: %s\n", + sqlite3_errmsg( db ) ); + return NULL; + } + + sql_result = sqlite3_bind_text( card_stmts[1], + 1, + name, + -1, + SQLITE_TRANSIENT ); + if( sql_result != SQLITE_OK ) { + printf( "couldn't bind the player name: %s\n", + sqlite3_errmsg( db ) ); + return NULL; + } + + free( ( char * ) suit ); + free( ( char * ) rank ); + free( ( char * ) name ); + + *count = 2; + return &card_stmts; +} + +int +main( int argc, char **argv ) { + struct stumpless_target *db_target; + int result = EXIT_SUCCESS; + sqlite3 *db; + const char *card_logs_create_sql = "CREATE TABLE card_logs (" + " log_id INTEGER PRIMARY KEY," + " facility INTEGER NOT NULL," + " severity INTEGER NOT NULL," + " timestamp TEXT," + " structured_data TEXT," + " message TEXT )"; + sqlite3_stmt *card_logs_create_stmt = NULL ; + int sql_result; + const char *card_logs_insert = "INSERT INTO card_logs ( facility, severity," + " timestamp," + " structured_data," + " message )" + "VALUES ( $facility, $severity, $timestamp," + " $structured_data, $message )"; + struct stumpless_entry *entry = NULL; + const char *played_cards_create_sql = "CREATE TABLE played_cards (" + " played_card_id INTEGER PRIMARY KEY," + " suit TEXT," + " rank TEXT" + ")"; + sqlite3_stmt *played_cards_create_stmt = NULL; + const char *taken_turns_create_sql = "CREATE TABLE taken_turns (" + " taken_turn_id INTEGER PRIMARY KEY," + " player_name TEXT" + ")"; + sqlite3_stmt *taken_turns_create_stmt = NULL; + + + // create the new database (or open the existing one) + db_target = stumpless_open_sqlite3_target( "stumpless_example.sqlite3" ); + if( !db_target ) { + stumpless_perror( "couldn't open sqlite3 target" ); + result = EXIT_FAILURE; + goto cleanup_and_finish; + } + + // create the default logs table (if it doesn't exist) + stumpless_create_default_sqlite3_table( db_target ); + if( stumpless_has_error() ) { + // for simplicity, if this fails we simply print a warning and continue + puts( "could not create default table, perhaps it already exists?" ); + } + + // send a simple message to our new logs table + stumpless_add_message( db_target, "cards are on the table" ); + if( stumpless_has_error() ) { + stumpless_perror( "couldn't send a message to a default SQLite target" ); + result = EXIT_FAILURE; + goto cleanup_and_finish; + } + + // after this, an initially empty database would look like this: + // sqlite> SELECT * FROM logs; + // log_id prival version timestamp hostname app_name procid msgid structured_data message + // ---------- ---------- ---------- --------------------------- ---------- ---------- ---------- ---------- --------------- ---------------------- + // 1 14 1 2023-11-22T04:35:02.909888Z Angus 3090 cards are on the table + + // now, let's use our own insertion statement + // we'll need to create a table + db = stumpless_get_sqlite3_db( db_target ); + if( !db ) { + stumpless_perror( "couldn't get the underlying database connection!" ); + result = EXIT_FAILURE; + goto cleanup_and_finish; + } + + sql_result = sqlite3_prepare_v2( db, + card_logs_create_sql, + -1, + &card_logs_create_stmt, + NULL ); + if( sql_result != SQLITE_OK ) { + // for simplicity, if this fails we simply print a warning and continue + puts( "could not create card_logs table, perhaps it already exists?" ); + } else { + sql_result = sqlite3_step( card_logs_create_stmt ); + if( sql_result != SQLITE_DONE ) { + printf( "couldn't create the card_logs table: %s\n", + sqlite3_errmsg( db ) ); + result = EXIT_FAILURE; + goto cleanup_and_finish; + } + } + + // set the target to use our custom insert SQL instead + stumpless_set_sqlite3_insert_sql( db_target, card_logs_insert ); + if( stumpless_has_error() ) { + stumpless_perror( "couldn't set the custom insert sql" ); + result = EXIT_FAILURE; + goto cleanup_and_finish; + } + + // create a new entry with some structured data + // we ignore the error checking here for brevity + entry = stumpless_new_entry( STUMPLESS_FACILITY_USER, + STUMPLESS_SEVERITY_INFO, + "card-counter", + "card-played", + "a card was played" ); + + stumpless_add_new_param_to_entry( entry, "card", "suit", "hearts" ); + stumpless_add_new_param_to_entry( entry, "card", "rank", "5" ); + stumpless_add_new_param_to_entry( entry, "player", "name", "bill" ); + + // use our custom insert SQL to add to the card_logs table + stumpless_add_entry( db_target, entry ); + + // here's what the database looks like after this: + // sqlite> SELECT * FROM card_logs; + // log_id facility severity timestamp structured_data message + // ---------- ---------- ---------- --------------------------- ------------------------------------------------- ----------------- + // 1 8 6 2023-11-23T19:05:33.234523Z [card suit="hearts" rank="5"][player name="bill"] a card was played + + // finally, let's use a custom preparation function for more structure + // first we create our new tables + sql_result = sqlite3_prepare_v2( db, + played_cards_create_sql, + -1, + &played_cards_create_stmt, + NULL ); + if( sql_result != SQLITE_OK ) { + // for simplicity, if this fails we simply print a warning and continue + puts( "could not create played_cards table, perhaps it already exists?" ); + } else { + sql_result = sqlite3_step( played_cards_create_stmt ); + if( sql_result != SQLITE_DONE ) { + printf( "couldn't create the played_cards table: %s\n", + sqlite3_errmsg( db ) ); + result = EXIT_FAILURE; + goto cleanup_and_finish; + } + } + + sql_result = sqlite3_prepare_v2( db, + taken_turns_create_sql, + -1, + &taken_turns_create_stmt, + NULL ); + if( sql_result != SQLITE_OK ) { + // for simplicity, if this fails we simply print a warning and continue + puts( "could not create taken_turns table, perhaps it already exists?" ); + } else { + sql_result = sqlite3_step( taken_turns_create_stmt ); + if( sql_result != SQLITE_DONE ) { + printf( "couldn't create the taken_turns table: %s\n", + sqlite3_errmsg( db ) ); + result = EXIT_FAILURE; + goto cleanup_and_finish; + } + } + + // we only need the db, so we use this as our custom data pointer + stumpless_set_sqlite3_prepare( db_target, &card_played_prepare, db ); + if( stumpless_has_error() ) { + stumpless_perror( "couldn't set the custom prepare function" ); + result = EXIT_FAILURE; + goto cleanup_and_finish; + } + + // add the same entry again to see it in our new tables + stumpless_add_entry( db_target, entry ); + + // sqlite> SELECT * FROM played_cards; + // played_card_id suit rank + // -------------- ---------- ---------- + // 1 hearts 5 + // sqlite> SELECT * FROM taken_turns; + // taken_turn_id player_name + // ------------- ----------- + // 1 bill + +cleanup_and_finish: + sqlite3_finalize( card_logs_create_stmt ); + sqlite3_finalize( played_cards_create_stmt ); + sqlite3_finalize( taken_turns_create_stmt ); + sqlite3_finalize( card_stmts[0] ); + sqlite3_finalize( card_stmts[1] ); + stumpless_destroy_entry_and_contents( entry ); + stumpless_close_sqlite3_target_and_db( db_target ); + stumpless_free_all(); + + return result; +} diff --git a/docs/roadmap.md b/docs/roadmap.md index 40dab5348..c907ce430 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -56,7 +56,6 @@ or want to make a suggestion, please submit an issue on the project's * [ADD] **Powershell language bindings** * [ADD] **Perl language bindings** * [ADD] **AWS/S3 logging target** - * [ADD] **Database logging target** * [ADD] **REST endpoint logging target** * [ADD] **Hyperledger/blockchain logging target** * [ADD] **Apache Kafka logging target** diff --git a/docs/style.md b/docs/style.md index 1bad3db6e..7e31c5ab8 100644 --- a/docs/style.md +++ b/docs/style.md @@ -1,15 +1,9 @@ # Stumpless Coding Style Stumpless follows a fairly straightforward style, although it is not a well-known one such as the K&R standard. Because the formatting in the code base -is admittedly not common, there will be some leniency in changes that do not -follow it perfectly. This is up to the pull request reviewer's discretion. - -If you are not sure about a formatting choice, you can simply run the GNU indent -wrapper available in `scripts/indent.sh` to format your changes. Note that you -will need to make sure that this does not introduce changes to parts of the -source file that you did not change. `indent` will save the original file as the -original with a `~` character at the end if you need to go back, or you can use -the git history. +is admittedly not common and even inconsistent in some places, there will be +some leniency in changes that do not follow it perfectly. This is up to the pull +request reviewer's discretion. Here are a few other style points to follow in the code base: diff --git a/include/private/config/locale/bg-bg.h b/include/private/config/locale/bg-bg.h index 64d946876..2df039e49 100644 --- a/include/private/config/locale/bg-bg.h +++ b/include/private/config/locale/bg-bg.h @@ -229,6 +229,46 @@ ARG_NAME " беше NULL" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"Stumpless registration of Windows Event Log Source" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + // todo translate # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "could not write to the stream" diff --git a/include/private/config/locale/bn-in.h b/include/private/config/locale/bn-in.h index ad09ea933..c3228c41e 100644 --- a/include/private/config/locale/bn-in.h +++ b/include/private/config/locale/bn-in.h @@ -215,6 +215,46 @@ ARG_NAME " NULL ছিল" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"উইন্ডোজ ইভেন্ট লগ সোর্সের স্টাম্পলেস রেজিস্ট্রেশন" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "স্রোতে লিখতে পারিনি" diff --git a/include/private/config/locale/cz-cz.h b/include/private/config/locale/cz-cz.h index eb6d8edc1..344ab095d 100644 --- a/include/private/config/locale/cz-cz.h +++ b/include/private/config/locale/cz-cz.h @@ -221,6 +221,46 @@ ARG_NAME " měl hodnotu NULL" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"Stumpless registration of Windows Event Log Source" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "nelze zapisovat do streamu" diff --git a/include/private/config/locale/da-dk.h b/include/private/config/locale/da-dk.h index 6b602d708..ce77bc3fe 100644 --- a/include/private/config/locale/da-dk.h +++ b/include/private/config/locale/da-dk.h @@ -206,6 +206,46 @@ ARG_NAME " var NULL" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ "Stumpless registration af Windows Event Log kilde" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "kunne ikke skrive til strømmen" diff --git a/include/private/config/locale/de-de.h b/include/private/config/locale/de-de.h index 9745cafe9..1ffdd95da 100644 --- a/include/private/config/locale/de-de.h +++ b/include/private/config/locale/de-de.h @@ -237,6 +237,46 @@ ARG_NAME " war NULL" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"Stumpless registration of Windows Event Log Source" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + // todo translate # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "could not write to the stream" diff --git a/include/private/config/locale/el-gr.h b/include/private/config/locale/el-gr.h index 6101b14c4..e7662e2d6 100644 --- a/include/private/config/locale/el-gr.h +++ b/include/private/config/locale/el-gr.h @@ -224,6 +224,46 @@ ARG_NAME " κατέχει την τιμή NULL" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"Stumpless registration of Windows Event Log Source" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "αδυναμία εγγραφής στη ροή δεδομένων (stream)" diff --git a/include/private/config/locale/en-us.h b/include/private/config/locale/en-us.h index 2af5597bb..2a4c15905 100644 --- a/include/private/config/locale/en-us.h +++ b/include/private/config/locale/en-us.h @@ -209,6 +209,36 @@ ARG_NAME " was NULL" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"Stumpless registration of Windows Event Log Source" +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "could not write to the stream" diff --git a/include/private/config/locale/es-es.h b/include/private/config/locale/es-es.h index a03f6a1e5..18f10db3e 100644 --- a/include/private/config/locale/es-es.h +++ b/include/private/config/locale/es-es.h @@ -208,6 +208,46 @@ ARG_NAME " fue NULL" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"registro de Stumpless del Registro de Eventos de Windows" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "no se pudo escribir en el stream" diff --git a/include/private/config/locale/fr-fr.h b/include/private/config/locale/fr-fr.h index ce041936e..b61ba32e0 100644 --- a/include/private/config/locale/fr-fr.h +++ b/include/private/config/locale/fr-fr.h @@ -206,6 +206,46 @@ ARG_NAME " a été NULL" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"enregistrement Stumpless de Windows Event Log Source" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "impossible d'écrire dans le flux" diff --git a/include/private/config/locale/he-il.h b/include/private/config/locale/he-il.h index 4b79bcce6..e441dd7a8 100644 --- a/include/private/config/locale/he-il.h +++ b/include/private/config/locale/he-il.h @@ -207,6 +207,46 @@ # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"Windows Event Log Source של Stumpless רישום" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "stream-לא ניתן היה לכתוב ל" diff --git a/include/private/config/locale/hi-in.h b/include/private/config/locale/hi-in.h index 31e3949a8..d42639220 100644 --- a/include/private/config/locale/hi-in.h +++ b/include/private/config/locale/hi-in.h @@ -210,6 +210,46 @@ ARG_NAME " NULL था" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"विंडोज इवेंट लॉग सोर्स का स्टंपलेस पंजीकरण" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "धारा को नहीं लिख सका" diff --git a/include/private/config/locale/it-it.h b/include/private/config/locale/it-it.h index 7eb41dd5d..32d1e8817 100644 --- a/include/private/config/locale/it-it.h +++ b/include/private/config/locale/it-it.h @@ -208,6 +208,46 @@ ARG_NAME " era NULL" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"la registrazione per Stumpless di Windows Event Log Source" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "non è stato possibile scrivere al stream" diff --git a/include/private/config/locale/pl-pl.h b/include/private/config/locale/pl-pl.h index 9d1fdd1ce..bce94b7d9 100644 --- a/include/private/config/locale/pl-pl.h +++ b/include/private/config/locale/pl-pl.h @@ -224,6 +224,46 @@ ARG_NAME " miał wartość NULL" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"Stumpless registration of Windows Event Log Source" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "nie mogę pisać do streamu" diff --git a/include/private/config/locale/pt-br.h b/include/private/config/locale/pt-br.h index 7994a130c..2b2865499 100644 --- a/include/private/config/locale/pt-br.h +++ b/include/private/config/locale/pt-br.h @@ -209,6 +209,46 @@ ARG_NAME " era NULL" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"Registro do Stumpless no Windows Event Log Source" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "não foi possível escrever na stream" diff --git a/include/private/config/locale/sk-sk.h b/include/private/config/locale/sk-sk.h index 59a41e0cc..8b948d200 100644 --- a/include/private/config/locale/sk-sk.h +++ b/include/private/config/locale/sk-sk.h @@ -233,6 +233,46 @@ ARG_NAME " mal hodnotu NULL" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"Stumpless registration of Windows Event Log Source" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + // todo translate # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "could not write to the stream" diff --git a/include/private/config/locale/sv-se.h b/include/private/config/locale/sv-se.h index d8129a46a..d11115ac2 100644 --- a/include/private/config/locale/sv-se.h +++ b/include/private/config/locale/sv-se.h @@ -236,6 +236,46 @@ ARG_NAME " var NULL" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"Stumpless registration of Windows Event Log Source" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + // todo translate # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "could not write to the stream" diff --git a/include/private/config/locale/sw-ke.h b/include/private/config/locale/sw-ke.h index 01e0ea535..c40f7a7cc 100644 --- a/include/private/config/locale/sw-ke.h +++ b/include/private/config/locale/sw-ke.h @@ -212,6 +212,46 @@ ARG_NAME " ilikuwa NULL" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ "usajili wa Stumpless wa chanzo cha Windows Event Log" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "haikuweza kuandika kwenye mtiririko" diff --git a/include/private/config/locale/tr-tr.h b/include/private/config/locale/tr-tr.h index c436fa7ae..0a394215f 100644 --- a/include/private/config/locale/tr-tr.h +++ b/include/private/config/locale/tr-tr.h @@ -210,6 +210,46 @@ ARG_NAME " NULL idi" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"Windows Olay Günlüğü kaynağının Stumpless Kaydı" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "akışa(stream) yazılamadı" diff --git a/include/private/config/locale/zh-cn.h b/include/private/config/locale/zh-cn.h index e4c63c30b..4889c84b1 100644 --- a/include/private/config/locale/zh-cn.h +++ b/include/private/config/locale/zh-cn.h @@ -211,6 +211,46 @@ ARG_NAME "是空的" # define L10N_SOURCE_REGISTRATION_TRANSACTION_DESCRIPTION_W \ L"Windows事件日志源的无障碍注册" +// todo translate +# define L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( PARAM ) \ +"could not bind " PARAM " to the statement" + +// todo translate +# define L10N_SQLITE3_BUSY_ERROR_MESSAGE \ +"the database was busy and could not complete the transaction" + +// todo translate +# define L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE \ +"could not close the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE \ +"a custom callback for prepared statements failed" + +// todo translate +# define L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE \ +"could not open the sqlite3 database" + +// todo translate +# define L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE \ +"sqlite3_prepare_v2 failed" + +// todo translate +# define L10N_SQLITE3_RESULT_CODE_TYPE \ +"the return code of the failed sqlite3 call" + +// todo translate +# define L10N_SQLITE3_RETRY_COUNT_CODE_TYPE \ +"the number of times the operation was retried" + +// todo translate +# define L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE \ +"sqlite3_step failed" + +// todo translate +# define L10N_SQLITE3_TARGETS_UNSUPPORTED \ +"sqlite3 targets are not supported by this build" + # define L10N_STREAM_WRITE_FAILURE_ERROR_MESSAGE \ "无法写入流" diff --git a/include/private/config/wrapper/sqlite3.h b/include/private/config/wrapper/sqlite3.h new file mode 100644 index 000000000..ad73616d0 --- /dev/null +++ b/include/private/config/wrapper/sqlite3.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +/* + * Copyright 2023 Joel E. Anderson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __STUMPLESS_PRIVATE_CONFIG_WRAPPER_SQLITE3_H +#define __STUMPLESS_PRIVATE_CONFIG_WRAPPER_SQLITE3_H + +#include + +#ifdef STUMPLESS_SQLITE3_TARGETS_SUPPORTED +# include +# include "private/target/sqlite3.h" +# define config_close_sqlite3_target_and_db \ +stumpless_close_sqlite3_target_and_db +# define config_send_entry_to_sqlite3_target send_entry_to_sqlite3_target +#else +# include "private/target.h" +# define config_close_sqlite3_target_and_db close_unsupported_target +# define config_send_entry_to_sqlite3_target send_entry_to_unsupported_target +#endif + +#endif /* __STUMPLESS_PRIVATE_CONFIG_WRAPPER_SQLITE3_H */ diff --git a/include/private/entry.h b/include/private/entry.h index 23c83f332..0bc7ab2f6 100644 --- a/include/private/entry.h +++ b/include/private/entry.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ /* - * Copyright 2018-2022 Joel E. Anderson + * Copyright 2018-2023 Joel E. Anderson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,15 +17,15 @@ */ #ifndef __STUMPLESS_PRIVATE_ENTRY_H -# define __STUMPLESS_PRIVATE_ENTRY_H +#define __STUMPLESS_PRIVATE_ENTRY_H -# include -# include -# include -# include -# include -# include -# include "private/strbuilder.h" +#include +#include +#include +#include +#include +#include +#include "private/strbuilder.h" /** * Frees entry cache @@ -183,6 +183,31 @@ strbuilder_append_message( struct strbuilder *builder, struct strbuilder * strbuilder_append_procid( struct strbuilder *builder ); +/** + * Adds the structured data of an entry to the given strbuilder, in the format + * specified in RFC 5424. + * + * Assumes that the entry has already been locked. + * + * **Thread Safety: MT-Unsafe** + * This function is not thread safe as it accesses the entry's element list + * without any coordination. + * + * **Async Signal Safety: AS-Unsafe heap** + * This function is not safe to call from signal handlers due to the use of + * strbuilder functions which may allocate memory. + * + * **Async Cancel Safety: AC-Unsafe heap** + * This function is not safe to call from threads that may be asynchronously + * cancelled, as the memory allocation function may not be AC-Safe. + * + * @param builder The strbuilder to append the characters to. + * + * @param entry The entry to extract the structured data from. + * + * @return The modified builder if no error is encountered. If an error is + * encountered, then NULL is returned and an error code is set appropriately. + */ struct strbuilder * strbuilder_append_structured_data( struct strbuilder *builder, const struct stumpless_entry *entry ); diff --git a/include/private/error.h b/include/private/error.h index 90f1cd2a1..595d02102 100644 --- a/include/private/error.h +++ b/include/private/error.h @@ -17,11 +17,11 @@ */ #ifndef __STUMPLESS_PRIVATE_ERROR_H -# define __STUMPLESS_PRIVATE_ERROR_H +#define __STUMPLESS_PRIVATE_ERROR_H -# include -# include -# include "private/config.h" +#include +#include +#include "private/config.h" void clear_error( void ); @@ -235,6 +235,50 @@ raise_socket_send_failure( const char *message, int code, const char *code_type ); +/** + * Raises a STUMPESS_SQLITE3_BUSY error. + * + * **Thread Safety: MT-Safe** + * This function is thread safe. + * + * **Async Signal Safety: AS-Unsafe** + * This function is not safe to call from signal handlers due to the use of + * a thread-global structure to store the error. + * + * **Async Cancel Safety: AC-Unsafe** + * This function is not safe to call from threads that may be asynchronously + * cancelled, due to the use of a thread-global structure to store the error. + * + * @since release v2.2.0 + */ +COLD_FUNCTION +void +raise_sqlite3_busy( void ); + +/** + * Raises a STUMPESS_SQLITE3_FAILURE error. + * + * **Thread Safety: MT-Safe** + * This function is thread safe. + * + * **Async Signal Safety: AS-Unsafe** + * This function is not safe to call from signal handlers due to the use of + * a thread-global structure to store the error. + * + * **Async Cancel Safety: AC-Unsafe** + * This function is not safe to call from threads that may be asynchronously + * cancelled, due to the use of a thread-global structure to store the error. + * + * @since release v2.2.0 + * + * @param message A localized description of the failure. + * + * @param code The error code from the failed SQLite3 call. + */ +COLD_FUNCTION +void +raise_sqlite3_failure( const char *message, int code ); + COLD_FUNCTION void raise_stream_write_failure( void ); diff --git a/include/private/strbuilder.h b/include/private/strbuilder.h index 5ee931de8..2672c1dd0 100644 --- a/include/private/strbuilder.h +++ b/include/private/strbuilder.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ /* - * Copyright 2018-2021 Joel E. Anderson + * Copyright 2018-2023 Joel E. Anderson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,9 @@ */ #ifndef __STUMPLESS_PRIVATE_STRBUILDER_H -# define __STUMPLESS_PRIVATE_STRBUILDER_H +#define __STUMPLESS_PRIVATE_STRBUILDER_H -# include +#include struct strbuilder { char *buffer; @@ -76,6 +76,19 @@ strbuilder_destroy( const struct strbuilder *builder ); struct strbuilder * strbuilder_new( void ); +/** + * Resets the strbuilder to be empty, without giving up memory resources. + * + * @since release v2.2.0 + * + * @param builder The strbuilder to reset. If this is NULL, then this function + * does nothing. + * + * @return The reset builder. + */ +struct strbuilder * +strbuilder_reset( struct strbuilder *builder ); + char * strbuilder_to_string( const struct strbuilder *builder ); diff --git a/include/private/target/sqlite3.h b/include/private/target/sqlite3.h new file mode 100644 index 000000000..f7e29e011 --- /dev/null +++ b/include/private/target/sqlite3.h @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +/* + * Copyright 2023 Joel E. Anderson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __STUMPLESS_PRIVATE_TARGET_SQLITE3_H +#define __STUMPLESS_PRIVATE_TARGET_SQLITE3_H + +#include +#include +#include +#include +#include +#include "private/config/wrapper/thread_safety.h" + +/** + * Internal representation of a sqlite3 target. + */ +struct sqlite3_target { +/** A connection to the database this target writes to. */ + sqlite3 *db; +/** The SQL statement used to insert entries into the database. */ + const char *insert_sql; +/** The function used to create prepared statements for database insertion. */ + stumpless_sqlite3_prepare_func_t prepare_func; +/** The data pointer used for custom prepare functions. */ + void *prepare_data; +/** The prepared statement for the default prepare function. */ + sqlite3_stmt *insert_stmts[1]; +#ifdef STUMPLESS_THREAD_SAFETY_SUPPORTED +/** + * Protects this target structure. This mutex must be locked by a thread before + * it uses the database connection or fields in this structure. + */ + config_mutex_t db_mutex; +#endif +}; + +/** + * Destroys an internal SQLite3 target structure. The database handle is not + * modified: if it is needed after this, it must be saved elsewhere. + * + * **Thread Safety: MT-Unsafe** + * This function is not thread safe as it destroys resources that other threads + * would use if they tried to reference this target. + * + * **Async Signal Safety: AS-Unsafe lock heap** + * This function is not safe to call from signal handlers due to the destruction + * of a lock that may be in use as well as the use of the memory deallocation + * function to release memory. + * + * **Async Cancel Safety: AC-Unsafe lock heap** + * This function is not safe to call from threads that may be asynchronously + * cancelled, as the cleanup of the lock may not be completed, and the memory + * deallocation function may not be AC-Safe itself. + * + * @since release v2.2.0 + * + * @param target The SQLite3 target to close. + */ +void +destroy_sqlite3_target( const struct sqlite3_target *target ); + +/** + * Creates a new SQLite3 internal target structure with the given database + * handle. + * + * **Thread Safety: MT-Safe** + * This function is thread safe. + * + * **Async Signal Safety: AS-Unsafe heap** + * This function is not safe to call from signal handlers due to the use of + * memory allocation functions. + * + * **Async Cancel Safety: AC-Unsafe heap** + * This function is not safe to call from threads that may be asynchronously + * cancelled, as the memory allocation function may not be AC-Safe itself. + * + * @since release v2.2.0 + * + * @param db The database handle to use for the new target. + * + * @return The new target. If an error occurs then NULL is returned and an error + * code is set appropriately. + */ +struct sqlite3_target * +new_sqlite3_target( sqlite3 *db ); + +/** + * Sends an entry to a database target. + * + * **Thread Safety: MT-Safe** + * This function is thread safe. The db_mutex is used to coordinate requests + * to the database. + * + * **Async Signal Safety: AS-Unsafe lock** + * This function is not safe to call from signal handlers due to the use of a + * non-reentrant lock to coordinate database access. + * + * **Async Cancel Safety: AC-Unsafe lock** + * This function is not safe to call from threads that may be asynchronously + * cancelled, due to the use of a lock that could be left locked. + * + * @since release v2.2.0 + * + * @param target The SQLite3 target to send the entry to. + * + * @param entry The entry to send to the database. + * + * @return A value greater than or equal to zero if no errors were encountered. + * If an error was encountered then a negative value is returned an an error + * code is set appropriately. + */ +int +send_entry_to_sqlite3_target( const struct stumpless_target *target, + const struct stumpless_entry *entry ); + +#endif /* __STUMPLESS_PRIVATE_TARGET_SQLITE3_H */ diff --git a/include/private/validate.h b/include/private/validate.h index e14133be9..5b23e45ef 100644 --- a/include/private/validate.h +++ b/include/private/validate.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ /* - * Copyright 2020-2022 Joel E. Anderson + * Copyright 2020-2023 Joel E. Anderson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,21 +17,21 @@ */ #ifndef __STUMPLESS_PRIVATE_VALIDATE_H -# define __STUMPLESS_PRIVATE_VALIDATE_H +#define __STUMPLESS_PRIVATE_VALIDATE_H -# include -# include -# include -# include "private/config.h" -# include "private/config/locale/wrapper.h" -# include "private/error.h" +#include +#include +#include +#include "private/config.h" +#include "private/config/locale/wrapper.h" +#include "private/error.h" /** * Checks to see if the variable with the provided name is NULL, and if it is * then raises an argument empty error and returns NULL. */ # define VALIDATE_ARG_NOT_NULL( ARG_NAME ) \ -if( unlikely( ARG_NAME == NULL ) ) { \ +if( unlikely( ( ARG_NAME ) == NULL ) ) { \ raise_argument_empty( L10N_NULL_ARG_ERROR_MESSAGE( #ARG_NAME ) ); \ return NULL; \ } @@ -45,7 +45,7 @@ if( unlikely( ARG_NAME == NULL ) ) { \ * negative value is needed to signify failure. */ # define VALIDATE_ARG_NOT_NULL_INT_RETURN( ARG_NAME ) \ -if( unlikely( ARG_NAME == NULL ) ) { \ +if( unlikely( ( ARG_NAME ) == NULL ) ) { \ raise_argument_empty( L10N_NULL_ARG_ERROR_MESSAGE( #ARG_NAME ) ); \ return -STUMPLESS_ARGUMENT_EMPTY; \ } @@ -59,11 +59,26 @@ if( unlikely( ARG_NAME == NULL ) ) { \ * and zero is needed to signify failure. */ # define VALIDATE_ARG_NOT_NULL_UNSIGNED_RETURN( ARG_NAME ) \ -if( unlikely( ARG_NAME == NULL ) ) { \ +if( unlikely( ( ARG_NAME ) == NULL ) ) { \ raise_argument_empty( L10N_NULL_ARG_ERROR_MESSAGE( #ARG_NAME ) ); \ return 0; \ } +/** + * Checks to see if the variable with the provided name is NULL, and if it is + * then raises an argument empty error and returns. + * + * This is nearly identical to VALIDATE_ARG_NOT_NULL, but is suitable for use in + * functions where the return type is void. + * + * @since release v2.2.0 + */ +# define VALIDATE_ARG_NOT_NULL_VOID_RETURN( ARG_NAME ) \ +if( unlikely( ( ARG_NAME) == NULL ) ) { \ + raise_argument_empty( L10N_NULL_ARG_ERROR_MESSAGE( #ARG_NAME ) ); \ + return; \ +} + /** * Checks that the passed in app name is of the appropriate length and * contains only printable ASCII characters. diff --git a/include/stumpless.h b/include/stumpless.h index e08220e63..d707f4316 100644 --- a/include/stumpless.h +++ b/include/stumpless.h @@ -97,6 +97,7 @@ #include #include #include +#include #include #include #include @@ -130,6 +131,14 @@ # include #endif +#ifdef STUMPLESS_SQLITE3_TARGETS_SUPPORTED +/** @example sqlite3_example.c + * Demonstrates how to work with a sqlite3 target. + * + * @since release v2.2.0 + */ +#endif + #ifdef STUMPLESS_WINDOWS_EVENT_LOG_TARGETS_SUPPORTED /** @example wel_example.c * Demonstrates how to work with a Windows Event Log target. diff --git a/include/stumpless/config.h.in b/include/stumpless/config.h.in index 6f71d8502..7234d6126 100644 --- a/include/stumpless/config.h.in +++ b/include/stumpless/config.h.in @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ /* - * Copyright 2018-2022 Joel E. Anderson + * Copyright 2018-2023 Joel E. Anderson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,6 +54,25 @@ /** Defined if socket targets are supported by this build. */ #cmakedefine STUMPLESS_SOCKET_TARGETS_SUPPORTED 1 +/** + * A string literal with the name of the table used by default for SQLite3 + * targets. + * + * @since release v2.2.0 + */ +#define STUMPLESS_DEFAULT_SQLITE3_TABLE_NAME_STRING \ +"@SQLITE3_DEFAULT_TABLE_NAME@" + +/** + * The maximum number of retries for SQLite operations. + * + * @since release v2.2.0 + */ +#define STUMPLESS_SQLITE3_RETRY_MAX @SQLITE3_RETRY_MAX@ + +/** Defined if sqlite3 targets are supported by this build. */ +#cmakedefine STUMPLESS_SQLITE3_TARGETS_SUPPORTED 1 + /** Defined if this build can directly replace syslog.h usage. */ #cmakedefine STUMPLESS_SYSLOG_H_COMPATIBLE 1 diff --git a/include/stumpless/entry.h b/include/stumpless/entry.h index 355d496c4..f2c89a76c 100644 --- a/include/stumpless/entry.h +++ b/include/stumpless/entry.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ /* - * Copyright 2018-2022 Joel E. Anderson + * Copyright 2018-2023 Joel E. Anderson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -95,7 +95,10 @@ struct stumpless_entry { char app_name[STUMPLESS_MAX_APP_NAME_LENGTH + 1]; /** The length of the app name, without the NULL terminator. */ size_t app_name_length; -/** The message of this entry, as a NULL-terminated string. */ +/** + * The message of this entry, as a NULL-terminated string. This may be NULL + * if the entry does not have a message set. + */ char *message; /** The length of the message in bytes, without the NULL terminator. */ size_t message_length; @@ -589,6 +592,10 @@ stumpless_get_entry_hostname( const struct stumpless_entry *entry ); * Note that if this message was originally set using format specifiers, the * result will have them substituted, instead of the original placeholders. * + * It is also important to note that the message may be NULL if the entry + * does not have one. This differs from other fields like the app id or msgid, + * which will be an RFC 5424 NILVALUE '-' if they have not been set. + * * In versions prior to v2.0.0, the returned pointer was to the internal buffer * used to store the name and was not to be modified by the caller. This * behavior changed in v2.0.0 in order to avoid thread safety issues. @@ -611,9 +618,8 @@ stumpless_get_entry_hostname( const struct stumpless_entry *entry ); * * @param entry The entry to get the message of. * - * @return The message of the entry if no error is encountered. If an error - * was encountered, then NULL is returned and an error code is set - * appropriately. + * @return The message of the entry (which may be NULL). If an error was + * encountered, then NULL is returned and an error code is set appropriately. */ STUMPLESS_PUBLIC_FUNCTION const char * @@ -1226,43 +1232,6 @@ struct stumpless_entry * stumpless_set_entry_hostname( struct stumpless_entry *entry, const char *hostname ); -/** - * Sets the msgid for an entry. - * - * **Thread Safety: MT-Safe race:msgid** - * This function is thread safe, of course assuming that the msgid is not - * changed by any other threads during execution. A mutex is used to coordinate - * changes to the entry while it is being modified. - * - * **Async Signal Safety: AS-Unsafe lock heap** - * This function is not safe to call from signal handlers due to the use of a - * non-reentrant lock to coordinate changes and the use of memory management - * functions to create the new msgid and free the old one. - * - * **Async Cancel Safety: AC-Unsafe lock heap** - * This function is not safe to call from threads that may be asynchronously - * cancelled, due to the use of a lock that could be left locked as well as - * memory management functions. - * - * @since release v1.6.0. - * - * @param entry The entry for which the msgid will be set. - * - * @param msgid A NULL-terminated string holding the new msgid for the entry. - * The string must be in the ASCII printable range 33 <= character <= 126 as - * specified in RFC5424. This will be copied in to the entry, and therefore - * may be modified or freed after this call without affecting the entry. If - * this is NULL, then a single '-' character will be used, as specified as - * the NILVALUE in RFC 5424. - * - * @return The modified entry if no error is encountered. If an error is - * encountered, then NULL is returned and an error code is set appropriately. - */ -STUMPLESS_PUBLIC_FUNCTION -struct stumpless_entry * -stumpless_set_entry_msgid( struct stumpless_entry *entry, - const char *msgid ); - /** * Sets the message of a given entry. * @@ -1340,6 +1309,43 @@ struct stumpless_entry * stumpless_set_entry_message_str( struct stumpless_entry *entry, const char *message ); +/** + * Sets the msgid for an entry. + * + * **Thread Safety: MT-Safe race:msgid** + * This function is thread safe, of course assuming that the msgid is not + * changed by any other threads during execution. A mutex is used to coordinate + * changes to the entry while it is being modified. + * + * **Async Signal Safety: AS-Unsafe lock heap** + * This function is not safe to call from signal handlers due to the use of a + * non-reentrant lock to coordinate changes and the use of memory management + * functions to create the new msgid and free the old one. + * + * **Async Cancel Safety: AC-Unsafe lock heap** + * This function is not safe to call from threads that may be asynchronously + * cancelled, due to the use of a lock that could be left locked as well as + * memory management functions. + * + * @since release v1.6.0. + * + * @param entry The entry for which the msgid will be set. + * + * @param msgid A NULL-terminated string holding the new msgid for the entry. + * The string must be in the ASCII printable range 33 <= character <= 126 as + * specified in RFC5424. This will be copied in to the entry, and therefore + * may be modified or freed after this call without affecting the entry. If + * this is NULL, then a single '-' character will be used, as specified as + * the NILVALUE in RFC 5424. + * + * @return The modified entry if no error is encountered. If an error is + * encountered, then NULL is returned and an error code is set appropriately. + */ +STUMPLESS_PUBLIC_FUNCTION +struct stumpless_entry * +stumpless_set_entry_msgid( struct stumpless_entry *entry, + const char *msgid ); + /** * Puts the param in the element at the given index of an entry. * diff --git a/include/stumpless/error.h b/include/stumpless/error.h index 1863cc8d0..dab644c62 100644 --- a/include/stumpless/error.h +++ b/include/stumpless/error.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ /* - * Copyright 2018-2022 Joel E. Anderson + * Copyright 2018-2023 Joel E. Anderson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ */ #ifndef __STUMPLESS_ERROR_H -# define __STUMPLESS_ERROR_H +#define __STUMPLESS_ERROR_H #include #include @@ -132,7 +132,25 @@ extern "C" { * * @since release v2.2.0 */\ - ERROR( STUMPLESS_INVALID_PARAM_STRING, 29 ) + ERROR( STUMPLESS_INVALID_PARAM_STRING, 29 ) \ +/** + * SQLite3 was busy and could not complete the request. + * + * @since release v2.2.0 + */\ + ERROR( STUMPLESS_SQLITE3_BUSY, 30 ) \ +/** + * A custom callback to a SQLite3 target failed. + * + * @since release v2.2.0 + */\ + ERROR( STUMPLESS_SQLITE3_CALLBACK_FAILURE, 31 ) \ +/** + * SQLite3 encountered a failure. + * + * @since release v2.2.0 + */\ + ERROR( STUMPLESS_SQLITE3_FAILURE, 32 ) /** * An (enum) identifier of the types of errors that might be encountered. diff --git a/include/stumpless/target.h b/include/stumpless/target.h index cd4801395..49848dcfc 100644 --- a/include/stumpless/target.h +++ b/include/stumpless/target.h @@ -25,50 +25,53 @@ */ #ifndef __STUMPLESS_TARGET_H -# define __STUMPLESS_TARGET_H +#define __STUMPLESS_TARGET_H -# include -# include -# include -# include -# include -# include -# include -# include +#include +#include +#include +#include +#include +#include +#include +#include /** The file opened if the default target is to a file. */ -# define STUMPLESS_DEFAULT_FILE "stumpless-default.log" +#define STUMPLESS_DEFAULT_FILE "stumpless-default.log" /** The name of the default target. */ -# define STUMPLESS_DEFAULT_TARGET_NAME "stumpless-default" +#define STUMPLESS_DEFAULT_TARGET_NAME "stumpless-default" -# ifdef __cplusplus +#ifdef __cplusplus extern "C" { -# endif +#endif /**< write to a character buffer */ # define STUMPLESS_BUFFER_TARGET_VALUE 0 /**< write to a file */ -# define STUMPLESS_FILE_TARGET_VALUE 1 +#define STUMPLESS_FILE_TARGET_VALUE 1 /**< call a custom function */ -# define STUMPLESS_FUNCTION_TARGET_VALUE 2 +#define STUMPLESS_FUNCTION_TARGET_VALUE 2 /**< send to the systemd journald service */ -# define STUMPLESS_JOURNALD_TARGET_VALUE 3 +#define STUMPLESS_JOURNALD_TARGET_VALUE 3 /**< send to a network endpoint */ -# define STUMPLESS_NETWORK_TARGET_VALUE 4 +#define STUMPLESS_NETWORK_TARGET_VALUE 4 /**< write to a Unix socket */ -# define STUMPLESS_SOCKET_TARGET_VALUE 5 +#define STUMPLESS_SOCKET_TARGET_VALUE 5 /**< write to a FILE stream */ -# define STUMPLESS_STREAM_TARGET_VALUE 6 +#define STUMPLESS_STREAM_TARGET_VALUE 6 /**< add to the Windows Event Log */ -# define STUMPLESS_WINDOWS_EVENT_LOG_TARGET_VALUE 7 +#define STUMPLESS_WINDOWS_EVENT_LOG_TARGET_VALUE 7 + +/**< add to a SQLite3 database */ +#define STUMPLESS_SQLITE3_TARGET_VALUE 8 /** * A macro function that runs the provided action once for each target_type, @@ -76,7 +79,7 @@ extern "C" { * first being the symbol name of the target_type, and the second the numeric * value of the target_type. */ -# define STUMPLESS_FOREACH_TARGET_TYPE( ACTION )\ +#define STUMPLESS_FOREACH_TARGET_TYPE( ACTION )\ /**< write to a character buffer */\ ACTION( STUMPLESS_BUFFER_TARGET, STUMPLESS_BUFFER_TARGET_VALUE )\ /**< write to a file */\ @@ -92,7 +95,9 @@ ACTION( STUMPLESS_SOCKET_TARGET, STUMPLESS_SOCKET_TARGET_VALUE )\ /**< write to a FILE stream */\ ACTION( STUMPLESS_STREAM_TARGET, STUMPLESS_STREAM_TARGET_VALUE )\ /**< add to the Windows Event Log */\ -ACTION( STUMPLESS_WINDOWS_EVENT_LOG_TARGET, STUMPLESS_WINDOWS_EVENT_LOG_TARGET_VALUE ) +ACTION( STUMPLESS_WINDOWS_EVENT_LOG_TARGET, STUMPLESS_WINDOWS_EVENT_LOG_TARGET_VALUE )\ +/**< add to a SQLite3 database */\ +ACTION( STUMPLESS_SQLITE3_TARGET, STUMPLESS_SQLITE3_TARGET_VALUE ) /** Types of targets that may be created. */ enum stumpless_target_type { @@ -184,13 +189,13 @@ struct stumpless_target { * @since release v2.1.0 */ stumpless_filter_func_t filter; -# ifdef STUMPLESS_THREAD_SAFETY_SUPPORTED +#ifdef STUMPLESS_THREAD_SAFETY_SUPPORTED /** * A pointer to a mutex which protects all target fields. The exact type of * this mutex depends on the build. */ void *mutex; -# endif +#endif }; /** @@ -426,6 +431,10 @@ stumpless_add_message_str( struct stumpless_target *target, /** * Closes a target. * + * Targets that can be closed in multiple ways will be closed in the most + * complete way possible. Specifically, SQLite3 targets will have the underlying + * database connection closed as well. + * * This function can be used when you'd like to avoid checking the type of the * target and then calling the appropriate close function. Note that use of this * doesn't actually avoid the check - it just does the check on your behalf. It @@ -1631,8 +1640,8 @@ const char * stumpless_get_target_type_string( enum stumpless_target_type target ); -# ifdef __cplusplus +#ifdef __cplusplus } /* extern "C" */ -# endif +#endif #endif /* __STUMPLESS_TARGET_H */ diff --git a/include/stumpless/target/sqlite3.h b/include/stumpless/target/sqlite3.h new file mode 100644 index 000000000..7b8f49b13 --- /dev/null +++ b/include/stumpless/target/sqlite3.h @@ -0,0 +1,496 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +/* + * Copyright 2023 Joel E. Anderson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @file + * SQLite3 targets allow logs to be sent to a SQLite3 database. The database, + * tables, and fields can be left up to the defaults, or customized as needed. + * + * **Thread Safety: MT-Safe** + * Logging to sqlite3 targets is thread safe. A mutex is used to coordinate + * transactions. + * + * **Async Signal Safety: AS-Unsafe lock** + * Logging to sqlite3 targets is not signal safe, as a non-reentrant lock is + * used to coordinate transactions. + * + * **Async Cancel Safety: AC-Unsafe lock** + * Logging to sqlite3 targets is not safe to call from threads that may be + * asynchronously cancelled, as the cleanup of the lock may not be completed. + * + * @since release v2.2.0 + */ + +#ifndef __STUMPLESS_TARGET_SQLITE3_H +#define __STUMPLESS_TARGET_SQLITE3_H + +#include +#include +#include +#include +#include + +/** + * The default SQL statement used to insert entries into a SQLite3 database. + * + * @since release v2.2.0 + */ +#define STUMPLESS_DEFAULT_SQLITE3_INSERT_SQL \ +"INSERT INTO " STUMPLESS_DEFAULT_SQLITE3_TABLE_NAME_STRING " " \ +"( prival, version, timestamp, hostname, app_name, procid, msgid," \ +" structured_data, message ) " \ +"VALUES ( $prival, 1, $timestamp, $hostname, $app_name, $procid, $msgid, " \ + "$structured_data, $message )" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * A function for generating SQLite3 prepared statements for a given entry. See + * \ref stumpless_set_sqlite3_prepare for the semantics of writing and using a + * prepare function with SQLite3 targets. + * + * @since release v2.2.0 + */ +typedef +void * +( *stumpless_sqlite3_prepare_func_t )( const struct stumpless_entry *entry, + void *data, + size_t *count ); + +/** + * Closes a SQLite3 target and its database handle. + * + * This function can fail if the database handle cannot be closed. In this case, + * the target is not closed, and no resources are released. + * + * **Thread Safety: MT-Unsafe** + * This function is not thread safe as it destroys resources that other threads + * would use if they tried to reference this target. + * + * **Async Signal Safety: AS-Unsafe lock heap** + * This function is not safe to call from signal handlers due to the destruction + * of a lock that may be in use as well as the use of the memory deallocation + * function to release memory. + * + * **Async Cancel Safety: AC-Unsafe lock heap** + * This function is not safe to call from threads that may be asynchronously + * cancelled, as the cleanup of the lock may not be completed, and the memory + * deallocation function may not be AC-Safe itself. + * + * @since release v2.2.0 + * + * @param target The SQLite3 target to close. + * + * @return true if the target was closed, and false if not. An error code is + * set appropriately if the target could not be closed. + */ +STUMPLESS_PUBLIC_FUNCTION +bool +stumpless_close_sqlite3_target_and_db( const struct stumpless_target *target ); + +/** + * Closes a SQLite3 target, but does not touch the database handle. + * + * **Thread Safety: MT-Unsafe** + * This function is not thread safe as it destroys resources that other threads + * would use if they tried to reference this target. + * + * **Async Signal Safety: AS-Unsafe lock heap** + * This function is not safe to call from signal handlers due to the destruction + * of a lock that may be in use as well as the use of the memory deallocation + * function to release memory. + * + * **Async Cancel Safety: AC-Unsafe lock heap** + * This function is not safe to call from threads that may be asynchronously + * cancelled, as the cleanup of the lock may not be completed, and the memory + * deallocation function may not be AC-Safe itself. + * + * @since release v2.2.0 + * + * @param target The SQLite3 target to close. + */ +STUMPLESS_PUBLIC_FUNCTION +void +stumpless_close_sqlite3_target_only( const struct stumpless_target *target ); + +/** + * Creates a table in the target's database for use with the default SQLite3 + * insertion behavior. The schema of this table is described below. Note that + * the value of \c STUMPLESS_DEFAULT_SQLITE3_TABLE_NAME_STRING is configurable + * and set at build time for the library. + * + * \code{.sql} + * CREATE TABLE STUMPLESS_DEFAULT_SQLITE3_TABLE_NAME_STRING ( + * log_id INTEGER PRIMARY KEY, + * prival INTEGER NOT NULL, + * version INTEGER NOT NULL, + * timestamp TEXT, + * hostname TEXT, + * app_name TEXT, + * procid TEXT, + * msgid TEXT, + * structured_data TEXT, + * message TEXT + * ); + * \endcode + * + * **Thread Safety: MT-Safe** + * This function is thread safe as a mutex is used to coordinate the table + * creation with other target modifications. + * + * **Async Signal Safety: AS-Unsafe lock** + * Thisi function is not signal safe, as a non-reentrant lock is used + * to coordinate the read of the target with other potential accesses. + * + * **Async Cancel Safety: AC-Unsafe lock** + * This function is not safe to call from threads that may be asynchronously + * cancelled, as the cleanup of the lock used for the target may not be + * completed. + * + * @since release v2.2.0 + * + * @param target The target to create the default table in. + * + * @return The modified target if no error is encountered. If an error is + * encountered, then NULL is returned and an error code is set appropriately. + */ +STUMPLESS_PUBLIC_FUNCTION +struct stumpless_target * +stumpless_create_default_sqlite3_table( struct stumpless_target *target ); + +/** + * Gets the SQLite3 database handle used by the target. + * + * The database handle is used by stumpless sqlite3 routines, and serialized + * using an internal mutex. When you use this handle outside of the library, you + * must ensure that your operations are also thread safe without this mutex, for + * example by using the SQLITE_OPEN_NOMUTEX or SQLITE_OPEN_FULLMUTEX options. + * + * **Thread Safety: MT-Safe** + * This function is thread safe as a mutex is used to coordinate the retrieval + * of the handle with other target modifications. + * + * **Async Signal Safety: AS-Unsafe lock** + * Thisi function is not signal safe, as a non-reentrant lock is used + * to coordinate the read of the target with other potential accesses. + * + * **Async Cancel Safety: AC-Unsafe lock** + * This function is not safe to call from threads that may be asynchronously + * cancelled, as the cleanup of the lock used for the target may not be + * completed. + * + * @since release v2.2.0 + * + * @return The sqlite3 database handle for this target, a sqlite3 *. The return + * type is void * so that all users of stumpless do not have to have sqlite3 + * types defined in order to include the headers. + */ +STUMPLESS_PUBLIC_FUNCTION +void * +stumpless_get_sqlite3_db( const struct stumpless_target *target ); + +/** + * Gets the SQL statement used to insert entries into the database. See + * \ref stumpless_set_sqlite3_insert_sql to change this statement. + * + * **Thread Safety: MT-Safe** + * This function is thread safe as a mutex is used to coordinate the retrieval + * of the SQL statement with other target modifications. + * + * **Async Signal Safety: AS-Unsafe lock** + * Thisi function is not signal safe, as a non-reentrant lock is used + * to coordinate the read of the target with other potential accesses. + * + * **Async Cancel Safety: AC-Unsafe lock** + * This function is not safe to call from threads that may be asynchronously + * cancelled, as the cleanup of the lock used for the target may not be + * completed. + * + * @since release v2.2.0 + * + * @param target The target to get the insert SQL from. + * + * @return The current SQL used to insert entries into the database, as a UTF-8 + * encoded string. If an error occurs then NULL is returned and an error is set + * appropriately. + */ +STUMPLESS_PUBLIC_FUNCTION +const char * +stumpless_get_sqlite3_insert_sql( const struct stumpless_target *target ); + +/** + * Gets the preparation function and data pointer used to prepare statements + * for insertion into the database. See \ref stumpless_set_sqlite3_prepare + * to change this function. + * + * **Thread Safety: MT-Safe** + * This function is thread safe as a mutex is used to coordinate the retrieval + * of the function with other target modifications. + * + * **Async Signal Safety: AS-Unsafe lock** + * Thisi function is not signal safe, as a non-reentrant lock is used + * to coordinate the read of the target with other potential accesses. + * + * **Async Cancel Safety: AC-Unsafe lock** + * This function is not safe to call from threads that may be asynchronously + * cancelled, as the cleanup of the lock used for the target may not be + * completed. + * + * @since release v2.2.0 + * + * @param target The target to get the prepare function from. + * + * @param data A pointer to a variable where the data pointer should be written + * to. If this is NULL, then it is ignored. + * + * @return The current prepare function for the target. If an error occurs + * then NULL is returned and an error is set appropriately. + */ +STUMPLESS_PUBLIC_FUNCTION +stumpless_sqlite3_prepare_func_t +stumpless_get_sqlite3_prepare( const struct stumpless_target *target, + void **data ); + +/** + * Opens a SQLite3 target. + * + * This is equivalent to calling \ref stumpless_open_sqlite3_target_with_options + * with SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE as the flags and NULL as the + * VFS module name. + * + * **Thread Safety: MT-Safe race:name** + * This function is thread safe, of course assuming that name is not modified by + * any other threads during execution. + * + * **Async Signal Safety: AS-Unsafe heap** + * This function is not safe to call from signal handlers due to the use of + * memory allocation functions. + * + * **Async Cancel Safety: AC-Unsafe heap** + * This function is not safe to call from threads that may be asynchronously + * cancelled, as the memory allocation function may not be AC-Safe itself. + * + * @since release v2.2.0 + * + * @param name The name of the database to open. + * + * @return The opened target if no error is encountered. In the event of an + * error, NULL is returned and an error code is set appropriately. + */ +STUMPLESS_PUBLIC_FUNCTION +struct stumpless_target * +stumpless_open_sqlite3_target( const char *name ); + +/** + * Opens a SQLite3 target with an already-open database handle. + * + * This function allows a specialized database to be opened, or an existing + * handle to be used. For example, if an in-memory database is needed, it + * can be opened and then passed to this function for logging. + * + * Note that there are two close functions for SQLite3 targets: + * \ref stumpless_close_sqlite3_target_and_db and + * \ref stumpless_close_sqlite3_target_only. Be sure to call the appropriate + * one depending on whether you want this handle to be closed when the target + * is closed. + * + * **Thread Safety: MT-Safe** + * This function is thread safe. + * + * **Async Signal Safety: AS-Unsafe heap** + * This function is not safe to call from signal handlers due to the use of + * memory allocation functions. + * + * **Async Cancel Safety: AC-Unsafe heap** + * This function is not safe to call from threads that may be asynchronously + * cancelled, as the memory allocation function may not be AC-Safe itself. + * + * @since release v2.2.0 + * + * @param db The database handle to use. This should be a sqlite3 *, though it + * is not declared as such so that sqlite3 headers are not required. + * + * @return The opened target if no error is encountered. In the event of an + * error, NULL is returned and an error code is set appropriately. + */ +STUMPLESS_PUBLIC_FUNCTION +struct stumpless_target * +stumpless_open_sqlite3_target_from_db( void *db ); + +/** + * Opens a SQLite3 target with the provided options. The three parameters are + * passed directly to sqlite3_open_v2. + * + * **Thread Safety: MT-Safe race:name race:vfs** + * This function is thread safe, of course assuming that name and vfs are not + * modified by any other threads during execution. + * + * **Async Signal Safety: AS-Unsafe heap** + * This function is not safe to call from signal handlers due to the use of + * memory allocation functions. + * + * **Async Cancel Safety: AC-Unsafe heap** + * This function is not safe to call from threads that may be asynchronously + * cancelled, as the memory allocation function may not be AC-Safe itself. + * + * @since release v2.2.0 + * + * @param name The name of the database to open. + * + * @param flags Flags as defined for sqlite3_open_v2. + * + * @param vfs The name of the VFS module to use as defined by sqlite3_open_v2. + * + * @return The opened target if no error is encountered. In the event of an + * error, NULL is returned and an error code is set appropriately. + */ +STUMPLESS_PUBLIC_FUNCTION +struct stumpless_target * +stumpless_open_sqlite3_target_with_options( const char *name, + int flags, + const char *vfs ); + +/** + * Sets the SQL statement used to insert entries into the database. + * + * **Thread Safety: MT-Safe** + * This function is thread safe as a mutex is used to coordinate the changes + * with other target modifications. + * + * **Async Signal Safety: AS-Unsafe lock** + * Thisi function is not signal safe, as a non-reentrant lock is used + * to coordinate the read of the target with other potential accesses. + * + * **Async Cancel Safety: AC-Unsafe lock** + * This function is not safe to call from threads that may be asynchronously + * cancelled, as the cleanup of the lock used for the target may not be + * completed. + * + * @since release v2.2.0 + * + * @param target The target to set the insert SQL for. + * + * @param sql The new SQL insertion statement to use for the target. This string + * must be valid for the duration of its use in the target, as a pointer to it + * is kept internally. + * + * @return The modified target on success, or NULL on failure. In the event + * of failure an error code is set appropriately. + */ +STUMPLESS_PUBLIC_FUNCTION +struct stumpless_target * +stumpless_set_sqlite3_insert_sql( struct stumpless_target *target, + const char *sql ); + +/** + * Set the function used to prepare statements for entries to this target. + * + * Preparation functions take three arguments: the entry that is being sent to + * the database target, a pointer provided when the function is set, and an + * output parameter where the number of prepared statements to use is written. + * The function should return a pointer to an array of prepared statements that + * should be committed to the database for the entry, or NULL if an error + * occurs. The default prepare function is \ref stumpless_sqlite3_prepare, which + * is a good place to look for an example. + * + * Note that the return type is a void * instead of the actual type of + * sqlite3_stmt **. This is so that the SQLite3 headers are not needed. + * + * Be careful when using a custom prepare function in builds where SQLite was + * directly embedded into Stumpless instead of dynamically linked. If this is + * the case, using a single databse handle in both SQLite functions compiled + * into Stumpless and SQLite functions compiled in a separate module may cause + * serious issues, for example due to static data structures. The example + * \ref sqlite3_example.c does this, and is a good way to check if you are in + * this situation as it will fail in this scenario. + * + * **Thread Safety: MT-Safe** + * This function is thread safe as a mutex is used to coordinate accesses of the + * target structures. + * + * **Async Signal Safety: AS-Unsafe lock** + * Thisi function is not signal safe, as a non-reentrant lock is used + * to coordinate the read of the target with other potential accesses. + * + * **Async Cancel Safety: AC-Unsafe lock** + * This function is not safe to call from threads that may be asynchronously + * cancelled, as the cleanup of the lock used for the target may not be + * completed. + * + * @since release v2.2.0 + * + * @param target The target to set the function for. + * + * @param preparer The new prepare function to use in the target. + * + * @param data A pointer that will be passed to the prepare function on each + * invocation. + * + * @return The modified target on success, or NULL on failure. In the event + * of failure an error code is set appropriately. + */ +STUMPLESS_PUBLIC_FUNCTION +struct stumpless_target * +stumpless_set_sqlite3_prepare( struct stumpless_target *target, + stumpless_sqlite3_prepare_func_t preparer, + void *data ); + +/** + * The default prepare function used for SQLite3 targets. + * + * This function will generate a single prepared statement based on the target's + * current insert SQL statement. See \ref stumpless_set_sqlite3_insert_sql for + * how to set the SQL used by this function. + * + * **Thread Safety: MT-Safe** + * This function is thread safe as a mutex is used to coordinate accesses of the + * entry and target structures. + * + * **Async Signal Safety: AS-Unsafe lock** + * Thisi function is not signal safe, as a non-reentrant lock is used + * to coordinate the read of the target with other potential accesses. + * + * **Async Cancel Safety: AC-Unsafe lock** + * This function is not safe to call from threads that may be asynchronously + * cancelled, as the cleanup of the lock used for the target may not be + * completed. + * + * @since release v2.2.0 + * + * @param entry The entry to prepare the statement based on. + * + * @param data The internal SQLite3 target structure. + * + * @param count A pointer to an output variable where the number of valid + * prepared statements will be written to. + * + * @return A pointer to an array of sqlite3_stmt pointers on success, or NULL + * on failure. In the event of failure an error code is set appropriately. + */ +STUMPLESS_PUBLIC_FUNCTION +void * +stumpless_sqlite3_prepare( const struct stumpless_entry *entry, + void *data, + size_t *count ); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __STUMPLESS_TARGET_SQLITE3_H */ diff --git a/include/test/helper/fixture.hpp b/include/test/helper/fixture.hpp index 01f18d7f4..4a2582311 100644 --- a/include/test/helper/fixture.hpp +++ b/include/test/helper/fixture.hpp @@ -59,6 +59,16 @@ create_empty_entry( void ); struct stumpless_entry * create_entry( void ); +/** + * Creates an entry with all nullable fields set to NULL. + * + * @since release v2.2.0 + * + * @return The newly-created entry, or NULL if an error occurred. + */ +struct stumpless_entry * +create_nil_entry( void ); + /** * Returns a buffer holding the contents of the fuzz corpus file at the named * location in the test/corpora folder. For example, a name of "message/ascii" diff --git a/include/test/helper/memory_counter.hpp b/include/test/helper/memory_counter.hpp index 6270cb388..5a0732238 100644 --- a/include/test/helper/memory_counter.hpp +++ b/include/test/helper/memory_counter.hpp @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ /* - * Copyright 2018-2021 Joel E. Anderson + * Copyright 2018-2023 Joel E. Anderson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,9 @@ struct memory_counter { size_t realloc_count; size_t free_count; size_t free_total; + void * ( *previous_malloc )( size_t ); + void * ( *previous_realloc )( void *, size_t ); + void ( *previous_free )( void * ); }; #define INIT_MEMORY_COUNTER(PREFIX) \ @@ -38,10 +41,17 @@ PREFIX##_memory_counter.alloc_total = 0; \ PREFIX##_memory_counter.realloc_count = 0; \ PREFIX##_memory_counter.free_count = 0; \ PREFIX##_memory_counter.free_total = 0; \ +PREFIX##_memory_counter.previous_malloc = malloc; \ +PREFIX##_memory_counter.previous_realloc = realloc; \ +PREFIX##_memory_counter.previous_free = free; \ stumpless_set_malloc( PREFIX##_memory_counter_malloc ); \ stumpless_set_realloc( PREFIX##_memory_counter_realloc ); \ stumpless_set_free( PREFIX##_memory_counter_free ); +#define FINALIZE_MEMORY_COUNTER(PREFIX) \ +stumpless_set_malloc( PREFIX##_memory_counter.previous_malloc ); \ +stumpless_set_realloc( PREFIX##_memory_counter.previous_realloc ); \ +stumpless_set_free( PREFIX##_memory_counter.previous_free ); #define NEW_MEMORY_COUNTER(PREFIX) \ static struct memory_counter PREFIX##_memory_counter; \ diff --git a/include/test/helper/rfc5424.hpp b/include/test/helper/rfc5424.hpp index 652142e41..a12134b17 100644 --- a/include/test/helper/rfc5424.hpp +++ b/include/test/helper/rfc5424.hpp @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ /* - * Copyright 2018-2021 Joel E. Anderson + * Copyright 2018-2023 Joel E. Anderson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,42 @@ */ #ifndef __STUMPLESS_TEST_HELPER_RFC5424_HPP -# define __STUMPLESS_TEST_HELPER_RFC5424_HPP +#define __STUMPLESS_TEST_HELPER_RFC5424_HPP -/* +#include + +/** + * Matches timestamps formatted according to RFC 5424. + * + * Matched groups are included as follows: + * 0 - TIMESTAMP (the entire timestamp) + * 1 - FULL-DATE "T" FULL-YEAR if TIMESTAMP was not NILVALUE + * 2 - DATE-FULLYEAR + * 3 - DATE-MONTH + * 4 - DATE-MDAY + * 5 - TIME-SECFRAC if it was provided + * 6 - TIME-OFFSET + * 7 - TIME-NUMOFFSET if provided (that is, if TIME-OFFSET was not "Z") + */ +#define RFC_5424_TIMESTAMP_REGEX_STRING \ +"-|(" /* NILVALUE */ \ +"(\\d{4})-(\\d{2})-(\\d{2})" /* FULL-DATE */ \ +"T" /* "T" */ \ +"\\d{2}:\\d{2}:\\d{2}" /* PARTIAL-TIME */ \ +"(\\.\\d{1,6})?" /* TIME-SECFRAC */ \ +"(Z|(" /* TIME-OFFSET */ \ +"(\\+|-)\\d{2}:\\d{2}" /* TIME-NUMOFFSET */ \ +")))" + + +#define RFC_5424_TIMESTAMP_DATE_FULLYEAR_MATCH_INDEX 2 +#define RFC_5424_TIMESTAMP_DATE_MONTH_MATCH_INDEX 3 +#define RFC_5424_TIMESTAMP_DATE_MDAY_MATCH_INDEX 4 +#define RFC_5424_TIMESTAMP_TIME_SECFRAC_MATCH_INDEX 5 +#define RFC_5424_TIMESTAMP_TIME_OFFSET_MATCH_INDEX 6 +#define RFC_5424_TIMESTAMP_TIME_NUMOFFSET_MATCH_INDEX 7 + +/** * This format is specified in https://tools.ietf.org/html/rfc5424 * Note that this regular expression does not ensure total compliance with the * RFC. Specifically, the following aspects of the specification are left @@ -46,86 +79,74 @@ * absent in the message. For subggroups that are always a component of the RFC * grammar, there is a #define of the form RFC_5424_xxx_MATCH_INDEX, for example * RFC_5424_HOSTNAME_MATCH_INDEX. - * 0 - SYSLOG-MSG (the entire message) - * 1 - PRIVAL - * 2 - VERSION - * 3 - TIMESTAMP - * 4 - FULL-DATE "T" FULL-YEAR if TIMESTAMP was not NILVALUE - * 5 - DATE-FULLYEAR - * 6 - DATE-MONTH - * 7 - DATE-MDAY - * 8 - TIME-SECFRAC if it was provided - * 9 - TIME-OFFSET - * 10 - TIME-NUMOFFSET if provided (that is, if TIME-OFFSET was not "Z") - * 11 - The "+" or "-" character in TIME-NUMOFFSET if it was provided - * 12 - HOSTNAME - * 13 - HOSTNAME if it was not NILVALUE - * 14 - APP-NAME - * 15 - APP-NAME if it was not NILVALUE - * 16 - PROCID - * 17 - PROCID if it was not NILVALUE - * 18 - MSGID - * 19 - MSGID if it was not NILVALUE - * 20 - STRUCTURED-DATA - * 21 - 1*SD-ELEMENT if STRUCTURED-DATA was not NILVALUE - * 22 - 1*SD-ELEMENT IF STRUCTURED-DATA was not NILVALUE - * 23 - the first SD-ID in the message - * 24 - everything from the first PARAM-NAME to the last "PARAM-VALUE" - * 25 - MSG (the log message itself) + * 0 - SYSLOG-MSG (the entire message) + * 1 - PRIVAL + * 2 - VERSION + * 3 - TIMESTAMP + * 4-10 - Fields as matched by RFC_5424_TIMESTAMP_REGEX_STRING + * 11 - The "+" or "-" character in TIME-NUMOFFSET if it was provided + * 12 - HOSTNAME + * 13 - HOSTNAME if it was not NILVALUE + * 14 - APP-NAME + * 15 - APP-NAME if it was not NILVALUE + * 16 - PROCID + * 17 - PROCID if it was not NILVALUE + * 18 - MSGID + * 19 - MSGID if it was not NILVALUE + * 20 - STRUCTURED-DATA + * 21 - 1*SD-ELEMENT if STRUCTURED-DATA was not NILVALUE + * 22 - 1*SD-ELEMENT IF STRUCTURED-DATA was not NILVALUE + * 23 - the first SD-ID in the message + * 24 - everything from the first PARAM-NAME to the last "PARAM-VALUE" + * 25 - MSG (the log message itself) */ -# define RFC_5424_REGEX_STRING "^<(\\d{1,3})>" /* PRI */ \ - "([1-9]\\d{0,2})" /* VERSION */ \ - " " /* SP */ \ - "(-|(" /* TIMESTAMP */ \ - "(\\d{4})-(\\d{2})-(\\d{2})" /* FULL-DATE */ \ - "T" /* "T" */ \ - "\\d{2}:\\d{2}:\\d{2}" /* PARTIAL-TIME */ \ - "(\\.\\d{1,6})?" /* TIME-SECFRAC */ \ - "(Z|(" /* TIME-OFFSET */ \ - "(\\+|-)\\d{2}:\\d{2}" /* TIME-NUMOFFSET */ \ - "))" /* TIME-OFFSET */ \ - "))" /* TIMESTAMP */ \ - " " /* SP */ \ - "(-|([!-~]{1,255}))" /* HOSTNAME */ \ - " " /* SP */ \ - "(-|([!-~]{1,48}))" /* APP-NAME */ \ - " " /* SP */ \ - "(-|([!-~]{1,128}))" /* PROCID */ \ - " " /* SP */ \ - "(-|([!-~]{1,32}))" /* MSGID */ \ - " " /* SP */ \ - "(-|((" /* STRUCTURED-DATA */ \ - "\\[" /* SD-ELEMENT */ \ - "([!#-<>-\\\\\\^-~]{1,32})" /* SD-ID */ \ - "( [!#-<>-\\\\\\^-~]{1,32}" /* PARAM-NAME */ \ - "=\".*\")*" /* PARAM-VALUE */ \ - "\\]" /* SD-ELEMENT */ \ - ")+))" /* STRUCTURED-DATA */ \ - "( " /* SP */ \ - "(.*))?$" /* MSG */ +#define RFC_5424_REGEX_STRING \ +"^<(\\d{1,3})>" /* PRI */ \ +"([1-9]\\d{0,2})" /* VERSION */ \ +" " /* SP */ \ +"(" RFC_5424_TIMESTAMP_REGEX_STRING ")" /* TIMESTAMP */ \ +" " /* SP */ \ +"(-|([!-~]{1,255}))" /* HOSTNAME */ \ +" " /* SP */ \ +"(-|([!-~]{1,48}))" /* APP-NAME */ \ +" " /* SP */ \ +"(-|([!-~]{1,128}))" /* PROCID */ \ +" " /* SP */ \ +"(-|([!-~]{1,32}))" /* MSGID */ \ +" " /* SP */ \ +"(-|((" /* STRUCTURED-DATA */ \ +"\\[" /* SD-ELEMENT */ \ +"([!#-<>-\\\\\\^-~]{1,32})" /* SD-ID */ \ +"( [!#-<>-\\\\\\^-~]{1,32}" /* PARAM-NAME */ \ +"=\".*\")*" /* PARAM-VALUE */ \ +"\\]" /* SD-ELEMENT */ \ +")+))" /* STRUCTURED-DATA */ \ +"( " /* SP */ \ +"(.*))?$" /* MSG */ -# define RFC_5424_SYSLOG_MSG_MATCH_INDEX 0 -# define RFC_5424_PRIVAL_MATCH_INDEX 1 -# define RFC_5424_VERSION_MATCH_INDEX 2 -# define RFC_5424_TIMESTAMP_MATCH_INDEX 3 -# define RFC_5424_DATE_FULLYEAR_MATCH_INDEX 5 -# define RFC_5424_DATE_MONTH_MATCH_INDEX 6 -# define RFC_5424_DATE_MDAY_MATCH_INDEX 7 -# define RFC_5424_TIME_SECFRAC_MATCH_INDEX 8 -# define RFC_5424_TIME_OFFSET_MATCH_INDEX 9 -# define RFC_5424_TIME_NUMOFFSET_MATCH_INDEX 10 -# define RFC_5424_HOSTNAME_MATCH_INDEX 12 -# define RFC_5424_APP_NAME_MATCH_INDEX 14 -# define RFC_5424_PROCID_MATCH_INDEX 16 -# define RFC_5424_MSGID_MATCH_INDEX 18 -# define RFC_5424_STRUCTURED_DATA_MATCH_INDEX 20 -# define RFC_5424_SD_ELEMENTS_MATCH_INDEX 21 -# define RFC_5424_MSG_MATCH_INDEX 25 +#define RFC_5424_SYSLOG_MSG_MATCH_INDEX 0 +#define RFC_5424_PRIVAL_MATCH_INDEX 1 +#define RFC_5424_VERSION_MATCH_INDEX 2 +#define RFC_5424_TIMESTAMP_MATCH_INDEX 3 +#define RFC_5424_DATE_FULLYEAR_MATCH_INDEX 5 +#define RFC_5424_DATE_MONTH_MATCH_INDEX 6 +#define RFC_5424_DATE_MDAY_MATCH_INDEX 7 +#define RFC_5424_TIME_SECFRAC_MATCH_INDEX 8 +#define RFC_5424_TIME_OFFSET_MATCH_INDEX 9 +#define RFC_5424_TIME_NUMOFFSET_MATCH_INDEX 10 +#define RFC_5424_HOSTNAME_MATCH_INDEX 12 +#define RFC_5424_APP_NAME_MATCH_INDEX 14 +#define RFC_5424_PROCID_MATCH_INDEX 16 +#define RFC_5424_MSGID_MATCH_INDEX 18 +#define RFC_5424_STRUCTURED_DATA_MATCH_INDEX 20 +#define RFC_5424_SD_ELEMENTS_MATCH_INDEX 21 +#define RFC_5424_MSG_MATCH_INDEX 25 -# define RFC_5424_PRIVAL_MIN 0 -# define RFC_5424_PRIVAL_MAX 191 +#define RFC_5424_PRIVAL_MIN 0 +#define RFC_5424_PRIVAL_MAX 191 -void TestRFC5424Compliance( const char *syslog_msg ); -void TestRFC5424StructuredData( const char *sd_elements ); +void TestRFC5424Compliance( const std::string &syslog_msg ); +void TestRFC5424StructuredData( const std::string &sd_elements ); +void TestRFC5424Timestamp( const std::string ×tamp ); #endif /* __STUMPLESS_TEST_HELPER_RFC5424_HPP */ diff --git a/include/test/helper/utf8.hpp b/include/test/helper/utf8.hpp index 9c921f024..3a07d86c7 100644 --- a/include/test/helper/utf8.hpp +++ b/include/test/helper/utf8.hpp @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ /* - * Copyright 2018-2021 Joel E. Anderson + * Copyright 2018-2023 Joel E. Anderson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,10 @@ */ #ifndef __STUMPLESS_TEST_HELPER_UTF8_HPP -# define __STUMPLESS_TEST_HELPER_UTF8_HPP +#define __STUMPLESS_TEST_HELPER_UTF8_HPP -void TestUTF8Compliance( const char *str ); +#include + +void TestUTF8Compliance( const std::string &str ); #endif /* __STUMPLESS_TEST_HELPER_UTF8_HPP */ diff --git a/scripts/README.md b/scripts/README.md index aef5a6e9a..bf1804312 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -7,9 +7,6 @@ include directory. This is not the desired end state, but will be needed until Wrapture has a way to designate the output directory for header files. -## indent.sh -Runs `indent` on a source file to apply standard formatting. - ## Repair-HeaderDllExports.ps1 Modifies a C++ header file to add `__declspec(dllexport)` to all class declarations. This is needed to patch C++ classes generated by Wrapture so diff --git a/scripts/indent.sh b/scripts/indent.sh deleted file mode 100755 index 5c19596bd..000000000 --- a/scripts/indent.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -indent \ - --blank-lines-before-block-comments \ - --braces-on-func-def-line \ - --braces-on-if-line \ - --braces-on-struct-decl-line \ - --case-indentation2 \ - --comment-line-length80 \ - --cuddle-else \ - --format-all-comments \ - --ignore-profile \ - --indent-level2 \ - --line-length80 \ - --no-space-after-for \ - --no-space-after-if \ - --no-space-after-function-call-names \ - --no-tabs \ - --preprocessor-indentation2 \ - --space-after-parentheses \ - --tab-size2 \ - "$@" diff --git a/src/config/sqlite3_unsupported.c b/src/config/sqlite3_unsupported.c new file mode 100644 index 000000000..6e8d4c4a6 --- /dev/null +++ b/src/config/sqlite3_unsupported.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2023 Joel E. Anderson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include "private/config/locale/wrapper.h" +#include "private/error.h" + +bool +stumpless_close_sqlite3_target_and_db( const struct stumpless_target *target ) { + raise_target_unsupported( L10N_SQLITE3_TARGETS_UNSUPPORTED ); + return false; +} + +void +stumpless_close_sqlite3_target_only( const struct stumpless_target *target ) { + raise_target_unsupported( L10N_SQLITE3_TARGETS_UNSUPPORTED ); + return; +} + +struct stumpless_target * +stumpless_create_default_sqlite3_table( struct stumpless_target *target ) { + raise_target_unsupported( L10N_SQLITE3_TARGETS_UNSUPPORTED ); + return NULL; +} + +void * +stumpless_get_sqlite3_db( const struct stumpless_target *target ) { + raise_target_unsupported( L10N_SQLITE3_TARGETS_UNSUPPORTED ); + return NULL; +} + +const char * +stumpless_get_sqlite3_insert_sql( const struct stumpless_target *target ) { + raise_target_unsupported( L10N_SQLITE3_TARGETS_UNSUPPORTED ); + return NULL; +} + +stumpless_sqlite3_prepare_func_t +stumpless_get_sqlite3_prepare( const struct stumpless_target *target, + void **data ) { + raise_target_unsupported( L10N_SQLITE3_TARGETS_UNSUPPORTED ); + return NULL; +} + +struct stumpless_target * +stumpless_open_sqlite3_target( const char *name ) { + raise_target_unsupported( L10N_SQLITE3_TARGETS_UNSUPPORTED ); + return NULL; +} + +struct stumpless_target * +stumpless_open_sqlite3_target_from_db( void *db ) { + raise_target_unsupported( L10N_SQLITE3_TARGETS_UNSUPPORTED ); + return NULL; +} + +struct stumpless_target * +stumpless_open_sqlite3_target_with_options( const char *name, + int flags, + const char *vfs ) { + raise_target_unsupported( L10N_SQLITE3_TARGETS_UNSUPPORTED ); + return NULL; +} + +struct stumpless_target * +stumpless_set_sqlite3_insert_sql( struct stumpless_target *target, + const char *sql ) { + raise_target_unsupported( L10N_SQLITE3_TARGETS_UNSUPPORTED ); + return NULL; +} + +struct stumpless_target * +stumpless_set_sqlite3_prepare( struct stumpless_target *target, + stumpless_sqlite3_prepare_func_t preparer, + void *data ) { + raise_target_unsupported( L10N_SQLITE3_TARGETS_UNSUPPORTED ); + return NULL; +} + +void * +stumpless_sqlite3_prepare( const struct stumpless_entry *entry, + void *data, + size_t *count ) { + raise_target_unsupported( L10N_SQLITE3_TARGETS_UNSUPPORTED ); + return NULL; +} diff --git a/src/entry.c b/src/entry.c index 1b673281b..b2d5b76ee 100644 --- a/src/entry.c +++ b/src/entry.c @@ -412,11 +412,15 @@ stumpless_get_entry_message( const struct stumpless_entry *entry ) { VALIDATE_ARG_NOT_NULL( entry ); lock_entry( entry ); - message_copy = alloc_mem( entry->message_length + 1 ); - if( !message_copy ) { - goto cleanup_and_return; + if( !entry->message ) { + message_copy = NULL; + } else { + message_copy = alloc_mem( entry->message_length + 1 ); + if( !message_copy ) { + goto cleanup_and_return; + } + memcpy( message_copy, entry->message, entry->message_length + 1 ); } - memcpy( message_copy, entry->message, entry->message_length + 1 ); clear_error( ); cleanup_and_return: diff --git a/src/error.c b/src/error.c index 71e7e68e4..698db7242 100644 --- a/src/error.c +++ b/src/error.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "private/config/locale/wrapper.h" #include "private/config/wrapper/thread_safety.h" @@ -326,6 +327,22 @@ raise_socket_send_failure( const char *message, raise_error( STUMPLESS_SOCKET_SEND_FAILURE, message, code, code_type ); } +void +raise_sqlite3_busy( void ) { + raise_error( STUMPLESS_SQLITE3_BUSY, + L10N_SQLITE3_BUSY_ERROR_MESSAGE, + STUMPLESS_SQLITE3_RETRY_MAX, + L10N_SQLITE3_RETRY_COUNT_CODE_TYPE ); +} + +void +raise_sqlite3_failure( const char *message, int code ) { + raise_error( STUMPLESS_SQLITE3_FAILURE, + message, + code, + L10N_SQLITE3_RESULT_CODE_TYPE ); +} + void raise_stream_write_failure( void ) { raise_error( STUMPLESS_STREAM_WRITE_FAILURE, diff --git a/src/strbuilder.c b/src/strbuilder.c index 554ffa679..1139726f1 100644 --- a/src/strbuilder.c +++ b/src/strbuilder.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 /* - * Copyright 2018-2021 Joel E. Anderson + * Copyright 2018-2023 Joel E. Anderson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -218,6 +218,15 @@ strbuilder_new( void ) { return NULL; } +struct strbuilder * +strbuilder_reset( struct strbuilder *builder ) { + if( builder ) { + builder->position = builder->buffer; + } + + return builder; +} + char * strbuilder_to_string( const struct strbuilder *builder ) { size_t string_length; diff --git a/src/target.c b/src/target.c index 9755a1d69..416f7242e 100644 --- a/src/target.c +++ b/src/target.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 /* - * Copyright 2018-2022 Joel E. Anderson + * Copyright 2018-2023 Joel E. Anderson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +43,7 @@ #include "private/config/wrapper/wel.h" #include "private/config/wrapper/journald.h" #include "private/config/wrapper/socket.h" +#include "private/config/wrapper/sqlite3.h" #include "private/config/wrapper/thread_safety.h" #include "private/element.h" #include "private/entry.h" @@ -219,6 +220,12 @@ stumpless_add_entry( struct stumpless_target *target, goto finish; } + // sqlite3 targets are not formatted + if( target->type == STUMPLESS_SQLITE3_TARGET ) { + result = config_send_entry_to_sqlite3_target( target, entry ); + goto finish; + } + // entry was not formatted before if( !buffer ){ builder = format_entry( entry, target ); @@ -401,6 +408,10 @@ stumpless_close_target( struct stumpless_target *target ) { config_close_socket_target( target ); break; + case STUMPLESS_SQLITE3_TARGET: + config_close_sqlite3_target_and_db( target ); + break; + case STUMPLESS_STREAM_TARGET: stumpless_close_stream_target( target ); break; diff --git a/src/target/sqlite3.c b/src/target/sqlite3.c new file mode 100644 index 000000000..f7ae2da6e --- /dev/null +++ b/src/target/sqlite3.c @@ -0,0 +1,634 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2023 Joel E. Anderson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "private/config.h" +#include "private/config/locale/wrapper.h" +#include "private/config/wrapper/get_now.h" +#include "private/config/wrapper/thread_safety.h" +#include "private/entry.h" +#include "private/error.h" +#include "private/facility.h" +#include "private/formatter.h" +#include "private/inthelper.h" +#include "private/memory.h" +#include "private/severity.h" +#include "private/strbuilder.h" +#include "private/target.h" +#include "private/target/sqlite3.h" +#include "private/validate.h" + + +bool +stumpless_close_sqlite3_target_and_db( const struct stumpless_target *target ) { + struct sqlite3_target *db_target; + if( unlikely( !target ) ) { + raise_argument_empty( L10N_NULL_ARG_ERROR_MESSAGE( "target" ) ); + return false; + } + + if( target->type != STUMPLESS_SQLITE3_TARGET ) { + raise_target_incompatible( L10N_INVALID_TARGET_TYPE_ERROR_MESSAGE ); + return false; + } + + // we use v2 here to prevent close from being blocked by pending transactions + db_target = target->id; + int sql_result = sqlite3_close_v2( db_target->db ); + if( sql_result != SQLITE_OK ) { + raise_sqlite3_failure( L10N_SQLITE3_CLOSE_FAILED_ERROR_MESSAGE, + sql_result ); + return false; + } + + destroy_sqlite3_target( db_target ); + destroy_target( target ); + clear_error( ); + return true; +} + +void +stumpless_close_sqlite3_target_only( const struct stumpless_target *target ) { + VALIDATE_ARG_NOT_NULL_VOID_RETURN( target ); + + if( target->type != STUMPLESS_SQLITE3_TARGET ) { + raise_target_incompatible( L10N_INVALID_TARGET_TYPE_ERROR_MESSAGE ); + return; + } + + destroy_sqlite3_target( target->id ); + destroy_target( target ); + clear_error( ); +} + +struct stumpless_target * +stumpless_create_default_sqlite3_table( struct stumpless_target *target ) { + struct sqlite3_target *db_target; + const char* create_sql = "CREATE TABLE " + STUMPLESS_DEFAULT_SQLITE3_TABLE_NAME_STRING " " + "( log_id INTEGER PRIMARY KEY," + " prival INTEGER NOT NULL," + " version INTEGER NOT NULL," + " timestamp TEXT," + " hostname TEXT," + " app_name TEXT," + " procid TEXT," + " msgid TEXT," + " structured_data TEXT," + " message TEXT )"; + sqlite3_stmt *create_statement = NULL; + int sql_result; + size_t try_count = 0; + bool busy; + struct stumpless_target *return_result; + + VALIDATE_ARG_NOT_NULL( target ); + + db_target = target->id; + return_result = target; + clear_error(); + + config_lock_mutex( &db_target->db_mutex ); + + sql_result = sqlite3_prepare_v2( db_target->db, + create_sql, + -1, + &create_statement, + NULL ); + if( sql_result != SQLITE_OK ) { + raise_sqlite3_failure( L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE, + sql_result ); + return_result = NULL; + goto cleanup_and_finish; + } + + do { + try_count++; + sql_result = sqlite3_step( create_statement ); + + busy = sql_result == SQLITE_BUSY; + if( busy && try_count >= STUMPLESS_SQLITE3_RETRY_MAX ) { + raise_sqlite3_busy(); + return_result = NULL; + goto cleanup_and_finish; + } + } while( busy ); + + if( sql_result != SQLITE_DONE ) { + raise_sqlite3_failure( L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE, + sql_result ); + return_result = NULL; + } + +cleanup_and_finish: + sqlite3_finalize( create_statement ); + config_unlock_mutex( &db_target->db_mutex ); + return return_result; +} + +void * +stumpless_get_sqlite3_db( const struct stumpless_target *target ) { + struct sqlite3_target *db_target; + + VALIDATE_ARG_NOT_NULL( target ); + + if( target->type != STUMPLESS_SQLITE3_TARGET ) { + raise_target_incompatible( L10N_INVALID_TARGET_TYPE_ERROR_MESSAGE ); + return NULL; + } + + db_target = target->id; + return db_target->db; +} + +const char * +stumpless_get_sqlite3_insert_sql( const struct stumpless_target *target ) { + const struct sqlite3_target *db_target; + const char *result; + + VALIDATE_ARG_NOT_NULL( target ); + + db_target = target->id; + + config_lock_mutex( &db_target->db_mutex ); + result = db_target->insert_sql; + config_unlock_mutex( &db_target->db_mutex ); + + return result; +} + +stumpless_sqlite3_prepare_func_t +stumpless_get_sqlite3_prepare( const struct stumpless_target *target, + void **data ) { + struct sqlite3_target *db_target; + + VALIDATE_ARG_NOT_NULL( target ); + + db_target = target->id; + + if( data ) { + *data = db_target->prepare_data; + } + + clear_error(); + return db_target->prepare_func; +} + +struct stumpless_target * +stumpless_open_sqlite3_target( const char *name ) { + int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + return stumpless_open_sqlite3_target_with_options( name, flags, NULL ); +} + +struct stumpless_target * +stumpless_open_sqlite3_target_from_db( void *db ) { + struct stumpless_target *target; + + VALIDATE_ARG_NOT_NULL( db ); + + target = new_target( STUMPLESS_SQLITE3_TARGET, "" ); + + if( !target ) { + goto fail; + } + + target->id = new_sqlite3_target( db ); + if( !target->id ) { + goto fail_id; + } + + stumpless_set_current_target( target ); + return target; + +fail_id: + destroy_target( target ); +fail: + return NULL; +} + +struct stumpless_target * +stumpless_open_sqlite3_target_with_options( const char *name, + int flags, + const char *vfs ) { + struct stumpless_target *target; + sqlite3 *db; + int sql_result; + + VALIDATE_ARG_NOT_NULL( name ); + + target = new_target( STUMPLESS_SQLITE3_TARGET, name ); + + if( !target ) { + goto fail; + } + + db = NULL; + sql_result = sqlite3_open_v2( name, &db, flags, vfs ); + if( sql_result != SQLITE_OK ) { + raise_sqlite3_failure( L10N_SQLITE3_OPEN_FAILED_ERROR_MESSAGE, sql_result ); + goto fail_db; + } + + target->id = new_sqlite3_target( db ); + if( !target->id ) { + goto fail_db; + } + + stumpless_set_current_target( target ); + return target; + +fail_db: + sqlite3_close( db ); + destroy_target( target ); +fail: + return NULL; +} + +struct stumpless_target * +stumpless_set_sqlite3_insert_sql( struct stumpless_target *target, + const char *sql ) { + struct sqlite3_target *db_target; + + VALIDATE_ARG_NOT_NULL( target ); + + db_target = target->id; + + config_lock_mutex( &db_target->db_mutex ); + db_target->insert_sql = sql; + sqlite3_finalize( db_target->insert_stmts[0] ); + db_target->insert_stmts[0] = NULL; + config_unlock_mutex( &db_target->db_mutex ); + + return target; +} + +struct stumpless_target * +stumpless_set_sqlite3_prepare( struct stumpless_target *target, + stumpless_sqlite3_prepare_func_t preparer, + void *data ) { + struct sqlite3_target *db_target; + + VALIDATE_ARG_NOT_NULL( target ); + VALIDATE_ARG_NOT_NULL( preparer ); + + db_target = target->id; + + config_lock_mutex( &db_target->db_mutex ); + db_target->prepare_func = preparer; + db_target->prepare_data = data; + config_unlock_mutex( &db_target->db_mutex ); + + return target; +} + +void * +stumpless_sqlite3_prepare( const struct stumpless_entry *entry, + void *data, + size_t *count ) { + struct sqlite3_target *target; + int sql_result; + int length; + sqlite3_stmt *insert_stmt; + int prival_index; + int facility_index; + int severity_index; + int timestamp_index; + int hostname_index; + int app_name_index; + int procid_index; + int msgid_index; + int structured_data_index; + int message_index; + char timestamp[RFC_5424_TIMESTAMP_BUFFER_SIZE]; + struct strbuilder *builder; + const struct strbuilder *strbuilder_result; + const char *buffer; + size_t buffer_size; + const char *msg; + + buffer_size = config_get_now( timestamp ); + + target = data; + if( !target->insert_stmts[0] ) { + sql_result = sqlite3_prepare_v2( target->db, + target->insert_sql, + -1, + &target->insert_stmts[0], + NULL ); + if( sql_result != SQLITE_OK ) { + raise_sqlite3_failure( L10N_SQLITE3_PREPARE_FAILED_ERROR_MESSAGE, + sql_result ); + return NULL; + } + } else { + sqlite3_reset( target->insert_stmts[0] ); + } + + builder = strbuilder_new(); + if( !builder ) { + return NULL; + } + + insert_stmt = target->insert_stmts[0]; + + // we can gather the indexes before we need to lock the entry + prival_index = sqlite3_bind_parameter_index( insert_stmt, "$prival" ); + facility_index = sqlite3_bind_parameter_index( insert_stmt, "$facility" ); + severity_index = sqlite3_bind_parameter_index( insert_stmt, "$severity" ); + timestamp_index = sqlite3_bind_parameter_index( insert_stmt, "$timestamp" ); + hostname_index = sqlite3_bind_parameter_index( insert_stmt, "$hostname" ); + app_name_index = sqlite3_bind_parameter_index( insert_stmt, "$app_name" ); + procid_index = sqlite3_bind_parameter_index( insert_stmt, "$procid" ); + msgid_index = sqlite3_bind_parameter_index( insert_stmt, "$msgid" ); + structured_data_index = sqlite3_bind_parameter_index( insert_stmt, + "$structured_data" ); + message_index = sqlite3_bind_parameter_index( insert_stmt, "$message" ); + + lock_entry( entry ); + + if( prival_index != 0 ) { + sql_result = sqlite3_bind_int( insert_stmt, prival_index, entry->prival ); + if( sql_result != SQLITE_OK ) { + msg = L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( "$prival" ); + goto fail_bind; + } + } + + if( facility_index != 0 ) { + sql_result = sqlite3_bind_int( insert_stmt, + facility_index, + get_facility( entry->prival ) ); + if( sql_result != SQLITE_OK ) { + msg = L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( "$facility" ); + goto fail_bind; + } + } + + if( severity_index != 0 ) { + sql_result = sqlite3_bind_int( insert_stmt, + severity_index, + get_severity( entry->prival ) ); + if( sql_result != SQLITE_OK ) { + msg = L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( "$severity" ); + goto fail_bind; + } + } + + if( timestamp_index != 0 ) { + if( buffer_size == 1 && timestamp[0] == '-' ) { + sql_result = sqlite3_bind_null( insert_stmt, timestamp_index ); + } else { + // transient since it lives on the stack for this function, which will + // disappear before the step occurs + sql_result = sqlite3_bind_text( insert_stmt, + timestamp_index, + timestamp, + cap_size_t_to_int( buffer_size ), + SQLITE_TRANSIENT ); + } + if( sql_result != SQLITE_OK ) { + msg = L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( "$timestamp" ); + goto fail_bind; + } + } + + if( hostname_index != 0 ) { + strbuilder_result = strbuilder_append_hostname( builder ); + if( !strbuilder_result ) { + goto fail; + } + buffer = strbuilder_get_buffer( builder, &buffer_size ); + if( buffer_size == 1 && buffer[0] == '-' ) { + sql_result = sqlite3_bind_null( insert_stmt, hostname_index ); + } else { + // transient since we reuse and destroy this strbuilder before the + // statement is executed + sql_result = sqlite3_bind_text( insert_stmt, + hostname_index, + buffer, + cap_size_t_to_int( buffer_size ), + SQLITE_TRANSIENT ); + } + if( sql_result != SQLITE_OK ) { + msg = L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( "$hostname" ); + goto fail_bind; + } + } + + if( app_name_index != 0 ) { + if( entry->app_name_length == 1 && entry->app_name[0] == '-' ) { + sql_result = sqlite3_bind_null( insert_stmt, app_name_index ); + } else { + length = cap_size_t_to_int( entry->app_name_length ); + sql_result = sqlite3_bind_text( insert_stmt, + app_name_index, + entry->app_name, + length, + SQLITE_STATIC ); + } + if( sql_result != SQLITE_OK ) { + msg = L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( "$app_name" ); + goto fail_bind; + } + } + + if( procid_index != 0 ) { + strbuilder_reset( builder ); + strbuilder_result = strbuilder_append_procid( builder ); + if( !strbuilder_result ) { + goto fail; + } + buffer = strbuilder_get_buffer( builder, &buffer_size ); + if( buffer_size == 1 && buffer[0] == '-' ) { + sql_result = sqlite3_bind_null( insert_stmt, procid_index ); + } else { + // transient since we reuse and destroy this strbuilder before the + // statement is executed + sql_result = sqlite3_bind_text( insert_stmt, + procid_index, + buffer, + cap_size_t_to_int( buffer_size ), + SQLITE_TRANSIENT ); + } + if( sql_result != SQLITE_OK ) { + msg = L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( "$procid" ); + goto fail_bind; + } + } + + if( msgid_index != 0 ) { + if( entry->msgid_length == 1 && entry->msgid[0] == '-' ) { + sql_result = sqlite3_bind_null( insert_stmt, msgid_index ); + } else { + length = cap_size_t_to_int( entry->msgid_length ); + sql_result = sqlite3_bind_text( insert_stmt, + msgid_index, + entry->msgid, + length, + SQLITE_STATIC ); + } + if( sql_result != SQLITE_OK ) { + msg = L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( "$msgid" ); + goto fail_bind; + } + } + + if( structured_data_index != 0 ) { + strbuilder_reset( builder ); + strbuilder_result = strbuilder_append_structured_data( builder, entry ); + if( !strbuilder_result ) { + goto fail; + } + buffer = strbuilder_get_buffer( builder, &buffer_size ); + if( buffer_size == 1 && buffer[0] == '-' ) { + sql_result = sqlite3_bind_null( insert_stmt, structured_data_index ); + } else { + // transient since we reuse and destroy this strbuilder before the + // statement is executed + sql_result = sqlite3_bind_text( insert_stmt, + structured_data_index, + buffer, + cap_size_t_to_int( buffer_size ), + SQLITE_TRANSIENT ); + } + if( sql_result != SQLITE_OK ) { + msg = L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( "$structured_data" ); + goto fail_bind; + } + } + + if( message_index != 0 ) { + if( entry->message ) { + length = cap_size_t_to_int( entry->message_length ); + sql_result = sqlite3_bind_text( insert_stmt, + message_index, + entry->message, + length, + SQLITE_STATIC ); + } else { + sql_result = sqlite3_bind_null( insert_stmt, message_index ); + } + if( sql_result != SQLITE_OK ) { + msg = L10N_SQLITE3_BIND_FAILED_ERROR_MESSAGE( "$message" ); + goto fail_bind; + } + } + + unlock_entry( entry ); + strbuilder_destroy( builder ); + + *count = 1; + return &target->insert_stmts; + +fail_bind: + raise_sqlite3_failure( msg, sql_result ); +fail: + unlock_entry( entry ); + strbuilder_destroy( builder ); + return NULL; +} + +/* private definitions */ + +void +destroy_sqlite3_target( const struct sqlite3_target *target ) { + sqlite3_finalize( target->insert_stmts[0] ); + config_destroy_mutex( &target->db_mutex ); + free_mem( target ); +} + +struct sqlite3_target * +new_sqlite3_target( sqlite3 *db ) { + struct sqlite3_target *target; + + target = alloc_mem( sizeof( *target ) ); + if( !target ) { + return NULL; + } + + target->db = db; + target->insert_sql = STUMPLESS_DEFAULT_SQLITE3_INSERT_SQL; + target->prepare_func = stumpless_sqlite3_prepare; + target->prepare_data = target; + target->insert_stmts[0] = NULL; + config_init_mutex( &target->db_mutex ); + + return target; +} + +int +send_entry_to_sqlite3_target( const struct stumpless_target *target, + const struct stumpless_entry *entry ) { + struct sqlite3_target *db_target; + size_t stmt_count; + size_t i; + sqlite3_stmt **statements; + int result = 1; + int sql_result; + size_t try_count = 0; + bool busy; + + db_target = target->id; + + config_lock_mutex( &db_target->db_mutex ); + + statements = db_target->prepare_func( entry, + db_target->prepare_data, + &stmt_count ); + if( !statements ) { + result = -1; + if( db_target->prepare_func != &stumpless_sqlite3_prepare ) { + raise_error( STUMPLESS_SQLITE3_CALLBACK_FAILURE, + L10N_SQLITE3_CUSTOM_PREPARE_FAILED_ERROR_MESSAGE, + 0, + NULL ); + } + goto cleanup_and_finish; + } + + for( i = 0; i < stmt_count; i++ ) { + do { + try_count++; + sql_result = sqlite3_step( statements[i] ); + + busy = sql_result == SQLITE_BUSY; + if( busy && try_count >= STUMPLESS_SQLITE3_RETRY_MAX ) { + raise_sqlite3_busy(); + goto cleanup_and_finish; + } + } while( busy ); + + if( sql_result != SQLITE_DONE ) { + result = -1; + raise_sqlite3_failure( L10N_SQLITE3_STEP_FAILED_ERROR_MESSAGE, + sql_result ); + goto cleanup_and_finish; + } + } + +cleanup_and_finish: + config_unlock_mutex( &db_target->db_mutex ); + return result; +} diff --git a/src/windows/stumpless.def b/src/windows/stumpless.def index 5f2ad6df0..ef52d5dab 100644 --- a/src/windows/stumpless.def +++ b/src/windows/stumpless.def @@ -226,3 +226,15 @@ EXPORTS stumpless_prival_from_string @209 stumpless_get_severity_enum_from_buffer @210 stumpless_get_facility_enum_from_buffer @211 + stumpless_close_sqlite3_target_and_db @212 + stumpless_close_sqlite3_target_only @213 + stumpless_create_default_sqlite3_table @214 + stumpless_get_sqlite3_db @215 + stumpless_get_sqlite3_insert_sql @216 + stumpless_get_sqlite3_prepare @217 + stumpless_open_sqlite3_target @218 + stumpless_open_sqlite3_target_from_db @219 + stumpless_open_sqlite3_target_with_options @220 + stumpless_set_sqlite3_insert_sql @221 + stumpless_set_sqlite3_prepare @222 + stumpless_sqlite3_prepare @223 diff --git a/test/function/config/sqlite3_unsupported.cpp b/test/function/config/sqlite3_unsupported.cpp new file mode 100644 index 000000000..9813a4910 --- /dev/null +++ b/test/function/config/sqlite3_unsupported.cpp @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2023 Joel E. Anderson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include "test/helper/assert.hpp" +#include "test/helper/fixture.hpp" + +namespace { + + TEST( Sqlite3TargetTest, CloseTargetAndDb ) { + struct stumpless_target *target; + bool result; + const struct stumpless_error *error; + + target = stumpless_open_stdout_target( "fake-sqlite3-target" ); + ASSERT_NOT_NULL( target ); + + stumpless_close_sqlite3_target_only( target ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_UNSUPPORTED ); + + stumpless_close_stream_target( target ); + stumpless_free_all( ); + } + + TEST( Sqlite3TargetTest, CloseTargetOnly ) { + struct stumpless_target *target; + const struct stumpless_error *error; + + target = stumpless_open_stdout_target( "fake-sqlite3-target" ); + ASSERT_NOT_NULL( target ); + + stumpless_close_sqlite3_target_and_db( target ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_UNSUPPORTED ); + + stumpless_close_stream_target( target ); + stumpless_free_all( ); + } + + TEST( Sqlite3TargetTest, CreateDefaultDatabase ) { + struct stumpless_target *target; + const struct stumpless_error *error; + + target = stumpless_open_stdout_target( "fake-sqlite3-target" ); + ASSERT_NOT_NULL( target ); + + stumpless_create_default_sqlite3_table( target ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_UNSUPPORTED ); + + stumpless_close_stream_target( target ); + stumpless_free_all( ); + } + + TEST( Sqlite3TargetTest, DefaultPrepare ) { + void *result; + const struct stumpless_error *error; + + result = stumpless_sqlite3_prepare( NULL, NULL, NULL ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_UNSUPPORTED ); + EXPECT_NULL( result ); + } + + TEST( Sqlite3TargetTest, GenericClose ) { + struct stumpless_target *target; + const struct stumpless_error *error; + + target = stumpless_open_stdout_target( "fake-sqlite3-target" ); + ASSERT_NOT_NULL( target ); + + target->type = STUMPLESS_SQLITE3_TARGET; + + stumpless_close_target( target ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_UNSUPPORTED ); + + target->type = STUMPLESS_STREAM_TARGET; + stumpless_close_stream_target( target ); + stumpless_free_all( ); + } + + TEST( Sqlite3TargetTest, GetDb ) { + struct stumpless_target *target; + void *result; + const struct stumpless_error *error; + + target = stumpless_open_stdout_target( "fake-sqlite3-target" ); + ASSERT_NOT_NULL( target ); + + result = stumpless_get_sqlite3_db( target ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_UNSUPPORTED ); + EXPECT_NULL( result ); + + stumpless_close_stream_target( target ); + stumpless_free_all( ); + } + + TEST( Sqlite3TargetTest, GetInsertSql ) { + struct stumpless_target *target; + const char *result; + const struct stumpless_error *error; + + target = stumpless_open_stdout_target( "fake-sqlite3-target" ); + ASSERT_NOT_NULL( target ); + + result = stumpless_get_sqlite3_insert_sql( target ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_UNSUPPORTED ); + EXPECT_NULL( result ); + + stumpless_close_stream_target( target ); + stumpless_free_all( ); + } + + TEST( Sqlite3TargetTest, GetPrepare ) { + struct stumpless_target *target; + stumpless_sqlite3_prepare_func_t result; + const struct stumpless_error *error; + + target = stumpless_open_stdout_target( "fake-sqlite3-target" ); + ASSERT_NOT_NULL( target ); + + result = stumpless_get_sqlite3_prepare( target, NULL ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_UNSUPPORTED ); + + stumpless_close_stream_target( target ); + stumpless_free_all( ); + } + + TEST( Sqlite3TargetTest, Open ) { + const struct stumpless_target *target; + const struct stumpless_error *error; + + target = stumpless_open_sqlite3_target( "open-please" ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_UNSUPPORTED ); + EXPECT_NULL( target ); + + stumpless_free_all( ); + } + + TEST( Sqlite3TargetTest, OpenFromDb ) { + const struct stumpless_target *target; + const struct stumpless_error *error; + + target = stumpless_open_sqlite3_target_from_db( NULL ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_UNSUPPORTED ); + EXPECT_NULL( target ); + + stumpless_free_all( ); + } + + TEST( Sqlite3TargetTest, OpenWithOptions ) { + const struct stumpless_target *target; + const struct stumpless_error *error; + + target = stumpless_open_sqlite3_target_with_options( "optional", 0, NULL ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_UNSUPPORTED ); + EXPECT_NULL( target ); + + stumpless_free_all( ); + } + + TEST( Sqlite3TargetTest, SetInsertSql ) { + struct stumpless_target *target; + struct stumpless_target *result; + const struct stumpless_error *error; + + target = stumpless_open_stdout_target( "fake-sqlite3-target" ); + ASSERT_NOT_NULL( target ); + + result = stumpless_set_sqlite3_insert_sql( target, NULL ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_UNSUPPORTED ); + EXPECT_NULL( result ); + + stumpless_close_stream_target( target ); + stumpless_free_all( ); + } + + TEST( Sqlite3TargetTest, SetPrepare ) { + struct stumpless_target *target; + struct stumpless_target *result; + const struct stumpless_error *error; + + target = stumpless_open_stdout_target( "fake-sqlite3-target" ); + ASSERT_NOT_NULL( target ); + + result = stumpless_set_sqlite3_prepare( target, + &stumpless_sqlite3_prepare, + NULL ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_UNSUPPORTED ); + EXPECT_NULL( result ); + + stumpless_close_stream_target( target ); + stumpless_free_all( ); + } + + TEST( Sqlite3TargetTest, Unsupported ) { + struct stumpless_target *target; + struct stumpless_entry *entry; + const struct stumpless_error *error; + int result; + + entry = create_entry( ); + ASSERT_NOT_NULL( entry ); + + target = stumpless_open_stdout_target( "fake-sqlite3-target" ); + ASSERT_NOT_NULL( target ); + + target->type = STUMPLESS_SQLITE3_TARGET; + + result = stumpless_add_entry( target, entry ); + EXPECT_LT( result, 0 ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_UNSUPPORTED ); + + target->type = STUMPLESS_STREAM_TARGET; + stumpless_close_stream_target( target ); + + stumpless_destroy_entry_and_contents( entry ); + stumpless_free_all( ); + } +} diff --git a/test/function/entry.cpp b/test/function/entry.cpp index 2fcfc6b0c..9d8ddbd66 100644 --- a/test/function/entry.cpp +++ b/test/function/entry.cpp @@ -16,11 +16,11 @@ * limitations under the License. */ +#include +#include +#include #include #include -#include -#include -#include #include #include "test/helper/assert.hpp" #include "test/helper/fixture.hpp" @@ -64,6 +64,7 @@ namespace { const char *param_1_1_name = "basic-param"; const char *param_1_1_value = "basic-value"; struct stumpless_param *param_1_1 = NULL; + struct stumpless_entry *nil_entry = NULL; virtual void SetUp( void ) { @@ -81,11 +82,14 @@ namespace { element_2 = stumpless_new_element( element_2_name ); stumpless_add_element( basic_entry, element_2 ); + + nil_entry = create_nil_entry(); } virtual void TearDown( void ){ stumpless_destroy_entry_and_contents( basic_entry ); + stumpless_destroy_entry_only( nil_entry ); stumpless_free_all( ); } }; @@ -546,6 +550,14 @@ namespace { EXPECT_TRUE( set_malloc_result == malloc ); } + TEST_F( EntryTest, GetNullMessage ) { + const char *message; + + message = stumpless_get_entry_message( nil_entry ); + EXPECT_NULL( message ); + EXPECT_NO_ERROR; + } + TEST_F( EntryTest, GetParamByIndex ) { const struct stumpless_param *result; diff --git a/test/function/target/file.cpp b/test/function/target/file.cpp index 9de7714da..7cda40293 100644 --- a/test/function/target/file.cpp +++ b/test/function/target/file.cpp @@ -94,6 +94,7 @@ namespace { filename ); stumpless_free_all( ); + remove( filename ); } TEST( FileTargetCloseTest, NullTarget ) { diff --git a/test/function/target/sqlite3.cpp b/test/function/target/sqlite3.cpp new file mode 100644 index 000000000..cc8aa96bd --- /dev/null +++ b/test/function/target/sqlite3.cpp @@ -0,0 +1,784 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2023 Joel E. Anderson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "test/helper/assert.hpp" +#include "test/helper/fixture.hpp" +#include "test/helper/memory_allocation.hpp" +#include "test/helper/rfc5424.hpp" + +struct test_prepare_data { + struct stumpless_target *target; + sqlite3_stmt *insert_stmts[2]; +}; + +static +void * +failing_prepare( const struct stumpless_entry *entry, + void *data, + size_t *count ) { + return NULL; +} + +static +void * +test_prepare( const struct stumpless_entry *entry, void *data, size_t *count ) { + const char *insert_sql = "INSERT INTO " + STUMPLESS_DEFAULT_SQLITE3_TABLE_NAME_STRING + " (prival, version, message) " + "VALUES (0, 1, ?)"; + int sql_result; + struct test_prepare_data *test_data = ( struct test_prepare_data * ) data; + sqlite3 *db = ( sqlite3 * ) stumpless_get_sqlite3_db( test_data->target ); + + if( !test_data->insert_stmts[0] ) { + sql_result = sqlite3_prepare_v2( db, + insert_sql, + -1, + &test_data->insert_stmts[0], + NULL ); + if( sql_result != SQLITE_OK ) { + return NULL; + } + } else { + sqlite3_reset( test_data->insert_stmts[0] ); + } + + if( !test_data->insert_stmts[1] ) { + sql_result = sqlite3_prepare_v2( db, + insert_sql, + -1, + &test_data->insert_stmts[1], + NULL ); + if( sql_result != SQLITE_OK ) { + return NULL; + } + } else { + sqlite3_reset( test_data->insert_stmts[1] ); + } + + sql_result = sqlite3_bind_text( test_data->insert_stmts[0], + 1, + "test-prepare-1", + -1, + SQLITE_STATIC ); + if( sql_result != SQLITE_OK ) { + return NULL; + } + + sql_result = sqlite3_bind_text( test_data->insert_stmts[1], + 1, + "test-prepare-2", + -1, + SQLITE_STATIC ); + if( sql_result != SQLITE_OK ) { + return NULL; + } + + *count = 2; + return &test_data->insert_stmts; +} + +static +void +TestEntryInDatabase( std::string const &db_name, + std::string const &table_name, + const struct stumpless_entry *entry ) { + sqlite3 *db; + sqlite3_stmt *result_stmt; + int sql_result; + const char *expected_message; + int expected_prival; + int actual_prival; + int actual_version; + const unsigned char *timestamp; + const unsigned char *hostname; + const char *actual_hostname; + const unsigned char *app_name; + const char *actual_app_name; + const unsigned char *procid; + const char *actual_procid; + const unsigned char *msgid; + const char *actual_msgid; + const char *structured_data; + std::string const sd; + + std::ostringstream query_stream; + query_stream << "SELECT prival, version, timestamp, hostname," + " app_name, procid, msgid, structured_data," + " message " + "FROM " << table_name; + + expected_message = stumpless_get_entry_message( entry ); + if( expected_message ) { + query_stream << " WHERE message = ?"; + } else { + query_stream << " WHERE message IS NULL"; + } + std::string result_query = query_stream.str(); + + sql_result = sqlite3_open_v2( db_name.c_str(), + &db, + SQLITE_OPEN_READONLY, + NULL ); + ASSERT_EQ( sql_result, SQLITE_OK ); + + sql_result = sqlite3_prepare_v2( db, + result_query.c_str(), + -1, + &result_stmt, + NULL ); + EXPECT_EQ( sql_result, SQLITE_OK ); + + if( expected_message ) { + sql_result = sqlite3_bind_text( result_stmt, + 1, + expected_message, + -1, + SQLITE_STATIC ); + EXPECT_EQ( sql_result, SQLITE_OK ); + } + + sql_result = sqlite3_step( result_stmt ); + EXPECT_EQ( sql_result, SQLITE_ROW ); + + actual_prival = sqlite3_column_int( result_stmt, 0 ); + expected_prival = stumpless_get_entry_prival( entry ); + EXPECT_EQ( actual_prival, expected_prival ); + + actual_version = sqlite3_column_int( result_stmt, 1 ); + EXPECT_EQ( actual_version, 1 ); + + timestamp = sqlite3_column_text( result_stmt, 2 ); + EXPECT_NOT_NULL( timestamp ); + TestRFC5424Timestamp( reinterpret_cast( timestamp ) ); + + hostname = sqlite3_column_text( result_stmt, 3 ); + // hostname might be NULL if it couldn't be retrieved + if( !hostname ) { + hostname = ( const unsigned char * ) "-"; + } + actual_hostname = stumpless_get_entry_hostname( entry ); + EXPECT_NOT_NULL( actual_hostname ); + EXPECT_STREQ( ( const char * ) hostname, actual_hostname ); + free( ( void * ) actual_hostname ); + + app_name = sqlite3_column_text( result_stmt, 4 ); + if( !app_name ) { + app_name = ( const unsigned char * ) "-"; + } + actual_app_name = stumpless_get_entry_app_name( entry ); + EXPECT_NOT_NULL( actual_app_name ); + EXPECT_STREQ( ( const char * ) app_name, actual_app_name ); + free( ( void * ) actual_app_name ); + + procid = sqlite3_column_text( result_stmt, 5 ); + if( !procid ) { + procid = ( const unsigned char * ) "-"; + } + actual_procid = stumpless_get_entry_procid( entry ); + EXPECT_NOT_NULL( actual_procid ); + EXPECT_STREQ( ( const char * ) procid, actual_procid ); + free( ( void * ) actual_procid ); + + msgid = sqlite3_column_text( result_stmt, 6 ); + if( !msgid ) { + msgid = ( const unsigned char * ) "-"; + } + actual_msgid = stumpless_get_entry_msgid( entry ); + EXPECT_NOT_NULL( actual_msgid ); + EXPECT_STREQ( ( const char * ) msgid, actual_msgid ); + free( ( void * ) actual_msgid ); + + structured_data = ( const char * ) sqlite3_column_text( result_stmt, 7 ); + if( !structured_data ) { + structured_data = "-"; + } + TestRFC5424StructuredData( structured_data ); + + sqlite3_finalize( result_stmt ); + free( ( void * ) expected_message ); + sql_result = sqlite3_close_v2( db ); + EXPECT_EQ( sql_result, SQLITE_OK ); +} + +namespace { + class Sqlite3TargetTest : public::testing::Test { + protected: + sqlite3 *db = NULL; + const char *db_filename = "test_function_fixture.sqlite3"; + struct stumpless_target *target = NULL; + struct stumpless_entry *basic_entry = NULL; + struct stumpless_entry *empty_entry = NULL; + + virtual void + SetUp( void ) { + target = stumpless_open_sqlite3_target( db_filename ); + + stumpless_create_default_sqlite3_table( target ); + + stumpless_set_target_default_app_name( target, "sqlite3-target-test" ); + stumpless_set_target_default_msgid( target, "default-message" ); + + basic_entry = create_entry(); + empty_entry = create_empty_entry(); + + db = ( sqlite3 * ) stumpless_get_sqlite3_db( target ); + } + + virtual void + TearDown( void ) { + stumpless_destroy_entry_and_contents( basic_entry ); + stumpless_destroy_entry_only( empty_entry ); + stumpless_close_sqlite3_target_and_db( target ); + stumpless_free_all(); + remove( db_filename ); + } + }; + + TEST_F( Sqlite3TargetTest, AddBasicEntry ) { + int add_result; + + add_result = stumpless_add_entry( target, basic_entry ); + EXPECT_GE( add_result, 0 ); + EXPECT_NO_ERROR; + + TestEntryInDatabase( std::string( db_filename ), "logs", basic_entry ); + } + + TEST_F( Sqlite3TargetTest, AddNullFieldEntry ) { + struct stumpless_entry *entry; + int add_result; + + entry = create_nil_entry(); + + add_result = stumpless_add_entry( target, entry ); + EXPECT_GE( add_result, 0 ); + EXPECT_NO_ERROR; + + TestEntryInDatabase( std::string( db_filename ), "logs", entry ); + stumpless_destroy_entry_only( entry ); + } + + TEST_F( Sqlite3TargetTest, AddTwoEntries ) { + int add_result; + + add_result = stumpless_add_entry( target, basic_entry ); + EXPECT_GE( add_result, 0 ); + EXPECT_NO_ERROR; + + add_result = stumpless_add_entry( target, empty_entry ); + EXPECT_GE( add_result, 0 ); + EXPECT_NO_ERROR; + + TestEntryInDatabase( std::string( db_filename ), "logs", basic_entry ); + TestEntryInDatabase( std::string( db_filename ), "logs", empty_entry ); + } + + TEST_F( Sqlite3TargetTest, FailedPrepare ) { + const struct stumpless_target *set_result; + int add_result; + const struct stumpless_error *error; + + set_result = stumpless_set_sqlite3_prepare( target, failing_prepare, NULL ); + ASSERT_EQ( set_result, target ); + EXPECT_NO_ERROR; + + add_result = stumpless_add_entry( target, basic_entry ); + EXPECT_LT( add_result, 0 ); + EXPECT_ERROR_ID_EQ( STUMPLESS_SQLITE3_CALLBACK_FAILURE ); + } + + TEST_F( Sqlite3TargetTest, CustomHardcodedInsert ) { + const struct stumpless_target *set_result; + const char *insert_sql = "INSERT INTO logs (prival, version, message) " + "VALUES (0, 1, 'hardcoded')"; + const char *current_sql; + int add_result; + const char *result_query = "SELECT prival, version, message " + "FROM logs WHERE message = 'hardcoded'"; + sqlite3_stmt *result_stmt; + int sql_result; + int prival; + int version; + + set_result = stumpless_set_sqlite3_insert_sql( target, insert_sql ); + ASSERT_EQ( set_result, target ); + + current_sql = stumpless_get_sqlite3_insert_sql( target ); + ASSERT_EQ( current_sql, insert_sql ); + + add_result = stumpless_add_entry( target, basic_entry ); + EXPECT_GE( add_result, 0 ); + EXPECT_NO_ERROR; + + sql_result = sqlite3_prepare_v2( db, result_query, -1, &result_stmt, NULL ); + EXPECT_EQ( sql_result, SQLITE_OK ); + + sql_result = sqlite3_step( result_stmt ); + EXPECT_EQ( sql_result, SQLITE_ROW ); + + prival = sqlite3_column_int( result_stmt, 0 ); + EXPECT_EQ( prival, 0 ); + + version = sqlite3_column_int( result_stmt, 1 ); + EXPECT_EQ( version, 1 ); + + sqlite3_finalize( result_stmt ); + } + + TEST_F( Sqlite3TargetTest, CustomPrepare ) { + struct test_prepare_data data; + const struct stumpless_target *target_result; + int add_result; + int sql_result; + const char *result_query = "SELECT prival, version, message FROM logs " + "WHERE message = ?"; + sqlite3_stmt *result_stmt; + int prival; + int version; + const unsigned char *message; + + data.target = target; + data.insert_stmts[0] = NULL; + data.insert_stmts[1] = NULL; + target_result = stumpless_set_sqlite3_prepare( target, + test_prepare, + &data ); + ASSERT_EQ( target_result, target ); + EXPECT_NO_ERROR; + + add_result = stumpless_add_entry( target, basic_entry ); + EXPECT_GE( add_result, 0 ); + EXPECT_NO_ERROR; + + sql_result = sqlite3_prepare_v2( db, result_query, -1, &result_stmt, NULL ); + EXPECT_EQ( sql_result, SQLITE_OK ); + + sql_result = sqlite3_bind_text( result_stmt, + 1, + "test-prepare-1", + -1, + SQLITE_STATIC ); + EXPECT_EQ( sql_result, SQLITE_OK ); + + sql_result = sqlite3_step( result_stmt ); + EXPECT_EQ( sql_result, SQLITE_ROW ); + + prival = sqlite3_column_int( result_stmt, 0 ); + EXPECT_EQ( prival, 0 ); + + version = sqlite3_column_int( result_stmt, 1 ); + EXPECT_EQ( version, 1 ); + + message = sqlite3_column_text( result_stmt, 2 ); + EXPECT_NOT_NULL( message ); + EXPECT_STREQ( ( const char * ) message, "test-prepare-1" ); + + sqlite3_reset( result_stmt ); + + sql_result = sqlite3_bind_text( result_stmt, + 1, + "test-prepare-2", + -1, + SQLITE_STATIC ); + EXPECT_EQ( sql_result, SQLITE_OK ); + + sql_result = sqlite3_step( result_stmt ); + EXPECT_EQ( sql_result, SQLITE_ROW ); + + prival = sqlite3_column_int( result_stmt, 0 ); + EXPECT_EQ( prival, 0 ); + + version = sqlite3_column_int( result_stmt, 1 ); + EXPECT_EQ( version, 1 ); + + message = sqlite3_column_text( result_stmt, 2 ); + EXPECT_NOT_NULL( message ); + EXPECT_STREQ( ( const char * ) message, "test-prepare-2" ); + + sqlite3_finalize( data.insert_stmts[0] ); + sqlite3_finalize( data.insert_stmts[1] ); + sqlite3_finalize( result_stmt ); + } + + TEST_F( Sqlite3TargetTest, CustomTableInsert ) { + const char *create_sql = "CREATE TABLE l (log_id INTEGER PRIMARY KEY, " + "prival INTEGER NOT NULL, " + "version INTEGER NOT NULL, " + "timestamp TEXT, " + "hostname TEXT, " + "app_name TEXT, " + "procid TEXT, " + "msgid TEXT, " + "structured_data TEXT, " + "message TEXT)"; + sqlite3_stmt *create_stmt = NULL; + int sql_result; + const char *insert_sql = "INSERT INTO l ( prival, version, timestamp," + " hostname, app_name, procid," + " msgid, structured_data," + " message ) " + "VALUES ( $prival, 1, $timestamp, $hostname," + " $app_name, $procid, $msgid, '-'," + " $message )"; + const struct stumpless_target *result; + const char *current_sql; + int add_result; + + sql_result = sqlite3_prepare_v2( db, create_sql, -1, &create_stmt, NULL ); + EXPECT_EQ( sql_result, SQLITE_OK ); + + sql_result = sqlite3_step( create_stmt ); + EXPECT_EQ( sql_result, SQLITE_DONE ); + + result = stumpless_set_sqlite3_insert_sql( target, insert_sql ); + ASSERT_EQ( result, target ); + + current_sql = stumpless_get_sqlite3_insert_sql( target ); + ASSERT_EQ( current_sql, insert_sql ); + + add_result = stumpless_add_entry( target, basic_entry ); + EXPECT_GE( add_result, 0 ); + EXPECT_NO_ERROR; + + TestEntryInDatabase( std::string( db_filename ), "l", basic_entry ); + + sqlite3_finalize( create_stmt ); + } + + TEST_F( Sqlite3TargetTest, DefaultInsertSql ) { + const char *insert_sql; + + insert_sql = stumpless_get_sqlite3_insert_sql( target ); + EXPECT_NO_ERROR; + ASSERT_STREQ( insert_sql, STUMPLESS_DEFAULT_SQLITE3_INSERT_SQL ); + } + + TEST_F( Sqlite3TargetTest, DefaultTableAlreadyExists ) { + const struct stumpless_target *result; + const struct stumpless_error *error; + + result = stumpless_create_default_sqlite3_table( target ); + EXPECT_NULL( result ); + EXPECT_ERROR_ID_EQ( STUMPLESS_SQLITE3_FAILURE ); + } + + TEST_F( Sqlite3TargetTest, FacilityAndSeverityParameters ) { + const char *create_sql = "CREATE TABLE priorities" + "( log_id INTEGER PRIMARY KEY," + " prival INTEGER NOT NULL," + " facility INTEGER NOT NULL," + " severity INTEGER NOT NULL," + " message TEXT )"; + sqlite3_stmt *create_stmt = NULL; + int sql_result; + const char *insert_sql = "INSERT INTO priorities (prival, facility," + " severity, message) " + "VALUES ($prival, $facility, $severity," + " 'hardcoded')"; + const struct stumpless_target *result; + const char *current_sql; + int add_result; + const char *result_query = "SELECT prival, facility, severity " + "FROM priorities WHERE message = 'hardcoded'"; + sqlite3_stmt *result_stmt = NULL; + int prival; + int facility; + int severity; + + sql_result = sqlite3_prepare_v2( db, create_sql, -1, &create_stmt, NULL ); + EXPECT_EQ( sql_result, SQLITE_OK ); + + sql_result = sqlite3_step( create_stmt ); + EXPECT_EQ( sql_result, SQLITE_DONE ); + + result = stumpless_set_sqlite3_insert_sql( target, insert_sql ); + ASSERT_EQ( result, target ); + + current_sql = stumpless_get_sqlite3_insert_sql( target ); + ASSERT_EQ( current_sql, insert_sql ); + + add_result = stumpless_add_entry( target, basic_entry ); + EXPECT_GE( add_result, 0 ); + EXPECT_NO_ERROR; + + sql_result = sqlite3_prepare_v2( db, result_query, -1, &result_stmt, NULL ); + EXPECT_EQ( sql_result, SQLITE_OK ); + + sql_result = sqlite3_step( result_stmt ); + EXPECT_EQ( sql_result, SQLITE_ROW ); + + prival = sqlite3_column_int( result_stmt, 0 ); + EXPECT_EQ( prival, stumpless_get_entry_prival( basic_entry ) ); + + facility = sqlite3_column_int( result_stmt, 1 ); + EXPECT_EQ( facility, stumpless_get_entry_facility( basic_entry ) ); + + severity = sqlite3_column_int( result_stmt, 2 ); + EXPECT_EQ( severity, stumpless_get_entry_severity( basic_entry ) ); + + sqlite3_finalize( create_stmt ); + sqlite3_finalize( result_stmt ); + } + + TEST_F( Sqlite3TargetTest, GetPrepareWithAndWithoutNull ) { + stumpless_sqlite3_prepare_func_t non_null_result; + stumpless_sqlite3_prepare_func_t null_result; + void *data; + + non_null_result = stumpless_get_sqlite3_prepare( target , &data ); + EXPECT_EQ( data, target->id ); + EXPECT_NO_ERROR; + + null_result = stumpless_get_sqlite3_prepare( target , NULL ); + EXPECT_EQ( null_result, non_null_result ); + } + + TEST_F( Sqlite3TargetTest, InvalidInsertSql ) { + const struct stumpless_target *set_result; + const char *bad_sql = "this isn't valid sql"; + const char *current_sql; + int add_result; + const struct stumpless_error *error; + + set_result = stumpless_set_sqlite3_insert_sql( target, bad_sql ); + ASSERT_EQ( set_result, target ); + + current_sql = stumpless_get_sqlite3_insert_sql( target ); + ASSERT_EQ( current_sql, bad_sql ); + + add_result = stumpless_add_entry( target, basic_entry ); + EXPECT_LT( add_result, 0 ); + EXPECT_ERROR_ID_EQ( STUMPLESS_SQLITE3_FAILURE ); + } + + TEST_F( Sqlite3TargetTest, NullPreparer ) { + const struct stumpless_target *result; + const struct stumpless_error *error; + + result = stumpless_set_sqlite3_prepare( target, NULL, NULL ); + EXPECT_NULL( result ); + EXPECT_ERROR_ID_EQ( STUMPLESS_ARGUMENT_EMPTY ); + } + + /* non-fixture tests */ + + TEST( Sqlite3TargetCloseTest, Generic ) { + const char *db_filename = "test_function_close.sqlite3"; + struct stumpless_target *target; + + remove( db_filename ); + target = stumpless_open_sqlite3_target( db_filename ); + EXPECT_NO_ERROR; + EXPECT_NOT_NULL( target ); + EXPECT_EQ( stumpless_get_current_target( ), target ); + + stumpless_close_target( target ); + EXPECT_NO_ERROR; + + EXPECT_EQ( stumpless_get_current_target( ), + stumpless_get_default_target( ) ); + EXPECT_STRNE( stumpless_get_current_target( )->name, + db_filename ); + + stumpless_free_all( ); + remove( db_filename ); + } + + TEST( Sqlite3TargetCloseDbTest, NullTarget ) { + const struct stumpless_error *error; + + stumpless_close_sqlite3_target_and_db( NULL ); + EXPECT_ERROR_ID_EQ( STUMPLESS_ARGUMENT_EMPTY ); + stumpless_free_all(); + } + + TEST( Sqlite3TargetCloseDbTest, WrongTargetType ) { + struct stumpless_target *target; + const struct stumpless_error *error; + + target = stumpless_open_stdout_target( "not-a-file-target" ); + + stumpless_close_sqlite3_target_and_db( target ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_INCOMPATIBLE ); + + stumpless_close_stream_target( target ); + stumpless_free_all(); + } + + TEST( Sqlite3TargetCloseTargetOnlyTest, General ) { + sqlite3 *db; + int sql_result; + struct stumpless_target *target; + + sql_result = sqlite3_open_v2( "close_target_only_test.sqlite3", + &db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_MEMORY, + NULL ); + ASSERT_EQ( sql_result, SQLITE_OK ); + + target = stumpless_open_sqlite3_target_from_db( db ); + EXPECT_NOT_NULL( target ); + EXPECT_NO_ERROR; + + stumpless_close_sqlite3_target_only( target ); + EXPECT_NO_ERROR; + + sqlite3_close( db ); + stumpless_free_all(); + } + + TEST( Sqlite3TargetCloseTargetOnlyTest, NullTarget ) { + const struct stumpless_error *error; + + stumpless_close_sqlite3_target_only( NULL ); + EXPECT_ERROR_ID_EQ( STUMPLESS_ARGUMENT_EMPTY ); + stumpless_free_all(); + } + + TEST( Sqlite3TargetCloseTargetOnlyTest, WrongTargetType ) { + struct stumpless_target *target; + const struct stumpless_error *error; + + target = stumpless_open_stdout_target( "not-a-file-target" ); + + stumpless_close_sqlite3_target_only( target ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_INCOMPATIBLE ); + + stumpless_close_stream_target( target ); + stumpless_free_all(); + } + + TEST( Sqlite3TargetCreateDefaultTableTest, Normal ) { + const char *db_filename = "test_function_default_table.sqlite3"; + struct stumpless_target *target; + struct stumpless_target *result; + + remove( db_filename ); + target = stumpless_open_sqlite3_target( db_filename ); + ASSERT_NOT_NULL( target ); + EXPECT_NO_ERROR; + + result = stumpless_create_default_sqlite3_table( target ); + EXPECT_EQ( result, target ); + EXPECT_NO_ERROR; + + stumpless_close_sqlite3_target_and_db( target ); + stumpless_free_all(); + remove( db_filename ); + } + + TEST( Sqlite3TargetGetDbTest, WrongTargetType ) { + void *result; + struct stumpless_target *target; + const struct stumpless_error *error; + + target = stumpless_open_stdout_target( "not-a-file-target" ); + + result = stumpless_get_sqlite3_db( target ); + EXPECT_NULL( result ); + EXPECT_ERROR_ID_EQ( STUMPLESS_TARGET_INCOMPATIBLE ); + + stumpless_close_stream_target( target ); + stumpless_free_all(); + } + + TEST( Sqlite3TargetOpenTest, Directory ) { + struct stumpless_target *target; + const struct stumpless_error *error; + + target = stumpless_open_sqlite3_target( "./" ); + EXPECT_NULL( target ); + EXPECT_ERROR_ID_EQ( STUMPLESS_SQLITE3_FAILURE ); + + stumpless_free_all(); + } + + TEST( Sqlite3TargetOpenTest, MallocFailure ) { + const char *db_filename = "malloc_fail_on_open.sqlite3"; + struct stumpless_target *target; + const struct stumpless_error *error; + void *(*set_malloc_result)(size_t); + + set_malloc_result = stumpless_set_malloc( MALLOC_FAIL ); + ASSERT_NOT_NULL( set_malloc_result ); + + target = stumpless_open_sqlite3_target( db_filename ); + EXPECT_NULL( target ); + EXPECT_ERROR_ID_EQ( STUMPLESS_MEMORY_ALLOCATION_FAILURE ); + + set_malloc_result = stumpless_set_malloc( malloc ); + ASSERT_TRUE( set_malloc_result == malloc ); + stumpless_free_all(); + remove( db_filename ); + } + + TEST( Sqlite3TargetOpenTest, NullName ) { + struct stumpless_target *target; + const struct stumpless_error *error; + + target = stumpless_open_sqlite3_target( NULL ); + EXPECT_NULL( target ); + EXPECT_ERROR_ID_EQ( STUMPLESS_ARGUMENT_EMPTY ); + stumpless_free_all(); + } + + TEST( Sqlite3TargetOpenDbTest, MallocFailure ) { + const char *db_filename = "malloc_fail_on_open.sqlite3"; + sqlite3 *db = NULL; + int sql_result; + struct stumpless_target *target; + const struct stumpless_error *error; + void *(*set_malloc_result)(size_t); + + sql_result = sqlite3_open_v2( db_filename, + &db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_MEMORY, + NULL ); + ASSERT_EQ( sql_result, SQLITE_OK ); + + set_malloc_result = stumpless_set_malloc( MALLOC_FAIL ); + ASSERT_NOT_NULL( set_malloc_result ); + + target = stumpless_open_sqlite3_target_from_db( db ); + EXPECT_NULL( target ); + EXPECT_ERROR_ID_EQ( STUMPLESS_MEMORY_ALLOCATION_FAILURE ); + + set_malloc_result = stumpless_set_malloc( malloc ); + ASSERT_TRUE( set_malloc_result == malloc ); + stumpless_free_all(); + sqlite3_close( db ); + } + + TEST( Sqlite3TargetOpenDbTest, NullName ) { + struct stumpless_target *target; + const struct stumpless_error *error; + + target = stumpless_open_sqlite3_target_from_db( NULL ); + EXPECT_NULL( target ); + EXPECT_ERROR_ID_EQ( STUMPLESS_ARGUMENT_EMPTY ); + stumpless_free_all(); + } +} diff --git a/test/function/target/stream.cpp b/test/function/target/stream.cpp index a540f0770..360f3c72c 100644 --- a/test/function/target/stream.cpp +++ b/test/function/target/stream.cpp @@ -111,6 +111,7 @@ namespace { filename ); stumpless_free_all( ); + remove( filename ); } TEST( StreamTargetCloseTest, NullTarget ) { diff --git a/test/helper/fixture.cpp b/test/helper/fixture.cpp index 004d0a57d..03d6260d6 100644 --- a/test/helper/fixture.cpp +++ b/test/helper/fixture.cpp @@ -55,6 +55,15 @@ create_entry( void ) { return entry; } +struct stumpless_entry * +create_nil_entry( void ) { + return stumpless_new_entry_str( STUMPLESS_FACILITY_USER, + STUMPLESS_SEVERITY_INFO, + NULL, + NULL, + NULL ); +} + const char * load_corpus( const string& name ) { int file_length; diff --git a/test/helper/rfc5424.cpp b/test/helper/rfc5424.cpp index 6b39dc9ad..99f572a09 100644 --- a/test/helper/rfc5424.cpp +++ b/test/helper/rfc5424.cpp @@ -17,71 +17,34 @@ */ #include -#include #include #include #include "test/helper/rfc5424.hpp" #include "test/helper/utf8.hpp" -void TestRFC5424Compliance(const char *syslog_msg){ - std::cmatch matches; - std::regex rfcRegex(RFC_5424_REGEX_STRING); - if( !std::regex_match(syslog_msg, matches, rfcRegex) ) { +void TestRFC5424Compliance( const std::string &syslog_msg ) { + std::smatch matches; + std::regex rfcRegex( RFC_5424_REGEX_STRING ); + if( !std::regex_match( syslog_msg, matches, rfcRegex ) ) { FAIL( ) << "message does not match RFC 5424 regex: " << syslog_msg; } - int prival = std::stoi(matches[RFC_5424_PRIVAL_MATCH_INDEX]); - EXPECT_GE(prival, RFC_5424_PRIVAL_MIN); - EXPECT_LE(prival, RFC_5424_PRIVAL_MAX); + int prival = std::stoi( matches[RFC_5424_PRIVAL_MATCH_INDEX] ); + EXPECT_GE( prival, RFC_5424_PRIVAL_MIN ); + EXPECT_LE( prival, RFC_5424_PRIVAL_MAX ); - EXPECT_EQ(matches[2], "1"); + EXPECT_EQ( matches[RFC_5424_VERSION_MATCH_INDEX], "1" ); - int year = std::stoi(matches[RFC_5424_DATE_FULLYEAR_MATCH_INDEX]); - EXPECT_GE(year, 0); - - int month = std::stoi(matches[RFC_5424_DATE_MONTH_MATCH_INDEX]); - int day = std::stoi(matches[RFC_5424_DATE_MDAY_MATCH_INDEX]); - EXPECT_GE(day, 1); - switch(month){ - case 1: - case 3: - case 5: - case 7: - case 8: - case 10: - case 12: - EXPECT_LE(day, 31); - break; - case 4: - case 6: - case 9: - case 11: - EXPECT_LE(day, 30); - break; - case 2: - if((year % 4 == 0 && year % 100 != 0) || year % 400 == 0){ - EXPECT_LE(day, 29); - } else { - EXPECT_LE(day, 28); - } - break; - default: - ADD_FAILURE() << "DATE-MONTH was not a value between 1 and 12"; - } - - TestRFC5424StructuredData(matches.str(RFC_5424_STRUCTURED_DATA_MATCH_INDEX).c_str()); + TestRFC5424Timestamp( matches.str( RFC_5424_TIMESTAMP_MATCH_INDEX ) ); + TestRFC5424StructuredData( matches.str( RFC_5424_STRUCTURED_DATA_MATCH_INDEX ) ); - std::string msg_string = matches.str(RFC_5424_MSG_MATCH_INDEX); - char *msg = (char *)malloc(msg_string.length() + 1); - msg_string.copy( msg, msg_string.length() ); - msg[msg_string.length()] = '\0'; - if(msg[0] == '\xef' && msg[1] == '\xbb' && msg[2] == '\xbf'){ - TestUTF8Compliance(msg); + std::string msg = matches.str( RFC_5424_MSG_MATCH_INDEX ); + if( msg[0] == '\xef' && msg[1] == '\xbb' && msg[2] == '\xbf' ) { + TestUTF8Compliance( msg ); } - free(msg); } -void TestRFC5424StructuredData(const char *structured_data){ +void TestRFC5424StructuredData( const std::string &structured_data ) { enum sd_state { INIT_STATE, SD_ELEMENT_EMPTY, @@ -97,12 +60,12 @@ void TestRFC5424StructuredData(const char *structured_data){ bool backslash_preceded = false; std::string paramValue; - for(const char *c=structured_data; *c != '\0'; c++){ - switch(current_state){ + for( const char &c : structured_data ) { + switch( current_state ) { case INIT_STATE: - if(*c == '-'){ + if( c == '-' ) { current_state = SD_ELEMENT_EMPTY; - } else if( *c == '['){ + } else if( c == '[' ) { current_state = SD_ID_NAME; } break; @@ -112,88 +75,88 @@ void TestRFC5424StructuredData(const char *structured_data){ break; case SD_ELEMENT_BEGIN: - ASSERT_EQ(*c, '['); + ASSERT_EQ( c, '[' ); current_state = SD_ID_NAME; break; // todo validate with RFC case SD_ID_NAME: - if(*c == '@'){ + if( c == '@' ) { current_state = SD_ID_ENTERPRISE_NUMBER; break; } - if(*c == ']'){ + if( c == ']' ) { current_state = SD_ELEMENT_BEGIN; break; } - if(*c == ' '){ + if( c == ' ' ) { current_state = SD_PARAM_NAME; break; } - ASSERT_GT(*c, 32); - ASSERT_LT(*c, 127); - ASSERT_NE(*c, '='); - ASSERT_NE(*c, '"'); + ASSERT_GT( c, 32 ); + ASSERT_LT( c, 127 ); + ASSERT_NE( c, '=' ); + ASSERT_NE( c, '"' ); break; // todo validate with registry case SD_ID_ENTERPRISE_NUMBER: - if(*c == ']'){ + if( c == ']' ) { current_state = SD_ELEMENT_BEGIN; break; } - if(*c == ' '){ + if( c == ' ' ) { current_state = SD_PARAM_NAME; break; } - ASSERT_GE(*c, '0'); - ASSERT_LE(*c, '9'); + ASSERT_GE( c, '0' ); + ASSERT_LE( c, '9' ); break; case SD_PARAM_NAME: - if(*c == '='){ + if( c == '=' ) { current_state = SD_PARAM_VALUE_BEGIN; break; } - ASSERT_GT(*c, 32); - ASSERT_LT(*c, 127); - ASSERT_NE(*c, ' '); - ASSERT_NE(*c, ']'); - ASSERT_NE(*c, '"'); + ASSERT_GT( c, 32 ); + ASSERT_LT( c, 127 ); + ASSERT_NE( c, ' ' ); + ASSERT_NE( c, ']' ); + ASSERT_NE( c, '"' ); break; case SD_PARAM_VALUE_BEGIN: - ASSERT_EQ(*c, '"'); + ASSERT_EQ( c, '"' ); current_state = SD_PARAM_VALUE; paramValue = std::string(); break; case SD_PARAM_VALUE: - paramValue.push_back(*c); - if(backslash_preceded){ + paramValue.push_back( c ); + if( backslash_preceded ) { backslash_preceded = false; break; } else { - if(*c == '"'){ + if( c == '"' ) { current_state = SD_PARAM_VALUE_END; break; } - ASSERT_NE(*c, '='); - ASSERT_NE(*c, ']'); + ASSERT_NE( c, '=' ); + ASSERT_NE( c, ']' ); } - if(*c == '\\'){ + if( c == '\\' ) { backslash_preceded = true; } break; case SD_PARAM_VALUE_END: - TestUTF8Compliance(paramValue.c_str()); - if(*c == ' '){ + TestUTF8Compliance( paramValue ); + if( c == ' ' ) { current_state = SD_PARAM_NAME; break; } - if(*c == ']'){ + if( c == ']' ) { current_state = SD_ELEMENT_BEGIN; break; } @@ -205,3 +168,44 @@ void TestRFC5424StructuredData(const char *structured_data){ } } } + +void TestRFC5424Timestamp( const std::string ×tamp ) { + std::smatch matches; + std::regex timestampRegex( RFC_5424_TIMESTAMP_REGEX_STRING ); + if( !std::regex_match( timestamp, matches, timestampRegex ) ) { + FAIL( ) << timestamp << " does not match RFC 5424 timestamp regex: "; + } + + int year = std::stoi( matches[RFC_5424_TIMESTAMP_DATE_FULLYEAR_MATCH_INDEX] ); + EXPECT_GE( year, 0 ); + + int month = std::stoi( matches[RFC_5424_TIMESTAMP_DATE_MONTH_MATCH_INDEX] ); + int day = std::stoi( matches[RFC_5424_TIMESTAMP_DATE_MDAY_MATCH_INDEX] ); + EXPECT_GE( day, 1 ); + switch( month ) { + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + EXPECT_LE( day, 31 ); + break; + case 4: + case 6: + case 9: + case 11: + EXPECT_LE( day, 30 ); + break; + case 2: + if( ( year % 4 == 0 && year % 100 != 0 ) || year % 400 == 0 ) { + EXPECT_LE( day, 29 ); + } else { + EXPECT_LE( day, 28 ); + } + break; + default: + ADD_FAILURE() << "DATE-MONTH was not a value between 1 and 12"; + } +} \ No newline at end of file diff --git a/test/helper/utf8.cpp b/test/helper/utf8.cpp index 0a1f63da2..b13d28eec 100644 --- a/test/helper/utf8.cpp +++ b/test/helper/utf8.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 /* - * Copyright 2018-2021 Joel E. Anderson + * Copyright 2018-2023 Joel E. Anderson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,12 @@ * limitations under the License. */ -#include +#include +#include #include #include "test/helper/utf8.hpp" -void TestUTF8Compliance(const char *str){ - // strip off the BOM if it exists - if(*str == '\xef' && *(str+1) == '\xbb' && *(str+2) == '\xbf'){ - str += 3; - } - +void TestUTF8Compliance( const std::string &str ){ enum utf8_state { LEAD_CHAR, TWO_CHAR, @@ -38,85 +34,92 @@ void TestUTF8Compliance(const char *str){ size_t char_count; char bytes[6]; - for(const char *c=str; *c != '\0'; c++){ + auto it = str.cbegin(); + + // strip off the BOM if it exists + if( str[0] == '\xef' && str[1] == '\xbb' && str[2] == '\xbf' ){ + std::advance( it, 3 ); + } + + for( const char &c = *it; it != str.cend(); it++ ){ switch(current_state){ case LEAD_CHAR: - if((*c & '\xe0') == '\xc0'){ + if( (c & '\xe0') == '\xc0' ) { current_state = TWO_CHAR; - bytes[0] = *c & '\x1f'; + bytes[0] = c & '\x1f'; break; } - if((*c & '\xf0') == '\xe0'){ + if( ( c & '\xf0' ) == '\xe0' ) { current_state = THREE_CHAR; - bytes[0] = *c & '\x0f'; + bytes[0] = c & '\x0f'; char_count = 1; break; } - if((*c & '\xf8') == '\xf0'){ + if( ( c & '\xf8' ) == '\xf0' ) { current_state = FOUR_CHAR; - bytes[0] = *c & '\x07'; + bytes[0] = c & '\x07'; char_count = 1; break; } - if((*c & '\xfc') == '\xf8'){ + if( ( c & '\xfc' ) == '\xf8' ) { current_state = FIVE_CHAR; - bytes[0] = *c & '\x03'; + bytes[0] = c & '\x03'; char_count = 1; break; } - if((*c & '\xfe') == '\xfc'){ + if( ( c & '\xfe' ) == '\xfc' ) { current_state = SIX_CHAR; - bytes[0] = *c & '\x01'; + bytes[0] = c & '\x01'; char_count = 1; break; } - ASSERT_EQ(*c & '\x80', 0) << "invalid lead byte"; + ASSERT_EQ( c & '\x80', 0 ) << "invalid lead byte"; break; case TWO_CHAR: - ASSERT_EQ(*c & '\xc0', '\x80') << "invalid continuation byte"; - ASSERT_NE(bytes[0] & '\x1e', 0) << "non-shortest form not allowed"; + ASSERT_EQ( c & '\xc0', '\x80' ) << "invalid continuation byte"; + ASSERT_NE( bytes[0] & '\x1e', 0 ) << "non-shortest form not allowed"; current_state = LEAD_CHAR; break; case THREE_CHAR: - ASSERT_EQ(*c & '\xc0', '\x80') << "invalid continuation byte"; - bytes[char_count] = *c & '\x3f'; + ASSERT_EQ( c & '\xc0', '\x80' ) << "invalid continuation byte"; + bytes[char_count] = c & '\x3f'; char_count++; - if(char_count == 3){ - ASSERT_NE(bytes[0] | (bytes[1] & '\x20'), 0) << "non-shortest form not allowed"; + if( char_count == 3 ) { + ASSERT_NE( bytes[0] | ( bytes[1] & '\x20' ), 0 ) << "non-shortest form not allowed"; current_state = LEAD_CHAR; } break; case FOUR_CHAR: - ASSERT_EQ(*c & '\xc0', '\x80') << "invalid continuation byte"; - bytes[char_count] = *c & '\x3f'; + ASSERT_EQ( c & '\xc0', '\x80' ) << "invalid continuation byte"; + bytes[char_count] = c & '\x3f'; char_count++; - if(char_count == 4){ - ASSERT_NE(bytes[0] | (bytes[1] & '\x30'), 0) << "non-shortest form not allowed"; + if( char_count == 4 ) { + ASSERT_NE( bytes[0] | ( bytes[1] & '\x30' ), 0 ) << "non-shortest form not allowed"; current_state = LEAD_CHAR; } break; case FIVE_CHAR: - ASSERT_EQ(*c & '\xc0', '\x80') << "invalid continuation byte"; - bytes[char_count] = *c & '\x3f'; + ASSERT_EQ( c & '\xc0', '\x80' ) << "invalid continuation byte"; + bytes[char_count] = c & '\x3f'; char_count++; if(char_count == 5){ - ASSERT_NE(bytes[0] | (bytes[1] & '\x38'), 0) << "non-shortest form not allowed"; + ASSERT_NE( bytes[0] | ( bytes[1] & '\x38' ), 0 ) << "non-shortest form not allowed"; current_state = LEAD_CHAR; } break; case SIX_CHAR: - ASSERT_EQ(*c & '\xc0', '\x80') << "invalid continuation byte"; - bytes[char_count] = *c & '\x3f'; + ASSERT_EQ( c & '\xc0', '\x80' ) << "invalid continuation byte"; + bytes[char_count] = c & '\x3f'; char_count++; if(char_count == 6){ - ASSERT_NE(bytes[0] | (bytes[1] & '\x3c'), 0) << "non-shortest form not allowed"; + ASSERT_NE( bytes[0] | ( bytes[1] & '\x3c' ), 0 ) << "non-shortest form not allowed"; current_state = LEAD_CHAR; } break; diff --git a/test/performance/target/function.cpp b/test/performance/target/function.cpp index 4e5ec9c7c..c54fb4bd5 100644 --- a/test/performance/target/function.cpp +++ b/test/performance/target/function.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 /* - * Copyright 2021 Joel E. Anderson + * Copyright 2021-2023 Joel E. Anderson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ */ #include -#include #include #include "test/helper/fixture.hpp" #include "test/helper/memory_counter.hpp" @@ -46,9 +45,7 @@ class FunctionFixture : public::benchmark::Fixture { } void TearDown( const ::benchmark::State &state ) { - stumpless_set_malloc( malloc ); - stumpless_set_realloc( realloc ); - stumpless_set_free( free ); + FINALIZE_MEMORY_COUNTER( function ); stumpless_destroy_entry_and_contents( entry ); stumpless_close_function_target( target ); stumpless_free_all( ); diff --git a/test/performance/target/sqlite3.cpp b/test/performance/target/sqlite3.cpp new file mode 100644 index 000000000..0194a359a --- /dev/null +++ b/test/performance/target/sqlite3.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2023 Joel E. Anderson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "test/helper/fixture.hpp" +#include "test/helper/memory_counter.hpp" + +NEW_MEMORY_COUNTER( sqlite3_add ); + +class Sqlite3Fixture : public::benchmark::Fixture { +protected: + struct stumpless_target *target; + struct stumpless_entry *entry; + +public: + void SetUp( const ::benchmark::State &state ) { + target = stumpless_open_sqlite3_target( ":memory:" ); + stumpless_create_default_sqlite3_table( target ); + entry = create_entry(); + INIT_MEMORY_COUNTER( sqlite3_add ); + } + + void TearDown( const ::benchmark::State &state ) { + FINALIZE_MEMORY_COUNTER( sqlite3_add ); + stumpless_destroy_entry_and_contents( entry ); + stumpless_close_sqlite3_target_and_db( target ); + stumpless_free_all(); + } +}; + +BENCHMARK_F( Sqlite3Fixture, AddEntry )( benchmark::State &state ) { + for( auto _ : state ) { + if( stumpless_add_entry( target, entry ) < 0 ) { + state.SkipWithError( "could not send an entry" ); + } + } + + SET_STATE_COUNTERS( state, sqlite3_add ); +} diff --git a/test/thread_safety/target/sqlite3.cpp b/test/thread_safety/target/sqlite3.cpp new file mode 100644 index 000000000..a002abcf4 --- /dev/null +++ b/test/thread_safety/target/sqlite3.cpp @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2023 Joel E. Anderson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include "test/helper/assert.hpp" +#include "test/helper/usage.hpp" + +namespace { + const int THREAD_COUNT = 16; + const int MESSAGE_COUNT = 100; + + TEST( Sqlite3WriteConsistency, SimultaneousWrites ) { + const char *filename = "test_thread_safety.sqlite3"; + struct stumpless_target *target; + size_t i; + std::thread *threads[THREAD_COUNT]; + sqlite3 *db; + int sql_result; + sqlite3_stmt *stmt; + const char *result_query = "SELECT * FROM logs"; + + target = stumpless_open_sqlite3_target( filename ); + EXPECT_NO_ERROR; + ASSERT_NOT_NULL( target ); + + stumpless_create_default_sqlite3_table( target ); + EXPECT_NO_ERROR; + + for( i = 0; i < THREAD_COUNT; i++ ) { + threads[i] = new std::thread( add_messages, target, MESSAGE_COUNT ); + } + + for( i = 0; i < THREAD_COUNT; i++ ) { + threads[i]->join(); + delete threads[i]; + } + + // cleanup after the test + stumpless_close_sqlite3_target_and_db( target ); + EXPECT_NO_ERROR; + + stumpless_free_all( ); + + // check for consistency in the database + sql_result = sqlite3_open( filename, &db ); + ASSERT_EQ( sql_result, SQLITE_OK ); + + sql_result = sqlite3_prepare_v2( db, result_query, -1, &stmt, NULL ); + ASSERT_EQ( sql_result, SQLITE_OK ); + + for( i = 0; i < THREAD_COUNT * MESSAGE_COUNT; i++ ) { + sql_result = sqlite3_step( stmt ); + EXPECT_EQ( sql_result, SQLITE_ROW ); + } + + sql_result = sqlite3_step( stmt ); + EXPECT_EQ( sql_result, SQLITE_DONE ); + + sql_result = sqlite3_close_v2( db ); + EXPECT_EQ( sql_result, SQLITE_OK ); + + remove( filename ); + } +} diff --git a/tools/check_headers/check_headers.rb b/tools/check_headers/check_headers.rb index 0fef6fba9..9d39f6ed1 100755 --- a/tools/check_headers/check_headers.rb +++ b/tools/check_headers/check_headers.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -# Copyright 2019 Joel E. Anderson +# Copyright 2019-2023 Joel E. Anderson # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -52,7 +52,7 @@ def load_manifest(filename, term_hash) common_known_terms = {} %w[benchmark.yml standard_library.yml gtest.yml stumpless.yml - stumpless_private.yml stumplesscpp.yml].each do |filename| + stumpless_private.yml stumplesscpp.yml sqlite.yml].each do |filename| load_manifest(filename, common_known_terms) end diff --git a/tools/check_headers/sqlite.yml b/tools/check_headers/sqlite.yml new file mode 100644 index 000000000..ce4260f66 --- /dev/null +++ b/tools/check_headers/sqlite.yml @@ -0,0 +1,15 @@ +"SQLITE_BUSY": "sqlite3.h" +"SQLITE_DONE": "sqlite3.h" +"SQLITE_OK": "sqlite3.h" +"sqlite3": "sqlite3.h" +"sqlite3_bind_int": "sqlite3.h" +"sqlite3_bind_parameter_index": "sqlite3.h" +"sqlite3_bind_text": "sqlite3.h" +"sqlite3_close_v2": "sqlite3.h" +"sqlite3_column_int": "sqlite3.h" +"sqlite3_column_text": "sqlite3.h" +"sqlite3_finalize": "sqlite3.h" +"sqlite3_open": "sqlite3.h" +"sqlite3_reset": "sqlite3.h" +"sqlite3_step": "sqlite3.h" +"sqlite3_stmt": "sqlite3.h" diff --git a/tools/check_headers/stumpless.yml b/tools/check_headers/stumpless.yml index 1c8931830..06f8a818c 100644 --- a/tools/check_headers/stumpless.yml +++ b/tools/check_headers/stumpless.yml @@ -24,6 +24,7 @@ "config_close_udp4_target": "private/config/network_support_wrapper.h" "config_getpagesize": "private/config/wrapper/getpagesize.h" "config_get_now": "private/config/wrapper/get_now.h" +"stumpless_get_sqlite3_db": "stumpless/target/sqlite3.h" "config_init_tcp4": "private/config/network_support_wrapper.h" "config_init_udp4": "private/config/network_support_wrapper.h" "config_network_free_all": "private/config/network_support_wrapper.h" @@ -133,13 +134,6 @@ "realloc_mem": "private/memory.h" "recv_from_handle": "test/helper/server.hpp" "resize_insertion_params": "private/config/wel_supported.h" -"send_entry_to_unsupported_target": "private/target.h" -"sendto_buffer_target": "private/target/buffer.h" -"sendto_file_target": "private/target/file.h" -"sendto_network_target": "private/target/network.h" -"sendto_socket_target": "private/target/socket.h" -"sendto_stream_target": "private/target/stream.h" -"sendto_unsupported_target": "private/target.h" "set_entry_wel_type": "private/config/wel_supported.h" "severity_is_invalid": "private/severity.h" "size_t_to_int": "private/inthelper.h" @@ -200,12 +194,17 @@ "stumpless_close_journald_target": "stumpless/target/journald.h" "stumpless_close_network_target": "stumpless/target/network.h" "stumpless_close_socket_target": "stumpless/target/socket.h" +"stumpless_close_sqlite3_target_and_db": "stumpless/target/sqlite3.h" +"stumpless_close_sqlite3_target_only": "stumpless/target/sqlite3.h" "stumpless_close_stream_target": "stumpless/target/stream.h" "stumpless_close_target": "stumpless/target.h" "stumpless_close_wel_target": "stumpless/target/wel.h" +"stumpless_create_default_sqlite3_table": "stumpless/target/sqlite3.h" "STUMPLESS_DEFAULT_FACILITY": "stumpless/config.h" "STUMPLESS_DEFAULT_FILE": "stumpless/target.h" "STUMPLESS_DEFAULT_SEVERITY": "stumpless/config.h" +"STUMPLESS_DEFAULT_SQLITE3_INSERT_SQL": "stumpless/target/sqlite3.h" +"STUMPLESS_DEFAULT_SQLITE3_TABLE_NAME_STRING": "stumpless/config.h" "STUMPLESS_DEFAULT_TARGET_NAME": "stumpless/target.h" "STUMPLESS_DEFAULT_TRANSPORT_PORT": "stumpless/target/network.h" "STUMPLESS_DEFAULT_UDP_MAX_MESSAGE_SIZE": "stumpless/target/network.h" @@ -320,6 +319,9 @@ "stumpless_open_network_target": "stumpless/target/network.h" "stumpless_open_remote_wel_target": "stumpless/target/wel.h" "stumpless_open_socket_target": "stumpless/target/socket.h" +"stumpless_open_sqlite3_target": "stumpless/target/sqlite3.h" +"stumpless_open_sqlite3_target_from_db": "stumpless/target/sqlite3.h" +"stumpless_open_sqlite3_target_with_options": "stumpless/target/sqlite3.h" "stumpless_open_stream_target": "stumpless/target/stream.h" "stumpless_open_target": "stumpless/target.h" "stumpless_open_tcp4_target": "stumpless/target/network.h" @@ -360,6 +362,8 @@ "stumpless_set_param_value": "stumpless/param.h" "stumpless_set_param_value_by_name": "stumpless/element.h" "stumpless_set_param_value_by_index": "stumpless/element.h" +"stumpless_set_sqlite3_insert_sql": "stumpless/target/sqlite3.h" +"stumpless_set_sqlite3_prepare": "stumpless/target/sqlite3.h" "stumpless_set_target_filter": "stumpless/target.h" "stumpless_set_target_mask": "stumpless/target.h" "stumpless_set_transport_port": "stumpless/target/network.h" @@ -384,6 +388,15 @@ "STUMPLESS_SOCKET_SEND_FAILURE": "stumpless/error.h" "STUMPLESS_SOCKET_TARGETS_SUPPORTED": "stumpless/config.h" "STUMPLESS_SOCKET_TARGET": "stumpless/target.h" +"STUMPLESS_SQLITE3_BUSY": "stumpless/error.h" +"STUMPLESS_SQLITE3_CALLBACK_FAILURE": "stumpless/error.h" +"STUMPLESS_SQLITE3_FAILURE": "stumpless/error.h" +"stumpless_sqlite3_prepare": "stumpless/target/sqlite3.h" +"stumpless_sqlite3_prepare_func_t": "stumpless/target/sqlite3.h" +"STUMPLESS_SQLITE3_RETRY_MAX": "stumpless/config.h" +"STUMPLESS_SQLITE3_TARGET": "stumpless/target.h" +"STUMPLESS_SQLITE3_TARGET_VALUE": "stumpless/target.h" +"STUMPLESS_SQLITE3_TARGETS_SUPPORTED": "stumpless/config.h" "STUMPLESS_STREAM_TARGET": "stumpless/target.h" "STUMPLESS_STREAM_WRITE_FAILURE": "stumpless/error.h" "STUMPLESS_SYSLOG_H_COMPATIBLE": "stumpless/config.h" diff --git a/tools/check_headers/stumpless_private.yml b/tools/check_headers/stumpless_private.yml index 7432c5e64..70e8391a1 100644 --- a/tools/check_headers/stumpless_private.yml +++ b/tools/check_headers/stumpless_private.yml @@ -11,6 +11,8 @@ "config_check_mutex_valid": "private/config/wrapper/thread_safety.h" "config_close_journald_target": "private/config/wrapper/journald.h" "config_close_socket_target": "private/config/wrapper/socket.h" +"config_close_sqlite3_target_and_db": "private/config/wrapper/sqlite3.h" +"config_close_sqlite3_target_only": "private/config/wrapper/sqlite3.h" "config_compare_exchange_bool": "private/config/wrapper/thread_safety.h" "config_compare_exchange_ptr": "private/config/wrapper/thread_safety.h" "config_copy_wstring_to_cstring": "private/config/wrapper/wstring.h" @@ -34,6 +36,7 @@ "config_read_flag": "private/config/wrapper/thread_safety.h" "config_read_ptr": "private/config/wrapper/thread_safety.h" "config_send_entry_to_journald_target": "private/config/wrapper/journald.h" +"config_send_entry_to_sqlite3_target": "private/config/wrapper/sqlite3.h" "config_sendto_socket_target": "private/config/wrapper/socket.h" "config_close_wel_target": "private/config/wrapper/wel.h" "config_copy_wel_data": "private/config/wrapper/wel.h" @@ -52,7 +55,10 @@ "copy_param_value_to_lpwstr": "private/config/wel_supported.h" "copy_wel_data": "private/config/wel_supported.h" "create_empty_entry": "test/helper/fixture.hpp" +"create_nil_entry": "test/helper/fixture.hpp" +"destroy_sqlite3_target": "private/target/sqlite3.h" "fallback_copy_wstring_to_cstring": "private/config/fallback.h" +"FINALIZE_MEMORY_COUNTER": "test/helper/memory_counter.hpp" "FOR_EACH_PARAM_WITH_NAME": "private/element.h" "FUZZ_CORPORA_DIR": "test/config.hpp" "GENERATE_STRING": "private/strhelper.h" @@ -98,6 +104,7 @@ "locked_get_param_by_index": "private/element.h" "locked_swap_wel_insertion_string": "private/config/wel_supported.h" "new_entry": "private/entry.h" +"new_sqlite3_target": "private/target/sqlite3.h" "no_abstract_socket_names_get_local_socket_name": "private/config/abstract_socket_names_unsupported.h" "no_thread_safety_compare_exchange_bool": "private/config/thread_safety_unsupported.h" "no_thread_safety_compare_exchange_ptr": "private/config/thread_safety_unsupported.h" @@ -117,6 +124,8 @@ "raise_journald_failure": "private/error.h" "raise_mb_conversion_failure": "private/error.h" "raise_resolve_hostname_failure": "private/error.h" +"raise_sqlite3_busy": "private/error.h" +"raise_sqlite3_failure": "private/error.h" "raise_wide_conversion_failure": "private/error.h" "raise_windows_failure": "private/error.h" "repeat_add_entry": "test/helper/usage.hpp" @@ -127,10 +136,19 @@ "RFC_5424_REGEX_STRING": "test/helper/rfc5424.hpp" "RFC_5424_TIME_SECFRAC_BUFFER_SIZE": "private/formatter.h" "RFC_5424_TIMESTAMP_BUFFER_SIZE": "private/formatter.h" +"RFC_5424_TIMESTAMP_REGEX_STRING": "test/helper/rfc5424.hpp" "RFC_5424_WHOLE_TIME_BUFFER_SIZE": "private/formatter.h" "send_entry_and_msg_to_unsupported_target": "private/target.h" "send_entry_to_function_target": "private/target/function.h" "send_entry_to_journald_target": "private/target/journald.h" +"send_entry_to_sqlite3_target": "private/target/sqlite3.h" +"send_entry_to_unsupported_target": "private/target.h" +"sendto_buffer_target": "private/target/buffer.h" +"sendto_file_target": "private/target/file.h" +"sendto_network_target": "private/target/network.h" +"sendto_socket_target": "private/target/socket.h" +"sendto_stream_target": "private/target/stream.h" +"sendto_unsupported_target": "private/target.h" "sendto_wel_target": "private/target/wel.h" "set_field_bases": "private/target/journald.h" "SET_STATE_COUNTERS": "test/helper/memory_counter.hpp" @@ -142,7 +160,9 @@ "stdatomic_write_flag": "private/config/have_stdatomic.h" "stdatomic_write_ptr": "private/config/have_stdatomic.h" "strbuilder_append_positive_int": "private/strbuilder.h" +"strbuilder_reset": "private/strbuilder.h" "struct function_target": "private/target/function.h" +"struct sqlite3_target": "private/target/sqlite3.h" "SUPPORT_ABSTRACT_SOCKET_NAMES": "private/config.h" "SUPPORT_GETHOSTBYNAME": "private/config.h" "SUPPORT_UNISTD_SYSCONF_GETPAGESIZE": "private/config.h" @@ -155,6 +175,7 @@ "TEST_LEVEL_DISABLED": "test/helper/level_disabled.hpp" "TEST_LEVEL_ENABLED": "test/helper/level_enabled.hpp" "TestRFC5424Compliance": "test/helper/rfc5424.hpp" +"TestRFC5424Timestamp": "test/helper/rfc5424.hpp" "TestSetDestinationOnOpenTarget": "test/helper/network.hpp" "TestSetDestinationOnPausedTarget": "test/helper/network.hpp" "TestSetTransportPortOnOpenTarget": "test/helper/network.hpp" @@ -178,6 +199,7 @@ "VALIDATE_ARG_NOT_NULL": "private/validate.h" "VALIDATE_ARG_NOT_NULL_INT_RETURN": "private/validate.h" "VALIDATE_ARG_NOT_NULL_UNSIGNED_RETURN": "private/validate.h" +"VALIDATE_ARG_NOT_NULL_VOID_RETURN": "private/validate.h" "VALIDATE_ARG_NOT_NULL_WINDOWS_RETURN": "private/config/wel_supported.h" "validate_app_name": "private/validate.h" "validate_hostname": "private/validate.h" diff --git a/tools/cmake/cpp.cmake b/tools/cmake/cpp.cmake index 46281b1d3..0245a9885 100644 --- a/tools/cmake/cpp.cmake +++ b/tools/cmake/cpp.cmake @@ -107,6 +107,11 @@ if(STUMPLESS_SOCKET_TARGETS_SUPPORTED) list(APPEND GENERATED_CPP_LIB_SOURCES ${CPP_LIB_BUILD_DIR}/SocketTarget.cpp) endif() +if(STUMPLESS_SQLITE3_TARGETS_SUPPORTED) + list(APPEND GENERATED_CPP_LIB_HEADERS ${CPP_LIB_BUILD_DIR}/Sqlite3Target.hpp) + list(APPEND GENERATED_CPP_LIB_SOURCES ${CPP_LIB_BUILD_DIR}/Sqlite3Target.cpp) +endif() + if(STUMPLESS_WINDOWS_EVENT_LOG_TARGETS_SUPPORTED) list(APPEND GENERATED_CPP_LIB_HEADERS ${CPP_LIB_BUILD_DIR}/WelTarget.hpp) list(APPEND GENERATED_CPP_LIB_SOURCES ${CPP_LIB_BUILD_DIR}/WelTarget.cpp) diff --git a/tools/cmake/journald.cmake b/tools/cmake/journald.cmake index 1643841dc..2e7b01086 100644 --- a/tools/cmake/journald.cmake +++ b/tools/cmake/journald.cmake @@ -37,6 +37,8 @@ add_function_test(journald systemd ) +list(APPEND STUMPLESS_LINK_LIBRARIES "systemd") + add_function_test(journald_supported SOURCES ${PROJECT_SOURCE_DIR}/test/function/config/journald_supported.cpp diff --git a/tools/cmake/sqlite3.cmake b/tools/cmake/sqlite3.cmake new file mode 100644 index 000000000..23c611055 --- /dev/null +++ b/tools/cmake/sqlite3.cmake @@ -0,0 +1,53 @@ +list(APPEND STUMPLESS_SOURCES "${PROJECT_SOURCE_DIR}/src/target/sqlite3.c") +list(APPEND WRAPTURE_SPECS "${PROJECT_SOURCE_DIR}/tools/wrapture/sqlite3_target.yml") +list(APPEND DOXYGEN_MANPAGES "${PROJECT_BINARY_DIR}/docs/man/man3/sqlite3.h.3") + +if(INCLUDE_MANPAGES_IN_INSTALL) + install(FILES + "${PROJECT_BINARY_DIR}/docs/man/man3/sqlite3.h.3" + RENAME "stumpless_target_sqlite3.h.3" + DESTINATION "${CMAKE_INSTALL_MANDIR}/man3" + ) +endif() + +if(SQLITE3_SRC_PATH) + add_library(sqlite3-src SHARED EXCLUDE_FROM_ALL "${SQLITE3_SRC_PATH}") + set_target_properties(sqlite3-src PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE) + target_compile_definitions(sqlite3-src PUBLIC "SQLITE_OMIT_LOAD_EXTENSION") + set(SQLITE3_LINK_NAME "sqlite3-src") +else() + list(APPEND STUMPLESS_LINK_LIBRARIES "sqlite3") + set(SQLITE3_LINK_NAME "sqlite3") +endif() + +add_function_test(sqlite3 + SOURCES + "${PROJECT_SOURCE_DIR}/test/function/target/sqlite3.cpp" + $ + $ + LIBRARIES + "${SQLITE3_LINK_NAME}" +) + +add_performance_test(sqlite3 + SOURCES + "${PROJECT_SOURCE_DIR}/test/performance/target/sqlite3.cpp" + $ +) + +add_thread_safety_test(sqlite3 + SOURCES + "${PROJECT_SOURCE_DIR}/test/thread_safety/target/sqlite3.cpp" + $ + LIBRARIES + "${SQLITE3_LINK_NAME}" +) + +add_example(sqlite3 + "${PROJECT_SOURCE_DIR}/docs/examples/sqlite3/sqlite3_example.c" + "${SQLITE3_SRC_PATH}" +) + +target_link_libraries(example-sqlite3 + "${SQLITE3_LINK_NAME}" +) diff --git a/tools/cmake/wel.cmake b/tools/cmake/wel.cmake index 6a72cb7ee..a14001d96 100644 --- a/tools/cmake/wel.cmake +++ b/tools/cmake/wel.cmake @@ -82,6 +82,9 @@ if(MSVC) set_target_properties(events PROPERTIES LINK_FLAGS "/NOENTRY" ) endif() + +list(APPEND STUMPLESS_LINK_LIBRARIES "KtmW32") + add_function_test(wel SOURCES ${PROJECT_SOURCE_DIR}/test/function/target/wel.cpp diff --git a/tools/wrapture/sqlite3_target.yml b/tools/wrapture/sqlite3_target.yml new file mode 100644 index 000000000..e1546a2a1 --- /dev/null +++ b/tools/wrapture/sqlite3_target.yml @@ -0,0 +1,30 @@ +version: "0.5.0" +classes: + - name: "Sqlite3Target" + namespace: "stumpless" + equivalent-struct: + name: "stumpless_target" + includes: "stumpless/target.h" + constructors: + - doc: "Creates a new target for the given database." + wrapped-function: + name: "stumpless_open_sqlite3_target" + includes: "stumpless/target/sqlite3.h" + params: + - name: "name" + doc: "The filename of the database." + type: "const char *" + return: + type: "equivalent-struct-pointer" + use-template: "pointer-return-error-check" + destructor: + doc: > + Closes this target and releases all memory and other resources held by + it. + wrapped-function: + name: "stumpless_close_sqlite3_target_and_db" + includes: "stumpless/target/sqlite3.h" + params: + - name: "equivalent-struct-pointer" + functions: + - use-template: "common-target-functions"