summaryrefslogtreecommitdiffstats
path: root/fluent-bit/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-03-09 13:19:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-03-09 13:20:02 +0000
commit58daab21cd043e1dc37024a7f99b396788372918 (patch)
tree96771e43bb69f7c1c2b0b4f7374cb74d7866d0cb /fluent-bit/src
parentReleasing debian version 1.43.2-1. (diff)
downloadnetdata-58daab21cd043e1dc37024a7f99b396788372918.tar.xz
netdata-58daab21cd043e1dc37024a7f99b396788372918.zip
Merging upstream version 1.44.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'fluent-bit/src')
-rw-r--r--fluent-bit/src/CMakeLists.txt575
-rw-r--r--fluent-bit/src/aws/CMakeLists.txt32
-rw-r--r--fluent-bit/src/aws/compression/CMakeLists.txt6
-rw-r--r--fluent-bit/src/aws/compression/arrow/CMakeLists.txt7
-rw-r--r--fluent-bit/src/aws/compression/arrow/compress.c147
-rw-r--r--fluent-bit/src/aws/compression/arrow/compress.h13
-rw-r--r--fluent-bit/src/aws/flb_aws_compress.c245
-rw-r--r--fluent-bit/src/aws/flb_aws_credentials.c862
-rw-r--r--fluent-bit/src/aws/flb_aws_credentials_ec2.c371
-rw-r--r--fluent-bit/src/aws/flb_aws_credentials_http.c566
-rw-r--r--fluent-bit/src/aws/flb_aws_credentials_log.h39
-rw-r--r--fluent-bit/src/aws/flb_aws_credentials_process.c783
-rw-r--r--fluent-bit/src/aws/flb_aws_credentials_profile.c753
-rw-r--r--fluent-bit/src/aws/flb_aws_credentials_sts.c958
-rw-r--r--fluent-bit/src/aws/flb_aws_error_reporter.c276
-rw-r--r--fluent-bit/src/aws/flb_aws_imds.c370
-rw-r--r--fluent-bit/src/aws/flb_aws_util.c1047
-rw-r--r--fluent-bit/src/config_format/flb_cf_fluentbit.c804
-rw-r--r--fluent-bit/src/config_format/flb_cf_yaml.c2110
-rw-r--r--fluent-bit/src/config_format/flb_config_format.c878
-rw-r--r--fluent-bit/src/flb_api.c56
-rw-r--r--fluent-bit/src/flb_avro.c397
-rw-r--r--fluent-bit/src/flb_base64.c239
-rw-r--r--fluent-bit/src/flb_callback.c134
-rw-r--r--fluent-bit/src/flb_chunk_trace.c692
-rw-r--r--fluent-bit/src/flb_compression.c221
-rw-r--r--fluent-bit/src/flb_config.c942
-rw-r--r--fluent-bit/src/flb_config_map.c817
-rw-r--r--fluent-bit/src/flb_config_static.c63
-rw-r--r--fluent-bit/src/flb_connection.c257
-rw-r--r--fluent-bit/src/flb_coro.c56
-rw-r--r--fluent-bit/src/flb_crypto.c405
-rw-r--r--fluent-bit/src/flb_csv.c313
-rw-r--r--fluent-bit/src/flb_custom.c314
-rw-r--r--fluent-bit/src/flb_dlfcn_win32.c101
-rw-r--r--fluent-bit/src/flb_downstream.c514
-rw-r--r--fluent-bit/src/flb_dump.c260
-rw-r--r--fluent-bit/src/flb_engine.c1124
-rw-r--r--fluent-bit/src/flb_engine_dispatch.c339
-rw-r--r--fluent-bit/src/flb_env.c273
-rw-r--r--fluent-bit/src/flb_event.c76
-rw-r--r--fluent-bit/src/flb_file.c73
-rw-r--r--fluent-bit/src/flb_filter.c669
-rw-r--r--fluent-bit/src/flb_fstore.c558
-rw-r--r--fluent-bit/src/flb_gzip.c747
-rw-r--r--fluent-bit/src/flb_hash.c205
-rw-r--r--fluent-bit/src/flb_hash_table.c539
-rw-r--r--fluent-bit/src/flb_help.c655
-rw-r--r--fluent-bit/src/flb_hmac.c382
-rw-r--r--fluent-bit/src/flb_http_client.c1399
-rw-r--r--fluent-bit/src/flb_http_client_debug.c196
-rw-r--r--fluent-bit/src/flb_input.c1965
-rw-r--r--fluent-bit/src/flb_input_chunk.c2009
-rw-r--r--fluent-bit/src/flb_input_log.c123
-rw-r--r--fluent-bit/src/flb_input_metric.c101
-rw-r--r--fluent-bit/src/flb_input_thread.c745
-rw-r--r--fluent-bit/src/flb_input_trace.c102
-rw-r--r--fluent-bit/src/flb_io.c749
-rw-r--r--fluent-bit/src/flb_kafka.c216
-rw-r--r--fluent-bit/src/flb_kernel.c161
-rw-r--r--fluent-bit/src/flb_kv.c132
-rw-r--r--fluent-bit/src/flb_lib.c815
-rw-r--r--fluent-bit/src/flb_log.c696
-rw-r--r--fluent-bit/src/flb_log_event_decoder.c383
-rw-r--r--fluent-bit/src/flb_log_event_encoder.c391
-rw-r--r--fluent-bit/src/flb_log_event_encoder_dynamic_field.c272
-rw-r--r--fluent-bit/src/flb_log_event_encoder_primitives.c721
-rw-r--r--fluent-bit/src/flb_lua.c841
-rw-r--r--fluent-bit/src/flb_luajit.c97
-rw-r--r--fluent-bit/src/flb_meta.c79
-rw-r--r--fluent-bit/src/flb_metrics.c365
-rw-r--r--fluent-bit/src/flb_metrics_exporter.c336
-rw-r--r--fluent-bit/src/flb_mp.c645
-rw-r--r--fluent-bit/src/flb_network.c2168
-rw-r--r--fluent-bit/src/flb_oauth2.c437
-rw-r--r--fluent-bit/src/flb_output.c1445
-rw-r--r--fluent-bit/src/flb_output_thread.c568
-rw-r--r--fluent-bit/src/flb_pack.c1270
-rw-r--r--fluent-bit/src/flb_pack_gelf.c826
-rw-r--r--fluent-bit/src/flb_parser.c1304
-rw-r--r--fluent-bit/src/flb_parser_decoder.c777
-rw-r--r--fluent-bit/src/flb_parser_json.c246
-rw-r--r--fluent-bit/src/flb_parser_logfmt.c326
-rw-r--r--fluent-bit/src/flb_parser_ltsv.c269
-rw-r--r--fluent-bit/src/flb_parser_regex.c227
-rw-r--r--fluent-bit/src/flb_pipe.c183
-rw-r--r--fluent-bit/src/flb_plugin.c452
-rw-r--r--fluent-bit/src/flb_plugin_proxy.c498
-rw-r--r--fluent-bit/src/flb_processor.c1129
-rw-r--r--fluent-bit/src/flb_ra_key.c808
-rw-r--r--fluent-bit/src/flb_random.c97
-rw-r--r--fluent-bit/src/flb_record_accessor.c906
-rw-r--r--fluent-bit/src/flb_regex.c319
-rw-r--r--fluent-bit/src/flb_reload.c517
-rw-r--r--fluent-bit/src/flb_ring_buffer.c205
-rw-r--r--fluent-bit/src/flb_router.c271
-rw-r--r--fluent-bit/src/flb_routes_mask.c143
-rw-r--r--fluent-bit/src/flb_scheduler.c727
-rw-r--r--fluent-bit/src/flb_sds.c500
-rw-r--r--fluent-bit/src/flb_sds_list.c185
-rw-r--r--fluent-bit/src/flb_signv4.c1245
-rw-r--r--fluent-bit/src/flb_slist.c356
-rw-r--r--fluent-bit/src/flb_snappy.c344
-rw-r--r--fluent-bit/src/flb_socket.c46
-rw-r--r--fluent-bit/src/flb_sosreport.c322
-rw-r--r--fluent-bit/src/flb_sqldb.c132
-rw-r--r--fluent-bit/src/flb_storage.c718
-rw-r--r--fluent-bit/src/flb_strptime.c750
-rw-r--r--fluent-bit/src/flb_task.c520
-rw-r--r--fluent-bit/src/flb_thread_pool.c209
-rw-r--r--fluent-bit/src/flb_time.c444
-rw-r--r--fluent-bit/src/flb_typecast.c525
-rw-r--r--fluent-bit/src/flb_unescape.c328
-rw-r--r--fluent-bit/src/flb_upstream.c1202
-rw-r--r--fluent-bit/src/flb_upstream_ha.c357
-rw-r--r--fluent-bit/src/flb_upstream_node.c213
-rw-r--r--fluent-bit/src/flb_uri.c186
-rw-r--r--fluent-bit/src/flb_utils.c1433
-rw-r--r--fluent-bit/src/flb_worker.c173
-rw-r--r--fluent-bit/src/fluent-bit.c1417
-rw-r--r--fluent-bit/src/http_server/CMakeLists.txt20
-rw-r--r--fluent-bit/src/http_server/api/v1/CMakeLists.txt20
-rw-r--r--fluent-bit/src/http_server/api/v1/health.c335
-rw-r--r--fluent-bit/src/http_server/api/v1/health.h73
-rw-r--r--fluent-bit/src/http_server/api/v1/metrics.c579
-rw-r--r--fluent-bit/src/http_server/api/v1/metrics.h30
-rw-r--r--fluent-bit/src/http_server/api/v1/plugins.c109
-rw-r--r--fluent-bit/src/http_server/api/v1/plugins.h28
-rw-r--r--fluent-bit/src/http_server/api/v1/register.c49
-rw-r--r--fluent-bit/src/http_server/api/v1/register.h28
-rw-r--r--fluent-bit/src/http_server/api/v1/storage.c204
-rw-r--r--fluent-bit/src/http_server/api/v1/storage.h28
-rw-r--r--fluent-bit/src/http_server/api/v1/trace.c615
-rw-r--r--fluent-bit/src/http_server/api/v1/trace.h28
-rw-r--r--fluent-bit/src/http_server/api/v1/uptime.c111
-rw-r--r--fluent-bit/src/http_server/api/v1/uptime.h28
-rw-r--r--fluent-bit/src/http_server/api/v2/CMakeLists.txt10
-rw-r--r--fluent-bit/src/http_server/api/v2/metrics.c259
-rw-r--r--fluent-bit/src/http_server/api/v2/metrics.h28
-rw-r--r--fluent-bit/src/http_server/api/v2/register.c31
-rw-r--r--fluent-bit/src/http_server/api/v2/register.h28
-rw-r--r--fluent-bit/src/http_server/api/v2/reload.c161
-rw-r--r--fluent-bit/src/http_server/api/v2/reload.h28
-rw-r--r--fluent-bit/src/http_server/flb_hs.c147
-rw-r--r--fluent-bit/src/http_server/flb_hs_endpoints.c121
-rw-r--r--fluent-bit/src/http_server/flb_hs_utils.c48
-rw-r--r--fluent-bit/src/multiline/CMakeLists.txt15
-rw-r--r--fluent-bit/src/multiline/flb_ml.c1562
-rw-r--r--fluent-bit/src/multiline/flb_ml_group.c86
-rw-r--r--fluent-bit/src/multiline/flb_ml_mode.c111
-rw-r--r--fluent-bit/src/multiline/flb_ml_parser.c347
-rw-r--r--fluent-bit/src/multiline/flb_ml_parser_cri.c81
-rw-r--r--fluent-bit/src/multiline/flb_ml_parser_docker.c110
-rw-r--r--fluent-bit/src/multiline/flb_ml_parser_go.c140
-rw-r--r--fluent-bit/src/multiline/flb_ml_parser_java.c143
-rw-r--r--fluent-bit/src/multiline/flb_ml_parser_python.c98
-rw-r--r--fluent-bit/src/multiline/flb_ml_parser_ruby.c87
-rw-r--r--fluent-bit/src/multiline/flb_ml_rule.c421
-rw-r--r--fluent-bit/src/multiline/flb_ml_stream.c338
-rw-r--r--fluent-bit/src/proxy/CMakeLists.txt3
-rw-r--r--fluent-bit/src/proxy/go/CMakeLists.txt10
-rw-r--r--fluent-bit/src/proxy/go/go.c289
-rw-r--r--fluent-bit/src/proxy/go/go.h72
-rw-r--r--fluent-bit/src/record_accessor/CMakeLists.txt31
-rw-r--r--fluent-bit/src/record_accessor/flb_ra_parser.c365
-rw-r--r--fluent-bit/src/record_accessor/ra.l69
-rw-r--r--fluent-bit/src/record_accessor/ra.y99
-rw-r--r--fluent-bit/src/stream_processor/CMakeLists.txt21
-rw-r--r--fluent-bit/src/stream_processor/README.md84
-rw-r--r--fluent-bit/src/stream_processor/flb_sp.c2157
-rw-r--r--fluent-bit/src/stream_processor/flb_sp_aggregate_func.c364
-rw-r--r--fluent-bit/src/stream_processor/flb_sp_func_record.c77
-rw-r--r--fluent-bit/src/stream_processor/flb_sp_func_time.c95
-rw-r--r--fluent-bit/src/stream_processor/flb_sp_groupby.c82
-rw-r--r--fluent-bit/src/stream_processor/flb_sp_key.c231
-rw-r--r--fluent-bit/src/stream_processor/flb_sp_snapshot.c277
-rw-r--r--fluent-bit/src/stream_processor/flb_sp_stream.c168
-rw-r--r--fluent-bit/src/stream_processor/flb_sp_window.c122
-rw-r--r--fluent-bit/src/stream_processor/parser/CMakeLists.txt31
-rw-r--r--fluent-bit/src/stream_processor/parser/flb_sp_parser.c851
-rw-r--r--fluent-bit/src/stream_processor/parser/sql.l190
-rw-r--r--fluent-bit/src/stream_processor/parser/sql.y437
-rw-r--r--fluent-bit/src/tls/flb_tls.c665
-rw-r--r--fluent-bit/src/tls/openssl.c616
-rw-r--r--fluent-bit/src/wamrc/CMakeLists.txt210
-rw-r--r--fluent-bit/src/wasm/CMakeLists.txt111
-rw-r--r--fluent-bit/src/wasm/flb_wasm.c316
-rw-r--r--fluent-bit/src/win32/winsvc.c148
188 files changed, 78401 insertions, 0 deletions
diff --git a/fluent-bit/src/CMakeLists.txt b/fluent-bit/src/CMakeLists.txt
new file mode 100644
index 000000000..b6233d9f7
--- /dev/null
+++ b/fluent-bit/src/CMakeLists.txt
@@ -0,0 +1,575 @@
+add_definitions(-DFLB_CORE=1)
+
+# Core Source
+set(src
+ ${src}
+ flb_mp.c
+ flb_kv.c
+ flb_api.c
+ flb_csv.c
+ flb_lib.c
+ flb_log.c
+ flb_env.c
+ flb_file.c
+ flb_uri.c
+ flb_hash_table.c
+ flb_help.c
+ flb_pack.c
+ flb_pack_gelf.c
+ flb_sds.c
+ flb_sds_list.c
+ flb_pipe.c
+ flb_meta.c
+ flb_kernel.c
+ flb_custom.c
+ flb_input.c
+ flb_input_chunk.c
+ flb_input_log.c
+ flb_input_metric.c
+ flb_input_trace.c
+ flb_input_thread.c
+ flb_filter.c
+ flb_output.c
+ flb_output_thread.c
+ flb_config.c
+ flb_config_map.c
+ flb_socket.c
+ flb_network.c
+ flb_utils.c
+ flb_slist.c
+ flb_engine.c
+ flb_engine_dispatch.c
+ flb_task.c
+ flb_unescape.c
+ flb_scheduler.c
+ flb_io.c
+ flb_storage.c
+ flb_connection.c
+ flb_downstream.c
+ flb_upstream.c
+ flb_upstream_ha.c
+ flb_upstream_node.c
+ flb_router.c
+ flb_worker.c
+ flb_coro.c
+ flb_time.c
+ flb_sosreport.c
+ flb_hmac.c
+ flb_hash.c
+ flb_crypto.c
+ flb_random.c
+ flb_plugin.c
+ flb_gzip.c
+ flb_snappy.c
+ flb_compression.c
+ flb_http_client.c
+ flb_callback.c
+ flb_strptime.c
+ flb_fstore.c
+ flb_thread_pool.c
+ flb_routes_mask.c
+ flb_typecast.c
+ flb_event.c
+ flb_base64.c
+ flb_ring_buffer.c
+ flb_log_event_decoder.c
+ flb_log_event_encoder.c
+ flb_log_event_encoder_primitives.c
+ flb_log_event_encoder_dynamic_field.c
+ flb_processor.c
+ flb_reload.c
+ )
+
+# Config format
+set(src
+ ${src}
+ config_format/flb_config_format.c
+ config_format/flb_cf_fluentbit.c
+)
+if(FLB_HAVE_LIBYAML)
+ set(src
+ ${src}
+ config_format/flb_cf_yaml.c
+ )
+endif()
+
+# Multiline subsystem
+add_subdirectory(multiline)
+set(src
+ ${src}
+ ${src_multiline}
+ )
+
+if(FLB_SYSTEM_WINDOWS)
+ set(src
+ ${src}
+ flb_dlfcn_win32.c
+ )
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W2")
+endif()
+
+if(FLB_PARSER)
+ set(src
+ ${src}
+ flb_parser.c
+ flb_parser_regex.c
+ flb_parser_json.c
+ flb_parser_decoder.c
+ flb_parser_ltsv.c
+ flb_parser_logfmt.c
+ )
+endif()
+
+if(FLB_AVRO_ENCODER)
+ set(src
+ ${src}
+ flb_avro.c
+ )
+endif()
+
+# Fluent Bit have TLS support
+if(FLB_TLS)
+ # Register the TLS interface and functions
+ set(src
+ ${src}
+ "tls/flb_tls.c"
+ "flb_oauth2.c"
+ )
+
+ # Make sure our output targets links to the TLS library
+ set(extra_libs
+ ${extra_libs}
+ )
+endif()
+
+if(FLB_PROXY_GO)
+ set(src
+ ${src}
+ "flb_plugin_proxy.c"
+ )
+endif()
+
+if(FLB_METRICS)
+ set(src
+ ${src}
+ "flb_metrics.c"
+ "flb_metrics_exporter.c"
+ )
+endif()
+
+if(FLB_SIGNV4 AND FLB_TLS)
+ set(src
+ ${src}
+ "flb_signv4.c"
+ )
+endif()
+
+if(FLB_HTTP_CLIENT_DEBUG)
+ set(src
+ ${src}
+ "flb_http_client_debug.c"
+ )
+endif()
+
+if (FLB_AWS_ERROR_REPORTER)
+ set(src
+ ${src}
+ "aws/flb_aws_error_reporter.c"
+ )
+endif()
+
+if(FLB_LUAJIT)
+ set(src
+ ${src}
+ "flb_lua.c"
+ "flb_luajit.c"
+ )
+endif()
+
+if(FLB_IN_KAFKA OR FLB_OUT_KAFKA)
+ set(src
+ ${src}
+ "flb_kafka.c"
+ )
+endif()
+
+# Link to libco
+set(extra_libs
+ ${extra_libs}
+ "co")
+
+set(extra_libs
+ ${extra_libs}
+ "rbtree")
+
+if(FLB_JEMALLOC)
+ set(extra_libs
+ ${extra_libs}
+ "libjemalloc")
+endif()
+
+if(FLB_REGEX)
+ set(FLB_DEPS
+ ${FLB_DEPSS}
+ onigmo-static)
+ set(src
+ ${src}
+ "flb_regex.c"
+ )
+endif()
+
+if(FLB_LUAJIT)
+ set(extra_libs
+ ${extra_libs}
+ "libluajit")
+endif()
+
+if(FLB_SQLDB)
+ set(src
+ ${src}
+ "flb_sqldb.c"
+ )
+ set(extra_libs
+ ${extra_libs}
+ "sqlite3")
+endif()
+
+if(FLB_STATIC_CONF)
+ set(src
+ ${src}
+ "flb_config_static.c"
+ )
+endif()
+
+if(FLB_CHUNK_TRACE)
+ set(src
+ ${src}
+ "flb_chunk_trace.c"
+ )
+endif()
+
+include(CheckSymbolExists)
+check_symbol_exists(accept4 "sys/socket.h" HAVE_ACCEPT4)
+
+# Core dependencies
+if(FLB_SYSTEM_WINDOWS)
+ set(FLB_DEPS
+ "ws2_32.lib"
+ "crypt32.lib"
+ "Bcrypt.lib"
+ "Shlwapi.lib"
+ )
+else()
+ set(FLB_DEPS
+ ${FLB_DEPS}
+ ${CMAKE_DL_LIBS}
+ m
+ )
+endif()
+
+# Link timer library
+if(CMAKE_SYSTEM_NAME MATCHES "Linux")
+ set(FLB_DEPS
+ ${FLB_DEPS}
+ rt
+ )
+endif()
+
+
+# Record Accessor
+# ---------------
+# Make sure it dependency is enabled
+if(FLB_RECORD_ACCESSOR AND NOT FLB_REGEX)
+ message(FATAL_ERROR
+ "FLB_RECORD_ACCESSOR depends on FLB_REGEX, "
+ "enable it with: -DFLB_REGEX=ON")
+endif()
+
+# Build record accessor files
+if(FLB_RECORD_ACCESSOR)
+ set(src
+ ${src}
+ "flb_record_accessor.c"
+ "flb_ra_key.c"
+ )
+ add_subdirectory(record_accessor)
+endif()
+
+# Stream Processor
+if(FLB_STREAM_PROCESSOR)
+ add_subdirectory(stream_processor)
+endif()
+
+# AWS specific
+if(FLB_AWS)
+ add_subdirectory(aws)
+endif()
+
+# HTTP Server
+if(FLB_HTTP_SERVER)
+ add_subdirectory(http_server)
+endif()
+
+# Proxy interfaces
+add_subdirectory(proxy)
+
+set(FLB_PROXY_PLUGINS "")
+if(FLB_PROXY_GO)
+ set(FLB_PROXY_PLUGINS ${FLB_PROXY_PLUGINS} flb-plugin-proxy-go)
+endif()
+
+# WASM runtime
+if(FLB_WASM)
+ add_subdirectory(wasm)
+endif()
+
+# WAMRC compiler
+if(FLB_WAMRC)
+ add_subdirectory(wamrc)
+endif()
+
+# HTTP Server
+if(FLB_HTTP_SERVER)
+ set(FLB_DEPS
+ ${FLB_DEPS}
+ flb-http-server)
+endif()
+
+# AVRO Encoding
+if(FLB_AVRO_ENCODER)
+set(FLB_DEPS
+ ${FLB_DEPS}
+ avro-static
+ jansson
+ )
+endif()
+
+# WASM runtime
+if(FLB_WASM)
+ set(FLB_DEPS
+ ${FLB_DEPS}
+ vmlib-static
+ flb-wasm-static)
+endif()
+
+# Set static dependencies
+set(FLB_DEPS
+ ${FLB_DEPS}
+ cfl-static
+ fluent-otel-proto
+ cmetrics-static
+ ctraces-static
+ mk_core
+ jsmn
+ msgpack-c-static
+ mpack-static
+ chunkio-static
+ miniz
+
+ ${FLB_PLUGINS}
+ ${FLB_PROXY_PLUGINS}
+ ${extra_libs}
+ c-ares
+ snappy-c
+ lwrb
+ )
+
+if(OPENSSL_FOUND)
+ set(FLB_DEPS
+ ${FLB_DEPS}
+ OpenSSL::SSL
+ )
+endif()
+
+# libyaml
+if(FLB_HAVE_LIBYAML)
+set(FLB_DEPS
+ ${FLB_DEPS}
+ yaml
+ )
+endif()
+
+# UTF8 Encoding
+if(FLB_UTF8_ENCODER)
+set(FLB_DEPS
+ ${FLB_DEPS}
+ tutf8e
+ )
+endif()
+
+# AWS specific
+if(FLB_AWS)
+ set(FLB_DEPS
+ ${FLB_DEPS}
+ flb-aws
+ )
+endif()
+
+# Record Accessor
+if(FLB_RECORD_ACCESSOR)
+ set(FLB_DEPS
+ ${FLB_DEPS}
+ flb-ra-parser
+ )
+endif()
+
+# Stream Processor
+if(FLB_STREAM_PROCESSOR)
+ set(FLB_DEPS
+ ${FLB_DEPS}
+ flb-sp
+ )
+endif()
+
+if (MSVC)
+set(flb_rc_files
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ )
+endif()
+
+# Shared Library
+if(FLB_SHARED_LIB)
+ add_library(fluent-bit-shared SHARED ${src})
+ add_sanitizers(fluent-bit-shared)
+ set_target_properties(fluent-bit-shared
+ PROPERTIES OUTPUT_NAME fluent-bit)
+
+ # Windows doesn't provide pthread (see winpthreads.c in mk_core).
+ if(CMAKE_SYSTEM_NAME MATCHES "Windows")
+ target_link_libraries(fluent-bit-shared ${FLB_DEPS})
+ else()
+ target_link_libraries(fluent-bit-shared ${FLB_DEPS} -lpthread)
+ endif()
+
+ if (MSVC)
+ set_target_properties(fluent-bit-shared
+ PROPERTIES PDB_NAME fluent-bit.dll)
+ target_link_options(fluent-bit-shared
+ PUBLIC /pdb:$<TARGET_PDB_FILE:fluent-bit-shared>)
+ endif()
+
+ # Library install routines
+ install(TARGETS fluent-bit-shared
+ LIBRARY DESTINATION ${FLB_INSTALL_LIBDIR}
+ COMPONENT library
+ RUNTIME DESTINATION ${FLB_INSTALL_BINDIR})
+endif()
+
+# Static Library
+add_library(fluent-bit-static STATIC ${src})
+add_sanitizers(fluent-bit-static)
+target_link_libraries(fluent-bit-static ${FLB_DEPS})
+
+if(MSVC)
+ # Rename the output for Windows environment to avoid naming issues
+ set_target_properties(fluent-bit-static PROPERTIES OUTPUT_NAME libfluent-bit)
+else()
+ set_target_properties(fluent-bit-static PROPERTIES OUTPUT_NAME fluent-bit)
+endif(MSVC)
+
+if(FLB_JEMALLOC)
+ target_link_libraries(fluent-bit-static libjemalloc)
+endif()
+
+# Binary / Executable
+if(FLB_BINARY)
+ find_package (Threads)
+ if (FLB_SYSTEM_WINDOWS)
+ add_executable(fluent-bit-bin fluent-bit.c flb_dump.c win32/winsvc.c ${flb_rc_files})
+ else()
+ add_executable(fluent-bit-bin fluent-bit.c flb_dump.c)
+ endif()
+ add_sanitizers(fluent-bit-bin)
+
+
+ if(FLB_STATIC_CONF)
+ add_dependencies(fluent-bit-bin flb-static-conf)
+ endif()
+
+ if(FLB_REGEX)
+ target_link_libraries(fluent-bit-bin onigmo-static)
+ endif()
+
+ if(FLB_JEMALLOC)
+ target_link_libraries(fluent-bit-bin libjemalloc)
+ endif()
+
+ if(FLB_BACKTRACE)
+ add_definitions(-DFLB_DUMP_STACKTRACE=1)
+ target_link_libraries(fluent-bit-bin libbacktrace)
+ endif()
+
+ target_link_libraries(fluent-bit-bin fluent-bit-static ${CMAKE_THREAD_LIBS_INIT})
+
+ set_target_properties(fluent-bit-bin
+ PROPERTIES
+ OUTPUT_NAME ${FLB_OUT_NAME}
+ ENABLE_EXPORTS ON)
+ install(TARGETS fluent-bit-bin RUNTIME DESTINATION ${FLB_INSTALL_BINDIR} COMPONENT binary)
+
+ # Include PDB file (if available)
+ if (MSVC)
+ target_link_options(fluent-bit-bin
+ PUBLIC /pdb:$<TARGET_PDB_FILE:fluent-bit-bin>)
+ install(FILES $<TARGET_PDB_FILE:fluent-bit-bin>
+ DESTINATION "${FLB_INSTALL_BINDIR}")
+ endif()
+
+ # Detect init system, install upstart, systemd or init.d script
+
+ # Handle issues with detection on some systems during build
+ if(NOT SYSTEMD_UNITDIR AND IS_DIRECTORY /lib/systemd/system)
+ set(SYSTEMD_UNITDIR /lib/systemd/system)
+ endif()
+
+ if(SYSTEMD_UNITDIR)
+ set(FLB_SYSTEMD_SCRIPT "${PROJECT_SOURCE_DIR}/init/${FLB_OUT_NAME}.service")
+ configure_file(
+ "${PROJECT_SOURCE_DIR}/init/systemd.in"
+ ${FLB_SYSTEMD_SCRIPT}
+ )
+ install(FILES ${FLB_SYSTEMD_SCRIPT} COMPONENT binary DESTINATION ${SYSTEMD_UNITDIR})
+ install(DIRECTORY DESTINATION ${FLB_INSTALL_CONFDIR} COMPONENT binary)
+ elseif(IS_DIRECTORY /usr/share/upstart)
+ set(FLB_UPSTART_SCRIPT "${PROJECT_SOURCE_DIR}/init/${FLB_OUT_NAME}.conf")
+ configure_file(
+ "${PROJECT_SOURCE_DIR}/init/upstart.in"
+ ${FLB_UPSTART_SCRIPT}
+ )
+ install(FILES ${FLB_UPSTART_SCRIPT} COMPONENT binary DESTINATION /etc/init)
+ install(DIRECTORY DESTINATION COMPONENT binary ${FLB_INSTALL_CONFDIR})
+ else()
+ # FIXME: should we support Sysv init script ?
+ endif()
+
+ if(FLB_SYSTEM_WINDOWS)
+ install(FILES
+ "${PROJECT_SOURCE_DIR}/conf/fluent-bit-win32.conf"
+ DESTINATION ${FLB_INSTALL_CONFDIR}
+ COMPONENT binary
+ RENAME "${FLB_OUT_NAME}.conf")
+ elseif(FLB_SYSTEM_MACOS)
+ install(FILES
+ "${PROJECT_SOURCE_DIR}/conf/fluent-bit-macos.conf"
+ DESTINATION ${FLB_INSTALL_CONFDIR}
+ COMPONENT binary
+ RENAME "${FLB_OUT_NAME}.conf")
+ else()
+ install(FILES
+ "${PROJECT_SOURCE_DIR}/conf/fluent-bit.conf"
+ DESTINATION ${FLB_INSTALL_CONFDIR}
+ COMPONENT binary
+ RENAME "${FLB_OUT_NAME}.conf")
+ endif()
+
+ install(FILES
+ "${PROJECT_SOURCE_DIR}/conf/parsers.conf"
+ COMPONENT binary
+ DESTINATION ${FLB_INSTALL_CONFDIR})
+
+ install(FILES
+ "${PROJECT_SOURCE_DIR}/conf/plugins.conf"
+ COMPONENT binary
+ DESTINATION ${FLB_INSTALL_CONFDIR})
+
+endif()
diff --git a/fluent-bit/src/aws/CMakeLists.txt b/fluent-bit/src/aws/CMakeLists.txt
new file mode 100644
index 000000000..a6580e7a5
--- /dev/null
+++ b/fluent-bit/src/aws/CMakeLists.txt
@@ -0,0 +1,32 @@
+add_subdirectory(compression)
+
+include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR}
+ )
+
+set(src
+ "flb_aws_credentials_log.h"
+ "flb_aws_compress.c"
+ "flb_aws_util.c"
+ "flb_aws_credentials.c"
+ "flb_aws_credentials_sts.c"
+ "flb_aws_credentials_ec2.c"
+ "flb_aws_imds.c"
+ "flb_aws_credentials_http.c"
+ "flb_aws_credentials_profile.c"
+ )
+
+if(FLB_HAVE_AWS_CREDENTIAL_PROCESS)
+ set(src
+ ${src}
+ "flb_aws_credentials_process.c"
+ )
+endif()
+
+add_library(flb-aws STATIC ${src})
+target_link_libraries(flb-aws flb-aws-compress)
+
+if(FLB_JEMALLOC)
+ target_link_libraries(flb-aws libjemalloc)
+endif()
diff --git a/fluent-bit/src/aws/compression/CMakeLists.txt b/fluent-bit/src/aws/compression/CMakeLists.txt
new file mode 100644
index 000000000..02a1ba3a6
--- /dev/null
+++ b/fluent-bit/src/aws/compression/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_library(flb-aws-compress INTERFACE)
+
+if(FLB_ARROW)
+ add_subdirectory(arrow EXCLUDE_FROM_ALL)
+ target_link_libraries(flb-aws-compress INTERFACE flb-aws-arrow)
+endif()
diff --git a/fluent-bit/src/aws/compression/arrow/CMakeLists.txt b/fluent-bit/src/aws/compression/arrow/CMakeLists.txt
new file mode 100644
index 000000000..846f65441
--- /dev/null
+++ b/fluent-bit/src/aws/compression/arrow/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(src
+ compress.c)
+
+add_library(flb-aws-arrow STATIC ${src})
+
+target_include_directories(flb-aws-arrow PRIVATE ${ARROW_GLIB_INCLUDE_DIRS})
+target_link_libraries(flb-aws-arrow ${ARROW_GLIB_LDFLAGS})
diff --git a/fluent-bit/src/aws/compression/arrow/compress.c b/fluent-bit/src/aws/compression/arrow/compress.c
new file mode 100644
index 000000000..a48b34f80
--- /dev/null
+++ b/fluent-bit/src/aws/compression/arrow/compress.c
@@ -0,0 +1,147 @@
+/*
+ * This converts S3 plugin's request buffer into Apache Arrow format.
+ *
+ * We use GLib binding to call Arrow functions (which is implemented
+ * in C++) from Fluent Bit.
+ *
+ * https://github.com/apache/arrow/tree/master/c_glib
+ */
+
+#include <arrow-glib/arrow-glib.h>
+#include <inttypes.h>
+
+/*
+ * GArrowTable is the central structure that represents "table" (a.k.a.
+ * data frame).
+ */
+static GArrowTable* parse_json(uint8_t *json, int size)
+{
+ GArrowJSONReader *reader;
+ GArrowBuffer *buffer;
+ GArrowBufferInputStream *input;
+ GArrowJSONReadOptions *options;
+ GArrowTable *table;
+ GError *error = NULL;
+
+ buffer = garrow_buffer_new(json, size);
+ if (buffer == NULL) {
+ return NULL;
+ }
+
+ input = garrow_buffer_input_stream_new(buffer);
+ if (input == NULL) {
+ g_object_unref(buffer);
+ return NULL;
+ }
+
+ options = garrow_json_read_options_new();
+ if (options == NULL) {
+ g_object_unref(buffer);
+ g_object_unref(input);
+ return NULL;
+ }
+
+ reader = garrow_json_reader_new(GARROW_INPUT_STREAM(input), options, &error);
+ if (reader == NULL) {
+ g_error_free(error);
+ g_object_unref(buffer);
+ g_object_unref(input);
+ g_object_unref(options);
+ return NULL;
+ }
+
+ table = garrow_json_reader_read(reader, &error);
+ if (table == NULL) {
+ g_error_free(error);
+ g_object_unref(buffer);
+ g_object_unref(input);
+ g_object_unref(options);
+ g_object_unref(reader);
+ return NULL;
+ }
+ g_object_unref(buffer);
+ g_object_unref(input);
+ g_object_unref(options);
+ g_object_unref(reader);
+ return table;
+}
+
+static GArrowResizableBuffer* table_to_buffer(GArrowTable *table)
+{
+ GArrowResizableBuffer *buffer;
+ GArrowBufferOutputStream *sink;
+ GError *error = NULL;
+ gboolean success;
+
+ buffer = garrow_resizable_buffer_new(0, &error);
+ if (buffer == NULL) {
+ g_error_free(error);
+ return NULL;
+ }
+
+ sink = garrow_buffer_output_stream_new(buffer);
+ if (sink == NULL) {
+ g_object_unref(buffer);
+ return NULL;
+ }
+
+ success = garrow_table_write_as_feather(
+ table, GARROW_OUTPUT_STREAM(sink),
+ NULL, &error);
+ if (!success) {
+ g_error_free(error);
+ g_object_unref(buffer);
+ g_object_unref(sink);
+ return NULL;
+ }
+ g_object_unref(sink);
+ return buffer;
+}
+
+int out_s3_compress_arrow(void *json, size_t size, void **out_buf, size_t *out_size)
+{
+ GArrowTable *table;
+ GArrowResizableBuffer *buffer;
+ GBytes *bytes;
+ gconstpointer ptr;
+ gsize len;
+ uint8_t *buf;
+
+ table = parse_json((uint8_t *) json, size);
+ if (table == NULL) {
+ return -1;
+ }
+
+ buffer = table_to_buffer(table);
+ g_object_unref(table);
+ if (buffer == NULL) {
+ return -1;
+ }
+
+ bytes = garrow_buffer_get_data(GARROW_BUFFER(buffer));
+ if (bytes == NULL) {
+ g_object_unref(buffer);
+ return -1;
+ }
+
+ ptr = g_bytes_get_data(bytes, &len);
+ if (ptr == NULL) {
+ g_object_unref(buffer);
+ g_bytes_unref(bytes);
+ return -1;
+ }
+
+ buf = malloc(len);
+ if (buf == NULL) {
+ g_object_unref(buffer);
+ g_bytes_unref(bytes);
+ return -1;
+ }
+ memcpy(buf, ptr, len);
+ *out_buf = (void *) buf;
+ *out_size = len;
+
+ g_object_unref(buffer);
+ g_bytes_unref(bytes);
+ return 0;
+}
diff --git a/fluent-bit/src/aws/compression/arrow/compress.h b/fluent-bit/src/aws/compression/arrow/compress.h
new file mode 100644
index 000000000..82e94f43c
--- /dev/null
+++ b/fluent-bit/src/aws/compression/arrow/compress.h
@@ -0,0 +1,13 @@
+/*
+ * This function converts out_s3 buffer into Apache Arrow format.
+ *
+ * `json` is a string that contain (concatenated) JSON objects.
+ *
+ * `size` is the length of the json data (excluding the trailing
+ * null-terminator character).
+ *
+ * Return 0 on success (with `out_buf` and `out_size` updated),
+ * and -1 on failure
+ */
+
+int out_s3_compress_arrow(void *json, size_t size, void **out_buf, size_t *out_size);
diff --git a/fluent-bit/src/aws/flb_aws_compress.c b/fluent-bit/src/aws/flb_aws_compress.c
new file mode 100644
index 000000000..e98ce8318
--- /dev/null
+++ b/fluent-bit/src/aws/flb_aws_compress.c
@@ -0,0 +1,245 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2019-2021 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_base64.h>
+
+#include <fluent-bit/aws/flb_aws_compress.h>
+#include <fluent-bit/flb_gzip.h>
+
+#include <stdint.h>
+
+#ifdef FLB_HAVE_ARROW
+#include "compression/arrow/compress.h"
+#endif
+
+struct compression_option {
+ int compression_type;
+ char *compression_keyword;
+ int(*compress)(void *in_data, size_t in_len, void **out_data, size_t *out_len);
+};
+
+/*
+ * Library of compression options
+ * AWS plugins that support compression will have these options.
+ * Referenced function should return -1 on error and 0 on success.
+ */
+static const struct compression_option compression_options[] = {
+ /* FLB_AWS_COMPRESS_NONE which is 0 is reserved for array footer */
+ {
+ FLB_AWS_COMPRESS_GZIP,
+ "gzip",
+ &flb_gzip_compress
+ },
+#ifdef FLB_HAVE_ARROW
+ {
+ FLB_AWS_COMPRESS_ARROW,
+ "arrow",
+ &out_s3_compress_arrow
+ },
+#endif
+ { 0 }
+};
+
+int flb_aws_compression_get_type(const char *compression_keyword)
+{
+ int ret;
+ const struct compression_option *o;
+
+ o = compression_options;
+
+ while (o->compression_type != 0) {
+ ret = strcmp(o->compression_keyword, compression_keyword);
+ if (ret == 0) {
+ return o->compression_type;
+ }
+ ++o;
+ }
+
+ flb_error("[aws_compress] unknown compression type: %s", compression_keyword);
+ return -1;
+}
+
+int flb_aws_compression_compress(int compression_type, void *in_data, size_t in_len,
+ void **out_data, size_t *out_len)
+{
+ const struct compression_option *o;
+
+ o = compression_options;
+
+ while (o->compression_type != 0) {
+ if (o->compression_type == compression_type) {
+ return o->compress(in_data, in_len, out_data, out_len);
+ }
+ ++o;
+ }
+
+ flb_error("[aws_compress] invalid compression type: %i", compression_type);
+ flb_errno();
+ return -1;
+}
+
+int flb_aws_compression_b64_truncate_compress(int compression_type, size_t max_out_len,
+ void *in_data, size_t in_len,
+ void **out_data, size_t *out_len)
+{
+ static const void *truncation_suffix = "[Truncated...]";
+ static const size_t truncation_suffix_len = 14;
+ static const double truncation_reduction_percent = 90; /* % out of 100 */
+ static const int truncation_compression_max_attempts = 10;
+
+ int ret;
+ int is_truncated;
+ int compression_attempts;
+ size_t truncated_in_len_prev;
+ size_t truncated_in_len;
+ void *truncated_in_buf;
+ void *compressed_buf;
+ size_t compressed_len;
+ size_t original_b64_compressed_len;
+
+ unsigned char *b64_compressed_buf;
+ size_t b64_compressed_len;
+ size_t b64_actual_len;
+
+ /* Iterative approach to truncation */
+ truncated_in_len = in_len;
+ truncated_in_buf = in_data;
+ is_truncated = FLB_FALSE;
+ b64_compressed_len = SIZE_MAX;
+ compression_attempts = 0;
+ while (max_out_len < b64_compressed_len - 1) {
+
+ /* Limit compression truncation attempts, just to be safe */
+ if (compression_attempts >= truncation_compression_max_attempts) {
+ if (is_truncated) {
+ flb_free(truncated_in_buf);
+ }
+ flb_error("[aws_compress] truncation failed, too many compression attempts");
+ return -1;
+ }
+
+ ret = flb_aws_compression_compress(compression_type, truncated_in_buf,
+ truncated_in_len, &compressed_buf,
+ &compressed_len);
+ ++compression_attempts;
+ if (ret != 0) {
+ if (is_truncated) {
+ flb_free(truncated_in_buf);
+ }
+ return -1;
+ }
+
+ /* Determine encoded base64 buffer size */
+ b64_compressed_len = compressed_len / 3; /* Compute number of 4 sextet groups */
+ b64_compressed_len += (compressed_len % 3 != 0); /* Add padding partial group */
+ b64_compressed_len *= 4; /* Compute number of sextets */
+ b64_compressed_len += 1; /* Add room for null character 0x00 */
+
+ /* Truncation needed */
+ if (max_out_len < b64_compressed_len - 1) {
+ flb_debug("[aws_compress] iterative truncation round");
+
+ /* This compressed_buf is the wrong size. Free */
+ flb_free(compressed_buf);
+
+ /* Base case: input compressed empty string, output still too large */
+ if (truncated_in_len == 0) {
+ if (is_truncated) {
+ flb_free(truncated_in_buf);
+ }
+ flb_error("[aws_compress] truncation failed, compressed empty input too "
+ "large");
+ return -1;
+ }
+
+ /* Calculate corrected input size */
+ truncated_in_len_prev = truncated_in_len;
+ truncated_in_len = (max_out_len * truncated_in_len) / b64_compressed_len;
+ truncated_in_len = (truncated_in_len * truncation_reduction_percent) / 100;
+
+ /* Ensure working down */
+ if (truncated_in_len >= truncated_in_len_prev) {
+ truncated_in_len = truncated_in_len_prev - 1;
+ }
+
+ /* Allocate truncation buffer */
+ if (!is_truncated) {
+ is_truncated = FLB_TRUE;
+ original_b64_compressed_len = b64_compressed_len;
+ truncated_in_buf = flb_malloc(in_len);
+ if (!truncated_in_buf) {
+ flb_errno();
+ return -1;
+ }
+ memcpy(truncated_in_buf, in_data, in_len);
+ }
+
+ /* Slap on truncation suffix */
+ if (truncated_in_len < truncation_suffix_len) {
+ /* No room for the truncation suffix. Terminal error */
+ flb_error("[aws_compress] truncation failed, no room for suffix");
+ flb_free(truncated_in_buf);
+ return -1;
+ }
+ memcpy((char *) truncated_in_buf + truncated_in_len - truncation_suffix_len,
+ truncation_suffix, truncation_suffix_len);
+ }
+ }
+
+ /* Truncate buffer free and compression buffer allocation */
+ if (is_truncated) {
+ flb_free(truncated_in_buf);
+ flb_warn("[aws_compress][size=%zu] Truncating input for compressed output "
+ "larger than %zu bytes, output from %zu to %zu bytes",
+ in_len, max_out_len, original_b64_compressed_len - 1,
+ b64_compressed_len - 1);
+ }
+ b64_compressed_buf = flb_malloc(b64_compressed_len);
+ if (!b64_compressed_buf) {
+ flb_errno();
+ return -1;
+ }
+
+ /* Base64 encode compressed out bytes */
+ ret = flb_base64_encode(b64_compressed_buf, b64_compressed_len, &b64_actual_len,
+ compressed_buf, compressed_len);
+ flb_free(compressed_buf);
+
+ if (ret == FLB_BASE64_ERR_BUFFER_TOO_SMALL) {
+ flb_error("[aws_compress] compressed log base64 buffer too small");
+ return -1; /* not handle truncation at this point */
+ }
+ if (ret != 0) {
+ flb_free(b64_compressed_buf);
+ return -1;
+ }
+
+ /* Double check b64 buf len */
+ if (b64_compressed_len - 1 != b64_actual_len) {
+ flb_error("[aws_compress] buffer len should be 1 greater than actual len");
+ flb_free(b64_compressed_buf);
+ return -1;
+ }
+
+ *out_data = b64_compressed_buf;
+ *out_len = b64_compressed_len - 1; /* disregard added null character */
+ return 0;
+}
diff --git a/fluent-bit/src/aws/flb_aws_credentials.c b/fluent-bit/src/aws/flb_aws_credentials.c
new file mode 100644
index 000000000..850142e24
--- /dev/null
+++ b/fluent-bit/src/aws/flb_aws_credentials.c
@@ -0,0 +1,862 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_http_client.h>
+#include <fluent-bit/flb_aws_credentials.h>
+#include <fluent-bit/flb_aws_util.h>
+#include <fluent-bit/flb_jsmn.h>
+#include <fluent-bit/flb_output_plugin.h>
+
+#include <stdlib.h>
+#include <time.h>
+
+#define FIVE_MINUTES 300
+#define TWELVE_HOURS 43200
+
+/* Credentials Environment Variables */
+#define AWS_ACCESS_KEY_ID "AWS_ACCESS_KEY_ID"
+#define AWS_SECRET_ACCESS_KEY "AWS_SECRET_ACCESS_KEY"
+#define AWS_SESSION_TOKEN "AWS_SESSION_TOKEN"
+
+#define EKS_POD_EXECUTION_ROLE "EKS_POD_EXECUTION_ROLE"
+
+/* declarations */
+static struct flb_aws_provider *standard_chain_create(struct flb_config
+ *config,
+ struct flb_tls *tls,
+ char *region,
+ char *sts_endpoint,
+ char *proxy,
+ struct
+ flb_aws_client_generator
+ *generator,
+ int eks_irsa,
+ char *profile);
+
+
+/*
+ * The standard credential provider chain:
+ * 1. Environment variables
+ * 2. Shared credentials file (AWS Profile)
+ * 3. EKS OIDC
+ * 4. EC2 IMDS
+ * 5. ECS HTTP credentials endpoint
+ *
+ * This provider will evaluate each provider in order, returning the result
+ * from the first provider that returns valid credentials.
+ *
+ * Note: Client code should use this provider by default.
+ */
+struct flb_aws_provider_chain {
+ struct mk_list sub_providers;
+
+ /*
+ * The standard chain provider picks the first successful provider and
+ * then uses it until a call to refresh is made.
+ */
+ struct flb_aws_provider *sub_provider;
+};
+
+/*
+ * Iterates through the chain and returns credentials from the first provider
+ * that successfully returns creds. Caches this provider on the implementation.
+ */
+struct flb_aws_credentials *get_from_chain(struct flb_aws_provider_chain
+ *implementation)
+{
+ struct flb_aws_provider *sub_provider = NULL;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_aws_credentials *creds = NULL;
+
+ /* find the first provider that produces a valid set of creds */
+ mk_list_foreach_safe(head, tmp, &implementation->sub_providers) {
+ sub_provider = mk_list_entry(head,
+ struct flb_aws_provider,
+ _head);
+ creds = sub_provider->provider_vtable->get_credentials(sub_provider);
+ if (creds) {
+ implementation->sub_provider = sub_provider;
+ return creds;
+ }
+ }
+
+ return NULL;
+}
+
+struct flb_aws_credentials *get_credentials_fn_standard_chain(struct
+ flb_aws_provider
+ *provider)
+{
+ struct flb_aws_credentials *creds = NULL;
+ struct flb_aws_provider_chain *implementation = provider->implementation;
+ struct flb_aws_provider *sub_provider = implementation->sub_provider;
+
+ if (sub_provider) {
+ return sub_provider->provider_vtable->get_credentials(sub_provider);
+ }
+
+ if (try_lock_provider(provider)) {
+ creds = get_from_chain(implementation);
+ unlock_provider(provider);
+ return creds;
+ }
+
+ /*
+ * We failed to lock the provider and sub_provider is unset. This means that
+ * another co-routine is selecting a provider from the chain.
+ */
+ flb_warn("[aws_credentials] No cached credentials are available and "
+ "a credential refresh is already in progress. The current "
+ "co-routine will retry.");
+ return NULL;
+}
+
+int init_fn_standard_chain(struct flb_aws_provider *provider)
+{
+ struct flb_aws_provider_chain *implementation = provider->implementation;
+ struct flb_aws_provider *sub_provider = NULL;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ int ret = -1;
+
+ if (try_lock_provider(provider)) {
+ /* find the first provider that indicates successful init */
+ mk_list_foreach_safe(head, tmp, &implementation->sub_providers) {
+ sub_provider = mk_list_entry(head,
+ struct flb_aws_provider,
+ _head);
+ ret = sub_provider->provider_vtable->init(sub_provider);
+ if (ret >= 0) {
+ implementation->sub_provider = sub_provider;
+ break;
+ }
+ }
+ unlock_provider(provider);
+ }
+
+ return ret;
+}
+
+/*
+ * Client code should only call refresh if there has been an
+ * error from the AWS APIs indicating creds are expired/invalid.
+ * Refresh may change the current sub_provider.
+ */
+int refresh_fn_standard_chain(struct flb_aws_provider *provider)
+{
+ struct flb_aws_provider_chain *implementation = provider->implementation;
+ struct flb_aws_provider *sub_provider = NULL;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ int ret = -1;
+
+ if (try_lock_provider(provider)) {
+ /* find the first provider that indicates successful refresh */
+ mk_list_foreach_safe(head, tmp, &implementation->sub_providers) {
+ sub_provider = mk_list_entry(head,
+ struct flb_aws_provider,
+ _head);
+ ret = sub_provider->provider_vtable->refresh(sub_provider);
+ if (ret >= 0) {
+ implementation->sub_provider = sub_provider;
+ break;
+ }
+ }
+ unlock_provider(provider);
+ }
+
+ return ret;
+}
+
+void sync_fn_standard_chain(struct flb_aws_provider *provider)
+{
+ struct flb_aws_provider_chain *implementation = provider->implementation;
+ struct flb_aws_provider *sub_provider = NULL;
+ struct mk_list *tmp;
+ struct mk_list *head;
+
+ /* set all providers to sync mode */
+ mk_list_foreach_safe(head, tmp, &implementation->sub_providers) {
+ sub_provider = mk_list_entry(head,
+ struct flb_aws_provider,
+ _head);
+ sub_provider->provider_vtable->sync(sub_provider);
+ }
+}
+
+void async_fn_standard_chain(struct flb_aws_provider *provider)
+{
+ struct flb_aws_provider_chain *implementation = provider->implementation;
+ struct flb_aws_provider *sub_provider = NULL;
+ struct mk_list *tmp;
+ struct mk_list *head;
+
+ /* set all providers to async mode */
+ mk_list_foreach_safe(head, tmp, &implementation->sub_providers) {
+ sub_provider = mk_list_entry(head,
+ struct flb_aws_provider,
+ _head);
+ sub_provider->provider_vtable->async(sub_provider);
+ }
+}
+
+void upstream_set_fn_standard_chain(struct flb_aws_provider *provider,
+ struct flb_output_instance *ins)
+{
+ struct flb_aws_provider_chain *implementation = provider->implementation;
+ struct flb_aws_provider *sub_provider = NULL;
+ struct mk_list *tmp;
+ struct mk_list *head;
+
+ /* set all providers to async mode */
+ mk_list_foreach_safe(head, tmp, &implementation->sub_providers) {
+ sub_provider = mk_list_entry(head,
+ struct flb_aws_provider,
+ _head);
+ sub_provider->provider_vtable->upstream_set(sub_provider, ins);
+ }
+}
+
+void destroy_fn_standard_chain(struct flb_aws_provider *provider) {
+ struct flb_aws_provider *sub_provider;
+ struct flb_aws_provider_chain *implementation;
+ struct mk_list *tmp;
+ struct mk_list *head;
+
+ implementation = provider->implementation;
+
+ if (implementation) {
+ mk_list_foreach_safe(head, tmp, &implementation->sub_providers) {
+ sub_provider = mk_list_entry(head, struct flb_aws_provider,
+ _head);
+ mk_list_del(&sub_provider->_head);
+ flb_aws_provider_destroy(sub_provider);
+ }
+
+ flb_free(implementation);
+ }
+}
+
+static struct flb_aws_provider_vtable standard_chain_provider_vtable = {
+ .get_credentials = get_credentials_fn_standard_chain,
+ .init = init_fn_standard_chain,
+ .refresh = refresh_fn_standard_chain,
+ .destroy = destroy_fn_standard_chain,
+ .sync = sync_fn_standard_chain,
+ .async = async_fn_standard_chain,
+ .upstream_set = upstream_set_fn_standard_chain,
+};
+
+struct flb_aws_provider *flb_standard_chain_provider_create(struct flb_config
+ *config,
+ struct flb_tls *tls,
+ char *region,
+ char *sts_endpoint,
+ char *proxy,
+ struct
+ flb_aws_client_generator
+ *generator,
+ char *profile)
+{
+ struct flb_aws_provider *provider;
+ struct flb_aws_provider *tmp_provider;
+ char *eks_pod_role = NULL;
+ char *session_name;
+
+ eks_pod_role = getenv(EKS_POD_EXECUTION_ROLE);
+ if (eks_pod_role && strlen(eks_pod_role) > 0) {
+ /*
+ * eks fargate
+ * standard chain will be base provider used to
+ * assume the EKS_POD_EXECUTION_ROLE
+ */
+ flb_debug("[aws_credentials] Using EKS_POD_EXECUTION_ROLE=%s", eks_pod_role);
+ tmp_provider = standard_chain_create(config, tls, region, sts_endpoint,
+ proxy, generator, FLB_FALSE, profile);
+
+ if (!tmp_provider) {
+ return NULL;
+ }
+
+ session_name = flb_sts_session_name();
+ if (!session_name) {
+ flb_error("Failed to generate random STS session name");
+ flb_aws_provider_destroy(tmp_provider);
+ return NULL;
+ }
+
+ provider = flb_sts_provider_create(config, tls, tmp_provider, NULL,
+ eks_pod_role, session_name,
+ region, sts_endpoint,
+ NULL, generator);
+ if (!provider) {
+ flb_error("Failed to create EKS Fargate Credential Provider");
+ flb_aws_provider_destroy(tmp_provider);
+ return NULL;
+ }
+ /* session name can freed after provider is created */
+ flb_free(session_name);
+ session_name = NULL;
+
+ return provider;
+ }
+
+ /* standard case- not in EKS Fargate */
+ provider = standard_chain_create(config, tls, region, sts_endpoint,
+ proxy, generator, FLB_TRUE, profile);
+ return provider;
+}
+
+struct flb_aws_provider *flb_managed_chain_provider_create(struct flb_output_instance
+ *ins,
+ struct flb_config
+ *config,
+ char *config_key_prefix,
+ char *proxy,
+ struct
+ flb_aws_client_generator
+ *generator)
+{
+ flb_sds_t config_key_region;
+ flb_sds_t config_key_sts_endpoint;
+ flb_sds_t config_key_role_arn;
+ flb_sds_t config_key_external_id;
+ flb_sds_t config_key_profile;
+ const char *region = NULL;
+ const char *sts_endpoint = NULL;
+ const char *role_arn = NULL;
+ const char *external_id = NULL;
+ const char *profile = NULL;
+ char *session_name = NULL;
+ int key_prefix_len;
+ int key_max_len;
+
+ /* Provider managed dependencies */
+ struct flb_aws_provider *aws_provider = NULL;
+ struct flb_aws_provider *base_aws_provider = NULL;
+ struct flb_tls *cred_tls = NULL;
+ struct flb_tls *sts_tls = NULL;
+
+ /* Config keys */
+ key_prefix_len = strlen(config_key_prefix);
+ key_max_len = key_prefix_len + 12; /* max length of
+ "region", "sts_endpoint", "role_arn",
+ "external_id" */
+
+ /* Evaluate full config keys */
+ config_key_region = flb_sds_create_len(config_key_prefix, key_max_len);
+ strcpy(config_key_region + key_prefix_len, "region");
+ config_key_sts_endpoint = flb_sds_create_len(config_key_prefix, key_max_len);
+ strcpy(config_key_sts_endpoint + key_prefix_len, "sts_endpoint");
+ config_key_role_arn = flb_sds_create_len(config_key_prefix, key_max_len);
+ strcpy(config_key_role_arn + key_prefix_len, "role_arn");
+ config_key_external_id = flb_sds_create_len(config_key_prefix, key_max_len);
+ strcpy(config_key_external_id + key_prefix_len, "external_id");
+ config_key_profile = flb_sds_create_len(config_key_prefix, key_max_len);
+ strcpy(config_key_profile + key_prefix_len, "profile");
+
+ /* AWS provider needs a separate TLS instance */
+ cred_tls = flb_tls_create(FLB_TLS_CLIENT_MODE,
+ FLB_TRUE,
+ ins->tls_debug,
+ ins->tls_vhost,
+ ins->tls_ca_path,
+ ins->tls_ca_file,
+ ins->tls_crt_file,
+ ins->tls_key_file,
+ ins->tls_key_passwd);
+ if (!cred_tls) {
+ flb_plg_error(ins, "Failed to create TLS instance for AWS Provider");
+ flb_errno();
+ goto error;
+ }
+
+ region = flb_output_get_property(config_key_region, ins);
+ if (!region) {
+ flb_plg_error(ins, "aws_auth enabled but %s not set", config_key_region);
+ goto error;
+ }
+
+ /* Use null sts_endpoint if none provided */
+ sts_endpoint = flb_output_get_property(config_key_sts_endpoint, ins);
+ /* Get the profile from configuration */
+ profile = flb_output_get_property(config_key_profile, ins);
+ aws_provider = flb_standard_chain_provider_create(config,
+ cred_tls,
+ (char *) region,
+ (char *) sts_endpoint,
+ NULL,
+ flb_aws_client_generator(),
+ profile);
+ if (!aws_provider) {
+ flb_plg_error(ins, "Failed to create AWS Credential Provider");
+ goto error;
+ }
+
+ role_arn = flb_output_get_property(config_key_role_arn, ins);
+ if (role_arn) {
+ /* Use the STS Provider */
+ base_aws_provider = aws_provider;
+ external_id = flb_output_get_property(config_key_external_id, ins);
+
+ session_name = flb_sts_session_name();
+ if (!session_name) {
+ flb_plg_error(ins, "Failed to generate aws iam role "
+ "session name");
+ goto error;
+ }
+
+ /* STS provider needs yet another separate TLS instance */
+ sts_tls = flb_tls_create(FLB_TLS_CLIENT_MODE,
+ FLB_TRUE,
+ ins->tls_debug,
+ ins->tls_vhost,
+ ins->tls_ca_path,
+ ins->tls_ca_file,
+ ins->tls_crt_file,
+ ins->tls_key_file,
+ ins->tls_key_passwd);
+ if (!sts_tls) {
+ flb_plg_error(ins, "Failed to create TLS instance for AWS STS Credential "
+ "Provider");
+ flb_errno();
+ goto error;
+ }
+
+ aws_provider = flb_sts_provider_create(config,
+ sts_tls,
+ base_aws_provider,
+ (char *) external_id,
+ (char *) role_arn,
+ session_name,
+ (char *) region,
+ (char *) sts_endpoint,
+ NULL,
+ flb_aws_client_generator());
+ if (!aws_provider) {
+ flb_plg_error(ins, "Failed to create AWS STS Credential "
+ "Provider");
+ goto error;
+ }
+ }
+
+ /* initialize credentials in sync mode */
+ aws_provider->provider_vtable->sync(aws_provider);
+ aws_provider->provider_vtable->init(aws_provider);
+
+ /* set back to async */
+ aws_provider->provider_vtable->async(aws_provider);
+
+ /* store dependencies in aws_provider for managed cleanup */
+ aws_provider->base_aws_provider = base_aws_provider;
+ aws_provider->cred_tls = cred_tls;
+ aws_provider->sts_tls = sts_tls;
+
+ goto cleanup;
+
+error:
+ if (aws_provider) {
+ /* disconnect dependencies */
+ aws_provider->base_aws_provider = NULL;
+ aws_provider->cred_tls = NULL;
+ aws_provider->sts_tls = NULL;
+ /* destroy */
+ flb_aws_provider_destroy(aws_provider);
+ }
+ /* free dependencies */
+ if (base_aws_provider) {
+ flb_aws_provider_destroy(base_aws_provider);
+ }
+ if (cred_tls) {
+ flb_tls_destroy(cred_tls);
+ }
+ if (sts_tls) {
+ flb_tls_destroy(sts_tls);
+ }
+ aws_provider = NULL;
+
+cleanup:
+ if (config_key_region) {
+ flb_sds_destroy(config_key_region);
+ }
+ if (config_key_sts_endpoint) {
+ flb_sds_destroy(config_key_sts_endpoint);
+ }
+ if (config_key_role_arn) {
+ flb_sds_destroy(config_key_role_arn);
+ }
+ if (config_key_external_id) {
+ flb_sds_destroy(config_key_external_id);
+ }
+ if (session_name) {
+ flb_free(session_name);
+ }
+
+ return aws_provider;
+}
+
+static struct flb_aws_provider *standard_chain_create(struct flb_config
+ *config,
+ struct flb_tls *tls,
+ char *region,
+ char *sts_endpoint,
+ char *proxy,
+ struct
+ flb_aws_client_generator
+ *generator,
+ int eks_irsa,
+ char *profile)
+{
+ struct flb_aws_provider *sub_provider;
+ struct flb_aws_provider *provider;
+ struct flb_aws_provider_chain *implementation;
+
+ provider = flb_calloc(1, sizeof(struct flb_aws_provider));
+
+ if (!provider) {
+ flb_errno();
+ return NULL;
+ }
+
+ pthread_mutex_init(&provider->lock, NULL);
+
+ implementation = flb_calloc(1, sizeof(struct flb_aws_provider_chain));
+
+ if (!implementation) {
+ flb_errno();
+ flb_free(provider);
+ return NULL;
+ }
+
+ provider->provider_vtable = &standard_chain_provider_vtable;
+ provider->implementation = implementation;
+
+ /* Create chain of providers */
+ mk_list_init(&implementation->sub_providers);
+
+ sub_provider = flb_aws_env_provider_create();
+ if (!sub_provider) {
+ /* Env provider will only fail creation if a memory alloc failed */
+ flb_aws_provider_destroy(provider);
+ return NULL;
+ }
+ flb_debug("[aws_credentials] Initialized Env Provider in standard chain");
+
+ mk_list_add(&sub_provider->_head, &implementation->sub_providers);
+
+ flb_debug("[aws_credentials] creating profile %s provider", profile);
+ sub_provider = flb_profile_provider_create(profile);
+ if (sub_provider) {
+ /* Profile provider can fail if HOME env var is not set */;
+ mk_list_add(&sub_provider->_head, &implementation->sub_providers);
+ flb_debug("[aws_credentials] Initialized AWS Profile Provider in "
+ "standard chain");
+ }
+
+ if (eks_irsa == FLB_TRUE) {
+ sub_provider = flb_eks_provider_create(config, tls, region, sts_endpoint, proxy, generator);
+ if (sub_provider) {
+ /* EKS provider can fail if we are not running in k8s */;
+ mk_list_add(&sub_provider->_head, &implementation->sub_providers);
+ flb_debug("[aws_credentials] Initialized EKS Provider in standard chain");
+ }
+ }
+
+ sub_provider = flb_ecs_provider_create(config, generator);
+ if (sub_provider) {
+ /* ECS Provider will fail creation if we are not running in ECS */
+ mk_list_add(&sub_provider->_head, &implementation->sub_providers);
+ flb_debug("[aws_credentials] Initialized ECS Provider in standard chain");
+ }
+
+ sub_provider = flb_ec2_provider_create(config, generator);
+ if (!sub_provider) {
+ /* EC2 provider will only fail creation if a memory alloc failed */
+ flb_aws_provider_destroy(provider);
+ return NULL;
+ }
+ mk_list_add(&sub_provider->_head, &implementation->sub_providers);
+ flb_debug("[aws_credentials] Initialized EC2 Provider in standard chain");
+
+ return provider;
+}
+
+/* Environment Provider */
+struct flb_aws_credentials *get_credentials_fn_environment(struct
+ flb_aws_provider
+ *provider)
+{
+ char *access_key = NULL;
+ char *secret_key = NULL;
+ char *session_token = NULL;
+ struct flb_aws_credentials *creds = NULL;
+
+ flb_debug("[aws_credentials] Requesting credentials from the "
+ "env provider..");
+
+ access_key = getenv(AWS_ACCESS_KEY_ID);
+ if (!access_key || strlen(access_key) <= 0) {
+ return NULL;
+ }
+
+ secret_key = getenv(AWS_SECRET_ACCESS_KEY);
+ if (!secret_key || strlen(secret_key) <= 0) {
+ return NULL;
+ }
+
+ creds = flb_calloc(1, sizeof(struct flb_aws_credentials));
+ if (!creds) {
+ flb_errno();
+ return NULL;
+ }
+
+ creds->access_key_id = flb_sds_create(access_key);
+ if (!creds->access_key_id) {
+ flb_aws_credentials_destroy(creds);
+ flb_errno();
+ return NULL;
+ }
+
+ creds->secret_access_key = flb_sds_create(secret_key);
+ if (!creds->secret_access_key) {
+ flb_aws_credentials_destroy(creds);
+ flb_errno();
+ return NULL;
+ }
+
+ session_token = getenv(AWS_SESSION_TOKEN);
+ if (session_token && strlen(session_token) > 0) {
+ creds->session_token = flb_sds_create(session_token);
+ if (!creds->session_token) {
+ flb_aws_credentials_destroy(creds);
+ flb_errno();
+ return NULL;
+ }
+ } else {
+ creds->session_token = NULL;
+ }
+
+ return creds;
+
+}
+
+int refresh_env(struct flb_aws_provider *provider)
+{
+ char *access_key = NULL;
+ char *secret_key = NULL;
+
+ access_key = getenv(AWS_ACCESS_KEY_ID);
+ if (!access_key || strlen(access_key) <= 0) {
+ return -1;
+ }
+
+ secret_key = getenv(AWS_SECRET_ACCESS_KEY);
+ if (!secret_key || strlen(secret_key) <= 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * For the env provider, refresh simply checks if the environment
+ * variables are available.
+ */
+int refresh_fn_environment(struct flb_aws_provider *provider)
+{
+ flb_debug("[aws_credentials] Refresh called on the env provider");
+
+ return refresh_env(provider);
+}
+
+int init_fn_environment(struct flb_aws_provider *provider)
+{
+ flb_debug("[aws_credentials] Init called on the env provider");
+
+ return refresh_env(provider);
+}
+
+/*
+ * sync and async are no-ops for the env provider because it does not make
+ * network IO calls
+ */
+void sync_fn_environment(struct flb_aws_provider *provider)
+{
+ return;
+}
+
+void async_fn_environment(struct flb_aws_provider *provider)
+{
+ return;
+}
+
+void upstream_set_fn_environment(struct flb_aws_provider *provider,
+ struct flb_output_instance *ins)
+{
+ return;
+}
+
+/* Destroy is a no-op for the env provider */
+void destroy_fn_environment(struct flb_aws_provider *provider) {
+ return;
+}
+
+static struct flb_aws_provider_vtable environment_provider_vtable = {
+ .get_credentials = get_credentials_fn_environment,
+ .init = init_fn_environment,
+ .refresh = refresh_fn_environment,
+ .destroy = destroy_fn_environment,
+ .sync = sync_fn_environment,
+ .async = async_fn_environment,
+ .upstream_set = upstream_set_fn_environment,
+};
+
+struct flb_aws_provider *flb_aws_env_provider_create() {
+ struct flb_aws_provider *provider = flb_calloc(1, sizeof(
+ struct flb_aws_provider));
+
+ if (!provider) {
+ flb_errno();
+ return NULL;
+ }
+
+ provider->provider_vtable = &environment_provider_vtable;
+ provider->implementation = NULL;
+
+ return provider;
+}
+
+
+void flb_aws_credentials_destroy(struct flb_aws_credentials *creds)
+{
+ if (creds) {
+ if (creds->access_key_id) {
+ flb_sds_destroy(creds->access_key_id);
+ }
+ if (creds->secret_access_key) {
+ flb_sds_destroy(creds->secret_access_key);
+ }
+ if (creds->session_token) {
+ flb_sds_destroy(creds->session_token);
+ }
+
+ flb_free(creds);
+ }
+}
+
+void flb_aws_provider_destroy(struct flb_aws_provider *provider)
+{
+ if (provider) {
+ if (provider->implementation) {
+ provider->provider_vtable->destroy(provider);
+ }
+
+ pthread_mutex_destroy(&provider->lock);
+
+ /* free managed dependencies */
+ if (provider->base_aws_provider) {
+ flb_aws_provider_destroy(provider->base_aws_provider);
+ }
+ if (provider->cred_tls) {
+ flb_tls_destroy(provider->cred_tls);
+ }
+ if (provider->sts_tls) {
+ flb_tls_destroy(provider->sts_tls);
+ }
+
+ flb_free(provider);
+ }
+}
+
+time_t timestamp_to_epoch(const char *timestamp)
+{
+ struct tm tm = {0};
+ time_t seconds;
+ int r;
+
+ r = sscanf(timestamp, "%d-%d-%dT%d:%d:%dZ", &tm.tm_year, &tm.tm_mon,
+ &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
+ if (r != 6) {
+ return -1;
+ }
+
+ tm.tm_year -= 1900;
+ tm.tm_mon -= 1;
+ tm.tm_isdst = -1;
+ seconds = timegm(&tm);
+ if (seconds < 0) {
+ return -1;
+ }
+
+ return seconds;
+}
+
+time_t flb_aws_cred_expiration(const char *timestamp)
+{
+ time_t now;
+ time_t expiration = timestamp_to_epoch(timestamp);
+ if (expiration < 0) {
+ flb_warn("[aws_credentials] Could not parse expiration: %s", timestamp);
+ return -1;
+ }
+ /*
+ * Sanity check - expiration should be ~10 minutes to 12 hours in the future
+ * (> 12 hours is impossible with the current APIs and would likely indicate
+ * a bug in how this code processes timestamps.)
+ */
+ now = time(NULL);
+ if (expiration < (now + FIVE_MINUTES)) {
+ flb_warn("[aws_credentials] Credential expiration '%s' is less than "
+ "5 minutes in the future.",
+ timestamp);
+ }
+ if (expiration > (now + TWELVE_HOURS)) {
+ flb_warn("[aws_credentials] Credential expiration '%s' is greater than "
+ "12 hours in the future. This should not be possible.",
+ timestamp);
+ }
+ return expiration;
+}
+
+/*
+ * Fluent Bit is now multi-threaded and asynchonous with coros.
+ * The trylock prevents deadlock, and protects the provider
+ * when a cred refresh happens. The refresh frees and
+ * sets the shared cred cache, a double free could occur
+ * if two threads do it at the same exact time.
+ */
+
+/* Like a traditional try lock- it does not block if the lock is not obtained */
+int try_lock_provider(struct flb_aws_provider *provider)
+{
+ int ret = 0;
+ ret = pthread_mutex_trylock(&provider->lock);
+ if (ret != 0) {
+ return FLB_FALSE;
+ }
+ return FLB_TRUE;
+}
+
+void unlock_provider(struct flb_aws_provider *provider)
+{
+ pthread_mutex_unlock(&provider->lock);
+}
diff --git a/fluent-bit/src/aws/flb_aws_credentials_ec2.c b/fluent-bit/src/aws/flb_aws_credentials_ec2.c
new file mode 100644
index 000000000..5d2d8515c
--- /dev/null
+++ b/fluent-bit/src/aws/flb_aws_credentials_ec2.c
@@ -0,0 +1,371 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_http_client.h>
+#include <fluent-bit/flb_aws_credentials.h>
+#include <fluent-bit/flb_aws_util.h>
+#include <fluent-bit/flb_jsmn.h>
+#include <fluent-bit/aws/flb_aws_imds.h>
+
+#include <stdlib.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define AWS_IMDS_ROLE_PATH "/latest/meta-data/iam/security-credentials/"
+#define AWS_IMDS_ROLE_PATH_LEN 43
+
+struct flb_aws_provider_ec2;
+static int get_creds_ec2(struct flb_aws_provider_ec2 *implementation);
+static int ec2_credentials_request(struct flb_aws_provider_ec2
+ *implementation, char *cred_path);
+
+/* EC2 IMDS Provider */
+
+/*
+ * A provider that obtains credentials from EC2 IMDS.
+ */
+struct flb_aws_provider_ec2 {
+ struct flb_aws_credentials *creds;
+ time_t next_refresh;
+
+ /* upstream connection to IMDS */
+ struct flb_aws_client *client;
+
+ /* IMDS interface */
+ struct flb_aws_imds *imds_interface;
+};
+
+struct flb_aws_credentials *get_credentials_fn_ec2(struct flb_aws_provider
+ *provider)
+{
+ struct flb_aws_credentials *creds;
+ int refresh = FLB_FALSE;
+ struct flb_aws_provider_ec2 *implementation = provider->implementation;
+
+ flb_debug("[aws_credentials] Requesting credentials from the "
+ "EC2 provider..");
+
+ /* a negative next_refresh means that auto-refresh is disabled */
+ if (implementation->next_refresh > 0
+ && time(NULL) > implementation->next_refresh) {
+ refresh = FLB_TRUE;
+ }
+ if (!implementation->creds || refresh == FLB_TRUE) {
+ if (try_lock_provider(provider)) {
+ get_creds_ec2(implementation);
+ unlock_provider(provider);
+ }
+ }
+
+ if (!implementation->creds) {
+ /*
+ * We failed to lock the provider and creds are unset. This means that
+ * another co-routine is performing the refresh.
+ */
+ flb_warn("[aws_credentials] No cached credentials are available and "
+ "a credential refresh is already in progress. The current "
+ "co-routine will retry.");
+
+ return NULL;
+ }
+
+ creds = flb_calloc(1, sizeof(struct flb_aws_credentials));
+ if (!creds) {
+ flb_errno();
+ return NULL;
+ }
+
+ creds->access_key_id = flb_sds_create(implementation->creds->access_key_id);
+ if (!creds->access_key_id) {
+ flb_errno();
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+ }
+
+ creds->secret_access_key = flb_sds_create(implementation->creds->
+ secret_access_key);
+ if (!creds->secret_access_key) {
+ flb_errno();
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+ }
+
+ if (implementation->creds->session_token) {
+ creds->session_token = flb_sds_create(implementation->creds->
+ session_token);
+ if (!creds->session_token) {
+ flb_errno();
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+ }
+
+ } else {
+ creds->session_token = NULL;
+ }
+
+ return creds;
+}
+
+int refresh_fn_ec2(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_ec2 *implementation = provider->implementation;
+ int ret = -1;
+
+ flb_debug("[aws_credentials] Refresh called on the EC2 IMDS provider");
+ if (try_lock_provider(provider)) {
+ ret = get_creds_ec2(implementation);
+ unlock_provider(provider);
+ }
+ return ret;
+}
+
+int init_fn_ec2(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_ec2 *implementation = provider->implementation;
+ int ret = -1;
+
+ implementation->client->debug_only = FLB_TRUE;
+
+ flb_debug("[aws_credentials] Init called on the EC2 IMDS provider");
+ if (try_lock_provider(provider)) {
+ ret = get_creds_ec2(implementation);
+ unlock_provider(provider);
+ }
+
+ implementation->client->debug_only = FLB_FALSE;
+ return ret;
+}
+
+void sync_fn_ec2(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_ec2 *implementation = provider->implementation;
+
+ flb_debug("[aws_credentials] Sync called on the EC2 provider");
+ /* remove async flag */
+ flb_stream_disable_async_mode(&implementation->client->upstream->base);
+}
+
+void async_fn_ec2(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_ec2 *implementation = provider->implementation;
+
+ flb_debug("[aws_credentials] Async called on the EC2 provider");
+ /* add async flag */
+ flb_stream_enable_async_mode(&implementation->client->upstream->base);
+}
+
+void upstream_set_fn_ec2(struct flb_aws_provider *provider,
+ struct flb_output_instance *ins) {
+ struct flb_aws_provider_ec2 *implementation = provider->implementation;
+
+ flb_debug("[aws_credentials] upstream_set called on the EC2 provider");
+ /* Make sure TLS is set to false before setting upstream, then reset it */
+ ins->use_tls = FLB_FALSE;
+ flb_output_upstream_set(implementation->client->upstream, ins);
+ ins->use_tls = FLB_TRUE;
+}
+
+void destroy_fn_ec2(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_ec2 *implementation = provider->implementation;
+
+ if (implementation) {
+ if (implementation->creds) {
+ flb_aws_credentials_destroy(implementation->creds);
+ }
+
+ if (implementation->imds_interface) {
+ flb_aws_imds_destroy(implementation->imds_interface);
+ }
+
+ if (implementation->client) {
+ flb_aws_client_destroy(implementation->client);
+ }
+
+ flb_free(implementation);
+ provider->implementation = NULL;
+ }
+
+ return;
+}
+
+static struct flb_aws_provider_vtable ec2_provider_vtable = {
+ .get_credentials = get_credentials_fn_ec2,
+ .init = init_fn_ec2,
+ .refresh = refresh_fn_ec2,
+ .destroy = destroy_fn_ec2,
+ .sync = sync_fn_ec2,
+ .async = async_fn_ec2,
+ .upstream_set = upstream_set_fn_ec2,
+};
+
+struct flb_aws_provider *flb_ec2_provider_create(struct flb_config *config,
+ struct
+ flb_aws_client_generator
+ *generator)
+{
+ struct flb_aws_provider_ec2 *implementation;
+ struct flb_aws_provider *provider;
+ struct flb_upstream *upstream;
+
+ provider = flb_calloc(1, sizeof(struct flb_aws_provider));
+
+ if (!provider) {
+ flb_errno();
+ return NULL;
+ }
+
+ pthread_mutex_init(&provider->lock, NULL);
+
+ implementation = flb_calloc(1, sizeof(struct flb_aws_provider_ec2));
+
+ if (!implementation) {
+ flb_free(provider);
+ flb_errno();
+ return NULL;
+ }
+
+ provider->provider_vtable = &ec2_provider_vtable;
+ provider->implementation = implementation;
+
+ upstream = flb_upstream_create(config, FLB_AWS_IMDS_HOST, FLB_AWS_IMDS_PORT,
+ FLB_IO_TCP, NULL);
+ if (!upstream) {
+ flb_aws_provider_destroy(provider);
+ flb_debug("[aws_credentials] unable to connect to EC2 IMDS.");
+ return NULL;
+ }
+
+ /* IMDSv2 token request will timeout if hops = 1 and running within container */
+ upstream->base.net.connect_timeout = FLB_AWS_IMDS_TIMEOUT;
+ upstream->base.net.io_timeout = FLB_AWS_IMDS_TIMEOUT;
+ upstream->base.net.keepalive = FLB_FALSE; /* On timeout, the connection is broken */
+
+ implementation->client = generator->create();
+ if (!implementation->client) {
+ flb_aws_provider_destroy(provider);
+ flb_upstream_destroy(upstream);
+ flb_error("[aws_credentials] EC2 IMDS: client creation error");
+ return NULL;
+ }
+ implementation->client->name = "ec2_imds_provider_client";
+ implementation->client->has_auth = FLB_FALSE;
+ implementation->client->provider = NULL;
+ implementation->client->region = NULL;
+ implementation->client->service = NULL;
+ implementation->client->port = 80;
+ implementation->client->flags = 0;
+ implementation->client->proxy = NULL;
+ implementation->client->upstream = upstream;
+
+ /* Use default imds configuration */
+ implementation->imds_interface = flb_aws_imds_create(&flb_aws_imds_config_default,
+ implementation->client);
+ if (!implementation->imds_interface) {
+ flb_aws_provider_destroy(provider);
+ flb_error("[aws_credentials] EC2 IMDS configuration error");
+ return NULL;
+ }
+
+ return provider;
+}
+
+/* Requests creds from IMDSv1 and sets them on the provider */
+static int get_creds_ec2(struct flb_aws_provider_ec2 *implementation)
+{
+ int ret;
+ flb_sds_t instance_role;
+ size_t instance_role_len;
+ char *cred_path;
+ size_t cred_path_size;
+
+ flb_debug("[aws_credentials] requesting credentials from EC2 IMDS");
+
+ /* Get the name of the instance role */
+ ret = flb_aws_imds_request(implementation->imds_interface, AWS_IMDS_ROLE_PATH,
+ &instance_role, &instance_role_len);
+
+ if (ret < 0) {
+ return -1;
+ }
+
+ flb_debug("[aws_credentials] Requesting credentials for instance role %s",
+ instance_role);
+
+ /* Construct path where we will find the credentials */
+ cred_path_size = sizeof(char) * (AWS_IMDS_ROLE_PATH_LEN +
+ instance_role_len) + 1;
+ cred_path = flb_malloc(cred_path_size);
+ if (!cred_path) {
+ flb_sds_destroy(instance_role);
+ flb_errno();
+ return -1;
+ }
+
+ ret = snprintf(cred_path, cred_path_size, "%s%s", AWS_IMDS_ROLE_PATH,
+ instance_role);
+ if (ret < 0) {
+ flb_sds_destroy(instance_role);
+ flb_free(cred_path);
+ flb_errno();
+ return -1;
+ }
+
+ /* request creds */
+ ret = ec2_credentials_request(implementation, cred_path);
+
+ flb_sds_destroy(instance_role);
+ flb_free(cred_path);
+ return ret;
+
+}
+
+static int ec2_credentials_request(struct flb_aws_provider_ec2
+ *implementation, char *cred_path)
+{
+ int ret;
+ flb_sds_t credentials_response;
+ size_t credentials_response_len;
+ struct flb_aws_credentials *creds;
+ time_t expiration;
+
+ ret = flb_aws_imds_request(implementation->imds_interface, cred_path,
+ &credentials_response, &credentials_response_len);
+
+ if (ret < 0) {
+ return -1;
+ }
+
+ creds = flb_parse_http_credentials(credentials_response,
+ credentials_response_len,
+ &expiration);
+
+ if (creds == NULL) {
+ flb_sds_destroy(credentials_response);
+ return -1;
+ }
+
+ /* destroy existing credentials first */
+ flb_aws_credentials_destroy(implementation->creds);
+ implementation->creds = NULL;
+ /* set new creds */
+ implementation->creds = creds;
+ implementation->next_refresh = expiration - FLB_AWS_REFRESH_WINDOW;
+
+ flb_sds_destroy(credentials_response);
+ return 0;
+}
diff --git a/fluent-bit/src/aws/flb_aws_credentials_http.c b/fluent-bit/src/aws/flb_aws_credentials_http.c
new file mode 100644
index 000000000..c08b6b559
--- /dev/null
+++ b/fluent-bit/src/aws/flb_aws_credentials_http.c
@@ -0,0 +1,566 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_http_client.h>
+#include <fluent-bit/flb_aws_credentials.h>
+#include <fluent-bit/flb_aws_util.h>
+
+#include <fluent-bit/flb_jsmn.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define AWS_CREDENTIAL_RESPONSE_ACCESS_KEY "AccessKeyId"
+#define AWS_CREDENTIAL_RESPONSE_SECRET_KEY "SecretAccessKey"
+#define AWS_HTTP_RESPONSE_TOKEN "Token"
+#define AWS_CREDENTIAL_RESPONSE_EXPIRATION "Expiration"
+
+#define ECS_CREDENTIALS_HOST "169.254.170.2"
+#define ECS_CREDENTIALS_HOST_LEN 13
+#define ECS_CREDENTIALS_PATH_ENV_VAR "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
+
+
+/* Declarations */
+struct flb_aws_provider_http;
+static int http_credentials_request(struct flb_aws_provider_http
+ *implementation);
+
+
+/*
+ * HTTP Credentials Provider - retrieve credentials from a local http server
+ * Used to implement the ECS Credentials provider.
+ * Equivalent to:
+ * https://github.com/aws/aws-sdk-go/tree/master/aws/credentials/endpointcreds
+ */
+
+struct flb_aws_provider_http {
+ struct flb_aws_credentials *creds;
+ time_t next_refresh;
+
+ struct flb_aws_client *client;
+
+ /* Host and Path to request credentials */
+ flb_sds_t host;
+ flb_sds_t path;
+};
+
+
+struct flb_aws_credentials *get_credentials_fn_http(struct flb_aws_provider
+ *provider)
+{
+ struct flb_aws_credentials *creds = NULL;
+ int refresh = FLB_FALSE;
+ struct flb_aws_provider_http *implementation = provider->implementation;
+
+ flb_debug("[aws_credentials] Retrieving credentials from the "
+ "HTTP provider..");
+
+ /* a negative next_refresh means that auto-refresh is disabled */
+ if (implementation->next_refresh > 0
+ && time(NULL) > implementation->next_refresh) {
+ refresh = FLB_TRUE;
+ }
+ if (!implementation->creds || refresh == FLB_TRUE) {
+ if (try_lock_provider(provider)) {
+ http_credentials_request(implementation);
+ unlock_provider(provider);
+ }
+ }
+
+ if (!implementation->creds) {
+ /*
+ * We failed to lock the provider and creds are unset. This means that
+ * another co-routine is performing the refresh.
+ */
+ flb_warn("[aws_credentials] No cached credentials are available and "
+ "a credential refresh is already in progress. The current "
+ "co-routine will retry.");
+
+ return NULL;
+ }
+
+ creds = flb_calloc(1, sizeof(struct flb_aws_credentials));
+ if (!creds) {
+ flb_errno();
+ goto error;
+ }
+
+ creds->access_key_id = flb_sds_create(implementation->creds->access_key_id);
+ if (!creds->access_key_id) {
+ flb_errno();
+ goto error;
+ }
+
+ creds->secret_access_key = flb_sds_create(implementation->creds->
+ secret_access_key);
+ if (!creds->secret_access_key) {
+ flb_errno();
+ goto error;
+ }
+
+ if (implementation->creds->session_token) {
+ creds->session_token = flb_sds_create(implementation->creds->
+ session_token);
+ if (!creds->session_token) {
+ flb_errno();
+ goto error;
+ }
+
+ } else {
+ creds->session_token = NULL;
+ }
+
+ return creds;
+
+error:
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+}
+
+int refresh_fn_http(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_http *implementation = provider->implementation;
+ int ret = -1;
+ flb_debug("[aws_credentials] Refresh called on the http provider");
+
+ if (try_lock_provider(provider)) {
+ ret = http_credentials_request(implementation);
+ unlock_provider(provider);
+ }
+ return ret;
+}
+
+int init_fn_http(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_http *implementation = provider->implementation;
+ int ret = -1;
+ flb_debug("[aws_credentials] Init called on the http provider");
+
+ implementation->client->debug_only = FLB_TRUE;
+
+ if (try_lock_provider(provider)) {
+ ret = http_credentials_request(implementation);
+ unlock_provider(provider);
+ }
+
+ implementation->client->debug_only = FLB_FALSE;
+
+ return ret;
+}
+
+void sync_fn_http(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_http *implementation = provider->implementation;
+
+ flb_debug("[aws_credentials] Sync called on the http provider");
+ /* remove async flag */
+ flb_stream_disable_async_mode(&implementation->client->upstream->base);
+}
+
+void async_fn_http(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_http *implementation = provider->implementation;
+
+ flb_debug("[aws_credentials] Async called on the http provider");
+ /* add async flag */
+ flb_stream_enable_async_mode(&implementation->client->upstream->base);
+}
+
+void upstream_set_fn_http(struct flb_aws_provider *provider,
+ struct flb_output_instance *ins) {
+ struct flb_aws_provider_http *implementation = provider->implementation;
+
+ flb_debug("[aws_credentials] upstream_set called on the http provider");
+ /* Make sure TLS is set to false before setting upstream, then reset it */
+ ins->use_tls = FLB_FALSE;
+ flb_output_upstream_set(implementation->client->upstream, ins);
+ ins->use_tls = FLB_TRUE;
+}
+
+void destroy_fn_http(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_http *implementation = provider->implementation;
+
+ if (implementation) {
+ if (implementation->creds) {
+ flb_aws_credentials_destroy(implementation->creds);
+ }
+
+ if (implementation->client) {
+ flb_aws_client_destroy(implementation->client);
+ }
+
+ if (implementation->host) {
+ flb_sds_destroy(implementation->host);
+ }
+
+ if (implementation->path) {
+ flb_sds_destroy(implementation->path);
+ }
+
+ flb_free(implementation);
+ provider->implementation = NULL;
+ }
+
+ return;
+}
+
+static struct flb_aws_provider_vtable http_provider_vtable = {
+ .get_credentials = get_credentials_fn_http,
+ .init = init_fn_http,
+ .refresh = refresh_fn_http,
+ .destroy = destroy_fn_http,
+ .sync = sync_fn_http,
+ .async = async_fn_http,
+ .upstream_set = upstream_set_fn_http,
+};
+
+struct flb_aws_provider *flb_http_provider_create(struct flb_config *config,
+ flb_sds_t host,
+ flb_sds_t path,
+ struct
+ flb_aws_client_generator
+ *generator)
+{
+ struct flb_aws_provider_http *implementation = NULL;
+ struct flb_aws_provider *provider = NULL;
+ struct flb_upstream *upstream = NULL;
+
+ flb_debug("[aws_credentials] Configuring HTTP provider with %s:80%s",
+ host, path);
+
+ provider = flb_calloc(1, sizeof(struct flb_aws_provider));
+
+ if (!provider) {
+ flb_errno();
+ return NULL;
+ }
+
+ pthread_mutex_init(&provider->lock, NULL);
+
+ implementation = flb_calloc(1, sizeof(struct flb_aws_provider_http));
+
+ if (!implementation) {
+ flb_free(provider);
+ flb_errno();
+ return NULL;
+ }
+
+ provider->provider_vtable = &http_provider_vtable;
+ provider->implementation = implementation;
+
+ implementation->host = host;
+ implementation->path = path;
+
+ upstream = flb_upstream_create(config, host, 80, FLB_IO_TCP, NULL);
+
+ if (!upstream) {
+ flb_aws_provider_destroy(provider);
+ flb_error("[aws_credentials] HTTP Provider: connection initialization "
+ "error");
+ return NULL;
+ }
+
+ upstream->base.net.connect_timeout = FLB_AWS_CREDENTIAL_NET_TIMEOUT;
+
+ implementation->client = generator->create();
+ if (!implementation->client) {
+ flb_aws_provider_destroy(provider);
+ flb_upstream_destroy(upstream);
+ flb_error("[aws_credentials] HTTP Provider: client creation error");
+ return NULL;
+ }
+ implementation->client->name = "http_provider_client";
+ implementation->client->has_auth = FLB_FALSE;
+ implementation->client->provider = NULL;
+ implementation->client->region = NULL;
+ implementation->client->service = NULL;
+ implementation->client->port = 80;
+ implementation->client->flags = 0;
+ implementation->client->proxy = NULL;
+ implementation->client->upstream = upstream;
+
+ return provider;
+}
+
+/*
+ * ECS Provider
+ * The ECS Provider is just a wrapper around the HTTP Provider
+ * with the ECS credentials endpoint.
+ */
+
+ struct flb_aws_provider *flb_ecs_provider_create(struct flb_config *config,
+ struct
+ flb_aws_client_generator
+ *generator)
+{
+ flb_sds_t host = NULL;
+ flb_sds_t path = NULL;
+ char *path_var = NULL;
+
+ host = flb_sds_create_len(ECS_CREDENTIALS_HOST, ECS_CREDENTIALS_HOST_LEN);
+ if (!host) {
+ flb_errno();
+ return NULL;
+ }
+
+ path_var = getenv(ECS_CREDENTIALS_PATH_ENV_VAR);
+ if (path_var && strlen(path_var) > 0) {
+ path = flb_sds_create(path_var);
+ if (!path) {
+ flb_errno();
+ flb_free(host);
+ return NULL;
+ }
+
+ return flb_http_provider_create(config, host, path, generator);
+ } else {
+ flb_debug("[aws_credentials] Not initializing ECS Provider because"
+ " %s is not set", ECS_CREDENTIALS_PATH_ENV_VAR);
+ flb_sds_destroy(host);
+ return NULL;
+ }
+
+}
+
+static int http_credentials_request(struct flb_aws_provider_http
+ *implementation)
+{
+ char *response = NULL;
+ size_t response_len;
+ time_t expiration;
+ struct flb_aws_credentials *creds = NULL;
+ struct flb_aws_client *client = implementation->client;
+ struct flb_http_client *c = NULL;
+
+ c = client->client_vtable->request(client, FLB_HTTP_GET,
+ implementation->path, NULL, 0,
+ NULL, 0);
+
+ if (!c || c->resp.status != 200) {
+ flb_debug("[aws_credentials] http credentials request failed");
+ if (c) {
+ flb_http_client_destroy(c);
+ }
+ return -1;
+ }
+
+ response = c->resp.payload;
+ response_len = c->resp.payload_size;
+
+ creds = flb_parse_http_credentials(response, response_len, &expiration);
+ if (!creds) {
+ flb_http_client_destroy(c);
+ return -1;
+ }
+
+ /* destroy existing credentials */
+ flb_aws_credentials_destroy(implementation->creds);
+ implementation->creds = NULL;
+
+ implementation->creds = creds;
+ implementation->next_refresh = expiration - FLB_AWS_REFRESH_WINDOW;
+ flb_http_client_destroy(c);
+ return 0;
+}
+
+/*
+ * All HTTP credentials endpoints (IMDS, ECS, custom) follow the same spec:
+ * {
+ * "AccessKeyId": "ACCESS_KEY_ID",
+ * "Expiration": "2019-12-18T21:27:58Z",
+ * "SecretAccessKey": "SECRET_ACCESS_KEY",
+ * "Token": "SECURITY_TOKEN_STRING"
+ * }
+ * (some implementations (IMDS) have additional fields)
+ * Returns NULL if any part of parsing was unsuccessful.
+ */
+struct flb_aws_credentials *flb_parse_http_credentials(char *response,
+ size_t response_len,
+ time_t *expiration)
+{
+ return flb_parse_json_credentials(response, response_len, AWS_HTTP_RESPONSE_TOKEN,
+ expiration);
+}
+
+struct flb_aws_credentials *flb_parse_json_credentials(char *response,
+ size_t response_len,
+ char* session_token_field,
+ time_t *expiration)
+{
+ jsmntok_t *tokens = NULL;
+ const jsmntok_t *t = NULL;
+ char *current_token = NULL;
+ jsmn_parser parser;
+ int tokens_size = 50;
+ size_t size;
+ int ret;
+ struct flb_aws_credentials *creds = NULL;
+ int i = 0;
+ int len;
+ flb_sds_t tmp;
+
+ /*
+ * Remove/reset existing value of expiration.
+ * Expiration should be in the response, but it is not
+ * strictly speaking needed. Fluent Bit logs a warning if it is missing.
+ */
+ *expiration = -1;
+
+ jsmn_init(&parser);
+
+ size = sizeof(jsmntok_t) * tokens_size;
+ tokens = flb_calloc(1, size);
+ if (!tokens) {
+ goto error;
+ }
+
+ ret = jsmn_parse(&parser, response, response_len,
+ tokens, tokens_size);
+
+ if (ret == JSMN_ERROR_INVAL || ret == JSMN_ERROR_PART) {
+ flb_error("[aws_credentials] Could not parse credentials response"
+ " - invalid JSON.");
+ goto error;
+ }
+
+ /* Shouldn't happen, but just in case, check for too many tokens error */
+ if (ret == JSMN_ERROR_NOMEM) {
+ flb_error("[aws_credentials] Could not parse credentials response"
+ " - response contained more tokens than expected.");
+ goto error;
+ }
+
+ /* return value is number of tokens parsed */
+ tokens_size = ret;
+
+ creds = flb_calloc(1, sizeof(struct flb_aws_credentials));
+ if (!creds) {
+ flb_errno();
+ goto error;
+ }
+
+ /*
+ * jsmn will create an array of tokens like:
+ * key, value, key, value
+ */
+ while (i < (tokens_size - 1)) {
+ t = &tokens[i];
+
+ if (t->start == -1 || t->end == -1 || (t->start == 0 && t->end == 0)) {
+ break;
+ }
+
+ if (t->type == JSMN_STRING) {
+ current_token = &response[t->start];
+ len = t->end - t->start;
+
+ if (strncmp(current_token, AWS_CREDENTIAL_RESPONSE_ACCESS_KEY, len) == 0)
+ {
+ i++;
+ t = &tokens[i];
+ current_token = &response[t->start];
+ len = t->end - t->start;
+ if (creds->access_key_id != NULL) {
+ flb_error("Trying to double allocate access_key_id");
+ goto error;
+ }
+ creds->access_key_id = flb_sds_create_len(current_token, len);
+ if (!creds->access_key_id) {
+ flb_errno();
+ goto error;
+ }
+ continue;
+ }
+ if (strncmp(current_token, AWS_CREDENTIAL_RESPONSE_SECRET_KEY, len) == 0)
+ {
+ i++;
+ t = &tokens[i];
+ current_token = &response[t->start];
+ len = t->end - t->start;
+ if (creds->secret_access_key != NULL) {
+ flb_error("Trying to double allocate secret_access_key");
+ goto error;
+ }
+ creds->secret_access_key = flb_sds_create_len(current_token,
+ len);
+ if (!creds->secret_access_key) {
+ flb_errno();
+ goto error;
+ }
+ continue;
+ }
+ if (strncmp(current_token, session_token_field, len) == 0) {
+ i++;
+ t = &tokens[i];
+ current_token = &response[t->start];
+ len = t->end - t->start;
+ if (creds->session_token != NULL) {
+ flb_error("Trying to double allocate session_token");
+ goto error;
+ }
+ creds->session_token = flb_sds_create_len(current_token, len);
+ if (!creds->session_token) {
+ flb_errno();
+ goto error;
+ }
+ continue;
+ }
+ if (strncmp(current_token, AWS_CREDENTIAL_RESPONSE_EXPIRATION, len) == 0)
+ {
+ i++;
+ t = &tokens[i];
+ current_token = &response[t->start];
+ len = t->end - t->start;
+ tmp = flb_sds_create_len(current_token, len);
+ if (!tmp) {
+ flb_errno();
+ goto error;
+ }
+ *expiration = flb_aws_cred_expiration(tmp);
+ flb_sds_destroy(tmp);
+ if (*expiration < 0) {
+ flb_warn("[aws_credentials] '%s' was invalid or "
+ "could not be parsed. Disabling auto-refresh of "
+ "credentials.", AWS_CREDENTIAL_RESPONSE_EXPIRATION);
+ }
+ }
+ }
+
+ i++;
+ }
+
+ if (creds->access_key_id == NULL) {
+ flb_error("[aws_credentials] Missing %s field in"
+ "credentials response", AWS_CREDENTIAL_RESPONSE_ACCESS_KEY);
+ goto error;
+ }
+
+ if (creds->secret_access_key == NULL) {
+ flb_error("[aws_credentials] Missing %s field in"
+ "credentials response", AWS_CREDENTIAL_RESPONSE_SECRET_KEY);
+ goto error;
+ }
+
+ flb_free(tokens);
+ return creds;
+
+error:
+ flb_aws_credentials_destroy(creds);
+ flb_free(tokens);
+ return NULL;
+}
diff --git a/fluent-bit/src/aws/flb_aws_credentials_log.h b/fluent-bit/src/aws/flb_aws_credentials_log.h
new file mode 100644
index 000000000..6e9f0806d
--- /dev/null
+++ b/fluent-bit/src/aws/flb_aws_credentials_log.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2021 The Fluent Bit Authors
+ *
+ * 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 FLB_AWS_CREDENTIALS_LOG_H
+
+#define FLB_AWS_CREDENTIALS_LOG_H
+
+#include <fluent-bit/flb_log.h>
+
+#define AWS_CREDS_ERROR(format, ...) flb_error("[aws_credentials] " format, ##__VA_ARGS__)
+#define AWS_CREDS_WARN(format, ...) flb_warn("[aws_credentials] " format, ##__VA_ARGS__)
+#define AWS_CREDS_DEBUG(format, ...) flb_debug("[aws_credentials] " format, ##__VA_ARGS__)
+
+#define AWS_CREDS_ERROR_OR_DEBUG(debug_only, format, ...) do {\
+ if (debug_only == FLB_TRUE) {\
+ AWS_CREDS_DEBUG(format, ##__VA_ARGS__);\
+ }\
+ else {\
+ AWS_CREDS_ERROR(format, ##__VA_ARGS__);\
+ }\
+} while (0)
+
+#endif
diff --git a/fluent-bit/src/aws/flb_aws_credentials_process.c b/fluent-bit/src/aws/flb_aws_credentials_process.c
new file mode 100644
index 000000000..44c024ca7
--- /dev/null
+++ b/fluent-bit/src/aws/flb_aws_credentials_process.c
@@ -0,0 +1,783 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2021 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_aws_credentials.h>
+
+#include "flb_aws_credentials_log.h"
+
+#include <fluent-bit/flb_compat.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_pipe.h>
+#include <fluent-bit/flb_time.h>
+
+#include <fcntl.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+
+#define DEV_NULL "/dev/null"
+
+#define MS_PER_SEC 1000
+#define MICROS_PER_MS 1000
+#define NS_PER_MS 1000000
+
+#define CREDENTIAL_PROCESS_TIMEOUT_MS 60000
+#define CREDENTIAL_PROCESS_BUFFER_SIZE 8 * 1024
+
+#define WAITPID_POLL_FREQUENCY_MS 20
+#define WAITPID_TIMEOUT_MS 10 * WAITPID_POLL_FREQUENCY_MS
+
+#define CREDENTIAL_PROCESS_RESPONSE_SESSION_TOKEN "SessionToken"
+
+/* Declarations */
+struct token_array;
+static int new_token_array(struct token_array *arr, int cap);
+static int append_token(struct token_array *arr, char* elem);
+
+struct readbuf;
+static int new_readbuf(struct readbuf* buf, int cap);
+
+static int get_monotonic_time(struct flb_time* tm);
+
+static char* ltrim(char* input);
+static int scan_credential_process_token_quoted(char *input);
+static int scan_credential_process_token_unquoted(char *input);
+static int credential_process_token_count(char* process);
+static int parse_credential_process_token(char **input, char** out_token);
+
+static int read_until_block(char* name, flb_pipefd_t fd, struct readbuf* buf);
+static int waitpid_timeout(char* name, pid_t pid, int* wstatus);
+
+struct process;
+static int new_process(struct process* p, char** args);
+static void exec_process_child(struct process* p);
+static int exec_process(struct process* p);
+static int read_from_process(struct process* p, struct readbuf* buf);
+static int wait_process(struct process* p);
+static void destroy_process(struct process* p);
+/* End Declarations */
+
+struct token_array {
+ char** tokens;
+ int len;
+ int cap;
+};
+
+/*
+ * Initializes a new token array with the given capacity.
+ * Returns 0 on success and < 0 on failure.
+ * The caller is responsible for calling `flb_free(arr->tokens)`.
+ */
+static int new_token_array(struct token_array *arr, int cap)
+{
+ *arr = (struct token_array) { .len = 0, .cap = cap };
+ arr->tokens = flb_malloc(cap * sizeof(char*));
+ if (!arr->tokens) {
+ flb_errno();
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Appends the given token to the array, if there is capacity.
+ * Returns 0 on success and < 0 on failure.
+ */
+static int append_token(struct token_array *arr, char* token)
+{
+ if (arr->len >= arr->cap) {
+ /* This means there is a bug in credential_process_token_count. */
+ AWS_CREDS_ERROR("append_token called on full token_array");
+ return -1;
+ }
+
+ (arr->tokens)[arr->len] = token;
+ arr->len++;
+ return 0;
+}
+
+struct readbuf {
+ char* buf;
+ int len;
+ int cap;
+};
+
+/*
+ * Initializes a new buffer with the given capacity.
+ * Returns 0 on success and < 0 on failure.
+ * The caller is responsible for calling `flb_free(buf->buf)`.
+ */
+static int new_readbuf(struct readbuf* buf, int cap)
+{
+ *buf = (struct readbuf) { .len = 0, .cap = cap };
+ buf->buf = flb_malloc(cap * sizeof(char));
+ if (!buf->buf) {
+ flb_errno();
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Fetches the current time from the monotonic clock.
+ * Returns 0 on success and < 0 on failure.
+ * This is useful for calculating deadlines that are not sensitive to changes
+ * in the system clock.
+ */
+static int get_monotonic_time(struct flb_time* tm)
+{
+ struct timespec ts;
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
+ flb_errno();
+ return -1;
+ }
+ flb_time_set(tm, ts.tv_sec, ts.tv_nsec);
+ return 0;
+}
+
+/*
+ * Skips over any leading spaces in the input string, returning the remainder.
+ * If the entire string is consumed, returns the empty string (not NULL).
+ */
+static char* ltrim(char* input)
+{
+ while (*input == ' ') {
+ input++;
+ }
+ return input;
+}
+
+/*
+ * Scans the unquoted token string at the start of the input string.
+ * The input must be the start of an unquoted token.
+ * Returns the token length on success, and < 0 on failure.
+ * This function does not add a null terminator to the token.
+ * The token length is the index where the null terminator must be placed.
+ * If the entire input is consumed, returns the length of the input string
+ * (excluding the null terminator).
+ */
+static int scan_credential_process_token_unquoted(char *input)
+{
+ int i;
+
+ for (i = 0; input[i] != ' '; i++) {
+ if (input[i] == '\0') {
+ break;
+ }
+ if (input[i] == '"') {
+ AWS_CREDS_ERROR("unexpected quote in credential_process");
+ return -1;
+ }
+ }
+
+ return i;
+}
+
+/*
+ * Scans the quoted token at the start of the input string.
+ * The input must be the string after the opening quote.
+ * Returns the token length on success, and < 0 on failure.
+ * This function does not add a null terminator to the token.
+ * The token length is the index where the null terminator must be placed.
+ */
+static int scan_credential_process_token_quoted(char *input)
+{
+ int i;
+
+ for (i = 0; input[i] != '"'; i++) {
+ if (input[i] == '\0') {
+ AWS_CREDS_ERROR("unterminated quote in credential_process");
+ return -1;
+ }
+ }
+
+ if (input[i+1] != '\0' && input[i+1] != ' ') {
+ AWS_CREDS_ERROR("unexpected character %c after closing quote in "
+ "credential_process", input[i+1]);
+ return -1;
+ }
+
+ return i;
+}
+
+/*
+ * Counts the number of tokens in the input string, which is assumed to be the
+ * credential_process from the config file.
+ * Returns < 0 on failure.
+ */
+static int credential_process_token_count(char* process)
+{
+ int count = 0;
+ int i;
+
+ while (1) {
+ process = ltrim(process);
+ if (*process == '\0') {
+ break;
+ }
+
+ count++;
+
+ if (*process == '"') {
+ process++;
+ i = scan_credential_process_token_quoted(process);
+ }
+ else {
+ i = scan_credential_process_token_unquoted(process);
+ }
+
+ if (i < 0) {
+ return -1;
+ }
+
+ process += i;
+ if (*process != '\0') {
+ process++;
+ }
+ }
+
+ return count;
+}
+
+/*
+ * Parses the input string, which is assumed to be the credential_process
+ * from the config file. The next token will be put in *out_token, and the
+ * remaining unprocessed input will be put in *input.
+ * Returns 0 on success and < 0 on failure.
+ * If there is an error, the value of *input and *out_token is not defined.
+ * If it succeeds and *out_token is NULL, then there are no more tokens,
+ * and this function should not be called again.
+ * *out_token will be some substring of the original *input, so it should not
+ * be freed.
+ */
+static int parse_credential_process_token(char** input, char** out_token)
+{
+ *out_token = NULL;
+ int i;
+
+ if (!*input) {
+ AWS_CREDS_ERROR("parse_credential_process_token called after yielding last token");
+ return -1;
+ }
+
+ *input = ltrim(*input);
+
+ if (**input == '\0') {
+ *input = NULL;
+ *out_token = NULL;
+ return 0;
+ }
+
+ if (**input == '"') {
+ (*input)++;
+ i = scan_credential_process_token_quoted(*input);
+ }
+ else {
+ i = scan_credential_process_token_unquoted(*input);
+ }
+
+ if (i < 0) {
+ return -1;
+ }
+
+ *out_token = *input;
+ *input += i;
+
+ if (**input != '\0') {
+ **input = '\0';
+ (*input)++;
+ }
+
+ return 0;
+}
+
+/* See <fluent-bit/flb_aws_credentials.h>. */
+char** parse_credential_process(char* input)
+{
+ char* next_token = NULL;
+ struct token_array arr = { 0 };
+ int token_count = credential_process_token_count(input);
+
+ if (token_count < 0) {
+ goto error;
+ }
+
+ /* Add one extra capacity for the NULL terminator. */
+ if (new_token_array(&arr, token_count + 1) < 0) {
+ goto error;
+ }
+
+ while (1) {
+ if (parse_credential_process_token(&input, &next_token) < 0) {
+ goto error;
+ }
+
+ if (!next_token) {
+ break;
+ }
+
+ if (append_token(&arr, next_token) < 0) {
+ goto error;
+ }
+ }
+
+ if (append_token(&arr, NULL) < 0) {
+ goto error;
+ }
+
+ return arr.tokens;
+
+error:
+ flb_free(arr.tokens);
+ return NULL;
+}
+
+/*
+ * Reads from the pipe into the buffer until no more input is available.
+ * If the input is exhausted (EOF), returns 0.
+ * If reading would block (EWOULDBLOCK/EAGAIN), returns > 0.
+ * If an error occurs or the buffer is full, returns < 0.
+ */
+static int read_until_block(char* name, flb_pipefd_t fd, struct readbuf* buf)
+{
+ int result = -1;
+
+ while (1) {
+ if (buf->len >= buf->cap) {
+ AWS_CREDS_ERROR("credential_process %s exceeded max buffer size", name);
+ return -1;
+ }
+
+ result = flb_pipe_r(fd, buf->buf + buf->len, buf->cap - buf->len);
+ if (result < 0) {
+ if (FLB_PIPE_WOULDBLOCK()) {
+ return 1;
+ }
+ flb_errno();
+ return -1;
+ }
+ else if (result == 0) { /* EOF */
+ return 0;
+ }
+ else {
+ buf->len += result;
+ }
+ }
+}
+
+/*
+ * Polls waitpid until the given process exits, or the timeout is reached.
+ * Returns 0 on success and < 0 on failure.
+ */
+static int waitpid_timeout(char* name, pid_t pid, int* wstatus)
+{
+ int result = -1;
+ int retries = WAITPID_TIMEOUT_MS / WAITPID_POLL_FREQUENCY_MS;
+
+ while (1) {
+ result = waitpid(pid, wstatus, WNOHANG);
+ if (result < 0) {
+ flb_errno();
+ return -1;
+ }
+
+ if (result > 0) {
+ return 0;
+ }
+
+ if (retries <= 0) {
+ AWS_CREDS_ERROR("timed out waiting for credential_process %s to exit", name);
+ return -1;
+ }
+ retries--;
+
+ usleep(WAITPID_POLL_FREQUENCY_MS * MICROS_PER_MS);
+ }
+}
+
+struct process {
+ int initialized;
+ char** args;
+ int stdin_stream;
+ flb_pipefd_t stdout_stream[2];
+ int stderr_stream;
+ pid_t pid;
+};
+
+/*
+ * Initializes a new process with the given args.
+ * args is assumed to be a NULL terminated array, for use with execvp.
+ * It must have a least one element, and the first element is assumed to be the
+ * name/path of the executable.
+ * Returns 0 on success and < 0 on failure.
+ * The caller is responsible for calling `destroy_process(p)`.
+ */
+static int new_process(struct process* p, char** args)
+{
+ *p = (struct process) {
+ .initialized = FLB_TRUE,
+ .args = args,
+ .stdin_stream = -1,
+ .stdout_stream = {-1, -1},
+ .stderr_stream = -1,
+ .pid = -1,
+ };
+
+ while ((p->stdin_stream = open(DEV_NULL, O_RDONLY|O_CLOEXEC)) < 0) {
+ if (errno != EINTR) {
+ flb_errno();
+ return -1;
+ }
+ }
+
+ if (flb_pipe_create(p->stdout_stream) < 0) {;
+ flb_errno();
+ return -1;
+ }
+
+ if (fcntl(p->stdout_stream[0], F_SETFL, O_CLOEXEC) < 0) {
+ flb_errno();
+ return -1;
+ }
+
+ if (fcntl(p->stdout_stream[1], F_SETFL, O_CLOEXEC) < 0) {
+ flb_errno();
+ return -1;
+ }
+
+ while ((p->stderr_stream = open(DEV_NULL, O_WRONLY|O_CLOEXEC)) < 0) {
+ if (errno != EINTR) {
+ flb_errno();
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Sets up the credential_process's stdin, stdout, and stderr, and exec's
+ * the actual process.
+ * For this function to return at all is an error.
+ * This function should not be called more than once.
+ */
+static void exec_process_child(struct process* p)
+{
+ while ((dup2(p->stdin_stream, STDIN_FILENO) < 0)) {
+ if (errno != EINTR) {
+ return;
+ }
+ }
+ while ((dup2(p->stdout_stream[1], STDOUT_FILENO) < 0)) {
+ if (errno != EINTR) {
+ return;
+ }
+ }
+ while ((dup2(p->stderr_stream, STDERR_FILENO) < 0)) {
+ if (errno != EINTR) {
+ return;
+ }
+ }
+
+ close(p->stdin_stream);
+ flb_pipe_close(p->stdout_stream[0]);
+ flb_pipe_close(p->stdout_stream[1]);
+ close(p->stderr_stream);
+
+ execvp(p->args[0], p->args);
+}
+
+/*
+ * Forks the credential_process, but does not wait for it to finish.
+ * Returns 0 on success and < 0 on failure.
+ * This function should not be called more than once.
+ */
+static int exec_process(struct process* p)
+{
+ AWS_CREDS_DEBUG("executing credential_process %s", p->args[0]);
+
+ p->pid = fork();
+ if (p->pid < 0) {
+ flb_errno();
+ return -1;
+ }
+
+ if (p->pid == 0) {
+ exec_process_child(p);
+
+ /* It should not be possible to reach this under normal circumstances. */
+ exit(EXIT_FAILURE);
+ }
+
+ close(p->stdin_stream);
+ p->stdin_stream = -1;
+
+ flb_pipe_close(p->stdout_stream[1]);
+ p->stdout_stream[1] = -1;
+
+ close(p->stderr_stream);
+ p->stderr_stream = -1;
+
+ return 0;
+}
+
+/*
+ * Reads from the credential_process's stdout into the given buffer.
+ * Returns 0 on success, and < 0 on failure or timeout.
+ * This function should not be called more than once.
+ */
+static int read_from_process(struct process* p, struct readbuf* buf)
+{
+ int result = -1;
+ struct pollfd pfd;
+ struct flb_time start, timeout, deadline, now, remaining;
+ int remaining_ms;
+
+ if (fcntl(p->stdout_stream[0], F_SETFL, O_NONBLOCK) < 0) {
+ flb_errno();
+ return -1;
+ }
+
+ if (get_monotonic_time(&start) < 0) {
+ return -1;
+ }
+
+ flb_time_set(&timeout,
+ (time_t) (CREDENTIAL_PROCESS_TIMEOUT_MS / MS_PER_SEC),
+ ((long) (CREDENTIAL_PROCESS_TIMEOUT_MS % MS_PER_SEC)) * NS_PER_MS);
+
+ /* deadline = start + timeout */
+ flb_time_add(&start, &timeout, &deadline);
+
+ while (1) {
+ pfd = (struct pollfd) {
+ .fd = p->stdout_stream[0],
+ .events = POLLIN,
+ };
+
+ if (get_monotonic_time(&now) < 0) {
+ return -1;
+ }
+
+ /* remaining = deadline - now */
+ if (flb_time_diff(&deadline, &now, &remaining) < 0) {
+ AWS_CREDS_ERROR("credential_process %s timed out", p->args[0]);
+ return -1;
+ }
+
+ /*
+ * poll uses millisecond resolution for the timeout.
+ * If there is less than a millisecond left, then for simplicity we'll just
+ * declare that it timed out.
+ */
+ remaining_ms = (int) (flb_time_to_nanosec(&remaining) / NS_PER_MS);
+ if (remaining_ms <= 0) {
+ AWS_CREDS_ERROR("credential_process %s timed out", p->args[0]);
+ return -1;
+ }
+
+ result = poll(&pfd, 1, remaining_ms);
+ if (result < 0) {
+ if (errno != EINTR) {
+ flb_errno();
+ return -1;
+ }
+ continue;
+ }
+
+ if (result == 0) {
+ AWS_CREDS_ERROR("credential_process %s timed out", p->args[0]);
+ return -1;
+ }
+
+ if ((pfd.revents & POLLNVAL) == POLLNVAL) {
+ AWS_CREDS_ERROR("credential_process %s POLLNVAL", p->args[0]);
+ return -1;
+ }
+
+ if ((pfd.revents & POLLERR) == POLLERR) {
+ AWS_CREDS_ERROR("credential_process %s POLLERR", p->args[0]);
+ return -1;
+ }
+
+ if ((pfd.revents & POLLIN) == POLLIN || (pfd.revents & POLLHUP) == POLLHUP) {
+ result = read_until_block(p->args[0], p->stdout_stream[0], buf);
+ if (result <= 0) {
+ return result;
+ }
+ }
+ }
+}
+
+/*
+ * Waits for the process to exit, up to a timeout.
+ * Returns 0 on success and < 0 on failure.
+ * This function should not be called more than once.
+ */
+static int wait_process(struct process* p)
+{
+ int wstatus;
+
+ if (waitpid_timeout(p->args[0], p->pid, &wstatus) < 0) {
+ return -1;
+ }
+ p->pid = -1;
+
+ if (!WIFEXITED(wstatus)) {
+ AWS_CREDS_ERROR("credential_process %s did not terminate normally", p->args[0]);
+ return -1;
+ }
+
+ if (WEXITSTATUS(wstatus) != EXIT_SUCCESS) {
+ AWS_CREDS_ERROR("credential_process %s exited with status %d", p->args[0],
+ WEXITSTATUS(wstatus));
+ return -1;
+ }
+
+ AWS_CREDS_DEBUG("credential_process %s exited successfully", p->args[0]);
+ return 0;
+}
+
+/*
+ * Release all resources associated with this process.
+ * Calling this function multiple times is a no-op.
+ * Since the process does not own p->args, it does not free it.
+ * Note that p->args will be set to NULL, so the caller must hold onto
+ * it separately in order to free it.
+ */
+static void destroy_process(struct process* p)
+{
+ if (p->initialized) {
+ if (p->stdin_stream >= 0) {
+ close(p->stdin_stream);
+ p->stdin_stream = -1;
+ }
+ if (p->stdout_stream[0] >= 0) {
+ close(p->stdout_stream[0]);
+ p->stdout_stream[0] = -1;
+ }
+ if (p->stdout_stream[1] >= 0) {
+ close(p->stdout_stream[1]);
+ p->stdout_stream[1] = -1;
+ }
+ if (p->stderr_stream >= 0) {
+ close(p->stderr_stream);
+ p->stderr_stream = -1;
+ }
+
+ if (p->pid > 0) {
+ if (kill(p->pid, SIGKILL) < 0) {
+ flb_errno();
+ AWS_CREDS_ERROR("could not kill credential_process %s (pid=%d) "
+ "during cleanup", p->args[0], p->pid);
+ }
+ else {
+ while (waitpid(p->pid, NULL, 0) < 0) {
+ if (errno != EINTR) {
+ flb_errno();
+ break;
+ }
+ }
+ }
+ p->pid = -1;
+ }
+
+ p->args = NULL;
+
+ p->initialized = FLB_FALSE;
+ }
+}
+
+/* See <fluent-bit/flb_aws_credentials.h>. */
+int exec_credential_process(char* process, struct flb_aws_credentials** creds,
+ time_t* expiration)
+{
+ char** args = NULL;
+ int result = -1;
+ struct process p = { 0 };
+ struct readbuf buf = { 0 };
+ *creds = NULL;
+ *expiration = 0;
+
+ args = parse_credential_process(process);
+ if (!args) {
+ result = -1;
+ goto end;
+ }
+
+ if (!args[0]) {
+ AWS_CREDS_ERROR("invalid credential_process");
+ result = -1;
+ goto end;
+ }
+
+ if (new_process(&p, args) < 0) {
+ result = -1;
+ goto end;
+ }
+
+ if (new_readbuf(&buf, CREDENTIAL_PROCESS_BUFFER_SIZE) < 0) {
+ result = -1;
+ goto end;
+ }
+
+ if (exec_process(&p) < 0) {
+ result = -1;
+ goto end;
+ }
+
+ if (read_from_process(&p, &buf) < 0) {
+ result = -1;
+ goto end;
+ }
+
+ if (wait_process(&p) < 0) {
+ result = -1;
+ goto end;
+ }
+
+ *creds = flb_parse_json_credentials(buf.buf, buf.len,
+ CREDENTIAL_PROCESS_RESPONSE_SESSION_TOKEN,
+ expiration);
+ if (!*creds) {
+ AWS_CREDS_ERROR("could not parse credentials from credential_process %s", args[0]);
+ result = -1;
+ goto end;
+ }
+
+ AWS_CREDS_DEBUG("successfully parsed credentials from credential_process %s", args[0]);
+
+ result = 0;
+
+end:
+ destroy_process(&p);
+
+ flb_free(buf.buf);
+ buf.buf = NULL;
+
+ flb_free(args);
+ args = NULL;
+
+ if (result < 0) {
+ flb_aws_credentials_destroy(*creds);
+ *creds = NULL;
+ }
+
+ return result;
+}
diff --git a/fluent-bit/src/aws/flb_aws_credentials_profile.c b/fluent-bit/src/aws/flb_aws_credentials_profile.c
new file mode 100644
index 000000000..6b3ab5dbd
--- /dev/null
+++ b/fluent-bit/src/aws/flb_aws_credentials_profile.c
@@ -0,0 +1,753 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 "flb_aws_credentials_log.h"
+
+#include <fluent-bit/flb_aws_credentials.h>
+#include <fluent-bit/flb_aws_util.h>
+#include <fluent-bit/flb_http_client.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_sds.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+
+#define ACCESS_KEY_PROPERTY_NAME "aws_access_key_id"
+#define SECRET_KEY_PROPERTY_NAME "aws_secret_access_key"
+#define SESSION_TOKEN_PROPERTY_NAME "aws_session_token"
+#define CREDENTIAL_PROCESS_PROPERTY_NAME "credential_process"
+
+#define AWS_PROFILE "AWS_PROFILE"
+#define AWS_DEFAULT_PROFILE "AWS_DEFAULT_PROFILE"
+
+#define AWS_CONFIG_FILE "AWS_CONFIG_FILE"
+#define AWS_SHARED_CREDENTIALS_FILE "AWS_SHARED_CREDENTIALS_FILE"
+
+#define DEFAULT_PROFILE "default"
+#define CONFIG_PROFILE_PREFIX "profile "
+#define CONFIG_PROFILE_PREFIX_LEN (sizeof(CONFIG_PROFILE_PREFIX)-1)
+
+/* Declarations */
+struct flb_aws_provider_profile;
+static int refresh_credentials(struct flb_aws_provider_profile *implementation,
+ int debug_only);
+
+static int get_aws_shared_file_path(flb_sds_t* field, char* env_var, char* home_aws_path);
+
+static int parse_config_file(char *buf, char* profile, struct flb_aws_credentials** creds,
+ time_t* expiration, int debug_only);
+static int parse_credentials_file(char *buf, char *profile,
+ struct flb_aws_credentials *creds, int debug_only);
+
+static int get_shared_config_credentials(char* config_path,
+ char*profile,
+ struct flb_aws_credentials** creds,
+ time_t* expiration,
+ int debug_only);
+static int get_shared_credentials(char* credentials_path,
+ char* profile,
+ struct flb_aws_credentials** creds,
+ int debug_only);
+
+static flb_sds_t parse_property_value(char *s, int debug_only);
+static char *parse_property_line(char *line);
+static int has_profile(char *line, char* profile, int debug_only);
+static int is_profile_line(char *line);
+static int config_file_profile_matches(char *line, char *profile);
+
+/*
+ * A provider that reads from the shared credentials file.
+ */
+struct flb_aws_provider_profile {
+ struct flb_aws_credentials *creds;
+ time_t next_refresh;
+
+ flb_sds_t profile;
+ flb_sds_t config_path;
+ flb_sds_t credentials_path;
+};
+
+struct flb_aws_credentials *get_credentials_fn_profile(struct flb_aws_provider
+ *provider)
+{
+ struct flb_aws_credentials *creds;
+ int ret;
+ struct flb_aws_provider_profile *implementation = provider->implementation;
+
+ /*
+ * If next_refresh <= 0, it means we don't know how long the credentials
+ * are valid for. So we won't refresh them unless explicitly asked
+ * via refresh_fn_profile.
+ */
+ if (!implementation->creds || (implementation->next_refresh > 0 &&
+ time(NULL) >= implementation->next_refresh)) {
+ AWS_CREDS_DEBUG("Retrieving credentials for AWS Profile %s",
+ implementation->profile);
+ if (try_lock_provider(provider) == FLB_TRUE) {
+ ret = refresh_credentials(implementation, FLB_FALSE);
+ unlock_provider(provider);
+ if (ret < 0) {
+ AWS_CREDS_ERROR("Failed to retrieve credentials for AWS Profile %s",
+ implementation->profile);
+ return NULL;
+ }
+ } else {
+ AWS_CREDS_WARN("Another thread is refreshing credentials, will retry");
+ return NULL;
+ }
+ }
+
+ creds = flb_calloc(1, sizeof(struct flb_aws_credentials));
+ if (!creds) {
+ flb_errno();
+ goto error;
+ }
+
+ creds->access_key_id = flb_sds_create(implementation->creds->access_key_id);
+ if (!creds->access_key_id) {
+ flb_errno();
+ goto error;
+ }
+
+ creds->secret_access_key = flb_sds_create(implementation->
+ creds->secret_access_key);
+ if (!creds->secret_access_key) {
+ flb_errno();
+ goto error;
+ }
+
+ if (implementation->creds->session_token) {
+ creds->session_token = flb_sds_create(implementation->
+ creds->session_token);
+ if (!creds->session_token) {
+ flb_errno();
+ goto error;
+ }
+
+ } else {
+ creds->session_token = NULL;
+ }
+
+ return creds;
+
+error:
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+}
+
+int refresh_fn_profile(struct flb_aws_provider *provider)
+{
+ struct flb_aws_provider_profile *implementation = provider->implementation;
+ int ret = -1;
+ AWS_CREDS_DEBUG("Refresh called on the profile provider");
+ if (try_lock_provider(provider) == FLB_TRUE) {
+ ret = refresh_credentials(implementation, FLB_FALSE);
+ unlock_provider(provider);
+ return ret;
+ }
+ return ret;
+}
+
+int init_fn_profile(struct flb_aws_provider *provider)
+{
+ struct flb_aws_provider_profile *implementation = provider->implementation;
+ int ret = -1;
+ AWS_CREDS_DEBUG("Init called on the profile provider");
+ if (try_lock_provider(provider) == FLB_TRUE) {
+ ret = refresh_credentials(implementation, FLB_TRUE);
+ unlock_provider(provider);
+ return ret;
+ }
+ return ret;
+}
+
+/*
+ * Sync and Async are no-ops for the profile provider because it does not
+ * make network IO calls
+ */
+void sync_fn_profile(struct flb_aws_provider *provider)
+{
+ return;
+}
+
+void async_fn_profile(struct flb_aws_provider *provider)
+{
+ return;
+}
+
+void upstream_set_fn_profile(struct flb_aws_provider *provider,
+ struct flb_output_instance *ins)
+{
+ return;
+}
+
+void destroy_fn_profile(struct flb_aws_provider *provider)
+{
+ struct flb_aws_provider_profile *implementation = provider->implementation;
+
+ if (implementation) {
+ if (implementation->creds) {
+ flb_aws_credentials_destroy(implementation->creds);
+ }
+
+ if (implementation->profile) {
+ flb_sds_destroy(implementation->profile);
+ }
+
+ if (implementation->config_path) {
+ flb_sds_destroy(implementation->config_path);
+ }
+
+ if (implementation->credentials_path) {
+ flb_sds_destroy(implementation->credentials_path);
+ }
+
+ flb_free(implementation);
+ provider->implementation = NULL;
+ }
+
+ return;
+}
+
+static struct flb_aws_provider_vtable profile_provider_vtable = {
+ .get_credentials = get_credentials_fn_profile,
+ .init = init_fn_profile,
+ .refresh = refresh_fn_profile,
+ .destroy = destroy_fn_profile,
+ .sync = sync_fn_profile,
+ .async = async_fn_profile,
+ .upstream_set = upstream_set_fn_profile,
+};
+
+struct flb_aws_provider *flb_profile_provider_create(char* profile)
+{
+ struct flb_aws_provider *provider = NULL;
+ struct flb_aws_provider_profile *implementation = NULL;
+ int result = -1;
+
+ provider = flb_calloc(1, sizeof(struct flb_aws_provider));
+
+ if (!provider) {
+ flb_errno();
+ goto error;
+ }
+
+ pthread_mutex_init(&provider->lock, NULL);
+
+ implementation = flb_calloc(1,
+ sizeof(
+ struct flb_aws_provider_profile));
+
+ if (!implementation) {
+ flb_errno();
+ goto error;
+ }
+
+ provider->provider_vtable = &profile_provider_vtable;
+ provider->implementation = implementation;
+
+ result = get_aws_shared_file_path(&implementation->config_path, AWS_CONFIG_FILE,
+ "/.aws/config");
+ if (result < 0) {
+ goto error;
+ }
+
+ result = get_aws_shared_file_path(&implementation->credentials_path,
+ AWS_SHARED_CREDENTIALS_FILE, "/.aws/credentials");
+ if (result < 0) {
+ goto error;
+ }
+
+ if (!implementation->config_path && !implementation->credentials_path) {
+ AWS_CREDS_WARN("Failed to initialize profile provider: "
+ "HOME, %s, and %s not set.",
+ AWS_CONFIG_FILE, AWS_SHARED_CREDENTIALS_FILE);
+ goto error;
+ }
+
+ /* AWS profile name. */
+ if (profile == NULL) {
+ profile = getenv(AWS_PROFILE);
+ }
+ if (profile && strlen(profile) > 0) {
+ goto set_profile;
+ }
+
+ profile = getenv(AWS_DEFAULT_PROFILE);
+ if (profile && strlen(profile) > 0) {
+ goto set_profile;
+ }
+
+ profile = DEFAULT_PROFILE;
+
+set_profile:
+ implementation->profile = flb_sds_create(profile);
+ if (!implementation->profile) {
+ flb_errno();
+ goto error;
+ }
+
+ return provider;
+
+error:
+ flb_aws_provider_destroy(provider);
+ return NULL;
+}
+
+
+/*
+ * Fetches the path of either the shared config file or the shared credentials file.
+ * Returns 0 on success and < 0 on failure.
+ * On success, the result will be stored in *field.
+ *
+ * If the given environment variable is set, then its value will be used verbatim.
+ * Else if $HOME is set, then it will be concatenated with home_aws_path.
+ * If neither is set, then *field will be set to NULL. This is not considered a failure.
+ *
+ * In practice, env_var will be "AWS_CONFIG_FILE" or "AWS_SHARED_CREDENTIALS_FILE",
+ * and home_aws_path will be "/.aws/config" or "/.aws/credentials".
+ */
+static int get_aws_shared_file_path(flb_sds_t* field, char* env_var, char* home_aws_path)
+{
+ char* path = NULL;
+ int result = -1;
+ flb_sds_t value = NULL;
+
+ path = getenv(env_var);
+ if (path && *path) {
+ value = flb_sds_create(path);
+ if (!value) {
+ flb_errno();
+ goto error;
+ }
+ } else {
+ path = getenv("HOME");
+ if (path && *path) {
+ value = flb_sds_create(path);
+ if (!value) {
+ flb_errno();
+ goto error;
+ }
+
+ if (path[strlen(path) - 1] == '/') {
+ home_aws_path++;
+ }
+ result = flb_sds_cat_safe(&value, home_aws_path, strlen(home_aws_path));
+ if (result < 0) {
+ flb_errno();
+ goto error;
+ }
+ }
+ }
+
+ *field = value;
+ return 0;
+
+error:
+ flb_sds_destroy(value);
+ return -1;
+}
+
+static int is_profile_line(char *line) {
+ if (line[0] == '[') {
+ return FLB_TRUE;
+ }
+ return FLB_FALSE;
+}
+
+/* Called on lines that have is_profile_line == True */
+static int has_profile(char *line, char* profile, int debug_only) {
+ char *end_bracket = strchr(line, ']');
+ if (!end_bracket) {
+ if (debug_only) {
+ AWS_CREDS_DEBUG("Profile header has no ending bracket:\n %s", line);
+ }
+ else {
+ AWS_CREDS_WARN("Profile header has no ending bracket:\n %s", line);
+ }
+ return FLB_FALSE;
+ }
+ *end_bracket = '\0';
+
+ if (strcmp(&line[1], profile) == 0) {
+ return FLB_TRUE;
+ }
+
+ return FLB_FALSE;
+}
+
+/*
+ * Sets a null byte such that line becomes the property name
+ * Returns a pointer to the rest of the line (the value), if successful.
+ */
+static char *parse_property_line(char *line) {
+ int len = strlen(line);
+ int found_delimeter = FLB_FALSE;
+ int i = 0;
+
+ if (isspace(line[0])) {
+ /* property line can not start with whitespace */
+ return NULL;
+ }
+
+ /*
+ * Go through the line char by char, once we find whitespace/= we are
+ * passed the property name. Return the first char of the property value.
+ * There should be a single "=" separating name and value.
+ */
+ for (i=0; i < (len - 1); i++) {
+ if (isspace(line[i])) {
+ line[i] = '\0';
+ } else if (found_delimeter == FLB_FALSE && line[i] == '=') {
+ found_delimeter = FLB_TRUE;
+ line[i] = '\0';
+ } else if (found_delimeter == FLB_TRUE) {
+ return &line[i];
+ }
+ }
+
+ return NULL;
+}
+
+/* called on the rest of a line after parse_property_line is called */
+static flb_sds_t parse_property_value(char *s, int debug_only) {
+ int len = strlen(s);
+ int i = 0;
+ char *val = NULL;
+ flb_sds_t prop;
+
+ for (i=0; i < len; i++) {
+ if (isspace(s[i])) {
+ s[i] = '\0';
+ continue;
+ } else if (!val) {
+ val = &s[i];
+ }
+ }
+
+ if (!val) {
+ AWS_CREDS_ERROR_OR_DEBUG(debug_only, "Could not parse credential value from %s", s);
+ }
+
+ prop = flb_sds_create(val);
+ if (!prop) {
+ flb_errno();
+ return NULL;
+ }
+
+ return prop;
+}
+
+static int config_file_profile_matches(char *line, char *profile) {
+ char *current_profile = line + 1;
+ char* current_profile_end = strchr(current_profile, ']');
+
+ if (!current_profile_end) {
+ return FLB_FALSE;
+ }
+ *current_profile_end = '\0';
+
+ /*
+ * Non-default profiles look like `[profile <name>]`.
+ * The default profile can look like `[profile default]` or just `[default]`.
+ * This is different than the credentials file, where everything is `[<name>]`.
+ */
+ if (strncmp(current_profile, CONFIG_PROFILE_PREFIX, CONFIG_PROFILE_PREFIX_LEN) != 0) {
+ if (strcmp(current_profile, DEFAULT_PROFILE) != 0) {
+ /* This is not a valid profile line. */
+ return FLB_FALSE;
+ }
+ } else {
+ current_profile += CONFIG_PROFILE_PREFIX_LEN;
+ }
+
+ if (strcmp(current_profile, profile) == 0) {
+ return FLB_TRUE;
+ }
+ return FLB_FALSE;
+}
+
+static int parse_config_file(char *buf, char* profile, struct flb_aws_credentials** creds,
+ time_t* expiration, int debug_only)
+{
+ char *line = NULL;
+ char *line_end = NULL;
+ char *prop_val = NULL;
+ char *credential_process = NULL;
+ int found_profile = FLB_FALSE;
+
+ for (line = buf; line[0] != '\0'; line = buf) {
+ /*
+ * Find the next newline and replace it with a null terminator.
+ * That way we can easily manipulate the current line as a string.
+ */
+ line_end = strchr(line, '\n');
+ if (line_end) {
+ *line_end = '\0';
+ buf = line_end + 1;
+ } else {
+ buf = "";
+ }
+
+ if (found_profile != FLB_TRUE) {
+ if (is_profile_line(line) != FLB_TRUE) {
+ continue;
+ }
+ if (config_file_profile_matches(line, profile) != FLB_TRUE) {
+ continue;
+ }
+ found_profile = FLB_TRUE;
+ } else {
+ if (is_profile_line(line) == FLB_TRUE) {
+ break;
+ }
+ prop_val = parse_property_line(line);
+ if (strcmp(line, CREDENTIAL_PROCESS_PROPERTY_NAME) == 0) {
+ credential_process = prop_val;
+ }
+ }
+ }
+
+ if (credential_process) {
+#ifdef FLB_HAVE_AWS_CREDENTIAL_PROCESS
+ if (exec_credential_process(credential_process, creds, expiration) < 0) {
+ return -1;
+ }
+#else
+ AWS_CREDS_WARN("credential_process not supported for this platform");
+ return -1;
+#endif
+ }
+
+ return 0;
+}
+
+/*
+ * Parses a shared credentials file.
+ * Expects the contents of 'creds' to be initialized to NULL (i.e use calloc).
+ */
+static int parse_credentials_file(char *buf, char *profile,
+ struct flb_aws_credentials *creds, int debug_only)
+{
+ char *line;
+ char *line_end;
+ char *prop_val = NULL;
+ int found_profile = FLB_FALSE;
+
+ line = buf;
+
+ while (line[0] != '\0') {
+ /* turn the line into a C string */
+ line_end = strchr(line, '\n');
+ if (line_end) {
+ *line_end = '\0';
+ }
+
+ if (is_profile_line(line) == FLB_TRUE) {
+ if (found_profile == FLB_TRUE) {
+ break;
+ }
+ if (has_profile(line, profile, debug_only)) {
+ found_profile = FLB_TRUE;
+ }
+ } else {
+ prop_val = parse_property_line(line);
+ if (prop_val && found_profile == FLB_TRUE) {
+ if (strcmp(line, ACCESS_KEY_PROPERTY_NAME) == 0) {
+ creds->access_key_id = parse_property_value(prop_val,
+ debug_only);
+ }
+ if (strcmp(line, SECRET_KEY_PROPERTY_NAME) == 0) {
+ creds->secret_access_key = parse_property_value(prop_val,
+ debug_only);
+ }
+ if (strcmp(line, SESSION_TOKEN_PROPERTY_NAME) == 0) {
+ creds->session_token = parse_property_value(prop_val,
+ debug_only);
+ }
+ }
+ }
+
+ /* advance to next line */
+ if (line_end) {
+ line = line_end + 1;
+ } else {
+ break;
+ }
+ }
+
+ if (creds->access_key_id && creds->secret_access_key) {
+ return 0;
+ }
+ AWS_CREDS_ERROR_OR_DEBUG(debug_only, "%s and %s keys not parsed in shared "
+ "credentials file for profile %s.", ACCESS_KEY_PROPERTY_NAME,
+ SECRET_KEY_PROPERTY_NAME, profile);
+ return -1;
+}
+
+static int get_shared_config_credentials(char* config_path,
+ char*profile,
+ struct flb_aws_credentials** creds,
+ time_t* expiration,
+ int debug_only) {
+ int result = -1;
+ char* buf = NULL;
+ size_t size;
+ *creds = NULL;
+ *expiration = 0;
+
+ AWS_CREDS_DEBUG("Reading shared config file.");
+
+ if (flb_read_file(config_path, &buf, &size) < 0) {
+ if (errno == ENOENT) {
+ AWS_CREDS_DEBUG("Shared config file %s does not exist", config_path);
+ result = 0;
+ goto end;
+ }
+ flb_errno();
+ AWS_CREDS_ERROR_OR_DEBUG(debug_only, "Could not read shared config file %s",
+ config_path);
+ result = -1;
+ goto end;
+ }
+
+ if (parse_config_file(buf, profile, creds, expiration, debug_only) < 0) {
+ result = -1;
+ goto end;
+ }
+
+ result = 0;
+
+end:
+ flb_free(buf);
+ return result;
+}
+
+static int get_shared_credentials(char* credentials_path,
+ char* profile,
+ struct flb_aws_credentials** creds,
+ int debug_only) {
+ int result = -1;
+ char* buf = NULL;
+ size_t size;
+ *creds = NULL;
+
+ *creds = flb_calloc(1, sizeof(struct flb_aws_credentials));
+ if (!*creds) {
+ flb_errno();
+ result = -1;
+ goto end;
+ }
+
+ AWS_CREDS_DEBUG("Reading shared credentials file.");
+
+ if (flb_read_file(credentials_path, &buf, &size) < 0) {
+ if (errno == ENOENT) {
+ AWS_CREDS_ERROR_OR_DEBUG(debug_only, "Shared credentials file %s does not exist",
+ credentials_path);
+ } else {
+ flb_errno();
+ AWS_CREDS_ERROR_OR_DEBUG(debug_only, "Could not read shared credentials file %s",
+ credentials_path);
+ }
+ result = -1;
+ goto end;
+ }
+
+ if (parse_credentials_file(buf, profile, *creds, debug_only) < 0) {
+ AWS_CREDS_ERROR_OR_DEBUG(debug_only, "Could not parse shared credentials file: "
+ "valid profile with name '%s' not found", profile);
+ result = -1;
+ goto end;
+ }
+
+ result = 0;
+
+end:
+ flb_free(buf);
+
+ if (result < 0) {
+ flb_aws_credentials_destroy(*creds);
+ *creds = NULL;
+ }
+
+ return result;
+}
+
+static int refresh_credentials(struct flb_aws_provider_profile *implementation,
+ int debug_only)
+{
+ struct flb_aws_credentials *creds = NULL;
+ time_t expiration = 0;
+ int ret;
+
+ if (implementation->config_path) {
+ ret = get_shared_config_credentials(implementation->config_path,
+ implementation->profile,
+ &creds,
+ &expiration,
+ debug_only);
+ if (ret < 0) {
+ goto error;
+ }
+ }
+
+ /*
+ * If we did not find a credential_process in the shared config file, fall back to
+ * the shared credentials file.
+ */
+ if (!creds) {
+ if (!implementation->credentials_path) {
+ AWS_CREDS_ERROR("shared config file contains no credential_process and "
+ "no shared credentials file was configured");
+ goto error;
+ }
+
+ ret = get_shared_credentials(implementation->credentials_path,
+ implementation->profile,
+ &creds,
+ debug_only);
+ if (ret < 0) {
+ goto error;
+ }
+
+ /* The shared credentials file does not record when the credentials expire. */
+ expiration = 0;
+ }
+
+ /* unset and free existing credentials */
+ flb_aws_credentials_destroy(implementation->creds);
+ implementation->creds = creds;
+
+ if (expiration > 0) {
+ implementation->next_refresh = expiration - FLB_AWS_REFRESH_WINDOW;
+ } else {
+ implementation->next_refresh = 0;
+ }
+
+ return 0;
+
+error:
+ flb_aws_credentials_destroy(creds);
+ return -1;
+}
diff --git a/fluent-bit/src/aws/flb_aws_credentials_sts.c b/fluent-bit/src/aws/flb_aws_credentials_sts.c
new file mode 100644
index 000000000..d992485cc
--- /dev/null
+++ b/fluent-bit/src/aws/flb_aws_credentials_sts.c
@@ -0,0 +1,958 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_http_client.h>
+#include <fluent-bit/flb_aws_credentials.h>
+#include <fluent-bit/flb_aws_util.h>
+#include <fluent-bit/flb_random.h>
+#include <fluent-bit/flb_jsmn.h>
+
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+
+#define STS_ASSUME_ROLE_URI_FORMAT "/?Version=2011-06-15&Action=%s\
+&RoleSessionName=%s&RoleArn=%s"
+#define STS_ASSUME_ROLE_URI_BASE_LEN 54
+
+/*
+ * The STS APIs return an XML document with credentials.
+ * The part of the document we care about looks like this:
+ * <Credentials>
+ * <AccessKeyId>akid</AccessKeyId>
+ * <SecretAccessKey>skid</SecretAccessKey>
+ * <SessionToken>token</SessionToken>
+ * <Expiration>2019-11-09T13:34:41Z</Expiration>
+ * </Credentials>
+ */
+#define CREDENTIALS_NODE "<Credentials>"
+#define CREDENTIALS_NODE_LEN 13
+#define ACCESS_KEY_NODE "<AccessKeyId>"
+#define ACCESS_KEY_NODE_LEN 13
+#define ACCESS_KEY_NODE_END "</AccessKeyId>"
+#define SECRET_KEY_NODE "<SecretAccessKey>"
+#define SECRET_KEY_NODE_LEN 17
+#define SECRET_KEY_NODE_END "</SecretAccessKey>"
+#define SESSION_TOKEN_NODE "<SessionToken>"
+#define SESSION_TOKEN_NODE_LEN 14
+#define SESSION_TOKEN_NODE_END "</SessionToken>"
+#define EXPIRATION_NODE "<Expiration>"
+#define EXPIRATION_NODE_LEN 12
+#define EXPIRATION_NODE_END "</Expiration>"
+
+#define TOKEN_FILE_ENV_VAR "AWS_WEB_IDENTITY_TOKEN_FILE"
+#define ROLE_ARN_ENV_VAR "AWS_ROLE_ARN"
+#define SESSION_NAME_ENV_VAR "AWS_ROLE_SESSION_NAME"
+
+#define SESSION_NAME_RANDOM_BYTE_LEN 32
+
+struct flb_aws_provider_eks;
+void bytes_to_string(unsigned char *data, char *buf, size_t len);
+static int assume_with_web_identity(struct flb_aws_provider_eks
+ *implementation);
+static int sts_assume_role_request(struct flb_aws_client *sts_client,
+ struct flb_aws_credentials **creds,
+ char *uri,
+ time_t *next_refresh);
+static flb_sds_t get_node(char *cred_node, char* node_name, int node_name_len, char* node_end);
+
+
+/*
+ * A provider that uses credentials from the base provider to call STS
+ * and assume an IAM Role.
+ */
+struct flb_aws_provider_sts {
+ int custom_endpoint;
+ struct flb_aws_provider *base_provider;
+
+ struct flb_aws_credentials *creds;
+ time_t next_refresh;
+
+ struct flb_aws_client *sts_client;
+
+ /* Fluent Bit uses regional STS endpoints; this is a best practice. */
+ char *endpoint;
+
+ flb_sds_t uri;
+};
+
+struct flb_aws_credentials *get_credentials_fn_sts(struct flb_aws_provider
+ *provider)
+{
+ struct flb_aws_credentials *creds;
+ int refresh = FLB_FALSE;
+ struct flb_aws_provider_sts *implementation = provider->implementation;
+
+ flb_debug("[aws_credentials] Requesting credentials from the "
+ "STS provider..");
+
+ /* a negative next_refresh means that auto-refresh is disabled */
+ if (implementation->next_refresh > 0
+ && time(NULL) > implementation->next_refresh) {
+ refresh = FLB_TRUE;
+ }
+ if (!implementation->creds || refresh == FLB_TRUE) {
+ /* credentials need to be refreshed/obtained */
+ if (try_lock_provider(provider)) {
+ flb_debug("[aws_credentials] STS Provider: Refreshing credential "
+ "cache.");
+ sts_assume_role_request(implementation->sts_client,
+ &implementation->creds,
+ implementation->uri,
+ &implementation->next_refresh);
+ unlock_provider(provider);
+ }
+ }
+
+ if (!implementation->creds) {
+ /*
+ * We failed to lock the provider and creds are unset. This means that
+ * another co-routine is performing the refresh.
+ */
+ flb_warn("[aws_credentials] No cached credentials are available and "
+ "a credential refresh is already in progress. The current "
+ "co-routine will retry.");
+
+ return NULL;
+ }
+
+ /* return a copy of the existing cached credentials */
+ creds = flb_calloc(1, sizeof(struct flb_aws_credentials));
+ if (!creds) {
+ goto error;
+ }
+
+ creds->access_key_id = flb_sds_create(implementation->creds->access_key_id);
+ if (!creds->access_key_id) {
+ goto error;
+ }
+
+ creds->secret_access_key = flb_sds_create(implementation->creds->
+ secret_access_key);
+ if (!creds->secret_access_key) {
+ goto error;
+ }
+
+ if (implementation->creds->session_token) {
+ creds->session_token = flb_sds_create(implementation->creds->
+ session_token);
+ if (!creds->session_token) {
+ goto error;
+ }
+
+ } else {
+ creds->session_token = NULL;
+ }
+
+ return creds;
+
+error:
+ flb_errno();
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+}
+
+int refresh_fn_sts(struct flb_aws_provider *provider) {
+ int ret = -1;
+ struct flb_aws_provider_sts *implementation = provider->implementation;
+
+ flb_debug("[aws_credentials] Refresh called on the STS provider");
+
+ if (try_lock_provider(provider)) {
+ ret = sts_assume_role_request(implementation->sts_client,
+ &implementation->creds, implementation->uri,
+ &implementation->next_refresh);
+ unlock_provider(provider);
+ }
+ return ret;
+}
+
+int init_fn_sts(struct flb_aws_provider *provider) {
+ int ret = -1;
+ struct flb_aws_provider_sts *implementation = provider->implementation;
+
+ flb_debug("[aws_credentials] Init called on the STS provider");
+
+ /* Call Init on the base provider first */
+ implementation->base_provider->provider_vtable->
+ init(implementation->base_provider);
+
+ implementation->sts_client->debug_only = FLB_TRUE;
+
+ if (try_lock_provider(provider)) {
+ ret = sts_assume_role_request(implementation->sts_client,
+ &implementation->creds, implementation->uri,
+ &implementation->next_refresh);
+ unlock_provider(provider);
+ }
+
+ implementation->sts_client->debug_only = FLB_FALSE;
+ return ret;
+}
+
+void sync_fn_sts(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_sts *implementation = provider->implementation;
+ struct flb_aws_provider *base_provider = implementation->base_provider;
+
+ flb_debug("[aws_credentials] Sync called on the STS provider");
+ /* Remove async flag */
+ flb_stream_disable_async_mode(&implementation->sts_client->upstream->base);
+
+ /* we also need to call sync on the base_provider */
+ base_provider->provider_vtable->sync(base_provider);
+}
+
+void async_fn_sts(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_sts *implementation = provider->implementation;
+ struct flb_aws_provider *base_provider = implementation->base_provider;
+
+ flb_debug("[aws_credentials] Async called on the STS provider");
+ /* Add async flag */
+ flb_stream_enable_async_mode(&implementation->sts_client->upstream->base);
+
+ /* we also need to call async on the base_provider */
+ base_provider->provider_vtable->async(base_provider);
+}
+
+void upstream_set_fn_sts(struct flb_aws_provider *provider,
+ struct flb_output_instance *ins) {
+ struct flb_aws_provider_sts *implementation = provider->implementation;
+ struct flb_aws_provider *base_provider = implementation->base_provider;
+
+ flb_debug("[aws_credentials] upstream_set called on the STS provider");
+ /* associate output and upstream */
+ flb_output_upstream_set(implementation->sts_client->upstream, ins);
+
+ /* we also need to call upstream_set on the base_provider */
+ base_provider->provider_vtable->upstream_set(base_provider, ins);
+}
+
+void destroy_fn_sts(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_sts *implementation;
+
+ implementation = provider->implementation;
+
+ if (implementation) {
+ if (implementation->creds) {
+ flb_aws_credentials_destroy(implementation->creds);
+ }
+
+ if (implementation->sts_client) {
+ flb_aws_client_destroy(implementation->sts_client);
+ }
+
+ if (implementation->uri) {
+ flb_sds_destroy(implementation->uri);
+ }
+
+ if (implementation->custom_endpoint == FLB_FALSE) {
+ flb_free(implementation->endpoint);
+ }
+
+ flb_free(implementation);
+ provider->implementation = NULL;
+ }
+
+ return;
+}
+
+static struct flb_aws_provider_vtable sts_provider_vtable = {
+ .get_credentials = get_credentials_fn_sts,
+ .init = init_fn_sts,
+ .refresh = refresh_fn_sts,
+ .destroy = destroy_fn_sts,
+ .sync = sync_fn_sts,
+ .async = async_fn_sts,
+ .upstream_set = upstream_set_fn_sts,
+};
+
+struct flb_aws_provider *flb_sts_provider_create(struct flb_config *config,
+ struct flb_tls *tls,
+ struct flb_aws_provider
+ *base_provider,
+ char *external_id,
+ char *role_arn,
+ char *session_name,
+ char *region,
+ char *sts_endpoint,
+ char *proxy,
+ struct
+ flb_aws_client_generator
+ *generator)
+{
+ struct flb_aws_provider_sts *implementation = NULL;
+ struct flb_aws_provider *provider = NULL;
+ struct flb_upstream *upstream = NULL;
+
+ provider = flb_calloc(1, sizeof(struct flb_aws_provider));
+ if (!provider) {
+ flb_errno();
+ return NULL;
+ }
+
+ pthread_mutex_init(&provider->lock, NULL);
+
+ implementation = flb_calloc(1, sizeof(struct flb_aws_provider_sts));
+ if (!implementation) {
+ goto error;
+ }
+
+ provider->provider_vtable = &sts_provider_vtable;
+ provider->implementation = implementation;
+
+ implementation->uri = flb_sts_uri("AssumeRole", role_arn, session_name,
+ external_id, NULL);
+ if (!implementation->uri) {
+ goto error;
+ }
+
+ if (sts_endpoint) {
+ implementation->endpoint = removeProtocol(sts_endpoint, "https://");
+ implementation->custom_endpoint = FLB_TRUE;
+ }
+ else {
+ implementation->endpoint = flb_aws_endpoint("sts", region);
+ implementation->custom_endpoint = FLB_FALSE;
+ }
+
+ if(!implementation->endpoint) {
+ goto error;
+ }
+
+ implementation->base_provider = base_provider;
+ implementation->sts_client = generator->create();
+ if (!implementation->sts_client) {
+ goto error;
+ }
+ implementation->sts_client->name = "sts_client_assume_role_provider";
+ implementation->sts_client->has_auth = FLB_TRUE;
+ implementation->sts_client->provider = base_provider;
+ implementation->sts_client->region = region;
+ implementation->sts_client->service = "sts";
+ implementation->sts_client->port = 443;
+ implementation->sts_client->flags = 0;
+ implementation->sts_client->proxy = proxy;
+
+ upstream = flb_upstream_create(config, implementation->endpoint, 443,
+ FLB_IO_TLS, tls);
+ if (!upstream) {
+ flb_error("[aws_credentials] Connection initialization error");
+ goto error;
+ }
+
+ upstream->base.net.connect_timeout = FLB_AWS_CREDENTIAL_NET_TIMEOUT;
+
+ implementation->sts_client->upstream = upstream;
+ implementation->sts_client->host = implementation->endpoint;
+
+ return provider;
+
+error:
+ flb_errno();
+ flb_aws_provider_destroy(provider);
+ return NULL;
+}
+
+/*
+ * A provider that uses OIDC tokens provided by kubernetes to obtain
+ * AWS credentials.
+ *
+ * The AWS SDKs have defined a spec for an OIDC provider that obtains tokens
+ * from environment variables or the shared config file.
+ * This provider only contains the functionality needed for EKS- obtaining the
+ * location of the OIDC token from an environment variable.
+ */
+struct flb_aws_provider_eks {
+ int custom_endpoint;
+ struct flb_aws_credentials *creds;
+ /*
+ * Time to auto-refresh creds before they expire. A negative value disables
+ * auto-refresh. Client code can always force a refresh.
+ */
+ time_t next_refresh;
+
+ struct flb_aws_client *sts_client;
+
+ /* Fluent Bit uses regional STS endpoints; this is a best practice. */
+ char *endpoint;
+
+ char *session_name;
+ /* session name can come from env or be generated by the provider */
+ int free_session_name;
+ char *role_arn;
+
+ char *token_file;
+};
+
+
+struct flb_aws_credentials *get_credentials_fn_eks(struct flb_aws_provider
+ *provider)
+{
+ struct flb_aws_credentials *creds = NULL;
+ int refresh = FLB_FALSE;
+ struct flb_aws_provider_eks *implementation = provider->implementation;
+
+ flb_debug("[aws_credentials] Requesting credentials from the "
+ "EKS provider..");
+
+ /* a negative next_refresh means that auto-refresh is disabled */
+ if (implementation->next_refresh > 0
+ && time(NULL) > implementation->next_refresh) {
+ refresh = FLB_TRUE;
+ }
+ if (!implementation->creds || refresh == FLB_TRUE) {
+ if (try_lock_provider(provider)) {
+ flb_debug("[aws_credentials] EKS Provider: Refreshing credential "
+ "cache.");
+ assume_with_web_identity(implementation);
+ unlock_provider(provider);
+ }
+ }
+
+ if (!implementation->creds) {
+ /*
+ * We failed to lock the provider and creds are unset. This means that
+ * another co-routine is performing the refresh.
+ */
+ flb_warn("[aws_credentials] No cached credentials are available and "
+ "a credential refresh is already in progress. The current "
+ "co-routine will retry.");
+
+ return NULL;
+ }
+
+ creds = flb_calloc(1, sizeof(struct flb_aws_credentials));
+ if (!creds) {
+ goto error;
+ }
+
+ creds->access_key_id = flb_sds_create(implementation->creds->access_key_id);
+ if (!creds->access_key_id) {
+ goto error;
+ }
+
+ creds->secret_access_key = flb_sds_create(implementation->creds->
+ secret_access_key);
+ if (!creds->secret_access_key) {
+ goto error;
+ }
+
+ if (implementation->creds->session_token) {
+ creds->session_token = flb_sds_create(implementation->creds->
+ session_token);
+ if (!creds->session_token) {
+ goto error;
+ }
+
+ }
+ else {
+ creds->session_token = NULL;
+ }
+
+ return creds;
+
+error:
+ flb_errno();
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+}
+
+int refresh_fn_eks(struct flb_aws_provider *provider) {
+ int ret = -1;
+ struct flb_aws_provider_eks *implementation = provider->implementation;
+
+ flb_debug("[aws_credentials] Refresh called on the EKS provider");
+ if (try_lock_provider(provider)) {
+ ret = assume_with_web_identity(implementation);
+ unlock_provider(provider);
+ }
+ return ret;
+}
+
+int init_fn_eks(struct flb_aws_provider *provider) {
+ int ret = -1;
+ struct flb_aws_provider_eks *implementation = provider->implementation;
+
+ implementation->sts_client->debug_only = FLB_TRUE;
+
+ flb_debug("[aws_credentials] Init called on the EKS provider");
+ if (try_lock_provider(provider)) {
+ ret = assume_with_web_identity(implementation);
+ unlock_provider(provider);
+ }
+
+ implementation->sts_client->debug_only = FLB_FALSE;
+ return ret;
+}
+
+void sync_fn_eks(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_eks *implementation = provider->implementation;
+ flb_debug("[aws_credentials] Sync called on the EKS provider");
+ /* remove async flag */
+ flb_stream_disable_async_mode(&implementation->sts_client->upstream->base);
+}
+
+void async_fn_eks(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_eks *implementation = provider->implementation;
+ flb_debug("[aws_credentials] Async called on the EKS provider");
+ /* add async flag */
+ flb_stream_enable_async_mode(&implementation->sts_client->upstream->base);
+}
+
+void upstream_set_fn_eks(struct flb_aws_provider *provider,
+ struct flb_output_instance *ins) {
+ struct flb_aws_provider_eks *implementation = provider->implementation;
+ flb_debug("[aws_credentials] upstream_set called on the EKS provider");
+ /* set upstream on output */
+ flb_output_upstream_set(implementation->sts_client->upstream, ins);
+}
+
+void destroy_fn_eks(struct flb_aws_provider *provider) {
+ struct flb_aws_provider_eks *implementation = provider->
+ implementation;
+ if (implementation) {
+ if (implementation->creds) {
+ flb_aws_credentials_destroy(implementation->creds);
+ }
+
+ if (implementation->sts_client) {
+ flb_aws_client_destroy(implementation->sts_client);
+ }
+
+ if (implementation->custom_endpoint == FLB_FALSE) {
+ flb_free(implementation->endpoint);
+ }
+ if (implementation->free_session_name == FLB_TRUE) {
+ flb_free(implementation->session_name);
+ }
+
+ flb_free(implementation);
+ provider->implementation = NULL;
+ }
+
+ return;
+}
+
+static struct flb_aws_provider_vtable eks_provider_vtable = {
+ .get_credentials = get_credentials_fn_eks,
+ .init = init_fn_eks,
+ .refresh = refresh_fn_eks,
+ .destroy = destroy_fn_eks,
+ .sync = sync_fn_eks,
+ .async = async_fn_eks,
+ .upstream_set = upstream_set_fn_eks,
+};
+
+struct flb_aws_provider *flb_eks_provider_create(struct flb_config *config,
+ struct flb_tls *tls,
+ char *region,
+ char *sts_endpoint,
+ char *proxy,
+ struct
+ flb_aws_client_generator
+ *generator)
+{
+ struct flb_aws_provider_eks *implementation = NULL;
+ struct flb_aws_provider *provider = NULL;
+ struct flb_upstream *upstream = NULL;
+
+ provider = flb_calloc(1, sizeof(struct flb_aws_provider));
+
+ if (!provider) {
+ flb_errno();
+ return NULL;
+ }
+
+ pthread_mutex_init(&provider->lock, NULL);
+
+ implementation = flb_calloc(1, sizeof(struct flb_aws_provider_eks));
+
+ if (!implementation) {
+ goto error;
+ }
+
+ provider->provider_vtable = &eks_provider_vtable;
+ provider->implementation = implementation;
+
+ /* session name either comes from the env var or is a random uuid */
+ implementation->session_name = getenv(SESSION_NAME_ENV_VAR);
+ implementation->free_session_name = FLB_FALSE;
+ if (!implementation->session_name ||
+ strlen(implementation->session_name) == 0) {
+ implementation->session_name = flb_sts_session_name();
+ if (!implementation->session_name) {
+ goto error;
+ }
+ implementation->free_session_name = FLB_TRUE;
+ }
+
+ implementation->role_arn = getenv(ROLE_ARN_ENV_VAR);
+ if (!implementation->role_arn || strlen(implementation->role_arn) == 0) {
+ flb_debug("[aws_credentials] Not initializing EKS provider because"
+ " %s was not set", ROLE_ARN_ENV_VAR);
+ flb_aws_provider_destroy(provider);
+ return NULL;
+ }
+
+ implementation->token_file = getenv(TOKEN_FILE_ENV_VAR);
+ if (!implementation->token_file || strlen(implementation->token_file) == 0)
+ {
+ flb_debug("[aws_credentials] Not initializing EKS provider because"
+ " %s was not set", TOKEN_FILE_ENV_VAR);
+ flb_aws_provider_destroy(provider);
+ return NULL;
+ }
+
+ if (sts_endpoint) {
+ implementation->endpoint = removeProtocol(sts_endpoint, "https://");
+ implementation->custom_endpoint = FLB_TRUE;
+ }
+ else {
+ implementation->endpoint = flb_aws_endpoint("sts", region);
+ implementation->custom_endpoint = FLB_FALSE;
+ }
+
+ if(!implementation->endpoint) {
+ goto error;
+ }
+
+ implementation->sts_client = generator->create();
+ if (!implementation->sts_client) {
+ goto error;
+ }
+ implementation->sts_client->name = "sts_client_eks_provider";
+ /* AssumeRoleWithWebIdentity does not require sigv4 */
+ implementation->sts_client->has_auth = FLB_FALSE;
+ implementation->sts_client->provider = NULL;
+ implementation->sts_client->region = region;
+ implementation->sts_client->service = "sts";
+ implementation->sts_client->port = 443;
+ implementation->sts_client->flags = 0;
+ implementation->sts_client->proxy = proxy;
+
+ upstream = flb_upstream_create(config, implementation->endpoint, 443,
+ FLB_IO_TLS, tls);
+
+ if (!upstream) {
+ goto error;
+ }
+
+ upstream->base.net.connect_timeout = FLB_AWS_CREDENTIAL_NET_TIMEOUT;
+
+ implementation->sts_client->upstream = upstream;
+ implementation->sts_client->host = implementation->endpoint;
+
+ return provider;
+
+error:
+ flb_errno();
+ flb_aws_provider_destroy(provider);
+ return NULL;
+}
+
+/* Generates string which can serve as a unique session name */
+char *flb_sts_session_name() {
+ unsigned char random_data[SESSION_NAME_RANDOM_BYTE_LEN];
+ char *session_name = NULL;
+ int ret;
+
+ ret = flb_random_bytes(random_data, SESSION_NAME_RANDOM_BYTE_LEN);
+
+ if (ret != 0) {
+ flb_errno();
+
+ return NULL;
+ }
+
+ session_name = flb_calloc(SESSION_NAME_RANDOM_BYTE_LEN + 1,
+ sizeof(char));
+ if (session_name == NULL) {
+ flb_errno();
+
+ return NULL;
+ }
+
+ bytes_to_string(random_data, session_name, SESSION_NAME_RANDOM_BYTE_LEN);
+
+ return session_name;
+}
+
+/* converts random bytes to a string we can safely put in a URL */
+void bytes_to_string(unsigned char *data, char *buf, size_t len) {
+ int index;
+ char charset[] = "0123456789"
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ while (len-- > 0) {
+ index = (int) data[len];
+ index = index % (sizeof(charset) - 1);
+ buf[len] = charset[index];
+ }
+}
+
+static int assume_with_web_identity(struct flb_aws_provider_eks
+ *implementation)
+{
+ int ret;
+ char *web_token = NULL;
+ size_t web_token_size;
+ flb_sds_t uri = NULL;
+ int init_mode = implementation->sts_client->debug_only;
+
+ ret = flb_read_file(implementation->token_file, &web_token,
+ &web_token_size);
+ if (ret < 0) {
+ if (init_mode == FLB_TRUE) {
+ flb_debug("[aws_credentials] Could not read web identify token file");
+ } else {
+ flb_error("[aws_credentials] Could not read web identify token file");
+ }
+ return -1;
+ }
+
+ uri = flb_sts_uri("AssumeRoleWithWebIdentity", implementation->role_arn,
+ implementation->session_name, NULL, web_token);
+ if (!uri) {
+ flb_free(web_token);
+ return -1;
+ }
+
+ ret = sts_assume_role_request(implementation->sts_client,
+ &implementation->creds, uri,
+ &implementation->next_refresh);
+ flb_free(web_token);
+ flb_sds_destroy(uri);
+ return ret;
+}
+
+static int sts_assume_role_request(struct flb_aws_client *sts_client,
+ struct flb_aws_credentials **creds,
+ char *uri,
+ time_t *next_refresh)
+{
+ time_t expiration;
+ struct flb_aws_credentials *credentials = NULL;
+ struct flb_http_client *c = NULL;
+ flb_sds_t error_type;
+ int init_mode = sts_client->debug_only;
+
+ flb_debug("[aws_credentials] Calling STS..");
+
+ c = sts_client->client_vtable->request(sts_client, FLB_HTTP_GET,
+ uri, NULL, 0, NULL, 0);
+
+ if (c && c->resp.status == 200) {
+ credentials = flb_parse_sts_resp(c->resp.payload, &expiration);
+ if (!credentials) {
+ if (init_mode == FLB_TRUE) {
+ flb_debug("[aws_credentials] Failed to parse response from STS");
+ }
+ else {
+ flb_error("[aws_credentials] Failed to parse response from STS");
+ }
+ flb_http_client_destroy(c);
+ return -1;
+ }
+
+ /* unset and free existing credentials first */
+ flb_aws_credentials_destroy(*creds);
+ *creds = NULL;
+
+ *next_refresh = expiration - FLB_AWS_REFRESH_WINDOW;
+ *creds = credentials;
+ flb_http_client_destroy(c);
+ return 0;
+ }
+
+ if (c && c->resp.payload_size > 0) {
+ error_type = flb_aws_error(c->resp.payload, c->resp.payload_size);
+ if (error_type) {
+ if (init_mode == FLB_TRUE) {
+ flb_debug("[aws_credentials] STS API responded with %s", error_type);
+ }
+ else {
+ flb_error("[aws_credentials] STS API responded with %s", error_type);
+ }
+ } else {
+ flb_debug("[aws_credentials] STS raw response: \n%s",
+ c->resp.payload);
+ }
+ }
+
+ if (c) {
+ flb_http_client_destroy(c);
+ }
+ if (init_mode == FLB_TRUE) {
+ flb_debug("[aws_credentials] STS assume role request failed");
+ }
+ else {
+ flb_error("[aws_credentials] STS assume role request failed");
+ }
+ return -1;
+
+}
+
+/*
+ * The STS APIs return an XML document with credentials.
+ * The part of the document we care about looks like this:
+ * <Credentials>
+ * <AccessKeyId>akid</AccessKeyId>
+ * <SecretAccessKey>skid</SecretAccessKey>
+ * <SessionToken>token</SessionToken>
+ * <Expiration>2019-11-09T13:34:41Z</Expiration>
+ * </Credentials>
+ */
+struct flb_aws_credentials *flb_parse_sts_resp(char *response,
+ time_t *expiration)
+{
+ struct flb_aws_credentials *creds = NULL;
+ char *cred_node = NULL;
+ flb_sds_t tmp = NULL;
+
+ cred_node = strstr(response, CREDENTIALS_NODE);
+ if (!cred_node) {
+ flb_error("[aws_credentials] Could not find '%s' node in sts response",
+ CREDENTIALS_NODE);
+ return NULL;
+ }
+ cred_node += CREDENTIALS_NODE_LEN;
+
+ creds = flb_calloc(1, sizeof(struct flb_aws_credentials));
+ if (!creds) {
+ flb_errno();
+ return NULL;
+ }
+
+ creds->access_key_id = get_node(cred_node, ACCESS_KEY_NODE,
+ ACCESS_KEY_NODE_LEN, ACCESS_KEY_NODE_END);
+ if (!creds->access_key_id) {
+ goto error;
+ }
+
+ creds->secret_access_key = get_node(cred_node, SECRET_KEY_NODE,
+ SECRET_KEY_NODE_LEN, SECRET_KEY_NODE_END);
+ if (!creds->secret_access_key) {
+ goto error;
+ }
+
+ creds->session_token = get_node(cred_node, SESSION_TOKEN_NODE,
+ SESSION_TOKEN_NODE_LEN, SESSION_TOKEN_NODE_END);
+ if (!creds->session_token) {
+ goto error;
+ }
+
+ tmp = get_node(cred_node, EXPIRATION_NODE, EXPIRATION_NODE_LEN, EXPIRATION_NODE_END);
+ if (!tmp) {
+ goto error;
+ }
+ *expiration = flb_aws_cred_expiration(tmp);
+
+ flb_sds_destroy(tmp);
+ return creds;
+
+error:
+ flb_aws_credentials_destroy(creds);
+ if (tmp) {
+ flb_sds_destroy(tmp);
+ }
+ return NULL;
+}
+
+/*
+ * Constructs the STS request uri.
+ * external_id can be NULL.
+ */
+flb_sds_t flb_sts_uri(char *action, char *role_arn, char *session_name,
+ char *external_id, char *identity_token)
+{
+ flb_sds_t tmp;
+ flb_sds_t uri = NULL;
+ size_t len = STS_ASSUME_ROLE_URI_BASE_LEN;
+
+ if (external_id) {
+ len += 12; /* will add "&ExternalId=" */
+ len += strlen(external_id);
+ }
+
+ if (identity_token) {
+ len += 18; /* will add "&WebIdentityToken=" */
+ len += strlen(identity_token);
+ }
+
+
+ len += strlen(session_name);
+ len += strlen(role_arn);
+ len += strlen(action);
+ len++; /* null char */
+
+ uri = flb_sds_create_size(len);
+ if (!uri) {
+ return NULL;
+ }
+
+ tmp = flb_sds_printf(&uri, STS_ASSUME_ROLE_URI_FORMAT, action, session_name,
+ role_arn);
+ if (!tmp) {
+ flb_sds_destroy(uri);
+ return NULL;
+ }
+
+ if (external_id) {
+ flb_sds_printf(&uri, "&ExternalId=%s", external_id);
+ }
+
+ if (identity_token) {
+ flb_sds_printf(&uri, "&WebIdentityToken=%s", identity_token);
+ }
+
+ return uri;
+}
+
+static flb_sds_t get_node(char *cred_node, char* node_name, int node_name_len, char* node_end)
+{
+ char *node = NULL;
+ char *end = NULL;
+ flb_sds_t val = NULL;
+ int len;
+
+ node = strstr(cred_node, node_name);
+ if (!node) {
+ flb_error("[aws_credentials] Could not find '%s' node in sts response",
+ node_name);
+ return NULL;
+ }
+ node += node_name_len;
+ end = strstr(node, node_end);
+ if (!end) {
+ flb_error("[aws_credentials] Could not find end of '%s' node in "
+ "sts response", node_name);
+ return NULL;
+ }
+ len = end - node;
+ val = flb_sds_create_len(node, len);
+ if (!val) {
+ flb_errno();
+ return NULL;
+ }
+
+ return val;
+}
diff --git a/fluent-bit/src/aws/flb_aws_error_reporter.c b/fluent-bit/src/aws/flb_aws_error_reporter.c
new file mode 100644
index 000000000..72da9666c
--- /dev/null
+++ b/fluent-bit/src/aws/flb_aws_error_reporter.c
@@ -0,0 +1,276 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <monkey/mk_core/mk_list.h>
+
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_env.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/aws/flb_aws_error_reporter.h>
+
+/* helper function to get int type environment variable*/
+static int getenv_int(const char *name) {
+ char *value, *end;
+ long result;
+
+ value = getenv(name);
+ if (!value) {
+ return 0;
+ }
+
+ result = strtol(value, &end, 10);
+ if (*end != '\0') {
+ return 0;
+ }
+ return (int) result;
+}
+
+/* create an error reporter*/
+struct flb_aws_error_reporter *flb_aws_error_reporter_create()
+{
+ char *path_var = NULL;
+ int ttl_var, status_message_length;
+ struct flb_aws_error_reporter *error_reporter;
+ FILE *f;
+ int ret;
+
+ error_reporter = flb_calloc(1, sizeof(struct flb_aws_error_reporter));
+ if (!error_reporter) {
+ flb_errno();
+ return NULL;
+ }
+
+ /* setup error report file path */
+ path_var = getenv(STATUS_MESSAGE_FILE_PATH_ENV);
+ if (path_var == NULL) {
+ flb_free(error_reporter);
+ flb_errno();
+ return NULL;
+ }
+
+ error_reporter->file_path = flb_sds_create(path_var);
+ if (!error_reporter->file_path) {
+ flb_free(error_reporter);
+ flb_errno();
+ return NULL;
+ }
+
+ /* clean up existing file*/
+ if ((f = fopen(error_reporter->file_path, "r")) != NULL) {
+ /* file exist, try delete it*/
+ if (remove(error_reporter->file_path)) {
+ flb_free(error_reporter);
+ flb_errno();
+ return NULL;
+ }
+ }
+
+ /* setup error reporter message TTL */
+ ttl_var = getenv_int(STATUS_MESSAGE_TTL_ENV);
+ if (ttl_var <= 0) {
+ ttl_var = STATUS_MESSAGE_TTL_DEFAULT;
+ }
+ error_reporter->ttl = ttl_var;
+
+ /* setup error reporter file size */
+ status_message_length = getenv_int(STATUS_MESSAGE_MAX_BYTE_LENGTH_ENV);
+ if(status_message_length <= 0) {
+ status_message_length = STATUS_MESSAGE_MAX_BYTE_LENGTH_DEFAULT;
+ }
+ error_reporter->max_size = status_message_length;
+
+ /* create the message Linked Lists */
+ mk_list_init(&error_reporter->messages);
+
+ return error_reporter;
+}
+
+/* error reporter write the error message into reporting file and memory*/
+int flb_aws_error_reporter_write(struct flb_aws_error_reporter *error_reporter, char *msg)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_error_message *message;
+ struct flb_error_message *tmp_message;
+ flb_sds_t buf;
+ flb_sds_t buf_tmp;
+ int deleted_message_count = 0;
+ FILE *f;
+
+ if (error_reporter == NULL) {
+ return -1;
+ }
+
+ buf = flb_sds_create(msg);
+ if (!buf) {
+ flb_errno();
+ return -1;
+ }
+ /* check if the message is the same with latest one in queue*/
+ if (mk_list_is_empty(&error_reporter->messages) != 0) {
+ tmp_message = mk_list_entry_last(&error_reporter->messages,
+ struct flb_error_message, _head);
+ if (tmp_message->len == flb_sds_len(buf) &&
+ flb_sds_cmp(tmp_message->data, buf, tmp_message->len) == 0) {
+
+ tmp_message->timestamp = time(NULL);
+ flb_sds_destroy(buf);
+ return 0;
+ }
+ }
+
+ message = flb_malloc(sizeof(struct flb_error_message));
+ if (!message) {
+ flb_sds_destroy(buf);
+ flb_errno();
+ return -1;
+ }
+
+ /* check if new message is too large and truncate*/
+ if (flb_sds_len(buf) > error_reporter->max_size) {
+ // truncate message
+ buf_tmp = flb_sds_copy(buf, msg, error_reporter->max_size);
+ if (!buf_tmp) {
+ flb_sds_destroy(buf);
+ flb_free(message);
+ return -1;
+ }
+ }
+
+ message->data = flb_sds_create(buf);
+ if (!message->data) {
+ flb_sds_destroy(buf);
+ flb_free(message);
+ return -1;
+ }
+
+ message->len = flb_sds_len(buf);
+
+ /* clean up old message to provide enough space for new message*/
+ mk_list_foreach_safe(head, tmp, &error_reporter->messages) {
+ tmp_message = mk_list_entry(head, struct flb_error_message, _head);
+ if (error_reporter->file_size + flb_sds_len(buf) <= error_reporter->max_size) {
+ break;
+ }
+ else {
+ error_reporter->file_size -= tmp_message->len;
+ deleted_message_count++;
+ mk_list_del(&tmp_message->_head);
+ flb_sds_destroy(tmp_message->data);
+ flb_free(tmp_message);
+ }
+ }
+ message->timestamp = time(NULL);
+
+ mk_list_add(&message->_head, &error_reporter->messages);
+ error_reporter->file_size += message->len;
+
+ if (deleted_message_count == 0) {
+ f = fopen(error_reporter->file_path, "a");
+ fprintf(f, message->data);
+ }
+ else {
+ f = fopen(error_reporter->file_path, "w");
+ mk_list_foreach_safe(head, tmp, &error_reporter->messages) {
+ tmp_message = mk_list_entry(head, struct flb_error_message, _head);
+ fprintf(f, tmp_message->data);
+ }
+ }
+ fclose(f);
+
+ flb_sds_destroy(buf);
+
+ return 0;
+
+}
+
+/* error reporter clean up the expired message based on TTL*/
+void flb_aws_error_reporter_clean(struct flb_aws_error_reporter *error_reporter)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_error_message *message;
+ int expired_message_count = 0;
+ FILE *f;
+
+ if (error_reporter == NULL) {
+ return;
+ }
+
+ /* check the timestamp for every message and clean up expired messages*/
+ mk_list_foreach_safe(head, tmp, &error_reporter->messages) {
+ message = mk_list_entry(head, struct flb_error_message, _head);
+ if (error_reporter->ttl > time(NULL) - message->timestamp) {
+ break;
+ }
+ error_reporter->file_size -= message->len;
+ mk_list_del(&message->_head);
+ flb_sds_destroy(message->data);
+ flb_free(message);
+ expired_message_count++;
+ }
+
+ /* rewrite error report file if any message is cleaned up*/
+ if (expired_message_count > 0) {
+ f = fopen(error_reporter->file_path, "w");
+ mk_list_foreach_safe(head, tmp, &error_reporter->messages) {
+ message = mk_list_entry(head, struct flb_error_message, _head);
+ fprintf(f, message->data);
+ }
+ fclose(f);
+ }
+}
+
+/* error reporter clean up when fluent bit shutdown*/
+void flb_aws_error_reporter_destroy(struct flb_aws_error_reporter *error_reporter)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_error_message *message;
+
+ if (error_reporter == NULL) {
+ return;
+ }
+
+ if(error_reporter->file_path) {
+ flb_sds_destroy(error_reporter->file_path);
+ }
+ if (mk_list_is_empty(&error_reporter->messages) != 0) {
+
+ mk_list_foreach_safe(head, tmp, &error_reporter->messages) {
+ message = mk_list_entry(head, struct flb_error_message, _head);
+ mk_list_del(&message->_head);
+ flb_sds_destroy(message->data);
+ flb_free(message);
+ }
+ mk_list_del(&error_reporter->messages);
+ }
+
+ flb_free(error_reporter);
+}
+
+/*check if system enable error reporting*/
+int is_error_reporting_enabled()
+{
+ return getenv(STATUS_MESSAGE_FILE_PATH_ENV) != NULL;
+} \ No newline at end of file
diff --git a/fluent-bit/src/aws/flb_aws_imds.c b/fluent-bit/src/aws/flb_aws_imds.c
new file mode 100644
index 000000000..0e54db161
--- /dev/null
+++ b/fluent-bit/src/aws/flb_aws_imds.c
@@ -0,0 +1,370 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/aws/flb_aws_imds.h>
+#include <fluent-bit/flb_aws_credentials.h>
+#include <fluent-bit/flb_aws_util.h>
+#include <fluent-bit/flb_http_client.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_jsmn.h>
+
+#define FLB_AWS_IMDS_ROOT "/"
+#define FLB_AWS_IMDS_V2_TOKEN_PATH "/latest/api/token"
+
+/* Request headers */
+static struct flb_aws_header imds_v2_token_ttl_header = {
+ .key = "X-aws-ec2-metadata-token-ttl-seconds",
+ .key_len = 36,
+ .val = "21600", /* 6 hours (ie maximum ttl) */
+ .val_len = 5,
+};
+
+/* Request header templates */
+const static struct flb_aws_header imds_v2_token_token_header_template = {
+ .key = "X-aws-ec2-metadata-token",
+ .key_len = 24,
+ .val = "", /* Replace with token value */
+ .val_len = 0, /* Replace with token length */
+};
+
+/* Declarations */
+static int get_imds_version(struct flb_aws_imds *ctx);
+static int refresh_imds_v2_token(struct flb_aws_imds *ctx);
+
+/* Default config values */
+const struct flb_aws_imds_config flb_aws_imds_config_default = {
+ FLB_AWS_IMDS_VERSION_EVALUATE};
+
+/* Create IMDS context */
+struct flb_aws_imds *flb_aws_imds_create(const struct flb_aws_imds_config *imds_config,
+ struct flb_aws_client *ec2_imds_client)
+{
+ struct flb_aws_imds *ctx = NULL;
+
+ /* Create context */
+ ctx = flb_calloc(1, sizeof(struct flb_aws_imds));
+ if (!ctx) {
+ flb_errno();
+ return NULL;
+ }
+
+ /*
+ * Set IMDS version to whatever is specified in config
+ * Version may be evaluated later if set to FLB_AWS_IMDS_VERSION_EVALUATE
+ */
+ ctx->imds_version = imds_config->use_imds_version;
+ ctx->imds_v2_token = flb_sds_create_len("INVALID_TOKEN", 13);
+ ctx->imds_v2_token_len = 13;
+
+ /* Detect IMDS support */
+ if (!ec2_imds_client->upstream) {
+ flb_debug(
+ "[imds] unable to connect to EC2 IMDS. ec2_imds_client upstream is null");
+
+ flb_aws_imds_destroy(ctx);
+ return NULL;
+ }
+ if (0 != strncmp(ec2_imds_client->upstream->tcp_host, FLB_AWS_IMDS_HOST,
+ FLB_AWS_IMDS_HOST_LEN)) {
+ flb_debug("[imds] ec2_imds_client tcp host must be set to %s", FLB_AWS_IMDS_HOST);
+ flb_aws_imds_destroy(ctx);
+ return NULL;
+ }
+ if (ec2_imds_client->upstream->tcp_port != FLB_AWS_IMDS_PORT) {
+ flb_debug("[imds] ec2_imds_client tcp port must be set to %i", FLB_AWS_IMDS_PORT);
+ flb_aws_imds_destroy(ctx);
+ return NULL;
+ }
+
+ /* Connect client */
+ ctx->ec2_imds_client = ec2_imds_client;
+ return ctx;
+}
+
+/* Destroy IMDS context */
+void flb_aws_imds_destroy(struct flb_aws_imds *ctx)
+{
+ if (ctx->imds_v2_token) {
+ flb_sds_destroy(ctx->imds_v2_token);
+ }
+
+ flb_free(ctx);
+}
+
+/* Get IMDS metadata */
+int flb_aws_imds_request(struct flb_aws_imds *ctx, const char *metadata_path,
+ flb_sds_t *metadata, size_t *metadata_len)
+{
+ return flb_aws_imds_request_by_key(ctx, metadata_path, metadata, metadata_len, NULL);
+}
+
+/* Get IMDS metadata by key */
+int flb_aws_imds_request_by_key(struct flb_aws_imds *ctx, const char *metadata_path,
+ flb_sds_t *metadata, size_t *metadata_len, char *key)
+{
+ int ret;
+ flb_sds_t tmp;
+
+ struct flb_http_client *c = NULL;
+
+ struct flb_aws_client *ec2_imds_client = ctx->ec2_imds_client;
+ struct flb_aws_header token_header = imds_v2_token_token_header_template;
+
+ /* Get IMDS version */
+ int imds_version = get_imds_version(ctx);
+
+ /* Abort on version detection failure */
+ if (imds_version == FLB_AWS_IMDS_VERSION_EVALUATE) {
+ /* Exit gracefully allowing for retrys */
+ flb_warn("[imds] unable to evaluate IMDS version");
+ return -1;
+ }
+
+ if (imds_version == FLB_AWS_IMDS_VERSION_2) {
+ token_header.val = ctx->imds_v2_token;
+ token_header.val_len = ctx->imds_v2_token_len;
+ flb_debug("[imds] using IMDSv2");
+ }
+ else {
+ flb_debug("[imds] using IMDSv1");
+ }
+
+ c = ec2_imds_client->client_vtable->request(
+ ec2_imds_client, FLB_HTTP_GET, metadata_path, NULL, 0, &token_header,
+ (imds_version == FLB_AWS_IMDS_VERSION_1) ? 0 : 1);
+ if (!c) {
+ /* Exit gracefully allowing for retrys */
+ flb_warn("[imds] failed to retrieve metadata");
+ return -1;
+ }
+
+ /* Detect invalid token */
+ if (imds_version == FLB_AWS_IMDS_VERSION_2 && c->resp.status == 401) {
+ /* Refresh token and retry request */
+ flb_http_client_destroy(c);
+ ret = refresh_imds_v2_token(ctx);
+ if (ret < 0) {
+ flb_debug("[imds] failed to refresh IMDSv2 token");
+ return -1;
+ }
+ token_header.val = ctx->imds_v2_token;
+ token_header.val_len = ctx->imds_v2_token_len;
+ flb_debug("[imds] refreshed IMDSv2 token");
+ c = ec2_imds_client->client_vtable->request(
+ ec2_imds_client, FLB_HTTP_GET, metadata_path, NULL, 0, &token_header, 1);
+ if (!c) {
+ /* Exit gracefully allowing for retries */
+ flb_warn("[imds] failed to retrieve metadata");
+ return -1;
+ }
+ }
+
+ if (c->resp.status != 200) {
+ ret = -1;
+ if (c->resp.status == 404) {
+ ret = -2;
+ }
+ if (c->resp.payload_size > 0) {
+ flb_debug("[imds] metadata request failure response\n%s", c->resp.payload);
+ }
+ flb_http_client_destroy(c);
+ return ret;
+ }
+
+ if (key != NULL) {
+ /* get the value of the key from payload json string */
+ tmp = flb_json_get_val(c->resp.payload, c->resp.payload_size, key);
+ if (!tmp) {
+ tmp = flb_sds_create_len("NULL", 4);
+ flb_error("[imds] %s is undefined in EC2 instance", key);
+ }
+ }
+ else {
+ tmp = flb_sds_create_len(c->resp.payload, c->resp.payload_size);
+ }
+
+ if (!tmp) {
+ flb_errno();
+ flb_http_client_destroy(c);
+ return -1;
+ }
+
+ *metadata = tmp;
+ *metadata_len = key == NULL ? c->resp.payload_size : strlen(tmp);
+
+ flb_http_client_destroy(c);
+ return 0;
+}
+
+/* Get VPC Id */
+flb_sds_t flb_aws_imds_get_vpc_id(struct flb_aws_imds *ctx)
+{
+ int ret;
+ flb_sds_t mac_id = NULL;
+ size_t mac_len = 0;
+ flb_sds_t vpc_id = NULL;
+ size_t vpc_id_len = 0;
+
+ /* get EC2 instance Mac id first before getting VPC id */
+ ret = flb_aws_imds_request(ctx, FLB_AWS_IMDS_MAC_PATH, &mac_id, &mac_len);
+
+ if (ret < 0) {
+ flb_sds_destroy(mac_id);
+ return NULL;
+ }
+
+ /*
+ * the VPC full path should be like:
+ * latest/meta-data/network/interfaces/macs/{mac_id}/vpc-id/"
+ */
+ flb_sds_t vpc_path = flb_sds_create_size(70);
+ vpc_path =
+ flb_sds_printf(&vpc_path, "%s/%s/%s/",
+ "/latest/meta-data/network/interfaces/macs", mac_id, "vpc-id");
+ ret = flb_aws_imds_request(ctx, vpc_path, &vpc_id, &vpc_id_len);
+
+ flb_sds_destroy(mac_id);
+ flb_sds_destroy(vpc_path);
+
+ return vpc_id;
+}
+
+/* Obtain the IMDS version */
+static int get_imds_version(struct flb_aws_imds *ctx)
+{
+ int ret;
+ struct flb_aws_client *client = ctx->ec2_imds_client;
+ struct flb_aws_header invalid_token_header;
+ struct flb_http_client *c = NULL;
+
+ if (ctx->imds_version != FLB_AWS_IMDS_VERSION_EVALUATE) {
+ return ctx->imds_version;
+ }
+
+ /*
+ * Evaluate version
+ * To evaluate wether IMDSv2 is available, send an invalid token
+ * in IMDS request. If response status is 'Unauthorized', then IMDSv2
+ * is available.
+ */
+ invalid_token_header = imds_v2_token_token_header_template;
+ invalid_token_header.val = "INVALID";
+ invalid_token_header.val_len = 7;
+ c = client->client_vtable->request(client, FLB_HTTP_GET, FLB_AWS_IMDS_ROOT, NULL, 0,
+ &invalid_token_header, 1);
+
+ if (!c) {
+ flb_debug("[imds] imds endpoint unavailable");
+ return FLB_AWS_IMDS_VERSION_EVALUATE;
+ }
+
+ /* Unauthorized response means that IMDS version 2 is in use */
+ if (c->resp.status == 401) {
+ ctx->imds_version = FLB_AWS_IMDS_VERSION_2;
+ ret = refresh_imds_v2_token(ctx);
+ if (ret == -1) {
+ /*
+ * Token cannot be refreshed, test IMDSv1
+ * If IMDSv1 cannot be used, response will be status 401
+ */
+ flb_http_client_destroy(c);
+ ctx->imds_version = FLB_AWS_IMDS_VERSION_EVALUATE;
+ c = client->client_vtable->request(client, FLB_HTTP_GET, FLB_AWS_IMDS_ROOT,
+ NULL, 0, NULL, 0);
+ if (!c) {
+ flb_debug("[imds] imds v1 attempt, endpoint unavailable");
+ return FLB_AWS_IMDS_VERSION_EVALUATE;
+ }
+
+ if (c->resp.status == 200) {
+ flb_info("[imds] to use IMDSv2, set --http-put-response-hop-limit to 2");
+ }
+ else {
+ /* IMDSv1 unavailable. IMDSv2 beyond network hop count */
+ flb_warn("[imds] failed to retrieve IMDSv2 token and IMDSv1 unavailable. "
+ "This is likely due to instance-metadata-options "
+ "--http-put-response-hop-limit being set to 1 and --http-tokens "
+ "set to required. "
+ "To use IMDSv2, please set --http-put-response-hop-limit to 2 as "
+ "described https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/"
+ "configuring-instance-metadata-options.html");
+ }
+ }
+ }
+
+ /*
+ * Success means that IMDS version 1 is in use
+ */
+ if (c->resp.status == 200) {
+ flb_warn("[imds] falling back on IMDSv1");
+ ctx->imds_version = FLB_AWS_IMDS_VERSION_1;
+ }
+
+ flb_http_client_destroy(c);
+ return ctx->imds_version;
+}
+
+/*
+ * Get an IMDSv2 token
+ * Token preserved in imds context
+ */
+static int refresh_imds_v2_token(struct flb_aws_imds *ctx)
+{
+ struct flb_http_client *c = NULL;
+ struct flb_aws_client *ec2_imds_client = ctx->ec2_imds_client;
+
+ c = ec2_imds_client->client_vtable->request(ec2_imds_client, FLB_HTTP_PUT,
+ FLB_AWS_IMDS_V2_TOKEN_PATH, NULL, 0,
+ &imds_v2_token_ttl_header, 1);
+
+ if (!c) {
+ return -1;
+ }
+
+ if (c->resp.status != 200) {
+ if (c->resp.payload_size > 0) {
+ flb_error("[imds] IMDSv2 token retrieval failure response\n%s",
+ c->resp.payload);
+ }
+
+ flb_http_client_destroy(c);
+ return -1;
+ }
+
+ /* Preserve token information in ctx */
+ if (c->resp.payload_size > 0) {
+ if (ctx->imds_v2_token) {
+ flb_sds_destroy(ctx->imds_v2_token);
+ }
+ ctx->imds_v2_token = flb_sds_create_len(c->resp.payload, c->resp.payload_size);
+ if (!ctx->imds_v2_token) {
+ flb_errno();
+ flb_http_client_destroy(c);
+ return -1;
+ }
+ ctx->imds_v2_token_len = c->resp.payload_size;
+
+ flb_http_client_destroy(c);
+ return 0;
+ }
+
+ flb_debug("[imds] IMDS metadata response was empty");
+ flb_http_client_destroy(c);
+ return -1;
+}
diff --git a/fluent-bit/src/aws/flb_aws_util.c b/fluent-bit/src/aws/flb_aws_util.c
new file mode 100644
index 000000000..533bba7eb
--- /dev/null
+++ b/fluent-bit/src/aws/flb_aws_util.c
@@ -0,0 +1,1047 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_http_client.h>
+#include <fluent-bit/flb_signv4.h>
+#include <fluent-bit/flb_aws_util.h>
+#include <fluent-bit/flb_aws_credentials.h>
+#include <fluent-bit/flb_output_plugin.h>
+#include <fluent-bit/flb_jsmn.h>
+#include <fluent-bit/flb_env.h>
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#define AWS_SERVICE_ENDPOINT_FORMAT "%s.%s.amazonaws.com"
+#define AWS_SERVICE_ENDPOINT_BASE_LEN 15
+
+#define TAG_PART_DESCRIPTOR "$TAG[%d]"
+#define TAG_DESCRIPTOR "$TAG"
+#define MAX_TAG_PARTS 10
+#define S3_KEY_SIZE 1024
+#define RANDOM_STRING "$UUID"
+#define INDEX_STRING "$INDEX"
+#define AWS_USER_AGENT_NONE "none"
+#define AWS_USER_AGENT_ECS "ecs"
+#define AWS_USER_AGENT_K8S "k8s"
+#define AWS_ECS_METADATA_URI "ECS_CONTAINER_METADATA_URI_V4"
+#define FLB_MAX_AWS_RESP_BUFFER_SIZE 0 /* 0 means unlimited capacity as per requirement */
+
+#ifdef FLB_SYSTEM_WINDOWS
+#define FLB_AWS_BASE_USER_AGENT "aws-fluent-bit-plugin-windows"
+#define FLB_AWS_BASE_USER_AGENT_FORMAT "aws-fluent-bit-plugin-windows-%s"
+#define FLB_AWS_BASE_USER_AGENT_LEN 29
+#else
+#define FLB_AWS_BASE_USER_AGENT "aws-fluent-bit-plugin"
+#define FLB_AWS_BASE_USER_AGENT_FORMAT "aws-fluent-bit-plugin-%s"
+#define FLB_AWS_BASE_USER_AGENT_LEN 21
+#endif
+
+#define FLB_AWS_MILLISECOND_FORMATTER_LENGTH 3
+#define FLB_AWS_NANOSECOND_FORMATTER_LENGTH 9
+#define FLB_AWS_MILLISECOND_FORMATTER "%3N"
+#define FLB_AWS_NANOSECOND_FORMATTER_N "%9N"
+#define FLB_AWS_NANOSECOND_FORMATTER_L "%L"
+
+struct flb_http_client *request_do(struct flb_aws_client *aws_client,
+ int method, const char *uri,
+ const char *body, size_t body_len,
+ struct flb_aws_header *dynamic_headers,
+ size_t dynamic_headers_len);
+
+/*
+ * https://service.region.amazonaws.com(.cn)
+ */
+char *flb_aws_endpoint(char* service, char* region)
+{
+ char *endpoint = NULL;
+ size_t len = AWS_SERVICE_ENDPOINT_BASE_LEN;
+ int is_cn = FLB_FALSE;
+ int bytes;
+
+
+ /* In the China regions, ".cn" is appended to the URL */
+ if (strcmp("cn-north-1", region) == 0) {
+ len += 3;
+ is_cn = FLB_TRUE;
+ }
+ if (strcmp("cn-northwest-1", region) == 0) {
+ len += 3;
+ is_cn = FLB_TRUE;
+ }
+
+ len += strlen(service);
+ len += strlen(region);
+ len++; /* null byte */
+
+ endpoint = flb_calloc(len, sizeof(char));
+ if (!endpoint) {
+ flb_errno();
+ return NULL;
+ }
+
+ bytes = snprintf(endpoint, len, AWS_SERVICE_ENDPOINT_FORMAT, service, region);
+ if (bytes < 0) {
+ flb_errno();
+ flb_free(endpoint);
+ return NULL;
+ }
+
+ if (is_cn) {
+ memcpy(endpoint + bytes, ".cn", 3);
+ endpoint[bytes + 3] = '\0';
+ }
+
+ return endpoint;
+
+}
+
+int flb_read_file(const char *path, char **out_buf, size_t *out_size)
+{
+ int ret;
+ long bytes;
+ char *buf = NULL;
+ struct stat st;
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ return -1;
+ }
+
+ ret = fstat(fd, &st);
+ if (ret == -1) {
+ flb_errno();
+ close(fd);
+ return -1;
+ }
+
+ buf = flb_calloc(st.st_size + 1, sizeof(char));
+ if (!buf) {
+ flb_errno();
+ close(fd);
+ return -1;
+ }
+
+ bytes = read(fd, buf, st.st_size);
+ if (bytes < 0) {
+ flb_errno();
+ flb_free(buf);
+ close(fd);
+ return -1;
+ }
+
+ /* fread does not add null byte */
+ buf[st.st_size] = '\0';
+
+ close(fd);
+ *out_buf = buf;
+ *out_size = st.st_size;
+
+ return 0;
+}
+
+
+char *removeProtocol (char *endpoint, char *protocol) {
+ if (strncmp(protocol, endpoint, strlen(protocol)) == 0){
+ endpoint = endpoint + strlen(protocol);
+ }
+ return endpoint;
+}
+
+struct flb_http_client *flb_aws_client_request(struct flb_aws_client *aws_client,
+ int method, const char *uri,
+ const char *body, size_t body_len,
+ struct flb_aws_header
+ *dynamic_headers,
+ size_t dynamic_headers_len)
+{
+ struct flb_http_client *c = NULL;
+
+ c = request_do(aws_client, method, uri, body, body_len,
+ dynamic_headers, dynamic_headers_len);
+
+ // Auto retry if request fails
+ if (c == NULL && aws_client->retry_requests) {
+ flb_debug("[aws_client] auto-retrying");
+ c = request_do(aws_client, method, uri, body, body_len,
+ dynamic_headers, dynamic_headers_len);
+ }
+
+ /*
+ * 400 or 403 could indicate an issue with credentials- so we check for auth
+ * specific error messages and then force a refresh on the provider.
+ * For safety a refresh can be performed only once
+ * per FLB_AWS_CREDENTIAL_REFRESH_LIMIT.
+ *
+ */
+ if (c && (c->resp.status >= 400 && c->resp.status < 500)) {
+ if (aws_client->has_auth && time(NULL) > aws_client->refresh_limit) {
+ if (flb_aws_is_auth_error(c->resp.payload, c->resp.payload_size)
+ == FLB_TRUE) {
+ flb_info("[aws_client] auth error, refreshing creds");
+ aws_client->refresh_limit = time(NULL)
+ + FLB_AWS_CREDENTIAL_REFRESH_LIMIT;
+ aws_client->provider->provider_vtable->
+ refresh(aws_client->provider);
+ }
+ }
+ }
+
+ return c;
+}
+
+static struct flb_aws_client_vtable client_vtable = {
+ .request = flb_aws_client_request,
+};
+
+struct flb_aws_client *flb_aws_client_create()
+{
+ struct flb_aws_client *client = flb_calloc(1, sizeof(struct flb_aws_client));
+ if (!client) {
+ flb_errno();
+ return NULL;
+ }
+ client->client_vtable = &client_vtable;
+ client->retry_requests = FLB_FALSE;
+ client->debug_only = FLB_FALSE;
+ return client;
+}
+
+/* Generator that returns clients with the default vtable */
+
+static struct flb_aws_client_generator default_generator = {
+ .create = flb_aws_client_create,
+};
+
+struct flb_aws_client_generator *flb_aws_client_generator()
+{
+ return &default_generator;
+}
+
+void flb_aws_client_destroy(struct flb_aws_client *aws_client)
+{
+ if (aws_client) {
+ if (aws_client->upstream) {
+ flb_upstream_destroy(aws_client->upstream);
+ }
+ if (aws_client->extra_user_agent) {
+ flb_sds_destroy(aws_client->extra_user_agent);
+ }
+ flb_free(aws_client);
+ }
+}
+
+int flb_aws_is_auth_error(char *payload, size_t payload_size)
+{
+ flb_sds_t error = NULL;
+
+ if (payload_size == 0) {
+ return FLB_FALSE;
+ }
+
+ /* Fluent Bit calls the STS API which returns XML */
+ if (strcasestr(payload, "InvalidClientTokenId") != NULL) {
+ return FLB_TRUE;
+ }
+
+ if (strcasestr(payload, "AccessDenied") != NULL) {
+ return FLB_TRUE;
+ }
+
+ if (strcasestr(payload, "Expired") != NULL) {
+ return FLB_TRUE;
+ }
+
+ /* Most APIs we use return JSON */
+ error = flb_aws_error(payload, payload_size);
+ if (error != NULL) {
+ if (strcmp(error, "ExpiredToken") == 0 ||
+ strcmp(error, "ExpiredTokenException") == 0 ||
+ strcmp(error, "AccessDeniedException") == 0 ||
+ strcmp(error, "AccessDenied") == 0 ||
+ strcmp(error, "IncompleteSignature") == 0 ||
+ strcmp(error, "SignatureDoesNotMatch") == 0 ||
+ strcmp(error, "MissingAuthenticationToken") == 0 ||
+ strcmp(error, "InvalidClientTokenId") == 0 ||
+ strcmp(error, "InvalidToken") == 0 ||
+ strcmp(error, "InvalidAccessKeyId") == 0 ||
+ strcmp(error, "UnrecognizedClientException") == 0) {
+ flb_sds_destroy(error);
+ return FLB_TRUE;
+ }
+ flb_sds_destroy(error);
+ }
+
+ return FLB_FALSE;
+}
+
+struct flb_http_client *request_do(struct flb_aws_client *aws_client,
+ int method, const char *uri,
+ const char *body, size_t body_len,
+ struct flb_aws_header *dynamic_headers,
+ size_t dynamic_headers_len)
+{
+ size_t b_sent;
+ int ret;
+ struct flb_connection *u_conn = NULL;
+ flb_sds_t signature = NULL;
+ int i;
+ int normalize_uri;
+ struct flb_aws_header header;
+ struct flb_http_client *c = NULL;
+ flb_sds_t tmp;
+ flb_sds_t user_agent_prefix;
+ flb_sds_t user_agent = NULL;
+ char *buf;
+ struct flb_env *env;
+
+ u_conn = flb_upstream_conn_get(aws_client->upstream);
+ if (!u_conn) {
+ if (aws_client->debug_only == FLB_TRUE) {
+ flb_debug("[aws_client] connection initialization error");
+ }
+ else {
+ flb_error("[aws_client] connection initialization error");
+ }
+ return NULL;
+ }
+
+ /* Compose HTTP request */
+ c = flb_http_client(u_conn, method, uri,
+ body, body_len,
+ aws_client->host, aws_client->port,
+ aws_client->proxy, aws_client->flags);
+
+ if (!c) {
+ if (aws_client->debug_only == FLB_TRUE) {
+ flb_debug("[aws_client] could not initialize request");
+ }
+ else {
+ flb_error("[aws_client] could not initialize request");
+ }
+ goto error;
+ }
+
+ /* Increase the maximum HTTP response buffer size to fit large responses from AWS services */
+ ret = flb_http_buffer_size(c, FLB_MAX_AWS_RESP_BUFFER_SIZE);
+ if (ret != 0) {
+ flb_warn("[aws_http_client] failed to increase max response buffer size");
+ }
+
+ /* Set AWS Fluent Bit user agent */
+ env = aws_client->upstream->base.config->env;
+ buf = (char *) flb_env_get(env, "FLB_AWS_USER_AGENT");
+ if (buf == NULL) {
+ if (getenv(AWS_ECS_METADATA_URI) != NULL) {
+ user_agent = AWS_USER_AGENT_ECS;
+ }
+ else {
+ buf = (char *) flb_env_get(env, AWS_USER_AGENT_K8S);
+ if (buf && strcasecmp(buf, "enabled") == 0) {
+ user_agent = AWS_USER_AGENT_K8S;
+ }
+ }
+
+ if (user_agent == NULL) {
+ user_agent = AWS_USER_AGENT_NONE;
+ }
+
+ flb_env_set(env, "FLB_AWS_USER_AGENT", user_agent);
+ }
+ if (aws_client->extra_user_agent == NULL) {
+ buf = (char *) flb_env_get(env, "FLB_AWS_USER_AGENT");
+ tmp = flb_sds_create(buf);
+ if (!tmp) {
+ flb_errno();
+ goto error;
+ }
+ aws_client->extra_user_agent = tmp;
+ tmp = NULL;
+ }
+
+ /* Add AWS Fluent Bit user agent header */
+ if (strcasecmp(aws_client->extra_user_agent, AWS_USER_AGENT_NONE) == 0) {
+ ret = flb_http_add_header(c, "User-Agent", 10,
+ FLB_AWS_BASE_USER_AGENT, FLB_AWS_BASE_USER_AGENT_LEN);
+ }
+ else {
+ user_agent_prefix = flb_sds_create_size(64);
+ if (!user_agent_prefix) {
+ flb_errno();
+ flb_error("[aws_client] failed to create user agent");
+ goto error;
+ }
+ tmp = flb_sds_printf(&user_agent_prefix, FLB_AWS_BASE_USER_AGENT_FORMAT,
+ aws_client->extra_user_agent);
+ if (!tmp) {
+ flb_errno();
+ flb_sds_destroy(user_agent_prefix);
+ flb_error("[aws_client] failed to create user agent");
+ goto error;
+ }
+ user_agent_prefix = tmp;
+
+ ret = flb_http_add_header(c, "User-Agent", 10, user_agent_prefix,
+ flb_sds_len(user_agent_prefix));
+ flb_sds_destroy(user_agent_prefix);
+ }
+
+ if (ret < 0) {
+ if (aws_client->debug_only == FLB_TRUE) {
+ flb_debug("[aws_client] failed to add header to request");
+ }
+ else {
+ flb_error("[aws_client] failed to add header to request");
+ }
+ goto error;
+ }
+
+ /* add headers */
+ for (i = 0; i < aws_client->static_headers_len; i++) {
+ header = aws_client->static_headers[i];
+ ret = flb_http_add_header(c,
+ header.key, header.key_len,
+ header.val, header.val_len);
+ if (ret < 0) {
+ if (aws_client->debug_only == FLB_TRUE) {
+ flb_debug("[aws_client] failed to add header to request");
+ }
+ else {
+ flb_error("[aws_client] failed to add header to request");
+ }
+ goto error;
+ }
+ }
+
+ for (i = 0; i < dynamic_headers_len; i++) {
+ header = dynamic_headers[i];
+ ret = flb_http_add_header(c,
+ header.key, header.key_len,
+ header.val, header.val_len);
+ if (ret < 0) {
+ if (aws_client->debug_only == FLB_TRUE) {
+ flb_debug("[aws_client] failed to add header to request");
+ }
+ else {
+ flb_error("[aws_client] failed to add header to request");
+ }
+ goto error;
+ }
+ }
+
+ if (aws_client->has_auth) {
+ if (aws_client->s3_mode == S3_MODE_NONE) {
+ normalize_uri = FLB_TRUE;
+ }
+ else {
+ normalize_uri = FLB_FALSE;
+ }
+ signature = flb_signv4_do(c, normalize_uri, FLB_TRUE, time(NULL),
+ aws_client->region, aws_client->service,
+ aws_client->s3_mode, NULL,
+ aws_client->provider);
+ if (!signature) {
+ if (aws_client->debug_only == FLB_TRUE) {
+ flb_debug("[aws_client] could not sign request");
+ }
+ else {
+ flb_error("[aws_client] could not sign request");
+ }
+ goto error;
+ }
+ }
+
+ /* Perform request */
+ ret = flb_http_do(c, &b_sent);
+
+ if (ret != 0 || c->resp.status != 200) {
+ flb_debug("[aws_client] %s: http_do=%i, HTTP Status: %i",
+ aws_client->host, ret, c->resp.status);
+ }
+
+ if (ret != 0 && c != NULL) {
+ flb_http_client_destroy(c);
+ c = NULL;
+ }
+
+ flb_upstream_conn_release(u_conn);
+ flb_sds_destroy(signature);
+ return c;
+
+error:
+ if (u_conn) {
+ flb_upstream_conn_release(u_conn);
+ }
+ if (signature) {
+ flb_sds_destroy(signature);
+ }
+ if (c) {
+ flb_http_client_destroy(c);
+ }
+ return NULL;
+}
+
+void flb_aws_print_xml_error(char *response, size_t response_len,
+ char *api, struct flb_output_instance *ins)
+{
+ flb_sds_t error;
+ flb_sds_t message;
+
+ error = flb_aws_xml_get_val(response, response_len, "<Code>", "</Code>");
+ if (!error) {
+ flb_plg_error(ins, "%s: Could not parse response", api);
+ return;
+ }
+
+ message = flb_aws_xml_get_val(response, response_len, "<Message>", "</Message>");
+ if (!message) {
+ /* just print the error */
+ flb_plg_error(ins, "%s API responded with error='%s'", api, error);
+ }
+ else {
+ flb_plg_error(ins, "%s API responded with error='%s', message='%s'",
+ api, error, message);
+ flb_sds_destroy(message);
+ }
+
+ flb_sds_destroy(error);
+}
+
+/* Parses AWS XML API Error responses and returns the value of the <code> tag */
+flb_sds_t flb_aws_xml_error(char *response, size_t response_len)
+{
+ return flb_aws_xml_get_val(response, response_len, "<Code>", "</Code>");
+}
+
+/*
+ * Parses an XML document and returns the value of the given tag
+ * Param `tag` should include angle brackets; ex "<code>"
+ * And param `end` should include end brackets: "</code>"
+ */
+flb_sds_t flb_aws_xml_get_val(char *response, size_t response_len, char *tag, char *tag_end)
+{
+ flb_sds_t val = NULL;
+ char *node = NULL;
+ char *end;
+ int len;
+
+ if (response_len == 0) {
+ return NULL;
+ }
+ node = strstr(response, tag);
+ if (!node) {
+ flb_debug("[aws] Could not find '%s' tag in API response", tag);
+ return NULL;
+ }
+
+ /* advance to end of tag */
+ node += strlen(tag);
+
+ end = strstr(node, tag_end);
+ if (!end) {
+ flb_error("[aws] Could not find end of '%s' node in xml", tag);
+ return NULL;
+ }
+ len = end - node;
+ val = flb_sds_create_len(node, len);
+ if (!val) {
+ flb_errno();
+ return NULL;
+ }
+
+ return val;
+}
+
+void flb_aws_print_error(char *response, size_t response_len,
+ char *api, struct flb_output_instance *ins)
+{
+ flb_sds_t error;
+ flb_sds_t message;
+
+ error = flb_json_get_val(response, response_len, "__type");
+ if (!error) {
+ return;
+ }
+
+ message = flb_json_get_val(response, response_len, "message");
+ if (!message) {
+ /* just print the error */
+ flb_plg_error(ins, "%s API responded with error='%s'", api, error);
+ }
+ else {
+ flb_plg_error(ins, "%s API responded with error='%s', message='%s'",
+ api, error, message);
+ flb_sds_destroy(message);
+ }
+
+ flb_sds_destroy(error);
+}
+
+/* parses AWS JSON API error responses and returns the value of the __type field */
+flb_sds_t flb_aws_error(char *response, size_t response_len)
+{
+ return flb_json_get_val(response, response_len, "__type");
+}
+
+/* gets the value of a key in a json string */
+flb_sds_t flb_json_get_val(char *response, size_t response_len, char *key)
+{
+ jsmntok_t *tokens = NULL;
+ const jsmntok_t *t = NULL;
+ char *current_token = NULL;
+ jsmn_parser parser;
+ int tokens_size = 50;
+ size_t size;
+ int ret;
+ int i = 0;
+ int len;
+ flb_sds_t error_type = NULL;
+
+ jsmn_init(&parser);
+
+ size = sizeof(jsmntok_t) * tokens_size;
+ tokens = flb_calloc(1, size);
+ if (!tokens) {
+ flb_errno();
+ return NULL;
+ }
+
+ ret = jsmn_parse(&parser, response, response_len,
+ tokens, tokens_size);
+
+ if (ret == JSMN_ERROR_INVAL || ret == JSMN_ERROR_PART) {
+ flb_free(tokens);
+ flb_debug("[aws_client] Unable to parse API response- response is not"
+ " valid JSON.");
+ return NULL;
+ }
+
+ /* return value is number of tokens parsed */
+ tokens_size = ret;
+
+ /*
+ * jsmn will create an array of tokens like:
+ * key, value, key, value
+ */
+ while (i < (tokens_size - 1)) {
+ t = &tokens[i];
+
+ if (t->start == -1 || t->end == -1 || (t->start == 0 && t->end == 0)) {
+ break;
+ }
+
+ if (t->type == JSMN_STRING) {
+ current_token = &response[t->start];
+
+ if (strncmp(current_token, key, strlen(key)) == 0) {
+ i++;
+ t = &tokens[i];
+ current_token = &response[t->start];
+ len = t->end - t->start;
+ error_type = flb_sds_create_len(current_token, len);
+ if (!error_type) {
+ flb_errno();
+ flb_free(tokens);
+ return NULL;
+ }
+ break;
+ }
+ }
+
+ i++;
+ }
+ flb_free(tokens);
+ return error_type;
+}
+
+/* Generic replace function for strings. */
+static char* replace_uri_tokens(const char* original_string, const char* current_word,
+ const char* new_word)
+{
+ char *result;
+ int i = 0;
+ int count = 0;
+ int new_word_len = strlen(new_word);
+ int old_word_len = strlen(current_word);
+
+ for (i = 0; original_string[i] != '\0'; i++) {
+ if (strstr(&original_string[i], current_word) == &original_string[i]) {
+ count++;
+ i += old_word_len - 1;
+ }
+ }
+
+ result = flb_sds_create_size(i + count * (new_word_len - old_word_len) + 1);
+ if (!result) {
+ flb_errno();
+ return NULL;
+ }
+
+ i = 0;
+ while (*original_string) {
+ if (strstr(original_string, current_word) == original_string) {
+ strncpy(&result[i], new_word, new_word_len);
+ i += new_word_len;
+ original_string += old_word_len;
+ }
+ else
+ result[i++] = *original_string++;
+ }
+
+ result[i] = '\0';
+ return result;
+}
+
+/*
+ * Linux has strtok_r as the concurrent safe version
+ * Windows has strtok_s
+ */
+char* strtok_concurrent(
+ char* str,
+ char* delimiters,
+ char** context
+)
+{
+#ifdef FLB_SYSTEM_WINDOWS
+ return strtok_s(str, delimiters, context);
+#else
+ return strtok_r(str, delimiters, context);
+#endif
+}
+
+/* Constructs S3 object key as per the format. */
+flb_sds_t flb_get_s3_key(const char *format, time_t time, const char *tag,
+ char *tag_delimiter, uint64_t seq_index)
+{
+ int i = 0;
+ int ret = 0;
+ int seq_index_len;
+ char *tag_token = NULL;
+ char *key;
+ char *random_alphanumeric;
+ char *seq_index_str;
+ /* concurrent safe strtok_r requires a tracking ptr */
+ char *strtok_saveptr;
+ int len;
+ flb_sds_t tmp = NULL;
+ flb_sds_t buf = NULL;
+ flb_sds_t s3_key = NULL;
+ flb_sds_t tmp_key = NULL;
+ flb_sds_t tmp_tag = NULL;
+ struct tm gmt = {0};
+
+ if (strlen(format) > S3_KEY_SIZE){
+ flb_warn("[s3_key] Object key length is longer than the 1024 character limit.");
+ }
+
+ tmp_tag = flb_sds_create_len(tag, strlen(tag));
+ if(!tmp_tag){
+ goto error;
+ }
+
+ s3_key = flb_sds_create_len(format, strlen(format));
+ if (!s3_key) {
+ goto error;
+ }
+
+ /* Check if delimiter(s) specifed exists in the tag. */
+ for (i = 0; i < strlen(tag_delimiter); i++){
+ if (strchr(tag, tag_delimiter[i])){
+ ret = 1;
+ break;
+ }
+ }
+
+ tmp = flb_sds_create_len(TAG_PART_DESCRIPTOR, 5);
+ if (!tmp) {
+ goto error;
+ }
+ if (strstr(s3_key, tmp)){
+ if(ret == 0){
+ flb_warn("[s3_key] Invalid Tag delimiter: does not exist in tag. "
+ "tag=%s, format=%s", tag, format);
+ }
+ }
+
+ flb_sds_destroy(tmp);
+ tmp = NULL;
+
+ /* Split the string on the delimiters */
+ tag_token = strtok_concurrent(tmp_tag, tag_delimiter, &strtok_saveptr);
+
+ /* Find all occurences of $TAG[*] and
+ * replaces it with the right token from tag.
+ */
+ i = 0;
+ while(tag_token != NULL && i < MAX_TAG_PARTS) {
+ buf = flb_sds_create_size(10);
+ if (!buf) {
+ goto error;
+ }
+ tmp = flb_sds_printf(&buf, TAG_PART_DESCRIPTOR, i);
+ if (!tmp) {
+ goto error;
+ }
+
+ tmp_key = replace_uri_tokens(s3_key, tmp, tag_token);
+ if (!tmp_key) {
+ goto error;
+ }
+
+ if(strlen(tmp_key) > S3_KEY_SIZE){
+ flb_warn("[s3_key] Object key length is longer than the 1024 character limit.");
+ }
+
+ if (buf != tmp) {
+ flb_sds_destroy(buf);
+ }
+ flb_sds_destroy(tmp);
+ tmp = NULL;
+ buf = NULL;
+ flb_sds_destroy(s3_key);
+ s3_key = tmp_key;
+ tmp_key = NULL;
+
+ tag_token = strtok_concurrent(NULL, tag_delimiter, &strtok_saveptr);
+ i++;
+ }
+
+ tmp = flb_sds_create_len(TAG_PART_DESCRIPTOR, 5);
+ if (!tmp) {
+ goto error;
+ }
+
+ /* A match against "$TAG[" indicates an invalid or out of bounds tag part. */
+ if (strstr(s3_key, tmp)){
+ flb_warn("[s3_key] Invalid / Out of bounds tag part: At most 10 tag parts "
+ "($TAG[0] - $TAG[9]) can be processed. tag=%s, format=%s, delimiters=%s",
+ tag, format, tag_delimiter);
+ }
+
+ /* Find all occurences of $TAG and replace with the entire tag. */
+ tmp_key = replace_uri_tokens(s3_key, TAG_DESCRIPTOR, tag);
+ if (!tmp_key) {
+ goto error;
+ }
+
+ if(strlen(tmp_key) > S3_KEY_SIZE){
+ flb_warn("[s3_key] Object key length is longer than the 1024 character limit.");
+ }
+
+ flb_sds_destroy(s3_key);
+ s3_key = tmp_key;
+ tmp_key = NULL;
+
+ /* Find all occurences of $INDEX and replace with the appropriate index. */
+ if (strstr((char *) format, INDEX_STRING)) {
+ seq_index_len = snprintf(NULL, 0, "%"PRIu64, seq_index);
+ seq_index_str = flb_calloc(seq_index_len + 1, sizeof(char));
+ if (seq_index_str == NULL) {
+ goto error;
+ }
+
+ sprintf(seq_index_str, "%"PRIu64, seq_index);
+ seq_index_str[seq_index_len] = '\0';
+ tmp_key = replace_uri_tokens(s3_key, INDEX_STRING, seq_index_str);
+ if (tmp_key == NULL) {
+ flb_free(seq_index_str);
+ goto error;
+ }
+ if (strlen(tmp_key) > S3_KEY_SIZE) {
+ flb_warn("[s3_key] Object key length is longer than the 1024 character limit.");
+ }
+
+ flb_sds_destroy(s3_key);
+ s3_key = tmp_key;
+ tmp_key = NULL;
+ flb_free(seq_index_str);
+ }
+
+ /* Find all occurences of $UUID and replace with a random string. */
+ random_alphanumeric = flb_sts_session_name();
+ if (!random_alphanumeric) {
+ goto error;
+ }
+ /* only use 8 chars of the random string */
+ random_alphanumeric[8] = '\0';
+ tmp_key = replace_uri_tokens(s3_key, RANDOM_STRING, random_alphanumeric);
+ if (!tmp_key) {
+ flb_free(random_alphanumeric);
+ goto error;
+ }
+
+ if(strlen(tmp_key) > S3_KEY_SIZE){
+ flb_warn("[s3_key] Object key length is longer than the 1024 character limit.");
+ }
+
+ flb_sds_destroy(s3_key);
+ s3_key = tmp_key;
+ tmp_key = NULL;
+ flb_free(random_alphanumeric);
+
+ if (!gmtime_r(&time, &gmt)) {
+ flb_error("[s3_key] Failed to create timestamp.");
+ goto error;
+ }
+
+ flb_sds_destroy(tmp);
+ tmp = NULL;
+
+ /* A string no longer than S3_KEY_SIZE + 1 is created to store the formatted timestamp. */
+ key = flb_calloc(1, (S3_KEY_SIZE + 1) * sizeof(char));
+ if (!key) {
+ goto error;
+ }
+
+ ret = strftime(key, S3_KEY_SIZE, s3_key, &gmt);
+ if(ret == 0){
+ flb_warn("[s3_key] Object key length is longer than the 1024 character limit.");
+ }
+ flb_sds_destroy(s3_key);
+
+ len = strlen(key);
+ if (len > S3_KEY_SIZE) {
+ len = S3_KEY_SIZE;
+ }
+
+ s3_key = flb_sds_create_len(key, len);
+ flb_free(key);
+ if (!s3_key) {
+ goto error;
+ }
+
+ flb_sds_destroy(tmp_tag);
+ tmp_tag = NULL;
+ return s3_key;
+
+ error:
+ flb_errno();
+ if (tmp_tag){
+ flb_sds_destroy(tmp_tag);
+ }
+ if (s3_key){
+ flb_sds_destroy(s3_key);
+ }
+ if (buf && buf != tmp){
+ flb_sds_destroy(buf);
+ }
+ if (tmp){
+ flb_sds_destroy(tmp);
+ }
+ if (tmp_key){
+ flb_sds_destroy(tmp_key);
+ }
+ return NULL;
+}
+
+/*
+ * This function is an extension to strftime which can support milliseconds with %3N,
+ * support nanoseconds with %9N or %L. The return value is the length of formatted
+ * time string.
+ */
+size_t flb_aws_strftime_precision(char **out_buf, const char *time_format,
+ struct flb_time *tms)
+{
+ char millisecond_str[FLB_AWS_MILLISECOND_FORMATTER_LENGTH+1];
+ char nanosecond_str[FLB_AWS_NANOSECOND_FORMATTER_LENGTH+1];
+ char *tmp_parsed_time_str;
+ char *buf;
+ size_t out_size;
+ size_t tmp_parsed_time_str_len;
+ size_t time_format_len;
+ struct tm timestamp;
+ struct tm *tmp;
+ int i;
+
+ /*
+ * Guess the max length needed for tmp_parsed_time_str and tmp_out_buf. The
+ * upper bound is 12*strlen(time_format) because the worst scenario will be only
+ * %c in time_format, and %c will be transfer to 24 chars long by function strftime().
+ */
+ time_format_len = strlen(time_format);
+ tmp_parsed_time_str_len = 12*time_format_len;
+
+ /*
+ * Use tmp_parsed_time_str to buffer when replace %3N with milliseconds, replace
+ * %9N and %L with nanoseconds in time_format.
+ */
+ tmp_parsed_time_str = (char *)flb_calloc(1, tmp_parsed_time_str_len*sizeof(char));
+ if (!tmp_parsed_time_str) {
+ flb_errno();
+ return 0;
+ }
+
+ buf = (char *)flb_calloc(1, tmp_parsed_time_str_len*sizeof(char));
+ if (!buf) {
+ flb_errno();
+ flb_free(tmp_parsed_time_str);
+ return 0;
+ }
+
+ /* Replace %3N to millisecond, %9N and %L to nanosecond in time_format. */
+ snprintf(millisecond_str, FLB_AWS_MILLISECOND_FORMATTER_LENGTH+1,
+ "%" PRIu64, (uint64_t) tms->tm.tv_nsec / 1000000);
+ snprintf(nanosecond_str, FLB_AWS_NANOSECOND_FORMATTER_LENGTH+1,
+ "%" PRIu64, (uint64_t) tms->tm.tv_nsec);
+ for (i = 0; i < time_format_len; i++) {
+ if (strncmp(time_format+i, FLB_AWS_MILLISECOND_FORMATTER, 3) == 0) {
+ strncat(tmp_parsed_time_str, millisecond_str,
+ FLB_AWS_MILLISECOND_FORMATTER_LENGTH+1);
+ i += 2;
+ }
+ else if (strncmp(time_format+i, FLB_AWS_NANOSECOND_FORMATTER_N, 3) == 0) {
+ strncat(tmp_parsed_time_str, nanosecond_str,
+ FLB_AWS_NANOSECOND_FORMATTER_LENGTH+1);
+ i += 2;
+ }
+ else if (strncmp(time_format+i, FLB_AWS_NANOSECOND_FORMATTER_L, 2) == 0) {
+ strncat(tmp_parsed_time_str, nanosecond_str,
+ FLB_AWS_NANOSECOND_FORMATTER_LENGTH+1);
+ i += 1;
+ }
+ else {
+ strncat(tmp_parsed_time_str,time_format+i,1);
+ }
+ }
+
+ tmp = gmtime_r(&tms->tm.tv_sec, &timestamp);
+ if (!tmp) {
+ return 0;
+ }
+
+ out_size = strftime(buf, tmp_parsed_time_str_len,
+ tmp_parsed_time_str, &timestamp);
+
+ /* Check whether tmp_parsed_time_str_len is enough for tmp_out_buff */
+ if (out_size == 0) {
+ flb_free(tmp_parsed_time_str);
+ flb_free(buf);
+ return 0;
+ }
+
+ *out_buf = buf;
+ flb_free(tmp_parsed_time_str);
+
+ return out_size;
+}
diff --git a/fluent-bit/src/config_format/flb_cf_fluentbit.c b/fluent-bit/src/config_format/flb_cf_fluentbit.c
new file mode 100644
index 000000000..a775fffe0
--- /dev/null
+++ b/fluent-bit/src/config_format/flb_cf_fluentbit.c
@@ -0,0 +1,804 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_config_format.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_compat.h>
+
+#include <monkey/mk_core.h>
+
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifndef _MSC_VER
+#include <glob.h>
+#endif
+
+#ifdef _WIN32
+#include <Windows.h>
+#include <strsafe.h>
+#define PATH_MAX MAX_PATH
+#endif
+
+#define FLB_CF_FILE_NUM_LIMIT 1000
+
+/* indent checker return codes */
+#define INDENT_ERROR -1
+#define INDENT_OK 0
+#define INDENT_GROUP_CONTENT 1
+
+/* Included file by configuration */
+struct local_file {
+ flb_sds_t path;
+ struct mk_list _head;
+};
+
+/* Local context to keep state of variables and general */
+struct local_ctx {
+ int level;
+ char *file;
+ flb_sds_t root_path;
+
+ /* included files */
+ struct mk_list includes;
+
+ /* meta instructions */
+ struct mk_list metas;
+
+ /* list of sections */
+ struct mk_list sections;
+};
+
+static int read_config(struct flb_cf *cf, struct local_ctx *ctx, char *cfg_file,
+ char *buf, size_t size, ino_t *ino_table, int *ino_num);
+
+/* Raise a configuration schema error */
+static void config_error(const char *path, int line, const char *msg)
+{
+ flb_error("[config] error in %s:%i: %s", path, line, msg);
+}
+
+/* Raise a warning */
+static void config_warn(const char *path, int line, const char *msg)
+{
+ mk_warn("Config file warning '%s':\n"
+ "\t\t\t\tat line %i: %s",
+ path, line, msg);
+}
+
+static int char_search(const char *string, int c, int len)
+{
+ char *p;
+
+ if (len < 0) {
+ len = strlen(string);
+ }
+
+ p = memchr(string, c, len);
+ if (p) {
+ return (p - string);
+ }
+
+ return -1;
+}
+
+/*
+ * Helper function to simulate a fgets(2) but instead of using a real file stream
+ * uses the data buffer provided.
+ */
+#ifdef FLB_HAVE_STATIC_CONF
+
+static int static_fgets(char *out, size_t size, const char *data, size_t *off)
+{
+ size_t len;
+ const char *start = data + *off;
+ char *end;
+
+ end = strchr(start, '\n');
+
+ if (!end || *off >= size) {
+ len = size - *off - 1;
+ memcpy(out, start, len);
+ out[len] = '\0';
+ *off += len + 1;
+ return 0;
+ }
+
+ len = end - start;
+ if (len >= size) {
+ len = size - 1;
+ }
+ memcpy(out, start, len);
+ out[len] = '\0';
+ *off += len + 1;
+
+ return 1;
+}
+#endif
+
+#ifndef _WIN32
+static int read_glob(struct flb_cf *cf, struct local_ctx *ctx, const char * path,
+ ino_t *ino_table, int *ino_num)
+{
+ int ret = -1;
+ glob_t glb;
+ char tmp[PATH_MAX];
+
+ const char *glb_path;
+ size_t i;
+ int ret_glb = -1;
+
+ if (ctx->root_path && path[0] != '/') {
+ snprintf(tmp, PATH_MAX, "%s/%s", ctx->root_path, path);
+ glb_path = tmp;
+ }
+ else {
+ glb_path = path;
+ }
+
+ ret_glb = glob(glb_path, GLOB_NOSORT, NULL, &glb);
+ if (ret_glb != 0) {
+ switch(ret_glb){
+ case GLOB_NOSPACE:
+ flb_warn("[%s] glob: [%s] no space", __FUNCTION__, glb_path);
+ break;
+ case GLOB_NOMATCH:
+ flb_warn("[%s] glob: [%s] no match", __FUNCTION__, glb_path);
+ break;
+ case GLOB_ABORTED:
+ flb_warn("[%s] glob: [%s] aborted", __FUNCTION__, glb_path);
+ break;
+ default:
+ flb_warn("[%s] glob: [%s] other error", __FUNCTION__, glb_path);
+ }
+ return ret;
+ }
+
+ for (i = 0; i < glb.gl_pathc; i++) {
+ ret = read_config(cf, ctx, glb.gl_pathv[i], NULL, 0, ino_table, ino_num);
+ if (ret < 0) {
+ break;
+ }
+ }
+
+ globfree(&glb);
+ return ret;
+}
+#else
+static int read_glob(struct flb_cf *cf, struct local_ctx *ctx, const char *path,
+ ino_t *ino_table, int *ino_num)
+{
+ char *star, *p0, *p1;
+ char pattern[MAX_PATH];
+ char buf[MAX_PATH];
+ int ret;
+ struct stat st;
+ HANDLE h;
+ WIN32_FIND_DATA data;
+
+ if (strlen(path) > MAX_PATH - 1) {
+ return -1;
+ }
+
+ star = strchr(path, '*');
+ if (star == NULL) {
+ return -1;
+ }
+
+ /*
+ * C:\data\tmp\input_*.conf
+ * 0<-----|
+ */
+ p0 = star;
+ while (path <= p0 && *p0 != '\\') {
+ p0--;
+ }
+
+ /*
+ * C:\data\tmp\input_*.conf
+ * |---->1
+ */
+ p1 = star;
+ while (*p1 && *p1 != '\\') {
+ p1++;
+ }
+
+ memcpy(pattern, path, (p1 - path));
+ pattern[p1 - path] = '\0';
+
+ h = FindFirstFileA(pattern, &data);
+ if (h == INVALID_HANDLE_VALUE) {
+ return 0;
+ }
+
+ do {
+ /* Ignore the current and parent dirs */
+ if (!strcmp(".", data.cFileName) || !strcmp("..", data.cFileName)) {
+ continue;
+ }
+
+ /* Avoid an infinite loop */
+ if (strchr(data.cFileName, '*')) {
+ continue;
+ }
+
+ /* Create a path (prefix + filename + suffix) */
+ memcpy(buf, path, p0 - path + 1);
+ buf[p0 - path + 1] = '\0';
+
+ if (FAILED(StringCchCatA(buf, MAX_PATH, data.cFileName))) {
+ continue;
+ }
+ if (FAILED(StringCchCatA(buf, MAX_PATH, p1))) {
+ continue;
+ }
+
+ if (strchr(p1, '*')) {
+ read_glob(cf, ctx, buf, ino_table, ino_num); /* recursive */
+ continue;
+ }
+
+ ret = stat(buf, &st);
+ if (ret == 0 && (st.st_mode & S_IFMT) == S_IFREG) {
+ if (read_config(cf, ctx, buf, NULL, 0, ino_table, ino_num) < 0) {
+ return -1;
+ }
+ }
+ } while (FindNextFileA(h, &data) != 0);
+
+ FindClose(h);
+ return 0;
+}
+#endif
+
+static int local_init(struct local_ctx *ctx, char *file)
+{
+ char *end;
+ char path[PATH_MAX + 1] = {0};
+
+#ifndef FLB_HAVE_STATIC_CONF
+ char *p;
+
+ if (file) {
+#ifdef _MSC_VER
+ p = _fullpath(path, file, PATH_MAX + 1);
+#else
+ p = realpath(file, path);
+#endif
+ if (!p) {
+ flb_errno();
+ flb_error("file=%s", file);
+ return -1;
+ }
+ }
+#endif
+
+ /* lookup path ending and truncate */
+#ifdef _MSC_VER
+ end = strrchr(path, '\\');
+#else
+ end = strrchr(path, '/');
+#endif
+
+ if (end) {
+ end++;
+ *end = '\0';
+ }
+
+ if (file) {
+ ctx->file = flb_sds_create(file);
+ ctx->root_path = flb_sds_create(path);
+ }
+ else {
+ ctx->file = NULL;
+ ctx->root_path = NULL;
+ }
+
+ ctx->level = 0;
+ mk_list_init(&ctx->metas);
+ mk_list_init(&ctx->sections);
+ mk_list_init(&ctx->includes);
+
+ return 0;
+}
+
+static void local_exit(struct local_ctx *ctx)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct local_file *f;
+
+ mk_list_foreach_safe(head, tmp, &ctx->includes) {
+ f = mk_list_entry(head, struct local_file, _head);
+ flb_sds_destroy(f->path);
+ mk_list_del(&f->_head);
+ flb_free(f);
+ }
+
+ if (ctx->file) {
+ flb_sds_destroy(ctx->file);
+ }
+
+ if (ctx->root_path) {
+ flb_sds_destroy(ctx->root_path);
+ }
+}
+
+static int is_file_included(struct local_ctx *ctx, const char *path)
+{
+ struct mk_list *head;
+ struct local_file *file;
+
+ mk_list_foreach(head, &ctx->includes) {
+ file = mk_list_entry(head, struct local_file, _head);
+ if (strcmp(file->path, path) == 0) {
+ return FLB_TRUE;
+ }
+ }
+
+ return FLB_FALSE;
+}
+
+static int check_indent(const char *line, const char *indent, int *out_level)
+{
+ int extra = 0;
+ int level = 0;
+
+ while (*line == *indent && *indent) {
+ line++;
+ indent++;
+ level++;
+ }
+
+ if (*indent != '\0') {
+ if (isblank(*line)) {
+ flb_error("[config] inconsistent use of tab and space");
+ }
+ else {
+ flb_error("[config] indentation level is too low");
+ }
+ return INDENT_ERROR;;
+ }
+
+ if (isblank(*line)) {
+ /* check if we have a 'group' key/value line */
+ while (isblank(*line)) {
+ line++;
+ extra++;
+ }
+
+ if (extra == level) {
+ *out_level = level + extra;
+ return INDENT_GROUP_CONTENT;
+ }
+
+ flb_error("[config] extra indentation level found");
+ return -1;
+ }
+
+ *out_level = level;
+ return INDENT_OK;
+}
+
+static int read_config(struct flb_cf *cf, struct local_ctx *ctx,
+ char *cfg_file, char *in_data, size_t in_size,
+ ino_t *ino_table, int *ino_num)
+{
+ int i;
+ int len;
+ int ret;
+ int end;
+ int level;
+ int line = 0;
+ int indent_len = -1;
+ int n_keys = 0;
+ char *key = NULL;
+ int key_len;
+ char *val = NULL;
+ int val_len;
+ char *buf;
+ char *fgets_ptr;
+ size_t bufsize = FLB_DEFAULT_CF_BUF_SIZE;
+ char tmp[PATH_MAX];
+ flb_sds_t section = NULL;
+ flb_sds_t indent = NULL;
+ struct stat st;
+ struct local_file *file;
+ struct flb_cf_meta *meta;
+ struct flb_cf_section *current_section = NULL;
+ struct flb_cf_group *current_group = NULL;
+ struct cfl_variant *var;
+ unsigned long line_hard_limit;
+
+ line_hard_limit = 32 * 1024 * 1024; /* 32MiB */
+
+ FILE *f = NULL;
+
+ if (*ino_num >= FLB_CF_FILE_NUM_LIMIT) {
+ return -1;
+ }
+
+ /* Check if the path exists (relative cases for included files) */
+#ifndef FLB_HAVE_STATIC_CONF
+ if (ctx->level >= 0) {
+ ret = stat(cfg_file, &st);
+ if (ret == -1 && errno == ENOENT) {
+ /* Try to resolve the real path (if exists) */
+ if (cfg_file[0] == '/') {
+ return -1;
+ }
+
+ if (ctx->root_path) {
+ snprintf(tmp, PATH_MAX, "%s/%s", ctx->root_path, cfg_file);
+ cfg_file = tmp;
+ }
+ /* stat again */
+ ret = stat(cfg_file, &st);
+ if (ret < 0) {
+ flb_errno();
+ return -1;
+ }
+ }
+#ifndef _WIN32
+ /* check if readed file */
+ for (i=0; i<*ino_num; i++) {
+ if (st.st_ino == ino_table[i]) {
+ flb_warn("[config] Read twice. path=%s", cfg_file);
+ return -1;
+ }
+ }
+ ino_table[*ino_num] = st.st_ino;
+ *ino_num += 1;
+#endif
+ }
+#endif
+
+ /* Check this file have not been included before */
+ ret = is_file_included(ctx, cfg_file);
+ if (ret) {
+ flb_error("[config] file already included %s", cfg_file);
+ return -1;
+ }
+ ctx->level++;
+
+#ifndef FLB_HAVE_STATIC_CONF
+ /* Open configuration file */
+ if ((f = fopen(cfg_file, "rb")) == NULL) {
+ flb_warn("[config] I cannot open %s file", cfg_file);
+ return -1;
+ }
+#endif
+
+ /* Allocate temporal buffer to read file content */
+ buf = flb_malloc(bufsize);
+ if (!buf) {
+ flb_errno();
+ goto error;
+ }
+
+#ifdef FLB_HAVE_STATIC_CONF
+ /*
+ * a static configuration comes from a buffer, so we use the static_fgets()
+ * workaround to retrieve the lines.
+ */
+ size_t off = 0;
+ while (static_fgets(buf, FLB_CF_BUF_SIZE, in_data, &off)) {
+#else
+ /* normal mode, read lines into a buffer */
+ /* note that we use "fgets_ptr" so we can continue reading after realloc */
+ fgets_ptr = buf;
+ while (fgets(fgets_ptr, bufsize - (fgets_ptr - buf), f)) {
+#endif
+ len = strlen(buf);
+ if (len > 0 && buf[len - 1] == '\n') {
+ buf[--len] = 0;
+ if (len && buf[len - 1] == '\r') {
+ buf[--len] = 0;
+ }
+ /* after a successful line read, restore "fgets_ptr" to point to the
+ * beginning of buffer */
+ fgets_ptr = buf;
+ } else if (feof(f)) {
+ /* handle EOF without a newline(CRLF or LF) */
+ fgets_ptr = buf;
+ }
+#ifndef FLB_HAVE_STATIC_CONF
+ else {
+ /* resize the line buffer */
+ bufsize *= 2;
+ if (bufsize > line_hard_limit) {
+ flb_error("reading line is exceeded to the limit size of %lu. Current size is: %zu",
+ line_hard_limit, bufsize);
+ goto error;
+ }
+ buf = flb_realloc(buf, bufsize);
+ if (!buf) {
+ flb_error("failed to resize line buffer to %zu", bufsize);
+ flb_errno();
+ goto error;
+ }
+ /* read more, starting at the buf + len position */
+ fgets_ptr = buf + len;
+ continue;
+ }
+#endif
+
+ /* Line number */
+ line++;
+
+ if (!buf[0]) {
+ continue;
+ }
+
+ /* Skip commented lines */
+ if (buf[0] == '#') {
+ continue;
+ }
+
+ if (len > 9 && strncasecmp(buf, "@INCLUDE ", 9) == 0) {
+ if (strchr(buf + 9, '*') != NULL) {
+ ret = read_glob(cf, ctx, buf + 9, ino_table, ino_num);
+ }
+ else {
+ ret = read_config(cf, ctx, buf + 9, NULL, 0, ino_table, ino_num);
+ }
+ if (ret == -1) {
+ ctx->level--;
+ if (indent) {
+ flb_sds_destroy(indent);
+ indent = NULL;
+ }
+ goto error;
+ }
+ continue;
+ }
+ else if (buf[0] == '@' && len > 3) {
+ meta = flb_cf_meta_property_add(cf, buf, len);
+ if (meta == NULL) {
+ goto error;
+ }
+ continue;
+ }
+
+ /* Section definition */
+ if (buf[0] == '[') {
+ current_group = NULL;
+
+ end = char_search(buf, ']', len);
+ if (end > 0) {
+ /*
+ * Before to add a new section, lets check the previous
+ * one have at least one key set
+ */
+ if (current_section && n_keys == 0) {
+ config_warn(cfg_file, line,
+ "previous section did not have keys");
+ }
+
+ /* Create new section */
+ current_section = flb_cf_section_create(cf, buf + 1, end - 1);
+ if (!current_section) {
+ continue;
+ }
+ current_group = NULL;
+ n_keys = 0;
+ continue;
+ }
+ else {
+ config_error(cfg_file, line, "bad header definition");
+ goto error;
+ }
+ }
+
+ /* No separator defined */
+ if (!indent) {
+ i = 0;
+
+ do { i++; } while (i < len && isblank(buf[i]));
+
+ indent = flb_sds_create_len(buf, i);
+ indent_len = flb_sds_len(indent);
+
+ /* Blank indented line */
+ if (i == len) {
+ continue;
+ }
+ }
+
+ /* Validate indentation level */
+ ret = check_indent(buf, indent, &level);
+ if (ret == INDENT_ERROR) {
+ config_error(cfg_file, line, "invalid indentation level");
+ goto error;
+ }
+ else {
+ if (ret == INDENT_OK && current_group) {
+ current_group = NULL;
+ }
+ indent_len = level;
+ }
+
+ if (buf[indent_len] == '#' || indent_len == len) {
+ continue;
+ }
+
+ /* get the key value separator */
+ i = char_search(buf + indent_len, ' ', len - indent_len);
+
+ /* key */
+ key = buf + indent_len;
+ key_len = i;
+
+ if (!key) {
+ config_error(cfg_file, line, "undefined key - check config is in valid classic format");
+ goto error;
+ }
+ else if(i < 0) {
+ config_error(cfg_file, line, "undefined value - check config is in valid classic format");
+ goto error;
+ }
+
+ /* Check possible start of a group */
+ if (key[0] == '[') {
+ end = char_search(key, ']', len - indent_len);
+ if (end == -1) {
+ config_error(cfg_file, line, "expected a valid group name: [..]");
+ goto error;
+ }
+
+ if (!current_section) {
+ config_warn(cfg_file, line,
+ "current group don't have a parent section");
+ goto error;
+ }
+
+ /* check if a previous group exists with one key */
+ if (current_group && n_keys == 0) {
+ config_warn(cfg_file, line, "previous group did not have keys");
+ goto error;
+ }
+
+ /* Create new group */
+ current_group = flb_cf_group_create(cf, current_section,
+ key + 1, end - 1);
+ if (!current_group) {
+ continue;
+ }
+ n_keys = 0;
+
+ /* continue processing since we need key/value pairs */
+ continue;
+ }
+
+ /* val */
+ val = buf + indent_len + i + 1;
+ val_len = len - indent_len - i - 1;
+
+ if (!key || !val || i < 0) {
+ config_error(cfg_file, line, "each key must have a value");
+ goto error;
+ }
+
+ if (val_len == 0) {
+ config_error(cfg_file, line, "key has an empty value");
+ goto error;
+ }
+
+ /* register entry: key and val are copied as duplicated */
+ var = NULL;
+ if (current_group) {
+ var = flb_cf_section_property_add(cf, current_group->properties,
+ key, key_len,
+ val, val_len);
+ }
+ else if (current_section) {
+ var = flb_cf_section_property_add(cf, current_section->properties,
+ key, key_len,
+ val, val_len);
+ }
+ if (var == NULL) {
+ config_error(cfg_file, line, "could not allocate key value pair");
+ goto error;
+ }
+
+ /* Free temporary key and val */
+ n_keys++;
+ }
+
+ if (section && n_keys == 0) {
+ /* No key, no warning */
+ }
+
+ if (f) {
+ fclose(f);
+ }
+
+ if (indent) {
+ flb_sds_destroy(indent);
+ indent = NULL;
+ }
+ flb_free(buf);
+
+ /* Append this file to the list */
+ file = flb_malloc(sizeof(struct local_file));
+ if (!file) {
+ flb_errno();
+ ctx->level--;
+ goto error;
+ }
+ file->path = flb_sds_create(cfg_file);
+ mk_list_add(&file->_head, &ctx->includes);
+ ctx->level--;
+
+ return 0;
+
+error:
+ if (f) {
+ fclose(f);
+ }
+ if (indent) {
+ flb_sds_destroy(indent);
+ }
+ flb_free(buf);
+ return -1;
+}
+
+struct flb_cf *flb_cf_fluentbit_create(struct flb_cf *cf,
+ char *file_path, char *buf, size_t size)
+{
+ int ret;
+ struct local_ctx ctx;
+ ino_t ino_table[FLB_CF_FILE_NUM_LIMIT];
+ int ino_num = 0;
+
+ if (!cf) {
+ cf = flb_cf_create();
+ if (!cf) {
+ return NULL;
+ }
+
+ flb_cf_set_origin_format(cf, FLB_CF_CLASSIC);
+ }
+
+ ret = local_init(&ctx, file_path);
+ if (ret != 0) {
+ if (cf) {
+ flb_cf_destroy(cf);
+ }
+ return NULL;
+ }
+
+ ret = read_config(cf, &ctx, file_path, buf, size, &ino_table[0], &ino_num);
+
+ local_exit(&ctx);
+
+ if (ret == -1) {
+ flb_cf_destroy(cf);
+ if (ino_num >= FLB_CF_FILE_NUM_LIMIT) {
+ flb_error("Too many config files. Limit = %d", FLB_CF_FILE_NUM_LIMIT);
+ }
+ return NULL;
+ }
+
+ return cf;
+}
diff --git a/fluent-bit/src/config_format/flb_cf_yaml.c b/fluent-bit/src/config_format/flb_cf_yaml.c
new file mode 100644
index 000000000..289760ec7
--- /dev/null
+++ b/fluent-bit/src/config_format/flb_cf_yaml.c
@@ -0,0 +1,2110 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_config_format.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_slist.h>
+
+#include <cfl/cfl.h>
+#include <cfl/cfl_sds.h>
+#include <cfl/cfl_variant.h>
+#include <cfl/cfl_kvlist.h>
+
+#include <yaml.h>
+
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifndef _MSC_VER
+#include <glob.h>
+#endif
+
+#ifdef _WIN32
+#include <Windows.h>
+#include <strsafe.h>
+#define PATH_MAX MAX_PATH
+#endif
+
+#include <stdio.h>
+
+enum section {
+ SECTION_ENV,
+ SECTION_INCLUDE,
+ SECTION_SERVICE,
+ SECTION_PIPELINE,
+ SECTION_CUSTOM,
+ SECTION_INPUT,
+ SECTION_FILTER,
+ SECTION_OUTPUT,
+ SECTION_PROCESSOR,
+ SECTION_OTHER,
+};
+
+static char *section_names[] = {
+ "env",
+ "include",
+ "service",
+ "pipeline",
+ "custom",
+ "input",
+ "filter",
+ "output",
+ "processor",
+ "other"
+};
+
+struct file_state {
+ /* file */
+ flb_sds_t name; /* file name */
+ flb_sds_t path; /* file root path */
+
+ /* parent file state */
+ struct file_state *parent;
+};
+
+struct local_ctx {
+ int level; /* inclusion level */
+
+ struct cfl_list states;
+
+ struct mk_list includes;
+
+ int service_set;
+};
+
+/* yaml_* functions return 1 on success and 0 on failure. */
+enum status {
+ YAML_SUCCESS = 1,
+ YAML_FAILURE = 0
+};
+
+enum state {
+ STATE_START, /* start state */
+ STATE_STREAM, /* start/end stream */
+ STATE_DOCUMENT, /* start/end document */
+
+ STATE_SECTION, /* top level */
+ STATE_SECTION_KEY,
+ STATE_SECTION_VAL,
+
+ STATE_SERVICE, /* 'service' section */
+ STATE_INCLUDE, /* 'includes' section */
+ STATE_OTHER, /* any other unknown section */
+
+ STATE_CUSTOM, /* custom plugins */
+ STATE_PIPELINE, /* pipeline groups customs inputs, filters and outputs */
+
+ STATE_PLUGIN_INPUT, /* input plugins section */
+ STATE_PLUGIN_FILTER, /* filter plugins section */
+ STATE_PLUGIN_OUTPUT, /* output plugins section */
+
+ STATE_PLUGIN_START,
+ STATE_PLUGIN_KEY,
+ STATE_PLUGIN_VAL,
+ STATE_PLUGIN_VAL_LIST,
+
+ STATE_GROUP_KEY,
+ STATE_GROUP_VAL,
+
+ STATE_INPUT_PROCESSORS,
+ STATE_INPUT_PROCESSOR,
+
+ /* environment variables */
+ STATE_ENV,
+
+
+ STATE_STOP /* end state */
+};
+
+/* parser state allocation flags */
+#define HAS_KEY (1 << 0)
+#define HAS_KEYVALS (1 << 1)
+
+struct parser_state {
+ /* tokens state */
+ enum state state;
+ /* nesting level */
+ int level;
+
+ /* active section (if any) */
+ enum section section;
+
+ /* active section */
+ struct flb_cf_section *cf_section;
+ /* active group */
+ struct flb_cf_group *cf_group;
+
+ /* key value */
+ flb_sds_t key;
+ /* section key/value list */
+ struct cfl_kvlist *keyvals;
+ /* pointer to current values in a list. */
+ struct cfl_array *values;
+ /* are we the owner of the values? */
+ int allocation_flags;
+
+ struct file_state *file;
+
+ struct cfl_list _head;
+};
+
+static struct parser_state *state_push(struct local_ctx *, enum state);
+static struct parser_state *state_push_withvals(struct local_ctx *,
+ struct parser_state *,
+ enum state);
+static struct parser_state *state_push_witharr(struct local_ctx *,
+ struct parser_state *,
+ enum state);
+static struct parser_state *state_push_section(struct local_ctx *, enum state,
+ enum section);
+static struct parser_state *state_push_key(struct local_ctx *, enum state,
+ const char *key);
+static int state_create_section(struct flb_cf *, struct parser_state *, char *);
+static int state_create_group(struct flb_cf *, struct parser_state *, char *);
+static struct parser_state *state_pop(struct local_ctx *ctx);
+static struct parser_state *state_create(struct file_state *parent, struct file_state *file);
+static void state_destroy(struct parser_state *s);
+
+
+static int read_config(struct flb_cf *cf, struct local_ctx *ctx,
+ struct file_state *parent, char *cfg_file);
+
+static char *state_str(enum state val)
+{
+ switch (val) {
+ case STATE_START:
+ return "start";
+ case STATE_STREAM:
+ return "stream";
+ case STATE_DOCUMENT:
+ return "document";
+ case STATE_SECTION:
+ return "section";
+ case STATE_SECTION_KEY:
+ return "section-key";
+ case STATE_SECTION_VAL:
+ return "section-value";
+ case STATE_SERVICE:
+ return "service";
+ case STATE_INCLUDE:
+ return "include";
+ case STATE_OTHER:
+ return "other";
+ case STATE_CUSTOM:
+ return "custom";
+ case STATE_PIPELINE:
+ return "pipeline";
+ case STATE_PLUGIN_INPUT:
+ return "input";
+ case STATE_PLUGIN_FILTER:
+ return "filter";
+ case STATE_PLUGIN_OUTPUT:
+ return "output";
+ case STATE_PLUGIN_START:
+ return "plugin-start";
+ case STATE_PLUGIN_KEY:
+ return "plugin-key";
+ case STATE_PLUGIN_VAL:
+ return "plugin-value";
+ case STATE_PLUGIN_VAL_LIST:
+ return "plugin-values";
+ case STATE_GROUP_KEY:
+ return "group-key";
+ case STATE_GROUP_VAL:
+ return "group-val";
+ case STATE_INPUT_PROCESSORS:
+ return "processors";
+ case STATE_INPUT_PROCESSOR:
+ return "processor";
+ case STATE_ENV:
+ return "env";
+ case STATE_STOP:
+ return "stop";
+ default:
+ return "unknown";
+ }
+}
+
+static int add_section_type(struct flb_cf *conf, struct parser_state *state)
+{
+ if (conf == NULL || state == NULL) {
+ return -1;
+ }
+
+ if (state->section == SECTION_INPUT) {
+ state->cf_section = flb_cf_section_create(conf, "INPUT", 0);
+ }
+ else if (state->section == SECTION_FILTER) {
+ state->cf_section = flb_cf_section_create(conf, "FILTER", 0);
+ }
+ else if (state->section == SECTION_OUTPUT) {
+ state->cf_section = flb_cf_section_create(conf, "OUTPUT", 0);
+ }
+ else if (state->section == SECTION_CUSTOM) {
+ state->cf_section = flb_cf_section_create(conf, "customs", 0);
+ }
+
+ if (!state->cf_section) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static char *event_type_str(yaml_event_t *event)
+{
+ switch (event->type) {
+ case YAML_NO_EVENT:
+ return "no-event";
+ case YAML_STREAM_START_EVENT:
+ return "stream-start-event";
+ case YAML_STREAM_END_EVENT:
+ return "stream-end-event";
+ case YAML_DOCUMENT_START_EVENT:
+ return "document-start-event";
+ case YAML_DOCUMENT_END_EVENT:
+ return "document-end-event";
+ case YAML_ALIAS_EVENT:
+ return "alias-event";
+ case YAML_SCALAR_EVENT:
+ return "scalar-event";
+ case YAML_SEQUENCE_START_EVENT:
+ return "sequence-start-event";
+ break;
+ case YAML_SEQUENCE_END_EVENT:
+ return "sequence-end-event";
+ case YAML_MAPPING_START_EVENT:
+ return "mapping-start-event";
+ case YAML_MAPPING_END_EVENT:
+ return "mapping-end-event";
+ default:
+ return "unknown";
+ }
+}
+
+static char *state_get_last(struct local_ctx *ctx)
+{
+ struct flb_slist_entry *entry;
+
+ entry = mk_list_entry_last(&ctx->includes, struct flb_slist_entry, _head);
+
+ if (entry == NULL) {
+ return NULL;
+ }
+ return entry->str;
+}
+
+static void yaml_error_event(struct local_ctx *ctx, struct parser_state *state,
+ yaml_event_t *event)
+{
+ struct flb_slist_entry *entry;
+
+ if (event == NULL) {
+ flb_error("[config] YAML error found but with no state or event");
+ return;
+ }
+
+ if (state == NULL) {
+ flb_error("[config] YAML error found but with no state, line %zu, column %zu: "
+ "unexpected event '%s' (%d).",
+ event->start_mark.line + 1, event->start_mark.column,
+ event_type_str(event), event->type);
+ return;
+ }
+
+ entry = mk_list_entry_last(&ctx->includes, struct flb_slist_entry, _head);
+
+ if (entry == NULL) {
+ flb_error("[config] YAML error found (no file info), line %zu, column %zu: "
+ "unexpected event '%s' (%d) in state '%s' (%d).",
+ event->start_mark.line + 1, event->start_mark.column,
+ event_type_str(event), event->type, state_str(state->state), state->state);
+ return;
+ }
+
+ flb_error("[config] YAML error found in file \"%s\", line %zu, column %zu: "
+ "unexpected event '%s' (%d) in state '%s' (%d).",
+ entry->str, event->start_mark.line + 1, event->start_mark.column,
+ event_type_str(event), event->type, state_str(state->state), state->state);
+}
+
+static void yaml_error_definition(struct local_ctx *ctx, struct parser_state *state,
+ yaml_event_t *event, char *value)
+{
+ flb_error("[config] YAML error found in file \"%s\", line %zu, column %zu: "
+ "duplicated definition of '%s'",
+ state->file->name, event->start_mark.line + 1, event->start_mark.column,
+ value);
+}
+
+static void yaml_error_plugin_category(struct local_ctx *ctx, struct parser_state *state,
+ yaml_event_t *event, char *value)
+{
+ flb_error("[config] YAML error found in file \"%s\", line %zu, column %zu: "
+ "the pipeline component '%s' is not valid. Try one of these values: "
+ "customs, inputs, filters or outputs.",
+ state->file->name, event->start_mark.line + 1, event->start_mark.column,
+ value);
+}
+
+static int is_file_included(struct local_ctx *ctx, const char *path)
+{
+ struct mk_list *head;
+ struct flb_slist_entry *entry;
+
+ mk_list_foreach(head, &ctx->includes) {
+ entry = mk_list_entry(head, struct flb_slist_entry, _head);
+
+ if (strcmp(entry->str, path) == 0) {
+ return FLB_TRUE;
+ }
+ }
+
+ return FLB_FALSE;
+}
+
+#ifndef _WIN32
+static int read_glob(struct flb_cf *conf, struct local_ctx *ctx,
+ struct parser_state *state, const char *path)
+{
+ int ret = -1;
+ glob_t glb;
+ char tmp[PATH_MAX+1];
+
+ const char *glb_path;
+ size_t idx;
+ int ret_glb = -1;
+
+ if (state->file->path && path[0] != '/') {
+ ret = snprintf(tmp, PATH_MAX, "%s/%s", state->file->path, path);
+
+ if (ret > PATH_MAX) {
+ return -1;
+ }
+ glb_path = tmp;
+ }
+ else {
+ glb_path = path;
+ }
+
+ ret_glb = glob(glb_path, GLOB_NOSORT, NULL, &glb);
+
+ if (ret_glb != 0) {
+ switch(ret_glb){
+ case GLOB_NOSPACE:
+ flb_warn("[%s] glob: [%s] no space", __FUNCTION__, glb_path);
+ break;
+ case GLOB_NOMATCH:
+ flb_warn("[%s] glob: [%s] no match", __FUNCTION__, glb_path);
+ break;
+ case GLOB_ABORTED:
+ flb_warn("[%s] glob: [%s] aborted", __FUNCTION__, glb_path);
+ break;
+ default:
+ flb_warn("[%s] glob: [%s] other error", __FUNCTION__, glb_path);
+ }
+ return ret;
+ }
+
+ for (idx = 0; idx < glb.gl_pathc; idx++) {
+ ret = read_config(conf, ctx, state->file, glb.gl_pathv[idx]);
+
+ if (ret < 0) {
+ break;
+ }
+ }
+
+ globfree(&glb);
+ return ret;
+}
+#else
+static char *dirname(char *path)
+{
+ char *ptr;
+
+
+ ptr = strrchr(path, '\\');
+
+ if (ptr == NULL) {
+ return path;
+ }
+ *ptr++='\0';
+ return path;
+}
+
+static int read_glob(struct flb_cf *conf, struct local_ctx *ctx,
+ struct parser_state *state, const char *path)
+{
+ char *star, *p0, *p1;
+ char pattern[MAX_PATH];
+ char buf[MAX_PATH];
+ int ret;
+ struct stat st;
+ HANDLE hnd;
+ WIN32_FIND_DATA data;
+
+ if (strlen(path) > MAX_PATH - 1) {
+ return -1;
+ }
+
+ star = strchr(path, '*');
+
+ if (star == NULL) {
+ return -1;
+ }
+
+ /*
+ * C:\data\tmp\input_*.conf
+ * 0<-----|
+ */
+ p0 = star;
+ while (path <= p0 && *p0 != '\\') {
+ p0--;
+ }
+
+ /*
+ * C:\data\tmp\input_*.conf
+ * |---->1
+ */
+ p1 = star;
+ while (*p1 && *p1 != '\\') {
+ p1++;
+ }
+
+ memcpy(pattern, path, (p1 - path));
+ pattern[p1 - path] = '\0';
+
+ hnd = FindFirstFileA(pattern, &data);
+
+ if (hnd == INVALID_HANDLE_VALUE) {
+ return 0;
+ }
+
+ do {
+ /* Ignore the current and parent dirs */
+ if (!strcmp(".", data.cFileName) || !strcmp("..", data.cFileName)) {
+ continue;
+ }
+
+ /* Avoid an infinite loop */
+ if (strchr(data.cFileName, '*')) {
+ continue;
+ }
+
+ /* Create a path (prefix + filename + suffix) */
+ memcpy(buf, path, p0 - path + 1);
+ buf[p0 - path + 1] = '\0';
+
+ if (FAILED(StringCchCatA(buf, MAX_PATH, data.cFileName))) {
+ continue;
+ }
+
+ if (FAILED(StringCchCatA(buf, MAX_PATH, p1))) {
+ continue;
+ }
+
+ if (strchr(p1, '*')) {
+ read_glob(conf, ctx, state, buf); /* recursive */
+ continue;
+ }
+
+ ret = stat(buf, &st);
+
+ if (ret == 0 && (st.st_mode & S_IFMT) == S_IFREG) {
+
+ if (read_config(conf, ctx, state, buf) < 0) {
+ return -1;
+ }
+ }
+ } while (FindNextFileA(hnd, &data) != 0);
+
+ FindClose(hnd);
+ return 0;
+}
+#endif
+
+static void print_current_state(struct local_ctx *ctx, struct parser_state *state,
+ yaml_event_t *event)
+{
+ flb_debug("%*s%s->%s", state->level*2, "", state_str(state->state),
+ event_type_str(event));
+}
+
+static void print_current_properties(struct parser_state *state)
+{
+ struct cfl_list *head;
+ struct cfl_kvpair *prop;
+ struct cfl_variant *var;
+ int idx;
+
+ flb_debug("%*s[%s] PROPERTIES:", state->level*2, "", section_names[state->section]);
+
+ cfl_list_foreach(head, &state->keyvals->list) {
+ prop = cfl_list_entry(head, struct cfl_kvpair, _head);
+ switch (prop->val->type) {
+ case CFL_VARIANT_STRING:
+ flb_debug("%*s%s: %s", (state->level+2)*2, "", prop->key, prop->val->data.as_string);
+ break;
+ case CFL_VARIANT_ARRAY:
+ flb_debug("%*s%s: [", (state->level+2)*2, "", prop->key);
+ for (idx = 0; idx < prop->val->data.as_array->entry_count; idx++) {
+ var = cfl_array_fetch_by_index(prop->val->data.as_array, idx);
+ flb_debug("%*s%s", (state->level+3)*2, "", var->data.as_string);
+ }
+ flb_debug("%*s]", (state->level+2)*2, "");
+ break;
+ }
+ }
+}
+
+static struct parser_state *get_current_state(struct local_ctx *ctx)
+{
+ struct parser_state *state;
+
+ if (cfl_list_size(&ctx->states) <= 0) {
+ return NULL;
+ }
+ state = cfl_list_entry_last(&ctx->states, struct parser_state, _head);
+ return state;
+}
+
+static enum status state_copy_into_config_group(struct parser_state *state, struct flb_cf_group *cf_group)
+{
+ struct cfl_list *head;
+ struct cfl_kvpair *kvp;
+ struct cfl_variant *var;
+ struct cfl_variant *varr;
+ struct cfl_array *arr;
+ struct cfl_array *carr;
+ struct cfl_kvlist *copy;
+ int idx;
+
+ if (cf_group == NULL) {
+ flb_error("no group for processor properties");
+ return YAML_FAILURE;
+ }
+
+ varr = cfl_kvlist_fetch(cf_group->properties, state->key);
+
+ if (varr == NULL) {
+ arr = cfl_array_create(1);
+
+ if (arr == NULL) {
+ flb_error("unable to allocate array");
+ return YAML_FAILURE;
+ }
+
+ cfl_array_resizable(arr, CFL_TRUE);
+
+ if (cfl_kvlist_insert_array(cf_group->properties, state->key, arr) < 0) {
+ cfl_array_destroy(arr);
+ flb_error("unable to insert into array");
+ return YAML_FAILURE;
+ }
+ }
+ else {
+ arr = varr->data.as_array;
+ }
+
+ copy = cfl_kvlist_create();
+
+ if (copy == NULL) {
+ cfl_array_destroy(arr);
+ flb_error("unable to allocate kvlist");
+ return YAML_FAILURE;
+ }
+
+ cfl_list_foreach(head, &state->keyvals->list) {
+ kvp = cfl_list_entry(head, struct cfl_kvpair, _head);
+ switch (kvp->val->type) {
+ case CFL_VARIANT_STRING:
+
+ if (cfl_kvlist_insert_string(copy, kvp->key, kvp->val->data.as_string) < 0) {
+ flb_error("unable to allocate kvlist");
+ cfl_kvlist_destroy(copy);
+ return YAML_FAILURE;
+ }
+ break;
+ case CFL_VARIANT_ARRAY:
+ carr = cfl_array_create(kvp->val->data.as_array->entry_count);
+
+ if (carr) {
+ flb_error("unable to allocate array");
+ cfl_kvlist_destroy(copy);
+ return YAML_FAILURE;
+ }
+ for (idx = 0; idx < kvp->val->data.as_array->entry_count; idx++) {
+ var = cfl_array_fetch_by_index(kvp->val->data.as_array, idx);
+
+ if (var == NULL) {
+ cfl_array_destroy(arr);
+ flb_error("unable to fetch from array by index");
+ return YAML_FAILURE;
+ }
+ switch (var->type) {
+ case CFL_VARIANT_STRING:
+
+ if (cfl_array_append_string(carr, var->data.as_string) < 0) {
+ flb_error("unable to append string");
+ cfl_kvlist_destroy(copy);
+ cfl_array_destroy(carr);
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ cfl_array_destroy(arr);
+ flb_error("unable to copy value for property");
+ cfl_kvlist_destroy(copy);
+ cfl_array_destroy(carr);
+ return YAML_FAILURE;
+ }
+ }
+
+ if (cfl_kvlist_insert_array(copy, kvp->key, carr) < 0) {
+ cfl_array_destroy(arr);
+ flb_error("unabelt to insert into array");
+ flb_error("unable to insert array into kvlist");
+ }
+ break;
+ default:
+ flb_error("unknown value type for properties: %d", kvp->val->type);
+ cfl_kvlist_destroy(copy);
+ return YAML_FAILURE;
+ }
+ }
+
+ if (cfl_array_append_kvlist(arr, copy) < 0) {
+ flb_error("unable to insert array into kvlist");
+ cfl_kvlist_destroy(copy);
+ return YAML_FAILURE;
+ }
+ return YAML_SUCCESS;
+}
+
+static enum status state_copy_into_properties(struct parser_state *state, struct flb_cf *conf, struct cfl_kvlist *properties)
+{
+ struct cfl_list *head;
+ struct cfl_kvpair *kvp;
+ struct cfl_variant *var;
+ struct cfl_array *arr;
+ int idx;
+
+ cfl_list_foreach(head, &state->keyvals->list) {
+ kvp = cfl_list_entry(head, struct cfl_kvpair, _head);
+ switch (kvp->val->type) {
+ case CFL_VARIANT_STRING:
+ var = flb_cf_section_property_add(conf,
+ properties,
+ kvp->key,
+ cfl_sds_len(kvp->key),
+ kvp->val->data.as_string,
+ cfl_sds_len(kvp->val->data.as_string));
+
+ if (var == NULL) {
+ flb_error("unable to add variant value property");
+ return YAML_FAILURE;
+ }
+ break;
+ case CFL_VARIANT_ARRAY:
+ arr = flb_cf_section_property_add_list(conf, properties,
+ kvp->key, cfl_sds_len(kvp->key));
+
+ if (arr == NULL) {
+ flb_error("unable to add property list");
+ return YAML_FAILURE;
+ }
+ for (idx = 0; idx < kvp->val->data.as_array->entry_count; idx++) {
+ var = cfl_array_fetch_by_index(kvp->val->data.as_array, idx);
+
+ if (var == NULL) {
+ flb_error("unable to retrieve from array by index");
+ return YAML_FAILURE;
+ }
+ switch (var->type) {
+ case CFL_VARIANT_STRING:
+
+ if (cfl_array_append_string(arr, var->data.as_string) < 0) {
+ flb_error("unable to append string to array");
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ flb_error("unable to copy value for property");
+ return YAML_FAILURE;
+ }
+ }
+ break;
+ default:
+ flb_error("unknown value type for properties: %d", kvp->val->type);
+ return YAML_FAILURE;
+ }
+ }
+ return YAML_SUCCESS;
+}
+
+static int consume_event(struct flb_cf *conf, struct local_ctx *ctx,
+ yaml_event_t *event)
+{
+ struct parser_state *state;
+ enum status status;
+ int ret;
+ char *value;
+ struct flb_kv *keyval;
+ char *last_included;
+
+ last_included = state_get_last(ctx);
+
+ if (last_included == NULL) {
+ last_included = "**unknown**";
+ }
+
+ state = get_current_state(ctx);
+
+ if (state == NULL) {
+ flb_error("unable to parse yaml: no state");
+ return YAML_FAILURE;
+ }
+ print_current_state(ctx, state, event);
+
+ switch (state->state) {
+ case STATE_START:
+ switch (event->type) {
+ case YAML_STREAM_START_EVENT:
+ state = state_push(ctx, STATE_STREAM);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_NO_EVENT:
+ state->state = STATE_STOP;
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+
+ case STATE_STREAM:
+ switch (event->type) {
+ case YAML_DOCUMENT_START_EVENT:
+ state = state_push(ctx, STATE_DOCUMENT);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_STREAM_END_EVENT:
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+
+ case STATE_DOCUMENT:
+ switch (event->type) {
+ case YAML_MAPPING_START_EVENT:
+ state = state_push(ctx, STATE_SECTION);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_DOCUMENT_END_EVENT:
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+
+ /*
+ * 'includes'
+ * --------
+ */
+ case STATE_INCLUDE:
+ switch (event->type) {
+ case YAML_SEQUENCE_START_EVENT:
+ break;
+ case YAML_SEQUENCE_END_EVENT:
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+
+ if (state->state != STATE_SECTION) {
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_SCALAR_EVENT:
+ value = (char *) event->data.scalar.value;
+ flb_error("[config yaml] including: %s", value);
+
+ if (strchr(value, '*') != NULL) {
+ ret = read_glob(conf, ctx, state, value);
+ }
+ else {
+ ret = read_config(conf, ctx, state->file, value);
+ }
+
+ if (ret == -1) {
+ flb_error("[config] including file '%s' at %s:%zu",
+ value,
+ last_included, event->start_mark.line + 1);
+ return YAML_FAILURE;
+ }
+ ctx->level++;
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+ /* end of 'includes' */
+
+ /*
+ * 'customs'
+ * --------
+ */
+ case STATE_CUSTOM:
+ switch (event->type) {
+ case YAML_SEQUENCE_START_EVENT:
+ break;
+ case YAML_MAPPING_START_EVENT:
+ state = state_push_withvals(ctx, state, STATE_PLUGIN_START);
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ if (add_section_type(conf, state) == -1) {
+ flb_error("unable to add section type");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_SEQUENCE_END_EVENT:
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+ /* end of 'customs' */
+
+ case STATE_PIPELINE:
+ switch (event->type) {
+ case YAML_SCALAR_EVENT:
+ value = (char *)event->data.scalar.value;
+
+ if (strcasecmp(value, "inputs") == 0) {
+ state = state_push_section(ctx, STATE_PLUGIN_INPUT, SECTION_INPUT);
+ }
+ else if (strcasecmp(value, "filters") == 0) {
+ state = state_push_section(ctx, STATE_PLUGIN_FILTER, SECTION_FILTER);
+ }
+ else if (strcasecmp(value, "outputs") == 0) {
+ state = state_push_section(ctx, STATE_PLUGIN_OUTPUT, SECTION_OUTPUT);
+ }
+ else {
+ yaml_error_plugin_category(ctx, state, event, value);
+ return YAML_FAILURE;
+ }
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_MAPPING_START_EVENT:
+ break;
+ case YAML_MAPPING_END_EVENT:
+ state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+
+ case STATE_SECTION:
+ switch (event->type) {
+ case YAML_SCALAR_EVENT:
+ value = (char *)event->data.scalar.value;
+
+ if (strcasecmp(value, "env") == 0) {
+ state = state_push_section(ctx, STATE_ENV, SECTION_ENV);
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ }
+ else if (strcasecmp(value, "pipeline") == 0) {
+ state = state_push_section(ctx, STATE_PIPELINE, SECTION_PIPELINE);
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ }
+ else if (strcasecmp(value, "service") == 0) {
+
+ if (ctx->service_set) {
+ yaml_error_definition(ctx, state, event, value);
+ return YAML_FAILURE;
+ }
+
+ state = state_push_section(ctx, STATE_SERVICE, SECTION_SERVICE);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+
+ if (state_create_section(conf, state, value) == -1) {
+ flb_error("unable to allocate section: %s", value);
+ return YAML_FAILURE;
+ }
+ ctx->service_set = 1;
+ }
+ else if (strcasecmp(value, "customs") == 0) {
+ state = state_push_section(ctx, STATE_CUSTOM, SECTION_CUSTOM);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ }
+ else if (strcasecmp(value, "includes") == 0) {
+ state = state_push_section(ctx, STATE_INCLUDE, SECTION_INCLUDE);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ }
+ else {
+ /* any other main section definition (e.g: similar to STATE_SERVICE) */
+ state = state_push(ctx, STATE_OTHER);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+
+ if (state_create_section(conf, state, value) == -1) {
+ flb_error("unable to allocate section: %s", value);
+ return YAML_FAILURE;
+ }
+ }
+ break;
+ case YAML_MAPPING_END_EVENT:
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_DOCUMENT_END_EVENT:
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+
+ /* service or others */
+ case STATE_ENV:
+ case STATE_SERVICE:
+ case STATE_OTHER:
+ switch(event->type) {
+ case YAML_MAPPING_START_EVENT:
+ state = state_push(ctx, STATE_SECTION_KEY);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_MAPPING_END_EVENT:
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+
+ if (state->state != STATE_SECTION) {
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+
+ case STATE_SECTION_KEY:
+ switch(event->type) {
+ case YAML_SCALAR_EVENT:
+ value = (char *) event->data.scalar.value;
+ state = state_push_key(ctx, STATE_SECTION_VAL, value);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_MAPPING_END_EVENT:
+ state = state_pop(ctx);
+ switch (state->state) {
+ case STATE_SERVICE:
+ case STATE_ENV:
+ case STATE_OTHER:
+ break;
+ default:
+ printf("BAD STATE FOR SECTION KEY POP=%s\n", state_str(state->state));
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+
+ case STATE_SECTION_VAL:
+ switch(event->type) {
+ case YAML_SCALAR_EVENT:
+ value = (char *) event->data.scalar.value;
+
+ /* Check if the incoming k/v pair set a config environment variable */
+ if (state->section == SECTION_ENV) {
+ keyval = flb_cf_env_property_add(conf,
+ state->key, flb_sds_len(state->key),
+ value, strlen(value));
+
+ if (keyval == NULL) {
+ flb_error("unable to add key value");
+ return YAML_FAILURE;
+ }
+ }
+ else {
+
+ /* register key/value pair as a property */
+ if (state->cf_section == NULL) {
+ flb_error("no section to register key value to");
+ return YAML_FAILURE;
+ }
+
+ if (flb_cf_section_property_add(conf, state->cf_section->properties,
+ state->key, flb_sds_len(state->key),
+ value, strlen(value)) < 0) {
+ flb_error("unable to add property");
+ return YAML_FAILURE;
+ }
+ }
+
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+
+ if (state->state != STATE_SECTION_KEY) {
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+
+ /* Plugin types */
+ case STATE_PLUGIN_INPUT:
+ case STATE_PLUGIN_FILTER:
+ case STATE_PLUGIN_OUTPUT:
+ switch(event->type) {
+ case YAML_SEQUENCE_START_EVENT:
+ break;
+ case YAML_SEQUENCE_END_EVENT:
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_MAPPING_START_EVENT:
+ state = state_push_withvals(ctx, state, STATE_PLUGIN_START);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+
+ if (add_section_type(conf, state) == -1) {
+ flb_error("unable to add section type");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_SCALAR_EVENT:
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+
+ if (state->state != STATE_SECTION) {
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_MAPPING_END_EVENT:
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+
+ if (state->state != STATE_SECTION_KEY) {
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+
+ case STATE_PLUGIN_START:
+ switch(event->type) {
+ case YAML_SCALAR_EVENT:
+ /* Here is where we process all the plugin properties for customs, pipelines
+ * and processors.
+ */
+ state = state_push_key(ctx, STATE_PLUGIN_VAL, (char *) event->data.scalar.value);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_MAPPING_END_EVENT:
+ print_current_properties(state);
+
+ if (state->section == SECTION_PROCESSOR) {
+ status = state_copy_into_config_group(state, state->cf_group);
+
+ if (status != YAML_SUCCESS) {
+ return status;
+ }
+ }
+ else {
+ status = state_copy_into_properties(state, conf, state->cf_section->properties);
+
+ if (status != YAML_SUCCESS) {
+ return status;
+ }
+ }
+
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_SEQUENCE_START_EVENT: /* start a new group */
+
+ if (state->key == NULL) {
+ flb_error("no key");
+ return YAML_FAILURE;
+ }
+
+ if (strcmp(state->key, "processors") == 0) {
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+
+ state = state_push_witharr(ctx, state, STATE_PLUGIN_VAL_LIST);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_SEQUENCE_END_EVENT:
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+
+ case STATE_PLUGIN_KEY:
+ switch(event->type) {
+ case YAML_SCALAR_EVENT:
+ /* Here is where we process all the plugin properties for customs, pipelines
+ * and processors.
+ */
+ state = state_push_key(ctx, STATE_PLUGIN_VAL, (char *) event->data.scalar.value);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_MAPPING_START_EVENT:
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+
+ if (state->state != STATE_PLUGIN_START) {
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_MAPPING_END_EVENT:
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+
+ if (state->state != STATE_PLUGIN_START) {
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_SEQUENCE_END_EVENT:
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+
+ case STATE_PLUGIN_VAL:
+ switch(event->type) {
+ case YAML_SCALAR_EVENT:
+
+ /* register key/value pair as a property */
+ if (cfl_kvlist_insert_string(state->keyvals, state->key, (char *)event->data.scalar.value) < 0) {
+ flb_error("unable to insert string");
+ return YAML_FAILURE;
+ }
+
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_SEQUENCE_START_EVENT: /* start a new group */
+ state = state_push_witharr(ctx, state, STATE_PLUGIN_VAL_LIST);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_MAPPING_START_EVENT:
+
+ if (strcmp(state->key, "processors") == 0) {
+ state = state_push(ctx, STATE_INPUT_PROCESSORS);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+
+ if (state_create_group(conf, state, "processors") == YAML_FAILURE) {
+ return YAML_FAILURE;
+ }
+ break;
+ }
+
+ state = state_push(ctx, STATE_GROUP_KEY);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ /* create group */
+ state->values = flb_cf_section_property_add_list(conf,
+ state->cf_section->properties,
+ state->key, flb_sds_len(state->key));
+
+ if (state->values == NULL) {
+ flb_error("no values");
+ return YAML_FAILURE;
+ }
+
+ state->cf_group = flb_cf_group_create(conf, state->cf_section, state->key, strlen(state->key));
+
+ if (state->cf_group == NULL) {
+ flb_error("unable to create group");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_SEQUENCE_END_EVENT: /* end of group */
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+
+ if (state->state != STATE_PLUGIN_KEY) {
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+
+ if (state->state != STATE_PLUGIN_KEY) {
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_MAPPING_END_EVENT:
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+
+ case STATE_PLUGIN_VAL_LIST:
+ switch(event->type) {
+ case YAML_SCALAR_EVENT:
+
+ if (state->values == NULL) {
+ flb_error("unable to add values to list");
+ return YAML_FAILURE;
+ }
+
+ if (cfl_array_append_string(state->values, (char *)event->data.scalar.value) < 0) {
+ flb_error("unable to add values to list");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_SEQUENCE_END_EVENT:
+
+ /* register key/value pair as a property */
+ if (cfl_kvlist_insert_array(state->keyvals, state->key, state->values) < 0) {
+ flb_error("unable to insert key values");
+ return YAML_FAILURE;
+ }
+
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+
+ case STATE_INPUT_PROCESSORS:
+ switch(event->type) {
+ case YAML_MAPPING_START_EVENT:
+ break;
+ case YAML_MAPPING_END_EVENT:
+
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_SCALAR_EVENT:
+
+ /* Check if we are entering a 'logs', 'metrics' or 'traces' section */
+ value = (char *) event->data.scalar.value;
+
+ if (strcasecmp(value, "logs") == 0) {
+ /* logs state */
+ state = state_push_key(ctx, STATE_INPUT_PROCESSOR, "logs");
+ }
+ else if (strcasecmp(value, "metrics") == 0) {
+ /* metrics state */
+ state = state_push_key(ctx, STATE_INPUT_PROCESSOR, "metrics");
+ }
+ else if (strcasecmp(value, "traces") == 0) {
+ /* metrics state */
+ state = state_push_key(ctx, STATE_INPUT_PROCESSOR, "traces");
+ }
+ else {
+ flb_error("[config] unknown processor '%s'", value);
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ };
+ break;
+
+ case STATE_INPUT_PROCESSOR:
+ switch(event->type) {
+ case YAML_SEQUENCE_START_EVENT:
+ break;
+ case YAML_SEQUENCE_END_EVENT:
+
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_MAPPING_START_EVENT:
+
+ state = state_push_withvals(ctx, state, STATE_PLUGIN_START);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ state->section = SECTION_PROCESSOR;
+ break;
+ case YAML_MAPPING_END_EVENT:
+ return YAML_FAILURE;
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ };
+ break;
+
+ /* groups: a group is a sub-section and here we handle the key/value pairs. */
+ case STATE_GROUP_KEY:
+ switch(event->type) {
+ case YAML_SCALAR_EVENT:
+ /* grab current value (key) */
+ value = (char *) event->data.scalar.value;
+
+ state = state_push_key(ctx, STATE_GROUP_VAL, value);
+
+ if (state == NULL) {
+ flb_error("unable to allocate state");
+ return YAML_FAILURE;
+ }
+ break;
+ case YAML_MAPPING_START_EVENT:
+ break;
+ case YAML_MAPPING_END_EVENT:
+
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+
+ /* This is also the end of the plugin values mapping.
+ * So we pop an additional state off the stack.
+ */
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+
+ case STATE_GROUP_VAL:
+ switch(event->type) {
+ case YAML_SCALAR_EVENT:
+ value = (char *) event->data.scalar.value;
+
+ /* add the kv pair to the active group properties */
+ if (flb_cf_section_property_add(conf, state->cf_group->properties,
+ state->key, flb_sds_len(state->key),
+ value, strlen(value)) == NULL) {
+ flb_error("unable to add property");
+ return YAML_FAILURE;
+ }
+
+ state = state_pop(ctx);
+
+ if (state == NULL) {
+ flb_error("no state left");
+ return YAML_FAILURE;
+ }
+ break;
+ default:
+ yaml_error_event(ctx, state, event);
+ return YAML_FAILURE;
+ }
+ break;
+
+ case STATE_STOP:
+ break;
+ }
+
+ return YAML_SUCCESS;
+}
+
+static struct parser_state *state_start(struct local_ctx *ctx, struct file_state *file)
+{
+ struct parser_state *state;
+
+ state = state_create(NULL, file);
+
+ if (state != NULL) {
+ cfl_list_add(&state->_head, &ctx->states);
+ }
+
+ return state;
+}
+
+static struct parser_state *state_push(struct local_ctx *ctx, enum state state_num)
+{
+ struct parser_state *last = NULL;
+ struct parser_state *state;
+
+ if (cfl_list_size(&ctx->states) <= 0) {
+ return NULL;
+ }
+
+ last = cfl_list_entry_last(&ctx->states, struct parser_state, _head);
+
+ if (last == NULL) {
+ return NULL;
+ }
+
+ state = state_create(last->file, last->file);
+
+ if (state == NULL) {
+ return NULL;
+ }
+ state->section = last->section;
+ state->keyvals = last->keyvals;
+ state->cf_section = last->cf_section;
+ state->cf_group = last->cf_group;
+ state->values = last->values;
+ state->file = last->file;
+ state->state = state_num;
+ state->level = last->level + 1;
+ state->key = last->key;
+
+ cfl_list_add(&state->_head, &ctx->states);
+ return state;
+}
+
+static struct parser_state *state_push_section(struct local_ctx *ctx,
+ enum state state_num,
+ enum section section)
+{
+ struct parser_state *state;
+
+ state = state_push(ctx, state_num);
+
+ if (state == NULL) {
+ return NULL;
+ }
+ state->section = section;
+
+ return state;
+}
+
+static struct parser_state *state_push_key(struct local_ctx *ctx,
+ enum state state_num,
+ const char *key)
+{
+ struct parser_state *state;
+ flb_sds_t skey;
+
+ if (key == NULL) {
+ return NULL;
+ }
+
+ skey = flb_sds_create(key);
+
+ if (skey == NULL) {
+ return NULL;
+ }
+
+ state = state_push(ctx, state_num);
+
+ if (state == NULL) {
+ flb_sds_destroy(skey);
+ return NULL;
+ }
+
+ state->key = skey;
+ state->allocation_flags |= HAS_KEY;
+ return state;
+}
+
+static struct parser_state *state_push_withvals(struct local_ctx *ctx,
+ struct parser_state *parent,
+ enum state state_num)
+{
+ struct parser_state *state;
+ struct cfl_kvlist *kvlist;
+
+ kvlist = cfl_kvlist_create();
+
+ if (kvlist == NULL) {
+ return NULL;
+ }
+
+ state = state_push(ctx, state_num);
+
+ if (state == NULL) {
+ cfl_kvlist_destroy(kvlist);
+ return NULL;
+ }
+
+ state->keyvals = kvlist;
+ state->allocation_flags |= HAS_KEYVALS;
+
+ return state;
+}
+
+static struct parser_state *state_push_witharr(struct local_ctx *ctx,
+ struct parser_state *parent,
+ enum state state_num)
+{
+ struct parser_state *state;
+
+ parent->values = cfl_array_create(4);
+
+ if (parent->values == NULL) {
+ flb_error("parent has no values");
+ return NULL;
+ }
+
+ cfl_array_resizable(parent->values, CFL_TRUE);
+
+ state = state_push(ctx, state_num);
+
+ return state;
+}
+
+static int state_create_section(struct flb_cf *conf, struct parser_state *state, char *name)
+{
+
+ if (state == NULL || conf == NULL || name == NULL) {
+ return -1;
+ }
+
+ state->cf_section = flb_cf_section_create(conf, name, 0);
+
+ if (state->cf_section == NULL) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int state_create_group(struct flb_cf *conf, struct parser_state *state, char *name)
+{
+ if (state == NULL || conf == NULL || name == NULL) {
+ return -1;
+ }
+
+ state->cf_group = flb_cf_group_create(conf, state->cf_section,
+ "processors", strlen("processors"));
+
+ if (state->cf_group == NULL) {
+ return -1;
+ }
+
+ return YAML_SUCCESS;
+}
+
+static struct parser_state *state_pop(struct local_ctx *ctx)
+{
+ struct parser_state *last;
+
+ if (ctx == NULL) {
+ return NULL;
+ }
+
+ if (cfl_list_size(&ctx->states) <= 0) {
+ return NULL;
+ }
+
+ last = cfl_list_entry_last(&ctx->states, struct parser_state, _head);
+ cfl_list_del(&last->_head);
+
+ if (last->allocation_flags & HAS_KEY) {
+ flb_sds_destroy(last->key);
+ }
+
+ if (last->allocation_flags & HAS_KEYVALS) {
+ cfl_kvlist_destroy(last->keyvals);
+ }
+
+ state_destroy(last);
+
+ if (cfl_list_size(&ctx->states) <= 0) {
+ return NULL;
+ }
+
+ return cfl_list_entry_last(&ctx->states, struct parser_state, _head);
+}
+
+static void state_destroy(struct parser_state *s)
+{
+ flb_free(s);
+}
+
+static struct parser_state *state_create(struct file_state *parent, struct file_state *file)
+{
+ struct parser_state *state;
+
+ /* allocate context */
+ state = flb_calloc(1, sizeof(struct parser_state));
+
+ if (!state) {
+ flb_errno();
+ return NULL;
+ }
+
+ state->file = file;
+#ifndef FLB_HAVE_STATIC_CONF
+
+ if (parent) {
+ state->file->parent = parent;
+ }
+
+#else
+
+ s->file->name = flb_sds_create("***static***");
+ s->file->path = flb_sds_create("***static***");
+
+#endif
+
+ return state;
+}
+
+static int read_config(struct flb_cf *conf, struct local_ctx *ctx,
+ struct file_state *parent, char *cfg_file)
+{
+ int ret;
+ int status;
+ int code = 0;
+ struct parser_state *state;
+ flb_sds_t include_dir = NULL;
+ flb_sds_t include_file = NULL;
+ yaml_parser_t parser;
+ yaml_event_t event;
+ FILE *fh;
+ struct file_state fstate;
+
+ if (parent && cfg_file[0] != '/') {
+
+ include_dir = flb_sds_create_size(strlen(cfg_file) + strlen(parent->path));
+
+ if (include_dir == NULL) {
+ flb_error("unable to create filename");
+ return -1;
+ }
+
+ if (flb_sds_printf(&include_dir, "%s/%s", parent->path, cfg_file) == NULL) {
+ flb_error("unable to create full filename");
+ return -1;
+ }
+
+ }
+ else {
+
+ include_dir = flb_sds_create(cfg_file);
+
+ if (include_dir == NULL) {
+ flb_error("unable to create filename");
+ return -1;
+ }
+ }
+
+ include_file = flb_sds_create(include_dir);
+
+ if (include_file == NULL) {
+ flb_error("unable to create include filename");
+ flb_sds_destroy(include_dir);
+ return -1;
+ }
+
+ fstate.name = basename(include_dir);
+ fstate.path = dirname(include_dir);
+
+ fstate.parent = parent;
+
+ state = state_start(ctx, &fstate);
+
+ if (!state) {
+ flb_error("unable to push initial include file state: %s", cfg_file);
+ flb_sds_destroy(include_dir);
+ flb_sds_destroy(include_file);
+ return -1;
+ }
+
+ /* check if this file has been included before */
+ ret = is_file_included(ctx, include_file);
+
+ if (ret) {
+ flb_error("[config] file '%s' is already included", cfg_file);
+ flb_sds_destroy(include_dir);
+ flb_sds_destroy(include_file);
+ state_destroy(state);
+ return -1;
+ }
+
+ flb_debug("============ %s ============", cfg_file);
+ fh = fopen(include_file, "r");
+
+ if (!fh) {
+ flb_errno();
+ flb_sds_destroy(include_dir);
+ flb_sds_destroy(include_file);
+ state_destroy(state);
+ return -1;
+ }
+
+ /* add file to the list of included files */
+ ret = flb_slist_add(&ctx->includes, include_file);
+
+ if (ret == -1) {
+ flb_error("[config] could not register file %s", cfg_file);
+ fclose(fh);
+ flb_sds_destroy(include_dir);
+ flb_sds_destroy(include_file);
+ state_destroy(state);
+ return -1;
+ }
+ ctx->level++;
+
+ yaml_parser_initialize(&parser);
+ yaml_parser_set_input_file(&parser, fh);
+
+ do {
+ status = yaml_parser_parse(&parser, &event);
+
+ if (status == YAML_FAILURE) {
+ flb_error("[config] invalid YAML format in file %s", cfg_file);
+ code = -1;
+ goto done;
+ }
+
+ status = consume_event(conf, ctx, &event);
+
+ if (status == YAML_FAILURE) {
+ flb_error("yaml error");
+ code = -1;
+ goto done;
+ }
+
+ yaml_event_delete(&event);
+ state = cfl_list_entry_last(&ctx->states, struct parser_state, _head);
+
+ } while (state->state != STATE_STOP);
+
+ flb_debug("==============================");
+done:
+
+ if (code == -1) {
+ yaml_event_delete(&event);
+ }
+
+ yaml_parser_delete(&parser);
+
+ /* free all remaining states */
+ if (code == -1) {
+ while (state = state_pop(ctx));
+ }
+ else {
+ state = state_pop(ctx);
+ }
+
+ fclose(fh);
+ ctx->level--;
+
+ flb_sds_destroy(include_file);
+ flb_sds_destroy(include_dir);
+
+ return code;
+}
+
+static int local_init(struct local_ctx *ctx)
+{
+ /* reset the state */
+ memset(ctx, '\0', sizeof(struct local_ctx));
+ cfl_list_init(&ctx->states);
+ ctx->level = 0;
+ flb_slist_create(&ctx->includes);
+
+ return 0;
+}
+
+static void local_exit(struct local_ctx *ctx)
+{
+ flb_slist_destroy(&ctx->includes);
+}
+
+struct flb_cf *flb_cf_yaml_create(struct flb_cf *conf, char *file_path,
+ char *buf, size_t size)
+{
+ int ret;
+ struct local_ctx ctx;
+
+ if (!conf) {
+ conf = flb_cf_create();
+ if (!conf) {
+ return NULL;
+ }
+
+ flb_cf_set_origin_format(conf, FLB_CF_YAML);
+ }
+
+ /* initialize the parser state */
+ ret = local_init(&ctx);
+
+ if (ret == -1) {
+ flb_cf_destroy(conf);
+ return NULL;
+ }
+
+ /* process the entry poing config file */
+ ret = read_config(conf, &ctx, NULL, file_path);
+
+ if (ret == -1) {
+ flb_cf_destroy(conf);
+ local_exit(&ctx);
+ return NULL;
+ }
+
+ local_exit(&ctx);
+ return conf;
+}
diff --git a/fluent-bit/src/config_format/flb_config_format.c b/fluent-bit/src/config_format/flb_config_format.c
new file mode 100644
index 000000000..3a62fc9e2
--- /dev/null
+++ b/fluent-bit/src/config_format/flb_config_format.c
@@ -0,0 +1,878 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <ctype.h>
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_config_format.h>
+
+#include <cfl/cfl.h>
+#include <cfl/cfl_sds.h>
+#include <cfl/cfl_variant.h>
+#include <cfl/cfl_kvlist.h>
+
+int flb_cf_file_read()
+{
+ return 0;
+}
+
+/* Retrieve the proper key name, it tries to auto-detect Yaml camelcase and convert it to snake_case */
+flb_sds_t flb_cf_key_translate(struct flb_cf *cf, char *key, int len)
+{
+ int i;
+ int x = 0;
+ int is_upper;
+ flb_sds_t out;
+
+ if (!key || len <= 0) {
+ return NULL;
+ }
+
+ /* If we got something in classic format, just convert it to lowercase and return */
+ if (cf->format == FLB_CF_CLASSIC) {
+ out = flb_sds_create_len(key, len);
+ if (!out) {
+ return NULL;
+ }
+
+ for (i = 0; i < len; i++) {
+ out[i] = tolower(key[i]);
+ }
+ flb_sds_len_set(out, len);
+ return out;
+ }
+
+ /*
+ * First step is try to identify if the incoming key is a strict Yaml camelcase format
+ */
+
+ /* does it start with a lowercase character ? */
+ if (!islower(key[0])) {
+ return flb_sds_create_len(key, len);
+ }
+
+ /* copy content and check if we have underscores */
+ out = flb_sds_create_size(len * 2);
+ flb_sds_cat_safe(&out, key, len);
+
+ for (i = 0; i < len; i++) {
+ if (key[i] == '_') {
+ /* the config is classic mode, so it's safe to return the same copy of the content */
+ for (i = 0; i < len; i++) {
+ out[i] = tolower(key[i]);
+ }
+ flb_sds_len_set(out, len);
+ return out;
+ }
+ }
+
+ /* translate from camelCase to snake_case */
+ for (i = 0; i < len; i++) {
+ is_upper = isupper(key[i]);
+ if (is_upper && i > 0) {
+ out[x++] = '_';
+ }
+ out[x++] = tolower(key[i]);
+
+ }
+ out[x] = '\0';
+ flb_sds_len_set(out, x);
+ return out;
+}
+
+struct flb_cf *flb_cf_create()
+{
+ struct flb_cf *ctx;
+
+ ctx = flb_calloc(1, sizeof(struct flb_cf));
+ if (!ctx) {
+ flb_errno();
+ return NULL;
+ }
+ ctx->format = FLB_CF_CLASSIC;
+
+ /* env vars */
+ mk_list_init(&ctx->env);
+
+ /* meta commands */
+ mk_list_init(&ctx->metas);
+
+ /* parsers */
+ mk_list_init(&ctx->parsers);
+ mk_list_init(&ctx->multiline_parsers);
+
+ /* custom plugins */
+ mk_list_init(&ctx->customs);
+
+ /* pipeline */
+ mk_list_init(&ctx->inputs);
+ mk_list_init(&ctx->filters);
+ mk_list_init(&ctx->outputs);
+
+ /* other sections */
+ mk_list_init(&ctx->others);
+
+ /* general list for sections */
+ mk_list_init(&ctx->sections);
+
+ return ctx;
+}
+
+void flb_cf_destroy(struct flb_cf *cf)
+{
+ flb_kv_release(&cf->env);
+ flb_kv_release(&cf->metas);
+ flb_cf_section_destroy_all(cf);
+ flb_free(cf);
+}
+
+int flb_cf_set_origin_format(struct flb_cf *cf, int format)
+{
+#ifdef FLB_HAVE_LIBYAML
+ if (format != FLB_CF_CLASSIC && format != FLB_CF_YAML) {
+#else
+ if (format != FLB_CF_CLASSIC) {
+#endif
+ return -1;
+ }
+
+ cf->format = format;
+ return 0;
+}
+
+static enum section_type get_section_type(char *name, int len)
+{
+ if (strncasecmp(name, "SERVICE", len) == 0) {
+ return FLB_CF_SERVICE;
+ }
+ else if (strncasecmp(name, "PARSER", len) == 0) {
+ return FLB_CF_PARSER;
+ }
+ else if (strncasecmp(name, "MULTILINE_PARSER", len) == 0) {
+ return FLB_CF_MULTILINE_PARSER;
+ }
+ else if (strncasecmp(name, "CUSTOM", len) == 0 ||
+ strncasecmp(name, "CUSTOMS", len) == 0) {
+ return FLB_CF_CUSTOM;
+ }
+ else if (strncasecmp(name, "INPUT", len) == 0 ||
+ strncasecmp(name, "INPUTS", len) == 0) {
+ return FLB_CF_INPUT;
+ }
+ else if (strncasecmp(name, "FILTER", len) == 0 ||
+ strncasecmp(name, "FILTERS", len) == 0) {
+ return FLB_CF_FILTER;
+ }
+ else if (strncasecmp(name, "OUTPUT", len) == 0 ||
+ strncasecmp(name, "OUTPUTS", len) == 0) {
+ return FLB_CF_OUTPUT;
+ }
+
+ return FLB_CF_OTHER;
+}
+
+int flb_cf_plugin_property_add(struct flb_cf *cf,
+ struct cfl_kvlist *kv_list,
+ char *k_buf, size_t k_len,
+ char *v_buf, size_t v_len)
+{
+ int ret;
+ flb_sds_t key;
+ flb_sds_t val;
+
+ if (k_len == 0) {
+ k_len = strlen(k_buf);
+ }
+ if (v_len == 0) {
+ v_len = strlen(v_buf);
+ }
+
+
+ key = flb_cf_key_translate(cf, k_buf, k_len);
+ if (key == NULL) {
+ return -1;
+ }
+
+ val = flb_sds_create_len(v_buf, v_len);
+ if (val == NULL) {
+ flb_sds_destroy(key);
+ return -1;
+ }
+
+ /* sanitize key and value by removing empty spaces */
+ ret = flb_sds_trim(key);
+ if (ret == -1) {
+ flb_cf_error_set(cf, FLB_CF_ERROR_KV_INVALID_KEY);
+ flb_sds_destroy(key);
+ flb_sds_destroy(val);
+ return -1;
+ }
+
+ ret = flb_sds_trim(val);
+ if (ret == -1) {
+ flb_cf_error_set(cf, FLB_CF_ERROR_KV_INVALID_VAL);
+ flb_sds_destroy(key);
+ flb_sds_destroy(val);
+ return ret;
+ }
+
+ ret = cfl_kvlist_insert_string(kv_list, key, val);
+ flb_sds_destroy(key);
+ flb_sds_destroy(val);
+ return ret;
+}
+
+struct cfl_variant *flb_cf_section_property_add(struct flb_cf *cf,
+ struct cfl_kvlist *kv_list,
+ char *k_buf, size_t k_len,
+ char *v_buf, size_t v_len)
+{
+ int rc;
+ flb_sds_t key;
+ flb_sds_t val;
+ struct cfl_variant *var;
+
+ if (k_len == 0) {
+ k_len = strlen(k_buf);
+ }
+
+ key = flb_cf_key_translate(cf, k_buf, k_len);
+ if (key == NULL) {
+ goto key_error;
+ }
+
+ /* sanitize key and value by removing empty spaces */
+ rc = flb_sds_trim(key);
+ if (rc == -1) {
+ flb_cf_error_set(cf, FLB_CF_ERROR_KV_INVALID_KEY);
+ goto val_error;
+ }
+
+ if (v_len == 0) {
+ v_len = strlen(v_buf);
+ }
+ val = flb_sds_create_len(v_buf, v_len);
+ if (val == NULL) {
+ goto val_error;
+ }
+ /* sanitize key and value by removing empty spaces */
+ rc = flb_sds_trim(val);
+ if (rc == -1) {
+ flb_cf_error_set(cf, FLB_CF_ERROR_KV_INVALID_VAL);
+ goto var_error;
+ }
+
+ var = cfl_variant_create_from_string(val);
+ if (var == NULL) {
+ goto var_error;
+ }
+
+ rc = cfl_kvlist_insert(kv_list, key, var);
+ if (rc < 0) {
+ goto insert_error;
+ }
+
+ flb_sds_destroy(val);
+ flb_sds_destroy(key);
+ return var;
+
+insert_error:
+ cfl_variant_destroy(var);
+var_error:
+ flb_sds_destroy(val);
+val_error:
+ flb_sds_destroy(key);
+key_error:
+ return NULL;
+}
+
+struct cfl_array *flb_cf_section_property_add_list(struct flb_cf *cf,
+ struct cfl_kvlist *kv_list,
+ char *k_buf, size_t k_len)
+{
+ int rc;
+ flb_sds_t key;
+ struct cfl_array *arr;
+
+
+ if (k_len == 0) {
+ k_len = strlen(k_buf);
+ }
+
+ key = flb_cf_key_translate(cf, k_buf, k_len);
+ if (key == NULL) {
+ goto key_error;
+ }
+
+ arr = cfl_array_create(10);
+ if (arr == NULL) {
+ goto array_error;
+ }
+ cfl_array_resizable(arr, 1);
+
+ /* sanitize key and value by removing empty spaces */
+ rc = flb_sds_trim(key);
+ if (rc == -1) {
+ flb_cf_error_set(cf, FLB_CF_ERROR_KV_INVALID_KEY);
+ goto cfg_error;
+ }
+
+ rc = cfl_kvlist_insert_array(kv_list, key, arr);
+ if (rc < 0) {
+ goto cfg_error;
+ }
+
+ flb_sds_destroy(key);
+ return arr;
+
+cfg_error:
+ cfl_array_destroy(arr);
+array_error:
+ flb_sds_destroy(key);
+key_error:
+ return NULL;
+}
+
+flb_sds_t flb_cf_section_property_get_string(struct flb_cf *cf, struct flb_cf_section *s,
+ char *key)
+{
+ (void) cf;
+ flb_sds_t tkey;
+ struct cfl_variant *val;
+ flb_sds_t ret = NULL;
+ struct cfl_variant *entry;
+ int i;
+ int len;
+
+ len = strlen(key);
+ tkey = flb_cf_key_translate(cf, key, len);
+
+ val = cfl_kvlist_fetch(s->properties, key);
+ flb_sds_destroy(tkey);
+ if (val == NULL) {
+ return NULL;
+ }
+
+ if (val->type == CFL_VARIANT_STRING) {
+ ret = flb_sds_create(val->data.as_string);
+ }
+ if (val->type == CFL_VARIANT_ARRAY) {
+ // recreate the format SLISTS are expecting...
+ ret = flb_sds_create(" ");
+ for (i = 0; i < val->data.as_array->entry_count; i++) {
+ entry = val->data.as_array->entries[i];
+ if (entry->type != CFL_VARIANT_STRING) {
+ flb_sds_destroy(ret);
+ return NULL;
+ }
+ if ((i+1) < val->data.as_array->entry_count) {
+ flb_sds_printf(&ret, "%s ", entry->data.as_string);
+ } else {
+ flb_sds_printf(&ret, "%s", entry->data.as_string);
+ }
+ }
+ }
+ return ret;
+}
+
+struct cfl_variant * flb_cf_section_property_get(struct flb_cf *cf, struct flb_cf_section *s,
+ char *key)
+{
+ (void) cf;
+ return cfl_kvlist_fetch(s->properties, key);
+}
+
+struct flb_kv *flb_cf_env_property_add(struct flb_cf *cf,
+ char *k_buf, size_t k_len,
+ char *v_buf, size_t v_len)
+{
+ int ret;
+ struct flb_kv *kv;
+
+ if (k_len == 0) {
+ k_len = strlen(k_buf);
+ }
+ if (v_len == 0) {
+ v_len = strlen(v_buf);
+ }
+
+ kv = flb_kv_item_create_len(&cf->env, k_buf, k_len, v_buf, v_len);
+ if (!kv) {
+ return NULL;
+ }
+
+ /* sanitize key and value by removing empty spaces */
+ ret = flb_sds_trim(kv->key);
+ if (ret == -1) {
+ flb_cf_error_set(cf, FLB_CF_ERROR_KV_INVALID_KEY);
+ flb_kv_item_destroy(kv);
+ return NULL;
+ }
+
+ ret = flb_sds_trim(kv->val);
+ if (ret == -1) {
+ flb_cf_error_set(cf, FLB_CF_ERROR_KV_INVALID_VAL);
+ flb_kv_item_destroy(kv);
+ return NULL;
+ }
+
+ return kv;
+}
+
+static struct flb_kv *meta_property_add(struct flb_cf *cf,
+ char *k_buf, size_t k_len,
+ char *v_buf, size_t v_len)
+{
+ int ret;
+ struct flb_kv *kv;
+
+ if (k_len == 0) {
+ k_len = strlen(k_buf);
+ }
+ if (v_len == 0) {
+ v_len = strlen(v_buf);
+ }
+
+ kv = flb_kv_item_create_len(&cf->metas, k_buf, k_len, v_buf, v_len);
+ if (!kv) {
+ return NULL;
+ }
+
+ /* sanitize key and value by removing empty spaces */
+ ret = flb_sds_trim(kv->key);
+ if (ret == -1) {
+ flb_cf_error_set(cf, FLB_CF_ERROR_KV_INVALID_KEY);
+ flb_kv_item_destroy(kv);
+ return NULL;
+ }
+
+ ret = flb_sds_trim(kv->val);
+ if (ret == -1) {
+ flb_cf_error_set(cf, FLB_CF_ERROR_KV_INVALID_VAL);
+ flb_kv_item_destroy(kv);
+ return NULL;
+ }
+
+ return kv;
+}
+
+struct flb_kv *flb_cf_meta_property_add(struct flb_cf *cf, char *meta, int len)
+{
+ int xlen;
+ char *p;
+ char *tmp;
+
+ if (len <= 0) {
+ len = strlen(meta);
+ if (len == 0) {
+ return NULL;
+ }
+ }
+
+ if (meta[0] != '@') {
+ flb_cf_error_set(cf, FLB_CF_ERROR_META_CHAR);
+ return NULL;
+ }
+
+ p = meta;
+ tmp = strchr(p, ' ');
+ if (tmp == NULL) {
+ return NULL;
+ }
+ xlen = (tmp - p);
+
+ /* create k/v pair */
+ return meta_property_add(cf,
+ meta + 1, xlen - 1,
+ meta + xlen + 1, len - xlen - 1);
+}
+
+struct flb_cf_group *flb_cf_group_create(struct flb_cf *cf, struct flb_cf_section *s,
+ char *name, int len)
+{
+ struct flb_cf_group *g;
+
+ if (!name || strlen(name) == 0 || len < 1) {
+ return NULL;
+ }
+
+ /* section context */
+ g = flb_malloc(sizeof(struct flb_cf_group));
+ if (!g) {
+ flb_errno();
+ return NULL;
+ }
+
+ /* initialize lists */
+ g->properties = cfl_kvlist_create();
+ if (g->properties == NULL) {
+ flb_free(g);
+ return NULL;
+ }
+
+ /* determinate type by name */
+ if (len <= 0) {
+ len = strlen(name);
+ }
+
+ /* create a NULL terminated name */
+ g->name = flb_sds_create_len(name, len);
+ if (!g->name) {
+ cfl_kvlist_destroy(g->properties);
+ flb_free(g);
+ return NULL;
+ }
+
+ /* link to global section */
+ mk_list_add(&g->_head, &s->groups);
+
+ return g;
+}
+
+struct flb_cf_group *flb_cf_group_get(struct flb_cf *cf, struct flb_cf_section *s, char *name)
+{
+ struct mk_list *head;
+ struct flb_cf_group *g;
+
+ mk_list_foreach(head, &s->groups) {
+ g = mk_list_entry(head, struct flb_cf_group, _head);
+ if (strcasecmp(g->name, name) == 0){
+ return g;
+ }
+ }
+
+ return NULL;
+}
+
+void flb_cf_group_print(struct flb_cf_group *g)
+{
+ cfl_kvlist_print(stdout, g->properties);
+}
+
+void flb_cf_group_destroy(struct flb_cf_group *g)
+{
+ if (g->name) {
+ flb_sds_destroy(g->name);
+ }
+
+ cfl_kvlist_destroy(g->properties);
+ mk_list_del(&g->_head);
+ flb_free(g);
+}
+
+struct flb_cf_section *flb_cf_section_create(struct flb_cf *cf, char *name, int len)
+{
+ int type;
+ struct flb_cf_section *s;
+
+ if (!name) {
+ return NULL;
+ }
+
+ /* determinate type by name */
+ if (len <= 0) {
+ len = strlen(name);
+ }
+
+ /* get the section type */
+ type = get_section_type(name, len);
+
+ /* check if 'service' already exists */
+ if (type == FLB_CF_SERVICE && cf->service) {
+ return cf->service;
+ }
+
+ /* section context */
+ s = flb_malloc(sizeof(struct flb_cf_section));
+ if (!s) {
+ flb_errno();
+ return NULL;
+ }
+
+ /* initialize lists */
+ s->properties = cfl_kvlist_create();
+ mk_list_init(&s->groups);
+
+ /* create a NULL terminated name */
+ s->name = flb_sds_create_len(name, len);
+ if (!s->name) {
+ flb_free(s->properties);
+ flb_free(s);
+ return NULL;
+ }
+ s->type = type;
+
+ if (type == FLB_CF_SERVICE && !cf->service) {
+ cf->service = s;
+ }
+
+ /* link to global section */
+ mk_list_add(&s->_head, &cf->sections);
+
+ /* link to list per type */
+ if (type == FLB_CF_PARSER) {
+ mk_list_add(&s->_head_section, &cf->parsers);
+ }
+ else if (type == FLB_CF_MULTILINE_PARSER) {
+ mk_list_add(&s->_head_section, &cf->multiline_parsers);
+ }
+ else if (type == FLB_CF_CUSTOM) {
+ mk_list_add(&s->_head_section, &cf->customs);
+ }
+ else if (type == FLB_CF_INPUT) {
+ mk_list_add(&s->_head_section, &cf->inputs);
+ }
+ else if (type == FLB_CF_FILTER) {
+ mk_list_add(&s->_head_section, &cf->filters);
+ }
+ else if (type == FLB_CF_OUTPUT) {
+ mk_list_add(&s->_head_section, &cf->outputs);
+ }
+ else if (type == FLB_CF_OTHER) {
+ mk_list_add(&s->_head_section, &cf->others);
+ }
+
+ return s;
+}
+
+/* returns the first match of a section that it name matches 'name' parameter */
+struct flb_cf_section *flb_cf_section_get_by_name(struct flb_cf *cf, char *name)
+{
+ struct mk_list *head;
+ struct flb_cf_section *s;
+
+ mk_list_foreach(head, &cf->sections) {
+ s = mk_list_entry(head, struct flb_cf_section, _head);
+ if (strcasecmp(s->name, name) == 0) {
+ return s;
+ }
+ }
+
+ return NULL;
+}
+
+void flb_cf_section_destroy(struct flb_cf *cf, struct flb_cf_section *s)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_cf_group *g;
+
+ if (s->name) {
+ flb_sds_destroy(s->name);
+ s->name = NULL;
+ }
+ cfl_kvlist_destroy(s->properties);
+
+ /* groups */
+ mk_list_foreach_safe(head, tmp, &s->groups) {
+ g = mk_list_entry(head, struct flb_cf_group, _head);
+ flb_cf_group_destroy(g);
+ }
+
+ /* unlink */
+ mk_list_del(&s->_head);
+
+ if (s->type != FLB_CF_SERVICE) {
+ mk_list_del(&s->_head_section);
+ }
+
+ flb_free(s);
+}
+
+static void section_destroy_list(struct flb_cf *cf, struct mk_list *list)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_cf_section *s;
+
+ mk_list_foreach_safe(head, tmp, list) {
+ s = mk_list_entry(head, struct flb_cf_section, _head);
+ flb_cf_section_destroy(cf, s);
+ }
+}
+
+void flb_cf_section_destroy_all(struct flb_cf *cf)
+{
+ section_destroy_list(cf, &cf->sections);
+}
+
+/*
+ * Helpers
+ * -------
+ */
+
+static char *section_type_str(int type)
+{
+ switch (type) {
+ case FLB_CF_SERVICE:
+ return "SERVICE";
+ case FLB_CF_PARSER:
+ return "PARSER";
+ case FLB_CF_MULTILINE_PARSER:
+ return "MULTILINE_PARSER";
+ case FLB_CF_CUSTOM:
+ return "CUSTOM";
+ case FLB_CF_INPUT:
+ return "INPUT";
+ case FLB_CF_FILTER:
+ return "FILTER";
+ case FLB_CF_OUTPUT:
+ return "OUTPUT";
+ case FLB_CF_OTHER:
+ return "OTHER";
+ default:
+ return "error / unknown";
+ }
+
+ return NULL;
+}
+
+static void dump_section(struct flb_cf_section *s)
+{
+ struct mk_list *head;
+ struct cfl_list *p_head;
+ struct cfl_kvpair *kv;
+ struct flb_cf_group *g;
+
+ printf("> section:\n name: %s\n type: %s\n",
+ s->name, section_type_str(s->type));
+
+ if (cfl_list_size(&s->properties->list) > 0) {
+ printf(" properties:\n");
+ cfl_list_foreach(p_head, &s->properties->list) {
+ kv = cfl_list_entry(p_head, struct cfl_kvpair, _head);
+ printf(" - %-15s: %s\n", kv->key, kv->val->data.as_string);
+ }
+ }
+ else {
+ printf(" properties: NONE\n");
+ }
+
+ if (mk_list_size(&s->groups) <= 0) {
+ printf(" groups : NONE\n");
+ return;
+ }
+
+ mk_list_foreach(head, &s->groups) {
+ g = mk_list_entry(head, struct flb_cf_group, _head);
+ printf(" > group:\n name: %s\n", g->name);
+
+ if (cfl_list_size(&g->properties->list) > 0) {
+ printf(" properties:\n");
+ cfl_list_foreach(p_head, &g->properties->list) {
+ kv = cfl_list_entry(p_head, struct cfl_kvpair, _head);
+ printf(" - %-11s: %s\n", kv->key, kv->val->data.as_string);
+ }
+ }
+ else {
+ printf(" properties: NONE\n");
+ }
+ }
+}
+
+static void dump_env(struct mk_list *list)
+{
+ struct mk_list *head;
+ struct flb_kv *kv;
+
+ if (mk_list_size(list) == 0) {
+ return;
+ }
+
+ printf("> env:\n");
+
+ mk_list_foreach(head, list) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ printf(" - %-15s: %s\n", kv->key, kv->val);
+ }
+}
+
+static void dump_metas(struct mk_list *list)
+{
+ struct mk_list *head;
+ struct flb_kv *kv;
+
+ if (mk_list_size(list) == 0) {
+ return;
+ }
+
+ printf("> metas:\n");
+
+ mk_list_foreach(head, list) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ printf(" - %-15s: %s\n", kv->key, kv->val);
+ }
+}
+
+static void dump_section_list(struct mk_list *list)
+{
+ struct mk_list *head;
+ struct flb_cf_section *s;
+
+ mk_list_foreach(head, list) {
+ s = mk_list_entry(head, struct flb_cf_section, _head);
+ dump_section(s);
+ }
+}
+
+void flb_cf_dump(struct flb_cf *cf)
+{
+ dump_metas(&cf->metas);
+ dump_env(&cf->env);
+ dump_section_list(&cf->sections);
+}
+
+struct flb_cf *flb_cf_create_from_file(struct flb_cf *cf, char *file)
+{
+ int format = FLB_CF_FLUENTBIT;
+ char *ptr;
+
+ if (!file) {
+ return NULL;
+ }
+
+ ptr = strrchr(file, '.');
+ if (!ptr) {
+ format = FLB_CF_FLUENTBIT;
+ }
+ else {
+ if (strcasecmp(ptr, ".conf") == 0) {
+ format = FLB_CF_FLUENTBIT;
+ }
+#ifdef FLB_HAVE_LIBYAML
+ else if (strcasecmp(ptr, ".yaml") == 0 || strcasecmp(ptr, ".yml") == 0) {
+ format = FLB_CF_YAML;
+ }
+#endif
+ }
+
+ if (format == FLB_CF_FLUENTBIT) {
+ cf = flb_cf_fluentbit_create(cf, file, NULL, 0);
+ }
+#ifdef FLB_HAVE_LIBYAML
+ else if (format == FLB_CF_YAML) {
+ cf = flb_cf_yaml_create(cf, file, NULL, 0);
+ }
+#endif
+
+ return cf;
+ }
+
diff --git a/fluent-bit/src/flb_api.c b/fluent-bit/src/flb_api.c
new file mode 100644
index 000000000..6c6a60ea6
--- /dev/null
+++ b/fluent-bit/src/flb_api.c
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_api.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_output.h>
+
+struct flb_api *flb_api_create()
+{
+ struct flb_api *api;
+
+ api = flb_malloc(sizeof(struct flb_api));
+ if (!api) {
+ flb_errno();
+ return NULL;
+ }
+
+ api->output_get_property = flb_output_get_property;
+ api->input_get_property = flb_input_get_property;
+
+#ifdef FLB_HAVE_METRICS
+ api->output_get_cmt_instance = flb_output_get_cmt_instance;
+ api->input_get_cmt_instance = flb_input_get_cmt_instance;
+#endif
+
+ api->log_print = flb_log_print;
+ api->input_log_check = flb_input_log_check;
+ api->output_log_check = flb_output_log_check;
+
+ return api;
+}
+
+void flb_api_destroy(struct flb_api *api)
+{
+ flb_free(api);
+}
diff --git a/fluent-bit/src/flb_avro.c b/fluent-bit/src/flb_avro.c
new file mode 100644
index 000000000..fe45fb5da
--- /dev/null
+++ b/fluent-bit/src/flb_avro.c
@@ -0,0 +1,397 @@
+/*-*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <fluent-bit/flb_macros.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_error.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_avro.h>
+
+static inline int do_avro(bool call, const char *msg) {
+ if (call) {
+ flb_error("%s:\n %s\n", msg, avro_strerror());
+ return FLB_FALSE;
+ }
+ return FLB_TRUE;
+}
+
+avro_value_iface_t *flb_avro_init(avro_value_t *aobject, char *json, size_t json_len, avro_schema_t *aschema)
+{
+
+ flb_debug("in flb_avro_init:before error:%s:json len:%zu:\n", avro_strerror(), json_len);
+
+ if (avro_schema_from_json_length(json, json_len, aschema)) {
+ flb_error("Unable to parse aobject schema:%s:error:%s:\n", json, avro_strerror());
+ return NULL;
+ }
+
+ avro_value_iface_t *aclass = avro_generic_class_from_schema(*aschema);
+
+ if(aclass == NULL) {
+ flb_error("Unable to instantiate class from schema:%s:\n", avro_strerror());
+ return NULL;
+ }
+
+ if(avro_generic_value_new(aclass, aobject) != 0) {
+ flb_error("Unable to allocate new avro value:%s:\n", avro_strerror());
+ return NULL;
+ }
+
+ return aclass;
+}
+
+int msgpack2avro(avro_value_t *val, msgpack_object *o)
+{
+ int ret = FLB_FALSE;
+ flb_debug("in msgpack2avro\n");
+
+ assert(val != NULL);
+ assert(o != NULL);
+
+ switch(o->type) {
+ case MSGPACK_OBJECT_NIL:
+ flb_debug("got a nil:\n");
+ ret = do_avro(avro_value_set_null(val), "failed on nil");
+ break;
+
+ case MSGPACK_OBJECT_BOOLEAN:
+ flb_debug("got a bool:%s:\n", (o->via.boolean ? "true" : "false"));
+ ret = do_avro(avro_value_set_boolean(val, o->via.boolean), "failed on bool");
+ break;
+
+ case MSGPACK_OBJECT_POSITIVE_INTEGER:
+ // for reference src/objectc.c +/msgpack_pack_object
+#if defined(PRIu64)
+ // msgpack_pack_fix_uint64
+ flb_debug("got a posint: %" PRIu64 "\n", o->via.u64);
+ ret = do_avro(avro_value_set_int(val, o->via.u64), "failed on posint");
+#else
+ if (o.via.u64 > ULONG_MAX)
+ flb_warn("over \"%lu\"", ULONG_MAX);
+ ret = do_avro(avro_value_set_int(val, ULONG_MAX), "failed on posint");
+ else
+ flb_debug("got a posint: %lu\n", (unsigned long)o->via.u64);
+ ret = do_avro(avro_value_set_int(val, o->via.u64), "failed on posint");
+#endif
+
+ break;
+
+ case MSGPACK_OBJECT_NEGATIVE_INTEGER:
+#if defined(PRIi64)
+ flb_debug("got a negint: %" PRIi64 "\n", o->via.i64);
+ ret = do_avro(avro_value_set_int(val, o->via.i64), "failed on negint");
+#else
+ if (o->via.i64 > LONG_MAX)
+ flb_warn("over +\"%ld\"", LONG_MAX);
+ ret = do_avro(avro_value_set_int(val, LONG_MAX), "failed on negint");
+ else if (o->via.i64 < LONG_MIN)
+ flb_warn("under -\"%ld\"", LONG_MIN);
+ ret = do_avro(avro_value_set_int(val, LONG_MIN), "failed on negint");
+ else
+ flb_debug("got a negint: %ld\n", (signed long)o->via.i64);
+ ret = do_avro(avro_value_set_int(val, o->via.i64), "failed on negint");
+#endif
+ break;
+
+ case MSGPACK_OBJECT_FLOAT32:
+ case MSGPACK_OBJECT_FLOAT64:
+ flb_debug("got a float: %f\n", o->via.f64);
+ ret = do_avro(avro_value_set_float(val, o->via.f64), "failed on float");
+ break;
+
+ case MSGPACK_OBJECT_STR:
+ {
+ flb_debug("got a string: \"");
+
+ if (flb_log_check(FLB_LOG_DEBUG))
+ fwrite(o->via.str.ptr, o->via.str.size, 1, stderr);
+ flb_debug("\"\n");
+
+ flb_debug("setting string:%.*s:\n", o->via.str.size, o->via.str.ptr);
+ flb_sds_t cstr = flb_sds_create_len(o->via.str.ptr, o->via.str.size);
+ ret = do_avro(avro_value_set_string_len(val, cstr, flb_sds_len(cstr) + 1), "failed on string");
+ flb_sds_destroy(cstr);
+ flb_debug("set string\n");
+ }
+ break;
+
+ case MSGPACK_OBJECT_BIN:
+ flb_debug("got a binary\n");
+ ret = do_avro(avro_value_set_bytes(val, (void *)o->via.bin.ptr, o->via.bin.size), "failed on bin");
+ break;
+
+ case MSGPACK_OBJECT_EXT:
+#if defined(PRIi8)
+ flb_debug("got an ext: %" PRIi8 ")", o->via.ext.type);
+#else
+ flb_debug("got an ext: %d)", (int)o->via.ext.type);
+#endif
+ ret = do_avro(avro_value_set_bytes(val, (void *)o->via.bin.ptr, o->via.bin.size), "failed on ext");
+ break;
+
+ case MSGPACK_OBJECT_ARRAY:
+ {
+
+ flb_debug("got a array:size:%u:\n", o->via.array.size);
+ if(o->via.array.size != 0) {
+ msgpack_object* p = o->via.array.ptr;
+ msgpack_object* const pend = o->via.array.ptr + o->via.array.size;
+ int i = 0;
+ for(; p < pend; ++p) {
+ avro_value_t element;
+ flb_debug("processing array\n");
+ if (
+ !do_avro(avro_value_append(val, &element, NULL), "Cannot append to array") ||
+ !do_avro(avro_value_get_by_index(val, i++, &element, NULL), "Cannot get element")) {
+ goto msg2avro_end;
+ }
+ ret = flb_msgpack_to_avro(&element, p);
+ }
+ }
+ }
+ break;
+
+ case MSGPACK_OBJECT_MAP:
+ flb_debug("got a map\n");
+ if(o->via.map.size != 0) {
+ msgpack_object_kv* p = o->via.map.ptr;
+ msgpack_object_kv* const pend = o->via.map.ptr + o->via.map.size;
+ for(; p < pend; ++p) {
+ avro_value_t element;
+ if (p->key.type != MSGPACK_OBJECT_STR) {
+ flb_debug("the key of in a map must be string.\n");
+ continue;
+ }
+ flb_sds_t key = flb_sds_create_len(p->key.via.str.ptr, p->key.via.str.size);
+ flb_debug("got key:%s:\n", key);
+
+ if (val == NULL) {
+ flb_debug("got a null val\n");
+ flb_sds_destroy(key);
+ continue;
+ }
+ // this does not always return 0 for succcess
+ if (avro_value_add(val, key, &element, NULL, NULL) != 0) {
+ flb_debug("avro_value_add:key:%s:avro error:%s:\n", key, avro_strerror());
+ }
+ flb_debug("added\n");
+
+ flb_debug("calling avro_value_get_by_name\n");
+ if (!do_avro(avro_value_get_by_name(val, key, &element, NULL), "Cannot get field")) {
+ flb_sds_destroy(key);
+ goto msg2avro_end;
+ }
+ flb_debug("called avro_value_get_by_index\n");
+
+ ret = flb_msgpack_to_avro(&element, &p->val);
+
+ flb_sds_destroy(key);
+ }
+ }
+ break;
+
+ default:
+ // FIXME
+#if defined(PRIu64)
+ flb_warn(" #<UNKNOWN %i %" PRIu64 ">\n", o->type, o->via.u64);
+#else
+ if (o.via.u64 > ULONG_MAX)
+ flb_warn(" #<UNKNOWN %i over 4294967295>", o->type);
+ else
+ flb_warn(" #<UNKNOWN %i %lu>", o.type, (unsigned long)o->via.u64);
+#endif
+ // noop
+ break;
+ }
+
+msg2avro_end:
+ return ret;
+
+}
+
+/**
+ * convert msgpack to an avro object.
+ * it will fill the avro value with whatever comes in from the msgpack
+ * instantiate the avro value properly according to avro-c style
+ * - avro_schema_from_json_literal
+ * - avro_generic_class_from_schema
+ * - avro_generic_value_new
+ *
+ * or use flb_avro_init for the initialization
+ *
+ * refer to avro docs
+ * http://avro.apache.org/docs/current/api/c/index.html#_avro_values
+ *
+ * @param val An initialized avro value, an instiatied instance of the class to be unpacked.
+ * @param data The msgpack_unpacked data.
+ * @return success FLB_TRUE on success
+ */
+int flb_msgpack_to_avro(avro_value_t *val, msgpack_object *o)
+{
+ int ret = -1;
+
+ if (val == NULL || o == NULL) {
+ flb_error("flb_msgpack_to_avro called with NULL\n");
+ return ret;
+ }
+
+ ret = msgpack2avro(val, o);
+
+ return ret;
+}
+
+bool flb_msgpack_raw_to_avro_sds(const void *in_buf, size_t in_size, struct flb_avro_fields *ctx, char *out_buff, size_t *out_size)
+{
+ msgpack_unpacked result;
+ msgpack_object *root;
+
+ avro_writer_t awriter;
+ flb_debug("in flb_msgpack_raw_to_avro_sds\n");
+ flb_debug("schemaID:%s:\n", ctx->schema_id);
+ flb_debug("schema string:%s:\n", ctx->schema_str);
+
+ size_t schema_json_len = flb_sds_len(ctx->schema_str);
+
+ avro_value_t aobject;
+
+ assert(in_buf != NULL);
+
+ avro_value_iface_t *aclass = NULL;
+ avro_schema_t aschema;
+
+ aclass = flb_avro_init(&aobject, (char *)ctx->schema_str, schema_json_len, &aschema);
+
+ if (!aclass) {
+ flb_error("Failed init avro:%s:n", avro_strerror());
+ return false;
+ }
+
+ msgpack_unpacked_init(&result);
+ if (msgpack_unpack_next(&result, in_buf, in_size, NULL) != MSGPACK_UNPACK_SUCCESS) {
+ flb_error("msgpack_unpack problem\n");
+ avro_value_decref(&aobject);
+ avro_value_iface_decref(aclass);
+ avro_schema_decref(aschema);
+ return false;
+ }
+
+ root = &result.data;
+
+ // create the avro object
+ // then serialize it into a buffer for the downstream
+ flb_debug("calling flb_msgpack_to_avro\n");
+
+ if (flb_msgpack_to_avro(&aobject, root) != FLB_TRUE) {
+ flb_errno();
+ flb_error("Failed msgpack to avro\n");
+ msgpack_unpacked_destroy(&result);
+ avro_value_decref(&aobject);
+ avro_value_iface_decref(aclass);
+ avro_schema_decref(aschema);
+ return false;
+ }
+
+ flb_debug("before avro_writer_memory\n");
+ awriter = avro_writer_memory(out_buff, *out_size);
+ if (awriter == NULL) {
+ flb_error("Unable to init avro writer\n");
+ msgpack_unpacked_destroy(&result);
+ avro_value_decref(&aobject);
+ avro_value_iface_decref(aclass);
+ avro_schema_decref(aschema);
+ return false;
+ }
+
+ // write the magic byte stuff
+ // write one bye of \0
+ // this is followed by
+ // 16 bytes of the schemaid where the schemaid is in hex
+ // in this implementation the schemaid is the md5hash of the avro schema
+ int rval;
+ rval = avro_write(awriter, "\0", 1);
+ if (rval != 0) {
+ flb_error("Unable to write magic byte\n");
+ avro_writer_free(awriter);
+ avro_value_decref(&aobject);
+ avro_value_iface_decref(aclass);
+ avro_schema_decref(aschema);
+ msgpack_unpacked_destroy(&result);
+ return false;
+ }
+
+ // write the schemaid
+ // its md5hash of the avro schema
+ // it looks like this c4b52aaf22429c7f9eb8c30270bc1795
+ const char *pos = ctx->schema_id;
+ unsigned char val[16];
+ size_t count;
+ for (count = 0; count < sizeof val/sizeof *val; count++) {
+ sscanf(pos, "%2hhx", &val[count]);
+ pos += 2;
+ }
+
+ // write it into a buffer which can be passed to librdkafka
+ rval = avro_write(awriter, val, 16);
+ if (rval != 0) {
+ flb_error("Unable to write schemaid\n");
+ avro_writer_free(awriter);
+ avro_value_decref(&aobject);
+ avro_value_iface_decref(aclass);
+ avro_schema_decref(aschema);
+ msgpack_unpacked_destroy(&result);
+ return false;
+ }
+
+ if (avro_value_write(awriter, &aobject)) {
+ flb_error("Unable to write avro value to memory buffer\nMessage: %s\n", avro_strerror());
+ avro_writer_free(awriter);
+ avro_value_decref(&aobject);
+ avro_value_iface_decref(aclass);
+ avro_schema_decref(aschema);
+ msgpack_unpacked_destroy(&result);
+ return false;
+ }
+
+ // null terminate it
+ avro_write(awriter, "\0", 1);
+
+ flb_debug("before avro_writer_flush\n");
+
+ avro_writer_flush(awriter);
+
+ int64_t bytes_written = avro_writer_tell(awriter);
+
+ // by here the entire object should be fully serialized into the sds buffer
+ avro_writer_free(awriter);
+ avro_value_decref(&aobject);
+ avro_value_iface_decref(aclass);
+ avro_schema_decref(aschema);
+ msgpack_unpacked_destroy(&result);
+
+ flb_debug("after memory free:bytes written:%zu:\n", bytes_written);
+ *out_size = bytes_written;
+
+ return true;
+
+}
diff --git a/fluent-bit/src/flb_base64.c b/fluent-bit/src/flb_base64.c
new file mode 100644
index 000000000..2bf442fba
--- /dev/null
+++ b/fluent-bit/src/flb_base64.c
@@ -0,0 +1,239 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2019-2021 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+/* This code is based on base64.c from the mbedtls-2.25.0 Library distribution,
+ * as originally written by Paul Bakker, et al., and forked by the Fluent Bit
+ * project to provide performant base64 encoding and decoding routines.
+ * The 2.25.0 implementation is included rather than 2.26.0+ implementation due
+ * to performance degradation introduced in 2.26.0.
+ *
+ * Method and variable names are changed by the Fluent Bit authors to maintain
+ * consistency with the Fluent Bit project.
+ * The self test section of the code was removed by the Fluent Bit authors.
+ * Other minor changes are made by the Fluent Bit authors.
+ *
+ * The original source file base64.c is copyright and licensed as follows;
+ *
+ * RFC 1521 base64 encoding/decoding
+ *
+ * Copyright The Mbed TLS Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * 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 <fluent-bit/flb_base64.h>
+
+#include <stdint.h>
+
+static const unsigned char base64_enc_map[64] =
+{
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', '+', '/'
+};
+
+static const unsigned char base64_dec_map[128] =
+{
+ 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 62, 127, 127, 127, 63, 52, 53,
+ 54, 55, 56, 57, 58, 59, 60, 61, 127, 127,
+ 127, 64, 127, 127, 127, 0, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 127, 127, 127, 127, 127, 127, 26, 27, 28,
+ 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
+ 49, 50, 51, 127, 127, 127, 127, 127
+};
+
+#define BASE64_SIZE_T_MAX ( (size_t) -1 ) /* SIZE_T_MAX is not standard */
+
+/*
+ * Encode a buffer into base64 format
+ */
+int flb_base64_encode( unsigned char *dst, size_t dlen, size_t *olen,
+ const unsigned char *src, size_t slen )
+{
+ size_t i, n;
+ int C1, C2, C3;
+ unsigned char *p;
+
+ if( slen == 0 )
+ {
+ *olen = 0;
+ return( 0 );
+ }
+
+ n = slen / 3 + ( slen % 3 != 0 );
+
+ if( n > ( BASE64_SIZE_T_MAX - 1 ) / 4 )
+ {
+ *olen = BASE64_SIZE_T_MAX;
+ return( FLB_BASE64_ERR_BUFFER_TOO_SMALL );
+ }
+
+ n *= 4;
+
+ if( ( dlen < n + 1 ) || ( NULL == dst ) )
+ {
+ *olen = n + 1;
+ return( FLB_BASE64_ERR_BUFFER_TOO_SMALL );
+ }
+
+ n = ( slen / 3 ) * 3;
+
+ for( i = 0, p = dst; i < n; i += 3 )
+ {
+ C1 = *src++;
+ C2 = *src++;
+ C3 = *src++;
+
+ *p++ = base64_enc_map[(C1 >> 2) & 0x3F];
+ *p++ = base64_enc_map[(((C1 & 3) << 4) + (C2 >> 4)) & 0x3F];
+ *p++ = base64_enc_map[(((C2 & 15) << 2) + (C3 >> 6)) & 0x3F];
+ *p++ = base64_enc_map[C3 & 0x3F];
+ }
+
+ if( i < slen )
+ {
+ C1 = *src++;
+ C2 = ( ( i + 1 ) < slen ) ? *src++ : 0;
+
+ *p++ = base64_enc_map[(C1 >> 2) & 0x3F];
+ *p++ = base64_enc_map[(((C1 & 3) << 4) + (C2 >> 4)) & 0x3F];
+
+ if( ( i + 1 ) < slen )
+ *p++ = base64_enc_map[((C2 & 15) << 2) & 0x3F];
+ else *p++ = '=';
+
+ *p++ = '=';
+ }
+
+ *olen = p - dst;
+ *p = 0;
+
+ return( 0 );
+}
+
+/*
+ * Decode a base64-formatted buffer
+ */
+int flb_base64_decode( unsigned char *dst, size_t dlen, size_t *olen,
+ const unsigned char *src, size_t slen )
+{
+ size_t i, n;
+ uint32_t j, x;
+ unsigned char *p;
+
+ /* First pass: check for validity and get output length */
+ for( i = n = j = 0; i < slen; i++ )
+ {
+ /* Skip spaces before checking for EOL */
+ x = 0;
+ while( i < slen && src[i] == ' ' )
+ {
+ ++i;
+ ++x;
+ }
+
+ /* Spaces at end of buffer are OK */
+ if( i == slen )
+ break;
+
+ if( ( slen - i ) >= 2 &&
+ src[i] == '\r' && src[i + 1] == '\n' )
+ continue;
+
+ if( src[i] == '\n' )
+ continue;
+
+ /* Space inside a line is an error */
+ if( x != 0 )
+ return( FLB_BASE64_ERR_INVALID_CHARACTER );
+
+ if( src[i] == '=' && ++j > 2 )
+ return( FLB_BASE64_ERR_INVALID_CHARACTER );
+
+ if( src[i] > 127 || base64_dec_map[src[i]] == 127 )
+ return( FLB_BASE64_ERR_INVALID_CHARACTER );
+
+ if( base64_dec_map[src[i]] < 64 && j != 0 )
+ return( FLB_BASE64_ERR_INVALID_CHARACTER );
+
+ n++;
+ }
+
+ if( n == 0 )
+ {
+ *olen = 0;
+ return( 0 );
+ }
+
+ /* The following expression is to calculate the following formula without
+ * risk of integer overflow in n:
+ * n = ( ( n * 6 ) + 7 ) >> 3;
+ */
+ n = ( 6 * ( n >> 3 ) ) + ( ( 6 * ( n & 0x7 ) + 7 ) >> 3 );
+ n -= j;
+
+ if( dst == NULL || dlen < n )
+ {
+ *olen = n;
+ return( FLB_BASE64_ERR_BUFFER_TOO_SMALL );
+ }
+
+ for( j = 3, n = x = 0, p = dst; i > 0; i--, src++ )
+ {
+ if( *src == '\r' || *src == '\n' || *src == ' ' )
+ continue;
+
+ j -= ( base64_dec_map[*src] == 64 );
+ x = ( x << 6 ) | ( base64_dec_map[*src] & 0x3F );
+
+ if( ++n == 4 )
+ {
+ n = 0;
+ if( j > 0 ) *p++ = (unsigned char)( x >> 16 );
+ if( j > 1 ) *p++ = (unsigned char)( x >> 8 );
+ if( j > 2 ) *p++ = (unsigned char)( x );
+ }
+ }
+
+ *olen = p - dst;
+
+ return( 0 );
+}
diff --git a/fluent-bit/src/flb_callback.c b/fluent-bit/src/flb_callback.c
new file mode 100644
index 000000000..760604ce7
--- /dev/null
+++ b/fluent-bit/src/flb_callback.c
@@ -0,0 +1,134 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_callback.h>
+
+struct flb_callback *flb_callback_create(char *name)
+{
+ struct flb_callback *ctx;
+
+ /* Create context */
+ ctx = flb_malloc(sizeof(struct flb_callback));
+ if (!ctx) {
+ flb_errno();
+ return NULL;
+ }
+
+ ctx->ht = flb_hash_table_create(FLB_HASH_TABLE_EVICT_NONE, 16, 0);
+ if (!ctx->ht) {
+ flb_error("[callback] error allocating hash table");
+ flb_free(ctx);
+ return NULL;
+ }
+ mk_list_init(&ctx->entries);
+
+ return ctx;
+}
+
+int flb_callback_set(struct flb_callback *ctx, char *name,
+ void (*cb)(char *, void *, void *))
+{
+ int ret;
+ int len;
+ struct flb_callback_entry *entry;
+
+ entry = flb_malloc(sizeof(struct flb_callback_entry));
+ if (!entry) {
+ flb_errno();
+ return -1;
+ }
+ entry->name = flb_sds_create(name);
+ if (!entry->name) {
+ flb_free(entry);
+ return -1;
+ }
+ entry->cb = cb;
+
+ len = strlen(name);
+ ret = flb_hash_table_add(ctx->ht, name, len,
+ (char *) &entry, sizeof(struct flb_callback_entry *));
+ if (ret == -1) {
+ flb_sds_destroy(entry->name);
+ flb_free(entry);
+ return -1;
+ }
+ mk_list_add(&entry->_head, &ctx->entries);
+
+ return ret;
+}
+
+int flb_callback_exists(struct flb_callback *ctx, char *name)
+{
+ int ret;
+ int len;
+ size_t out_size;
+ void *cb_addr;
+
+ len = strlen(name);
+ ret = flb_hash_table_get(ctx->ht, name, len, &cb_addr, &out_size);
+ if (ret == -1) {
+ return FLB_FALSE;
+ }
+
+ return FLB_TRUE;
+}
+
+int flb_callback_do(struct flb_callback *ctx, char *name, void *p1, void *p2)
+{
+ int ret;
+ int len;
+ size_t out_size;
+ void *cb_addr;
+ struct flb_callback_entry *entry;
+
+ if (!ctx) {
+ return -1;
+ }
+
+ len = strlen(name);
+ ret = flb_hash_table_get(ctx->ht, name, len, &cb_addr, &out_size);
+ if (ret == -1) {
+ return -1;
+ }
+
+ memcpy(&entry, cb_addr, sizeof(struct flb_callback_entry *));
+ entry->cb(entry->name, p1, p2);
+ return 0;
+}
+
+void flb_callback_destroy(struct flb_callback *ctx)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_callback_entry *entry;
+
+ flb_hash_table_destroy(ctx->ht);
+
+ mk_list_foreach_safe(head, tmp, &ctx->entries) {
+ entry = mk_list_entry(head, struct flb_callback_entry, _head);
+ mk_list_del(&entry->_head);
+ flb_sds_destroy(entry->name);
+ flb_free(entry);
+ }
+
+ flb_free(ctx);
+}
diff --git a/fluent-bit/src/flb_chunk_trace.c b/fluent-bit/src/flb_chunk_trace.c
new file mode 100644
index 000000000..adacb73f2
--- /dev/null
+++ b/fluent-bit/src/flb_chunk_trace.c
@@ -0,0 +1,692 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fcntl.h>
+
+#include <msgpack.h>
+#include <chunkio/chunkio.h>
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_input_chunk.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_chunk_trace.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_base64.h>
+#include <fluent-bit/flb_storage.h>
+#include <fluent-bit/flb_router.h>
+#include <fluent-bit/flb_kv.h>
+
+
+/* Register external function to emit records, check 'plugins/in_emitter' */
+int in_emitter_add_record(const char *tag, int tag_len,
+ const char *buf_data, size_t buf_size,
+ struct flb_input_instance *in);
+
+/****************************************************************************/
+/* To avoid double frees when enabling and disabling tracing as well */
+/* as avoiding race conditions when stopping fluent-bit while someone is */
+/* toggling tracing via the HTTP API this set of APIS with a mutex lock */
+/* is used: */
+/* * flb_chunk_trace_to_be_destroyed - query to see if the trace context */
+/* is slated to be freed */
+/* * flb_chunk_trace_set_destroy - set the trace context to be destroyed */
+/* once all chunks are freed (executed in flb_chunk_trace_destroy). */
+/* * flb_chunk_trace_has_chunks - see if there are still chunks using */
+/* using the tracing context */
+/* * flb_chunk_trace_add - increment the traces chunk count */
+/* * flb_chunk_trace_sub - decrement the traces chunk count */
+/****************************************************************************/
+static inline int flb_chunk_trace_to_be_destroyed(struct flb_chunk_trace_context *ctxt)
+{
+ int ret = FLB_FALSE;
+
+ ret = (ctxt->to_destroy == 1 ? FLB_TRUE : FLB_FALSE);
+ return ret;
+}
+
+static inline int flb_chunk_trace_has_chunks(struct flb_chunk_trace_context *ctxt)
+{
+ int ret = FLB_FALSE;
+
+ ret = ((ctxt->chunks > 0) ? FLB_TRUE : FLB_FALSE);
+ return ret;
+}
+
+static inline void flb_chunk_trace_add(struct flb_chunk_trace_context *ctxt)
+{
+ ctxt->chunks++;
+}
+
+static inline void flb_chunk_trace_sub(struct flb_chunk_trace_context *ctxt)
+{
+ ctxt->chunks--;
+}
+
+static inline void flb_chunk_trace_set_destroy(struct flb_chunk_trace_context *ctxt)
+{
+ ctxt->to_destroy = 1;
+}
+
+static struct flb_output_instance *find_calyptia_output_instance(struct flb_config *config)
+{
+ struct mk_list *head = NULL;
+ struct flb_output_instance *output = NULL;
+
+ mk_list_foreach(head, &config->outputs) {
+ output = mk_list_entry(head, struct flb_output_instance, _head);
+ if (strcmp(output->p->name, "calyptia") == 0) {
+ return output;
+ }
+ }
+ return NULL;
+}
+
+static void trace_chunk_context_destroy(struct flb_chunk_trace_context *ctxt)
+{
+ int i;
+
+
+ if (flb_chunk_trace_has_chunks(ctxt) == FLB_TRUE) {
+ flb_chunk_trace_set_destroy(ctxt);
+ flb_input_pause_all(ctxt->flb->config);
+ return;
+ }
+
+ /* pause all inputs, then destroy the input storage. */
+ flb_input_pause_all(ctxt->flb->config);
+ /* waiting for all tasks to end is key to safely stopping and destroying */
+ /* the fluent-bit pipeline. */
+ for (i = 0; i < 5 && flb_task_running_count(ctxt->flb->config) > 0; i++) {
+ usleep(10 * 1000);
+ }
+
+ flb_sds_destroy(ctxt->trace_prefix);
+ flb_stop(ctxt->flb);
+ flb_destroy(ctxt->flb);
+ flb_free(ctxt);
+}
+
+void flb_chunk_trace_context_destroy(void *input)
+{
+ struct flb_input_instance *in = (struct flb_input_instance *)input;
+ pthread_mutex_lock(&in->chunk_trace_lock);
+ if (in->chunk_trace_ctxt != NULL) {
+ trace_chunk_context_destroy(in->chunk_trace_ctxt);
+ in->chunk_trace_ctxt = NULL;
+ }
+ pthread_mutex_unlock(&in->chunk_trace_lock);
+}
+
+struct flb_chunk_trace_context *flb_chunk_trace_context_new(void *trace_input,
+ const char *output_name,
+ const char *trace_prefix,
+ void *data, struct mk_list *props)
+{
+ struct flb_input_instance *in = (struct flb_input_instance *)trace_input;
+ struct flb_config *config = in->config;
+ struct flb_input_instance *input = NULL;
+ struct flb_output_instance *output = NULL;
+ struct flb_output_instance *calyptia = NULL;
+ struct flb_chunk_trace_context *ctx = NULL;
+ struct mk_list *head = NULL;
+ struct flb_kv *prop = NULL;
+ int ret;
+
+ if (config->enable_chunk_trace == FLB_FALSE) {
+ flb_warn("[chunk trace] enable chunk tracing via the configuration or "
+ " command line to be able to activate tracing.");
+ return NULL;
+ }
+
+ pthread_mutex_lock(&in->chunk_trace_lock);
+
+ if (in->chunk_trace_ctxt) {
+ trace_chunk_context_destroy(in->chunk_trace_ctxt);
+ }
+
+ ctx = flb_calloc(1, sizeof(struct flb_chunk_trace_context));
+ if (ctx == NULL) {
+ flb_errno();
+ pthread_mutex_unlock(&in->chunk_trace_lock);
+ return NULL;
+ }
+
+ ctx->flb = flb_create();
+ if (ctx->flb == NULL) {
+ flb_errno();
+ goto error_ctxt;
+ }
+
+ flb_service_set(ctx->flb, "flush", "1", "grace", "1", NULL);
+
+ input = (void *)flb_input_new(ctx->flb->config, "emitter", NULL, FLB_FALSE);
+ if (input == NULL) {
+ flb_error("could not load trace emitter");
+ goto error_flb;
+ }
+
+ ret = flb_input_set_property(input, "alias", "trace-emitter");
+ if (ret != 0) {
+ flb_error("unable to set alias for trace emitter");
+ goto error_input;
+ }
+
+ ret = flb_input_set_property(input, "ring_buffer_size", "4096");
+ if (ret != 0) {
+ flb_error("unable to set ring buffer size for trace emitter");
+ goto error_input;
+ }
+
+ output = flb_output_new(ctx->flb->config, output_name, data, 1);
+ if (output == NULL) {
+ flb_error("could not create trace output");
+ goto error_input;
+ }
+
+ /* special handling for the calyptia plugin so we can copy the API */
+ /* key and other configuration properties. */
+ if (strcmp(output_name, "calyptia") == 0) {
+ calyptia = find_calyptia_output_instance(config);
+ if (calyptia == NULL) {
+ flb_error("unable to find calyptia output instance");
+ goto error_output;
+ }
+ mk_list_foreach(head, &calyptia->properties) {
+ prop = mk_list_entry(head, struct flb_kv, _head);
+ flb_output_set_property(output, prop->key, prop->val);
+ }
+ }
+ else if (props != NULL) {
+ mk_list_foreach(head, props) {
+ prop = mk_list_entry(head, struct flb_kv, _head);
+ flb_output_set_property(output, prop->key, prop->val);
+ }
+ }
+
+ ret = flb_router_connect_direct(input, output);
+ if (ret != 0) {
+ flb_error("unable to route traces");
+ goto error_output;
+ }
+
+ ctx->output = (void *)output;
+ ctx->input = (void *)input;
+ ctx->trace_prefix = flb_sds_create(trace_prefix);
+
+ flb_start_trace(ctx->flb);
+
+ in->chunk_trace_ctxt = ctx;
+ pthread_mutex_unlock(&in->chunk_trace_lock);
+ return ctx;
+
+error_output:
+ flb_output_instance_destroy(output);
+error_input:
+ if (ctx->cio) {
+ cio_destroy(ctx->cio);
+ }
+ flb_input_instance_destroy(input);
+error_flb:
+ flb_destroy(ctx->flb);
+error_ctxt:
+ flb_free(ctx);
+ pthread_mutex_unlock(&in->chunk_trace_lock);
+ return NULL;
+}
+
+struct flb_chunk_trace *flb_chunk_trace_new(struct flb_input_chunk *chunk)
+{
+ struct flb_chunk_trace *trace = NULL;
+ struct flb_input_instance *f_ins = (struct flb_input_instance *)chunk->in;
+
+ pthread_mutex_lock(&f_ins->chunk_trace_lock);
+
+ if (flb_chunk_trace_to_be_destroyed(f_ins->chunk_trace_ctxt) == FLB_TRUE) {
+ pthread_mutex_unlock(&f_ins->chunk_trace_lock);
+ return NULL;
+ }
+
+ trace = flb_calloc(1, sizeof(struct flb_chunk_trace));
+ if (trace == NULL) {
+ flb_errno();
+ pthread_mutex_unlock(&f_ins->chunk_trace_lock);
+ return NULL;
+ }
+
+ trace->ctxt = f_ins->chunk_trace_ctxt;
+ flb_chunk_trace_add(trace->ctxt);
+
+ trace->trace_id = flb_sds_create("");
+ if (flb_sds_printf(&trace->trace_id, "%s%d", trace->ctxt->trace_prefix,
+ trace->ctxt->trace_count++) == NULL) {
+ pthread_mutex_unlock(&f_ins->chunk_trace_lock);
+ flb_sds_destroy(trace->trace_id);
+ flb_free(trace);
+ return NULL;
+ }
+
+ trace->ic = chunk;
+
+ pthread_mutex_unlock(&f_ins->chunk_trace_lock);
+ return trace;
+}
+
+void flb_chunk_trace_destroy(struct flb_chunk_trace *trace)
+{
+ pthread_mutex_lock(&trace->ic->in->chunk_trace_lock);
+ flb_chunk_trace_sub(trace->ctxt);
+
+ /* check to see if we need to free the trace context. */
+ if (flb_chunk_trace_has_chunks(trace->ctxt) == FLB_FALSE &&
+ flb_chunk_trace_to_be_destroyed(trace->ctxt) == FLB_TRUE) {
+ trace_chunk_context_destroy(trace->ctxt);
+ }
+ else if (flb_chunk_trace_has_chunks(trace->ctxt) == FLB_TRUE &&
+ flb_chunk_trace_to_be_destroyed(trace->ctxt) == FLB_TRUE) {
+ }
+ pthread_mutex_unlock(&trace->ic->in->chunk_trace_lock);
+
+ flb_sds_destroy(trace->trace_id);
+ flb_free(trace);
+}
+
+int flb_chunk_trace_context_set_limit(void *input, int limit_type, int limit_arg)
+{
+ struct flb_input_instance *in = (struct flb_input_instance *)input;
+ struct flb_chunk_trace_context *ctxt = NULL;
+ struct flb_time tm;
+
+ pthread_mutex_lock(&in->chunk_trace_lock);
+
+ ctxt = in->chunk_trace_ctxt;
+ if (ctxt == NULL) {
+ pthread_mutex_unlock(&in->chunk_trace_lock);
+ return -1;
+ }
+
+ switch(limit_type) {
+ case FLB_CHUNK_TRACE_LIMIT_TIME:
+ flb_time_get(&tm);
+ ctxt->limit.type = FLB_CHUNK_TRACE_LIMIT_TIME;
+ ctxt->limit.seconds_started = tm.tm.tv_sec;
+ ctxt->limit.seconds = limit_arg;
+
+ pthread_mutex_unlock(&in->chunk_trace_lock);
+ return 0;
+ case FLB_CHUNK_TRACE_LIMIT_COUNT:
+ ctxt->limit.type = FLB_CHUNK_TRACE_LIMIT_COUNT;
+ ctxt->limit.count = limit_arg;
+
+ pthread_mutex_unlock(&in->chunk_trace_lock);
+ return 0;
+ }
+
+ pthread_mutex_unlock(&in->chunk_trace_lock);
+ return -1;
+}
+
+int flb_chunk_trace_context_hit_limit(void *input)
+{
+ struct flb_input_instance *in = (struct flb_input_instance *)input;
+ struct flb_time tm;
+ struct flb_chunk_trace_context *ctxt = NULL;
+
+ pthread_mutex_lock(&in->chunk_trace_lock);
+
+ ctxt = in->chunk_trace_ctxt;
+ if (ctxt == NULL) {
+ pthread_mutex_unlock(&in->chunk_trace_lock);
+ return FLB_FALSE;
+ }
+
+ switch(ctxt->limit.type) {
+ case FLB_CHUNK_TRACE_LIMIT_TIME:
+ flb_time_get(&tm);
+ if ((tm.tm.tv_sec - ctxt->limit.seconds_started) > ctxt->limit.seconds) {
+ pthread_mutex_unlock(&in->chunk_trace_lock);
+ return FLB_TRUE;
+ }
+ return FLB_FALSE;
+ case FLB_CHUNK_TRACE_LIMIT_COUNT:
+ if (ctxt->limit.count <= ctxt->trace_count) {
+ pthread_mutex_unlock(&in->chunk_trace_lock);
+ return FLB_TRUE;
+ }
+ pthread_mutex_unlock(&in->chunk_trace_lock);
+ return FLB_FALSE;
+ }
+ pthread_mutex_unlock(&in->chunk_trace_lock);
+ return FLB_FALSE;
+}
+
+void flb_chunk_trace_do_input(struct flb_input_chunk *ic)
+{
+ pthread_mutex_lock(&ic->in->chunk_trace_lock);
+ if (ic->in->chunk_trace_ctxt == NULL) {
+ pthread_mutex_unlock(&ic->in->chunk_trace_lock);
+ return;
+ }
+ pthread_mutex_unlock(&ic->in->chunk_trace_lock);
+
+ if (ic->trace == NULL) {
+ ic->trace = flb_chunk_trace_new(ic);
+ }
+
+ if (ic->trace) {
+ flb_chunk_trace_input(ic->trace);
+ if (flb_chunk_trace_context_hit_limit(ic->in) == FLB_TRUE) {
+ flb_chunk_trace_context_destroy(ic->in);
+ }
+ }
+}
+
+int flb_chunk_trace_input(struct flb_chunk_trace *trace)
+{
+ msgpack_packer mp_pck;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_unpacked result;
+ msgpack_object *record = NULL;
+ char *buf = NULL;
+ size_t buf_size;
+ struct flb_time tm;
+ struct flb_time tm_end;
+ struct flb_input_instance *input = (struct flb_input_instance *)trace->ic->in;
+ int rc = -1;
+ size_t off = 0;
+ flb_sds_t tag = flb_sds_create("trace");
+ int records = 0;
+
+
+ /* initiailize start time */
+ flb_time_get(&tm);
+ flb_time_get(&tm_end);
+
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+ msgpack_unpacked_init(&result);
+
+ cio_chunk_get_content(trace->ic->chunk, &buf, &buf_size);
+
+ msgpack_pack_array(&mp_pck, 2);
+ flb_pack_time_now(&mp_pck);
+ if (input->alias != NULL) {
+ msgpack_pack_map(&mp_pck, 7);
+ }
+ else {
+ msgpack_pack_map(&mp_pck, 6);
+ }
+
+ msgpack_pack_str_with_body(&mp_pck, "type", 4);
+ msgpack_pack_int(&mp_pck, FLB_CHUNK_TRACE_TYPE_INPUT);
+
+ msgpack_pack_str_with_body(&mp_pck, "trace_id", strlen("trace_id"));
+ msgpack_pack_str_with_body(&mp_pck, trace->trace_id, strlen(trace->trace_id));
+
+ msgpack_pack_str_with_body(&mp_pck, "plugin_instance", strlen("plugin_instance"));
+ msgpack_pack_str_with_body(&mp_pck, input->name, strlen(input->name));
+
+ if (input->alias != NULL) {
+ msgpack_pack_str_with_body(&mp_pck, "plugin_alias", strlen("plugin_alias"));
+ msgpack_pack_str_with_body(&mp_pck, input->alias, strlen(input->alias));
+ }
+
+ msgpack_pack_str_with_body(&mp_pck, "records", strlen("records"));
+
+ if (buf_size > 0) {
+ do {
+ rc = msgpack_unpack_next(&result, buf, buf_size, &off);
+ if (rc != MSGPACK_UNPACK_SUCCESS) {
+ flb_error("unable to unpack record");
+ goto sbuffer_error;
+ }
+ records++;
+ } while (rc == MSGPACK_UNPACK_SUCCESS && off < buf_size);
+
+ msgpack_pack_array(&mp_pck, records);
+
+ off = 0;
+ do {
+ rc = msgpack_unpack_next(&result, buf, buf_size, &off);
+ if (rc != MSGPACK_UNPACK_SUCCESS) {
+ flb_error("unable to unpack record");
+ goto sbuffer_error;
+ }
+ flb_time_pop_from_msgpack(&tm, &result, &record);
+
+ msgpack_pack_map(&mp_pck, 2);
+ msgpack_pack_str_with_body(&mp_pck, "timestamp", strlen("timestamp"));
+ flb_time_append_to_msgpack(&tm, &mp_pck, FLB_TIME_ETFMT_INT);
+ msgpack_pack_str_with_body(&mp_pck, "record", strlen("record"));
+ msgpack_pack_object(&mp_pck, *record);
+
+ } while (rc == MSGPACK_UNPACK_SUCCESS && off < buf_size);
+ }
+
+ msgpack_pack_str_with_body(&mp_pck, "start_time", strlen("start_time"));
+ flb_time_append_to_msgpack(&tm, &mp_pck, FLB_TIME_ETFMT_INT);
+ msgpack_pack_str_with_body(&mp_pck, "end_time", strlen("end_time"));
+ flb_time_append_to_msgpack(&tm_end, &mp_pck, FLB_TIME_ETFMT_INT);
+ in_emitter_add_record(tag, flb_sds_len(tag), mp_sbuf.data, mp_sbuf.size,
+ trace->ctxt->input);
+sbuffer_error:
+ flb_sds_destroy(tag);
+ msgpack_unpacked_destroy(&result);
+ msgpack_sbuffer_destroy(&mp_sbuf);
+ return rc;
+}
+
+int flb_chunk_trace_pre_output(struct flb_chunk_trace *trace)
+{
+ msgpack_packer mp_pck;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_unpacked result;
+ msgpack_object *record = NULL;
+ char *buf = NULL;
+ size_t buf_size;
+ struct flb_time tm;
+ struct flb_time tm_end;
+ struct flb_input_instance *input = (struct flb_input_instance *)trace->ic->in;
+ int rc = -1;
+ size_t off = 0;
+ flb_sds_t tag = flb_sds_create("trace");
+ int records = 0;
+
+
+ /* initiailize start time */
+ flb_time_get(&tm);
+ flb_time_get(&tm_end);
+
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+ msgpack_unpacked_init(&result);
+
+ cio_chunk_get_content(trace->ic->chunk, &buf, &buf_size);
+
+ msgpack_pack_array(&mp_pck, 2);
+ flb_pack_time_now(&mp_pck);
+ if (input->alias != NULL) {
+ msgpack_pack_map(&mp_pck, 7);
+ }
+ else {
+ msgpack_pack_map(&mp_pck, 6);
+ }
+
+ msgpack_pack_str_with_body(&mp_pck, "type", 4);
+ msgpack_pack_int(&mp_pck, FLB_CHUNK_TRACE_TYPE_PRE_OUTPUT);
+
+ msgpack_pack_str_with_body(&mp_pck, "trace_id", strlen("trace_id"));
+ msgpack_pack_str_with_body(&mp_pck, trace->trace_id, strlen(trace->trace_id));
+
+ msgpack_pack_str_with_body(&mp_pck, "plugin_instance", strlen("plugin_instance"));
+ msgpack_pack_str_with_body(&mp_pck, input->name, strlen(input->name));
+
+ if (input->alias != NULL) {
+ msgpack_pack_str_with_body(&mp_pck, "plugin_alias", strlen("plugin_alias"));
+ msgpack_pack_str_with_body(&mp_pck, input->alias, strlen(input->alias));
+ }
+
+ msgpack_pack_str_with_body(&mp_pck, "records", strlen("records"));
+
+ if (buf_size > 0) {
+ do {
+ rc = msgpack_unpack_next(&result, buf, buf_size, &off);
+ if (rc != MSGPACK_UNPACK_SUCCESS) {
+ flb_error("unable to unpack record");
+ goto sbuffer_error;
+ }
+ records++;
+ } while (rc == MSGPACK_UNPACK_SUCCESS && off < buf_size);
+
+ msgpack_pack_array(&mp_pck, records);
+ off = 0;
+ do {
+ rc = msgpack_unpack_next(&result, buf, buf_size, &off);
+ if (rc != MSGPACK_UNPACK_SUCCESS) {
+ flb_error("unable to unpack record");
+ goto sbuffer_error;
+ }
+ flb_time_pop_from_msgpack(&tm, &result, &record);
+
+ msgpack_pack_map(&mp_pck, 2);
+ msgpack_pack_str_with_body(&mp_pck, "timestamp", strlen("timestamp"));
+ flb_time_append_to_msgpack(&tm, &mp_pck, FLB_TIME_ETFMT_INT);
+ msgpack_pack_str_with_body(&mp_pck, "record", strlen("record"));
+ msgpack_pack_object(&mp_pck, *record);
+
+ } while (rc == MSGPACK_UNPACK_SUCCESS && off < buf_size);
+ }
+
+ msgpack_pack_str_with_body(&mp_pck, "start_time", strlen("start_time"));
+ flb_time_append_to_msgpack(&tm, &mp_pck, FLB_TIME_ETFMT_INT);
+ msgpack_pack_str_with_body(&mp_pck, "end_time", strlen("end_time"));
+ flb_time_append_to_msgpack(&tm_end, &mp_pck, FLB_TIME_ETFMT_INT);
+ in_emitter_add_record(tag, flb_sds_len(tag), mp_sbuf.data, mp_sbuf.size,
+ trace->ctxt->input);
+sbuffer_error:
+ flb_sds_destroy(tag);
+ msgpack_unpacked_destroy(&result);
+ msgpack_sbuffer_destroy(&mp_sbuf);
+ return rc;
+}
+
+int flb_chunk_trace_filter(struct flb_chunk_trace *tracer, void *pfilter, struct flb_time *tm_start, struct flb_time *tm_end, char *buf, size_t buf_size)
+{
+ msgpack_packer mp_pck;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_unpacked result;
+ msgpack_object *record = NULL;
+ int rc = -1;
+ struct flb_filter_instance *filter = (struct flb_filter_instance *)pfilter;
+ flb_sds_t tag = flb_sds_create("trace");
+ struct flb_time tm;
+ size_t off = 0;
+ int records = 0;
+
+
+ if (tracer == NULL) {
+ goto tracer_error;
+ }
+
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ msgpack_pack_array(&mp_pck, 2);
+ flb_pack_time_now(&mp_pck);
+ if (filter->alias == NULL) {
+ msgpack_pack_map(&mp_pck, 6);
+ }
+ else {
+ msgpack_pack_map(&mp_pck, 7);
+ }
+
+ msgpack_pack_str_with_body(&mp_pck, "type", strlen("type"));
+ rc = msgpack_pack_int(&mp_pck, FLB_CHUNK_TRACE_TYPE_FILTER);
+ if (rc == -1) {
+ goto sbuffer_error;
+ }
+
+ msgpack_pack_str_with_body(&mp_pck, "start_time", strlen("start_time"));
+ //msgpack_pack_double(&mp_pck, flb_time_to_double(tm_start));
+ flb_time_append_to_msgpack(tm_start, &mp_pck, FLB_TIME_ETFMT_INT);
+ msgpack_pack_str_with_body(&mp_pck, "end_time", strlen("end_time"));
+ //msgpack_pack_double(&mp_pck, flb_time_to_double(tm_end));
+ flb_time_append_to_msgpack(tm_end, &mp_pck, FLB_TIME_ETFMT_INT);
+
+ msgpack_pack_str_with_body(&mp_pck, "trace_id", strlen("trace_id"));
+ msgpack_pack_str_with_body(&mp_pck, tracer->trace_id, strlen(tracer->trace_id));
+
+
+ msgpack_pack_str_with_body(&mp_pck, "plugin_instance", strlen("plugin_instance"));
+ rc = msgpack_pack_str_with_body(&mp_pck, filter->name, strlen(filter->name));
+ if (rc == -1) {
+ goto sbuffer_error;
+ }
+
+ if (filter->alias != NULL) {
+ msgpack_pack_str_with_body(&mp_pck, "plugin_alias", strlen("plugin_alias"));
+ msgpack_pack_str_with_body(&mp_pck, filter->alias, strlen(filter->alias));
+ }
+
+ msgpack_pack_str_with_body(&mp_pck, "records", strlen("records"));
+
+ msgpack_unpacked_init(&result);
+
+ if (buf_size > 0) {
+ do {
+ rc = msgpack_unpack_next(&result, buf, buf_size, &off);
+ if (rc != MSGPACK_UNPACK_SUCCESS) {
+ flb_error("unable to unpack record");
+ goto unpack_error;
+ }
+ records++;
+ } while (rc == MSGPACK_UNPACK_SUCCESS && off < buf_size);
+
+ msgpack_pack_array(&mp_pck, records);
+ off = 0;
+ do {
+ rc = msgpack_unpack_next(&result, buf, buf_size, &off);
+ if (rc != MSGPACK_UNPACK_SUCCESS) {
+ flb_error("unable to unpack record");
+ goto unpack_error;
+ }
+ flb_time_pop_from_msgpack(&tm, &result, &record);
+
+ msgpack_pack_map(&mp_pck, 2);
+ msgpack_pack_str_with_body(&mp_pck, "timestamp", strlen("timestamp"));
+ flb_time_append_to_msgpack(&tm, &mp_pck, FLB_TIME_ETFMT_INT);
+ msgpack_pack_str_with_body(&mp_pck, "record", strlen("record"));
+ msgpack_pack_object(&mp_pck, *record);
+
+ } while (rc == MSGPACK_UNPACK_SUCCESS && off < buf_size);
+ }
+
+ in_emitter_add_record(tag, flb_sds_len(tag), mp_sbuf.data, mp_sbuf.size,
+ tracer->ctxt->input);
+
+ rc = 0;
+
+unpack_error:
+ msgpack_unpacked_destroy(&result);
+sbuffer_error:
+ msgpack_sbuffer_destroy(&mp_sbuf);
+tracer_error:
+ flb_sds_destroy(tag);
+ return rc;
+}
diff --git a/fluent-bit/src/flb_compression.c b/fluent-bit/src/flb_compression.c
new file mode 100644
index 000000000..86c94c90b
--- /dev/null
+++ b/fluent-bit/src/flb_compression.c
@@ -0,0 +1,221 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_gzip.h>
+#include <fluent-bit/flb_compression.h>
+
+static size_t flb_decompression_context_get_read_buffer_offset(
+ struct flb_decompression_context *context)
+{
+ uintptr_t input_buffer_offset;
+
+ if (context == NULL) {
+ return 0;
+ }
+
+ input_buffer_offset = (uintptr_t) context->read_buffer;
+ input_buffer_offset -= (uintptr_t) context->input_buffer;
+
+ return input_buffer_offset;
+}
+
+static void flb_decompression_context_adjust_buffer(
+ struct flb_decompression_context *context)
+{
+ uintptr_t input_buffer_offset;
+
+ if (context != NULL) {
+ input_buffer_offset = \
+ flb_decompression_context_get_read_buffer_offset(context);
+
+ if (input_buffer_offset >= (context->input_buffer_size / 2)) {
+ memmove(context->input_buffer,
+ context->read_buffer,
+ context->input_buffer_length);
+
+ context->read_buffer = context->input_buffer;
+ }
+ }
+}
+
+uint8_t *flb_decompression_context_get_append_buffer(
+ struct flb_decompression_context *context)
+{
+ if (context != NULL) {
+ flb_decompression_context_adjust_buffer(context);
+
+ return &context->read_buffer[context->input_buffer_length];
+ }
+
+ return NULL;
+}
+
+size_t flb_decompression_context_get_available_space(
+ struct flb_decompression_context *context)
+{
+ uintptr_t available_buffer_space;
+ uintptr_t input_buffer_offset;
+
+ if (context == NULL) {
+ return 0;
+ }
+
+ flb_decompression_context_adjust_buffer(context);
+
+ input_buffer_offset = \
+ flb_decompression_context_get_read_buffer_offset(context);
+
+ available_buffer_space = context->input_buffer_size;
+ available_buffer_space -= input_buffer_offset;
+ available_buffer_space -= context->input_buffer_length;
+
+ return available_buffer_space;
+}
+
+int flb_decompression_context_resize_buffer(
+ struct flb_decompression_context *context, size_t new_size)
+{
+ void *new_buffer_address;
+
+ if (new_size > context->input_buffer_length) {
+ new_buffer_address = flb_realloc(context->input_buffer,
+ new_size);
+
+ if (new_buffer_address == NULL) {
+ return FLB_DECOMPRESSOR_FAILURE;
+ }
+
+ if (new_buffer_address != context->input_buffer) {
+ context->read_buffer = (uint8_t *) \
+ (((uintptr_t) context->read_buffer -
+ (uintptr_t) context->input_buffer) +
+ (uintptr_t) new_buffer_address);
+ context->input_buffer = (uint8_t *) new_buffer_address;
+ context->input_buffer_size = new_size;
+ }
+ }
+ else if (new_size < context->input_buffer_length) {
+ return FLB_DECOMPRESSOR_FAILURE;
+ }
+
+ return FLB_DECOMPRESSOR_SUCCESS;
+}
+
+
+void flb_decompression_context_destroy(struct flb_decompression_context *context)
+{
+ if (context != NULL) {
+ if (context->input_buffer != NULL) {
+ flb_free(context->input_buffer);
+
+ context->input_buffer = NULL;
+ }
+
+ if (context->inner_context != NULL) {
+ flb_gzip_decompression_context_destroy(context->inner_context);
+
+ context->inner_context = NULL;
+ }
+
+ context->read_buffer = NULL;
+
+ flb_free(context);
+ }
+}
+
+struct flb_decompression_context *flb_decompression_context_create(int algorithm,
+ size_t input_buffer_size)
+{
+ struct flb_decompression_context *context;
+
+ if (input_buffer_size == 0) {
+ input_buffer_size = FLB_DECOMPRESSION_BUFFER_SIZE;
+ }
+
+ context =
+ flb_calloc(1, sizeof(struct flb_decompression_context));
+
+ if (context == NULL) {
+ flb_errno();
+
+ flb_error("error allocating decompression context");
+
+ return NULL;
+ }
+
+ context->input_buffer =
+ flb_calloc(input_buffer_size, sizeof(uint8_t));
+
+ if (context->input_buffer == NULL) {
+ flb_errno();
+
+ flb_error("error allocating decompression buffer");
+
+ flb_decompression_context_destroy(context);
+
+ return NULL;
+ }
+
+ if (algorithm == FLB_COMPRESSION_ALGORITHM_GZIP) {
+ context->inner_context = flb_gzip_decompression_context_create();
+ }
+ else {
+ flb_error("invalid compression algorithm : %d", algorithm);
+
+ flb_decompression_context_destroy(context);
+
+ return NULL;
+ }
+
+ if (context->inner_context == NULL) {
+ flb_errno();
+
+ flb_error("error allocating internal decompression context");
+
+ flb_decompression_context_destroy(context);
+
+ return NULL;
+ }
+
+ context->input_buffer_size = input_buffer_size;
+ context->read_buffer = context->read_buffer;
+ context->algorithm = algorithm;
+ context->state = FLB_DECOMPRESSOR_STATE_EXPECTING_HEADER;
+
+ return context;
+}
+
+int flb_decompress(struct flb_decompression_context *context,
+ void *output_buffer,
+ size_t *output_length)
+{
+ if (context != NULL) {
+ if (context->algorithm == FLB_COMPRESSION_ALGORITHM_GZIP) {
+ return flb_gzip_decompressor_dispatch(context,
+ output_buffer,
+ output_length);
+
+ }
+ }
+
+ return FLB_DECOMPRESSOR_FAILURE;
+}
diff --git a/fluent-bit/src/flb_config.c b/fluent-bit/src/flb_config.c
new file mode 100644
index 000000000..882a93c7c
--- /dev/null
+++ b/fluent-bit/src/flb_config.c
@@ -0,0 +1,942 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <stddef.h>
+
+#include <monkey/mk_core.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_env.h>
+#include <fluent-bit/flb_meta.h>
+#include <fluent-bit/flb_macros.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_parser.h>
+#include <fluent-bit/flb_plugin.h>
+#include <fluent-bit/flb_plugins.h>
+#include <fluent-bit/flb_slist.h>
+#include <fluent-bit/flb_kernel.h>
+#include <fluent-bit/flb_worker.h>
+#include <fluent-bit/flb_scheduler.h>
+#include <fluent-bit/flb_http_server.h>
+#include <fluent-bit/flb_plugin.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_config_format.h>
+#include <fluent-bit/multiline/flb_ml.h>
+#include <fluent-bit/flb_bucket_queue.h>
+
+const char *FLB_CONF_ENV_LOGLEVEL = "FLB_LOG_LEVEL";
+
+int flb_regex_init();
+
+struct flb_service_config service_configs[] = {
+ {FLB_CONF_STR_FLUSH,
+ FLB_CONF_TYPE_DOUBLE,
+ offsetof(struct flb_config, flush)},
+
+ {FLB_CONF_STR_GRACE,
+ FLB_CONF_TYPE_INT,
+ offsetof(struct flb_config, grace)},
+
+ {FLB_CONF_STR_CONV_NAN,
+ FLB_CONF_TYPE_BOOL,
+ offsetof(struct flb_config, convert_nan_to_null)},
+
+ {FLB_CONF_STR_DAEMON,
+ FLB_CONF_TYPE_BOOL,
+ offsetof(struct flb_config, daemon)},
+
+ {FLB_CONF_STR_LOGFILE,
+ FLB_CONF_TYPE_STR,
+ offsetof(struct flb_config, log_file)},
+
+ {FLB_CONF_STR_PARSERS_FILE,
+ FLB_CONF_TYPE_STR,
+ offsetof(struct flb_config, parsers_file)},
+
+ {FLB_CONF_STR_PLUGINS_FILE,
+ FLB_CONF_TYPE_STR,
+ offsetof(struct flb_config, plugins_file)},
+
+ {FLB_CONF_STR_LOGLEVEL,
+ FLB_CONF_TYPE_STR,
+ offsetof(struct flb_config, log)},
+
+#ifdef FLB_HAVE_HTTP_SERVER
+ {FLB_CONF_STR_HTTP_SERVER,
+ FLB_CONF_TYPE_BOOL,
+ offsetof(struct flb_config, http_server)},
+
+ {FLB_CONF_STR_HTTP_LISTEN,
+ FLB_CONF_TYPE_STR,
+ offsetof(struct flb_config, http_listen)},
+
+ {FLB_CONF_STR_HTTP_PORT,
+ FLB_CONF_TYPE_STR,
+ offsetof(struct flb_config, http_port)},
+
+ {FLB_CONF_STR_HEALTH_CHECK,
+ FLB_CONF_TYPE_BOOL,
+ offsetof(struct flb_config, health_check)},
+
+ {FLB_CONF_STR_HC_ERRORS_COUNT,
+ FLB_CONF_TYPE_INT,
+ offsetof(struct flb_config, hc_errors_count)},
+
+ {FLB_CONF_STR_HC_RETRIES_FAILURE_COUNT,
+ FLB_CONF_TYPE_INT,
+ offsetof(struct flb_config, hc_retry_failure_count)},
+
+ {FLB_CONF_STR_HC_PERIOD,
+ FLB_CONF_TYPE_INT,
+ offsetof(struct flb_config, health_check_period)},
+
+#endif
+ /* DNS*/
+ {FLB_CONF_DNS_MODE,
+ FLB_CONF_TYPE_STR,
+ offsetof(struct flb_config, dns_mode)},
+
+ {FLB_CONF_DNS_RESOLVER,
+ FLB_CONF_TYPE_STR,
+ offsetof(struct flb_config, dns_resolver)},
+
+ {FLB_CONF_DNS_PREFER_IPV4,
+ FLB_CONF_TYPE_BOOL,
+ offsetof(struct flb_config, dns_prefer_ipv4)},
+
+ /* Storage */
+ {FLB_CONF_STORAGE_PATH,
+ FLB_CONF_TYPE_STR,
+ offsetof(struct flb_config, storage_path)},
+ {FLB_CONF_STORAGE_SYNC,
+ FLB_CONF_TYPE_STR,
+ offsetof(struct flb_config, storage_sync)},
+ {FLB_CONF_STORAGE_METRICS,
+ FLB_CONF_TYPE_BOOL,
+ offsetof(struct flb_config, storage_metrics)},
+ {FLB_CONF_STORAGE_CHECKSUM,
+ FLB_CONF_TYPE_BOOL,
+ offsetof(struct flb_config, storage_checksum)},
+ {FLB_CONF_STORAGE_BL_MEM_LIMIT,
+ FLB_CONF_TYPE_STR,
+ offsetof(struct flb_config, storage_bl_mem_limit)},
+ {FLB_CONF_STORAGE_MAX_CHUNKS_UP,
+ FLB_CONF_TYPE_INT,
+ offsetof(struct flb_config, storage_max_chunks_up)},
+ {FLB_CONF_STORAGE_DELETE_IRRECOVERABLE_CHUNKS,
+ FLB_CONF_TYPE_BOOL,
+ offsetof(struct flb_config, storage_del_bad_chunks)},
+ {FLB_CONF_STORAGE_TRIM_FILES,
+ FLB_CONF_TYPE_BOOL,
+ offsetof(struct flb_config, storage_trim_files)},
+
+ /* Coroutines */
+ {FLB_CONF_STR_CORO_STACK_SIZE,
+ FLB_CONF_TYPE_INT,
+ offsetof(struct flb_config, coro_stack_size)},
+
+ /* Scheduler */
+ {FLB_CONF_STR_SCHED_CAP,
+ FLB_CONF_TYPE_INT,
+ offsetof(struct flb_config, sched_cap)},
+ {FLB_CONF_STR_SCHED_BASE,
+ FLB_CONF_TYPE_INT,
+ offsetof(struct flb_config, sched_base)},
+
+#ifdef FLB_HAVE_STREAM_PROCESSOR
+ {FLB_CONF_STR_STREAMS_FILE,
+ FLB_CONF_TYPE_STR,
+ offsetof(struct flb_config, stream_processor_file)},
+ {FLB_CONF_STR_STREAMS_STR_CONV,
+ FLB_CONF_TYPE_BOOL,
+ offsetof(struct flb_config, stream_processor_str_conv)},
+#endif
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+ {FLB_CONF_STR_ENABLE_CHUNK_TRACE,
+ FLB_CONF_TYPE_BOOL,
+ offsetof(struct flb_config, enable_chunk_trace)},
+#endif
+
+ {FLB_CONF_STR_HOT_RELOAD,
+ FLB_CONF_TYPE_BOOL,
+ offsetof(struct flb_config, enable_hot_reload)},
+
+ {FLB_CONF_STR_HOT_RELOAD_ENSURE_THREAD_SAFETY,
+ FLB_CONF_TYPE_BOOL,
+ offsetof(struct flb_config, ensure_thread_safety_on_hot_reloading)},
+
+ {NULL, FLB_CONF_TYPE_OTHER, 0} /* end of array */
+};
+
+
+struct flb_config *flb_config_init()
+{
+ int ret;
+ struct flb_config *config;
+ struct flb_cf *cf;
+ struct flb_cf_section *section;
+
+ config = flb_calloc(1, sizeof(struct flb_config));
+ if (!config) {
+ flb_errno();
+ return NULL;
+ }
+
+ MK_EVENT_ZERO(&config->ch_event);
+ MK_EVENT_ZERO(&config->event_flush);
+ MK_EVENT_ZERO(&config->event_shutdown);
+
+ /* is data ingestion active ? */
+ config->is_ingestion_active = FLB_TRUE;
+
+ /* Is the engine (event loop) actively running ? */
+ config->is_running = FLB_TRUE;
+
+ /* Initialize config_format context */
+ cf = flb_cf_create();
+ if (!cf) {
+ flb_free(config);
+ return NULL;
+ }
+ config->cf_main = cf;
+
+ section = flb_cf_section_create(cf, "service", 0);
+ if (!section) {
+ flb_cf_destroy(cf);
+ flb_free(config);
+ return NULL;
+ }
+
+ /* Flush */
+ config->flush = FLB_CONFIG_FLUSH_SECS;
+ config->daemon = FLB_FALSE;
+ config->init_time = time(NULL);
+ config->kernel = flb_kernel_info();
+ config->verbose = 3;
+ config->grace = 5;
+ config->grace_count = 0;
+ config->exit_status_code = 0;
+
+ /* json */
+ config->convert_nan_to_null = FLB_FALSE;
+
+#ifdef FLB_HAVE_HTTP_SERVER
+ config->http_ctx = NULL;
+ config->http_server = FLB_FALSE;
+ config->http_listen = flb_strdup(FLB_CONFIG_HTTP_LISTEN);
+ config->http_port = flb_strdup(FLB_CONFIG_HTTP_PORT);
+ config->health_check = FLB_FALSE;
+ config->hc_errors_count = HC_ERRORS_COUNT_DEFAULT;
+ config->hc_retry_failure_count = HC_RETRY_FAILURE_COUNTS_DEFAULT;
+ config->health_check_period = HEALTH_CHECK_PERIOD;
+#endif
+
+ config->http_proxy = getenv("HTTP_PROXY");
+ if (flb_str_emptyval(config->http_proxy) == FLB_TRUE) {
+ config->http_proxy = getenv("http_proxy");
+ if (flb_str_emptyval(config->http_proxy) == FLB_TRUE) {
+ /* Proxy should not be set when `HTTP_PROXY` or `http_proxy` are set to "" */
+ config->http_proxy = NULL;
+ }
+ }
+ config->no_proxy = getenv("NO_PROXY");
+ if (flb_str_emptyval(config->no_proxy) == FLB_TRUE || config->http_proxy == NULL) {
+ config->no_proxy = getenv("no_proxy");
+ if (flb_str_emptyval(config->no_proxy) == FLB_TRUE || config->http_proxy == NULL) {
+ /* NoProxy should not be set when `NO_PROXY` or `no_proxy` are set to "" or there is no Proxy. */
+ config->no_proxy = NULL;
+ }
+ }
+
+ config->cio = NULL;
+ config->storage_path = NULL;
+ config->storage_input_plugin = NULL;
+ config->storage_metrics = FLB_TRUE;
+
+ config->sched_cap = FLB_SCHED_CAP;
+ config->sched_base = FLB_SCHED_BASE;
+
+ /* reload */
+ config->ensure_thread_safety_on_hot_reloading = FLB_TRUE;
+ config->hot_reloaded_count = 0;
+
+#ifdef FLB_HAVE_SQLDB
+ mk_list_init(&config->sqldb_list);
+#endif
+
+#ifdef FLB_HAVE_LUAJIT
+ mk_list_init(&config->luajit_list);
+#endif
+
+#ifdef FLB_HAVE_STREAM_PROCESSOR
+ flb_slist_create(&config->stream_processor_tasks);
+ config->stream_processor_str_conv = FLB_TRUE;
+#endif
+
+ flb_slist_create(&config->external_plugins);
+
+ /* Set default coroutines stack size */
+ config->coro_stack_size = FLB_CORO_STACK_SIZE_BYTE;
+ if (config->coro_stack_size < getpagesize()) {
+ flb_info("[config] changing coro_stack_size from %u to %u bytes",
+ config->coro_stack_size, getpagesize());
+ config->coro_stack_size = (unsigned int)getpagesize();
+ }
+
+ /* collectors */
+ pthread_mutex_init(&config->collectors_mutex, NULL);
+
+ /* Initialize linked lists */
+ mk_list_init(&config->processor_plugins);
+ mk_list_init(&config->custom_plugins);
+ mk_list_init(&config->in_plugins);
+ mk_list_init(&config->parser_plugins);
+ mk_list_init(&config->filter_plugins);
+ mk_list_init(&config->out_plugins);
+ mk_list_init(&config->customs);
+ mk_list_init(&config->inputs);
+ mk_list_init(&config->parsers);
+ mk_list_init(&config->filters);
+ mk_list_init(&config->outputs);
+ mk_list_init(&config->proxies);
+ mk_list_init(&config->workers);
+ mk_list_init(&config->upstreams);
+ mk_list_init(&config->downstreams);
+ mk_list_init(&config->cmetrics);
+ mk_list_init(&config->cf_parsers_list);
+
+ memset(&config->tasks_map, '\0', sizeof(config->tasks_map));
+
+ /* Initialize multiline-parser list. We need this here, because from now
+ * on we use flb_config_exit to cleanup the config, which requires
+ * the config->multiline_parsers list to be initialized. */
+ mk_list_init(&config->multiline_parsers);
+
+ /* Environment */
+ config->env = flb_env_create();
+ if (config->env == NULL) {
+ flb_error("[config] environment creation failed");
+ flb_config_exit(config);
+ return NULL;
+ }
+
+ /* Multiline core */
+ ret = flb_ml_init(config);
+ if (ret == -1) {
+ flb_error("[config] multiline core initialization failed");
+ flb_config_exit(config);
+ return NULL;
+ }
+
+ /* Register static plugins */
+ ret = flb_plugins_register(config);
+ if (ret == -1) {
+ flb_error("[config] plugins registration failed");
+ flb_config_exit(config);
+ return NULL;
+ }
+
+ /* Create environment for dynamic plugins */
+ config->dso_plugins = flb_plugin_create();
+
+ /* Ignoring SIGPIPE on Windows (scary) */
+#ifndef _WIN32
+ /* Ignore SIGPIPE */
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ /* Prepare worker interface */
+ flb_worker_init(config);
+
+#ifdef FLB_HAVE_REGEX
+ /* Regex support */
+ flb_regex_init();
+#endif
+
+ return config;
+}
+
+void flb_config_exit(struct flb_config *config)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_cf *cf;
+
+ if (config->log_file) {
+ flb_free(config->log_file);
+ }
+
+ if (config->log) {
+ flb_log_destroy(config->log, config);
+ }
+
+ if (config->parsers_file) {
+ flb_free(config->parsers_file);
+ }
+
+ if (config->plugins_file) {
+ flb_free(config->plugins_file);
+ }
+
+ if (config->kernel) {
+ flb_kernel_destroy(config->kernel);
+ }
+
+ /* release resources */
+ if (config->ch_event.fd) {
+ mk_event_closesocket(config->ch_event.fd);
+ }
+
+ /* Pipe */
+ if (config->ch_data[0]) {
+ mk_event_closesocket(config->ch_data[0]);
+ mk_event_closesocket(config->ch_data[1]);
+ }
+
+ /* Channel manager */
+ if (config->ch_manager[0] > 0) {
+ mk_event_closesocket(config->ch_manager[0]);
+ if (config->ch_manager[0] != config->ch_manager[1]) {
+ mk_event_closesocket(config->ch_manager[1]);
+ }
+ }
+
+ /* Channel notifications */
+ if (config->ch_notif[0] > 0) {
+ mk_event_closesocket(config->ch_notif[0]);
+ if (config->ch_notif[0] != config->ch_notif[1]) {
+ mk_event_closesocket(config->ch_notif[1]);
+ }
+ }
+
+ if (config->env) {
+ flb_env_destroy(config->env);
+ }
+
+ /* Program name */
+ if (config->program_name) {
+ flb_sds_destroy(config->program_name);
+ }
+
+ /* Conf path */
+ if (config->conf_path) {
+ flb_free(config->conf_path);
+ }
+
+ /* conf path file (file system config path) */
+ if (config->conf_path_file) {
+ flb_sds_destroy(config->conf_path_file);
+ }
+
+ /* Working directory */
+ if (config->workdir) {
+ flb_free(config->workdir);
+ }
+
+ /* Destroy any DSO context */
+ if (config->dso_plugins) {
+ flb_plugin_destroy(config->dso_plugins);
+ }
+
+ /* Workers */
+ flb_worker_exit(config);
+
+ /* Event flush */
+ if (config->evl) {
+ if (config->event_flush.status != MK_EVENT_NONE) {
+ mk_event_timeout_destroy(config->evl, &config->event_flush);
+ }
+ }
+
+ /* Release scheduler */
+ if (config->sched) {
+ flb_sched_destroy(config->sched);
+ }
+
+#ifdef FLB_HAVE_HTTP_SERVER
+ if (config->http_listen) {
+ flb_free(config->http_listen);
+ }
+
+ if (config->http_port) {
+ flb_free(config->http_port);
+ }
+#endif
+
+#ifdef FLB_HAVE_PARSER
+ /* parsers */
+ flb_parser_exit(config);
+#endif
+
+ if (config->dns_mode) {
+ flb_free(config->dns_mode);
+ }
+ if (config->dns_resolver) {
+ flb_free(config->dns_resolver);
+ }
+
+ if (config->storage_path) {
+ flb_free(config->storage_path);
+ }
+ if (config->storage_sync) {
+ flb_free(config->storage_sync);
+ }
+ if (config->storage_bl_mem_limit) {
+ flb_free(config->storage_bl_mem_limit);
+ }
+
+#ifdef FLB_HAVE_STREAM_PROCESSOR
+ if (config->stream_processor_file) {
+ flb_free(config->stream_processor_file);
+ }
+
+ flb_slist_destroy(&config->stream_processor_tasks);
+#endif
+
+ flb_slist_destroy(&config->external_plugins);
+
+ if (config->evl) {
+ mk_event_loop_destroy(config->evl);
+ }
+ if (config->evl_bktq) {
+ flb_bucket_queue_destroy(config->evl_bktq);
+ }
+
+ flb_plugins_unregister(config);
+
+ if (config->cf_main) {
+ flb_cf_destroy(config->cf_main);
+ }
+
+ /* cf_opts' lifetime should differ from config's lifetime.
+ * This member should be storing just for the cf_opts reference.
+ * Don't destroy it here.
+ */
+
+ /* remove parsers */
+ mk_list_foreach_safe(head, tmp, &config->cf_parsers_list) {
+ cf = mk_list_entry(head, struct flb_cf, _head);
+ mk_list_del(&cf->_head);
+ flb_cf_destroy(cf);
+ }
+
+ flb_free(config);
+}
+
+const char *flb_config_prop_get(const char *key, struct mk_list *list)
+{
+ return flb_kv_get_key_value(key, list);
+}
+
+static inline int prop_key_check(const char *key, const char *kv, int k_len)
+{
+ size_t len;
+
+ len = strnlen(key,256);
+ if (strncasecmp(key, kv, k_len) == 0 && len == k_len) {
+ return 0;
+ }
+ return -1;
+}
+
+static int set_log_level(struct flb_config *config, const char *v_str)
+{
+ if (v_str != NULL) {
+ if (strcasecmp(v_str, "error") == 0) {
+ config->verbose = 1;
+ }
+ else if (strcasecmp(v_str, "warn") == 0 ||
+ strcasecmp(v_str, "warning") == 0) {
+ config->verbose = 2;
+ }
+ else if (strcasecmp(v_str, "info") == 0) {
+ config->verbose = 3;
+ }
+ else if (strcasecmp(v_str, "debug") == 0) {
+ config->verbose = 4;
+ }
+ else if (strcasecmp(v_str, "trace") == 0) {
+ config->verbose = 5;
+ }
+ else if (strcasecmp(v_str, "off") == 0) {
+ config->verbose = FLB_LOG_OFF;
+ }
+ else {
+ return -1;
+ }
+ }
+ else if (config->log) {
+ config->verbose = 3;
+ }
+ return 0;
+}
+
+int set_log_level_from_env(struct flb_config *config)
+{
+ const char *val = NULL;
+ val = flb_env_get(config->env, FLB_CONF_ENV_LOGLEVEL);
+ if (val) {
+ return set_log_level(config, val);
+ }
+ return -1;
+}
+
+int flb_config_set_property(struct flb_config *config,
+ const char *k, const char *v)
+{
+ int i=0;
+ int ret = -1;
+ int *i_val;
+ double *d_val;
+ char **s_val;
+ size_t len = strnlen(k, 256);
+ char *key = service_configs[0].key;
+ flb_sds_t tmp = NULL;
+
+ while (key != NULL) {
+ if (prop_key_check(key, k,len) == 0) {
+ if (!strncasecmp(key, FLB_CONF_STR_LOGLEVEL, 256)) {
+ #ifndef FLB_HAVE_STATIC_CONF
+ if (set_log_level_from_env(config) < 0) {
+ #endif
+ tmp = flb_env_var_translate(config->env, v);
+ if (tmp) {
+ ret = set_log_level(config, tmp);
+ flb_sds_destroy(tmp);
+ tmp = NULL;
+ }
+ else {
+ ret = set_log_level(config, v);
+ }
+ #ifndef FLB_HAVE_STATIC_CONF
+ }
+ #endif
+ }
+ else if (!strncasecmp(key, FLB_CONF_STR_PARSERS_FILE, 32)) {
+#ifdef FLB_HAVE_PARSER
+ tmp = flb_env_var_translate(config->env, v);
+ ret = flb_parser_conf_file(tmp, config);
+ flb_sds_destroy(tmp);
+ tmp = NULL;
+#endif
+ }
+ else if (!strncasecmp(key, FLB_CONF_STR_PLUGINS_FILE, 32)) {
+ tmp = flb_env_var_translate(config->env, v);
+ ret = flb_plugin_load_config_file(tmp, config);
+ flb_sds_destroy(tmp);
+ tmp = NULL;
+ }
+ else {
+ ret = 0;
+ tmp = flb_env_var_translate(config->env, v);
+ switch(service_configs[i].type) {
+ case FLB_CONF_TYPE_INT:
+ i_val = (int*)((char*)config + service_configs[i].offset);
+ *i_val = atoi(tmp);
+ flb_sds_destroy(tmp);
+ break;
+ case FLB_CONF_TYPE_DOUBLE:
+ d_val = (double*)((char*)config + service_configs[i].offset);
+ *d_val = atof(tmp);
+ flb_sds_destroy(tmp);
+ break;
+ case FLB_CONF_TYPE_BOOL:
+ i_val = (int*)((char*)config+service_configs[i].offset);
+ *i_val = flb_utils_bool(tmp);
+ flb_sds_destroy(tmp);
+ break;
+ case FLB_CONF_TYPE_STR:
+ s_val = (char**)((char*)config+service_configs[i].offset);
+ if ( *s_val != NULL ) {
+ flb_free(*s_val); /* release before overwriting */
+ }
+
+ *s_val = flb_strdup(tmp);
+ flb_sds_destroy(tmp);
+ break;
+ default:
+ ret = -1;
+ }
+ }
+
+ if (ret < 0) {
+ if (tmp) {
+ flb_sds_destroy(tmp);
+ }
+ return -1;
+ }
+ return 0;
+ }
+ key = service_configs[++i].key;
+ }
+ return 0;
+}
+
+int flb_config_set_program_name(struct flb_config *config, char *name)
+{
+ config->program_name = flb_sds_create(name);
+
+ if (!config->program_name) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int configure_plugins_type(struct flb_config *config, struct flb_cf *cf, enum section_type type)
+{
+ int ret;
+ char *tmp;
+ char *name;
+ char *s_type;
+ struct mk_list *list;
+ struct mk_list *head;
+ struct cfl_list *h_prop;
+ struct cfl_kvpair *kv;
+ struct cfl_variant *val;
+ struct flb_cf_section *s;
+ struct flb_cf_group *processors = NULL;
+ int i;
+ void *ins;
+
+ if (type == FLB_CF_CUSTOM) {
+ s_type = "custom";
+ list = &cf->customs;
+ }
+ else if (type == FLB_CF_INPUT) {
+ s_type = "input";
+ list = &cf->inputs;
+ }
+ else if (type == FLB_CF_FILTER) {
+ s_type = "filter";
+ list = &cf->filters;
+ }
+ else if (type == FLB_CF_OUTPUT) {
+ s_type = "output";
+ list = &cf->outputs;
+ }
+ else {
+ return -1;
+ }
+
+ mk_list_foreach(head, list) {
+ s = mk_list_entry(head, struct flb_cf_section, _head_section);
+ name = flb_cf_section_property_get_string(cf, s, "name");
+ if (!name) {
+ flb_error("[config] section '%s' is missing the 'name' property",
+ s_type);
+ return -1;
+ }
+
+ /* translate the variable */
+ tmp = flb_env_var_translate(config->env, name);
+
+ /* create an instance of the plugin */
+ ins = NULL;
+ if (type == FLB_CF_CUSTOM) {
+ ins = flb_custom_new(config, tmp, NULL);
+ }
+ else if (type == FLB_CF_INPUT) {
+ ins = flb_input_new(config, tmp, NULL, FLB_TRUE);
+ }
+ else if (type == FLB_CF_FILTER) {
+ ins = flb_filter_new(config, tmp, NULL);
+ }
+ else if (type == FLB_CF_OUTPUT) {
+ ins = flb_output_new(config, tmp, NULL, FLB_TRUE);
+ }
+ flb_sds_destroy(tmp);
+
+ /* validate the instance creation */
+ if (!ins) {
+ flb_error("[config] section '%s' tried to instance a plugin name "
+ "that don't exists", name);
+ flb_sds_destroy(name);
+ return -1;
+ }
+ flb_sds_destroy(name);
+
+ /*
+ * iterate section properties and populate instance by using specific
+ * api function.
+ */
+ cfl_list_foreach(h_prop, &s->properties->list) {
+ kv = cfl_list_entry(h_prop, struct cfl_kvpair, _head);
+ if (strcasecmp(kv->key, "name") == 0) {
+ continue;
+ }
+
+ /* set ret to -1 to ensure that we treat any unhandled plugin or
+ * value types as errors.
+ */
+ ret = -1;
+
+ if (type == FLB_CF_CUSTOM) {
+ if (kv->val->type == CFL_VARIANT_STRING) {
+ ret = flb_custom_set_property(ins, kv->key, kv->val->data.as_string);
+ } else if (kv->val->type == CFL_VARIANT_ARRAY) {
+ for (i = 0; i < kv->val->data.as_array->entry_count; i++) {
+ val = kv->val->data.as_array->entries[i];
+ ret = flb_custom_set_property(ins, kv->key, val->data.as_string);
+ }
+ }
+ }
+ else if (type == FLB_CF_INPUT) {
+ if (kv->val->type == CFL_VARIANT_STRING) {
+ ret = flb_input_set_property(ins, kv->key, kv->val->data.as_string);
+ } else if (kv->val->type == CFL_VARIANT_ARRAY) {
+ for (i = 0; i < kv->val->data.as_array->entry_count; i++) {
+ val = kv->val->data.as_array->entries[i];
+ ret = flb_input_set_property(ins, kv->key, val->data.as_string);
+ }
+ }
+ }
+ else if (type == FLB_CF_FILTER) {
+ if (kv->val->type == CFL_VARIANT_STRING) {
+ ret = flb_filter_set_property(ins, kv->key, kv->val->data.as_string);
+ } else if (kv->val->type == CFL_VARIANT_ARRAY) {
+ for (i = 0; i < kv->val->data.as_array->entry_count; i++) {
+ val = kv->val->data.as_array->entries[i];
+ ret = flb_filter_set_property(ins, kv->key, val->data.as_string);
+ }
+ }
+ }
+ else if (type == FLB_CF_OUTPUT) {
+ if (kv->val->type == CFL_VARIANT_STRING) {
+ ret = flb_output_set_property(ins, kv->key, kv->val->data.as_string);
+ } else if (kv->val->type == CFL_VARIANT_ARRAY) {
+ for (i = 0; i < kv->val->data.as_array->entry_count; i++) {
+ val = kv->val->data.as_array->entries[i];
+ ret = flb_output_set_property(ins, kv->key, val->data.as_string);
+ }
+ }
+ }
+
+ if (ret == -1) {
+ flb_error("[config] could not configure property '%s' on "
+ "%s plugin with section name '%s'",
+ kv->key, s_type, name);
+ }
+ }
+
+ /* Processors */
+ processors = flb_cf_group_get(cf, s, "processors");
+ if (processors) {
+ if (type == FLB_CF_INPUT) {
+ flb_processors_load_from_config_format_group(((struct flb_input_instance *) ins)->processor, processors);
+ }
+ else if (type == FLB_CF_OUTPUT) {
+ flb_processors_load_from_config_format_group(((struct flb_output_instance *) ins)->processor, processors);
+ }
+ else {
+ flb_error("[config] section '%s' does not support processors", s_type);
+ }
+ }
+ }
+
+ return 0;
+}
+/* Load a struct flb_config_format context into a flb_config instance */
+int flb_config_load_config_format(struct flb_config *config, struct flb_cf *cf)
+{
+ int ret;
+ struct flb_kv *kv;
+ struct mk_list *head;
+ struct cfl_kvpair *ckv;
+ struct cfl_list *chead;
+ struct flb_cf_section *s;
+
+ /* Process config environment vars */
+ mk_list_foreach(head, &cf->env) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ ret = flb_env_set(config->env, kv->key, kv->val);
+ if (ret == -1) {
+ flb_error("could not set config environment variable '%s'", kv->key);
+ return -1;
+ }
+ }
+
+ /* Process all meta commands */
+ mk_list_foreach(head, &cf->metas) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ flb_meta_run(config, kv->key, kv->val);
+ }
+
+ /* Validate sections */
+ mk_list_foreach(head, &cf->sections) {
+ s = mk_list_entry(head, struct flb_cf_section, _head);
+
+ if (strcasecmp(s->name, "env") == 0 ||
+ strcasecmp(s->name, "service") == 0 ||
+ strcasecmp(s->name, "custom") == 0 ||
+ strcasecmp(s->name, "input") == 0 ||
+ strcasecmp(s->name, "filter") == 0 ||
+ strcasecmp(s->name, "output") == 0) {
+
+ /* continue on valid sections */
+ continue;
+ }
+
+ /* Extra sanity checks */
+ if (strcasecmp(s->name, "parser") == 0 ||
+ strcasecmp(s->name, "multiline_parser") == 0) {
+ fprintf(stderr,
+ "Sections 'multiline_parser' and 'parser' are not valid in "
+ "the main configuration file. It belongs to \n"
+ "the 'parsers_file' configuration files.\n");
+ return -1;
+ }
+ }
+
+ /* Read main 'service' section */
+ s = cf->service;
+ if (s) {
+ /* Iterate properties */
+ cfl_list_foreach(chead, &s->properties->list) {
+ ckv = cfl_list_entry(chead, struct cfl_kvpair, _head);
+ flb_config_set_property(config, ckv->key, ckv->val->data.as_string);
+ }
+ }
+
+ ret = configure_plugins_type(config, cf, FLB_CF_CUSTOM);
+ if (ret == -1) {
+ return -1;
+ }
+
+ ret = configure_plugins_type(config, cf, FLB_CF_INPUT);
+ if (ret == -1) {
+ return -1;
+ }
+ ret = configure_plugins_type(config, cf, FLB_CF_FILTER);
+ if (ret == -1) {
+ return -1;
+ }
+ ret = configure_plugins_type(config, cf, FLB_CF_OUTPUT);
+ if (ret == -1) {
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/fluent-bit/src/flb_config_map.c b/fluent-bit/src/flb_config_map.c
new file mode 100644
index 000000000..9a3dd7ac4
--- /dev/null
+++ b/fluent-bit/src/flb_config_map.c
@@ -0,0 +1,817 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_env.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_slist.h>
+#include <fluent-bit/flb_macros.h>
+#include <fluent-bit/flb_config_map.h>
+
+static int check_list_size(struct mk_list *list, int type)
+{
+ int len;
+
+ len = mk_list_size(list);
+ if (type == FLB_CONFIG_MAP_SLIST_1 || type == FLB_CONFIG_MAP_CLIST_1) {
+ if (len < 1) {
+ return -1;
+ }
+ }
+ else if (type == FLB_CONFIG_MAP_SLIST_2 || type == FLB_CONFIG_MAP_CLIST_2) {
+ if (len < 2) {
+ return -1;
+ }
+ }
+ else if (type == FLB_CONFIG_MAP_SLIST_3 || type == FLB_CONFIG_MAP_CLIST_3) {
+ if (len < 3) {
+ return -1;
+ }
+ }
+ else if (type == FLB_CONFIG_MAP_SLIST_4 || type == FLB_CONFIG_MAP_CLIST_4) {
+ if (len < 4) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Given a string, split the content using it proper separator generating a linked
+ * list of 'slist'
+ */
+static struct mk_list *parse_string_map_to_list(struct flb_config_map *map, char *str)
+{
+ int ret = -1;
+ int type;
+ int max_split = -1;
+ struct mk_list *list;
+
+ type = map->type;
+
+ /* Allocate list head */
+ list = flb_malloc(sizeof(struct mk_list));
+ if (!list) {
+ flb_errno();
+ return NULL;
+ }
+ mk_list_init(list);
+
+ /* Determinate the max split value based on it type */
+ if (map->type > FLB_CONFIG_MAP_CLIST && map->type < FLB_CONFIG_MAP_SLIST) {
+ type = FLB_CONFIG_MAP_CLIST;
+ max_split = (map->type - FLB_CONFIG_MAP_CLIST);
+ }
+ else if (map->type > FLB_CONFIG_MAP_SLIST) {
+ type = FLB_CONFIG_MAP_SLIST;
+ max_split = (map->type - FLB_CONFIG_MAP_SLIST);
+ }
+
+ if (type == FLB_CONFIG_MAP_CLIST) {
+ ret = flb_slist_split_string(list, str, ',', max_split);
+ }
+ else if (type == FLB_CONFIG_MAP_SLIST) {
+ ret = flb_slist_split_tokens(list, str, max_split);
+ }
+
+ if (ret == -1) {
+ flb_error("[config map] error reading list of options");
+ flb_free(list);
+ return NULL;
+ }
+
+ return list;
+}
+
+static int translate_default_value(struct flb_config_map *map, char *val)
+{
+ int ret;
+ struct flb_config_map_val *entry = NULL;
+ struct mk_list *list = NULL;
+
+ /* Prepare contexts if the map allows multiple entries */
+ if (map->flags & FLB_CONFIG_MAP_MULT) {
+ entry = flb_calloc(1, sizeof(struct flb_config_map_val));
+ if (!entry) {
+ flb_errno();
+ /*
+ * do not worry about 'list' allocation, it will be destroyed by the caller
+ * when it catches this error
+ */
+ return -1;
+ }
+ }
+ else {
+ entry = &map->value;
+ }
+
+ /* Based on specific data types, populate 'value' */
+ if (map->type == FLB_CONFIG_MAP_STR) {
+ /* Duplicate string as a flb_sds_t */
+ entry->val.str = flb_sds_create(val);
+
+ /* Validate new memory allocation */
+ if (!entry->val.str) {
+ goto error;
+ }
+ }
+ else if (map->type == FLB_CONFIG_MAP_STR_PREFIX) {
+ /*
+ * For prefixed string types we don't process them, just validate
+ * that no default value has been set.
+ */
+ if (val) {
+ flb_error("[config map] invalid default value for prefixed string '%s'",
+ map->name);
+ goto error;
+ }
+ }
+ else if (map->type == FLB_CONFIG_MAP_BOOL) {
+ ret = flb_utils_bool(val);
+ if (ret == -1) {
+ flb_error("[config map] invalid default value for boolean '%s=%s'",
+ map->name, val);
+ goto error;
+ }
+ entry->val.boolean = flb_utils_bool(val);
+ }
+ else if (map->type == FLB_CONFIG_MAP_INT) {
+ entry->val.i_num = atoi(val);
+ }
+ else if (map->type == FLB_CONFIG_MAP_DOUBLE) {
+ entry->val.d_num = atof(val);
+ }
+ else if (map->type == FLB_CONFIG_MAP_SIZE) {
+ entry->val.s_num = flb_utils_size_to_bytes(val);
+ }
+ else if (map->type == FLB_CONFIG_MAP_TIME) {
+ entry->val.i_num = flb_utils_time_to_seconds(val);
+ }
+ else if (map->type >= FLB_CONFIG_MAP_CLIST &&
+ map->type <= FLB_CONFIG_MAP_SLIST_4) {
+
+ list = parse_string_map_to_list(map, val);
+ if (!list) {
+ flb_error("[config map] cannot parse list of values '%s'", val);
+ goto error;
+ }
+
+ entry->val.list = list;
+ list = NULL;
+ }
+
+ if (map->flags & FLB_CONFIG_MAP_MULT) {
+ mk_list_add(&entry->_head, map->value.mult);
+ }
+
+ return 0;
+
+ error:
+ if (map->flags & FLB_CONFIG_MAP_MULT) {
+ flb_free(entry);
+ }
+ return -1;
+}
+
+static flb_sds_t helper_map_options(struct mk_list *map)
+{
+ flb_sds_t buf;
+ flb_sds_t tmp;
+ struct mk_list *head;
+ struct flb_config_map *m;
+
+ buf = flb_sds_create_size(256);
+ if (!buf) {
+ flb_errno();
+ return NULL;
+ }
+
+ tmp = flb_sds_printf(&buf, "The following properties are allowed: ");
+ if (!tmp) {
+ flb_errno();
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ buf = tmp;
+
+ mk_list_foreach(head, map) {
+ m = mk_list_entry(head, struct flb_config_map, _head);
+ if (head->next != map) {
+ tmp = flb_sds_printf(&buf, "%s, ", m->name);
+ }
+ else {
+ if (mk_list_size(map) == 1) {
+ tmp = flb_sds_printf(&buf, "%s.", m->name);
+ }
+ else {
+ tmp = flb_sds_printf(&buf, "and %s.", m->name);
+ }
+ }
+
+ if (!tmp) {
+ flb_errno();
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ buf = tmp;
+ }
+
+ return buf;
+}
+
+/*
+ * Given a static plugin configuration map, create a linked list representation. We use a
+ * linked list using heap memory instead of the stack since a plugin can be loaded multiple
+ * times.
+ *
+ * In addition, for default values, we process them and populate the 'value' field with
+ * proper data types.
+ */
+struct mk_list *flb_config_map_create(struct flb_config *config,
+ struct flb_config_map *map)
+{
+ int ret;
+ flb_sds_t env;
+ struct mk_list *tmp;
+ struct mk_list *list;
+ struct flb_config_map *new = NULL;
+ struct flb_config_map *m;
+
+ list = flb_malloc(sizeof(struct mk_list));
+ if (!list) {
+ flb_errno();
+ return NULL;
+ }
+ mk_list_init(list);
+
+ /*
+ * Read every property defined in the config map and create a new dynamic list
+ * with the same content.
+ *
+ * As an additional step, it populate the 'value' field using the given default
+ * value if any. Note that default values are strings so they are processed
+ * to fit into the proper data type of 'value'.
+ */
+ m = map;
+ while (m && m->name) {
+ /* Allocate map node */
+ new = flb_calloc(1, sizeof(struct flb_config_map));
+ if (!new) {
+ flb_errno();
+ flb_config_map_destroy(list);
+ return NULL;
+ }
+
+ new->type = m->type;
+ new->name = flb_sds_create(m->name);
+ if (new->name == NULL) {
+ flb_free(new);
+ flb_config_map_destroy(list);
+ return NULL;
+ }
+
+ /* Translate default value */
+ if (m->def_value) {
+ /*
+ * Before to translate any value, make sure to disable the warning
+ * about unused variables. This might happen if a default value is an
+ * environment variable and the user is not using it (which is ok for
+ * that specific use case).
+ */
+ flb_env_warn_unused(config->env, FLB_FALSE);
+
+ /* Translate the value */
+ env = flb_env_var_translate(config->env, m->def_value);
+ if (env == NULL) {
+ flb_errno();
+ flb_sds_destroy(new->name);
+ flb_free(new);
+ flb_config_map_destroy(list);
+ return NULL;
+ }
+ new->def_value = env;
+ flb_env_warn_unused(config->env, FLB_TRUE);
+ }
+
+ new->flags = m->flags;
+ new->set_property = m->set_property;
+ new->offset = m->offset;
+ new->value.mult = NULL;
+ new->desc = m->desc;
+ mk_list_add(&new->_head, list);
+
+ if (new->set_property == FLB_FALSE) {
+ m++;
+ continue;
+ }
+
+ /* If this is a multiple type of entries, initialize the main list */
+ if (new->flags & FLB_CONFIG_MAP_MULT) {
+ tmp = flb_malloc(sizeof(struct mk_list));
+ if (!tmp) {
+ flb_errno();
+ flb_config_map_destroy(list);
+ return NULL;
+ }
+ mk_list_init(tmp);
+ new->value.mult = tmp;
+ }
+
+ /*
+ * If there is no default value or the entry will not be set, just
+ * continue with the next map entry
+ */
+ if (!m->def_value) {
+ m++;
+ continue;
+ }
+
+ /* Assign value based on data type and multiple mode if set */
+ ret = translate_default_value(new, new->def_value);
+ if (ret == -1) {
+ flb_config_map_destroy(list);
+ return NULL;
+ }
+ m++;
+ }
+
+ return list;
+}
+
+static void destroy_map_val(int type, struct flb_config_map_val *value)
+{
+ if (type == FLB_CONFIG_MAP_STR && value->val.str) {
+ flb_sds_destroy(value->val.str);
+ }
+ else if ((type >= FLB_CONFIG_MAP_CLIST &&
+ type <= FLB_CONFIG_MAP_SLIST_4) &&
+ value->val.list) {
+ flb_slist_destroy(value->val.list);
+ flb_free(value->val.list);
+ }
+}
+
+/* Destroy a config map context */
+void flb_config_map_destroy(struct mk_list *list)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct mk_list *v_head;
+ struct mk_list *v_tmp;
+ struct flb_config_map *map;
+ struct flb_config_map_val *entry;
+
+ mk_list_foreach_safe(head, tmp, list) {
+ map = mk_list_entry(head, struct flb_config_map, _head);
+ mk_list_del(&map->_head);
+
+ if (map->flags & FLB_CONFIG_MAP_MULT && map->value.mult) {
+ mk_list_foreach_safe(v_head, v_tmp, map->value.mult) {
+ entry = mk_list_entry(v_head, struct flb_config_map_val, _head);
+ mk_list_del(&entry->_head);
+ destroy_map_val(map->type, entry);
+ flb_free(entry);
+ }
+ flb_free(map->value.mult);
+ }
+ else {
+ destroy_map_val(map->type, &map->value);
+ }
+ if (map->def_value) {
+ flb_sds_destroy(map->def_value);
+ }
+ flb_sds_destroy(map->name);
+ flb_free(map);
+ }
+ flb_free(list);
+}
+
+/* Count the number of times a property key exists */
+int property_count(char *key, int len, struct mk_list *properties)
+{
+ int count = 0;
+ struct mk_list *head;
+ struct flb_kv *kv;
+
+ mk_list_foreach(head, properties) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ if (flb_sds_len(kv->key) != len) {
+ continue;
+ }
+
+ if (strncmp(kv->key, key, len) == 0) {
+ count++;
+ }
+ }
+ return count;
+}
+
+/*
+ * If the property starts with '_debug.', it's an internal property for
+ * some component of Fluent Bit, not the plugin it self.
+ */
+static int is_internal_debug_property(char *prop_name)
+{
+#ifdef FLB_HAVE_HTTP_CLIENT_DEBUG
+ if (strncmp(prop_name, "_debug.http.", 12) == 0) {
+ return FLB_TRUE;
+ }
+#endif
+
+ return FLB_FALSE;
+}
+
+
+/* Validate that the incoming properties set by the caller are allowed by the plugin */
+int flb_config_map_properties_check(char *context_name,
+ struct mk_list *in_properties,
+ struct mk_list *map)
+{
+ int len;
+ int found;
+ int count = 0;
+ int ret;
+ flb_sds_t helper;
+ struct flb_kv *kv;
+ struct mk_list *head;
+ struct mk_list *m_head;
+ struct flb_config_map *m;
+
+ /* Iterate all incoming property list */
+ mk_list_foreach(head, in_properties) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ found = FLB_FALSE;
+
+
+ ret = is_internal_debug_property(kv->key);
+ if (ret == FLB_TRUE) {
+ /* Skip the config map */
+ continue;
+ }
+
+ if (strcasecmp(kv->key, "active") == 0) {
+ /* Accept 'active' property ... */
+ continue;
+ }
+
+ /* Lookup the key into the provided map */
+ mk_list_foreach(m_head, map) {
+ m = mk_list_entry(m_head, struct flb_config_map, _head);
+
+ len = flb_sds_len(m->name);
+ if (m->type != FLB_CONFIG_MAP_STR_PREFIX) {
+ if (len != flb_sds_len(kv->key)) {
+ continue;
+ }
+ }
+
+ if (strncasecmp(kv->key, m->name, len) == 0) {
+ if (m->type == FLB_CONFIG_MAP_STR_PREFIX) {
+ if (flb_sds_len(kv->key) <= len) {
+ flb_error("[config] incomplete prefixed key '%s'", kv->key);
+ found = FLB_FALSE;
+ break;
+ }
+ }
+ else if(m->type == FLB_CONFIG_MAP_DEPRECATED) {
+ flb_warn("[config] %s: '%s' is deprecated",
+ context_name, kv->key);
+ }
+ found = FLB_TRUE;
+ break;
+ }
+ }
+
+ if (found == FLB_FALSE) {
+ helper = helper_map_options(map);
+ if (!helper) {
+ flb_error("[config] %s: unknown configuration property '%s'",
+ context_name, kv->key);
+ }
+ else {
+ flb_error("[config] %s: unknown configuration property '%s'. %s",
+ context_name, kv->key, helper);
+ flb_sds_destroy(helper);
+ }
+
+ return -1;
+ }
+
+ /* Validate number of times the property is set */
+ count = property_count(kv->key, flb_sds_len(kv->key), in_properties);
+ if ((m->flags & FLB_CONFIG_MAP_MULT) == 0) {
+ if (count > 1) {
+ flb_error("[config] %s: configuration property '%s' is set %i times",
+ context_name, kv->key, count);
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Returns FLB_TRUE or FLB_FALSE if a property aims to override the default value
+ * assigned to the map key valled 'name'.
+ */
+static int properties_override_default(struct mk_list *properties, char *name)
+{
+ int len;
+ struct mk_list *head;
+ struct flb_kv *kv;
+
+ len = strlen(name);
+
+ mk_list_foreach(head, properties) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ if (flb_sds_len(kv->key) != len) {
+ continue;
+ }
+
+ if (strcasecmp(kv->key, name) == 0) {
+ return FLB_TRUE;
+ }
+ }
+
+ return FLB_FALSE;
+}
+
+/*
+ * Return the number of expected values if the property type is from CLIST
+ * or SLIST family.
+ */
+int flb_config_map_expected_values(int type)
+{
+ if (type > FLB_CONFIG_MAP_CLIST && type < FLB_CONFIG_MAP_SLIST) {
+ return type - FLB_CONFIG_MAP_CLIST;
+ }
+ if (type > FLB_CONFIG_MAP_SLIST && type <= FLB_CONFIG_MAP_SLIST_4) {
+ return type - FLB_CONFIG_MAP_SLIST;
+ }
+ return -1;
+}
+
+
+/*
+ * Function used by plugins that needs to populate their context structure with the
+ * configuration properties already mapped.
+ */
+int flb_config_map_set(struct mk_list *properties, struct mk_list *map, void *context)
+{
+ int ret;
+ int len;
+ char *base;
+ char *m_bool;
+ int *m_i_num;
+ double *m_d_num;
+ size_t *m_s_num;
+ flb_sds_t *m_str;
+ struct flb_kv *kv;
+ struct mk_list *head;
+ struct mk_list *m_head;
+ struct mk_list **m_list;
+ struct mk_list *list;
+ struct flb_config_map *m = NULL;
+ struct flb_config_map_val *entry = NULL;
+
+ base = context;
+
+ /* Link 'already processed default values' into the caller context */
+ mk_list_foreach(m_head, map) {
+ m = mk_list_entry(m_head, struct flb_config_map, _head);
+
+ /*
+ * If the map type allows multiple entries, the user context is a pointer
+ * for a linked list. We just point their structure to our pre-processed
+ * list of entries.
+ */
+ if (m->flags & FLB_CONFIG_MAP_MULT && m->set_property == FLB_TRUE) {
+ m_list = (struct mk_list **) (base + m->offset);
+ *m_list = m->value.mult;
+ continue;
+ }
+
+ /*
+ * If no default value exists or the map will not write to the user
+ * context.. skip it.
+ */
+ if (!m->def_value || m->set_property == FLB_FALSE) {
+ continue;
+ }
+
+ /*
+ * If a property set by the user will override the default value, just
+ * do not put the default value into the context since it will be replaced
+ * later.
+ */
+ ret = properties_override_default(properties, m->name);
+ if (ret == FLB_TRUE) {
+ continue;
+ }
+
+ /* All the following steps are direct writes to the user context */
+ if (m->type == FLB_CONFIG_MAP_STR) {
+ m_str = (char **) (base + m->offset);
+ *m_str = m->value.val.str;
+ }
+ else if (m->type == FLB_CONFIG_MAP_INT) {
+ m_i_num = (int *) (base + m->offset);
+ *m_i_num = m->value.val.i_num;
+ }
+ else if (m->type == FLB_CONFIG_MAP_DOUBLE) {
+ m_d_num = (double *) (base + m->offset);
+ *m_d_num = m->value.val.d_num;
+ }
+ else if (m->type == FLB_CONFIG_MAP_SIZE) {
+ m_s_num = (size_t *) (base + m->offset);
+ *m_s_num = m->value.val.s_num;
+ }
+ else if (m->type == FLB_CONFIG_MAP_TIME) {
+ m_i_num = (int *) (base + m->offset);
+ *m_i_num = m->value.val.s_num;
+ }
+ else if (m->type == FLB_CONFIG_MAP_BOOL) {
+ m_bool = (char *) (base + m->offset);
+ *m_bool = m->value.val.boolean;
+ }
+ else if (m->type >= FLB_CONFIG_MAP_CLIST ||
+ m->type <= FLB_CONFIG_MAP_SLIST_4) {
+ m_list = (struct mk_list **) (base + m->offset);
+ *m_list = m->value.val.list;
+ }
+ }
+
+ /*
+ * Iterate all properties coming from the configuration reader. If a property overrides
+ * a default value already set in the previous step, just link to the new value.
+ */
+ mk_list_foreach(head, properties) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ if (kv->val == NULL) {
+ continue;
+ }
+
+ mk_list_foreach(m_head, map) {
+ m = mk_list_entry(m_head, struct flb_config_map, _head);
+ if (flb_sds_len(kv->key) != flb_sds_len(m->name)) {
+ m = NULL;
+ continue;
+ }
+
+ if (strncasecmp(kv->key, m->name, flb_sds_len(m->name)) == 0) {
+ break;
+ }
+ m = NULL;
+ continue;
+
+ }
+
+ if (!m || m->set_property == FLB_FALSE) {
+ continue;
+ }
+
+ /* Check if the map allows multiple entries */
+ if (m->flags & FLB_CONFIG_MAP_MULT) {
+ /* Create node */
+ entry = flb_calloc(1, sizeof(struct flb_config_map_val));
+ if (!entry) {
+ flb_errno();
+ return -1;
+ }
+
+ /* Populate value */
+ if (m->type == FLB_CONFIG_MAP_STR) {
+ entry->val.str = flb_sds_create(kv->val);
+ }
+ else if (m->type == FLB_CONFIG_MAP_INT) {
+ entry->val.i_num = atoi(kv->val);
+ }
+ else if (m->type == FLB_CONFIG_MAP_DOUBLE) {
+ entry->val.d_num = atof(kv->val);
+ }
+ else if (m->type == FLB_CONFIG_MAP_SIZE) {
+ entry->val.s_num = flb_utils_size_to_bytes(kv->val);
+ }
+ else if (m->type == FLB_CONFIG_MAP_TIME) {
+ entry->val.i_num = flb_utils_time_to_seconds(kv->val);
+ }
+ else if (m->type == FLB_CONFIG_MAP_BOOL) {
+ ret = flb_utils_bool(kv->val);
+ if (ret == -1) {
+ flb_free(entry);
+ flb_error("[config map] invalid value for boolean property '%s=%s'",
+ m->name, kv->val);
+ return -1;
+ }
+ entry->val.boolean = ret;
+ }
+ else if (m->type >= FLB_CONFIG_MAP_CLIST ||
+ m->type <= FLB_CONFIG_MAP_SLIST_4) {
+
+ list = parse_string_map_to_list(m, kv->val);
+ if (!list) {
+ flb_error("[config map] cannot parse list of values '%s'", kv->val);
+ flb_free(entry);
+ return -1;
+ }
+ entry->val.list = list;
+
+ /* Validate the number of entries are the minimum expected */
+ len = mk_list_size(list);
+ ret = check_list_size(list, m->type);
+ if (ret == -1) {
+ flb_error("[config map] property '%s' expects %i values "
+ "(only %i were found)",
+ kv->key,
+ flb_config_map_expected_values(m->type), len);
+ /*
+ * Register the entry anyways, so on exit the resources will
+ * be released
+ */
+ mk_list_add(&entry->_head, m->value.mult);
+ return -1;
+ }
+ }
+
+ /* Add entry to the map 'mult' list tail */
+ mk_list_add(&entry->_head, m->value.mult);
+
+ /* Override user context */
+ m_list = (struct mk_list **) (base + m->offset);
+ *m_list = m->value.mult;
+ }
+ else if (map != NULL) {
+ /* Direct write to user context */
+ if (m->type == FLB_CONFIG_MAP_STR) {
+ m_str = (char **) (base + m->offset);
+ *m_str = kv->val;
+ }
+ else if (m->type == FLB_CONFIG_MAP_INT) {
+ m_i_num = (int *) (base + m->offset);
+ *m_i_num = atoi(kv->val);
+ }
+ else if (m->type == FLB_CONFIG_MAP_DOUBLE) {
+ m_d_num = (double *) (base + m->offset);
+ *m_d_num = atof(kv->val);
+ }
+ else if (m->type == FLB_CONFIG_MAP_BOOL) {
+ m_bool = (char *) (base + m->offset);
+ ret = flb_utils_bool(kv->val);
+ if (ret == -1) {
+ flb_error("[config map] invalid value for boolean property '%s=%s'",
+ m->name, kv->val);
+ return -1;
+ }
+ *m_bool = ret;
+ }
+ else if (m->type == FLB_CONFIG_MAP_SIZE) {
+ m_s_num = (size_t *) (base + m->offset);
+ *m_s_num = flb_utils_size_to_bytes(kv->val);
+ }
+ else if (m->type == FLB_CONFIG_MAP_TIME) {
+ m_i_num = (int *) (base + m->offset);
+ *m_i_num = flb_utils_time_to_seconds(kv->val);
+ }
+ else if (m->type >= FLB_CONFIG_MAP_CLIST ||
+ m->type <= FLB_CONFIG_MAP_SLIST_4) {
+ list = parse_string_map_to_list(m, kv->val);
+ if (!list) {
+ flb_error("[config map] cannot parse list of values '%s'", kv->val);
+ flb_free(entry);
+ return -1;
+ }
+
+ if (m->value.val.list) {
+ destroy_map_val(m->type, &m->value);
+ }
+
+ m->value.val.list = list;
+ m_list = (struct mk_list **) (base + m->offset);
+ *m_list = m->value.val.list;
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/fluent-bit/src/flb_config_static.c b/fluent-bit/src/flb_config_static.c
new file mode 100644
index 000000000..7005b4ada
--- /dev/null
+++ b/fluent-bit/src/flb_config_static.c
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_config_format.h>
+#include <fluent-bit/conf/flb_static_conf.h>
+
+/*
+ * If Fluent Bit has static configuration support, this function allows to lookup,
+ * parse and create configuration file contexts from the entries generated by
+ * CMake at build-time.
+ *
+ * This routing passes a 'virtual' file name that should be registered into the
+ * static array 'flb_config_files'. Learn more about it at:
+ *
+ * include/fluent-bit/conf/flb_static_conf.h
+ *
+ */
+struct flb_cf *flb_config_static_open(const char *file)
+{
+ int i;
+ const char *k = NULL;
+ const char *v = NULL;
+ struct flb_cf *cf;
+
+ /* Iterate static array and lookup the file name */
+ for (i = 0; i < flb_config_files_size; i++) {
+ k = (const char *) flb_config_files[i][0];
+ v = (const char *) flb_config_files[i][1];
+
+ if (strcmp(k, file) == 0) {
+ break;
+ }
+ k = NULL;
+ }
+
+ if (!k) {
+ return NULL;
+ }
+
+ cf = flb_cf_fluentbit_create(NULL, (char *) file, (char *) v, 0);
+ if (!cf) {
+ return NULL;
+ }
+
+ return cf;
+}
diff --git a/fluent-bit/src/flb_connection.c b/fluent-bit/src/flb_connection.c
new file mode 100644
index 000000000..a3ed40265
--- /dev/null
+++ b/fluent-bit/src/flb_connection.c
@@ -0,0 +1,257 @@
+#include <assert.h>
+
+#include <fluent-bit/flb_connection.h>
+#include <fluent-bit/flb_upstream.h>
+#include <fluent-bit/flb_downstream.h>
+
+int flb_connection_setup(struct flb_connection *connection,
+ flb_sockfd_t socket,
+ int type,
+ void *stream,
+ struct mk_event_loop *event_loop,
+ struct flb_coro *coroutine)
+{
+ assert(connection != NULL);
+
+ memset(connection, 0, sizeof(struct flb_connection));
+
+ connection->fd = socket;
+ connection->type = type;
+ connection->stream = stream;
+ connection->net_error = -1;
+ connection->evl = event_loop;
+ connection->coroutine = coroutine;
+ connection->tls_session = NULL;
+ connection->ts_created = time(NULL);
+ connection->ts_assigned = time(NULL);
+ connection->busy_flag = FLB_FALSE;
+ connection->shutdown_flag = FLB_FALSE;
+
+ connection->net = &connection->stream->net;
+
+ assert(connection->net != NULL);
+
+ MK_EVENT_ZERO(&connection->event);
+
+ flb_connection_unset_connection_timeout(connection);
+ flb_connection_unset_io_timeout(connection);
+
+ return 0;
+}
+
+struct flb_connection *flb_connection_create(flb_sockfd_t socket,
+ int type,
+ void *stream,
+ struct mk_event_loop *event_loop,
+ struct flb_coro *coroutine)
+{
+ struct flb_connection *connection;
+ int result;
+
+ connection = flb_calloc(1, sizeof(struct flb_connection));
+
+ if (connection == NULL) {
+ flb_errno();
+ }
+ else {
+ result = flb_connection_setup(connection,
+ socket,
+ type,
+ stream,
+ event_loop,
+ coroutine);
+
+ if (result != 0) {
+ flb_connection_destroy(connection);
+
+ connection = NULL;
+ }
+ else {
+ connection->dynamically_allocated = FLB_TRUE;
+ }
+ }
+
+ return connection;
+}
+
+void flb_connection_destroy(struct flb_connection *connection)
+{
+ assert(connection != NULL);
+
+ if (connection->dynamically_allocated) {
+ flb_free(connection);
+ }
+}
+
+static void compose_user_friendly_remote_host(struct flb_connection *connection)
+{
+ int connection_type;
+
+ connection_type = connection->stream->transport;
+
+ if (connection_type == FLB_TRANSPORT_TCP) {
+ snprintf(connection->user_friendly_remote_host,
+ sizeof(connection->user_friendly_remote_host),
+ "tcp://%s:%u",
+ connection->remote_host,
+ connection->remote_port);
+ }
+ else if (connection_type == FLB_TRANSPORT_UDP) {
+ snprintf(connection->user_friendly_remote_host,
+ sizeof(connection->user_friendly_remote_host),
+ "udp://%s:%u",
+ connection->remote_host,
+ connection->remote_port);
+ }
+ else if (connection_type == FLB_TRANSPORT_UNIX_STREAM) {
+ snprintf(connection->user_friendly_remote_host,
+ sizeof(connection->user_friendly_remote_host),
+ "unix://%s",
+ connection->remote_host);
+ }
+ else if (connection_type == FLB_TRANSPORT_UNIX_DGRAM) {
+ snprintf(connection->user_friendly_remote_host,
+ sizeof(connection->user_friendly_remote_host),
+ "unix://%s",
+ connection->remote_host);
+ }
+}
+
+void flb_connection_set_remote_host(struct flb_connection *connection,
+ struct sockaddr *remote_host)
+{
+ size_t address_size;
+
+ address_size = flb_network_address_size((struct sockaddr_storage *) remote_host);
+
+ if (address_size > 0 &&
+ address_size < sizeof(struct sockaddr_storage)) {
+ memcpy(&connection->raw_remote_host,
+ remote_host,
+ address_size);
+ }
+}
+
+char *flb_connection_get_remote_address(struct flb_connection *connection)
+{
+ int address_refresh_required;
+ size_t dummy_size_receptacle;
+ int refresh_required;
+ int stream_type;
+ int transport;
+ int result;
+
+ stream_type = connection->stream->type;
+ transport = connection->stream->transport;
+
+ address_refresh_required = FLB_FALSE;
+ refresh_required = FLB_FALSE;
+
+ if (stream_type == FLB_DOWNSTREAM) {
+ if (transport == FLB_TRANSPORT_UDP) {
+ if (connection->raw_remote_host.ss_family != AF_UNSPEC) {
+ refresh_required = FLB_TRUE;
+ }
+ }
+ else if (transport == FLB_TRANSPORT_TCP ||
+ transport == FLB_TRANSPORT_UNIX_STREAM) {
+ if (connection->raw_remote_host.ss_family == AF_UNSPEC) {
+ address_refresh_required = FLB_TRUE;
+ }
+ }
+ }
+ else if (stream_type == FLB_UPSTREAM) {
+ if (transport == FLB_TRANSPORT_TCP ||
+ transport == FLB_TRANSPORT_UNIX_STREAM) {
+ if (connection->raw_remote_host.ss_family == AF_UNSPEC) {
+ address_refresh_required = FLB_TRUE;
+ }
+ }
+ }
+
+ if (connection->remote_port == 0) {
+ refresh_required = FLB_TRUE;
+ }
+
+ if (refresh_required) {
+ if (address_refresh_required) {
+ result = flb_net_socket_peer_address(connection->fd,
+ &connection->raw_remote_host);
+ }
+
+ result = flb_net_socket_address_info(connection->fd,
+ &connection->raw_remote_host,
+ &connection->remote_port,
+ connection->remote_host,
+ sizeof(connection->remote_host),
+ &dummy_size_receptacle);
+
+ if (result == 0) {
+ compose_user_friendly_remote_host(connection);
+ }
+ }
+
+ return connection->user_friendly_remote_host;
+}
+
+int flb_connection_get_flags(struct flb_connection *connection)
+{
+ return flb_stream_get_flags(connection->stream);
+}
+
+void flb_connection_reset_connection_timeout(struct flb_connection *connection)
+{
+ time_t current_time;
+ time_t timeout_time;
+
+ assert(connection != NULL);
+
+ if (connection->type == FLB_UPSTREAM_CONNECTION) {
+ if (connection->net->connect_timeout > 0) {
+ current_time = time(NULL);
+ timeout_time = current_time + connection->net->connect_timeout;
+
+ connection->ts_connect_start = current_time;
+ connection->ts_connect_timeout = timeout_time;
+ }
+ }
+ else if(connection->type == FLB_DOWNSTREAM_CONNECTION) {
+ if (connection->net->accept_timeout > 0) {
+ current_time = time(NULL);
+ timeout_time = current_time + connection->net->accept_timeout;
+
+ connection->ts_connect_start = current_time;
+ connection->ts_connect_timeout = timeout_time;
+ }
+ }
+}
+
+void flb_connection_unset_connection_timeout(struct flb_connection *connection)
+{
+ assert(connection != NULL);
+
+ connection->ts_connect_start = -1;
+ connection->ts_connect_timeout = -1;
+}
+
+void flb_connection_reset_io_timeout(struct flb_connection *connection)
+{
+ time_t current_time;
+ time_t timeout_time;
+
+ assert(connection != NULL);
+
+ if (connection->net->io_timeout > 0) {
+ current_time = time(NULL);
+ timeout_time = current_time + connection->net->io_timeout;
+
+ connection->ts_io_timeout = timeout_time;
+ }
+}
+
+void flb_connection_unset_io_timeout(struct flb_connection *connection)
+{
+ assert(connection != NULL);
+
+ connection->ts_io_timeout = -1;
+} \ No newline at end of file
diff --git a/fluent-bit/src/flb_coro.c b/fluent-bit/src/flb_coro.c
new file mode 100644
index 000000000..0d4e67f09
--- /dev/null
+++ b/fluent-bit/src/flb_coro.c
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_thread_storage.h>
+#include <fluent-bit/flb_coro.h>
+
+FLB_TLS_DEFINE(struct flb_coro, flb_coro_key);
+
+static pthread_mutex_t coro_mutex_init;
+
+void flb_coro_init()
+{
+ FLB_TLS_INIT(flb_coro_key);
+ pthread_mutex_init(&coro_mutex_init, NULL);
+}
+
+void flb_coro_thread_init()
+{
+ size_t s;
+ cothread_t th;
+
+ pthread_mutex_lock(&coro_mutex_init);
+ th = co_create(256, NULL, &s);
+ co_delete(th);
+ pthread_mutex_unlock(&coro_mutex_init);
+}
+
+struct flb_coro *flb_coro_get()
+{
+ struct flb_coro *coro;
+
+ coro = FLB_TLS_GET(flb_coro_key);
+ return coro;
+}
+
+void flb_coro_set(struct flb_coro *coro)
+{
+ FLB_TLS_SET(flb_coro_key, coro);
+}
diff --git a/fluent-bit/src/flb_crypto.c b/fluent-bit/src/flb_crypto.c
new file mode 100644
index 000000000..c2811039a
--- /dev/null
+++ b/fluent-bit/src/flb_crypto.c
@@ -0,0 +1,405 @@
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2019-2020 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_crypto.h>
+#include <openssl/bio.h>
+#include <string.h>
+
+static int flb_crypto_get_rsa_padding_type_by_id(int padding_type_id)
+{
+ int result;
+
+ if (padding_type_id == FLB_CRYPTO_PADDING_PKCS1) {
+ result = RSA_PKCS1_PADDING;
+ }
+ else if (padding_type_id == FLB_CRYPTO_PADDING_PKCS1_OEAP) {
+ result = RSA_PKCS1_OAEP_PADDING;
+ }
+ else if (padding_type_id == FLB_CRYPTO_PADDING_PKCS1_X931) {
+ result = RSA_X931_PADDING;
+ }
+ else if (padding_type_id == FLB_CRYPTO_PADDING_PKCS1_PSS) {
+ result = RSA_PKCS1_PSS_PADDING;
+ }
+ else {
+ result = FLB_CRYPTO_PADDING_NONE;
+ }
+
+ return result;
+}
+
+static const EVP_MD *flb_crypto_get_digest_algorithm_instance_by_id(int algorithm_id)
+{
+ const EVP_MD *algorithm;
+
+ if (algorithm_id == FLB_HASH_SHA256) {
+ algorithm = EVP_sha256();
+ }
+ else if (algorithm_id == FLB_HASH_SHA512) {
+ algorithm = EVP_sha512();
+ }
+ else if (algorithm_id == FLB_HASH_MD5) {
+ algorithm = EVP_md5();
+ }
+ else {
+ algorithm = NULL;
+ }
+
+ return algorithm;
+}
+
+static int flb_crypto_import_pem_key(int key_type,
+ unsigned char *key,
+ size_t key_length,
+ EVP_PKEY **ingested_key)
+{
+ BIO *io_provider;
+ int result;
+
+ if (key_type != FLB_CRYPTO_PUBLIC_KEY &&
+ key_type != FLB_CRYPTO_PRIVATE_KEY) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ if (ingested_key == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ result = FLB_CRYPTO_BACKEND_ERROR;
+
+ io_provider = BIO_new_mem_buf((void*) key, key_length);
+
+ if (io_provider != NULL) {
+ if (ingested_key != NULL) {
+ if (key_type == FLB_CRYPTO_PRIVATE_KEY) {
+ *ingested_key = PEM_read_bio_PrivateKey(io_provider,
+ NULL, NULL,
+ NULL);
+ }
+ else if (key_type == FLB_CRYPTO_PUBLIC_KEY) {
+ *ingested_key = PEM_read_bio_PUBKEY(io_provider,
+ NULL, NULL,
+ NULL);
+
+ // printf("\n\nFAILURE? %p\n\n", *ingested_key);
+ // printf("ERROR : %s\n", ERR_error_string(ERR_get_error(), NULL));
+ // exit(0);
+ }
+
+ if (*ingested_key != NULL) {
+ result = FLB_CRYPTO_SUCCESS;
+ }
+ }
+
+ BIO_free_all(io_provider);
+ }
+
+ return result;
+}
+
+int flb_crypto_init(struct flb_crypto *context,
+ int padding_type,
+ int digest_algorithm,
+ int key_type,
+ unsigned char *key,
+ size_t key_length)
+{
+ int result;
+
+ if (context == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ if (key == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ if (key_length == 0) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ memset(context, 0, sizeof(struct flb_crypto));
+
+ result = flb_crypto_import_pem_key(key_type,
+ key,
+ key_length,
+ &context->key);
+
+ if (result != FLB_CRYPTO_SUCCESS) {
+ if (result == FLB_CRYPTO_BACKEND_ERROR) {
+ context->last_error = ERR_get_error();
+ }
+
+ flb_crypto_cleanup(context);
+
+ return result;
+ }
+
+ context->backend_context = EVP_PKEY_CTX_new(context->key, NULL);
+
+ if (context->backend_context == NULL) {
+ context->last_error = ERR_get_error();
+
+ flb_crypto_cleanup(context);
+
+ return result;
+ }
+
+ context->block_size = (size_t) EVP_PKEY_size(context->key);
+
+ context->padding_type = flb_crypto_get_rsa_padding_type_by_id(padding_type);
+
+ context->digest_algorithm = flb_crypto_get_digest_algorithm_instance_by_id(digest_algorithm);
+
+ return FLB_CRYPTO_SUCCESS;
+}
+
+
+int flb_crypto_cleanup(struct flb_crypto *context)
+{
+ if (context->backend_context != NULL) {
+ EVP_PKEY_free(context->key);
+
+ context->key = NULL;
+ }
+
+ if (context->backend_context != NULL) {
+ EVP_PKEY_CTX_free(context->backend_context);
+
+ context->backend_context = NULL;
+ }
+
+ return FLB_CRYPTO_SUCCESS;
+}
+
+int flb_crypto_transform(struct flb_crypto *context,
+ int operation,
+ unsigned char *input_buffer,
+ size_t input_length,
+ unsigned char *output_buffer,
+ size_t *output_length)
+{
+ int result = FLB_CRYPTO_BACKEND_ERROR;
+
+ if (context == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ if (operation != FLB_CRYPTO_OPERATION_SIGN &&
+ operation != FLB_CRYPTO_OPERATION_ENCRYPT &&
+ operation != FLB_CRYPTO_OPERATION_DECRYPT) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ if (context->last_operation == FLB_CRYPTO_OPERATION_NONE) {
+ if (operation == FLB_CRYPTO_OPERATION_SIGN) {
+ result = EVP_PKEY_sign_init(context->backend_context);
+ }
+ else if (operation == FLB_CRYPTO_OPERATION_ENCRYPT) {
+ result = EVP_PKEY_encrypt_init(context->backend_context);
+ }
+ else if (operation == FLB_CRYPTO_OPERATION_DECRYPT) {
+ result = EVP_PKEY_decrypt_init(context->backend_context);
+ }
+
+ if (result == 1) {
+ result = EVP_PKEY_CTX_set_rsa_padding(context->backend_context,
+ context->padding_type);
+
+ if (result > 0) {
+ if (context->digest_algorithm != NULL) {
+ result = EVP_PKEY_CTX_set_signature_md(context->backend_context,
+ context->digest_algorithm);
+ }
+ }
+
+ if (result > 0) {
+ result = 1;
+ }
+ }
+
+ if (result != 1) {
+ context->last_error = ERR_get_error();
+
+ return FLB_CRYPTO_BACKEND_ERROR;
+ }
+
+ context->last_operation = operation;
+ }
+ else if (context->last_operation != operation) {
+ return FLB_CRYPTO_INVALID_STATE;
+ }
+
+ if (operation == FLB_CRYPTO_OPERATION_SIGN) {
+ result = EVP_PKEY_sign(context->backend_context,
+ output_buffer, output_length,
+ input_buffer, input_length);
+ }
+ else if(operation == FLB_CRYPTO_OPERATION_ENCRYPT) {
+ result = EVP_PKEY_encrypt(context->backend_context,
+ output_buffer, output_length,
+ input_buffer, input_length);
+ }
+ else if(operation == FLB_CRYPTO_OPERATION_DECRYPT) {
+ result = EVP_PKEY_decrypt(context->backend_context,
+ output_buffer, output_length,
+ input_buffer, input_length);
+ }
+
+ if (result != 1) {
+ context->last_error = ERR_get_error();
+
+ return FLB_CRYPTO_BACKEND_ERROR;
+ }
+
+ return FLB_CRYPTO_SUCCESS;
+}
+
+int flb_crypto_sign(struct flb_crypto *context,
+ unsigned char *input_buffer,
+ size_t input_length,
+ unsigned char *output_buffer,
+ size_t *output_length)
+{
+ return flb_crypto_transform(context,
+ FLB_CRYPTO_OPERATION_SIGN,
+ input_buffer,
+ input_length,
+ output_buffer,
+ output_length);
+}
+
+int flb_crypto_encrypt(struct flb_crypto *context,
+ unsigned char *input_buffer,
+ size_t input_length,
+ unsigned char *output_buffer,
+ size_t *output_length)
+{
+ return flb_crypto_transform(context,
+ FLB_CRYPTO_OPERATION_ENCRYPT,
+ input_buffer,
+ input_length,
+ output_buffer,
+ output_length);
+}
+
+int flb_crypto_decrypt(struct flb_crypto *context,
+ unsigned char *input_buffer,
+ size_t input_length,
+ unsigned char *output_buffer,
+ size_t *output_length)
+{
+ return flb_crypto_transform(context,
+ FLB_CRYPTO_OPERATION_DECRYPT,
+ input_buffer,
+ input_length,
+ output_buffer,
+ output_length);
+}
+
+int flb_crypto_sign_simple(int key_type,
+ int padding_type,
+ int digest_algorithm,
+ unsigned char *key,
+ size_t key_length,
+ unsigned char *input_buffer,
+ size_t input_length,
+ unsigned char *output_buffer,
+ size_t *output_length)
+{
+ struct flb_crypto context;
+ int result;
+
+ result = flb_crypto_init(&context,
+ padding_type,
+ digest_algorithm,
+ key_type,
+ key,
+ key_length);
+
+ if (result == FLB_CRYPTO_SUCCESS) {
+ result = flb_crypto_sign(&context,
+ input_buffer, input_length,
+ output_buffer, output_length);
+
+ flb_crypto_cleanup(&context);
+ }
+
+ return result;
+}
+
+int flb_crypto_encrypt_simple(int padding_type,
+ unsigned char *key,
+ size_t key_length,
+ unsigned char *input_buffer,
+ size_t input_length,
+ unsigned char *output_buffer,
+ size_t *output_length)
+{
+ struct flb_crypto context;
+ int result;
+
+ result = flb_crypto_init(&context,
+ padding_type,
+ FLB_HASH_NONE,
+ FLB_CRYPTO_PUBLIC_KEY,
+ key,
+ key_length);
+
+ if (result == FLB_CRYPTO_SUCCESS) {
+ result = flb_crypto_encrypt(&context,
+ input_buffer, input_length,
+ output_buffer, output_length);
+
+
+ flb_crypto_cleanup(&context);
+ }
+
+ return result;
+}
+
+int flb_crypto_decrypt_simple(int padding_type,
+ unsigned char *key,
+ size_t key_length,
+ unsigned char *input_buffer,
+ size_t input_length,
+ unsigned char *output_buffer,
+ size_t *output_length)
+{
+ struct flb_crypto context;
+ int result;
+
+ result = flb_crypto_init(&context,
+ padding_type,
+ FLB_HASH_NONE,
+ FLB_CRYPTO_PRIVATE_KEY,
+ key,
+ key_length);
+
+ if (result == FLB_CRYPTO_SUCCESS) {
+ result = flb_crypto_decrypt(&context,
+ input_buffer, input_length,
+ output_buffer, output_length);
+
+ flb_crypto_cleanup(&context);
+ }
+
+ return result;
+}
+
+
+
diff --git a/fluent-bit/src/flb_csv.c b/fluent-bit/src/flb_csv.c
new file mode 100644
index 000000000..5ba38574e
--- /dev/null
+++ b/fluent-bit/src/flb_csv.c
@@ -0,0 +1,313 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#define _GNU_SOURCE
+#include <time.h>
+
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_str.h>
+
+#include <fluent-bit/flb_csv.h>
+
+
+enum {
+ FLB_CSV_STATE_INITIAL = 0,
+ FLB_CSV_STATE_STARTED_SIMPLE,
+ FLB_CSV_STATE_STARTED_DQUOTE,
+ FLB_CSV_STATE_FOUND_DQUOTE,
+ FLB_CSV_STATE_FOUND_CR
+};
+
+static void reset_state(struct flb_csv_state *state)
+{
+ state->start = 0;
+ state->length = 0;
+ state->has_dquote = false;
+ state->field_parsed = false;
+ state->state = FLB_CSV_STATE_INITIAL;
+ flb_sds_len_set(state->escape_buffer, 0);
+ flb_sds_len_set(state->buffered_data, 0);
+ state->offset = 0;
+}
+
+static int invoke_field_callback(struct flb_csv_state *state, const char *buf, size_t bufsize)
+{
+ size_t escpos;
+ size_t bufpos;
+ size_t bufend;
+
+ if (!state->has_dquote) {
+ /* simple case, since there's no double quotes, no escaping needs
+ * to be done */
+ state->field_callback(state->data, buf + state->start, state->length);
+ return 0;
+ }
+ /* ensure there's enough space in the escape buffer */
+ if (flb_sds_alloc(state->escape_buffer) < state->length) {
+ state->escape_buffer = flb_sds_increase(
+ state->escape_buffer, state->length);
+ if (!state->escape_buffer) {
+ return FLB_CSV_ALLOC_FAILED;
+ }
+ }
+
+ escpos = 0;
+ bufpos = state->start;
+ bufend = bufpos + state->length;
+ while (bufpos < bufend) {
+ if (buf[bufpos] == '"') {
+ /* escape double quote */
+ bufpos++;
+ }
+ state->escape_buffer[escpos++] = buf[bufpos++];
+ }
+ state->escape_buffer[escpos] = 0;
+ flb_sds_len_set(state->escape_buffer, escpos);
+ state->field_callback(state->data, state->escape_buffer, escpos);
+ return 0;
+}
+
+static int parse_simple(struct flb_csv_state *state, const char *buf, size_t bufsize)
+{
+ char c;
+
+ for (;;) {
+ if (state->offset >= bufsize) {
+ return FLB_CSV_EOF;
+ }
+ c = buf[state->offset];
+ if (c == ',' || c == '\n' || c == '\r') {
+ /* end of field */
+ break;
+ }
+ state->offset++;
+ }
+
+ state->length = state->offset - state->start;
+ return 0;
+}
+
+static int parse_quoted(struct flb_csv_state *state, const char *buf, size_t bufsize)
+{
+ char c;
+
+ for (;;) {
+ if (state->offset >= bufsize) {
+ return FLB_CSV_EOF;
+ }
+ c = buf[state->offset];
+ if (state->state == FLB_CSV_STATE_FOUND_DQUOTE) {
+ state->state = FLB_CSV_STATE_STARTED_DQUOTE;
+ if (c == '"') {
+ /* dquote inside field, skip but set flag so we can properly escape later */
+ state->has_dquote = true;
+ }
+ else {
+ /* end of field */
+ break;
+ }
+ }
+ else if (c == '"') {
+ state->state = FLB_CSV_STATE_FOUND_DQUOTE;
+ }
+ state->offset++;
+ }
+
+ /* subtract 1 to remove ending double quote */
+ state->length = state->offset - state->start - 1;
+ return 0;
+}
+
+static int parse_csv_field(struct flb_csv_state *state, const char *data, size_t len)
+{
+ int ret;
+ const char *buf;
+ size_t bufsize;
+ bool buffered = false;
+
+ buf = data;
+ bufsize = len;
+
+ if (state->state == FLB_CSV_STATE_INITIAL) {
+ if (data[state->offset] == '"') {
+ /* advance past opening quote */
+ state->offset++;
+ state->state = FLB_CSV_STATE_STARTED_DQUOTE;
+ }
+ else {
+ state->state = FLB_CSV_STATE_STARTED_SIMPLE;
+ }
+ state->start = state->offset;
+ }
+ else if (state->field_callback) {
+ state->buffered_data = flb_sds_cat(state->buffered_data, data, len);
+ if (!state->buffered_data) {
+ return FLB_CSV_ALLOC_FAILED;
+ }
+ buf = state->buffered_data;
+ bufsize = flb_sds_len(state->buffered_data);
+ buffered = true;
+ }
+
+ switch (state->state) {
+ case FLB_CSV_STATE_STARTED_SIMPLE:
+ ret = parse_simple(state, buf, bufsize);
+ break;
+ case FLB_CSV_STATE_STARTED_DQUOTE:
+ case FLB_CSV_STATE_FOUND_DQUOTE:
+ ret = parse_quoted(state, buf, bufsize);
+ break;
+ default:
+ return FLB_CSV_INVALID_STATE;
+ }
+
+ if (ret) {
+ if (!buffered && ret == FLB_CSV_EOF) {
+ /* not finished, we need to save data in the buffer */
+ state->buffered_data = flb_sds_cat(state->buffered_data, data, len);
+ if (!state->buffered_data) {
+ return FLB_CSV_ALLOC_FAILED;
+ }
+ }
+ return ret;
+ }
+
+ if (state->field_callback) {
+ ret = invoke_field_callback(state, buf, bufsize);
+ if (ret) {
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+void flb_csv_init(struct flb_csv_state *state,
+ flb_csv_field_parsed_callback field_callback,
+ void *data)
+{
+ state->buffered_data = flb_sds_create("");
+ state->escape_buffer = flb_sds_create("");
+ state->field_callback = field_callback;
+ state->data = data;
+ state->field_count = 0;
+ reset_state(state);
+}
+
+int flb_csv_parse_record(struct flb_csv_state *state,
+ char **bufptr,
+ size_t *buflen,
+ size_t *field_count)
+{
+ char c;
+ int ret;
+ size_t initial_offset;
+ size_t advanced;
+
+ for (;;) {
+ if (!(*buflen)) {
+ return FLB_CSV_EOF;
+ }
+ c = **bufptr;
+ if (state->state == FLB_CSV_STATE_INITIAL) {
+ if (c == '\r') {
+ state->state = FLB_CSV_STATE_FOUND_CR;
+ (*bufptr)++;
+ (*buflen)--;
+ continue;
+ }
+ else if (c == '\n') {
+ /* accept single linefeed as record terminator, even
+ * though the spec says to look for \r\n */
+ (*bufptr)++;
+ (*buflen)--;
+ break;
+ }
+ else if (c == ',') {
+ (*bufptr)++;
+ (*buflen)--;
+ if (!state->field_parsed) {
+ state->field_count++;
+ if (state->field_callback) {
+ /* empty field, but we need to invoke the callback anyway */
+ state->field_callback(state->data, "", 0);
+ }
+ }
+ state->field_parsed = false;
+ continue;
+ }
+ }
+ else if (state->state == FLB_CSV_STATE_FOUND_CR) {
+ state->state = FLB_CSV_STATE_INITIAL;
+ if (c == '\n') {
+ /* if the character following \r is \n, consume it */
+ (*bufptr)++;
+ (*buflen)--;
+ }
+ /* in any case, accept lone \r as record separator */
+ break;
+ }
+
+ initial_offset = state->offset;
+
+ ret = parse_csv_field(state, *bufptr, *buflen);
+
+ advanced = state->offset - initial_offset;
+ *bufptr += advanced;
+ *buflen -= advanced;
+
+ if (ret) {
+ if (!state->field_callback) {
+ /* when no field callback is set, we shouldn't keep
+ * offset state between calls since no data will be buffered */
+ state->offset = 0;
+ }
+ return ret;
+ }
+
+ /* when a field is fully parsed, we can reset state */
+ reset_state(state);
+ /* set this flag so we can properly handle empty fields at the start
+ * of the loop */
+ state->field_parsed = true;
+ state->field_count++;
+ }
+
+ if (!state->field_parsed) {
+ state->field_count++;
+ if (state->field_callback) {
+ /* empty field, but we need to invoke the callback anyway */
+ state->field_callback(state->data, "", 0);
+ }
+ }
+ state->field_parsed = false;
+ *field_count = state->field_count;
+ state->field_count = 0;
+ return FLB_CSV_SUCCESS;
+}
+
+void flb_csv_destroy(struct flb_csv_state *state)
+{
+ flb_sds_destroy(state->buffered_data);
+ flb_sds_destroy(state->escape_buffer);
+}
diff --git a/fluent-bit/src/flb_custom.c b/fluent-bit/src/flb_custom.c
new file mode 100644
index 000000000..8279bb65d
--- /dev/null
+++ b/fluent-bit/src/flb_custom.c
@@ -0,0 +1,314 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_custom.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_env.h>
+#include <fluent-bit/flb_router.h>
+#include <fluent-bit/flb_mp.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_metrics.h>
+#include <fluent-bit/flb_utils.h>
+#include <chunkio/chunkio.h>
+
+static inline int instance_id(struct flb_config *config)
+{
+ struct flb_custom_instance *entry;
+
+ if (mk_list_size(&config->customs) == 0) {
+ return 0;
+ }
+
+ entry = mk_list_entry_last(&config->customs, struct flb_custom_instance,
+ _head);
+ return (entry->id + 1);
+}
+
+static inline int prop_key_check(const char *key, const char *kv, int k_len)
+{
+ int len;
+
+ len = strlen(key);
+ if (strncasecmp(key, kv, k_len) == 0 && len == k_len) {
+ return 0;
+ }
+
+ return -1;
+}
+
+int flb_custom_set_property(struct flb_custom_instance *ins,
+ const char *k, const char *v)
+{
+ int len;
+ int ret;
+ flb_sds_t tmp;
+ struct flb_kv *kv;
+
+ len = strlen(k);
+ tmp = flb_env_var_translate(ins->config->env, v);
+ if (!tmp) {
+ return -1;
+ }
+
+ if (prop_key_check("alias", k, len) == 0 && tmp) {
+ flb_utils_set_plugin_string_property("alias", &ins->alias, tmp);
+ }
+ else if (prop_key_check("log_level", k, len) == 0 && tmp) {
+ ret = flb_log_get_level_str(tmp);
+ flb_sds_destroy(tmp);
+ if (ret == -1) {
+ return -1;
+ }
+ ins->log_level = ret;
+ }
+ else {
+ /*
+ * Create the property, we don't pass the value since we will
+ * map it directly to avoid an extra memory allocation.
+ */
+ kv = flb_kv_item_create(&ins->properties, (char *) k, NULL);
+ if (!kv) {
+ if (tmp) {
+ flb_sds_destroy(tmp);
+ }
+ return -1;
+ }
+ kv->val = tmp;
+ }
+
+ return 0;
+}
+
+const char *flb_custom_get_property(const char *key,
+ struct flb_custom_instance *ins)
+{
+ return flb_kv_get_key_value(key, &ins->properties);
+}
+
+void flb_custom_instance_exit(struct flb_custom_instance *ins,
+ struct flb_config *config)
+{
+ struct flb_custom_plugin *p;
+
+ p = ins->p;
+ if (p->cb_exit && ins->context) {
+ p->cb_exit(ins->context, config);
+ }
+}
+
+/* Invoke exit call for the custom plugin */
+void flb_custom_exit(struct flb_config *config)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_custom_instance *ins;
+ struct flb_custom_plugin *p;
+
+ mk_list_foreach_safe(head, tmp, &config->customs) {
+ ins = mk_list_entry(head, struct flb_custom_instance, _head);
+ p = ins->p;
+ if (!p) {
+ continue;
+ }
+ flb_custom_instance_exit(ins, config);
+ flb_custom_instance_destroy(ins);
+ }
+}
+
+struct flb_custom_instance *flb_custom_new(struct flb_config *config,
+ const char *custom, void *data)
+{
+ int id;
+ struct mk_list *head;
+ struct flb_custom_plugin *plugin;
+ struct flb_custom_instance *instance = NULL;
+
+ if (!custom) {
+ return NULL;
+ }
+
+ mk_list_foreach(head, &config->custom_plugins) {
+ plugin = mk_list_entry(head, struct flb_custom_plugin, _head);
+ if (strcmp(plugin->name, custom) == 0) {
+ break;
+ }
+ plugin = NULL;
+ }
+
+ if (!plugin) {
+ return NULL;
+ }
+
+ instance = flb_calloc(1, sizeof(struct flb_custom_instance));
+ if (!instance) {
+ flb_errno();
+ return NULL;
+ }
+ instance->config = config;
+
+ /* Get an ID */
+ id = instance_id(config);
+
+ /* format name (with instance id) */
+ snprintf(instance->name, sizeof(instance->name) - 1,
+ "%s.%i", plugin->name, id);
+
+ instance->id = id;
+ instance->alias = NULL;
+ instance->p = plugin;
+ instance->data = data;
+ instance->log_level = -1;
+
+ mk_list_init(&instance->properties);
+ mk_list_add(&instance->_head, &config->customs);
+
+ return instance;
+}
+
+/* Return an instance name or alias */
+const char *flb_custom_name(struct flb_custom_instance *ins)
+{
+ if (ins->alias) {
+ return ins->alias;
+ }
+
+ return ins->name;
+}
+
+int flb_custom_plugin_property_check(struct flb_custom_instance *ins,
+ struct flb_config *config)
+{
+ int ret = 0;
+ struct mk_list *config_map;
+ struct flb_custom_plugin *p = ins->p;
+
+ if (p->config_map) {
+ /*
+ * Create a dynamic version of the configmap that will be used by the specific
+ * instance in question.
+ */
+ config_map = flb_config_map_create(config, p->config_map);
+ if (!config_map) {
+ flb_error("[custom] error loading config map for '%s' plugin",
+ p->name);
+ return -1;
+ }
+ ins->config_map = config_map;
+
+ /* Validate incoming properties against config map */
+ ret = flb_config_map_properties_check(ins->p->name,
+ &ins->properties, ins->config_map);
+ if (ret == -1) {
+ if (config->program_name) {
+ flb_helper("try the command: %s -F %s -h\n",
+ config->program_name, ins->p->name);
+ }
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* Initialize all custom plugins */
+int flb_custom_init_all(struct flb_config *config)
+{
+ int ret;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_custom_plugin *p;
+ struct flb_custom_instance *ins;
+
+ /* Iterate all active custom instance plugins */
+ mk_list_foreach_safe(head, tmp, &config->customs) {
+ ins = mk_list_entry(head, struct flb_custom_instance, _head);
+
+ if (ins->log_level == -1) {
+ ins->log_level = config->log->level;
+ }
+
+ p = ins->p;
+
+#ifdef FLB_HAVE_METRICS
+ /* CMetrics */
+ ins->cmt = cmt_create();
+ if (!ins->cmt) {
+ flb_error("[custom] could not create cmetrics context: %s",
+ flb_custom_name(ins));
+ return -1;
+ }
+#endif
+
+ /*
+ * Before to call the initialization callback, make sure that the received
+ * configuration parameters are valid if the plugin is registering a config map.
+ */
+ if (flb_custom_plugin_property_check(ins, config) == -1) {
+ flb_custom_instance_destroy(ins);
+ return -1;
+ }
+
+ /* Initialize the input */
+ if (p->cb_init) {
+ ret = p->cb_init(ins, config, ins->data);
+ if (ret != 0) {
+ flb_error("Failed initialize custom %s", ins->name);
+ flb_custom_instance_destroy(ins);
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+void flb_custom_instance_destroy(struct flb_custom_instance *ins)
+{
+ if (!ins) {
+ return;
+ }
+
+ /* destroy config map */
+ if (ins->config_map) {
+ flb_config_map_destroy(ins->config_map);
+ }
+
+ /* release properties */
+ flb_kv_release(&ins->properties);
+
+ if (ins->alias) {
+ flb_sds_destroy(ins->alias);
+ }
+
+#ifdef FLB_HAVE_METRICS
+ if (ins->cmt) {
+ cmt_destroy(ins->cmt);
+ }
+#endif
+
+ mk_list_del(&ins->_head);
+ flb_free(ins);
+}
+
+void flb_custom_set_context(struct flb_custom_instance *ins, void *context)
+{
+ ins->context = context;
+}
diff --git a/fluent-bit/src/flb_dlfcn_win32.c b/fluent-bit/src/flb_dlfcn_win32.c
new file mode 100644
index 000000000..1ca398f51
--- /dev/null
+++ b/fluent-bit/src/flb_dlfcn_win32.c
@@ -0,0 +1,101 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_compat.h>
+
+static CHAR dlerrorbuf[512];
+static BOOL has_error_message = FALSE;
+
+static void store_error(void)
+{
+ DWORD err = GetLastError();
+ if (err == NO_ERROR) {
+ return;
+ }
+
+ if (!FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ err,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ dlerrorbuf,
+ _countof(dlerrorbuf), NULL))
+ dlerrorbuf[0] = '\0';
+
+ has_error_message = TRUE;
+}
+
+__declspec(noinline)
+void *dlopen(const char *filename, int _flag)
+{
+ HMODULE handle;
+
+ handle = LoadLibrary(filename);
+ if (handle == NULL) {
+ store_error();
+ return NULL;
+ }
+ return (void *)handle;
+}
+
+char *dlerror(void)
+{
+ char *errorptr = dlerrorbuf;
+
+ /* POSIX requests that the second consective dlerror() calling should
+ * be return NULL.*/
+ if (!has_error_message)
+ {
+ return NULL;
+ }
+
+ has_error_message = FALSE;
+
+ return errorptr;
+}
+
+__declspec(noinline)
+void *dlsym(void *handle, const char *name)
+{
+ FARPROC *symbol;
+ symbol = NULL;
+
+ symbol = GetProcAddress((HMODULE) handle, name);
+ if (symbol == NULL) {
+ store_error();
+ return NULL;
+ }
+
+ return (void *)symbol;
+}
+
+int dlclose(void *handle)
+{
+ BOOL result;
+
+ result = FreeLibrary((HMODULE) handle);
+ if (!result)
+ store_error();
+
+ /* dlcose(3) returns 0 on success, and nonzero on error. */
+ /* FreeLibrary returns nonzero on success, and 0 on error. */
+ /* ref:
+ * https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-freelibrary */
+ return !result;
+}
diff --git a/fluent-bit/src/flb_downstream.c b/fluent-bit/src/flb_downstream.c
new file mode 100644
index 000000000..e4b7e19ef
--- /dev/null
+++ b/fluent-bit/src/flb_downstream.c
@@ -0,0 +1,514 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <monkey/mk_core.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_io.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_slist.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_engine.h>
+#include <fluent-bit/tls/flb_tls.h>
+#include <fluent-bit/flb_downstream.h>
+#include <fluent-bit/flb_connection.h>
+#include <fluent-bit/flb_config_map.h>
+#include <fluent-bit/flb_thread_storage.h>
+
+/* Config map for Downstream networking setup */
+struct flb_config_map downstream_net[] = {
+ {
+ FLB_CONFIG_MAP_TIME, "net.io_timeout", "0s",
+ 0, FLB_TRUE, offsetof(struct flb_net_setup, io_timeout),
+ "Set maximum time a connection can stay idle"
+ },
+
+ {
+ FLB_CONFIG_MAP_TIME, "net.accept_timeout", "10s",
+ 0, FLB_TRUE, offsetof(struct flb_net_setup, accept_timeout),
+ "Set maximum time allowed to establish an incoming connection, this time "
+ "includes the TLS handshake"
+ },
+
+ {
+ FLB_CONFIG_MAP_BOOL, "net.accept_timeout_log_error", "true",
+ 0, FLB_TRUE, offsetof(struct flb_net_setup, accept_timeout_log_error),
+ "On client accept timeout, specify if it should log an error. When "
+ "disabled, the timeout is logged as a debug message"
+ },
+
+ /* EOF */
+ {0}
+};
+
+/* Enable thread-safe mode for downstream connection */
+void flb_downstream_thread_safe(struct flb_downstream *stream)
+{
+ flb_stream_enable_thread_safety(&stream->base);
+}
+
+struct mk_list *flb_downstream_get_config_map(struct flb_config *config)
+{
+ return flb_config_map_create(config, downstream_net);
+}
+
+/* Initialize any downstream environment context */
+void flb_downstream_init()
+{
+ /* There's nothing to do here yet */
+}
+
+int flb_downstream_setup(struct flb_downstream *stream,
+ int transport, int flags,
+ const char *host,
+ unsigned short int port,
+ struct flb_tls *tls,
+ struct flb_config *config,
+ struct flb_net_setup *net_setup)
+{
+ char port_string[8];
+
+ flb_stream_setup(&stream->base,
+ FLB_DOWNSTREAM,
+ transport,
+ flags,
+ tls,
+ config,
+ net_setup);
+
+ stream->server_fd = FLB_INVALID_SOCKET;
+ stream->host = flb_strdup(host);
+ stream->port = port;
+
+ if (stream->host == NULL) {
+ return -1;
+ }
+
+ mk_list_init(&stream->busy_queue);
+ mk_list_init(&stream->destroy_queue);
+
+ snprintf(port_string, sizeof(port_string), "%u", port);
+
+ if (transport == FLB_TRANSPORT_TCP) {
+ stream->server_fd = flb_net_server(port_string, host);
+ }
+ else if (transport == FLB_TRANSPORT_UDP) {
+ stream->server_fd = flb_net_server_udp(port_string, host);
+ }
+ else if (transport == FLB_TRANSPORT_UNIX_STREAM) {
+ stream->server_fd = flb_net_server_unix(host,
+ FLB_TRUE,
+ FLB_NETWORK_DEFAULT_BACKLOG_SIZE);
+ }
+ else if (transport == FLB_TRANSPORT_UNIX_DGRAM) {
+ stream->server_fd = flb_net_server_unix(host,
+ FLB_FALSE,
+ FLB_NETWORK_DEFAULT_BACKLOG_SIZE);
+ }
+
+ if (stream->server_fd != -1) {
+ flb_debug("[downstream] listening on %s:%s", host, port_string);
+ }
+ else {
+ flb_error("[downstream] could not bind address %s:%s. Aborting",
+ host, port_string);
+
+ return -2;
+ }
+
+ mk_list_add(&stream->base._head, &config->downstreams);
+
+ return 0;
+}
+
+/* Creates a new downstream context */
+struct flb_downstream *flb_downstream_create(int transport, int flags,
+ const char *host,
+ unsigned short int port,
+ struct flb_tls *tls,
+ struct flb_config *config,
+ struct flb_net_setup *net_setup)
+{
+ struct flb_downstream *stream;
+ int result;
+
+ stream = flb_calloc(1, sizeof(struct flb_downstream));
+
+ if (stream == NULL) {
+ flb_errno();
+ }
+ else {
+ stream->base.dynamically_allocated = FLB_TRUE;
+
+ result = flb_downstream_setup(stream,
+ transport, flags,
+ host, port,
+ tls,
+ config,
+ net_setup);
+
+ if (result != 0) {
+ flb_downstream_destroy(stream);
+
+ stream = NULL;
+ }
+ }
+
+ return stream;
+}
+
+/*
+ * This function moves the 'downstream connection' into the queue to be
+ * destroyed. Note that the caller is responsible to validate and check
+ * required mutex if this is being used in multi-worker mode.
+ */
+static int prepare_destroy_conn(struct flb_connection *connection)
+{
+ flb_trace("[downstream] destroy connection #%i to %s",
+ connection->fd, flb_connection_get_remote_address(connection));
+
+ if (MK_EVENT_IS_REGISTERED((&connection->event))) {
+ mk_event_del(connection->evl, &connection->event);
+ }
+
+ /* This should be != -1 to cover those use cases where stdin, stdout
+ * and stderr are closed.
+ */
+
+ if (connection->fd != FLB_INVALID_SOCKET) {
+ flb_socket_close(connection->fd);
+
+ connection->fd = FLB_INVALID_SOCKET;
+ connection->event.fd = FLB_INVALID_SOCKET;
+ }
+
+ /* remove connection from the queue */
+ mk_list_del(&connection->_head);
+
+ /* Add node to destroy queue */
+ mk_list_add(&connection->_head, &connection->downstream->destroy_queue);
+
+ /*
+ * note: the connection context is destroyed by the engine once all events
+ * have been processed.
+ */
+ return 0;
+}
+
+/* 'safe' version of prepare_destroy_conn. It set locks if necessary */
+static inline int prepare_destroy_conn_safe(struct flb_connection *connection)
+{
+ int result;
+
+ /* This used to not wait for the lock in thread safe mode but it makes
+ * no sense so I'm changing it (08/28/22) leo
+ */
+
+ flb_stream_acquire_lock(connection->stream, FLB_TRUE);
+
+ result = prepare_destroy_conn(connection);
+
+ flb_stream_release_lock(connection->stream);
+
+ return result;
+}
+
+static int destroy_conn(struct flb_connection *connection)
+{
+ /* Delay the destruction of busy connections */
+ if (connection->busy_flag) {
+ return 0;
+ }
+
+ if (connection->tls_session != NULL) {
+ flb_tls_session_destroy(connection->tls_session);
+ }
+
+ mk_list_del(&connection->_head);
+
+ flb_connection_destroy(connection);
+
+ return 0;
+}
+
+struct flb_connection *flb_downstream_conn_get(struct flb_downstream *stream)
+{
+ flb_sockfd_t connection_fd;
+ struct flb_connection *connection;
+ int transport;
+ struct flb_coro *coroutine;
+ int result;
+
+ transport = stream->base.transport;
+
+ if (transport == FLB_TRANSPORT_UDP ||
+ transport == FLB_TRANSPORT_UNIX_DGRAM ) {
+ if (stream->dgram_connection != NULL) {
+ return stream->dgram_connection;
+ }
+
+ connection_fd = stream->server_fd;
+ }
+ else {
+ connection_fd = FLB_INVALID_SOCKET;
+ }
+
+ if (flb_downstream_is_async(stream)) {
+ coroutine = flb_coro_get();
+ }
+ else {
+ coroutine = NULL;
+ }
+
+ connection = flb_connection_create(connection_fd,
+ FLB_DOWNSTREAM_CONNECTION,
+ (void *) stream,
+ flb_engine_evl_get(),
+ coroutine);
+
+ if (connection == NULL) {
+ return NULL;
+ }
+
+ connection->busy_flag = FLB_TRUE;
+
+ flb_stream_acquire_lock(&stream->base, FLB_TRUE);
+
+ /* Link new connection to the busy queue */
+ mk_list_add(&connection->_head, &stream->busy_queue);
+
+ flb_stream_release_lock(&stream->base);
+
+ if (transport != FLB_TRANSPORT_UDP &&
+ transport != FLB_TRANSPORT_UNIX_DGRAM ) {
+ flb_connection_reset_connection_timeout(connection);
+
+ result = flb_io_net_accept(connection, coroutine);
+
+ if (result != 0) {
+ flb_connection_reset_connection_timeout(connection);
+
+ flb_debug("[downstream] connection #%i failed",
+ connection->fd);
+
+ prepare_destroy_conn_safe(connection);
+
+ connection->busy_flag = FLB_FALSE;
+
+ return NULL;
+ }
+
+ flb_connection_unset_connection_timeout(connection);
+ }
+
+ connection->busy_flag = FLB_FALSE;
+
+ flb_connection_reset_io_timeout(connection);
+
+ if (transport == FLB_TRANSPORT_UDP ||
+ transport == FLB_TRANSPORT_UNIX_DGRAM) {
+ if (stream->dgram_connection == NULL) {
+ stream->dgram_connection = connection;
+ }
+ }
+
+ return connection;
+}
+
+void flb_downstream_destroy(struct flb_downstream *stream)
+{
+ struct flb_connection *connection;
+ struct mk_list *head;
+ struct mk_list *tmp;
+
+ if (stream != NULL) {
+ mk_list_foreach_safe(head, tmp, &stream->busy_queue) {
+ connection = mk_list_entry(head, struct flb_connection, _head);
+
+ prepare_destroy_conn(connection);
+ }
+
+ mk_list_foreach_safe(head, tmp, &stream->destroy_queue) {
+ connection = mk_list_entry(head, struct flb_connection, _head);
+
+ destroy_conn(connection);
+ }
+
+ /* If the simulated UDP connection reference is set then
+ * it means that connection was already cleaned up by the
+ * preceding code which means server_fd holds a socket
+ * reference that has already been closed and we need to
+ * honor that.
+ */
+
+ if (stream->dgram_connection != NULL) {
+ stream->dgram_connection = NULL;
+ stream->server_fd = FLB_INVALID_SOCKET;
+ }
+
+ if (stream->host != NULL) {
+ flb_free(stream->host);
+ }
+
+ if (stream->server_fd != FLB_INVALID_SOCKET) {
+ flb_socket_close(stream->server_fd);
+ }
+
+ if (mk_list_entry_orphan(&stream->base._head) == 0) {
+ mk_list_del(&stream->base._head);
+ }
+
+ if (stream->base.dynamically_allocated) {
+ flb_free(stream);
+ }
+ }
+}
+
+int flb_downstream_conn_release(struct flb_connection *connection)
+{
+ return prepare_destroy_conn_safe(connection);
+}
+
+int flb_downstream_conn_timeouts(struct mk_list *list)
+{
+ int elapsed_time;
+ struct flb_connection *connection;
+ const char *reason;
+ struct flb_downstream *stream;
+ struct mk_list *s_head;
+ struct mk_list *head;
+ int drop;
+ int inject;
+ struct mk_list *tmp;
+ time_t now;
+
+ now = time(NULL);
+
+ /* Iterate all downstream contexts */
+ mk_list_foreach(head, list) {
+ stream = mk_list_entry(head, struct flb_downstream, base._head);
+
+ if (stream->base.transport == FLB_TRANSPORT_UDP) {
+ continue;
+ }
+
+ flb_stream_acquire_lock(&stream->base, FLB_TRUE);
+
+ /* Iterate every busy connection */
+ mk_list_foreach_safe(s_head, tmp, &stream->busy_queue) {
+ connection = mk_list_entry(s_head, struct flb_connection, _head);
+
+ drop = FLB_FALSE;
+
+ /* Connect timeouts */
+ if (connection->net->connect_timeout > 0 &&
+ connection->ts_connect_timeout > 0 &&
+ connection->ts_connect_timeout <= now) {
+ drop = FLB_TRUE;
+ reason = "connection timeout";
+ elapsed_time = connection->net->accept_timeout;
+ }
+ else if (connection->net->io_timeout > 0 &&
+ connection->ts_io_timeout > 0 &&
+ connection->ts_io_timeout <= now) {
+ drop = FLB_TRUE;
+ reason = "IO timeout";
+ elapsed_time = connection->net->io_timeout;
+ }
+
+ if (drop) {
+ if (!flb_downstream_is_shutting_down(stream)) {
+ if (connection->net->accept_timeout_log_error) {
+ flb_error("[downstream] connection #%i from %s timed "
+ "out after %i seconds (%s)",
+ connection->fd,
+ connection->user_friendly_remote_host,
+ elapsed_time,
+ reason);
+ }
+ else {
+ flb_debug("[downstream] connection #%i from %s timed "
+ "out after %i seconds (%s)",
+ connection->fd,
+ connection->user_friendly_remote_host,
+ elapsed_time,
+ reason);
+ }
+ }
+
+ inject = FLB_FALSE;
+ if (connection->event.status != MK_EVENT_NONE) {
+ inject = FLB_TRUE;
+ }
+ connection->net_error = ETIMEDOUT;
+ prepare_destroy_conn(connection);
+ if (inject == FLB_TRUE) {
+ mk_event_inject(connection->evl,
+ &connection->event,
+ connection->event.mask,
+ FLB_TRUE);
+ }
+ }
+ }
+
+ flb_stream_release_lock(&stream->base);
+ }
+
+ return 0;
+}
+
+int flb_downstream_conn_pending_destroy(struct flb_downstream *stream)
+{
+ struct flb_connection *connection;
+ struct mk_list *head;
+ struct mk_list *tmp;
+
+ flb_stream_acquire_lock(&stream->base, FLB_TRUE);
+
+ mk_list_foreach_safe(head, tmp, &stream->destroy_queue) {
+ connection = mk_list_entry(head, struct flb_connection, _head);
+
+ destroy_conn(connection);
+ }
+
+ flb_stream_release_lock(&stream->base);
+
+ return 0;
+}
+
+int flb_downstream_conn_pending_destroy_list(struct mk_list *list)
+{
+ struct flb_downstream *stream;
+ struct mk_list *head;
+
+ /* Iterate all downstream contexts */
+ mk_list_foreach(head, list) {
+ stream = mk_list_entry(head, struct flb_downstream, base._head);
+
+ flb_downstream_conn_pending_destroy(stream);
+ }
+
+ return 0;
+}
+
+int flb_downstream_is_async(struct flb_downstream *stream)
+{
+ return flb_stream_is_async(&stream->base);
+}
diff --git a/fluent-bit/src/flb_dump.c b/fluent-bit/src/flb_dump.c
new file mode 100644
index 000000000..fe1d93a59
--- /dev/null
+++ b/fluent-bit/src/flb_dump.c
@@ -0,0 +1,260 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_input_chunk.h>
+#include <fluent-bit/flb_task.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_storage.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_event.h>
+
+#ifdef FLB_DUMP_STACKTRACE
+#include <fluent-bit/flb_stacktrace.h>
+#endif
+
+#include <stdio.h>
+#include <time.h>
+
+/*
+ * Input Chunks
+ * ============
+ * Every input plugin instance has it own Chunk I/O stream. The stream is used to
+ * associate data from the specific origin.
+ *
+ * This dump prints out information about current status of chunks registered by
+ * the input plugin interface and resources usage.
+ */
+static void dump_input_chunks(struct flb_config *ctx)
+{
+ /* general */
+ int ret;
+ ssize_t size;
+
+ /* tasks */
+ int task_new;
+ int task_running;
+
+ /* chunks */
+ int up;
+ int down;
+ int busy;
+ int busy_size_err;
+ ssize_t busy_size;
+ char tmp[32];
+
+ struct mk_list *head;
+ struct mk_list *h_chunks;
+ struct mk_list *h_task;
+ struct flb_input_instance *i;
+ struct flb_input_chunk *ic;
+ struct flb_task *task;
+
+ fprintf(stderr, "\n===== Input =====\n");
+
+ mk_list_foreach(head, &ctx->inputs) {
+ i = mk_list_entry(head, struct flb_input_instance, _head);
+ fprintf(stderr, "%s (%s)\n", flb_input_name(i), i->p->name);
+
+ fprintf(stderr, "│\n");
+ fprintf(stderr, "├─ status\n");
+
+ /* Overlimit checks */
+ ret = FLB_FALSE;
+ if (i->mem_buf_limit > 0) {
+ if (i->mem_chunks_size >= i->mem_buf_limit) {
+ ret = FLB_TRUE;
+ }
+ }
+ fprintf(stderr, "│ └─ overlimit : %s\n",
+ ret ? "yes" : "no");
+
+ /* Current memory size used based on last ingestion */
+ flb_utils_bytes_to_human_readable_size(i->mem_chunks_size,
+ tmp, sizeof(tmp) - 1);
+ fprintf(stderr, "│ ├─ mem size : %s (%lu bytes)\n",
+ tmp, i->mem_chunks_size);
+
+ /* Mem buf limit set */
+ flb_utils_bytes_to_human_readable_size(i->mem_buf_limit,
+ tmp, sizeof(tmp) - 1);
+ fprintf(stderr, "│ └─ mem limit : %s (%lu bytes)\n",
+ tmp, i->mem_buf_limit);
+
+ /*
+ * Tasks
+ * =====
+ * Upon flush time, the engine look for 'chunks' ready to be flushed.
+ * For each one, it creates a Task, this task can be routed and
+ * referenced by different output destinations.
+ *
+ * For short: every task is a chunk. But it's a different structure
+ * handled by the engine to coordinate the flush process.
+ */
+ fprintf(stderr, "│\n");
+ fprintf(stderr, "├─ tasks\n");
+ fprintf(stderr, "│ ├─ total tasks : %i\n", mk_list_size(&i->tasks));
+
+ size = 0;
+ task_new = 0;
+ task_running = 0;
+ /* Iterate tasks and print a summary */
+ mk_list_foreach(h_task, &i->tasks) {
+ task = mk_list_entry(h_task, struct flb_task, _head);
+ size += task->event_chunk->size;
+ if (task->status == FLB_TASK_NEW) {
+ task_new++;
+ }
+ else if (task->status == FLB_TASK_RUNNING) {
+ task_running++;
+ }
+ }
+
+ flb_utils_bytes_to_human_readable_size(size, tmp, sizeof(tmp) - 1);
+
+ fprintf(stderr, "│ ├─ new : %i\n", task_new);
+ fprintf(stderr, "│ ├─ running : %i\n", task_running);
+ fprintf(stderr, "│ └─ size : %s (%lu bytes)\n", tmp, size);
+
+ /*
+ * Chunks
+ * ======
+ * Input plugins ingest record into a 'chunk'. If the storage layer type
+ * for the instance is memory, all chunks are considered 'up' (meaning:
+ * up in memory), for filesystem based chunks they can be 'up' or 'down'.
+ *
+ * We avoid to have all of them 'up' at the same time since this can
+ * lead to a high memory consumption. When filesystem mode is used, some
+ * of them are 'down' and only get 'up' when they are going to be
+ * processed.
+ */
+ fprintf(stderr, "│\n");
+ fprintf(stderr, "└─ chunks\n");
+
+ /* Number of chunks registered */
+ fprintf(stderr, " └─ total chunks : %i\n", mk_list_size(&i->chunks));
+
+ /* Busy chunks
+ * -----------
+ * Chunks marked as 'busy' are 'locked' since they are in a 'flush' state.
+ * No more data can be appended to a busy chunk.
+ */
+ busy = 0;
+ busy_size = 0;
+ busy_size_err = 0;
+
+ /* up/down */
+ up = 0;
+ down = 0;
+
+ /* Iterate chunks for the input instance in question */
+ mk_list_foreach(h_chunks, &i->chunks) {
+ ic = mk_list_entry(h_chunks, struct flb_input_chunk, _head);
+ if (ic->busy == FLB_TRUE) {
+ busy++;
+ size = cio_chunk_get_content_size(ic->chunk);
+ if (size >= 0) {
+ busy_size += size;
+ }
+ else {
+ busy_size_err++;
+ }
+ }
+
+ if (cio_chunk_is_up(ic->chunk) == CIO_TRUE) {
+ up++;
+ }
+ else {
+ down++;
+ }
+ }
+
+ fprintf(stderr, " ├─ up chunks : %i\n", up);
+ fprintf(stderr, " ├─ down chunks: %i\n", down);
+ flb_utils_bytes_to_human_readable_size(busy_size, tmp, sizeof(tmp) - 1);
+
+ fprintf(stderr, " └─ busy chunks: %i\n", busy);
+ fprintf(stderr, " ├─ size : %s (%lu bytes)\n", tmp, busy_size);
+ fprintf(stderr, " └─ size err: %i\n", busy_size_err);
+ fprintf(stderr, "\n");
+ }
+}
+
+/*
+ * Storage
+ * =======
+ * Dump Chunk I/O statistics, basic counters
+ */
+static void dump_storage(struct flb_config *ctx)
+{
+ struct cio_stats storage_st;
+
+ fprintf(stderr, "\n===== Storage Layer =====\n");
+ cio_stats_get(ctx->cio, &storage_st);
+
+ fprintf(stderr, "total chunks : %i\n", storage_st.chunks_total);
+ fprintf(stderr, "├─ mem chunks : %i\n", storage_st.chunks_mem);
+ fprintf(stderr, "└─ fs chunks : %i\n", storage_st.chunks_fs);
+ fprintf(stderr, " ├─ up : %i\n", storage_st.chunks_fs_up);
+ fprintf(stderr, " └─ down : %i\n", storage_st.chunks_fs_down);
+}
+
+void flb_dump(struct flb_config *ctx)
+{
+ time_t now;
+ struct tm *current;
+
+ now = time(NULL);
+ current = localtime(&now);
+
+ fprintf(stderr,
+ "[%i/%02i/%02i %02i:%02i:%02i] Fluent Bit Dump\n",
+ current->tm_year + 1900,
+ current->tm_mon + 1,
+ current->tm_mday,
+ current->tm_hour,
+ current->tm_min,
+ current->tm_sec);
+
+ /* Stacktrace */
+#ifdef FLB_DUMP_STACKTRACE
+ /*
+ * Sorry, I had to disable the stacktrace as part of the dump
+ * since if backtrace_full() is called while Fluent Bit is
+ * inside a co-routine (output flush), it might crash.
+ *
+ * If we are in a co-routine likely we need a different libbacktrace
+ * context, but it's just a guess, not tested.
+ */
+ //fprintf(stderr, "\n===== Stacktrace =====\n");
+ //flb_stacktrace_print();
+#endif
+
+ /* Input Plugins + Storage */
+ dump_input_chunks(ctx);
+
+ /* Storage Layer */
+ dump_storage(ctx);
+
+ /* Make sure to flush the stdout buffer in case output
+ * has been redirected to a file
+ */
+ fflush(stderr);
+}
diff --git a/fluent-bit/src/flb_engine.c b/fluent-bit/src/flb_engine.c
new file mode 100644
index 000000000..78be8d5ec
--- /dev/null
+++ b/fluent-bit/src/flb_engine.c
@@ -0,0 +1,1124 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <monkey/mk_core.h>
+#include <fluent-bit/flb_bucket_queue.h>
+#include <fluent-bit/flb_event_loop.h>
+#include <fluent-bit/flb_time.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_bits.h>
+
+#include <fluent-bit/flb_macros.h>
+#include <fluent-bit/flb_pipe.h>
+#include <fluent-bit/flb_custom.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_error.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_engine.h>
+#include <fluent-bit/flb_engine_dispatch.h>
+#include <fluent-bit/flb_network.h>
+#include <fluent-bit/flb_task.h>
+#include <fluent-bit/flb_router.h>
+#include <fluent-bit/flb_http_server.h>
+#include <fluent-bit/flb_scheduler.h>
+#include <fluent-bit/flb_parser.h>
+#include <fluent-bit/flb_sosreport.h>
+#include <fluent-bit/flb_storage.h>
+#include <fluent-bit/flb_http_server.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_metrics.h>
+#include <fluent-bit/flb_version.h>
+#include <fluent-bit/flb_upstream.h>
+#include <fluent-bit/flb_downstream.h>
+#include <fluent-bit/flb_ring_buffer.h>
+
+#ifdef FLB_HAVE_METRICS
+#include <fluent-bit/flb_metrics_exporter.h>
+#endif
+
+#ifdef FLB_HAVE_STREAM_PROCESSOR
+#include <fluent-bit/stream_processor/flb_sp.h>
+#endif
+
+#ifdef FLB_HAVE_AWS_ERROR_REPORTER
+#include <fluent-bit/aws/flb_aws_error_reporter.h>
+
+extern struct flb_aws_error_reporter *error_reporter;
+#endif
+
+#include <ctraces/ctr_version.h>
+
+static pthread_once_t local_thread_engine_evl_init = PTHREAD_ONCE_INIT;
+FLB_TLS_DEFINE(struct mk_event_loop, flb_engine_evl);
+
+static void flb_engine_evl_init_private()
+{
+ FLB_TLS_INIT(flb_engine_evl);
+}
+
+void flb_engine_evl_init()
+{
+ pthread_once(&local_thread_engine_evl_init, flb_engine_evl_init_private);
+}
+
+struct mk_event_loop *flb_engine_evl_get()
+{
+ struct mk_event_loop *evl;
+
+ evl = FLB_TLS_GET(flb_engine_evl);
+ return evl;
+}
+
+void flb_engine_evl_set(struct mk_event_loop *evl)
+{
+ FLB_TLS_SET(flb_engine_evl, evl);
+}
+
+int flb_engine_destroy_tasks(struct mk_list *tasks)
+{
+ int c = 0;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_task *task;
+
+ mk_list_foreach_safe(head, tmp, tasks) {
+ task = mk_list_entry(head, struct flb_task, _head);
+ flb_task_destroy(task, FLB_FALSE);
+ c++;
+ }
+
+ return c;
+}
+
+void flb_engine_reschedule_retries(struct flb_config *config)
+{
+ int ret;
+ struct mk_list *head;
+ struct mk_list *t_head;
+ struct mk_list *rt_head;
+ struct mk_list *tmp_task;
+ struct mk_list *tmp_retry_task;
+ struct flb_task *task;
+ struct flb_input_instance *ins;
+ struct flb_task_retry *retry;
+
+ /* Invalidate and reschedule all retry tasks to be retried immediately */
+ mk_list_foreach(head, &config->inputs) {
+ ins = mk_list_entry(head, struct flb_input_instance, _head);
+ mk_list_foreach_safe(t_head, tmp_task, &ins->tasks) {
+ task = mk_list_entry(t_head, struct flb_task, _head);
+ mk_list_foreach_safe(rt_head, tmp_retry_task, &task->retries) {
+ retry = mk_list_entry(rt_head, struct flb_task_retry, _head);
+ flb_sched_request_invalidate(config, retry);
+ ret = flb_sched_retry_now(config, retry);
+ if (ret == -1) {
+ /* Can't do much here, just continue on */
+ flb_warn("[engine] failed to immediately re-schedule retry=%p "
+ "for task %i. Err: %d", retry, task->id, flb_errno());
+ } else {
+ flb_debug("[engine] re-scheduled retry=%p for task %i",
+ retry, task->id);
+ }
+ }
+ }
+ }
+}
+
+int flb_engine_flush(struct flb_config *config,
+ struct flb_input_plugin *in_force)
+{
+ struct flb_input_instance *in;
+ struct flb_input_plugin *p;
+ struct mk_list *head;
+
+ mk_list_foreach(head, &config->inputs) {
+ in = mk_list_entry(head, struct flb_input_instance, _head);
+ p = in->p;
+
+ if (in_force != NULL && p != in_force) {
+ continue;
+ }
+ flb_engine_dispatch(0, in, config);
+ }
+
+ return 0;
+}
+
+/* Cleanup function that runs every 1.5 second */
+static void cb_engine_sched_timer(struct flb_config *ctx, void *data)
+{
+ (void) data;
+
+ /* Upstream timeout handling */
+ flb_upstream_conn_timeouts(&ctx->upstreams);
+
+ /* Downstream timeout handling */
+ flb_downstream_conn_timeouts(&ctx->downstreams);
+}
+
+static inline int handle_input_event(flb_pipefd_t fd, uint64_t ts,
+ struct flb_config *config)
+{
+ int bytes;
+ uint32_t type;
+ uint32_t ins_id;
+ uint64_t val;
+
+ bytes = flb_pipe_r(fd, &val, sizeof(val));
+ if (bytes == -1) {
+ flb_errno();
+ return -1;
+ }
+
+ /* Get type and key */
+ type = FLB_BITS_U64_HIGH(val);
+ ins_id = FLB_BITS_U64_LOW(val);
+
+ /* At the moment we only support events coming from an input coroutine */
+ if (type != FLB_ENGINE_IN_CORO) {
+ flb_error("[engine] invalid event type %i for input handler",
+ type);
+ return -1;
+ }
+
+ flb_input_coro_finished(config, ins_id);
+ return 0;
+}
+
+static inline int handle_output_event(uint64_t ts,
+ struct flb_config *config,
+ uint64_t val)
+{
+ int ret;
+ int task_id;
+ int out_id;
+ int retries;
+ int retry_seconds;
+ uint32_t type;
+ uint32_t key;
+ char *name;
+ struct flb_task *task;
+ struct flb_task_retry *retry;
+ struct flb_output_instance *ins;
+
+ /* Get type and key */
+ type = FLB_BITS_U64_HIGH(val);
+ key = FLB_BITS_U64_LOW(val);
+
+ if (type != FLB_ENGINE_TASK) {
+ flb_error("[engine] invalid event type %i for output handler",
+ type);
+ return -1;
+ }
+
+ /*
+ * The notion of ENGINE_TASK is associated to outputs. All thread
+ * references below belongs to flb_output_coro's.
+ */
+ ret = FLB_TASK_RET(key);
+ task_id = FLB_TASK_ID(key);
+ out_id = FLB_TASK_OUT(key);
+
+#ifdef FLB_HAVE_TRACE
+ char *trace_st = NULL;
+
+ if (ret == FLB_OK) {
+ trace_st = "OK";
+ }
+ else if (ret == FLB_ERROR) {
+ trace_st = "ERROR";
+ }
+ else if (ret == FLB_RETRY) {
+ trace_st = "RETRY";
+ }
+
+ flb_trace("%s[engine] [task event]%s task_id=%i out_id=%i return=%s",
+ ANSI_YELLOW, ANSI_RESET,
+ task_id, out_id, trace_st);
+#endif
+
+ task = config->tasks_map[task_id].task;
+ ins = flb_output_get_instance(config, out_id);
+ if (flb_output_is_threaded(ins) == FLB_FALSE) {
+ flb_output_flush_finished(config, out_id);
+ }
+ name = (char *) flb_output_name(ins);
+
+ /* If we are in synchronous mode, flush the next waiting task */
+ if (ins->flags & FLB_OUTPUT_SYNCHRONOUS) {
+ if (ret == FLB_OK || ret == FLB_RETRY || ret == FLB_ERROR) {
+ flb_output_task_singleplex_flush_next(ins->singleplex_queue);
+ }
+ }
+
+ /* A task has finished, delete it */
+ if (ret == FLB_OK) {
+ /* cmetrics */
+ cmt_counter_add(ins->cmt_proc_records, ts, task->event_chunk->total_events,
+ 1, (char *[]) {name});
+
+ cmt_counter_add(ins->cmt_proc_bytes, ts, task->event_chunk->size,
+ 1, (char *[]) {name});
+
+ /* [OLD API] Update metrics */
+#ifdef FLB_HAVE_METRICS
+ if (ins->metrics) {
+ flb_metrics_sum(FLB_METRIC_OUT_OK_RECORDS,
+ task->event_chunk->total_events, ins->metrics);
+ flb_metrics_sum(FLB_METRIC_OUT_OK_BYTES,
+ task->event_chunk->size, ins->metrics);
+ }
+#endif
+ /* Inform the user if a 'retry' succedeed */
+ if (mk_list_size(&task->retries) > 0) {
+ retries = flb_task_retry_count(task, ins);
+ if (retries > 0) {
+ flb_info("[engine] flush chunk '%s' succeeded at retry %i: "
+ "task_id=%i, input=%s > output=%s (out_id=%i)",
+ flb_input_chunk_get_name(task->ic),
+ retries, task_id,
+ flb_input_name(task->i_ins),
+ flb_output_name(ins), out_id);
+ }
+ }
+ else if (flb_task_from_fs_storage(task) == FLB_TRUE) {
+ flb_info("[engine] flush backlog chunk '%s' succeeded: "
+ "task_id=%i, input=%s > output=%s (out_id=%i)",
+ flb_input_chunk_get_name(task->ic),
+ task_id,
+ flb_input_name(task->i_ins),
+ flb_output_name(ins), out_id);
+ }
+
+ flb_task_retry_clean(task, ins);
+ flb_task_users_dec(task, FLB_TRUE);
+ }
+ else if (ret == FLB_RETRY) {
+ if (ins->retry_limit == FLB_OUT_RETRY_NONE) {
+ /* cmetrics: output_dropped_records_total */
+ cmt_counter_add(ins->cmt_dropped_records, ts, task->records,
+ 1, (char *[]) {name});
+
+ /* OLD metrics API */
+#ifdef FLB_HAVE_METRICS
+ flb_metrics_sum(FLB_METRIC_OUT_DROPPED_RECORDS, task->records, ins->metrics);
+#endif
+ flb_info("[engine] chunk '%s' is not retried (no retry config): "
+ "task_id=%i, input=%s > output=%s (out_id=%i)",
+ flb_input_chunk_get_name(task->ic),
+ task_id,
+ flb_input_name(task->i_ins),
+ flb_output_name(ins), out_id);
+
+ flb_task_retry_clean(task, ins);
+ flb_task_users_dec(task, FLB_TRUE);
+
+ return 0;
+ }
+
+ /* Create a Task-Retry */
+ retry = flb_task_retry_create(task, ins);
+ if (!retry) {
+ /*
+ * It can fail in two situations:
+ *
+ * - No enough memory (unlikely)
+ * - It reached the maximum number of re-tries
+ */
+
+ /* cmetrics */
+ cmt_counter_inc(ins->cmt_retries_failed, ts, 1, (char *[]) {name});
+ cmt_counter_add(ins->cmt_dropped_records, ts, task->records,
+ 1, (char *[]) {name});
+
+ /* OLD metrics API */
+#ifdef FLB_HAVE_METRICS
+ flb_metrics_sum(FLB_METRIC_OUT_RETRY_FAILED, 1, ins->metrics);
+ flb_metrics_sum(FLB_METRIC_OUT_DROPPED_RECORDS, task->records, ins->metrics);
+#endif
+ /* Notify about this failed retry */
+ flb_error("[engine] chunk '%s' cannot be retried: "
+ "task_id=%i, input=%s > output=%s",
+ flb_input_chunk_get_name(task->ic),
+ task_id,
+ flb_input_name(task->i_ins),
+ flb_output_name(ins));
+
+ flb_task_retry_clean(task, ins);
+ flb_task_users_dec(task, FLB_TRUE);
+
+ return 0;
+ }
+
+ /* Always destroy the old coroutine */
+ flb_task_users_dec(task, FLB_FALSE);
+
+ /* Let the scheduler to retry the failed task/thread */
+ retry_seconds = flb_sched_request_create(config,
+ retry, retry->attempts);
+
+ /*
+ * If for some reason the Scheduler could not include this retry,
+ * we need to get rid of it, likely this is because of not enough
+ * memory available or we ran out of file descriptors.
+ */
+ if (retry_seconds == -1) {
+ flb_warn("[engine] retry for chunk '%s' could not be scheduled: "
+ "input=%s > output=%s",
+ flb_input_chunk_get_name(task->ic),
+ flb_input_name(task->i_ins),
+ flb_output_name(ins));
+
+ flb_task_retry_destroy(retry);
+ flb_task_users_release(task);
+ }
+ else {
+ /* Inform the user 'retry' has been scheduled */
+ flb_warn("[engine] failed to flush chunk '%s', retry in %i seconds: "
+ "task_id=%i, input=%s > output=%s (out_id=%i)",
+ flb_input_chunk_get_name(task->ic),
+ retry_seconds,
+ task->id,
+ flb_input_name(task->i_ins),
+ flb_output_name(ins), out_id);
+
+ /* cmetrics */
+ cmt_counter_inc(ins->cmt_retries, ts, 1, (char *[]) {name});
+ cmt_counter_add(ins->cmt_retried_records, ts, task->records,
+ 1, (char *[]) {name});
+
+ /* OLD metrics API: update the metrics since a new retry is coming */
+#ifdef FLB_HAVE_METRICS
+ flb_metrics_sum(FLB_METRIC_OUT_RETRY, 1, ins->metrics);
+ flb_metrics_sum(FLB_METRIC_OUT_RETRIED_RECORDS, task->records, ins->metrics);
+#endif
+ }
+ }
+ else if (ret == FLB_ERROR) {
+ /* cmetrics */
+ cmt_counter_inc(ins->cmt_errors, ts, 1, (char *[]) {name});
+ cmt_counter_add(ins->cmt_dropped_records, ts, task->records,
+ 1, (char *[]) {name});
+
+ /* OLD API */
+#ifdef FLB_HAVE_METRICS
+ flb_metrics_sum(FLB_METRIC_OUT_ERROR, 1, ins->metrics);
+ flb_metrics_sum(FLB_METRIC_OUT_DROPPED_RECORDS, task->records, ins->metrics);
+#endif
+
+ flb_task_retry_clean(task, ins);
+ flb_task_users_dec(task, FLB_TRUE);
+ }
+
+ return 0;
+}
+
+static inline int handle_output_events(flb_pipefd_t fd,
+ struct flb_config *config)
+{
+ uint64_t values[FLB_ENGINE_OUTPUT_EVENT_BATCH_SIZE];
+ int result;
+ int bytes;
+ size_t limit;
+ size_t index;
+ uint64_t ts;
+
+ memset(&values, 0, sizeof(values));
+
+ bytes = flb_pipe_r(fd, &values, sizeof(values));
+
+ if (bytes == -1) {
+ flb_errno();
+ return -1;
+ }
+
+ limit = floor(bytes / sizeof(uint64_t));
+
+ ts = cfl_time_now();
+
+ for (index = 0 ;
+ index < limit &&
+ index < (sizeof(values) / sizeof(values[0])) ;
+ index++) {
+ if (values[index] == 0) {
+ break;
+ }
+
+ result = handle_output_event(ts, config, values[index]);
+ }
+
+ /* This is wrong, in one hand, if handle_output_event_ fails we should
+ * stop, on the other, we have already consumed the signals from the pipe
+ * so we have to do whatever we can with them.
+ *
+ * And a side effect is that since we have N results but we are not aborting
+ * as soon as we get an error there could be N results to this function which
+ * not only are we not ready to handle but is not even checked at the moment.
+ */
+
+ return result;
+}
+
+static inline int flb_engine_manager(flb_pipefd_t fd, struct flb_config *config)
+{
+ int bytes;
+ uint32_t type;
+ uint32_t key;
+ uint64_t val;
+
+ /* read the event */
+ bytes = flb_pipe_r(fd, &val, sizeof(val));
+ if (bytes == -1) {
+ flb_errno();
+ return -1;
+ }
+
+ /* Get type and key */
+ type = FLB_BITS_U64_HIGH(val);
+ key = FLB_BITS_U64_LOW(val);
+
+ /* Flush all remaining data */
+ if (type == 1) { /* Engine type */
+ if (key == FLB_ENGINE_STOP) {
+ flb_trace("[engine] flush enqueued data");
+ flb_engine_flush(config, NULL);
+ return FLB_ENGINE_STOP;
+ }
+ }
+
+ return 0;
+}
+
+static FLB_INLINE int flb_engine_handle_event(flb_pipefd_t fd, int mask,
+ struct flb_config *config)
+{
+ int ret;
+
+ /* flb_engine_shutdown was already initiated */
+ if (config->is_running == FLB_FALSE) {
+ return 0;
+ }
+
+ if (mask & MK_EVENT_READ) {
+ /* Check if we need to flush */
+ if (config->flush_fd == fd) {
+ flb_utils_timer_consume(fd);
+ flb_engine_flush(config, NULL);
+ return 0;
+ }
+ else if (config->shutdown_fd == fd) {
+ flb_utils_pipe_byte_consume(fd);
+ return FLB_ENGINE_SHUTDOWN;
+ }
+ else if (config->ch_manager[0] == fd) {
+ ret = flb_engine_manager(fd, config);
+ if (ret == FLB_ENGINE_STOP || ret == FLB_ENGINE_EV_STOP) {
+ return FLB_ENGINE_STOP;
+ }
+ }
+
+ /* Try to match the file descriptor with a collector event */
+ ret = flb_input_collector_fd(fd, config);
+ if (ret != -1) {
+ return ret;
+ }
+
+ /* Metrics exporter event ? */
+#ifdef FLB_HAVE_METRICS
+ ret = flb_me_fd_event(fd, config->metrics);
+ if (ret != -1) {
+ return ret;
+ }
+#endif
+
+ /* Stream processor event ? */
+#ifdef FLB_HAVE_STREAM_PROCESSOR
+ if (config->stream_processor_ctx) {
+ ret = flb_sp_fd_event(fd, config->stream_processor_ctx);
+ if (ret != -1) {
+ return ret;
+ }
+ }
+#endif
+ }
+
+ return 0;
+}
+
+static int flb_engine_started(struct flb_config *config)
+{
+ uint64_t val;
+
+ /* Check the channel is valid (enabled by library mode) */
+ if (config->ch_notif[1] <= 0) {
+ return -1;
+ }
+
+ val = FLB_ENGINE_STARTED;
+ return flb_pipe_w(config->ch_notif[1], &val, sizeof(uint64_t));
+}
+
+int flb_engine_failed(struct flb_config *config)
+{
+ int ret;
+ uint64_t val;
+
+ /* Check the channel is valid (enabled by library mode) */
+ if (config->ch_notif[1] <= 0) {
+ flb_error("[engine] no channel to notify FAILED message");
+ return -1;
+ }
+
+ val = FLB_ENGINE_FAILED;
+ ret = flb_pipe_w(config->ch_notif[1], &val, sizeof(uint64_t));
+ if (ret == -1) {
+ flb_error("[engine] fail to dispatch FAILED message");
+ }
+
+ /* Waiting flushing log */
+ sleep(1);
+
+ return ret;
+}
+
+static int flb_engine_log_start(struct flb_config *config)
+{
+ int type;
+ int level;
+
+ /* Log Level */
+ if (config->verbose != FLB_LOG_INFO) {
+ level = config->verbose;
+ }
+ else {
+ level = FLB_LOG_INFO;
+ }
+
+ /* Destination based on type */
+ if (config->log_file) {
+ type = FLB_LOG_FILE;
+ }
+ else {
+ type = FLB_LOG_STDERR;
+ }
+
+ if (flb_log_create(config, type, level, config->log_file) == NULL) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static void flb_engine_drain_ring_buffer_signal_channel(flb_pipefd_t fd)
+{
+ static char signal_buffer[512];
+
+ flb_pipe_r(fd, signal_buffer, sizeof(signal_buffer));
+}
+
+
+#ifdef FLB_HAVE_IN_STORAGE_BACKLOG
+extern int sb_segregate_chunks(struct flb_config *config);
+#else
+int sb_segregate_chunks(struct flb_config *config)
+{
+ return 0;
+}
+#endif
+
+int flb_engine_start(struct flb_config *config)
+{
+ int ret;
+ uint64_t ts;
+ char tmp[16];
+ int rb_flush_flag;
+ struct flb_time t_flush;
+ struct mk_event *event;
+ struct mk_event_loop *evl;
+ struct flb_bucket_queue *evl_bktq;
+ struct flb_sched *sched;
+ struct flb_net_dns dns_ctx;
+
+ /* Initialize the networking layer */
+ flb_net_lib_init();
+ flb_net_ctx_init(&dns_ctx);
+ flb_net_dns_ctx_init();
+ flb_net_dns_ctx_set(&dns_ctx);
+
+ flb_pack_init(config);
+
+ /* Create the event loop and set it in the global configuration */
+ evl = mk_event_loop_create(256);
+ if (!evl) {
+ fprintf(stderr, "[log] could not create event loop\n");
+ return -1;
+ }
+ config->evl = evl;
+
+ /* Create the bucket queue (FLB_ENGINE_PRIORITY_COUNT priorities) */
+ evl_bktq = flb_bucket_queue_create(FLB_ENGINE_PRIORITY_COUNT);
+ if (!evl_bktq) {
+ return -1;
+ }
+ config->evl_bktq = evl_bktq;
+
+ /*
+ * Event loop channel to ingest flush events from flb_engine_flush()
+ *
+ * - FLB engine uses 'ch_self_events[1]' to dispatch tasks to self
+ * - Self to receive message on ch_parent_events[0]
+ *
+ * The mk_event_channel_create() will attach the pipe read end ch_self_events[0]
+ * to the local event loop 'evl'.
+ */
+ ret = mk_event_channel_create(config->evl,
+ &config->ch_self_events[0],
+ &config->ch_self_events[1],
+ &config->event_thread_init);
+ if (ret == -1) {
+ flb_error("[engine] could not create engine thread channel");
+ return -1;
+ }
+ /* Signal type to indicate a "flush" request */
+ config->event_thread_init.type = FLB_ENGINE_EV_THREAD_ENGINE;
+ config->event_thread_init.priority = FLB_ENGINE_PRIORITY_THREAD;
+
+ /* Register the event loop on this thread */
+ flb_engine_evl_init();
+ flb_engine_evl_set(evl);
+
+ /* Start the Logging service */
+ ret = flb_engine_log_start(config);
+ if (ret == -1) {
+ fprintf(stderr, "[engine] log start failed\n");
+ return -1;
+ }
+
+ flb_info("[fluent bit] version=%s, commit=%.10s, pid=%i",
+ FLB_VERSION_STR, FLB_GIT_HASH, getpid());
+
+ /* Debug coroutine stack size */
+ flb_utils_bytes_to_human_readable_size(config->coro_stack_size,
+ tmp, sizeof(tmp));
+ flb_debug("[engine] coroutine stack size: %u bytes (%s)",
+ config->coro_stack_size, tmp);
+
+ /*
+ * Create a communication channel: this routine creates a channel to
+ * signal the Engine event loop. It's useful to stop the event loop
+ * or to instruct anything else without break.
+ */
+ ret = mk_event_channel_create(config->evl,
+ &config->ch_manager[0],
+ &config->ch_manager[1],
+ &config->ch_event);
+ if (ret != 0) {
+ flb_error("[engine] could not create manager channels");
+ return -1;
+ }
+
+ /* Initialize custom plugins */
+ ret = flb_custom_init_all(config);
+ if (ret == -1) {
+ return -1;
+ }
+
+ /* Start the Storage engine */
+ ret = flb_storage_create(config);
+ if (ret == -1) {
+ flb_error("[engine] storage creation failed");
+ return -1;
+ }
+
+ /* Init Metrics engine */
+ cmt_initialize();
+ flb_info("[cmetrics] version=%s", cmt_version());
+ flb_info("[ctraces ] version=%s", ctr_version());
+
+ /* Initialize the scheduler */
+ sched = flb_sched_create(config, config->evl);
+ if (!sched) {
+ flb_error("[engine] scheduler could not start");
+ return -1;
+ }
+ config->sched = sched;
+
+ /* Register the scheduler context */
+ flb_sched_ctx_init();
+ flb_sched_ctx_set(sched);
+
+ /* Initialize input plugins */
+ ret = flb_input_init_all(config);
+ if (ret == -1) {
+ flb_error("[engine] input initialization failed");
+ return -1;
+ }
+
+ /* Initialize filter plugins */
+ ret = flb_filter_init_all(config);
+ if (ret == -1) {
+ flb_error("[engine] filter initialization failed");
+ return -1;
+ }
+
+ /* Inputs pre-run */
+ flb_input_pre_run_all(config);
+
+ /* Initialize output plugins */
+ ret = flb_output_init_all(config);
+ if (ret == -1) {
+ flb_error("[engine] output initialization failed");
+ return -1;
+ }
+
+ /* Outputs pre-run */
+ flb_output_pre_run(config);
+
+ /* Create and register the timer fd for flush procedure */
+ event = &config->event_flush;
+ event->mask = MK_EVENT_EMPTY;
+ event->status = MK_EVENT_NONE;
+
+ flb_time_from_double(&t_flush, config->flush);
+ config->flush_fd = mk_event_timeout_create(evl,
+ t_flush.tm.tv_sec,
+ t_flush.tm.tv_nsec,
+ event);
+ event->priority = FLB_ENGINE_PRIORITY_FLUSH;
+ if (config->flush_fd == -1) {
+ flb_utils_error(FLB_ERR_CFG_FLUSH_CREATE);
+ }
+
+
+#ifdef FLB_HAVE_METRICS
+ if (config->storage_metrics == FLB_TRUE) {
+ config->storage_metrics_ctx = flb_storage_metrics_create(config);
+ }
+#endif
+
+ /* Prepare routing paths */
+ ret = flb_router_io_set(config);
+ if (ret == -1) {
+ flb_error("[engine] router failed");
+ return -1;
+ }
+
+ /* Support mode only */
+ if (config->support_mode == FLB_TRUE) {
+ sleep(1);
+ flb_sosreport(config);
+ exit(1);
+ }
+
+ /* Initialize Metrics exporter */
+#ifdef FLB_HAVE_METRICS
+ config->metrics = flb_me_create(config);
+#endif
+
+ /* Initialize HTTP Server */
+#ifdef FLB_HAVE_HTTP_SERVER
+ if (config->http_server == FLB_TRUE) {
+ config->http_ctx = flb_hs_create(config->http_listen, config->http_port,
+ config);
+ flb_hs_start(config->http_ctx);
+ }
+#endif
+
+#ifdef FLB_HAVE_STREAM_PROCESSOR
+ config->stream_processor_ctx = flb_sp_create(config);
+ if (!config->stream_processor_ctx) {
+ flb_error("[engine] could not initialize stream processor");
+ }
+#endif
+
+ /* Initialize collectors */
+ flb_input_collectors_start(config);
+
+ /*
+ * Sched a permanent callback triggered every 1.5 second to let other
+ * Fluent Bit components run tasks at that interval.
+ */
+ ret = flb_sched_timer_cb_create(config->sched,
+ FLB_SCHED_TIMER_CB_PERM,
+ 1500, cb_engine_sched_timer, config, NULL);
+ if (ret == -1) {
+ flb_error("[engine] could not schedule permanent callback");
+ return -1;
+ }
+
+ /* DEV/TEST change only */
+ int rb_ms;
+ char *rb_env;
+
+ rb_env = getenv("FLB_DEV_RB_MS");
+ if (!rb_env) {
+ rb_ms = 250;
+ }
+ else {
+ rb_ms = atoi(rb_env);
+ }
+
+ /* Input instance / Ring buffer collector */
+ ret = flb_sched_timer_cb_create(config->sched,
+ FLB_SCHED_TIMER_CB_PERM,
+ rb_ms, flb_input_chunk_ring_buffer_collector,
+ config, NULL);
+ if (ret == -1) {
+ flb_error("[engine] could not schedule permanent callback");
+ return -1;
+ }
+
+ /* Signal that we have started */
+ flb_engine_started(config);
+
+ ret = sb_segregate_chunks(config);
+
+ if (ret) {
+ flb_error("[engine] could not segregate backlog chunks");
+ return -2;
+ }
+
+ while (1) {
+ rb_flush_flag = FLB_FALSE;
+
+ mk_event_wait(evl); /* potentially conditional mk_event_wait or mk_event_wait_2 based on bucket queue capacity for one shot events */
+ flb_event_priority_live_foreach(event, evl_bktq, evl, FLB_ENGINE_LOOP_MAX_ITER) {
+ if (event->type == FLB_ENGINE_EV_CORE) {
+ ret = flb_engine_handle_event(event->fd, event->mask, config);
+ if (ret == FLB_ENGINE_STOP) {
+ if (config->grace_count == 0) {
+ if (config->grace >= 0) {
+ flb_warn("[engine] service will shutdown in max %u seconds",
+ config->grace);
+ } else {
+ flb_warn("[engine] service will shutdown when all remaining tasks are flushed");
+ }
+
+ /* Reschedule retry tasks to be retried immediately */
+ flb_engine_reschedule_retries(config);
+ }
+
+ /* mark the runtime as the ingestion is not active and that we are in shutting down mode */
+ config->is_ingestion_active = FLB_FALSE;
+ config->is_shutting_down = FLB_TRUE;
+
+ /* pause all input plugin instances */
+ flb_input_pause_all(config);
+
+ /*
+ * We are preparing to shutdown, we give a graceful time
+ * of 'config->grace' seconds to process any pending event.
+ */
+ event = &config->event_shutdown;
+ event->mask = MK_EVENT_EMPTY;
+ event->status = MK_EVENT_NONE;
+
+ /*
+ * Configure a timer of 1 second, on expiration the code will
+ * jump into the FLB_ENGINE_SHUTDOWN condition where it will
+ * check if the grace period has finished, or if there are
+ * any remaining tasks.
+ *
+ * If no tasks exists, there is no need to wait for the maximum
+ * grace period.
+ */
+ config->shutdown_fd = mk_event_timeout_create(evl,
+ 1,
+ 0,
+ event);
+ event->priority = FLB_ENGINE_PRIORITY_SHUTDOWN;
+ }
+ else if (ret == FLB_ENGINE_SHUTDOWN) {
+ if (config->shutdown_fd > 0) {
+ mk_event_timeout_destroy(config->evl,
+ &config->event_shutdown);
+ }
+
+ /* Increase the grace counter */
+ config->grace_count++;
+
+ /*
+ * Grace timeout has finished, but we need to check if there is
+ * any pending running task. A running task is associated to an
+ * output co-routine, since we don't know what's the state or
+ * resources allocated by that co-routine, the best thing is to
+ * wait again for the grace period and re-check again.
+ * If grace period is set to -1, keep trying to shut down until all
+ * tasks and retries get flushed.
+ */
+ ret = flb_task_running_count(config);
+ if (ret > 0 && (config->grace_count < config->grace || config->grace == -1)) {
+ if (config->grace_count == 1) {
+ flb_task_running_print(config);
+ }
+ flb_engine_exit(config);
+ }
+ else {
+ if (ret > 0) {
+ flb_task_running_print(config);
+ }
+ flb_info("[engine] service has stopped (%i pending tasks)",
+ ret);
+ ret = config->exit_status_code;
+ flb_engine_shutdown(config);
+ config = NULL;
+ return ret;
+ }
+ }
+ }
+ else if (event->type & FLB_ENGINE_EV_SCHED) {
+ /* Event type registered by the Scheduler */
+ flb_sched_event_handler(config, event);
+ }
+ else if (event->type == FLB_ENGINE_EV_THREAD_ENGINE) {
+ struct flb_output_flush *output_flush;
+
+ /* Read the coroutine reference */
+ ret = flb_pipe_r(event->fd, &output_flush, sizeof(struct flb_output_flush *));
+ if (ret <= 0 || output_flush == 0) {
+ flb_errno();
+ continue;
+ }
+
+ /* Init coroutine */
+ flb_coro_resume(output_flush->coro);
+ }
+ else if (event->type == FLB_ENGINE_EV_CUSTOM) {
+ event->handler(event);
+ }
+ else if (event->type == FLB_ENGINE_EV_THREAD) {
+ struct flb_connection *connection;
+
+ /*
+ * Check if we have some co-routine associated to this event,
+ * if so, resume the co-routine
+ */
+
+ connection = (struct flb_connection *) event;
+
+ if (connection->coroutine) {
+ flb_trace("[engine] resuming coroutine=%p", connection->coroutine);
+
+ flb_coro_resume(connection->coroutine);
+ }
+ }
+ else if (event->type == FLB_ENGINE_EV_OUTPUT) {
+ /*
+ * Event originated by an output plugin. likely a Task return
+ * status.
+ */
+ handle_output_events(event->fd, config);
+ }
+ else if (event->type == FLB_ENGINE_EV_INPUT) {
+ ts = cfl_time_now();
+ handle_input_event(event->fd, ts, config);
+ }
+ else if(event->type == FLB_ENGINE_EV_THREAD_INPUT) {
+ flb_engine_drain_ring_buffer_signal_channel(event->fd);
+
+ rb_flush_flag = FLB_TRUE;
+ }
+ }
+
+ if (rb_flush_flag) {
+ flb_input_chunk_ring_buffer_collector(config, NULL);
+ }
+
+ /* Cleanup functions associated to events and timers */
+ if (config->is_running == FLB_TRUE) {
+ flb_net_dns_lookup_context_cleanup(&dns_ctx);
+ flb_sched_timer_cleanup(config->sched);
+ flb_upstream_conn_pending_destroy_list(&config->upstreams);
+ flb_downstream_conn_pending_destroy_list(&config->downstreams);
+
+ /*
+ * depend on main thread to clean up expired message
+ * in aws error reporting message queue
+ */
+ #ifdef FLB_HAVE_AWS_ERROR_REPORTER
+ if (is_error_reporting_enabled()) {
+ flb_aws_error_reporter_clean(error_reporter);
+ }
+ #endif
+ }
+ }
+}
+
+/* Release all resources associated to the engine */
+int flb_engine_shutdown(struct flb_config *config)
+{
+
+ config->is_running = FLB_FALSE;
+ flb_input_pause_all(config);
+
+#ifdef FLB_HAVE_STREAM_PROCESSOR
+ if (config->stream_processor_ctx) {
+ flb_sp_destroy(config->stream_processor_ctx);
+ }
+#endif
+
+ /* router */
+ flb_router_exit(config);
+
+ /* cleanup plugins */
+ flb_filter_exit(config);
+ flb_output_exit(config);
+ flb_custom_exit(config);
+ flb_input_exit_all(config);
+
+ /* Destroy the storage context */
+ flb_storage_destroy(config);
+
+ /* metrics */
+#ifdef FLB_HAVE_METRICS
+ if (config->metrics) {
+ flb_me_destroy(config->metrics);
+ }
+#endif
+
+#ifdef FLB_HAVE_HTTP_SERVER
+ if (config->http_server == FLB_TRUE) {
+ flb_hs_destroy(config->http_ctx);
+ }
+#endif
+
+ return 0;
+}
+
+int flb_engine_exit(struct flb_config *config)
+{
+ int ret;
+ uint64_t val;
+
+ val = FLB_ENGINE_EV_STOP;
+ ret = flb_pipe_w(config->ch_manager[1], &val, sizeof(uint64_t));
+ return ret;
+}
+
+int flb_engine_exit_status(struct flb_config *config, int status)
+{
+ config->exit_status_code = status;
+ return flb_engine_exit(config);
+}
diff --git a/fluent-bit/src/flb_engine_dispatch.c b/fluent-bit/src/flb_engine_dispatch.c
new file mode 100644
index 000000000..5b30c28b4
--- /dev/null
+++ b/fluent-bit/src/flb_engine_dispatch.c
@@ -0,0 +1,339 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <stdlib.h>
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_input_chunk.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_router.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_coro.h>
+#include <fluent-bit/flb_engine.h>
+#include <fluent-bit/flb_task.h>
+#include <fluent-bit/flb_event.h>
+
+
+/* It creates a new output thread using a 'Retry' context */
+int flb_engine_dispatch_retry(struct flb_task_retry *retry,
+ struct flb_config *config)
+{
+ int ret;
+ char *buf_data;
+ size_t buf_size;
+ struct flb_task *task;
+
+ task = retry->parent;
+
+ /* Set file up/down based on restrictions */
+ ret = flb_input_chunk_set_up(task->ic);
+ if (ret == -1) {
+ /*
+ * The re-try is not possible. The chunk is not in memory and trying to bringing it
+ * up was not possible.
+ *
+ * A common cause for this is that the Chunk I/O system is not draining fast
+ * enough like errors on delivering data. So if we cannot put the chunk in memory
+ * it cannot be retried.
+ */
+ ret = flb_task_retry_reschedule(retry, config);
+ if (ret == -1) {
+ return -1;
+ }
+
+ /* Just return because it has been re-scheduled */
+ return 0;
+ }
+
+ /* There is a match, get the buffer */
+ buf_data = (char *) flb_input_chunk_flush(task->ic, &buf_size);
+ if (!buf_data) {
+ /* Could not retrieve chunk content */
+ flb_error("[engine_dispatch] could not retrieve chunk content, removing retry");
+ flb_task_retry_destroy(retry);
+ return -1;
+ }
+
+ /* Update the buffer reference */
+ flb_event_chunk_update(task->event_chunk, buf_data, buf_size);
+
+ /* flush the task */
+ if (retry->o_ins->flags & FLB_OUTPUT_SYNCHRONOUS) {
+ /*
+ * If the plugin doesn't allow for multiplexing.
+ * singleplex_enqueue deletes retry context on flush or delayed flush failure
+ */
+ ret = flb_output_task_singleplex_enqueue(retry->o_ins->singleplex_queue, retry,
+ task, retry->o_ins, config);
+ if (ret == -1) {
+ return -1;
+ }
+ }
+ else {
+ ret = flb_output_task_flush(task, retry->o_ins, config);
+ if (ret == -1) {
+ flb_task_retry_destroy(retry);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void test_run_formatter(struct flb_config *config,
+ struct flb_input_instance *i_ins,
+ struct flb_output_instance *o_ins,
+ struct flb_task *task,
+ void *flush_ctx)
+{
+ int ret;
+ void *out_buf = NULL;
+ size_t out_size = 0;
+ struct flb_test_out_formatter *otf;
+ struct flb_event_chunk *evc;
+
+ otf = &o_ins->test_formatter;
+ evc = task->event_chunk;
+
+ /* Invoke the output plugin formatter test callback */
+ ret = otf->callback(config,
+ i_ins,
+ o_ins->context,
+ flush_ctx,
+ evc->type,
+ evc->tag, flb_sds_len(evc->tag),
+ evc->data, evc->size,
+ &out_buf, &out_size);
+
+ /* Call the runtime test callback checker */
+ if (otf->rt_out_callback) {
+ otf->rt_out_callback(otf->rt_ctx,
+ otf->rt_ffd,
+ ret,
+ out_buf, out_size,
+ otf->rt_data);
+ }
+ else {
+ flb_free(out_buf);
+ }
+}
+
+static int tasks_start(struct flb_input_instance *in,
+ struct flb_config *config)
+{
+ int hits = 0;
+ int retry = 0;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct mk_list *r_head;
+ struct mk_list *r_tmp;
+ struct flb_task *task;
+ struct flb_task_route *route;
+ struct flb_output_instance *out;
+
+ /* At this point the input instance should have some tasks linked */
+ mk_list_foreach_safe(head, tmp, &in->tasks) {
+ task = mk_list_entry(head, struct flb_task, _head);
+
+ if (mk_list_is_empty(&task->retries) != 0) {
+ retry++;
+ }
+
+ /* Only process recently created tasks */
+ if (task->status != FLB_TASK_NEW) {
+ continue;
+ }
+ task->status = FLB_TASK_RUNNING;
+
+ /* A task contain one or more routes */
+ mk_list_foreach_safe(r_head, r_tmp, &task->routes) {
+ route = mk_list_entry(r_head, struct flb_task_route, _head);
+
+ /*
+ * Test mode: if the output plugin is in test mode, just invoke
+ * the proper test function and continue;
+ */
+ out = route->out;
+ if (out->test_mode == FLB_TRUE &&
+ out->test_formatter.callback != NULL) {
+
+ /* Run the formatter test */
+ test_run_formatter(config, in, out,
+ task,
+ out->test_formatter.flush_ctx);
+
+ /* Remove the route */
+ mk_list_del(&route->_head);
+ flb_free(route);
+ continue;
+ }
+
+ /*
+ * If the plugin don't allow multiplexing Tasks, check if it's
+ * running something.
+ */
+ if (out->flags & FLB_OUTPUT_NO_MULTIPLEX) {
+ if (flb_output_coros_size(route->out) > 0 || retry > 0) {
+ continue;
+ }
+ }
+
+ hits++;
+
+ /*
+ * If the plugin is in synchronous mode, enqueue the task and flush
+ * when appropriate.
+ */
+ if (out->flags & FLB_OUTPUT_SYNCHRONOUS) {
+ flb_output_task_singleplex_enqueue(route->out->singleplex_queue, NULL,
+ task, route->out, config);
+ }
+ else {
+ /*
+ * We have the Task and the Route, created a thread context for the
+ * data handling.
+ */
+ flb_output_task_flush(task, route->out, config);
+ }
+
+ /*
+ th = flb_output_thread(task,
+ in,
+ route->out,
+ config,
+ task->buf, task->size,
+ task->tag,
+ task->tag_len);
+ flb_task_add_thread(th, task);
+ flb_thread_resume(th);
+ */
+ }
+
+ if (hits == 0) {
+ task->status = FLB_TASK_NEW;
+ }
+
+ hits = 0;
+ }
+
+ return 0;
+}
+
+/*
+ * The engine dispatch is responsible for:
+ *
+ * - Get chunks generated by input plugins.
+ * - For each set of records under the same tag, create a Task. A Task set
+ * a reference to the records and routes through output instances.
+ */
+int flb_engine_dispatch(uint64_t id, struct flb_input_instance *in,
+ struct flb_config *config)
+{
+ int ret;
+ int t_err;
+ const char *buf_data;
+ size_t buf_size = 0;
+ const char *tag_buf;
+ int tag_len;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_input_plugin *p;
+ struct flb_input_chunk *ic;
+ struct flb_task *task = NULL;
+
+ p = in->p;
+ if (!p) {
+ return 0;
+ }
+
+ /* Look for chunks ready to go */
+ mk_list_foreach_safe(head, tmp, &in->chunks) {
+ ic = mk_list_entry(head, struct flb_input_chunk, _head);
+ if (ic->busy == FLB_TRUE) {
+ continue;
+ }
+
+ /* There is a match, get the buffer */
+ buf_data = flb_input_chunk_flush(ic, &buf_size);
+ if (buf_size == 0) {
+ /*
+ * Do not release the buffer since if allocated, it will be
+ * released when the task is destroyed.
+ */
+ flb_input_chunk_release_lock(ic);
+ continue;
+ }
+ if (!buf_data) {
+ flb_input_chunk_release_lock(ic);
+ continue;
+ }
+
+ /* Get the the tag reference (chunk metadata) */
+ ret = flb_input_chunk_get_tag(ic, &tag_buf, &tag_len);
+ if (ret == -1) {
+ flb_input_chunk_release_lock(ic);
+ continue;
+ }
+
+ /* Validate outgoing Tag information */
+ if (!tag_buf || tag_len <= 0) {
+ flb_input_chunk_release_lock(ic);
+ continue;
+ }
+
+ /* Create a task */
+ task = flb_task_create(id, buf_data, buf_size,
+ ic->in, ic,
+ tag_buf, tag_len,
+ config, &t_err);
+ if (!task) {
+ /*
+ * If task creation failed, check the error status flag. An error
+ * is associated with memory allocation or exhaustion of tasks_id,
+ * on that case the input chunk must be preserved and retried
+ * later. So we just release it busy lock.
+ */
+ if (t_err == FLB_TRUE) {
+ flb_input_chunk_release_lock(ic);
+ }
+ continue;
+ }
+ }
+
+ /* Start the new enqueued Tasks */
+ tasks_start(in, config);
+
+ /*
+ * Tasks cleanup: if some tasks are associated to output plugins running
+ * in test mode, they must be cleaned up since they do not longer contains
+ * an outgoing route.
+ */
+ mk_list_foreach_safe(head, tmp, &in->tasks) {
+ task = mk_list_entry(head, struct flb_task, _head);
+ if (task->users == 0 &&
+ mk_list_size(&task->retries) == 0 &&
+ mk_list_size(&task->routes) == 0) {
+ flb_info("[task] cleanup test task");
+ flb_task_destroy(task, FLB_TRUE);
+ }
+ }
+
+ return 0;
+}
diff --git a/fluent-bit/src/flb_env.c b/fluent-bit/src/flb_env.c
new file mode 100644
index 000000000..3b9158095
--- /dev/null
+++ b/fluent-bit/src/flb_env.c
@@ -0,0 +1,273 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_hash_table.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_env.h>
+
+#include <stdlib.h>
+
+static inline flb_sds_t buf_append(flb_sds_t buf, const char *str, int len)
+{
+ flb_sds_t tmp;
+
+ tmp = flb_sds_cat(buf, str, len);
+ if (!tmp) {
+ return NULL;
+ }
+
+ return tmp;
+}
+
+/* Preset some useful variables */
+static int env_preset(struct flb_env *env)
+{
+ int ret;
+ char *buf;
+ char tmp[512];
+
+ /*
+ * ${HOSTNAME} this variable is very useful to identify records,
+ * despite this variable is recognized by the Shell, that does not
+ * means that is exposed as a real environment variable, e.g:
+ *
+ * 1. $ echo $HOSTNAME
+ * monotop
+ * 2. $ env | grep HOSTNAME
+ * (nothing)
+ */
+ buf = getenv("HOSTNAME");
+ if (!buf) {
+ ret = gethostname(tmp, sizeof(tmp) - 1);
+ if (ret == 0) {
+ flb_env_set(env, "HOSTNAME", tmp);
+ }
+ }
+
+ return 0;
+}
+
+struct flb_env *flb_env_create()
+{
+ struct flb_env *env;
+ struct flb_hash_table *ht;
+
+ env = flb_malloc(sizeof(struct flb_env));
+ if (!env) {
+ flb_errno();
+ return NULL;
+ }
+
+ /* Create the hash-table */
+ ht = flb_hash_table_create(FLB_HASH_TABLE_EVICT_NONE, FLB_ENV_SIZE, -1);
+ if (!ht) {
+ flb_free(env);
+ return NULL;
+ }
+
+ env->warn_unused = FLB_TRUE;
+ env->ht = ht;
+ env_preset(env);
+
+ return env;
+}
+
+void flb_env_destroy(struct flb_env *env)
+{
+ flb_hash_table_destroy(env->ht);
+ flb_free(env);
+}
+
+int flb_env_set(struct flb_env *env, const char *key, const char *val)
+{
+ int id;
+ int klen;
+ int vlen;
+ void *out_buf;
+ size_t out_size;
+
+ /* Get lengths */
+ klen = strlen(key);
+ vlen = strlen(val);
+
+ /* Check if the key is already set */
+ id = flb_hash_table_get(env->ht, key, klen, &out_buf, &out_size);
+ if (id >= 0) {
+ /* Remove the old entry */
+ flb_hash_table_del(env->ht, key);
+ }
+
+ /* Register the new key */
+ id = flb_hash_table_add(env->ht, key, klen, (void *) val, vlen);
+ return id;
+}
+
+const char *flb_env_get(struct flb_env *env, const char *key)
+{
+ int len;
+ int ret;
+ void *out_buf;
+ size_t out_size;
+
+ if (!key) {
+ return NULL;
+ }
+
+ len = strlen(key);
+
+ /* Try to get the value from the hash table */
+ ret = flb_hash_table_get(env->ht, key, len, &out_buf, &out_size);
+ if (ret >= 0) {
+ return (char *) out_buf;
+ }
+
+ /* If it was not found, try to get it from the real environment */
+ out_buf = getenv(key);
+ if (!out_buf) {
+ return NULL;
+ }
+
+ if (strlen(out_buf) == 0) {
+ return NULL;
+ }
+
+ return (char *) out_buf;
+}
+
+/*
+ * Given a 'value', lookup for variables, if found, return a new composed
+ * sds string.
+ */
+flb_sds_t flb_env_var_translate(struct flb_env *env, const char *value)
+{
+ int i;
+ int len;
+ int v_len;
+ int e_len;
+ int pre_var;
+ int have_var = FLB_FALSE;
+ const char *env_var = NULL;
+ char *v_start = NULL;
+ char *v_end = NULL;
+ char tmp[4096];
+ flb_sds_t buf;
+ flb_sds_t s;
+
+ if (!value) {
+ return NULL;
+ }
+
+ len = strlen(value);
+ buf = flb_sds_create_size(len);
+ if (!buf) {
+ return NULL;
+ }
+
+ for (i = 0; i < len; i++) {
+ v_start = strstr(value + i, "${");
+ if (!v_start) {
+ break;
+ }
+
+ v_end = strstr(value + i, "}");
+ if (!v_end) {
+ break;
+ }
+
+ v_start += 2;
+ v_len = v_end - v_start;
+ if (v_len <= 0 || v_len >= sizeof(tmp)) {
+ break;
+ }
+
+ /* variable */
+ strncpy(tmp, v_start, v_len);
+ tmp[v_len] = '\0';
+ have_var = FLB_TRUE;
+
+ /* Append pre-variable content */
+ pre_var = (v_start - 2) - (value + i);
+ if (pre_var > 0) {
+ s = buf_append(buf, value + i, (v_start - 2) - (value + i));
+ if (!s) {
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ if (s != buf) {
+ buf = s;
+ }
+ }
+
+ /* Lookup the variable in our env-hash */
+ env_var = flb_env_get(env, tmp);
+ if (env_var) {
+ e_len = strlen(env_var);
+ s = buf_append(buf, env_var, e_len);
+ if (!s) {
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ if (s != buf) {
+ buf = s;
+ }
+ }
+ else if (env->warn_unused == FLB_TRUE) {
+ flb_warn("[env] variable ${%s} is used but not set", tmp);
+ }
+ i += (v_start - (value + i)) + v_len;
+ }
+
+ /* Copy the remaining value into our buffer */
+ if (v_end) {
+ if (have_var == FLB_TRUE && (value + len) - (v_end + 1) > 0) {
+ s = buf_append(buf, v_end + 1, (value + len) - (v_end + 1));
+ if (!s) {
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ if (s != buf) {
+ buf = s;
+ }
+ }
+ }
+
+ if (flb_sds_len(buf) == 0) {
+ /*
+ * If the output length buffer is zero, it could mean:
+ *
+ * - just one variable was given and it don't have any value
+ * - no variables given (keep original value)
+ *
+ * In order to avoid problems in the caller, if a variable is null
+ * and is the only one content available, return a new empty memory
+ * string.
+ */
+ if (have_var == FLB_TRUE) {
+ return flb_sds_copy(buf, "", 0);
+ }
+ else {
+ return flb_sds_copy(buf, value, len);
+ }
+ }
+
+ return buf;
+}
diff --git a/fluent-bit/src/flb_event.c b/fluent-bit/src/flb_event.c
new file mode 100644
index 000000000..d18bbe32a
--- /dev/null
+++ b/fluent-bit/src/flb_event.c
@@ -0,0 +1,76 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+
+#include <fluent-bit/flb_event.h>
+#include <fluent-bit/flb_sds.h>
+
+struct flb_event_chunk *flb_event_chunk_create(int type,
+ int total_events,
+ char *tag_buf, int tag_len,
+ char *buf_data, size_t buf_size)
+{
+ struct flb_event_chunk *evc;
+
+ /* event chunk context */
+ evc = flb_malloc(sizeof(struct flb_event_chunk));
+ if (!evc) {
+ flb_errno();
+ return NULL;
+ }
+
+ /* create a copy of the tag */
+ evc->tag = flb_sds_create_len(tag_buf, tag_len);
+ if (!evc->tag) {
+ flb_free(evc);
+ return NULL;
+ }
+
+ evc->type = type;
+ evc->data = buf_data;
+ evc->size = buf_size;
+ evc->total_events = total_events;
+
+ return evc;
+}
+
+/* Update the buffer reference */
+int flb_event_chunk_update(struct flb_event_chunk *evc,
+ char *buf_data, size_t buf_size)
+{
+ evc->data = buf_data;
+ evc->size = buf_size;
+
+ return 0;
+}
+
+void flb_event_chunk_destroy(struct flb_event_chunk *evc)
+{
+ if (!evc) {
+ return;
+ }
+
+ if (evc->tag) {
+ flb_sds_destroy(evc->tag);
+ }
+ flb_free(evc);
+}
diff --git a/fluent-bit/src/flb_file.c b/fluent-bit/src/flb_file.c
new file mode 100644
index 000000000..2225bc3c9
--- /dev/null
+++ b/fluent-bit/src/flb_file.c
@@ -0,0 +1,73 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2019-2021 The Fluent Bit Authors
+ * Copyright (C) 2015-2018 Treasure Data Inc.
+ *
+ * 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 <fluent-bit/flb_file.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_sds.h>
+
+#include <stdio.h>
+
+flb_sds_t flb_file_read(const char *path)
+{
+ long flen;
+ FILE *f = NULL;
+ flb_sds_t result = NULL;
+
+ f = fopen(path, "rb");
+ if (!f) {
+ return NULL;
+ }
+
+ if (fseek(f, 0, SEEK_END) == -1) {
+ goto err;
+ }
+
+ flen = ftell(f);
+ if (flen < 0) {
+ goto err;
+ }
+
+ if (fseek(f, 0, SEEK_SET) == -1) {
+ goto err;
+ }
+
+ result = flb_sds_create_size(flen);
+ if (!result) {
+ goto err;
+ }
+
+ if (flen > 0 && fread(result, flen, 1, f) != 1) {
+ goto err;
+ }
+
+ result[flen] = 0;
+ flb_sds_len_set(result, flen);
+ fclose(f);
+ return result;
+
+err:
+ flb_errno();
+ fclose(f);
+ if (result) {
+ flb_sds_destroy(result);
+ }
+ return NULL;
+}
diff --git a/fluent-bit/src/flb_filter.c b/fluent-bit/src/flb_filter.c
new file mode 100644
index 000000000..389709a9a
--- /dev/null
+++ b/fluent-bit/src/flb_filter.c
@@ -0,0 +1,669 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_filter.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_env.h>
+#include <fluent-bit/flb_router.h>
+#include <fluent-bit/flb_mp.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_metrics.h>
+#include <fluent-bit/flb_utils.h>
+#include <chunkio/chunkio.h>
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+#include <fluent-bit/flb_chunk_trace.h>
+#endif /* FLB_HAVE_CHUNK_TRACE */
+
+static inline int instance_id(struct flb_config *config)
+{
+ struct flb_filter_instance *entry;
+
+ if (mk_list_size(&config->filters) == 0) {
+ return 0;
+ }
+
+ entry = mk_list_entry_last(&config->filters, struct flb_filter_instance,
+ _head);
+ return (entry->id + 1);
+}
+
+static int is_active(struct mk_list *in_properties)
+{
+ struct mk_list *head;
+ struct flb_kv *kv;
+
+ mk_list_foreach(head, in_properties) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ if (strcasecmp(kv->key, "active") == 0) {
+ /* Skip checking deactivation ... */
+ if (strcasecmp(kv->val, "FALSE") == 0 || strcmp(kv->val, "0") == 0) {
+ return FLB_FALSE;
+ }
+ }
+ }
+ return FLB_TRUE;
+}
+
+static inline int prop_key_check(const char *key, const char *kv, int k_len)
+{
+ int len;
+
+ len = strlen(key);
+ if (strncasecmp(key, kv, k_len) == 0 && len == k_len) {
+ return 0;
+ }
+
+ return -1;
+}
+
+void flb_filter_do(struct flb_input_chunk *ic,
+ const void *data, size_t bytes,
+ const char *tag, int tag_len,
+ struct flb_config *config)
+{
+ int ret;
+#ifdef FLB_HAVE_METRICS
+ int in_records = 0;
+ int out_records = 0;
+ int diff = 0;
+ int pre_records = 0;
+ uint64_t ts;
+ char *name;
+#endif
+ char *ntag;
+ const char *work_data;
+ size_t work_size;
+ void *out_buf;
+ size_t cur_size;
+ size_t out_size;
+ ssize_t content_size;
+ ssize_t write_at;
+ struct mk_list *head;
+ struct flb_filter_instance *f_ins;
+ struct flb_input_instance *i_ins = ic->in;
+/* measure time between filters for chunk traces. */
+#ifdef FLB_HAVE_CHUNK_TRACE
+ struct flb_time tm_start;
+ struct flb_time tm_finish;
+#endif /* FLB_HAVE_CHUNK_TRACE */
+
+ /* For the incoming Tag make sure to create a NULL terminated reference */
+ ntag = flb_malloc(tag_len + 1);
+ if (!ntag) {
+ flb_errno();
+ flb_error("[filter] could not filter record due to memory problems");
+ return;
+ }
+ memcpy(ntag, tag, tag_len);
+ ntag[tag_len] = '\0';
+
+ work_data = (const char *) data;
+ work_size = bytes;
+
+#ifdef FLB_HAVE_METRICS
+ /* timestamp */
+ ts = cfl_time_now();
+
+ /* Count number of incoming records */
+ in_records = ic->added_records;
+ pre_records = ic->total_records - in_records;
+#endif
+
+ /* Iterate filters */
+ mk_list_foreach(head, &config->filters) {
+ f_ins = mk_list_entry(head, struct flb_filter_instance, _head);
+ if (is_active(&f_ins->properties) == FLB_FALSE) {
+ continue;
+ }
+ if (flb_router_match(ntag, tag_len, f_ins->match
+#ifdef FLB_HAVE_REGEX
+ , f_ins->match_regex
+#else
+ , NULL
+#endif
+ )) {
+ /* Reset filtered buffer */
+ out_buf = NULL;
+ out_size = 0;
+
+ content_size = cio_chunk_get_content_size(ic->chunk);
+
+ /* where to position the new content if modified ? */
+ write_at = (content_size - work_size);
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+ if (ic->trace) {
+ flb_time_get(&tm_start);
+ }
+#endif /* FLB_HAVE_CHUNK_TRACE */
+ /* Invoke the filter callback */
+ ret = f_ins->p->cb_filter(work_data, /* msgpack buffer */
+ work_size, /* msgpack size */
+ ntag, tag_len, /* input tag */
+ &out_buf, /* new data */
+ &out_size, /* new data size */
+ f_ins, /* filter instance */
+ i_ins, /* input instance */
+ f_ins->context, /* filter priv data */
+ config);
+#ifdef FLB_HAVE_CHUNK_TRACE
+ if (ic->trace) {
+ flb_time_get(&tm_finish);
+ }
+#endif /* FLB_HAVE_CHUNK_TRACE */
+
+#ifdef FLB_HAVE_METRICS
+ name = (char *) flb_filter_name(f_ins);
+
+ cmt_counter_add(f_ins->cmt_records, ts, in_records,
+ 1, (char *[]) {name});
+ cmt_counter_add(f_ins->cmt_bytes, ts, content_size,
+ 1, (char *[]) {name});
+
+ flb_metrics_sum(FLB_METRIC_N_RECORDS, in_records, f_ins->metrics);
+ flb_metrics_sum(FLB_METRIC_N_BYTES, content_size, f_ins->metrics);
+#endif
+
+ /* Override buffer just if it was modified */
+ if (ret == FLB_FILTER_MODIFIED) {
+ /* all records removed, no data to continue processing */
+ if (out_size == 0) {
+ /* reset data content length */
+ flb_input_chunk_write_at(ic, write_at, "", 0);
+#ifdef FLB_HAVE_CHUNK_TRACE
+ if (ic->trace) {
+ flb_chunk_trace_filter(ic->trace, (void *)f_ins, &tm_start, &tm_finish, "", 0);
+ }
+#endif /* FLB_HAVE_CHUNK_TRACE */
+
+
+#ifdef FLB_HAVE_METRICS
+ ic->total_records = pre_records;
+
+ /* cmetrics */
+ cmt_counter_add(f_ins->cmt_drop_records, ts, in_records,
+ 1, (char *[]) {name});
+
+ /* [OLD] Summarize all records removed */
+ flb_metrics_sum(FLB_METRIC_N_DROPPED,
+ in_records, f_ins->metrics);
+#endif
+ break;
+ }
+ else {
+#ifdef FLB_HAVE_METRICS
+ out_records = flb_mp_count(out_buf, out_size);
+ if (out_records > in_records) {
+ diff = (out_records - in_records);
+
+ /* cmetrics */
+ cmt_counter_add(f_ins->cmt_add_records, ts, diff,
+ 1, (char *[]) {name});
+
+ /* [OLD] Summarize new records */
+ flb_metrics_sum(FLB_METRIC_N_ADDED,
+ diff, f_ins->metrics);
+ }
+ else if (out_records < in_records) {
+ diff = (in_records - out_records);
+
+ /* cmetrics */
+ cmt_counter_add(f_ins->cmt_drop_records, ts, diff,
+ 1, (char *[]) {name});
+
+ /* [OLD] Summarize dropped records */
+ flb_metrics_sum(FLB_METRIC_N_DROPPED,
+ diff, f_ins->metrics);
+ }
+
+ /* set number of records in new chunk */
+ in_records = out_records;
+ ic->total_records = pre_records + in_records;
+#endif
+ }
+ ret = flb_input_chunk_write_at(ic, write_at,
+ out_buf, out_size);
+ if (ret == -1) {
+ flb_error("[filter] could not write data to storage. "
+ "Skipping filtering.");
+ flb_free(out_buf);
+ continue;
+ }
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+ if (ic->trace) {
+ flb_chunk_trace_filter(ic->trace, (void *)f_ins, &tm_start, &tm_finish, out_buf, out_size);
+ }
+#endif /* FLB_HAVE_CHUNK_TRACE */
+
+ /* Point back the 'data' pointer to the new address */
+ ret = cio_chunk_get_content(ic->chunk,
+ (char **) &work_data, &cur_size);
+ if (ret != CIO_OK) {
+ flb_error("[filter] error retrieving data chunk");
+ }
+ else {
+ work_data += (cur_size - out_size);
+ work_size = out_size;
+ }
+ flb_free(out_buf);
+ }
+ }
+ }
+
+ flb_free(ntag);
+}
+
+int flb_filter_set_property(struct flb_filter_instance *ins,
+ const char *k, const char *v)
+{
+ int len;
+ int ret;
+ flb_sds_t tmp;
+ struct flb_kv *kv;
+
+ len = strlen(k);
+ tmp = flb_env_var_translate(ins->config->env, v);
+ if (!tmp) {
+ return -1;
+ }
+
+ /* Check if the key is a known/shared property */
+#ifdef FLB_HAVE_REGEX
+ if (prop_key_check("match_regex", k, len) == 0) {
+ ins->match_regex = flb_regex_create(tmp);
+ flb_sds_destroy(tmp);
+ }
+ else
+#endif
+ if (prop_key_check("match", k, len) == 0) {
+ flb_utils_set_plugin_string_property("match", &ins->match, tmp);
+ }
+ else if (prop_key_check("alias", k, len) == 0 && tmp) {
+ flb_utils_set_plugin_string_property("alias", &ins->alias, tmp);
+ }
+ else if (prop_key_check("log_level", k, len) == 0 && tmp) {
+ ret = flb_log_get_level_str(tmp);
+ flb_sds_destroy(tmp);
+ if (ret == -1) {
+ return -1;
+ }
+ ins->log_level = ret;
+ }
+ else if (prop_key_check("log_suppress_interval", k, len) == 0 && tmp) {
+ ret = flb_utils_time_to_seconds(tmp);
+ flb_sds_destroy(tmp);
+ if (ret == -1) {
+ return -1;
+ }
+ ins->log_suppress_interval = ret;
+ }
+ else {
+ /*
+ * Create the property, we don't pass the value since we will
+ * map it directly to avoid an extra memory allocation.
+ */
+ kv = flb_kv_item_create(&ins->properties, (char *) k, NULL);
+ if (!kv) {
+ if (tmp) {
+ flb_sds_destroy(tmp);
+ }
+ return -1;
+ }
+ kv->val = tmp;
+ }
+
+ return 0;
+}
+
+const char *flb_filter_get_property(const char *key,
+ struct flb_filter_instance *ins)
+{
+ return flb_kv_get_key_value(key, &ins->properties);
+}
+
+void flb_filter_instance_exit(struct flb_filter_instance *ins,
+ struct flb_config *config)
+{
+ struct flb_filter_plugin *p;
+
+ p = ins->p;
+ if (p->cb_exit && ins->context) {
+ p->cb_exit(ins->context, config);
+ }
+}
+
+/* Invoke exit call for the filter plugin */
+void flb_filter_exit(struct flb_config *config)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_filter_instance *ins;
+ struct flb_filter_plugin *p;
+
+ mk_list_foreach_safe(head, tmp, &config->filters) {
+ ins = mk_list_entry(head, struct flb_filter_instance, _head);
+ p = ins->p;
+ if (!p) {
+ continue;
+ }
+ flb_filter_instance_exit(ins, config);
+ flb_filter_instance_destroy(ins);
+ }
+}
+
+struct flb_filter_instance *flb_filter_new(struct flb_config *config,
+ const char *filter, void *data)
+{
+ int id;
+ struct mk_list *head;
+ struct flb_filter_plugin *plugin;
+ struct flb_filter_instance *instance = NULL;
+
+ if (!filter) {
+ return NULL;
+ }
+
+ mk_list_foreach(head, &config->filter_plugins) {
+ plugin = mk_list_entry(head, struct flb_filter_plugin, _head);
+ if (strcasecmp(plugin->name, filter) == 0) {
+ break;
+ }
+ plugin = NULL;
+ }
+
+ if (!plugin) {
+ return NULL;
+ }
+
+ instance = flb_calloc(1, sizeof(struct flb_filter_instance));
+ if (!instance) {
+ flb_errno();
+ return NULL;
+ }
+ instance->config = config;
+
+ /*
+ * Initialize event type, if not set, default to FLB_FILTER_LOGS. Note that a
+ * zero value means it's undefined.
+ */
+ if (plugin->event_type == 0) {
+ instance->event_type = FLB_FILTER_LOGS;
+ }
+ else {
+ instance->event_type = plugin->event_type;
+ }
+
+ /* Get an ID */
+ id = instance_id(config);
+
+ /* format name (with instance id) */
+ snprintf(instance->name, sizeof(instance->name) - 1,
+ "%s.%i", plugin->name, id);
+
+ instance->id = id;
+ instance->alias = NULL;
+ instance->p = plugin;
+ instance->data = data;
+ instance->match = NULL;
+#ifdef FLB_HAVE_REGEX
+ instance->match_regex = NULL;
+#endif
+ instance->log_level = -1;
+ instance->log_suppress_interval = -1;
+
+ mk_list_init(&instance->properties);
+ mk_list_add(&instance->_head, &config->filters);
+
+ return instance;
+}
+
+/* Return an instance name or alias */
+const char *flb_filter_name(struct flb_filter_instance *ins)
+{
+ if (ins->alias) {
+ return ins->alias;
+ }
+
+ return ins->name;
+}
+
+int flb_filter_plugin_property_check(struct flb_filter_instance *ins,
+ struct flb_config *config)
+{
+ int ret = 0;
+ struct mk_list *config_map;
+ struct flb_filter_plugin *p = ins->p;
+
+ if (p->config_map) {
+ /*
+ * Create a dynamic version of the configmap that will be used by the specific
+ * instance in question.
+ */
+ config_map = flb_config_map_create(config, p->config_map);
+ if (!config_map) {
+ flb_error("[filter] error loading config map for '%s' plugin",
+ p->name);
+ return -1;
+ }
+ ins->config_map = config_map;
+
+ /* Validate incoming properties against config map */
+ ret = flb_config_map_properties_check(ins->p->name,
+ &ins->properties, ins->config_map);
+ if (ret == -1) {
+ if (config->program_name) {
+ flb_helper("try the command: %s -F %s -h\n",
+ config->program_name, ins->p->name);
+ }
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int flb_filter_match_property_existence(struct flb_filter_instance *ins)
+{
+ if (!ins->match
+#ifdef FLB_HAVE_REGEX
+ && !ins->match_regex
+#endif
+ ) {
+ return FLB_FALSE;
+ }
+
+ return FLB_TRUE;
+}
+
+int flb_filter_init(struct flb_config *config, struct flb_filter_instance *ins)
+{
+ int ret;
+ uint64_t ts;
+ char *name;
+ struct flb_filter_plugin *p;
+
+ if (flb_filter_match_property_existence(ins) == FLB_FALSE) {
+ flb_warn("[filter] NO match rule for %s filter instance, unloading.",
+ ins->name);
+ return -1;
+ }
+
+ if (ins->log_level == -1 && config->log) {
+ ins->log_level = config->log->level;
+ }
+
+ p = ins->p;
+
+ /* Get name or alias for the instance */
+ name = (char *) flb_filter_name(ins);
+ ts = cfl_time_now();
+
+ /* CMetrics */
+ ins->cmt = cmt_create();
+ if (!ins->cmt) {
+ flb_error("[filter] could not create cmetrics context: %s",
+ flb_filter_name(ins));
+ return -1;
+ }
+
+ /* Register generic filter plugin metrics */
+ ins->cmt_records = cmt_counter_create(ins->cmt,
+ "fluentbit", "filter",
+ "records_total",
+ "Total number of new records processed.",
+ 1, (char *[]) {"name"});
+ cmt_counter_set(ins->cmt_records, ts, 0, 1, (char *[]) {name});
+
+ /* Register generic filter plugin metrics */
+ ins->cmt_bytes = cmt_counter_create(ins->cmt,
+ "fluentbit", "filter",
+ "bytes_total",
+ "Total number of new bytes processed.",
+ 1, (char *[]) {"name"});
+ cmt_counter_set(ins->cmt_bytes, ts, 0, 1, (char *[]) {name});
+
+ /* Register generic filter plugin metrics */
+ ins->cmt_add_records = cmt_counter_create(ins->cmt,
+ "fluentbit", "filter",
+ "add_records_total",
+ "Total number of new added records.",
+ 1, (char *[]) {"name"});
+ cmt_counter_set(ins->cmt_add_records, ts, 0, 1, (char *[]) {name});
+
+ /* Register generic filter plugin metrics */
+ ins->cmt_drop_records = cmt_counter_create(ins->cmt,
+ "fluentbit", "filter",
+ "drop_records_total",
+ "Total number of dropped records.",
+ 1, (char *[]) {"name"});
+ cmt_counter_set(ins->cmt_drop_records, ts, 0, 1, (char *[]) {name});
+
+ /* OLD Metrics API */
+#ifdef FLB_HAVE_METRICS
+
+ /* Create the metrics context */
+ ins->metrics = flb_metrics_create(name);
+ if (!ins->metrics) {
+ flb_warn("[filter] cannot initialize metrics for %s filter, "
+ "unloading.", name);
+ return -1;
+ }
+
+ /* Register filter metrics */
+ flb_metrics_add(FLB_METRIC_N_DROPPED, "drop_records", ins->metrics);
+ flb_metrics_add(FLB_METRIC_N_ADDED, "add_records", ins->metrics);
+ flb_metrics_add(FLB_METRIC_N_RECORDS, "records", ins->metrics);
+ flb_metrics_add(FLB_METRIC_N_BYTES, "bytes", ins->metrics);
+#endif
+
+ /*
+ * Before to call the initialization callback, make sure that the received
+ * configuration parameters are valid if the plugin is registering a config map.
+ */
+ if (flb_filter_plugin_property_check(ins, config) == -1) {
+ return -1;
+ }
+
+ if (is_active(&ins->properties) == FLB_FALSE) {
+ return 0;
+ }
+
+ /* Initialize the input */
+ if (p->cb_init) {
+ ret = p->cb_init(ins, config, ins->data);
+ if (ret != 0) {
+ flb_error("Failed initialize filter %s", ins->name);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* Initialize all filter plugins */
+int flb_filter_init_all(struct flb_config *config)
+{
+ int ret;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_filter_instance *ins;
+
+ /* Iterate all active filter instance plugins */
+ mk_list_foreach_safe(head, tmp, &config->filters) {
+ ins = mk_list_entry(head, struct flb_filter_instance, _head);
+ ret = flb_filter_init(config, ins);
+ if (ret == -1) {
+ flb_filter_instance_destroy(ins);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void flb_filter_instance_destroy(struct flb_filter_instance *ins)
+{
+ if (!ins) {
+ return;
+ }
+
+ /* destroy config map */
+ if (ins->config_map) {
+ flb_config_map_destroy(ins->config_map);
+ }
+
+ /* release properties */
+ flb_kv_release(&ins->properties);
+
+ if (ins->match != NULL) {
+ flb_sds_destroy(ins->match);
+ }
+
+#ifdef FLB_HAVE_REGEX
+ if (ins->match_regex) {
+ flb_regex_destroy(ins->match_regex);
+ }
+#endif
+
+ /* Remove metrics */
+#ifdef FLB_HAVE_METRICS
+ if (ins->cmt) {
+ cmt_destroy(ins->cmt);
+ }
+
+ if (ins->metrics) {
+ flb_metrics_destroy(ins->metrics);
+ }
+#endif
+ if (ins->alias) {
+ flb_sds_destroy(ins->alias);
+ }
+
+ mk_list_del(&ins->_head);
+ flb_free(ins);
+}
+
+void flb_filter_set_context(struct flb_filter_instance *ins, void *context)
+{
+ ins->context = context;
+}
diff --git a/fluent-bit/src/flb_fstore.c b/fluent-bit/src/flb_fstore.c
new file mode 100644
index 000000000..03bcc99db
--- /dev/null
+++ b/fluent-bit/src/flb_fstore.c
@@ -0,0 +1,558 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_fstore.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_sds.h>
+#include <chunkio/chunkio.h>
+
+static int log_cb(struct cio_ctx *ctx, int level, const char *file, int line,
+ char *str)
+{
+ if (level == CIO_LOG_ERROR) {
+ flb_error("[fstore] %s", str);
+ }
+ else if (level == CIO_LOG_WARN) {
+ flb_warn("[fstore] %s", str);
+ }
+ else if (level == CIO_LOG_INFO) {
+ flb_info("[fstore] %s", str);
+ }
+ else if (level == CIO_LOG_DEBUG) {
+ flb_debug("[fstore] %s", str);
+ }
+
+ return 0;
+}
+
+/*
+ * this function sets metadata into a fstore_file structure, note that it makes
+ * it own copy of the data to set a NULL byte at the end.
+ */
+static int meta_set(struct flb_fstore_file *fsf, void *meta, size_t size)
+{
+
+ char *p;
+
+ p = flb_calloc(1, size + 1);
+ if (!p) {
+ flb_errno();
+ flb_error("[fstore] could not cache metadata in file: %s:%s",
+ fsf->stream->name, fsf->chunk->name);
+ return -1;
+ }
+
+ if (fsf->meta_buf) {
+ flb_free(fsf->meta_buf);
+ }
+ fsf->meta_buf = p;
+ memcpy(fsf->meta_buf, meta, size);
+ fsf->meta_size = size;
+
+ return 0;
+}
+
+/* Set a file metadata */
+int flb_fstore_file_meta_set(struct flb_fstore *fs,
+ struct flb_fstore_file *fsf,
+ void *meta, size_t size)
+{
+ int ret;
+ int set_down = FLB_FALSE;
+
+ /* Check if the chunk is up */
+ if (cio_chunk_is_up(fsf->chunk) == CIO_FALSE) {
+ ret = cio_chunk_up_force(fsf->chunk);
+ if (ret != CIO_OK) {
+ flb_error("[fstore] error loading up file chunk");
+ return -1;
+ }
+ set_down = FLB_TRUE;
+ }
+
+ ret = cio_meta_write(fsf->chunk, meta, size);
+ if (ret == -1) {
+ flb_error("[fstore] could not write metadata to file: %s:%s",
+ fsf->stream->name, fsf->chunk->name);
+
+ if (set_down == FLB_TRUE) {
+ cio_chunk_down(fsf->chunk);
+ }
+
+ return -1;
+ }
+
+ if (set_down == FLB_TRUE) {
+ cio_chunk_down(fsf->chunk);
+ }
+
+ return meta_set(fsf, meta, size);
+}
+
+/* Re-read Chunk I/O metadata into fstore file */
+int flb_fstore_file_meta_get(struct flb_fstore *fs,
+ struct flb_fstore_file *fsf)
+{
+ int ret;
+ int set_down = FLB_FALSE;
+ char *meta_buf = NULL;
+ int meta_size = 0;
+
+ /* Check if the chunk is up */
+ if (cio_chunk_is_up(fsf->chunk) == CIO_FALSE) {
+ ret = cio_chunk_up_force(fsf->chunk);
+ if (ret != CIO_OK) {
+ flb_error("[fstore] error loading up file chunk");
+ return -1;
+ }
+ set_down = FLB_TRUE;
+ }
+
+ ret = cio_meta_read(fsf->chunk, &meta_buf, &meta_size);
+ if (ret == -1) {
+ flb_error("[fstore] error reading file chunk metadata");
+ if (set_down == FLB_TRUE) {
+ cio_chunk_down(fsf->chunk);
+ }
+ }
+
+ ret = meta_set(fsf, meta_buf, meta_size);
+ if (ret == -1) {
+ flb_free(meta_buf);
+ if (set_down == FLB_TRUE) {
+ cio_chunk_down(fsf->chunk);
+ }
+ return -1;
+ }
+
+ if (set_down == FLB_TRUE) {
+ cio_chunk_down(fsf->chunk);
+ }
+ return 0;
+}
+
+/* Create a new file */
+struct flb_fstore_file *flb_fstore_file_create(struct flb_fstore *fs,
+ struct flb_fstore_stream *fs_stream,
+ char *name, size_t size)
+{
+ int err;
+ struct cio_chunk *chunk;
+ struct flb_fstore_file *fsf;
+
+ fsf = flb_calloc(1, sizeof(struct flb_fstore_file));
+ if (!fsf) {
+ flb_errno();
+ return NULL;
+ }
+ fsf->stream = fs_stream->stream;
+
+ fsf->name = flb_sds_create(name);
+ if (!fsf->name) {
+ flb_error("[fstore] could not create file: %s:%s",
+ fsf->stream->name, name);
+ flb_free(fsf);
+ return NULL;
+ }
+
+ chunk = cio_chunk_open(fs->cio, fs_stream->stream, name,
+ CIO_OPEN, size, &err);
+ if (!chunk) {
+ flb_error("[fstore] could not create file: %s:%s",
+ fsf->stream->name, name);
+ flb_sds_destroy(fsf->name);
+ flb_free(fsf);
+ return NULL;
+ }
+
+ fsf->chunk = chunk;
+ mk_list_add(&fsf->_head, &fs_stream->files);
+
+ return fsf;
+}
+
+/* Lookup file on stream by using it name */
+struct flb_fstore_file *flb_fstore_file_get(struct flb_fstore *fs,
+ struct flb_fstore_stream *fs_stream,
+ char *name, size_t size)
+{
+ struct mk_list *head;
+ struct flb_fstore_file *fsf;
+
+ mk_list_foreach(head, &fs_stream->files) {
+ fsf = mk_list_entry(head, struct flb_fstore_file, _head);
+ if (flb_sds_len(fsf->name) != size) {
+ continue;
+ }
+
+ if (strncmp(fsf->name, name, size) == 0) {
+ return fsf;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Set a file to inactive mode. Inactive means just to remove the reference
+ * from the list.
+ */
+int flb_fstore_file_inactive(struct flb_fstore *fs,
+ struct flb_fstore_file *fsf)
+{
+ /* close the Chunk I/O reference, but don't delete the real file */
+ if (fsf->chunk) {
+ cio_chunk_close(fsf->chunk, CIO_FALSE);
+ }
+
+ /* release */
+ mk_list_del(&fsf->_head);
+ flb_sds_destroy(fsf->name);
+ if (fsf->meta_buf) {
+ flb_free(fsf->meta_buf);
+ }
+ flb_free(fsf);
+
+ return 0;
+}
+
+/* Delete a file (permantent deletion) */
+int flb_fstore_file_delete(struct flb_fstore *fs,
+ struct flb_fstore_file *fsf)
+{
+ /* close the Chunk I/O reference, but don't delete it the real file */
+ cio_chunk_close(fsf->chunk, CIO_TRUE);
+
+ /* release */
+ mk_list_del(&fsf->_head);
+ if (fsf->meta_buf) {
+ flb_free(fsf->meta_buf);
+ }
+ flb_sds_destroy(fsf->name);
+ flb_free(fsf);
+
+ return 0;
+}
+
+/*
+ * Set an output buffer that contains a copy of the file. Note that this buffer
+ * needs to be freed by the caller (heap memory).
+ */
+int flb_fstore_file_content_copy(struct flb_fstore *fs,
+ struct flb_fstore_file *fsf,
+ void **out_buf, size_t *out_size)
+{
+ int ret;
+
+ ret = cio_chunk_get_content_copy(fsf->chunk, out_buf, out_size);
+ if (ret == CIO_OK) {
+ return 0;
+ }
+
+ return -1;
+}
+
+/* Append data to an existing file */
+int flb_fstore_file_append(struct flb_fstore_file *fsf, void *data, size_t size)
+{
+ int ret;
+ int set_down = FLB_FALSE;
+
+ /* Check if the chunk is up */
+ if (cio_chunk_is_up(fsf->chunk) == CIO_FALSE) {
+ ret = cio_chunk_up_force(fsf->chunk);
+ if (ret != CIO_OK) {
+ flb_error("[fstore] error loading up file chunk");
+ return -1;
+ }
+ set_down = FLB_TRUE;
+ }
+
+ ret = cio_chunk_write(fsf->chunk, data, size);
+ if (ret != CIO_OK) {
+ flb_error("[fstore] could not write data to file %s", fsf->name);
+
+ if (set_down == FLB_TRUE) {
+ cio_chunk_down(fsf->chunk);
+ }
+
+ return -1;
+ }
+
+ if (set_down == FLB_TRUE) {
+ cio_chunk_down(fsf->chunk);
+ }
+
+ return 0;
+}
+
+/*
+ * Create a new stream, if it already exists, it returns the stream
+ * reference.
+ */
+struct flb_fstore_stream *flb_fstore_stream_create(struct flb_fstore *fs,
+ char *stream_name)
+{
+ flb_sds_t path = NULL;
+ struct mk_list *head;
+ struct cio_ctx *ctx = NULL;
+ struct cio_stream *stream = NULL;
+ struct flb_fstore_stream *fs_stream = NULL;
+
+ ctx = fs->cio;
+
+ /* Check if the stream already exists in Chunk I/O */
+ mk_list_foreach(head, &ctx->streams) {
+ stream = mk_list_entry(head, struct cio_stream, _head);
+ if (strcmp(stream->name, stream_name) == 0) {
+ break;
+ }
+ stream = NULL;
+ }
+
+ /* If the stream exists, check if we have a fstore_stream reference */
+ if (stream) {
+ mk_list_foreach(head, &fs->streams) {
+ fs_stream = mk_list_entry(head, struct flb_fstore_stream, _head);
+ if (fs_stream->stream == stream) {
+ break;
+ }
+ fs_stream = NULL;
+ }
+
+ /* The stream was found, just return the reference */
+ if (fs_stream) {
+ return fs_stream;
+ }
+ }
+
+ if (!stream) {
+ /* create file-system based stream */
+ stream = cio_stream_create(fs->cio, stream_name, fs->store_type);
+ if (!stream) {
+ flb_error("[fstore] cannot create stream %s", stream_name);
+ return NULL;
+ }
+ }
+
+ fs_stream = flb_calloc(1, sizeof(struct flb_fstore_stream));
+ if (!fs_stream) {
+ flb_errno();
+ cio_stream_destroy(stream);
+ return NULL;
+ }
+ fs_stream->stream = stream;
+
+ path = flb_sds_create_size(256);
+ if (!path) {
+ cio_stream_destroy(stream);
+ flb_free(fs_stream);
+ return NULL;
+ }
+ path = flb_sds_printf(&path, "%s/%s", fs->root_path, stream->name);
+ fs_stream->path = path;
+ fs_stream->name = stream->name;
+
+ mk_list_init(&fs_stream->files);
+ mk_list_add(&fs_stream->_head, &fs->streams);
+
+ return fs_stream;
+}
+
+void flb_fstore_stream_destroy(struct flb_fstore_stream *stream, int delete)
+{
+ if (delete == FLB_TRUE) {
+ cio_stream_delete(stream->stream);
+ }
+
+ /*
+ * FYI: in this function we just release the fstore_stream context, the
+ * underlaying cio_stream is closed when the main Chunk I/O is destroyed.
+ */
+ mk_list_del(&stream->_head);
+ flb_sds_destroy(stream->path);
+ flb_free(stream);
+}
+
+static int map_chunks(struct flb_fstore *ctx, struct flb_fstore_stream *fs_stream,
+ struct cio_stream *stream)
+{
+ struct mk_list *head;
+ struct cio_chunk *chunk;
+ struct flb_fstore_file *fsf;
+
+ mk_list_foreach(head, &stream->chunks) {
+ chunk = mk_list_entry(head, struct cio_chunk, _head);
+
+ fsf = flb_calloc(1, sizeof(struct flb_fstore_file));
+ if (!fsf) {
+ flb_errno();
+ return -1;
+ }
+ fsf->name = flb_sds_create(chunk->name);
+ if (!fsf->name) {
+ flb_free(fsf);
+ flb_error("[fstore] could not create file: %s:%s",
+ stream->name, chunk->name);
+ return -1;
+ }
+
+ fsf->chunk = chunk;
+
+ /* load metadata */
+ flb_fstore_file_meta_get(ctx, fsf);
+ mk_list_add(&fsf->_head, &fs_stream->files);
+ }
+
+ return 0;
+}
+
+static int load_references(struct flb_fstore *fs)
+{
+ int ret;
+ struct mk_list *head;
+ struct cio_stream *stream;
+ struct flb_fstore_stream *fs_stream;
+
+ mk_list_foreach(head, &fs->cio->streams) {
+ stream = mk_list_entry(head, struct cio_stream, _head);
+ fs_stream = flb_fstore_stream_create(fs, stream->name);
+ if (!fs_stream) {
+ flb_error("[fstore] error loading stream reference: %s",
+ stream->name);
+ return -1;
+ }
+
+ /* Map chunks */
+ ret = map_chunks(fs, fs_stream, stream);
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+struct flb_fstore *flb_fstore_create(char *path, int store_type)
+{
+ int ret;
+ int flags;
+ struct cio_ctx *cio;
+ struct flb_fstore *fs;
+ struct cio_options opts = {0};
+ flags = CIO_OPEN;
+
+ /* Create Chunk I/O context */
+ cio_options_init(&opts);
+
+ opts.root_path = path;
+ opts.log_cb = log_cb;
+ opts.flags = flags;
+ opts.log_level = CIO_LOG_INFO;
+
+ cio = cio_create(&opts);
+ if (!cio) {
+ flb_error("[fstore] error initializing on path '%s'", path);
+ return NULL;
+ }
+
+ /* Load content from the file system if any */
+ ret = cio_load(cio, NULL);
+ if (ret == -1) {
+ flb_error("[fstore] error scanning root path content: %s", path);
+ cio_destroy(cio);
+ return NULL;
+ }
+
+ fs = flb_calloc(1, sizeof(struct flb_fstore));
+ if (!fs) {
+ flb_errno();
+ cio_destroy(cio);
+ return NULL;
+ }
+ fs->cio = cio;
+ fs->root_path = cio->options.root_path;
+ fs->store_type = store_type;
+ mk_list_init(&fs->streams);
+
+ /* Map Chunk I/O streams and chunks into fstore context */
+ load_references(fs);
+
+ return fs;
+}
+
+int flb_fstore_destroy(struct flb_fstore *fs)
+{
+ int files = 0;
+ int delete;
+ struct mk_list *head;
+ struct mk_list *f_head;
+ struct mk_list *tmp;
+ struct mk_list *f_tmp;
+ struct flb_fstore_stream *fs_stream;
+ struct flb_fstore_file *fsf;
+
+ mk_list_foreach_safe(head, tmp, &fs->streams) {
+ fs_stream = mk_list_entry(head, struct flb_fstore_stream, _head);
+
+ /* delete file references */
+ files = 0;
+ mk_list_foreach_safe(f_head, f_tmp, &fs_stream->files) {
+ fsf = mk_list_entry(f_head, struct flb_fstore_file, _head);
+ flb_fstore_file_inactive(fs, fsf);
+ files++;
+ }
+
+ if (files == 0) {
+ delete = FLB_TRUE;
+ }
+ else {
+ delete = FLB_FALSE;
+ }
+
+ flb_fstore_stream_destroy(fs_stream, delete);
+ }
+
+ if (fs->cio) {
+ cio_destroy(fs->cio);
+ }
+ flb_free(fs);
+ return 0;
+}
+
+void flb_fstore_dump(struct flb_fstore *fs)
+{
+ struct mk_list *head;
+ struct mk_list *f_head;
+ struct flb_fstore_stream *fs_stream;
+ struct flb_fstore_file *fsf;
+
+ printf("===== FSTORE DUMP =====\n");
+ mk_list_foreach(head, &fs->streams) {
+ fs_stream = mk_list_entry(head, struct flb_fstore_stream, _head);
+ printf("- stream: %s\n", fs_stream->name);
+ mk_list_foreach(f_head, &fs_stream->files) {
+ fsf = mk_list_entry(f_head, struct flb_fstore_file, _head);
+ printf(" %s/%s\n", fsf->stream->name, fsf->name);
+ }
+ }
+ printf("\n");
+}
diff --git a/fluent-bit/src/flb_gzip.c b/fluent-bit/src/flb_gzip.c
new file mode 100644
index 000000000..4b9b761fe
--- /dev/null
+++ b/fluent-bit/src/flb_gzip.c
@@ -0,0 +1,747 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_gzip.h>
+#include <fluent-bit/flb_compression.h>
+#include <miniz/miniz.h>
+
+#define FLB_GZIP_HEADER_OFFSET 10
+#define FLB_GZIP_HEADER_SIZE FLB_GZIP_HEADER_OFFSET
+
+#define FLB_GZIP_MAGIC_NUMBER 0x8B1F
+
+typedef enum {
+ FTEXT = 1,
+ FHCRC = 2,
+ FEXTRA = 4,
+ FNAME = 8,
+ FCOMMENT = 16
+} flb_tinf_gzip_flag;
+
+#pragma pack(push, 1)
+struct flb_gzip_header {
+ uint16_t magic_number;
+ uint8_t compression_method;
+ uint8_t header_flags;
+ uint32_t timestamp;
+ uint8_t compression_flags;
+ uint8_t operating_system_id;
+};
+#pragma pack(pop)
+
+struct flb_gzip_decompression_context {
+ struct flb_gzip_header gzip_header;
+ mz_stream miniz_stream;
+};
+
+static unsigned int read_le16(const unsigned char *p)
+{
+ return ((unsigned int) p[0]) | ((unsigned int) p[1] << 8);
+}
+
+static unsigned int read_le32(const unsigned char *p)
+{
+ return ((unsigned int) p[0])
+ | ((unsigned int) p[1] << 8)
+ | ((unsigned int) p[2] << 16)
+ | ((unsigned int) p[3] << 24);
+}
+
+static inline void gzip_header(void *buf)
+{
+ uint8_t *p;
+
+ /* GZip Magic bytes */
+ p = buf;
+ *p++ = 0x1F;
+ *p++ = 0x8B;
+ *p++ = 8;
+ *p++ = 0;
+ *p++ = 0;
+ *p++ = 0;
+ *p++ = 0;
+ *p++ = 0;
+ *p++ = 0;
+ *p++ = 0xFF;
+}
+
+
+#include <ctype.h>
+
+static inline void flb_hex_dump(uint8_t *buffer, size_t buffer_length, size_t line_length) {
+ char *printable_line;
+ size_t buffer_index;
+ size_t filler_index;
+
+ if (40 < line_length)
+ {
+ line_length = 40;
+ }
+
+ printable_line = alloca(line_length + 1);
+
+ if (NULL == printable_line)
+ {
+ printf("Alloca returned NULL\n");
+
+ return;
+ }
+
+ memset(printable_line, '\0', line_length + 1);
+
+ for (buffer_index = 0 ; buffer_index < buffer_length ; buffer_index++) {
+ if (0 != buffer_index &&
+ 0 == (buffer_index % line_length)) {
+
+ printf("%s\n", printable_line);
+
+ memset(printable_line, '\0', line_length + 1);
+ }
+
+ if (0 != isprint(buffer[buffer_index])) {
+ printable_line[(buffer_index % line_length)] = buffer[buffer_index];
+ }
+ else {
+ printable_line[(buffer_index % line_length)] = '.';
+ }
+
+ printf("%02X ", buffer[buffer_index]);
+ }
+
+ if (0 != buffer_index &&
+ 0 != (buffer_index % line_length)) {
+
+ for (filler_index = 0 ;
+ filler_index < (line_length - (buffer_index % line_length)) ;
+ filler_index++) {
+ printf(" ");
+ }
+
+ printf("%s\n", printable_line);
+
+ memset(printable_line, '.', line_length);
+ }
+}
+
+
+int flb_gzip_compress(void *in_data, size_t in_len,
+ void **out_data, size_t *out_len)
+{
+ int flush;
+ int status;
+ int footer_start;
+ uint8_t *pb;
+ size_t out_size;
+ void *out_buf;
+ z_stream strm;
+ mz_ulong crc;
+
+ /*
+ * Calculating the upper bound for a gzip compression is
+ * non-trivial, so we rely on miniz's own calculation
+ * to guarantee memory safety.
+ */
+ out_size = compressBound(in_len);
+ out_buf = flb_malloc(out_size);
+
+ if (!out_buf) {
+ flb_errno();
+ flb_error("[gzip] could not allocate outgoing buffer");
+ return -1;
+ }
+
+ /* Initialize streaming buffer context */
+ memset(&strm, '\0', sizeof(strm));
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.next_in = in_data;
+ strm.avail_in = in_len;
+ strm.total_out = 0;
+
+ /* Deflate mode */
+ deflateInit2(&strm, Z_DEFAULT_COMPRESSION,
+ Z_DEFLATED, -Z_DEFAULT_WINDOW_BITS, 9, Z_DEFAULT_STRATEGY);
+
+ /*
+ * Miniz don't support GZip format directly, instead we will:
+ *
+ * - append manual GZip magic bytes
+ * - deflate raw content
+ * - append manual CRC32 data
+ */
+ gzip_header(out_buf);
+
+ /* Header offset */
+ pb = (uint8_t *) out_buf + FLB_GZIP_HEADER_OFFSET;
+
+ flush = Z_NO_FLUSH;
+ while (1) {
+ strm.next_out = pb + strm.total_out;
+ strm.avail_out = out_size - (pb - (uint8_t *) out_buf);
+
+ if (strm.avail_in == 0) {
+ flush = Z_FINISH;
+ }
+
+ status = deflate(&strm, flush);
+ if (status == Z_STREAM_END) {
+ break;
+ }
+ else if (status != Z_OK) {
+ deflateEnd(&strm);
+ return -1;
+ }
+ }
+
+ if (deflateEnd(&strm) != Z_OK) {
+ flb_free(out_buf);
+ return -1;
+ }
+ *out_len = strm.total_out;
+
+ /* Construct the gzip checksum (CRC32 footer) */
+ footer_start = FLB_GZIP_HEADER_OFFSET + *out_len;
+ pb = (uint8_t *) out_buf + footer_start;
+
+ crc = mz_crc32(MZ_CRC32_INIT, in_data, in_len);
+ *pb++ = crc & 0xFF;
+ *pb++ = (crc >> 8) & 0xFF;
+ *pb++ = (crc >> 16) & 0xFF;
+ *pb++ = (crc >> 24) & 0xFF;
+ *pb++ = in_len & 0xFF;
+ *pb++ = (in_len >> 8) & 0xFF;
+ *pb++ = (in_len >> 16) & 0xFF;
+ *pb++ = (in_len >> 24) & 0xFF;
+
+ /* Set the real buffer size for the caller */
+ *out_len += FLB_GZIP_HEADER_OFFSET + 8;
+ *out_data = out_buf;
+
+ return 0;
+}
+
+/* Uncompress (inflate) GZip data */
+int flb_gzip_uncompress(void *in_data, size_t in_len,
+ void **out_data, size_t *out_len)
+{
+ int status;
+ uint8_t *p;
+ void *out_buf;
+ size_t out_size = 0;
+ void *zip_data;
+ size_t zip_len;
+ unsigned char flg;
+ unsigned int xlen, hcrc;
+ unsigned int dlen, crc;
+ mz_ulong crc_out;
+ mz_stream stream;
+ const unsigned char *start;
+
+ /* Minimal length: header + crc32 */
+ if (in_len < 18) {
+ flb_error("[gzip] unexpected content length");
+ return -1;
+ }
+
+ /* Magic bytes */
+ p = in_data;
+ if (p[0] != 0x1F || p[1] != 0x8B) {
+ flb_error("[gzip] invalid magic bytes");
+ return -1;
+ }
+
+ if (p[2] != 8) {
+ flb_error("[gzip] invalid method");
+ return -1;
+ }
+
+ /* Flag byte */
+ flg = p[3];
+
+ /* Reserved bits */
+ if (flg & 0xE0) {
+ flb_error("[gzip] invalid flag");
+ return -1;
+ }
+
+ /* Skip base header of 10 bytes */
+ start = p + FLB_GZIP_HEADER_OFFSET;
+
+ /* Skip extra data if present */
+ if (flg & FEXTRA) {
+ xlen = read_le16(start);
+ if (xlen > in_len - 12) {
+ flb_error("[gzip] invalid gzip data");
+ return -1;
+ }
+ start += xlen + 2;
+ }
+
+ /* Skip file name if present */
+ if (flg & FNAME) {
+ do {
+ if (start - p >= in_len) {
+ flb_error("[gzip] invalid gzip data (FNAME)");
+ return -1;
+ }
+ } while (*start++);
+ }
+
+ /* Skip file comment if present */
+ if (flg & FCOMMENT) {
+ do {
+ if (start - p >= in_len) {
+ flb_error("[gzip] invalid gzip data (FCOMMENT)");
+ return -1;
+ }
+ } while (*start++);
+ }
+
+ /* Check header crc if present */
+ if (flg & FHCRC) {
+ if (start - p > in_len - 2) {
+ flb_error("[gzip] invalid gzip data (FHRC)");
+ return -1;
+ }
+
+ hcrc = read_le16(start);
+ crc = mz_crc32(MZ_CRC32_INIT, p, start - p) & 0x0000FFFF;
+ if (hcrc != crc) {
+ flb_error("[gzip] invalid gzip header CRC");
+ return -1;
+ }
+ start += 2;
+ }
+
+ /* Get decompressed length */
+ dlen = read_le32(&p[in_len - 4]);
+
+ /* Limit decompressed length to 100MB */
+ if (dlen > 100000000) {
+ flb_error("[gzip] maximum decompression size is 100MB");
+ return -1;
+ }
+
+ /* Get CRC32 checksum of original data */
+ crc = read_le32(&p[in_len - 8]);
+
+ /* Decompress data */
+ if ((p + in_len) - p < 8) {
+ flb_error("[gzip] invalid gzip CRC32 checksum");
+ return -1;
+ }
+
+ /* Allocate outgoing buffer */
+ out_buf = flb_malloc(dlen);
+ if (!out_buf) {
+ flb_errno();
+ return -1;
+ }
+ out_size = dlen;
+
+ /* Ensure size is above 0 */
+ if (((p + in_len) - start - 8) <= 0) {
+ flb_free(out_buf);
+ return -1;
+ }
+
+ /* Map zip content */
+ zip_data = (uint8_t *) start;
+ zip_len = (p + in_len) - start - 8;
+
+ memset(&stream, 0, sizeof(stream));
+ stream.next_in = zip_data;
+ stream.avail_in = zip_len;
+ stream.next_out = out_buf;
+ stream.avail_out = out_size;
+
+ status = mz_inflateInit2(&stream, -Z_DEFAULT_WINDOW_BITS);
+ if (status != MZ_OK) {
+ flb_free(out_buf);
+ return -1;
+ }
+
+ status = mz_inflate(&stream, MZ_FINISH);
+ if (status != MZ_STREAM_END) {
+ mz_inflateEnd(&stream);
+ flb_free(out_buf);
+ return -1;
+ }
+
+ if (stream.total_out != dlen) {
+ mz_inflateEnd(&stream);
+ flb_free(out_buf);
+ flb_error("[gzip] invalid gzip data size");
+ return -1;
+ }
+
+ /* terminate the stream, it's not longer required */
+ mz_inflateEnd(&stream);
+
+ /* Validate message CRC vs inflated data CRC */
+ crc_out = mz_crc32(MZ_CRC32_INIT, out_buf, dlen);
+ if (crc_out != crc) {
+ flb_free(out_buf);
+ flb_error("[gzip] invalid GZip checksum (CRC32)");
+ return -1;
+ }
+
+ /* set the uncompressed data */
+ *out_len = dlen;
+ *out_data = out_buf;
+
+ return 0;
+}
+
+
+/* Stateful gzip decompressor */
+
+static int flb_gzip_decompressor_process_header(
+ struct flb_decompression_context *context)
+{
+ struct flb_gzip_decompression_context *inner_context;
+
+ inner_context = (struct flb_gzip_decompression_context *) \
+ context->inner_context;
+
+ /* Minimal length: header + crc32 */
+ if (context->input_buffer_length < FLB_GZIP_HEADER_SIZE) {
+ flb_error("[gzip] unexpected content length");
+
+ return FLB_DECOMPRESSOR_FAILURE;
+ }
+
+ memcpy(&inner_context->gzip_header,
+ context->read_buffer,
+ FLB_GZIP_HEADER_SIZE);
+
+ context->read_buffer = &context->read_buffer[FLB_GZIP_HEADER_SIZE];
+ context->input_buffer_length -= FLB_GZIP_HEADER_SIZE;
+
+ /* Magic bytes */
+ if (inner_context->gzip_header.magic_number != FLB_GZIP_MAGIC_NUMBER) {
+ context->state = FLB_DECOMPRESSOR_STATE_FAILED;
+
+ flb_error("[gzip] invalid magic bytes : %04x",
+ inner_context->gzip_header.magic_number);
+
+ return FLB_DECOMPRESSOR_FAILURE;
+ }
+
+ if (inner_context->gzip_header.compression_method != MZ_DEFLATED) {
+ context->state = FLB_DECOMPRESSOR_STATE_FAILED;
+
+ flb_error("[gzip] invalid method : %u",
+ inner_context->gzip_header.compression_method);
+
+ return FLB_DECOMPRESSOR_FAILURE;
+ }
+
+ /* Flag processing */
+ /* Reserved bits */
+ if (inner_context->gzip_header.header_flags & 0xE0) {
+ context->state = FLB_DECOMPRESSOR_STATE_FAILED;
+
+ flb_error("[gzip] invalid flag mask : %x",
+ inner_context->gzip_header.header_flags);
+
+ return FLB_DECOMPRESSOR_FAILURE;
+ }
+
+ context->state = FLB_DECOMPRESSOR_STATE_EXPECTING_OPTIONAL_HEADERS;
+
+ return FLB_DECOMPRESSOR_SUCCESS;
+}
+
+static int flb_gzip_decompressor_process_optional_headers(
+ struct flb_decompression_context *context)
+{
+ struct flb_gzip_decompression_context *inner_context;
+ int status;
+ uint16_t hcrc;
+ uint16_t xlen;
+ uint16_t crc;
+
+ inner_context = (struct flb_gzip_decompression_context *) \
+ context->inner_context;
+
+ /* Skip extra data if present */
+ if (inner_context->gzip_header.header_flags & FEXTRA) {
+ if (context->input_buffer_length <= sizeof(uint16_t)) {
+ return FLB_DECOMPRESSOR_INSUFFICIENT_DATA;
+ }
+
+ xlen = sizeof(uint16_t) + read_le16(context->read_buffer);
+
+ if (context->input_buffer_length < xlen) {
+ return FLB_DECOMPRESSOR_INSUFFICIENT_DATA;
+ }
+
+ context->read_buffer = &context->read_buffer[xlen];
+ context->input_buffer_length -= xlen;
+
+ inner_context->gzip_header.header_flags &= (~FEXTRA);
+ }
+
+ if (inner_context->gzip_header.header_flags != 0 &&
+ context->input_buffer_length == 0) {
+ return FLB_DECOMPRESSOR_INSUFFICIENT_DATA;
+ }
+
+ /* Skip file name if present */
+ if (inner_context->gzip_header.header_flags & FNAME) {
+ xlen = strnlen((char *) context->read_buffer,
+ context->input_buffer_length);
+
+ if (xlen == 0 ||
+ xlen == context->input_buffer_length) {
+ return FLB_DECOMPRESSOR_INSUFFICIENT_DATA;
+ }
+
+ xlen++;
+
+ context->read_buffer = &context->read_buffer[xlen];
+ context->input_buffer_length -= xlen;
+
+ inner_context->gzip_header.header_flags &= (~FNAME);
+ }
+
+ if (inner_context->gzip_header.header_flags != 0 &&
+ context->input_buffer_length == 0) {
+ return FLB_DECOMPRESSOR_INSUFFICIENT_DATA;
+ }
+
+ /* Skip file comment if present */
+ if (inner_context->gzip_header.header_flags & FCOMMENT) {
+ xlen = strnlen((char *) context->read_buffer,
+ context->input_buffer_length);
+
+ if (xlen == 0 ||
+ xlen == context->input_buffer_length) {
+ return FLB_DECOMPRESSOR_INSUFFICIENT_DATA;
+ }
+
+ context->read_buffer = &context->read_buffer[xlen];
+ context->input_buffer_length -= xlen;
+
+ inner_context->gzip_header.header_flags &= (~FCOMMENT);
+ }
+
+ if (inner_context->gzip_header.header_flags != 0 &&
+ context->input_buffer_length == 0) {
+ return FLB_DECOMPRESSOR_INSUFFICIENT_DATA;
+ }
+
+ /* Check header crc if present (lower 16 bits of the checksum)*/
+ if (inner_context->gzip_header.header_flags & FHCRC) {
+ if (context->input_buffer_length <= sizeof(uint16_t)) {
+ return FLB_DECOMPRESSOR_INSUFFICIENT_DATA;
+ }
+
+ hcrc = read_le16(context->read_buffer);
+
+ crc = mz_crc32(MZ_CRC32_INIT,
+ (const unsigned char *) &inner_context->gzip_header,
+ FLB_GZIP_HEADER_SIZE);
+
+ crc &= 0x0000FFFF;
+
+ if (hcrc != crc) {
+ context->state = FLB_DECOMPRESSOR_STATE_FAILED;
+
+ return FLB_DECOMPRESSOR_CORRUPTED_HEADER;
+ }
+
+ xlen = sizeof(uint16_t);
+
+ context->read_buffer = &context->read_buffer[xlen];
+ context->input_buffer_length -= xlen;
+
+ inner_context->gzip_header.header_flags &= (~FHCRC);
+ }
+
+ status = mz_inflateInit2(&inner_context->miniz_stream,
+ -Z_DEFAULT_WINDOW_BITS);
+
+ if (status != MZ_OK) {
+ context->state = FLB_DECOMPRESSOR_STATE_FAILED;
+
+ return FLB_DECOMPRESSOR_FAILURE;
+ }
+
+ context->state = FLB_DECOMPRESSOR_STATE_EXPECTING_BODY;
+
+ return FLB_DECOMPRESSOR_SUCCESS;
+}
+
+static int flb_gzip_decompressor_process_body_chunk(
+ struct flb_decompression_context *context,
+ void *output_buffer,
+ size_t *output_length)
+{
+ size_t processed_bytes;
+ struct flb_gzip_decompression_context *inner_context;
+ int status;
+
+ if (*output_length == 0) {
+ return FLB_DECOMPRESSOR_SUCCESS;
+ }
+
+ inner_context = (struct flb_gzip_decompression_context *) \
+ context->inner_context;
+
+ inner_context->miniz_stream.next_in = context->read_buffer;
+ inner_context->miniz_stream.avail_in = context->input_buffer_length;
+ inner_context->miniz_stream.next_out = output_buffer;
+ inner_context->miniz_stream.avail_out = *output_length;
+
+ status = mz_inflate(&inner_context->miniz_stream, MZ_PARTIAL_FLUSH);
+
+ if (status != MZ_OK && status != MZ_STREAM_END) {
+ context->state = FLB_DECOMPRESSOR_STATE_FAILED;
+
+ mz_inflateEnd(&inner_context->miniz_stream);
+
+ *output_length = 0;
+
+ return FLB_DECOMPRESSOR_FAILURE;
+ }
+
+ processed_bytes = context->input_buffer_length;;
+ processed_bytes -= inner_context->miniz_stream.avail_in;
+
+ *output_length -= inner_context->miniz_stream.avail_out;
+
+#ifdef FLB_DECOMPRESSOR_ERASE_DECOMPRESSED_DATA
+ if (processed_bytes > 0) {
+ memset(context->read_buffer, processed_bytes);
+ }
+#endif
+
+ context->read_buffer = &context->read_buffer[processed_bytes];
+ context->input_buffer_length = inner_context->miniz_stream.avail_in;
+
+ if (status == MZ_STREAM_END) {
+ mz_inflateEnd(&inner_context->miniz_stream);
+
+ context->state = FLB_DECOMPRESSOR_STATE_EXPECTING_FOOTER;
+
+ memset(&inner_context->miniz_stream, 0, sizeof(mz_stream));
+ }
+
+ return FLB_DECOMPRESSOR_SUCCESS;
+}
+
+
+static int flb_gzip_decompressor_process_footer(
+ struct flb_decompression_context *context)
+{
+ if (context->input_buffer_length < (sizeof(uint32_t) * 2)) {
+ return FLB_DECOMPRESSOR_INSUFFICIENT_DATA;
+ }
+
+ context->input_buffer_length -= (sizeof(uint32_t) * 2);
+
+ if (context->input_buffer_length > 0) {
+ context->read_buffer = &context->read_buffer[sizeof(uint32_t) * 2];
+ }
+ else {
+ context->read_buffer = context->input_buffer;
+ }
+
+ context->state = FLB_DECOMPRESSOR_STATE_EXPECTING_HEADER;
+
+ return FLB_DECOMPRESSOR_SUCCESS;
+}
+
+int flb_gzip_decompressor_dispatch(struct flb_decompression_context *context,
+ void *output_buffer,
+ size_t *output_length)
+{
+ size_t output_buffer_size;
+ int status;
+
+ output_buffer_size = *output_length;
+
+ *output_length = 0;
+
+ status = FLB_DECOMPRESSOR_SUCCESS;
+
+ if (context == NULL ||
+ context->inner_context == NULL) {
+ status = FLB_DECOMPRESSOR_FAILURE;
+ }
+
+ if (context->input_buffer_length == 0) {
+ flb_debug("[gzip] unexpected call with an empty input buffer");
+
+ status = FLB_DECOMPRESSOR_INSUFFICIENT_DATA;
+ }
+
+ if (status == FLB_DECOMPRESSOR_SUCCESS &&
+ context->state == FLB_DECOMPRESSOR_STATE_EXPECTING_HEADER) {
+ status = flb_gzip_decompressor_process_header(context);
+ }
+
+ if (status == FLB_DECOMPRESSOR_SUCCESS &&
+ context->state == FLB_DECOMPRESSOR_STATE_EXPECTING_OPTIONAL_HEADERS) {
+ status = flb_gzip_decompressor_process_optional_headers(context);
+ }
+
+ if (status == FLB_DECOMPRESSOR_SUCCESS &&
+ context->state == FLB_DECOMPRESSOR_STATE_EXPECTING_BODY) {
+ *output_length = output_buffer_size;
+
+ status = flb_gzip_decompressor_process_body_chunk(
+ context,
+ output_buffer,
+ output_length);
+ }
+
+ if (status == FLB_DECOMPRESSOR_SUCCESS &&
+ context->state == FLB_DECOMPRESSOR_STATE_EXPECTING_FOOTER) {
+ status = flb_gzip_decompressor_process_footer(context);
+ }
+
+ return status;
+}
+
+void *flb_gzip_decompression_context_create()
+{
+ struct flb_gzip_decompression_context *context;
+
+ context = flb_calloc(1, sizeof(struct flb_gzip_decompression_context));
+
+ if (context == NULL) {
+ flb_errno();
+ }
+
+ return (void *) context;
+}
+
+void flb_gzip_decompression_context_destroy(void *context)
+{
+ if (context != NULL) {
+ flb_free(context);
+ }
+}
diff --git a/fluent-bit/src/flb_hash.c b/fluent-bit/src/flb_hash.c
new file mode 100644
index 000000000..eef18d868
--- /dev/null
+++ b/fluent-bit/src/flb_hash.c
@@ -0,0 +1,205 @@
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2019-2020 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_hash.h>
+#include <openssl/bio.h>
+
+static const EVP_MD *flb_crypto_get_digest_algorithm_instance_by_id(int algorithm_id)
+{
+ const EVP_MD *algorithm;
+
+ if (algorithm_id == FLB_HASH_SHA256) {
+ algorithm = EVP_sha256();
+ }
+ else if (algorithm_id == FLB_HASH_SHA512) {
+ algorithm = EVP_sha512();
+ }
+ else if (algorithm_id == FLB_HASH_MD5) {
+ algorithm = EVP_md5();
+ }
+ else {
+ algorithm = NULL;
+ }
+
+ return algorithm;
+}
+
+int flb_hash_init(struct flb_hash *context, int hash_type)
+{
+ const EVP_MD *digest_algorithm;
+ int result;
+
+ if (context == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ digest_algorithm = flb_crypto_get_digest_algorithm_instance_by_id(hash_type);
+
+ if (digest_algorithm == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ context->backend_context = EVP_MD_CTX_create();
+
+ if (context->backend_context == NULL) {
+ context->last_error = ERR_get_error();
+
+ return FLB_CRYPTO_BACKEND_ERROR;
+ }
+
+ result = EVP_DigestInit_ex(context->backend_context, digest_algorithm, 0);
+
+ if (result == 0) {
+ context->last_error = ERR_get_error();
+
+ return FLB_CRYPTO_BACKEND_ERROR;
+ }
+
+ context->digest_size = EVP_MD_CTX_size(context->backend_context);
+
+ return FLB_CRYPTO_SUCCESS;
+}
+
+int flb_hash_finalize(struct flb_hash *context,
+ unsigned char *digest_buffer,
+ size_t digest_buffer_size)
+{
+ unsigned int digest_length;
+ int result;
+
+ if (context->backend_context == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ if (digest_buffer == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ if (digest_buffer_size < context->digest_size) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ result = EVP_DigestFinal_ex(context->backend_context,
+ digest_buffer, &digest_length);
+
+ if (result == 0) {
+ context->last_error = ERR_get_error();
+
+ return FLB_CRYPTO_BACKEND_ERROR;
+ }
+
+ (void) digest_length;
+
+ return FLB_CRYPTO_SUCCESS;
+}
+
+int flb_hash_update(struct flb_hash *context,
+ unsigned char *data,
+ size_t data_length)
+{
+ int result;
+
+ if (context->backend_context == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ if (data == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ result = EVP_DigestUpdate(context->backend_context,
+ data,
+ data_length);
+
+ if (result == 0) {
+ context->last_error = ERR_get_error();
+
+ return FLB_CRYPTO_BACKEND_ERROR;
+ }
+
+ return FLB_CRYPTO_SUCCESS;
+}
+
+int flb_hash_cleanup(struct flb_hash *context)
+{
+ if (context->backend_context == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ EVP_MD_CTX_destroy(context->backend_context);
+
+ context->backend_context = NULL;
+
+ return FLB_CRYPTO_SUCCESS;
+}
+
+int flb_hash_simple_batch(int hash_type,
+ size_t entry_count,
+ unsigned char **data_entries,
+ size_t *length_entries,
+ unsigned char *digest_buffer,
+ size_t digest_buffer_size)
+{
+ struct flb_hash digest_context;
+ size_t entry_index;
+ int result;
+
+ result = flb_hash_init(&digest_context, hash_type);
+
+ if (result == FLB_CRYPTO_SUCCESS) {
+ for (entry_index = 0 ;
+ entry_index < entry_count && result == FLB_CRYPTO_SUCCESS;
+ entry_index++) {
+ if (data_entries[entry_index] != NULL &&
+ length_entries[entry_index] > 0) {
+ result = flb_hash_update(&digest_context,
+ data_entries[entry_index],
+ length_entries[entry_index]);
+ }
+ }
+
+ if (result == FLB_CRYPTO_SUCCESS) {
+ result = flb_hash_finalize(&digest_context,
+ digest_buffer,
+ digest_buffer_size);
+ }
+
+ flb_hash_cleanup(&digest_context);
+ }
+
+ return result;
+}
+
+int flb_hash_simple(int hash_type,
+ unsigned char *data,
+ size_t data_length,
+ unsigned char *digest_buffer,
+ size_t digest_buffer_size)
+{
+ size_t length_entries[1];
+ unsigned char *data_entries[1];
+
+ data_entries[0] = data;
+ length_entries[0] = data_length;
+
+ return flb_hash_simple_batch(hash_type,
+ 1,
+ data_entries,
+ length_entries,
+ digest_buffer,
+ digest_buffer_size);
+}
diff --git a/fluent-bit/src/flb_hash_table.c b/fluent-bit/src/flb_hash_table.c
new file mode 100644
index 000000000..d31c42591
--- /dev/null
+++ b/fluent-bit/src/flb_hash_table.c
@@ -0,0 +1,539 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_hash_table.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_str.h>
+
+#include <cfl/cfl.h>
+
+static inline void flb_hash_table_entry_free(struct flb_hash_table *ht,
+ struct flb_hash_table_entry *entry)
+{
+ mk_list_del(&entry->_head);
+ mk_list_del(&entry->_head_parent);
+ entry->table->count--;
+ ht->total_count--;
+ flb_free(entry->key);
+ if (entry->val && entry->val_size > 0) {
+ flb_free(entry->val);
+ }
+ flb_free(entry);
+}
+
+struct flb_hash_table *flb_hash_table_create(int evict_mode, size_t size, int max_entries)
+{
+ int i;
+ struct flb_hash_table_chain *tmp;
+ struct flb_hash_table *ht;
+
+ if (size <= 0) {
+ return NULL;
+ }
+
+ ht = flb_malloc(sizeof(struct flb_hash_table));
+ if (!ht) {
+ flb_errno();
+ return NULL;
+ }
+
+ mk_list_init(&ht->entries);
+ ht->evict_mode = evict_mode;
+ ht->max_entries = max_entries;
+ ht->size = size;
+ ht->total_count = 0;
+ ht->cache_ttl = 0;
+ ht->table = flb_calloc(1, sizeof(struct flb_hash_table_chain) * size);
+ if (!ht->table) {
+ flb_errno();
+ flb_free(ht);
+ return NULL;
+ }
+
+ /* Initialize chains list head */
+ for (i = 0; i < size; i++) {
+ tmp = &ht->table[i];
+ tmp->count = 0;
+ mk_list_init(&tmp->chains);
+ }
+
+ return ht;
+}
+
+struct flb_hash_table *flb_hash_table_create_with_ttl(int cache_ttl, int evict_mode,
+ size_t size, int max_entries)
+{
+ struct flb_hash_table *ht;
+
+ ht = flb_hash_table_create(evict_mode, size, max_entries);
+ if (!ht) {
+ flb_errno();
+ return NULL;
+ }
+
+ ht->cache_ttl = cache_ttl;
+ return ht;
+}
+
+int flb_hash_table_del_ptr(struct flb_hash_table *ht, const char *key, int key_len,
+ void *ptr)
+{
+ int id;
+ uint64_t hash;
+ struct mk_list *head;
+ struct flb_hash_table_entry *entry = NULL;
+ struct flb_hash_table_chain *table;
+
+ /* Generate hash number */
+ hash = cfl_hash_64bits(key, key_len);
+ id = (hash % ht->size);
+
+ /* Link the new entry in our table at the end of the list */
+ table = &ht->table[id];
+
+ mk_list_foreach(head, &table->chains) {
+ entry = mk_list_entry(head, struct flb_hash_table_entry, _head);
+ if (strncmp(entry->key, key, key_len) == 0 && entry->val == ptr) {
+ break;
+ }
+ entry = NULL;
+ }
+
+ if (!entry) {
+ return -1;
+ }
+
+ /* delete the entry */
+ flb_hash_table_entry_free(ht, entry);
+ return 0;
+}
+
+
+void flb_hash_table_destroy(struct flb_hash_table *ht)
+{
+ int i;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_hash_table_entry *entry;
+ struct flb_hash_table_chain *table;
+
+ for (i = 0; i < ht->size; i++) {
+ table = &ht->table[i];
+ mk_list_foreach_safe(head, tmp, &table->chains) {
+ entry = mk_list_entry(head, struct flb_hash_table_entry, _head);
+ flb_hash_table_entry_free(ht, entry);
+ }
+ }
+
+ flb_free(ht->table);
+ flb_free(ht);
+}
+
+static void flb_hash_table_evict_random(struct flb_hash_table *ht)
+{
+ int id;
+ int count = 0;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_hash_table_entry *entry;
+
+ id = random() % ht->total_count;
+ mk_list_foreach_safe(head, tmp, &ht->entries) {
+ if (id == count) {
+ entry = mk_list_entry(head, struct flb_hash_table_entry, _head_parent);
+ flb_hash_table_entry_free(ht, entry);
+ break;
+ }
+ count++;
+ }
+}
+
+static void flb_hash_table_evict_less_used(struct flb_hash_table *ht)
+{
+ struct mk_list *head;
+ struct flb_hash_table_entry *entry;
+ struct flb_hash_table_entry *entry_less_used = NULL;
+
+ mk_list_foreach(head, &ht->entries) {
+ entry = mk_list_entry(head, struct flb_hash_table_entry, _head_parent);
+ if (!entry_less_used) {
+ entry_less_used = entry;
+ }
+ else if (entry->hits < entry_less_used->hits) {
+ entry_less_used = entry;
+ }
+ }
+
+ flb_hash_table_entry_free(ht, entry_less_used);
+}
+
+static void flb_hash_table_evict_older(struct flb_hash_table *ht)
+{
+ struct flb_hash_table_entry *entry;
+
+ entry = mk_list_entry_first(&ht->entries, struct flb_hash_table_entry, _head_parent);
+ flb_hash_table_entry_free(ht, entry);
+}
+
+static struct flb_hash_table_entry *hash_get_entry(struct flb_hash_table *ht,
+ const char *key, int key_len, int *out_id)
+{
+ int id;
+ uint64_t hash;
+ struct mk_list *head;
+ struct flb_hash_table_chain *table;
+ struct flb_hash_table_entry *entry;
+
+ if (!key || key_len <= 0) {
+ return NULL;
+ }
+
+ hash = cfl_hash_64bits(key, key_len);
+ id = (hash % ht->size);
+
+ table = &ht->table[id];
+ if (table->count == 0) {
+ return NULL;
+ }
+
+ if (table->count == 1) {
+ entry = mk_list_entry_first(&table->chains,
+ struct flb_hash_table_entry, _head);
+
+ if (entry->key_len != key_len
+ || strncmp(entry->key, key, key_len) != 0) {
+ entry = NULL;
+ }
+ }
+ else {
+ /* Iterate entries */
+ mk_list_foreach(head, &table->chains) {
+ entry = mk_list_entry(head, struct flb_hash_table_entry, _head);
+ if (entry->key_len != key_len) {
+ entry = NULL;
+ continue;
+ }
+
+ if (strncmp(entry->key, key, key_len) == 0) {
+ break;
+ }
+
+ entry = NULL;
+ }
+ }
+
+ if (entry) {
+ *out_id = id;
+ }
+
+ return entry;
+}
+
+static int entry_set_value(struct flb_hash_table_entry *entry, void *val, size_t val_size)
+{
+ char *ptr;
+
+ /*
+ * If the entry already contains a previous value in the heap, just remove
+ * the previously assigned memory.
+ */
+ if (entry->val_size > 0) {
+ flb_free(entry->val);
+ }
+
+ /*
+ * Now set the new value. If val_size > 0, we create a new memory area, otherwise
+ * it means the caller just wants to store a pointer address, no allocation
+ * is required.
+ */
+ if (val_size > 0) {
+ entry->val = flb_malloc(val_size + 1);
+ if (!entry->val) {
+ flb_errno();
+ return -1;
+ }
+
+ /*
+ * Copy the buffer and append a NULL byte in case the caller set and
+ * expects a string.
+ */
+ memcpy(entry->val, val, val_size);
+ ptr = (char *) entry->val;
+ ptr[val_size] = '\0';
+ entry->val_size = val_size;
+ }
+ else {
+ /* just do a reference */
+ entry->val = val;
+ entry->val_size = -1;
+ }
+
+ entry->created = time(NULL);
+
+ return 0;
+}
+
+int flb_hash_table_add(struct flb_hash_table *ht, const char *key, int key_len,
+ void *val, ssize_t val_size)
+{
+ int id;
+ int ret;
+ uint64_t hash;
+ struct flb_hash_table_entry *entry;
+ struct flb_hash_table_chain *table;
+
+ if (!key || key_len <= 0) {
+ return -1;
+ }
+
+ /* Check capacity */
+ if (ht->max_entries > 0 && ht->total_count >= ht->max_entries) {
+ if (ht->evict_mode == FLB_HASH_TABLE_EVICT_NONE) {
+ /* Do nothing */
+ }
+ else if (ht->evict_mode == FLB_HASH_TABLE_EVICT_OLDER) {
+ flb_hash_table_evict_older(ht);
+ }
+ else if (ht->evict_mode == FLB_HASH_TABLE_EVICT_LESS_USED) {
+ flb_hash_table_evict_less_used(ht);
+ }
+ else if (ht->evict_mode == FLB_HASH_TABLE_EVICT_RANDOM) {
+ flb_hash_table_evict_random(ht);
+ }
+ }
+
+ /* Check if this is a replacement */
+ entry = hash_get_entry(ht, key, key_len, &id);
+ if (entry) {
+ /*
+ * The key already exists, just perform a value replacement, check if the
+ * value refers to our own previous allocation.
+ */
+ ret = entry_set_value(entry, val, val_size);
+ if (ret == -1) {
+ return -1;
+ }
+
+ return id;
+ }
+
+ /*
+ * Below is just code to handle the creation of a new entry in the table
+ */
+
+ /* Generate hash number */
+ hash = cfl_hash_64bits(key, key_len);
+ id = (hash % ht->size);
+
+ /* Allocate the entry */
+ entry = flb_calloc(1, sizeof(struct flb_hash_table_entry));
+ if (!entry) {
+ flb_errno();
+ return -1;
+ }
+ entry->created = time(NULL);
+ entry->hash = hash;
+ entry->hits = 0;
+
+ /* Store the key and value as a new memory region */
+ entry->key = flb_strndup(key, key_len);
+ entry->key_len = key_len;
+ entry->val_size = 0;
+
+ /* store or reference the value */
+ ret = entry_set_value(entry, val, val_size);
+ if (ret == -1) {
+ flb_free(entry);
+ return -1;
+ }
+
+ /* Link the new entry in our table at the end of the list */
+ table = &ht->table[id];
+ entry->table = table;
+
+ /* Add the new entry */
+ mk_list_add(&entry->_head, &table->chains);
+ mk_list_add(&entry->_head_parent, &ht->entries);
+
+ /* Update counters */
+ table->count++;
+ ht->total_count++;
+
+ return id;
+}
+
+int flb_hash_table_get(struct flb_hash_table *ht,
+ const char *key, int key_len,
+ void **out_buf, size_t *out_size)
+{
+ int id;
+ struct flb_hash_table_entry *entry;
+ time_t expiration;
+
+ entry = hash_get_entry(ht, key, key_len, &id);
+ if (!entry) {
+ return -1;
+ }
+
+ if (ht->cache_ttl > 0) {
+ expiration = entry->created + ht->cache_ttl;
+ if (time(NULL) > expiration) {
+ flb_hash_table_entry_free(ht, entry);
+ return -1;
+ }
+ }
+
+ entry->hits++;
+ *out_buf = entry->val;
+ *out_size = entry->val_size;
+
+ return id;
+}
+
+/* check if a hash exists */
+int flb_hash_table_exists(struct flb_hash_table *ht, uint64_t hash)
+{
+ int id;
+ struct mk_list *head;
+ struct flb_hash_table_chain *table;
+ struct flb_hash_table_entry *entry;
+
+ id = (hash % ht->size);
+ table = &ht->table[id];
+
+ /* Iterate entries */
+ mk_list_foreach(head, &table->chains) {
+ entry = mk_list_entry(head, struct flb_hash_table_entry, _head);
+ if (entry->hash == hash) {
+ return FLB_TRUE;
+ }
+ }
+
+ return FLB_FALSE;
+}
+
+/*
+ * Get an entry based in the table id. Note that a table id might have multiple
+ * entries so the 'key' parameter is required to get an exact match.
+ */
+int flb_hash_table_get_by_id(struct flb_hash_table *ht, int id,
+ const char *key,
+ const char **out_buf, size_t * out_size)
+{
+ struct mk_list *head;
+ struct flb_hash_table_entry *entry = NULL;
+ struct flb_hash_table_chain *table;
+
+ if (ht->size <= id) {
+ return -1;
+ }
+
+ table = &ht->table[id];
+ if (table->count == 0) {
+ return -1;
+ }
+
+ if (table->count == 1) {
+ entry = mk_list_entry_first(&table->chains,
+ struct flb_hash_table_entry, _head);
+ }
+ else {
+ mk_list_foreach(head, &table->chains) {
+ entry = mk_list_entry(head, struct flb_hash_table_entry, _head);
+ if (strcmp(entry->key, key) == 0) {
+ break;
+ }
+ entry = NULL;
+ }
+ }
+
+ if (!entry) {
+ return -1;
+ }
+
+ *out_buf = entry->val;
+ *out_size = entry->val_size;
+
+ return 0;
+}
+
+void *flb_hash_table_get_ptr(struct flb_hash_table *ht, const char *key, int key_len)
+{
+ int id;
+ struct flb_hash_table_entry *entry;
+
+ entry = hash_get_entry(ht, key, key_len, &id);
+ if (!entry) {
+ return NULL;
+ }
+
+ entry->hits++;
+ return entry->val;
+}
+
+int flb_hash_table_del(struct flb_hash_table *ht, const char *key)
+{
+ int id;
+ int len;
+ uint64_t hash;
+ struct mk_list *head;
+ struct flb_hash_table_entry *entry = NULL;
+ struct flb_hash_table_chain *table;
+
+ if (!key) {
+ return -1;
+ }
+
+ len = strlen(key);
+ if (len == 0) {
+ return -1;
+ }
+
+ hash = cfl_hash_64bits(key, len);
+ id = (hash % ht->size);
+
+ table = &ht->table[id];
+ if (table->count == 1) {
+ entry = mk_list_entry_first(&table->chains,
+ struct flb_hash_table_entry,
+ _head);
+ if (strcmp(entry->key, key) != 0) {
+ entry = NULL;
+ }
+ }
+ else {
+ mk_list_foreach(head, &table->chains) {
+ entry = mk_list_entry(head, struct flb_hash_table_entry, _head);
+ if (strcmp(entry->key, key) == 0) {
+ break;
+ }
+ entry = NULL;
+ }
+ }
+
+ if (!entry) {
+ return -1;
+ }
+
+ flb_hash_table_entry_free(ht, entry);
+
+ return 0;
+}
diff --git a/fluent-bit/src/flb_help.c b/fluent-bit/src/flb_help.c
new file mode 100644
index 000000000..d93f9680b
--- /dev/null
+++ b/fluent-bit/src/flb_help.c
@@ -0,0 +1,655 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_version.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_help.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_mp.h>
+#include <fluent-bit/flb_custom.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_filter.h>
+#include <fluent-bit/flb_output.h>
+
+
+
+static inline void pack_str_s(msgpack_packer *mp_pck, char *str, int size)
+{
+ int len;
+
+ len = strlen(str);
+ msgpack_pack_str(mp_pck, len);
+
+ if (len > 0) {
+ msgpack_pack_str_body(mp_pck, str, len);
+ }
+}
+
+static inline void pack_str(msgpack_packer *mp_pck, char *str)
+{
+ int size = strlen(str);
+ pack_str_s(mp_pck, str, size);
+}
+
+int pack_config_map_entry(msgpack_packer *mp_pck, struct flb_config_map *m)
+{
+ int len;
+ struct flb_mp_map_header mh;
+
+ flb_mp_map_header_init(&mh, mp_pck);
+
+ /* name */
+ flb_mp_map_header_append(&mh);
+ pack_str(mp_pck, "name");
+ pack_str(mp_pck, m->name);
+
+ /* description */
+ flb_mp_map_header_append(&mh);
+ pack_str(mp_pck, "description");
+ if (m->desc) {
+ pack_str(mp_pck, m->desc);
+ }
+ else {
+ pack_str(mp_pck, "");
+ }
+
+ /* default value */
+ flb_mp_map_header_append(&mh);
+ pack_str(mp_pck, "default");
+ if (m->def_value) {
+ pack_str(mp_pck, m->def_value);
+ }
+ else {
+ msgpack_pack_nil(mp_pck);
+ }
+
+ /* type */
+ flb_mp_map_header_append(&mh);
+ pack_str(mp_pck, "type");
+
+ if (m->type == FLB_CONFIG_MAP_STR) {
+ pack_str(mp_pck, "string");
+ }
+ else if (m->type == FLB_CONFIG_MAP_DEPRECATED) {
+ pack_str(mp_pck, "deprecated");
+ }
+ else if (m->type == FLB_CONFIG_MAP_INT) {
+ pack_str(mp_pck, "integer");
+ }
+ else if (m->type == FLB_CONFIG_MAP_BOOL) {
+ pack_str(mp_pck, "boolean");
+ }
+ else if(m->type == FLB_CONFIG_MAP_DOUBLE) {
+ pack_str(mp_pck, "double");
+ }
+ else if (m->type == FLB_CONFIG_MAP_SIZE) {
+ pack_str(mp_pck, "size");
+ }
+ else if (m->type == FLB_CONFIG_MAP_TIME) {
+ pack_str(mp_pck, "time");
+ }
+ else if (flb_config_map_mult_type(m->type) == FLB_CONFIG_MAP_CLIST) {
+ len = flb_config_map_expected_values(m->type);
+ if (len == -1) {
+ pack_str(mp_pck, "multiple comma delimited strings");
+ }
+ else {
+ char tmp[64];
+ snprintf(tmp, sizeof(tmp) - 1,
+ "comma delimited strings (minimum %i)", len);
+ pack_str(mp_pck, tmp);
+ }
+ }
+ else if (flb_config_map_mult_type(m->type) == FLB_CONFIG_MAP_SLIST) {
+ len = flb_config_map_expected_values(m->type);
+ if (len == -1) {
+ pack_str(mp_pck, "multiple space delimited strings");
+ }
+ else {
+ char tmp[64];
+ snprintf(tmp, sizeof(tmp) - 1,
+ "space delimited strings (minimum %i)", len);
+ pack_str(mp_pck, tmp);
+ }
+ }
+ else if (m->type == FLB_CONFIG_MAP_STR_PREFIX) {
+ pack_str(mp_pck, "prefixed string");
+ }
+
+ flb_mp_map_header_end(&mh);
+ return 0;
+}
+
+int flb_help_custom(struct flb_custom_instance *ins, void **out_buf, size_t *out_size)
+{
+ struct mk_list *head;
+ struct mk_list *config_map;
+ struct flb_mp_map_header mh;
+ struct flb_config_map *m;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ msgpack_pack_map(&mp_pck, 4);
+
+ /* plugin type */
+ pack_str(&mp_pck, "type");
+ pack_str(&mp_pck, "custom");
+
+ /* plugin name */
+ pack_str(&mp_pck, "name");
+ pack_str(&mp_pck, ins->p->name);
+
+ /* description */
+ pack_str(&mp_pck, "description");
+ pack_str(&mp_pck, ins->p->description);
+
+ /* list of properties */
+ pack_str(&mp_pck, "properties");
+ flb_mp_map_header_init(&mh, &mp_pck);
+
+ /* properties['options']: options exposed by the plugin */
+ if (ins->p->config_map) {
+ flb_mp_map_header_append(&mh);
+ pack_str(&mp_pck, "options");
+
+ config_map = flb_config_map_create(ins->config, ins->p->config_map);
+ msgpack_pack_array(&mp_pck, mk_list_size(config_map));
+ mk_list_foreach(head, config_map) {
+ m = mk_list_entry(head, struct flb_config_map, _head);
+ pack_config_map_entry(&mp_pck, m);
+ }
+ flb_config_map_destroy(config_map);
+ }
+
+ flb_mp_map_header_end(&mh);
+
+ *out_buf = mp_sbuf.data;
+ *out_size = mp_sbuf.size;
+
+ return 0;
+}
+
+int flb_help_input(struct flb_input_instance *ins, void **out_buf, size_t *out_size)
+{
+ struct mk_list *head;
+ struct mk_list *config_map;
+ struct flb_mp_map_header mh;
+ struct flb_config_map *m;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+ int options_size = 0;
+ struct mk_list *tls_config;
+ struct flb_config_map m_input_net_listen = {
+ .type = FLB_CONFIG_MAP_STR,
+ .name = "host",
+ .def_value = "0.0.0.0",
+ .desc = "Listen Address",
+ };
+ struct flb_config_map m_input_net_port = {
+ .type = FLB_CONFIG_MAP_INT,
+ .name = "port",
+ .def_value = "0",
+ .desc = "Listen Port",
+ };
+
+
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ msgpack_pack_map(&mp_pck, 4);
+
+ /* plugin type */
+ pack_str(&mp_pck, "type");
+ pack_str(&mp_pck, "input");
+
+ /* plugin name */
+ pack_str(&mp_pck, "name");
+ pack_str(&mp_pck, ins->p->name);
+
+ /* description */
+ pack_str(&mp_pck, "description");
+ pack_str(&mp_pck, ins->p->description);
+
+ /* list of properties */
+ pack_str(&mp_pck, "properties");
+ flb_mp_map_header_init(&mh, &mp_pck);
+
+ /* properties['options']: options exposed by the plugin */
+ if (ins->p->config_map) {
+ flb_mp_map_header_append(&mh);
+ pack_str(&mp_pck, "options");
+
+ config_map = flb_config_map_create(ins->config, ins->p->config_map);
+ options_size = mk_list_size(config_map);
+
+ if ((ins->flags & (FLB_INPUT_NET | FLB_INPUT_NET_SERVER)) != 0) {
+ options_size += 2;
+ }
+ if (ins->flags & FLB_IO_OPT_TLS) {
+ tls_config = flb_tls_get_config_map(ins->config);
+ options_size += mk_list_size(tls_config);
+ }
+
+ msgpack_pack_array(&mp_pck, options_size);
+
+ if ((ins->flags & (FLB_INPUT_NET | FLB_INPUT_NET_SERVER)) != 0) {
+ pack_config_map_entry(&mp_pck, &m_input_net_listen);
+ pack_config_map_entry(&mp_pck, &m_input_net_port);
+ }
+ if (ins->flags & FLB_IO_OPT_TLS) {
+ mk_list_foreach(head, tls_config) {
+ m = mk_list_entry(head, struct flb_config_map, _head);
+ pack_config_map_entry(&mp_pck, m);
+ }
+ flb_config_map_destroy(tls_config);
+ }
+
+ mk_list_foreach(head, config_map) {
+ m = mk_list_entry(head, struct flb_config_map, _head);
+ pack_config_map_entry(&mp_pck, m);
+ }
+ flb_config_map_destroy(config_map);
+ }
+
+ flb_mp_map_header_end(&mh);
+
+ *out_buf = mp_sbuf.data;
+ *out_size = mp_sbuf.size;
+
+ return 0;
+}
+
+int flb_help_filter(struct flb_filter_instance *ins, void **out_buf, size_t *out_size)
+{
+ struct mk_list *head;
+ struct mk_list *config_map;
+ struct flb_mp_map_header mh;
+ struct flb_config_map *m;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ msgpack_pack_map(&mp_pck, 4);
+
+ /* plugin type */
+ pack_str(&mp_pck, "type");
+ pack_str(&mp_pck, "filter");
+
+ /* plugin name */
+ pack_str(&mp_pck, "name");
+ pack_str(&mp_pck, ins->p->name);
+
+ /* description */
+ pack_str(&mp_pck, "description");
+ pack_str(&mp_pck, ins->p->description);
+
+ /* list of properties */
+ pack_str(&mp_pck, "properties");
+ flb_mp_map_header_init(&mh, &mp_pck);
+
+ /* properties['options']: options exposed by the plugin */
+ if (ins->p->config_map) {
+ flb_mp_map_header_append(&mh);
+ pack_str(&mp_pck, "options");
+
+ config_map = flb_config_map_create(ins->config, ins->p->config_map);
+ msgpack_pack_array(&mp_pck, mk_list_size(config_map));
+ mk_list_foreach(head, config_map) {
+ m = mk_list_entry(head, struct flb_config_map, _head);
+ pack_config_map_entry(&mp_pck, m);
+ }
+ flb_config_map_destroy(config_map);
+ }
+
+ flb_mp_map_header_end(&mh);
+
+ *out_buf = mp_sbuf.data;
+ *out_size = mp_sbuf.size;
+
+ return 0;
+}
+
+int flb_help_output(struct flb_output_instance *ins, void **out_buf, size_t *out_size)
+{
+ struct mk_list *head;
+ struct mk_list *config_map;
+ struct flb_mp_map_header mh;
+ struct flb_config_map *m;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+ int options_size = 0;
+ struct mk_list *tls_config;
+ struct flb_config_map m_output_net_host = {
+ .type = FLB_CONFIG_MAP_STR,
+ .name = "host",
+ .def_value = "",
+ .flags = 0,
+ .desc = "Host Address",
+ };
+ struct flb_config_map m_output_net_port = {
+ .type = FLB_CONFIG_MAP_INT,
+ .name = "port",
+ .def_value = "0",
+ .flags = 0,
+ .desc = "host Port",
+ };
+
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ msgpack_pack_map(&mp_pck, 4);
+
+ /* plugin type */
+ pack_str(&mp_pck, "type");
+ pack_str(&mp_pck, "output");
+
+ /* plugin name */
+ pack_str(&mp_pck, "name");
+ pack_str(&mp_pck, ins->p->name);
+
+ /* description */
+ pack_str(&mp_pck, "description");
+ pack_str(&mp_pck, ins->p->description);
+
+ /* list of properties */
+ pack_str(&mp_pck, "properties");
+ flb_mp_map_header_init(&mh, &mp_pck);
+
+ /* properties['options']: options exposed by the plugin */
+ if (ins->p->config_map) {
+ flb_mp_map_header_append(&mh);
+ pack_str(&mp_pck, "options");
+
+ config_map = flb_config_map_create(ins->config, ins->p->config_map);
+ options_size = mk_list_size(config_map);
+
+ options_size = mk_list_size(config_map);
+ if (ins->flags & FLB_OUTPUT_NET) {
+ options_size += 2;
+ }
+ if (ins->flags & FLB_IO_OPT_TLS) {
+ tls_config = flb_tls_get_config_map(ins->config);
+ options_size += mk_list_size(tls_config);
+ }
+
+ msgpack_pack_array(&mp_pck, options_size);
+
+ if (ins->flags & FLB_OUTPUT_NET) {
+ pack_config_map_entry(&mp_pck, &m_output_net_host);
+ pack_config_map_entry(&mp_pck, &m_output_net_port);
+ }
+ if (ins->flags & FLB_IO_OPT_TLS) {
+ mk_list_foreach(head, tls_config) {
+ m = mk_list_entry(head, struct flb_config_map, _head);
+ pack_config_map_entry(&mp_pck, m);
+ }
+ flb_config_map_destroy(tls_config);
+ }
+
+ mk_list_foreach(head, config_map) {
+ m = mk_list_entry(head, struct flb_config_map, _head);
+ pack_config_map_entry(&mp_pck, m);
+ }
+ flb_config_map_destroy(config_map);
+ }
+
+ if (ins->p->flags & FLB_OUTPUT_NET) {
+ flb_mp_map_header_append(&mh);
+ pack_str(&mp_pck, "networking");
+
+ config_map = flb_upstream_get_config_map(ins->config);
+ msgpack_pack_array(&mp_pck, mk_list_size(config_map));
+ mk_list_foreach(head, config_map) {
+ m = mk_list_entry(head, struct flb_config_map, _head);
+ pack_config_map_entry(&mp_pck, m);
+ }
+ flb_config_map_destroy(config_map);
+ }
+
+ if (ins->p->flags & (FLB_IO_TLS | FLB_IO_OPT_TLS)) {
+ flb_mp_map_header_append(&mh);
+ pack_str(&mp_pck, "network_tls");
+
+ config_map = flb_tls_get_config_map(ins->config);
+ msgpack_pack_array(&mp_pck, mk_list_size(config_map));
+
+ /* Adjust 'tls' default value based on plugin type" */
+ m = mk_list_entry_first(config_map, struct flb_config_map, _head);
+ if (ins->p->flags & FLB_IO_TLS) {
+ m->value.val.boolean = FLB_TRUE;
+ }
+ else if (ins->p->flags & FLB_IO_OPT_TLS) {
+ m->value.val.boolean = FLB_FALSE;
+ }
+ mk_list_foreach(head, config_map) {
+ m = mk_list_entry(head, struct flb_config_map, _head);
+ pack_config_map_entry(&mp_pck, m);
+ }
+ flb_config_map_destroy(config_map);
+ }
+ flb_mp_map_header_end(&mh);
+
+ *out_buf = mp_sbuf.data;
+ *out_size = mp_sbuf.size;
+
+ return 0;
+}
+
+static int build_plugin_help(struct flb_config *config, int type, char *name,
+ char **out_buf, size_t *out_size)
+{
+ void *help_buf = NULL;
+ size_t help_size = 0;
+ struct flb_custom_instance *c = NULL;
+ struct flb_input_instance *i = NULL;
+ struct flb_filter_instance *f = NULL;
+ struct flb_output_instance *o = NULL;
+
+ if (type == FLB_HELP_PLUGIN_CUSTOM) {
+ c = flb_custom_new(config, name, NULL);
+ if (!c) {
+ fprintf(stderr, "invalid custom plugin '%s'", name);
+ return -1;
+ }
+ flb_help_custom(c, &help_buf, &help_size);
+ flb_custom_instance_destroy(c);
+ }
+ else if (type == FLB_HELP_PLUGIN_INPUT) {
+ i = flb_input_new(config, name, 0, FLB_TRUE);
+ if (!i) {
+ fprintf(stderr, "invalid input plugin '%s'", name);
+ return -1;
+ }
+ flb_help_input(i, &help_buf, &help_size);
+ flb_input_instance_destroy(i);
+ }
+ else if (type == FLB_HELP_PLUGIN_FILTER) {
+ f = flb_filter_new(config, name, 0);
+ if (!f) {
+ fprintf(stderr, "invalid filter plugin '%s'", name);
+ return -1;
+ }
+ flb_help_filter(f, &help_buf, &help_size);
+ flb_filter_instance_destroy(f);
+ }
+ else if (type == FLB_HELP_PLUGIN_OUTPUT) {
+ o = flb_output_new(config, name, 0, FLB_TRUE);
+ if (!o) {
+ fprintf(stderr, "invalid output plugin '%s'", name);
+ return -1;
+ }
+ flb_help_output(o, &help_buf, &help_size);
+ flb_output_instance_destroy(o);
+ }
+
+ *out_buf = help_buf;
+ *out_size = help_size;
+
+ return 0;
+}
+
+static void pack_map_kv(msgpack_packer *mp_pck, char *key, char *val)
+{
+ int k_len;
+ int v_len;
+
+ k_len = strlen(key);
+ v_len = strlen(val);
+
+ msgpack_pack_str(mp_pck, k_len);
+ msgpack_pack_str_body(mp_pck, key, k_len);
+
+ msgpack_pack_str(mp_pck, v_len);
+ msgpack_pack_str_body(mp_pck, val, v_len);
+
+}
+
+flb_sds_t flb_help_build_json_schema(struct flb_config *config)
+{
+ int ret;
+ char *out_buf;
+ flb_sds_t json;
+ size_t out_size;
+ struct mk_list *head;
+ struct flb_custom_plugin *c;
+ struct flb_input_plugin *i;
+ struct flb_filter_plugin *f;
+ struct flb_output_plugin *o;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+ struct flb_mp_map_header mh;
+
+ /* initialize buffer */
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ /*
+ * Root map for entries:
+ *
+ * - fluent-bit
+ * - customs
+ * - inputs
+ * - filters
+ * - outputs
+ */
+ msgpack_pack_map(&mp_pck, 5);
+
+ /* Fluent Bit */
+ msgpack_pack_str(&mp_pck, 10);
+ msgpack_pack_str_body(&mp_pck, "fluent-bit", 10);
+
+ /* fluent-bit['version'], fluent-bit['help_version'] and fluent-bit['os'] */
+ msgpack_pack_map(&mp_pck, 3);
+
+ pack_map_kv(&mp_pck, "version", FLB_VERSION_STR);
+ pack_map_kv(&mp_pck, "schema_version", FLB_HELP_SCHEMA_VERSION);
+ pack_map_kv(&mp_pck, "os", (char *) flb_utils_get_os_name());
+
+ /* customs */
+ msgpack_pack_str(&mp_pck, 7);
+ msgpack_pack_str_body(&mp_pck, "customs", 7);
+
+ flb_mp_array_header_init(&mh, &mp_pck);
+ mk_list_foreach(head, &config->custom_plugins) {
+ c = mk_list_entry(head, struct flb_custom_plugin, _head);
+ ret = build_plugin_help(config, FLB_HELP_PLUGIN_CUSTOM, c->name,
+ &out_buf, &out_size);
+ if (ret == -1) {
+ continue;
+ }
+
+ flb_mp_array_header_append(&mh);
+ msgpack_sbuffer_write(&mp_sbuf, out_buf, out_size);
+ flb_free(out_buf);
+ }
+ flb_mp_array_header_end(&mh);
+
+
+ /* inputs */
+ msgpack_pack_str(&mp_pck, 6);
+ msgpack_pack_str_body(&mp_pck, "inputs", 6);
+
+ flb_mp_array_header_init(&mh, &mp_pck);
+ mk_list_foreach(head, &config->in_plugins) {
+ i = mk_list_entry(head, struct flb_input_plugin, _head);
+ if (i->flags & FLB_INPUT_PRIVATE){
+ continue;
+ }
+ ret = build_plugin_help(config, FLB_HELP_PLUGIN_INPUT, i->name,
+ &out_buf, &out_size);
+ if (ret == -1) {
+ continue;
+ }
+ flb_mp_array_header_append(&mh);
+ msgpack_sbuffer_write(&mp_sbuf, out_buf, out_size);
+ flb_free(out_buf);
+ }
+ flb_mp_array_header_end(&mh);
+
+ /* filters */
+ msgpack_pack_str(&mp_pck, 7);
+ msgpack_pack_str_body(&mp_pck, "filters", 7);
+
+ flb_mp_array_header_init(&mh, &mp_pck);
+ mk_list_foreach(head, &config->filter_plugins) {
+ f = mk_list_entry(head, struct flb_filter_plugin, _head);
+ ret = build_plugin_help(config, FLB_HELP_PLUGIN_FILTER, f->name,
+ &out_buf, &out_size);
+ if (ret == -1) {
+ continue;
+ }
+
+ flb_mp_array_header_append(&mh);
+ msgpack_sbuffer_write(&mp_sbuf, out_buf, out_size);
+ flb_free(out_buf);
+ }
+ flb_mp_array_header_end(&mh);
+
+ /* outputs */
+ msgpack_pack_str(&mp_pck, 7);
+ msgpack_pack_str_body(&mp_pck, "outputs", 7);
+
+ flb_mp_array_header_init(&mh, &mp_pck);
+ mk_list_foreach(head, &config->out_plugins) {
+ o = mk_list_entry(head, struct flb_output_plugin, _head);
+ if (o->flags & FLB_OUTPUT_PRIVATE){
+ continue;
+ }
+ ret = build_plugin_help(config, FLB_HELP_PLUGIN_OUTPUT, o->name,
+ &out_buf, &out_size);
+ if (ret == -1) {
+ continue;
+ }
+ flb_mp_array_header_append(&mh);
+ msgpack_sbuffer_write(&mp_sbuf, out_buf, out_size);
+ flb_free(out_buf);
+ }
+ flb_mp_array_header_end(&mh);
+
+ json = flb_msgpack_raw_to_json_sds(mp_sbuf.data, mp_sbuf.size);
+ msgpack_sbuffer_destroy(&mp_sbuf);
+
+ return json;
+}
diff --git a/fluent-bit/src/flb_hmac.c b/fluent-bit/src/flb_hmac.c
new file mode 100644
index 000000000..9c159c635
--- /dev/null
+++ b/fluent-bit/src/flb_hmac.c
@@ -0,0 +1,382 @@
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2019-2020 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_hmac.h>
+#include <fluent-bit/flb_mem.h>
+
+#if FLB_CRYPTO_OPENSSL_COMPAT_MODE >= 3
+#include <openssl/params.h>
+#endif
+
+#include <openssl/evp.h>
+#include <openssl/bio.h>
+#include <string.h>
+
+#if FLB_CRYPTO_OPENSSL_COMPAT_MODE >= 3
+static const char *flb_crypto_get_algorithm_name_by_id(int algorithm_id)
+{
+ const char *algorithm_name;
+
+ if (algorithm_id == FLB_HASH_SHA256) {
+ algorithm_name = "SHA-256";
+ }
+ else if (algorithm_id == FLB_HASH_SHA512) {
+ algorithm_name = "SHA-512";
+ }
+ else if (algorithm_id == FLB_HASH_MD5) {
+ algorithm_name = "MD5";
+ }
+ else {
+ algorithm_name = NULL;
+ }
+
+ return algorithm_name;
+}
+
+int flb_hmac_init(struct flb_hmac *context,
+ int algorithm_id,
+ unsigned char *key,
+ size_t key_length)
+{
+ const char *digest_algorithm_name;
+ OSSL_PARAM hmac_parameters[2];
+ int result;
+
+
+ if (context == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ if (key == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ if (key_length == 0) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ memset(context, 0, sizeof(struct flb_hmac));
+
+ digest_algorithm_name = flb_crypto_get_algorithm_name_by_id(algorithm_id);
+
+ if (digest_algorithm_name == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ context->mac_algorithm = EVP_MAC_fetch(NULL, "HMAC", NULL);
+
+ if (context->mac_algorithm == NULL) {
+ context->last_error = ERR_get_error();
+
+ flb_hmac_cleanup(context);
+
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ context->backend_context = EVP_MAC_CTX_new(context->mac_algorithm);
+
+ if (context->backend_context == NULL) {
+ context->last_error = ERR_get_error();
+
+ flb_hmac_cleanup(context);
+
+ return FLB_CRYPTO_BACKEND_ERROR;
+ }
+
+ hmac_parameters[0] = OSSL_PARAM_construct_utf8_string("digest",
+ (char *) digest_algorithm_name,
+ 0);
+ hmac_parameters[1] = OSSL_PARAM_construct_end();
+
+
+ result = EVP_MAC_init(context->backend_context,
+ key, key_length,
+ hmac_parameters);
+
+ if (result == 0) {
+ context->last_error = ERR_get_error();
+
+ return FLB_CRYPTO_BACKEND_ERROR;
+ }
+
+ context->digest_size = EVP_MAC_CTX_get_mac_size(context->backend_context);
+
+ return FLB_CRYPTO_SUCCESS;
+}
+
+#else
+
+static const EVP_MD *flb_crypto_get_digest_algorithm_instance_by_id(int algorithm_id)
+{
+ const EVP_MD *algorithm;
+
+ if (algorithm_id == FLB_HASH_SHA256) {
+ algorithm = EVP_sha256();
+ }
+ else if (algorithm_id == FLB_HASH_SHA512) {
+ algorithm = EVP_sha512();
+ }
+ else if (algorithm_id == FLB_HASH_MD5) {
+ algorithm = EVP_md5();
+ }
+ else {
+ algorithm = NULL;
+ }
+
+ return algorithm;
+}
+
+int flb_hmac_init(struct flb_hmac *context,
+ int algorithm_id,
+ unsigned char *key,
+ size_t key_length)
+{
+ const EVP_MD *digest_algorithm_instance;
+ int result;
+
+
+ if (context == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ if (key == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ if (key_length == 0) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ memset(context, 0, sizeof(struct flb_hmac));
+
+ digest_algorithm_instance = flb_crypto_get_digest_algorithm_instance_by_id(algorithm_id);
+
+ if (digest_algorithm_instance == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+#if FLB_CRYPTO_OPENSSL_COMPAT_MODE == 0
+ context->backend_context = flb_calloc(1, sizeof(HMAC_CTX));
+
+ if (context->backend_context == NULL) {
+ return FLB_CRYPTO_ALLOCATION_ERROR;
+ }
+
+ HMAC_CTX_init(context->backend_context);
+#else
+ context->backend_context = HMAC_CTX_new();
+
+ if (context->backend_context == NULL) {
+ context->last_error = ERR_get_error();
+
+ return FLB_CRYPTO_BACKEND_ERROR;
+ }
+#endif
+
+ result = HMAC_Init_ex(context->backend_context,
+ key, key_length,
+ digest_algorithm_instance,
+ NULL);
+
+ if (result != 1) {
+ context->last_error = ERR_get_error();
+
+ return FLB_CRYPTO_BACKEND_ERROR;
+ }
+
+ context->digest_size = EVP_MD_size(digest_algorithm_instance);
+
+ return FLB_CRYPTO_SUCCESS;
+}
+#endif
+
+int flb_hmac_finalize(struct flb_hmac *context,
+ unsigned char *signature_buffer,
+ size_t signature_buffer_size)
+{
+ size_t signature_length;
+ int error_detected;
+ int result;
+
+ if (context->backend_context == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ if (signature_buffer == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ if (signature_buffer_size < context->digest_size) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+#if FLB_CRYPTO_OPENSSL_COMPAT_MODE >= 3
+ result = EVP_MAC_final(context->backend_context,
+ signature_buffer,
+ &signature_length,
+ signature_buffer_size);
+
+ error_detected = (result == 0);
+#else
+ signature_length = 0;
+
+ result = HMAC_Final(context->backend_context,
+ signature_buffer,
+ (unsigned int *) &signature_length);
+
+ error_detected = (result != 1);
+#endif
+
+ if (error_detected) {
+ context->last_error = ERR_get_error();
+
+ return FLB_CRYPTO_BACKEND_ERROR;
+ }
+
+ (void) signature_length;
+
+ return FLB_CRYPTO_SUCCESS;
+}
+
+int flb_hmac_update(struct flb_hmac *context,
+ unsigned char *data,
+ size_t data_length)
+{
+ int error_detected;
+ int result;
+
+ if (context->backend_context == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+ if (data == NULL) {
+ return FLB_CRYPTO_INVALID_ARGUMENT;
+ }
+
+#if FLB_CRYPTO_OPENSSL_COMPAT_MODE >= 3
+ result = EVP_MAC_update(context->backend_context,
+ data,
+ data_length);
+
+ error_detected = (result == 0);
+#else
+ result = HMAC_Update(context->backend_context,
+ data,
+ data_length);
+
+ error_detected = (result != 1);
+#endif
+
+ if (error_detected) {
+ context->last_error = ERR_get_error();
+
+ return FLB_CRYPTO_BACKEND_ERROR;
+ }
+
+ return FLB_CRYPTO_SUCCESS;
+}
+
+int flb_hmac_cleanup(struct flb_hmac *context)
+{
+#if FLB_CRYPTO_OPENSSL_COMPAT_MODE >= 3
+ if (context->backend_context != NULL) {
+ EVP_MAC_CTX_free(context->backend_context);
+
+ context->backend_context = NULL;
+ }
+
+ if (context->mac_algorithm != NULL) {
+ EVP_MAC_free(context->mac_algorithm);
+
+ context->mac_algorithm = NULL;
+ }
+#else
+ if (context->backend_context != NULL) {
+#if FLB_CRYPTO_OPENSSL_COMPAT_MODE == 0
+ HMAC_CTX_cleanup(context->backend_context);
+
+ flb_free(context->backend_context);
+#else
+ HMAC_CTX_reset(context->backend_context);
+
+ HMAC_CTX_free(context->backend_context);
+#endif
+
+ context->backend_context = NULL;
+ }
+#endif
+
+ return FLB_CRYPTO_SUCCESS;
+}
+
+int flb_hmac_simple_batch(int hash_type,
+ unsigned char *key, size_t key_length,
+ size_t entry_count,
+ unsigned char **data_entries,
+ size_t *length_entries,
+ unsigned char *signature_buffer,
+ size_t signature_buffer_size)
+{
+ struct flb_hmac digest_context;
+ size_t entry_index;
+ int result;
+
+ result = flb_hmac_init(&digest_context,
+ hash_type,
+ key, key_length);
+
+ if (result == FLB_CRYPTO_SUCCESS) {
+ for (entry_index = 0 ;
+ entry_index < entry_count && result == FLB_CRYPTO_SUCCESS;
+ entry_index++) {
+ result = flb_hmac_update(&digest_context,
+ data_entries[entry_index],
+ length_entries[entry_index]);
+ }
+
+ if (result == FLB_CRYPTO_SUCCESS) {
+ result = flb_hmac_finalize(&digest_context,
+ signature_buffer,
+ signature_buffer_size);
+ }
+
+ flb_hmac_cleanup(&digest_context);
+ }
+
+ return result;
+}
+
+int flb_hmac_simple(int hash_type,
+ unsigned char *key, size_t key_length,
+ unsigned char *data, size_t data_length,
+ unsigned char *signature_buffer,
+ size_t signature_buffer_size)
+{
+ size_t length_entries[1];
+ unsigned char *data_entries[1];
+
+ length_entries[0] = data_length;
+ data_entries[0] = data;
+
+ return flb_hmac_simple_batch(hash_type,
+ key, key_length,
+ 1,
+ data_entries,
+ length_entries,
+ signature_buffer,
+ signature_buffer_size);
+}
diff --git a/fluent-bit/src/flb_http_client.c b/fluent-bit/src/flb_http_client.c
new file mode 100644
index 000000000..2d2803293
--- /dev/null
+++ b/fluent-bit/src/flb_http_client.c
@@ -0,0 +1,1399 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+/*
+ * This is a very simple HTTP Client interface which aims to provide an
+ * easy way to issue HTTP requests and handle reponses from the input/output
+ * plugins.
+ *
+ * It scope is:
+ *
+ * - Use upstream connections.
+ * - Support 'retry' in case the HTTP server timeouts a connection.
+ * - Get return Status, Headers and Body content if found.
+ * - If Upstream supports keepalive, adjust headers
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_http_client.h>
+#include <fluent-bit/flb_http_client_debug.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_base64.h>
+
+
+
+void flb_http_client_debug(struct flb_http_client *c,
+ struct flb_callback *cb_ctx)
+{
+#ifdef FLB_HAVE_HTTP_CLIENT_DEBUG
+ if (cb_ctx) {
+ flb_http_client_debug_enable(c, cb_ctx);
+ }
+#endif
+}
+
+/*
+ * Removes the port from the host header
+ */
+int flb_http_strip_port_from_host(struct flb_http_client *c)
+{
+ struct mk_list *head;
+ struct flb_kv *kv;
+ char *out_host;
+ struct flb_upstream *u;
+
+ u = c->u_conn->upstream;
+
+ if (!c->host) {
+ if (!u->proxied_host) {
+ out_host = u->tcp_host;
+ } else {
+ out_host = u->proxied_host;
+ }
+ } else {
+ out_host = (char *) c->host;
+ }
+
+ mk_list_foreach(head, &c->headers) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ if (strcasecmp("Host", kv->key) == 0) {
+ flb_sds_destroy(kv->val);
+ kv->val = NULL;
+ kv->val = flb_sds_create(out_host);
+ if (!kv->val) {
+ flb_errno();
+ return -1;
+ }
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+int flb_http_allow_duplicated_headers(struct flb_http_client *c, int allow)
+{
+ if (allow != FLB_TRUE && allow != FLB_FALSE) {
+ return -1;
+ }
+
+ c->allow_dup_headers = allow;
+ return 0;
+}
+
+/* check if there is enough space in the client header buffer */
+static int header_available(struct flb_http_client *c, int bytes)
+{
+ int available;
+
+ available = c->header_size - c->header_len;
+ if (available < bytes) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Try to find a header value in the buffer */
+static int header_lookup(struct flb_http_client *c,
+ const char *header, int header_len,
+ const char **out_val, int *out_len)
+{
+ char *p;
+ char *crlf;
+ char *end;
+
+ if (!c->resp.data) {
+ return FLB_HTTP_MORE;
+ }
+
+ /* Lookup the beginning of the header */
+ p = strcasestr(c->resp.data, header);
+ end = strstr(c->resp.data, "\r\n\r\n");
+ if (!p) {
+ if (end) {
+ /* The headers are complete but the header is not there */
+ return FLB_HTTP_NOT_FOUND;
+ }
+
+ /* We need more data */
+ return FLB_HTTP_MORE;
+ }
+
+ /* Exclude matches in the body */
+ if (end && p > end) {
+ return FLB_HTTP_NOT_FOUND;
+ }
+
+ /* Lookup CRLF (end of line \r\n) */
+ crlf = strstr(p, "\r\n");
+ if (!crlf) {
+ return FLB_HTTP_MORE;
+ }
+
+ p += header_len;
+
+ *out_val = p;
+ *out_len = (crlf - p);
+
+ return FLB_HTTP_OK;
+}
+
+/* HTTP/1.1: Check if we have a Chunked Transfer Encoding */
+static int check_chunked_encoding(struct flb_http_client *c)
+{
+ int ret;
+ int len;
+ const char *header = NULL;
+
+ ret = header_lookup(c, "Transfer-Encoding: ", 19,
+ &header, &len);
+ if (ret == FLB_HTTP_NOT_FOUND) {
+ /* If the header is missing, this is fine */
+ c->resp.chunked_encoding = FLB_FALSE;
+ return FLB_HTTP_OK;
+ }
+ else if (ret == FLB_HTTP_MORE) {
+ return FLB_HTTP_MORE;
+ }
+
+ if (strncasecmp(header, "chunked", len) == 0) {
+ c->resp.chunked_encoding = FLB_TRUE;
+ }
+
+ return FLB_HTTP_OK;
+}
+
+/* Check response for a 'Content-Length' header */
+static int check_content_length(struct flb_http_client *c)
+{
+ int ret;
+ int len;
+ const char *header;
+ char tmp[256];
+
+ if (c->resp.status == 204) {
+ c->resp.content_length = -1;
+ return FLB_HTTP_OK;
+ }
+
+ ret = header_lookup(c, "Content-Length: ", 16,
+ &header, &len);
+ if (ret == FLB_HTTP_MORE) {
+ return FLB_HTTP_MORE;
+ }
+ else if (ret == FLB_HTTP_NOT_FOUND) {
+ return FLB_HTTP_NOT_FOUND;
+ }
+
+ if (len > sizeof(tmp) - 1) {
+ /* Value too long */
+ return FLB_HTTP_ERROR;
+ }
+
+ /* Copy to temporary buffer */
+ memcpy(tmp, header, len);
+ tmp[len] = '\0';
+
+ c->resp.content_length = atoi(tmp);
+ return FLB_HTTP_OK;
+}
+
+/* Check response for a 'Connection' header */
+static int check_connection(struct flb_http_client *c)
+{
+ int ret;
+ int len;
+ const char *header;
+ char *buf;
+
+ ret = header_lookup(c, "Connection: ", 12,
+ &header, &len);
+ if (ret == FLB_HTTP_NOT_FOUND) {
+ return FLB_HTTP_NOT_FOUND;
+ }
+ else if (ret == FLB_HTTP_MORE) {
+ return FLB_HTTP_MORE;
+ }
+
+ buf = flb_malloc(len + 1);
+ if (!buf) {
+ flb_errno();
+ return -1;
+ }
+
+ memcpy(buf, header, len);
+ buf[len] = '\0';
+
+ if (strncasecmp(buf, "close", 5) == 0) {
+ c->resp.connection_close = FLB_TRUE;
+ }
+ else if (strcasestr(buf, "keep-alive")) {
+ c->resp.connection_close = FLB_FALSE;
+ }
+ flb_free(buf);
+ return FLB_HTTP_OK;
+
+}
+
+static inline void consume_bytes(char *buf, int bytes, int length)
+{
+ memmove(buf, buf + bytes, length - bytes);
+}
+
+static int process_chunked_data(struct flb_http_client *c)
+{
+ long len;
+ long drop;
+ long val;
+ char *p;
+ char tmp[32];
+ struct flb_http_response *r = &c->resp;
+
+ chunk_start:
+ p = strstr(r->chunk_processed_end, "\r\n");
+ if (!p) {
+ return FLB_HTTP_MORE;
+ }
+
+ /* Hexa string length */
+ len = (p - r->chunk_processed_end);
+ if ((len > sizeof(tmp) - 1) || len == 0) {
+ return FLB_HTTP_ERROR;
+ }
+ p += 2;
+
+ /* Copy hexa string to temporary buffer */
+ memcpy(tmp, r->chunk_processed_end, len);
+ tmp[len] = '\0';
+
+ /* Convert hexa string to decimal */
+ errno = 0;
+ val = strtol(tmp, NULL, 16);
+ if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
+ || (errno != 0 && val == 0)) {
+ flb_errno();
+ return FLB_HTTP_ERROR;
+ }
+ if (val < 0) {
+ return FLB_HTTP_ERROR;
+ }
+ /*
+ * 'val' contains the expected number of bytes, check current lengths
+ * and do buffer adjustments.
+ *
+ * we do val + 2 because the chunk always ends with \r\n
+ */
+ val += 2;
+
+ /* Number of bytes after the Chunk header */
+ len = r->data_len - (p - r->data);
+ if (len < val) {
+ return FLB_HTTP_MORE;
+ }
+
+ /* From the current chunk we expect it ends with \r\n */
+ if (p[val -2] != '\r' || p[val - 1] != '\n') {
+ return FLB_HTTP_ERROR;
+ }
+
+ /*
+ * At this point we are just fine, the chunk is valid, next steps:
+ *
+ * 1. check possible last chunk
+ * 2. drop chunk header from the buffer
+ * 3. remove chunk ending \r\n
+ */
+
+ /* 1. Validate ending chunk */
+ if (val - 2 == 0) {
+ /*
+ * For an ending chunk we expect:
+ *
+ * 0\r\n
+ * \r\n
+ *
+ * so at least we need 5 bytes in the buffer
+ */
+ len = r->data_len - (r->chunk_processed_end - r->data);
+ if (len < 5) {
+ return FLB_HTTP_MORE;
+ }
+
+ if (r->chunk_processed_end[3] != '\r' ||
+ r->chunk_processed_end[4] != '\n') {
+ return FLB_HTTP_ERROR;
+ }
+ }
+
+ /* 2. Drop chunk header */
+ drop = (p - r->chunk_processed_end);
+ len = r->data_len - (r->chunk_processed_end - r->data);
+ consume_bytes(r->chunk_processed_end, drop, len);
+ r->data_len -= drop;
+ r->data[r->data_len] = '\0';
+
+ /* 3. Remove chunk ending \r\n */
+ drop = 2;
+ r->chunk_processed_end += labs(val - 2);
+ len = r->data_len - (r->chunk_processed_end - r->data);
+ consume_bytes(r->chunk_processed_end, drop, len);
+ r->data_len -= drop;
+
+ /* Always append a NULL byte */
+ r->data[r->data_len] = '\0';
+
+ /* Is this the last chunk ? */
+ if ((val - 2 == 0)) {
+ /* Update payload size */
+ r->payload_size = r->data_len - (r->headers_end - r->data);
+ return FLB_HTTP_OK;
+ }
+
+ /* If we have some remaining bytes, start over */
+ len = r->data_len - (r->chunk_processed_end - r->data);
+ if (len > 0) {
+ goto chunk_start;
+ }
+
+ return FLB_HTTP_MORE;
+}
+
+static int process_data(struct flb_http_client *c)
+{
+ int ret;
+ char code[4];
+ char *tmp;
+
+ if (c->resp.data_len < 15) {
+ /* we need more data */
+ return FLB_HTTP_MORE;
+ }
+
+ /* HTTP response status */
+ if (c->resp.status <= 0) {
+ memcpy(code, c->resp.data + 9, 3);
+ code[3] = '\0';
+ c->resp.status = atoi(code);
+ if (c->resp.status < 100 || c->resp.status > 599) {
+ return FLB_HTTP_ERROR;
+ }
+ }
+
+ /* Try to lookup content length */
+ if (c->resp.content_length == -1 && c->resp.chunked_encoding == FLB_FALSE) {
+ ret = check_content_length(c);
+ if (ret == FLB_HTTP_ERROR) {
+ return FLB_HTTP_ERROR;
+ }
+ }
+
+ /* Chunked encoding for HTTP/1.1 (no content length of course) */
+ if ((c->flags & FLB_HTTP_11) && c->resp.content_length == -1) {
+ if (c->resp.chunked_encoding == FLB_FALSE) {
+ ret = check_chunked_encoding(c);
+ if (ret == FLB_HTTP_ERROR) {
+ return FLB_HTTP_ERROR;
+ }
+ }
+ }
+
+ if (!c->resp.headers_end) {
+ tmp = strstr(c->resp.data, "\r\n\r\n");
+ if (tmp) {
+ c->resp.headers_end = tmp + 4;
+ if (c->resp.chunked_encoding == FLB_TRUE) {
+ c->resp.chunk_processed_end = c->resp.headers_end;
+ }
+
+ /* Mark the payload */
+ if ((tmp - c->resp.data + 4) < c->resp.data_len) {
+ c->resp.payload = tmp += 4;
+ c->resp.payload_size = (c->resp.data_len - (tmp - c->resp.data));
+ }
+ }
+ else {
+ return FLB_HTTP_MORE;
+ }
+ }
+
+ /* Re-check if an ending exists, if so process payload if required */
+ if (c->resp.headers_end) {
+ /* Mark the payload */
+ if (!c->resp.payload &&
+ c->resp.headers_end - c->resp.data < c->resp.data_len) {
+ c->resp.payload = c->resp.headers_end;
+ c->resp.payload_size = (c->resp.data_len - (c->resp.headers_end - c->resp.data));
+ }
+
+ if (c->resp.content_length >= 0) {
+ c->resp.payload_size = c->resp.data_len;
+ c->resp.payload_size -= (c->resp.headers_end - c->resp.data);
+ if (c->resp.payload_size >= c->resp.content_length) {
+ return FLB_HTTP_OK;
+ }
+ }
+ else if (c->resp.chunked_encoding == FLB_TRUE) {
+ ret = process_chunked_data(c);
+ if (ret == FLB_HTTP_ERROR) {
+ return FLB_HTTP_ERROR;
+ }
+ else if (ret == FLB_HTTP_OK) {
+ return FLB_HTTP_OK;
+ }
+ }
+ else {
+ return FLB_HTTP_OK;
+ }
+ }
+ else if (c->resp.headers_end && c->resp.content_length <= 0) {
+ return FLB_HTTP_OK;
+ }
+
+ return FLB_HTTP_MORE;
+}
+
+#if defined FLB_HAVE_TESTS_OSSFUZZ
+int fuzz_process_data(struct flb_http_client *c);
+int fuzz_process_data(struct flb_http_client *c) {
+ return process_data(c);
+}
+
+int fuzz_check_connection(struct flb_http_client *c);
+int fuzz_check_connection(struct flb_http_client *c) {
+ return check_connection(c);
+}
+
+#endif
+
+static int proxy_parse(const char *proxy, struct flb_http_client *c)
+{
+ int len;
+ int port;
+ int off = 0;
+ const char *s;
+ const char *e;
+ const char *host;
+
+ len = strlen(proxy);
+ if (len < 7) {
+ return -1;
+ }
+
+ /* Protocol lookup */
+ if (strncmp(proxy, "http://", 7) == 0) {
+ port = 80;
+ off = 7;
+ c->proxy.type = FLB_HTTP_PROXY_HTTP;
+ }
+ else if (strncmp(proxy, "https://", 8) == 0) {
+ port = 443;
+ off = 8;
+ c->proxy.type = FLB_HTTP_PROXY_HTTPS;
+ }
+ else {
+ return -1;
+ }
+
+ /* Separate host/ip from port if any */
+ s = proxy + off;
+ if (*s == '[') {
+ /* IPv6 address (RFC 3986) */
+ e = strchr(++s, ']');
+ if (!e) {
+ return -1;
+ }
+ host = strndup(s, e - s);
+ s = e + 1;
+ } else {
+ e = s;
+ while (!(*e == '\0' || *e == ':' || *e == '/')) {
+ ++e;
+ }
+ if (e == s) {
+ return -1;
+ }
+ host = strndup(s, e - s);
+ s = e;
+ }
+ if (*s == ':') {
+ port = atoi(++s);
+ }
+
+ flb_trace("[http_client] proxy type=%i host=%s port=%i",
+ c->proxy.type, host, port);
+
+ c->proxy.host = host;
+ c->proxy.port = port;
+
+ return 0;
+}
+
+static int add_host_and_content_length(struct flb_http_client *c)
+{
+ int len;
+ flb_sds_t tmp;
+ flb_sds_t host;
+ char *out_host;
+ int out_port;
+ size_t size;
+ struct flb_upstream *u = c->u_conn->upstream;
+
+ if (!c->host) {
+ if (u->proxied_host) {
+ out_host = u->proxied_host;
+ }
+ else {
+ out_host = u->tcp_host;
+ }
+ }
+ else {
+ out_host = (char *) c->host;
+ }
+
+ len = strlen(out_host);
+ host = flb_sds_create_size(len + 32);
+ if (!host) {
+ flb_error("[http_client] cannot create temporal buffer");
+ return -1;
+ }
+
+ if (c->port == 0) {
+ if (u->proxied_port != 0 ) {
+ out_port = u->proxied_port;
+ }
+ else {
+ out_port = u->tcp_port;
+ }
+ }
+ else {
+ out_port = c->port;
+ }
+
+ if (c->flags & FLB_IO_TLS && out_port == 443) {
+ tmp = flb_sds_copy(host, out_host, strlen(out_host));
+ }
+ else {
+ tmp = flb_sds_printf(&host, "%s:%i", out_host, out_port);
+ }
+
+ if (!tmp) {
+ flb_sds_destroy(host);
+ flb_error("[http_client] cannot compose temporary host header");
+ return -1;
+ }
+ host = tmp;
+ tmp = NULL;
+
+ flb_http_add_header(c, "Host", 4, host, flb_sds_len(host));
+ flb_sds_destroy(host);
+
+ /* Content-Length */
+ if (c->body_len >= 0) {
+ size = 32;
+ tmp = flb_malloc(size);
+ if (!tmp) {
+ flb_errno();
+ return -1;
+ }
+ len = snprintf(tmp, size - 1, "%i", c->body_len);
+ flb_http_add_header(c, "Content-Length", 14, tmp, len);
+ flb_free(tmp);
+ }
+
+ return 0;
+}
+
+struct flb_http_client *flb_http_client(struct flb_connection *u_conn,
+ int method, const char *uri,
+ const char *body, size_t body_len,
+ const char *host, int port,
+ const char *proxy, int flags)
+{
+ int ret;
+ char *p;
+ char *buf = NULL;
+ char *str_method = NULL;
+ char *fmt_plain = \
+ "%s %s HTTP/1.%i\r\n";
+ char *fmt_proxy = \
+ "%s http://%s:%i%s HTTP/1.%i\r\n"
+ "Proxy-Connection: KeepAlive\r\n";
+ // TODO: IPv6 should have the format of [ip]:port
+ char *fmt_connect = \
+ "%s %s:%i HTTP/1.%i\r\n"
+ "Proxy-Connection: KeepAlive\r\n";
+
+ struct flb_http_client *c;
+
+ switch (method) {
+ case FLB_HTTP_GET:
+ str_method = "GET";
+ break;
+ case FLB_HTTP_POST:
+ str_method = "POST";
+ break;
+ case FLB_HTTP_PUT:
+ str_method = "PUT";
+ break;
+ case FLB_HTTP_HEAD:
+ str_method = "HEAD";
+ break;
+ case FLB_HTTP_CONNECT:
+ str_method = "CONNECT";
+ break;
+ case FLB_HTTP_PATCH:
+ str_method = "PATCH";
+ break;
+ };
+
+ buf = flb_calloc(1, FLB_HTTP_BUF_SIZE);
+ if (!buf) {
+ flb_errno();
+ return NULL;
+ }
+
+ /* FIXME: handler for HTTPS proxy */
+ if (proxy) {
+ flb_debug("[http_client] using http_proxy %s for header", proxy);
+ ret = snprintf(buf, FLB_HTTP_BUF_SIZE,
+ fmt_proxy,
+ str_method,
+ host,
+ port,
+ uri,
+ flags & FLB_HTTP_10 ? 0 : 1);
+ }
+ else if (method == FLB_HTTP_CONNECT) {
+ flb_debug("[http_client] using HTTP CONNECT for proxy: proxy host %s, proxy port %i", host, port);
+ ret = snprintf(buf, FLB_HTTP_BUF_SIZE,
+ fmt_connect,
+ str_method,
+ host,
+ port,
+ flags & FLB_HTTP_10 ? 0 : 1);
+ }
+ else {
+ flb_debug("[http_client] not using http_proxy for header");
+ ret = snprintf(buf, FLB_HTTP_BUF_SIZE,
+ fmt_plain,
+ str_method,
+ uri,
+ flags & FLB_HTTP_10 ? 0 : 1);
+ }
+
+ if (ret == -1) {
+ flb_errno();
+ flb_free(buf);
+ return NULL;
+ }
+
+ c = flb_calloc(1, sizeof(struct flb_http_client));
+ if (!c) {
+ flb_free(buf);
+ return NULL;
+ }
+
+ c->u_conn = u_conn;
+ c->method = method;
+ c->uri = uri;
+ c->host = host;
+ c->port = port;
+ c->header_buf = buf;
+ c->header_size = FLB_HTTP_BUF_SIZE;
+ c->header_len = ret;
+ c->flags = flags;
+ c->allow_dup_headers = FLB_TRUE;
+ mk_list_init(&c->headers);
+
+ /* Check if we have a query string */
+ p = strchr(uri, '?');
+ if (p) {
+ p++;
+ c->query_string = p;
+ }
+
+ /* Is Upstream connection using keepalive mode ? */
+ if (flb_stream_get_flag_status(&u_conn->upstream->base, FLB_IO_TCP_KA)) {
+ c->flags |= FLB_HTTP_KA;
+ }
+
+ /* Response */
+ c->resp.content_length = -1;
+ c->resp.connection_close = -1;
+
+ if ((flags & FLB_HTTP_10) == 0) {
+ c->flags |= FLB_HTTP_11;
+ }
+
+ if (body && body_len > 0) {
+ c->body_buf = body;
+ c->body_len = body_len;
+ }
+
+ add_host_and_content_length(c);
+
+ /* Check proxy data */
+ if (proxy) {
+ flb_debug("[http_client] Using http_proxy: %s", proxy);
+ ret = proxy_parse(proxy, c);
+ if (ret != 0) {
+ flb_debug("[http_client] Something wrong with the http_proxy parsing");
+ flb_http_client_destroy(c);
+ return NULL;
+ }
+ }
+
+ /* 'Read' buffer size */
+ c->resp.data = flb_malloc(FLB_HTTP_DATA_SIZE_MAX);
+ if (!c->resp.data) {
+ flb_errno();
+ flb_http_client_destroy(c);
+ return NULL;
+ }
+ c->resp.data[0] = '\0';
+ c->resp.data_len = 0;
+ c->resp.data_size = FLB_HTTP_DATA_SIZE_MAX;
+ c->resp.data_size_max = FLB_HTTP_DATA_SIZE_MAX;
+
+ return c;
+}
+
+/*
+ * By default the HTTP client have a fixed buffer to read a response for a
+ * simple request. But in certain situations the caller might expect a
+ * larger response that exceed the buffer limit.
+ *
+ * This function allows to set a maximum buffer size for the client
+ * response where:
+ *
+ * 1. size = 0 no limit, read as much as possible.
+ * 2. size = N: specific limit, upon reach limit discard data (default: 4KB)
+ */
+int flb_http_buffer_size(struct flb_http_client *c, size_t size)
+{
+ if (size < c->resp.data_size_max && size != 0) {
+ flb_error("[http] requested buffer size %lu (bytes) needs to be greater than "
+ "minimum size allowed %lu (bytes)",
+ size, c->resp.data_size_max);
+ return -1;
+ }
+
+ c->resp.data_size_max = size;
+ return 0;
+}
+
+size_t flb_http_buffer_available(struct flb_http_client *c)
+{
+ return (c->resp.data_size - c->resp.data_len);
+}
+
+/*
+ * Increase the read buffer size based on the limits set by default or manually
+ * through the flb_http_buffer_size() function.
+ *
+ * The parameter 'size' is the amount of extra memory requested.
+ */
+int flb_http_buffer_increase(struct flb_http_client *c, size_t size,
+ size_t *out_size)
+{
+ int off_payload = 0;
+ int off_headers_end = 0;
+ int off_chunk_processed_end = 0;
+ char *tmp;
+ size_t new_size;
+ size_t allocated;
+
+ *out_size = 0;
+ new_size = c->resp.data_size + size;
+
+ /* Limit exceeded, adjust */
+ if (c->resp.data_size_max != 0) {
+ if (new_size > c->resp.data_size_max) {
+ new_size = c->resp.data_size_max;
+ if (new_size <= c->resp.data_size) {
+ /* Can't expand the buffer any further. */
+ return -1;
+ }
+ }
+ }
+
+
+ if (c->resp.headers_end) {
+ off_headers_end = c->resp.headers_end - c->resp.data;
+ }
+ if (c->resp.chunk_processed_end) {
+ off_chunk_processed_end = c->resp.chunk_processed_end - c->resp.data;
+ }
+
+ /*
+ * The payload is a reference to a position of 'data' buffer,
+ * we need to adjust the pointer after a memory buffer size change.
+ */
+ if (c->resp.payload_size > 0) {
+ off_payload = c->resp.payload - c->resp.data;
+ }
+
+ tmp = flb_realloc(c->resp.data, new_size);
+ if (!tmp) {
+ flb_errno();
+ return -1;
+ }
+ else {
+ allocated = new_size - c->resp.data_size;
+ c->resp.data = tmp;
+ c->resp.data_size = new_size;
+
+ if (off_headers_end > 0) {
+ c->resp.headers_end = c->resp.data + off_headers_end;
+ }
+ if (off_chunk_processed_end > 0) {
+ c->resp.chunk_processed_end = c->resp.data + off_chunk_processed_end;
+ }
+ if (off_payload > 0) {
+ c->resp.payload = c->resp.data + off_payload;
+ }
+ }
+
+ *out_size = allocated;
+ return 0;
+}
+
+
+/* Append a custom HTTP header to the request */
+int flb_http_add_header(struct flb_http_client *c,
+ const char *key, size_t key_len,
+ const char *val, size_t val_len)
+{
+ struct flb_kv *kv;
+ struct mk_list *tmp;
+ struct mk_list *head;
+
+ if (key_len < 1 || val_len < 1) {
+ return -1;
+ }
+
+ /* Check any previous header to avoid duplicates */
+ if (c->allow_dup_headers == FLB_FALSE) {
+ mk_list_foreach_safe(head, tmp, &c->headers) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ if (flb_sds_casecmp(kv->key, key, key_len) == 0) {
+ /* the header already exists, remove it */
+ flb_kv_item_destroy(kv);
+ break;
+ }
+ }
+ }
+
+ /* register new header in the temporal kv list */
+ kv = flb_kv_item_create_len(&c->headers,
+ (char *) key, key_len, (char *) val, val_len);
+ if (!kv) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * flb_http_get_header looks up a first value of request header.
+ * The return value should be destroyed after using.
+ * The return value is NULL, if the value is not found.
+ */
+flb_sds_t flb_http_get_header(struct flb_http_client *c,
+ const char *key, size_t key_len)
+{
+ flb_sds_t ret_str;
+ struct flb_kv *kv;
+ struct mk_list *head = NULL;
+ struct mk_list *tmp = NULL;
+
+ mk_list_foreach_safe(head, tmp, &c->headers) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ if (flb_sds_casecmp(kv->key, key, key_len) == 0) {
+ ret_str = flb_sds_create(kv->val);
+ return ret_str;
+ }
+ }
+
+ return NULL;
+}
+
+static int http_header_push(struct flb_http_client *c, struct flb_kv *header)
+{
+ char *tmp;
+ const char *key;
+ const char *val;
+ size_t key_len;
+ size_t val_len;
+ size_t required;
+ size_t new_size;
+
+ key = header->key;
+ key_len = flb_sds_len(header->key);
+ val = header->val;
+ val_len = flb_sds_len(header->val);
+
+ /*
+ * The new header will need enough space in the buffer:
+ *
+ * key : length of the key
+ * separator: ': ' (2 bytes)
+ * val : length of the key value
+ * CRLF : '\r\n' (2 bytes)
+ */
+ required = key_len + 2 + val_len + 2;
+
+ if (header_available(c, required) != 0) {
+ if (required < 512) {
+ new_size = c->header_size + 512;
+ }
+ else {
+ new_size = c->header_size + required;
+ }
+ tmp = flb_realloc(c->header_buf, new_size);
+ if (!tmp) {
+ flb_errno();
+ return -1;
+ }
+ c->header_buf = tmp;
+ c->header_size = new_size;
+ }
+
+ /* append the header key */
+ memcpy(c->header_buf + c->header_len,
+ key, key_len);
+ c->header_len += key_len;
+
+ /* append the separator */
+ c->header_buf[c->header_len++] = ':';
+ c->header_buf[c->header_len++] = ' ';
+
+ /* append the header value */
+ memcpy(c->header_buf + c->header_len,
+ val, val_len);
+ c->header_len += val_len;
+
+ /* Append the ending header CRLF */
+ c->header_buf[c->header_len++] = '\r';
+ c->header_buf[c->header_len++] = '\n';
+
+ return 0;
+}
+
+static int http_headers_compose(struct flb_http_client *c)
+{
+ int ret;
+ struct mk_list *head;
+ struct flb_kv *header;
+
+ /* Push header list to one buffer */
+ mk_list_foreach(head, &c->headers) {
+ header = mk_list_entry(head, struct flb_kv, _head);
+ ret = http_header_push(c, header);
+ if (ret != 0) {
+ flb_error("[http_client] cannot compose request headers");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void http_headers_destroy(struct flb_http_client *c)
+{
+ flb_kv_release(&c->headers);
+}
+
+int flb_http_set_keepalive(struct flb_http_client *c)
+{
+ /* check if 'keepalive' mode is enabled in the Upstream connection */
+ if (flb_stream_is_keepalive(c->u_conn->stream)) {
+ return -1;
+ }
+
+ /* append header */
+ return flb_http_add_header(c,
+ FLB_HTTP_HEADER_CONNECTION,
+ sizeof(FLB_HTTP_HEADER_CONNECTION) - 1,
+ FLB_HTTP_HEADER_KA,
+ sizeof(FLB_HTTP_HEADER_KA) - 1);
+}
+
+/* Adds a header specifying that the payload is compressed with gzip */
+int flb_http_set_content_encoding_gzip(struct flb_http_client *c)
+{
+ int ret;
+
+ ret = flb_http_add_header(c,
+ FLB_HTTP_HEADER_CONTENT_ENCODING,
+ sizeof(FLB_HTTP_HEADER_CONTENT_ENCODING) - 1,
+ "gzip", 4);
+ return ret;
+}
+
+int flb_http_set_callback_context(struct flb_http_client *c,
+ struct flb_callback *cb_ctx)
+{
+ c->cb_ctx = cb_ctx;
+ return 0;
+}
+
+int flb_http_add_auth_header(struct flb_http_client *c,
+ const char *user, const char *passwd, const char *header) {
+ int ret;
+ int len_u;
+ int len_p;
+ int len_h;
+ int len_out;
+ char tmp[1024];
+ char *p;
+ size_t b64_len;
+
+ /*
+ * We allow a max of 255 bytes for user and password (255 each), meaning
+ * we need at least:
+ *
+ * 'Basic base64(user : passwd)' => ~688 bytes
+ *
+ */
+
+ len_u = strlen(user);
+
+ if (passwd) {
+ len_p = strlen(passwd);
+ }
+ else {
+ len_p = 0;
+ }
+
+ p = flb_malloc(len_u + len_p + 2);
+ if (!p) {
+ flb_errno();
+ return -1;
+ }
+
+ memcpy(p, user, len_u);
+ p[len_u] = ':';
+ len_out = len_u + 1;
+
+ if (passwd) {
+ memcpy(p + len_out, passwd, len_p);
+ len_out += len_p;
+ }
+ p[len_out] = '\0';
+
+ memcpy(tmp, "Basic ", 6);
+ ret = flb_base64_encode((unsigned char *) tmp + 6, sizeof(tmp) - 7, &b64_len,
+ (unsigned char *) p, len_out);
+ if (ret != 0) {
+ flb_free(p);
+ return -1;
+ }
+
+ flb_free(p);
+ b64_len += 6;
+
+ len_h = strlen(header);
+ ret = flb_http_add_header(c,
+ header,
+ len_h,
+ tmp, b64_len);
+ return ret;
+}
+
+int flb_http_basic_auth(struct flb_http_client *c,
+ const char *user, const char *passwd)
+{
+ return flb_http_add_auth_header(c, user, passwd, FLB_HTTP_HEADER_AUTH);
+}
+
+int flb_http_proxy_auth(struct flb_http_client *c,
+ const char *user, const char *passwd)
+{
+ return flb_http_add_auth_header(c, user, passwd, FLB_HTTP_HEADER_PROXY_AUTH);
+}
+
+int flb_http_bearer_auth(struct flb_http_client *c, const char *token)
+{
+ flb_sds_t header_buffer;
+ flb_sds_t header_line;
+ int result;
+
+ result = -1;
+
+ if (token == NULL) {
+ token = "";
+
+ /* Shouldn't we log this and return instead of sending
+ * a malformed value?
+ */
+ }
+
+ header_buffer = flb_sds_create_size(strlen(token) + 64);
+
+ if (header_buffer == NULL) {
+ return -1;
+ }
+
+ header_line = flb_sds_printf(&header_buffer, "Bearer %s", token);
+
+ if (header_line != NULL) {
+ result = flb_http_add_header(c,
+ FLB_HTTP_HEADER_AUTH,
+ strlen(FLB_HTTP_HEADER_AUTH),
+ header_line,
+ flb_sds_len(header_line));
+ }
+
+ flb_sds_destroy(header_buffer);
+
+ return result;
+}
+
+
+int flb_http_do(struct flb_http_client *c, size_t *bytes)
+{
+ int ret;
+ int r_bytes;
+ int crlf = 2;
+ int new_size;
+ ssize_t available;
+ size_t out_size;
+ size_t bytes_header = 0;
+ size_t bytes_body = 0;
+ char *tmp;
+
+ /* Append pending headers */
+ ret = http_headers_compose(c);
+ if (ret == -1) {
+ return -1;
+ }
+
+ /* check enough space for the ending CRLF */
+ if (header_available(c, crlf) != 0) {
+ new_size = c->header_size + 2;
+ tmp = flb_realloc(c->header_buf, new_size);
+ if (!tmp) {
+ flb_errno();
+ return -1;
+ }
+ c->header_buf = tmp;
+ c->header_size = new_size;
+ }
+
+ /* Append the ending header CRLF */
+ c->header_buf[c->header_len++] = '\r';
+ c->header_buf[c->header_len++] = '\n';
+
+#ifdef FLB_HAVE_HTTP_CLIENT_DEBUG
+ /* debug: request_headers callback */
+ flb_http_client_debug_cb(c, "_debug.http.request_headers");
+
+ /* debug: request_payload callback */
+ if (c->body_len > 0) {
+ flb_http_client_debug_cb(c, "_debug.http.request_payload");
+ }
+#endif
+
+ /* Write the header */
+ ret = flb_io_net_write(c->u_conn,
+ c->header_buf, c->header_len,
+ &bytes_header);
+ if (ret == -1) {
+ /* errno might be changed from the original call */
+ if (errno != 0) {
+ flb_errno();
+ }
+ return -1;
+ }
+
+ if (c->body_len > 0) {
+ ret = flb_io_net_write(c->u_conn,
+ c->body_buf, c->body_len,
+ &bytes_body);
+ if (ret == -1) {
+ flb_errno();
+ return -1;
+ }
+ }
+
+ /* number of sent bytes */
+ *bytes = (bytes_header + bytes_body);
+
+ /* Read the server response, we need at least 19 bytes */
+ c->resp.data_len = 0;
+ while (1) {
+ available = flb_http_buffer_available(c) - 1;
+ if (available <= 1) {
+ /*
+ * If there is no more space available on our buffer, try to
+ * increase it.
+ */
+ ret = flb_http_buffer_increase(c, FLB_HTTP_DATA_CHUNK,
+ &out_size);
+ if (ret == -1) {
+ /*
+ * We could not allocate more space, let the caller handle
+ * this.
+ */
+ flb_warn("[http_client] cannot increase buffer: current=%zu "
+ "requested=%zu max=%zu", c->resp.data_size,
+ c->resp.data_size + FLB_HTTP_DATA_CHUNK,
+ c->resp.data_size_max);
+ flb_upstream_conn_recycle(c->u_conn, FLB_FALSE);
+ return 0;
+ }
+ available = flb_http_buffer_available(c) - 1;
+ }
+
+ r_bytes = flb_io_net_read(c->u_conn,
+ c->resp.data + c->resp.data_len,
+ available);
+ if (r_bytes <= 0) {
+ if (c->flags & FLB_HTTP_10) {
+ break;
+ }
+ }
+
+ /* Always append a NULL byte */
+ if (r_bytes >= 0) {
+ c->resp.data_len += r_bytes;
+ c->resp.data[c->resp.data_len] = '\0';
+
+ ret = process_data(c);
+ if (ret == FLB_HTTP_ERROR) {
+ flb_warn("[http_client] malformed HTTP response from %s:%i on "
+ "connection #%i",
+ c->u_conn->upstream->tcp_host,
+ c->u_conn->upstream->tcp_port,
+ c->u_conn->fd);
+ return -1;
+ }
+ else if (ret == FLB_HTTP_OK) {
+ break;
+ }
+ else if (ret == FLB_HTTP_MORE) {
+ continue;
+ }
+ }
+ else {
+ flb_error("[http_client] broken connection to %s:%i ?",
+ c->u_conn->upstream->tcp_host,
+ c->u_conn->upstream->tcp_port);
+ return -1;
+ }
+ }
+
+ /* Check 'Connection' response header */
+ ret = check_connection(c);
+ if (ret == FLB_HTTP_OK) {
+ /*
+ * If the server replied that the connection will be closed
+ * and our Upstream connection is in keepalive mode, we must
+ * inactivate the connection.
+ */
+ if (c->resp.connection_close == FLB_TRUE) {
+ /* Do not recycle the connection (no more keepalive) */
+ flb_upstream_conn_recycle(c->u_conn, FLB_FALSE);
+ flb_debug("[http_client] server %s:%i will close connection #%i",
+ c->u_conn->upstream->tcp_host,
+ c->u_conn->upstream->tcp_port,
+ c->u_conn->fd);
+ }
+ }
+
+#ifdef FLB_HAVE_HTTP_CLIENT_DEBUG
+ flb_http_client_debug_cb(c, "_debug.http.response_headers");
+ if (c->resp.payload_size > 0) {
+ flb_http_client_debug_cb(c, "_debug.http.response_payload");
+ }
+#endif
+
+ return 0;
+}
+
+/*
+ * flb_http_client_proxy_connect opens a tunnel to a proxy server via
+ * http `CONNECT` method. This is needed for https traffic through a
+ * http proxy.
+ * More: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT
+ */
+int flb_http_client_proxy_connect(struct flb_connection *u_conn)
+{
+ struct flb_upstream *u = u_conn->upstream;
+ struct flb_http_client *c;
+ size_t b_sent;
+ int ret = -1;
+
+ /* Don't pass proxy when using FLB_HTTP_CONNECT */
+ flb_debug("[upstream] establishing http tunneling to proxy: host %s port %d", u->tcp_host, u->tcp_port);
+ c = flb_http_client(u_conn, FLB_HTTP_CONNECT, "", NULL,
+ 0, u->proxied_host, u->proxied_port, NULL, 0);
+
+ /* Setup proxy's username and password */
+ if (u->proxy_username && u->proxy_password) {
+ flb_debug("[upstream] proxy uses username %s password %s", u->proxy_username, u->proxy_password);
+ flb_http_proxy_auth(c, u->proxy_username, u->proxy_password);
+ }
+
+ flb_http_buffer_size(c, 4192);
+
+ flb_http_add_header(c, "User-Agent", 10, "Fluent-Bit", 10);
+
+ /* Send HTTP request */
+ ret = flb_http_do(c, &b_sent);
+
+ /* Validate HTTP response */
+ if (ret != 0) {
+ flb_error("[upstream] error in flb_establish_proxy: %d", ret);
+ ret = -1;
+ }
+ else {
+ /* The request was issued successfully, validate the 'error' field */
+ flb_debug("[upstream] proxy returned %d", c->resp.status);
+ if (c->resp.status == 200) {
+ ret = 0;
+ }
+ else {
+ flb_error("flb_establish_proxy error: %s", c->resp.payload);
+ ret = -1;
+ }
+ }
+
+ /* Cleanup */
+ flb_http_client_destroy(c);
+
+ return ret;
+}
+
+void flb_http_client_destroy(struct flb_http_client *c)
+{
+ http_headers_destroy(c);
+ flb_free(c->resp.data);
+ flb_free(c->header_buf);
+ flb_free((void *)c->proxy.host);
+ flb_free(c);
+}
diff --git a/fluent-bit/src/flb_http_client_debug.c b/fluent-bit/src/flb_http_client_debug.c
new file mode 100644
index 000000000..3f69547af
--- /dev/null
+++ b/fluent-bit/src/flb_http_client_debug.c
@@ -0,0 +1,196 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_callback.h>
+#include <fluent-bit/flb_http_client.h>
+
+/*
+ * Callbacks
+ * =========
+ */
+static void debug_cb_request_headers(char *name, void *p1, void *p2)
+{
+ struct flb_http_client *c = p1;
+
+ flb_idebug("[http] request headers\n%s", c->header_buf);
+}
+
+static void debug_cb_request_payload(char *name, void *p1, void *p2)
+{
+ unsigned char *ptr;
+ struct flb_http_client *c = p1;
+
+ if (c->body_len > 3) {
+ ptr = (unsigned char *) c->body_buf;
+ if (ptr[0] == 0x1F && ptr[1] == 0x8B && ptr[2] == 0x08) {
+ flb_idebug("[http] request payload (%d bytes)\n[GZIP binary content...]",
+ c->body_len);
+ }
+ else {
+ flb_idebug("[http] request payload (%d bytes)\n%s",
+ c->body_len, c->body_buf);
+ }
+ }
+ else {
+ flb_idebug("[http] request payload (%d bytes)\n%s",
+ c->body_len, c->body_buf);
+ }
+}
+
+static void debug_cb_response_headers(char *name, void *p1, void *p2)
+{
+ char tmp;
+ struct flb_http_client *c = p1;
+
+ /*
+ * Just to make easier debugging, we are going to put a NULL byte after
+ * the header break (\r\n\r\n) and then restore it.
+ */
+ tmp = *c->resp.headers_end;
+ *c->resp.headers_end = '\0';
+
+ flb_idebug("[http] response headers\n%s", c->resp.data);
+ *c->resp.headers_end = tmp;
+}
+
+static void debug_cb_response_payload(char *name, void *p1, void *p2)
+{
+ struct flb_http_client *c = p1;
+
+ flb_idebug("[http] response payload (%lu bytes)\n%s",
+ c->resp.payload_size, c->resp.payload);
+}
+
+struct flb_http_callback {
+ char *name;
+ void (*func)(char *, void *, void *);
+};
+
+/*
+ * Callbacks Table
+ */
+struct flb_http_callback http_callbacks[] = {
+ /* request */
+ { "_debug.http.request_headers", debug_cb_request_headers },
+ { "_debug.http.request_payload", debug_cb_request_payload },
+
+ /* response */
+ { "_debug.http.response_headers", debug_cb_response_headers },
+ { "_debug.http.response_payload", debug_cb_response_payload },
+ { 0 }
+};
+
+/*
+ * Exported Functions
+ * ==================
+ */
+/* Determinate if a http.debug property is valid or not */
+int flb_http_client_debug_property_is_valid(char *key, char *val)
+{
+ int i;
+ int ret;
+ int len;
+ struct flb_http_callback *cb;
+
+ if (!key) {
+ flb_error("[http_client] given property is invalid");
+ return -1;
+ }
+
+ if (!val) {
+ flb_error("[http_client] property '%s' don't have a valid value",
+ key);
+ return -1;
+ }
+
+ len = (sizeof(http_callbacks) / sizeof(struct flb_http_callback)) - 1;
+ for (i = 0; i < len ; i++) {
+ cb = &http_callbacks[i];
+ if (strcasecmp(key, cb->name) == 0) {
+ ret = flb_utils_bool(val);
+ if (ret == -1) {
+ return FLB_FALSE;
+ }
+ return FLB_TRUE;
+ }
+ }
+
+ return FLB_FALSE;
+}
+
+
+int flb_http_client_debug_cb(struct flb_http_client *c, char *name)
+{
+ int ret;
+
+ ret = flb_callback_do(c->cb_ctx, name, c, NULL);
+ return ret;
+}
+
+/*
+ * This function helps to setup 'HTTP' debugging mode on a HTTP client context
+ * using the list of configuration properties set by the instance. On this
+ * specific case we don't pass the 'plugin instance' reference since it can be
+ * an input, filter or output, we try to make this agnostic.
+ */
+int flb_http_client_debug_setup(struct flb_callback *cb_ctx,
+ struct mk_list *props)
+{
+ int i;
+ int len;
+ int ret;
+ const char *tmp;
+ struct flb_http_callback *cb;
+
+ /*
+ * Iterate table of callbacks, if the callbacks are not pre-defined in the
+ * context (this might happen when callbacks are overrided by a library
+ * caller), set the default ones.
+ */
+ len = (sizeof(http_callbacks) / sizeof(struct flb_http_callback)) - 1;
+ for (i = 0; i < len ; i++) {
+ cb = &http_callbacks[i];
+
+ /* Check if the debug property has been enabled */
+ tmp = flb_config_prop_get(cb->name, props);
+ if (!tmp) {
+ continue;
+ }
+
+ ret = flb_utils_bool(tmp);
+ if (ret == FLB_FALSE) {
+ continue;
+ }
+
+ ret = flb_callback_exists(cb_ctx, cb->name);
+ if (ret == FLB_FALSE) {
+ /* Set default callback */
+ ret = flb_callback_set(cb_ctx, cb->name, cb->func);
+ if (ret == -1) {
+ flb_error("[http_client] error setting up default "
+ "callback '%s'", cb->name);
+ }
+ }
+ }
+ return 0;
+}
diff --git a/fluent-bit/src/flb_input.c b/fluent-bit/src/flb_input.c
new file mode 100644
index 000000000..1c4faad4d
--- /dev/null
+++ b/fluent-bit/src/flb_input.c
@@ -0,0 +1,1965 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <stdlib.h>
+
+#include <monkey/mk_core.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_env.h>
+#include <fluent-bit/flb_pipe.h>
+#include <fluent-bit/flb_macros.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_input_thread.h>
+#include <fluent-bit/flb_error.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_plugin_proxy.h>
+#include <fluent-bit/flb_engine.h>
+#include <fluent-bit/flb_metrics.h>
+#include <fluent-bit/flb_storage.h>
+#include <fluent-bit/flb_downstream.h>
+#include <fluent-bit/flb_plugin.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_hash_table.h>
+#include <fluent-bit/flb_scheduler.h>
+#include <fluent-bit/flb_ring_buffer.h>
+#include <fluent-bit/flb_processor.h>
+
+/* input plugin macro helpers */
+#include <fluent-bit/flb_input_plugin.h>
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+#include <fluent-bit/flb_chunk_trace.h>
+#endif /* FLB_HAVE_CHUNK_TRACE */
+
+struct flb_libco_in_params libco_in_param;
+pthread_key_t libco_in_param_key;
+
+#define protcmp(a, b) strncasecmp(a, b, strlen(a))
+
+/*
+ * Ring buffer size: we make space for 512 entries that each input instance can
+ * use to enqueue data. Note that this value is fixed and only affect input plugins
+ * which runs in threaded mode (separate thread)
+ *
+ * Ring buffer window: the current window size is set to 5% which means that the
+ * ring buffer will emit a flush request whenever there are 51 records or more
+ * awaiting to be consumed.
+ */
+
+#define FLB_INPUT_RING_BUFFER_SIZE (sizeof(void *) * 1024)
+#define FLB_INPUT_RING_BUFFER_WINDOW (5)
+
+
+static int check_protocol(const char *prot, const char *output)
+{
+ int len;
+
+ len = strlen(prot);
+ if (len != strlen(output)) {
+ return 0;
+ }
+
+ if (protcmp(prot, output) != 0) {
+ return 0;
+ }
+
+ return 1;
+}
+
+static inline int instance_id(struct flb_input_plugin *p,
+ struct flb_config *config) \
+{
+ int c = 0;
+ struct mk_list *head;
+ struct flb_input_instance *entry;
+
+ mk_list_foreach(head, &config->inputs) {
+ entry = mk_list_entry(head, struct flb_input_instance, _head);
+ if (entry->id == c) {
+ c++;
+ }
+ }
+
+ return c;
+}
+
+/* Generate a new collector ID for the instance in question */
+static int collector_id(struct flb_input_instance *ins)
+{
+ int id = 0;
+ struct flb_input_collector *collector;
+
+ if (mk_list_is_empty(&ins->collectors) == 0) {
+ return id;
+ }
+
+ collector = mk_list_entry_last(&ins->collectors,
+ struct flb_input_collector,
+ _head);
+ return (collector->id + 1);
+}
+
+void flb_input_net_default_listener(const char *listen, int port,
+ struct flb_input_instance *ins)
+{
+ /* Set default network configuration */
+ if (!ins->host.listen) {
+ ins->host.listen = flb_sds_create(listen);
+ }
+ if (ins->host.port == 0) {
+ ins->host.port = port;
+ }
+}
+
+/* Check input plugin's log level.
+ * Not for core plugins but for Golang plugins.
+ * Golang plugins do not have thread-local flb_worker_ctx information. */
+int flb_input_log_check(struct flb_input_instance *ins, int l)
+{
+ if (ins->log_level < l) {
+ return FLB_FALSE;
+ }
+
+ return FLB_TRUE;
+}
+
+/* Create an input plugin instance */
+struct flb_input_instance *flb_input_new(struct flb_config *config,
+ const char *input, void *data,
+ int public_only)
+{
+ int id;
+ int ret;
+ int flags = 0;
+ struct mk_list *head;
+ struct flb_input_plugin *plugin;
+ struct flb_input_instance *instance = NULL;
+
+/* use for locking the use of the chunk trace context. */
+#ifdef FLB_HAVE_CHUNK_TRACE
+ pthread_mutexattr_t attr = {0};
+ pthread_mutexattr_init(&attr);
+#endif
+
+ if (!input) {
+ return NULL;
+ }
+
+ mk_list_foreach(head, &config->in_plugins) {
+ plugin = mk_list_entry(head, struct flb_input_plugin, _head);
+ if (!check_protocol(plugin->name, input)) {
+ plugin = NULL;
+ continue;
+ }
+
+ /*
+ * Check if the plugin is private and validate the 'public_only'
+ * requirement.
+ */
+ if (public_only == FLB_TRUE && plugin->flags & FLB_INPUT_PRIVATE) {
+ return NULL;
+ }
+
+ /* Create plugin instance */
+ instance = flb_calloc(1, sizeof(struct flb_input_instance));
+ if (!instance) {
+ flb_errno();
+ return NULL;
+ }
+ instance->config = config;
+
+ /* Get an ID */
+ id = instance_id(plugin, config);
+
+ /* Index for log Chunks (hash table) */
+ instance->ht_log_chunks = flb_hash_table_create(FLB_HASH_TABLE_EVICT_NONE,
+ 512, 0);
+ if (!instance->ht_log_chunks) {
+ flb_free(instance);
+ return NULL;
+ }
+
+ /* Index for metric Chunks (hash table) */
+ instance->ht_metric_chunks = flb_hash_table_create(FLB_HASH_TABLE_EVICT_NONE,
+ 512, 0);
+ if (!instance->ht_metric_chunks) {
+ flb_hash_table_destroy(instance->ht_log_chunks);
+ flb_free(instance);
+ return NULL;
+ }
+
+ /* Index for trace Chunks (hash table) */
+ instance->ht_trace_chunks = flb_hash_table_create(FLB_HASH_TABLE_EVICT_NONE,
+ 512, 0);
+ if (!instance->ht_trace_chunks) {
+ flb_hash_table_destroy(instance->ht_log_chunks);
+ flb_hash_table_destroy(instance->ht_metric_chunks);
+ flb_free(instance);
+ return NULL;
+ }
+
+ /* format name (with instance id) */
+ snprintf(instance->name, sizeof(instance->name) - 1,
+ "%s.%i", plugin->name, id);
+
+ if (plugin->type == FLB_INPUT_PLUGIN_CORE) {
+ instance->context = NULL;
+ }
+ else {
+ struct flb_plugin_proxy_context *ctx;
+
+ ctx = flb_calloc(1, sizeof(struct flb_plugin_proxy_context));
+ if (!ctx) {
+ flb_errno();
+ flb_free(instance);
+ return NULL;
+ }
+
+ ctx->proxy = plugin->proxy;
+
+ instance->context = ctx;
+ }
+
+ /* initialize remaining vars */
+ instance->alias = NULL;
+ instance->id = id;
+ instance->flags = plugin->flags;
+ instance->p = plugin;
+ instance->tag = NULL;
+ instance->tag_len = 0;
+ instance->tag_default = FLB_FALSE;
+ instance->routable = FLB_TRUE;
+ instance->data = data;
+ instance->storage = NULL;
+ instance->storage_type = -1;
+ instance->log_level = -1;
+ instance->log_suppress_interval = -1;
+ instance->runs_in_coroutine = FLB_FALSE;
+
+ /* net */
+ instance->host.name = NULL;
+ instance->host.address = NULL;
+ instance->host.uri = NULL;
+ instance->host.listen = NULL;
+ instance->host.ipv6 = FLB_FALSE;
+
+ /* Initialize list heads */
+ mk_list_init(&instance->routes_direct);
+ mk_list_init(&instance->routes);
+ mk_list_init(&instance->tasks);
+ mk_list_init(&instance->chunks);
+ mk_list_init(&instance->collectors);
+ mk_list_init(&instance->input_coro_list);
+ mk_list_init(&instance->input_coro_list_destroy);
+ mk_list_init(&instance->downstreams);
+ mk_list_init(&instance->upstreams);
+
+ /* Initialize properties list */
+ flb_kv_init(&instance->properties);
+ flb_kv_init(&instance->net_properties);
+
+ /* Plugin use networking */
+ if (plugin->flags & (FLB_INPUT_NET | FLB_INPUT_NET_SERVER)) {
+ ret = flb_net_host_set(plugin->name, &instance->host, input);
+ if (ret != 0) {
+ flb_free(instance);
+ return NULL;
+ }
+ }
+
+/* initialize lock for access to chunk trace context. */
+#ifdef FLB_HAVE_CHUNK_TRACE
+ pthread_mutex_init(&instance->chunk_trace_lock, &attr);
+#endif
+
+ /* Parent plugin flags */
+ flags = instance->flags;
+ if (flags & FLB_IO_TCP) {
+ instance->use_tls = FLB_FALSE;
+ }
+ else if (flags & FLB_IO_TLS) {
+ instance->use_tls = FLB_TRUE;
+ }
+ else if (flags & FLB_IO_OPT_TLS) {
+ /* TLS must be enabled manually in the config */
+ instance->use_tls = FLB_FALSE;
+ instance->flags |= FLB_IO_TLS;
+ }
+
+#ifdef FLB_HAVE_TLS
+ instance->tls = NULL;
+ instance->tls_debug = -1;
+ instance->tls_verify = FLB_TRUE;
+ instance->tls_vhost = NULL;
+ instance->tls_ca_path = NULL;
+ instance->tls_ca_file = NULL;
+ instance->tls_crt_file = NULL;
+ instance->tls_key_file = NULL;
+ instance->tls_key_passwd = NULL;
+#endif
+
+ /* Plugin requires a co-routine context ? */
+ if (plugin->flags & FLB_INPUT_CORO) {
+ instance->runs_in_coroutine = FLB_TRUE;
+ }
+
+ /* Plugin will run in a separate thread ? */
+ if (plugin->flags & FLB_INPUT_THREADED) {
+ instance->is_threaded = FLB_TRUE;
+
+ }
+
+ /* allocate a ring buffer */
+ instance->rb = flb_ring_buffer_create(FLB_INPUT_RING_BUFFER_SIZE);
+ if (!instance->rb) {
+ flb_error("instance %s could not initialize ring buffer",
+ flb_input_name(instance));
+ flb_free(instance);
+ return NULL;
+ }
+
+ instance->mem_buf_status = FLB_INPUT_RUNNING;
+ instance->mem_buf_limit = 0;
+ instance->mem_chunks_size = 0;
+ instance->storage_buf_status = FLB_INPUT_RUNNING;
+ mk_list_add(&instance->_head, &config->inputs);
+
+ /* processor instance */
+ instance->processor = flb_processor_create(config, instance->name, instance, FLB_PLUGIN_INPUT);
+ }
+
+ return instance;
+}
+
+static inline int prop_key_check(const char *key, const char *kv, int k_len)
+{
+ int len;
+
+ len = strlen(key);
+
+ if (strncasecmp(key, kv, k_len) == 0 && len == k_len) {
+ return 0;
+ }
+
+ return -1;
+}
+
+struct flb_input_instance *flb_input_get_instance(struct flb_config *config,
+ int ins_id)
+{
+ struct mk_list *head;
+ struct flb_input_instance *ins;
+
+ mk_list_foreach(head, &config->inputs) {
+ ins = mk_list_entry(head, struct flb_input_instance, _head);
+ if (ins->id == ins_id) {
+ break;
+ }
+ ins = NULL;
+ }
+
+ if (!ins) {
+ return NULL;
+ }
+
+ return ins;
+}
+
+static void flb_input_coro_destroy(struct flb_input_coro *input_coro)
+{
+ flb_debug("[input coro] destroy coro_id=%i", input_coro->id);
+
+ mk_list_del(&input_coro->_head);
+ flb_coro_destroy(input_coro->coro);
+ flb_free(input_coro);
+}
+
+int flb_input_coro_finished(struct flb_config *config, int ins_id)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_input_instance *ins;
+ struct flb_input_coro *input_coro;
+
+ ins = flb_input_get_instance(config, ins_id);
+ if (!ins) {
+ return -1;
+ }
+
+ /* Look for input coroutines that needs to be destroyed */
+ mk_list_foreach_safe(head, tmp, &ins->input_coro_list_destroy) {
+ input_coro = mk_list_entry(head, struct flb_input_coro, _head);
+ flb_input_coro_destroy(input_coro);
+ }
+
+ return 0;
+}
+
+void flb_input_coro_prepare_destroy(struct flb_input_coro *input_coro)
+{
+ struct flb_input_instance *ins = input_coro->ins;
+
+ /* move flb_input_coro from 'input_coro_list' to 'input_coro_list_destroy' */
+ mk_list_del(&input_coro->_head);
+ mk_list_add(&input_coro->_head, &ins->input_coro_list_destroy);
+}
+
+int flb_input_name_exists(const char *name, struct flb_config *config)
+{
+ struct mk_list *head;
+ struct flb_input_instance *ins;
+
+ mk_list_foreach(head, &config->inputs) {
+ ins = mk_list_entry(head, struct flb_input_instance, _head);
+ if (strcmp(ins->name, name) == 0) {
+ return FLB_TRUE;
+ }
+
+ if (ins->alias) {
+ if (strcmp(ins->alias, name) == 0) {
+ return FLB_TRUE;
+ }
+ }
+ }
+
+ return FLB_FALSE;
+}
+
+struct mk_event_loop *flb_input_event_loop_get(struct flb_input_instance *ins)
+{
+ struct flb_input_thread_instance *thi;
+
+ if (flb_input_is_threaded(ins)) {
+ thi = ins->thi;
+ return thi->evl;
+ }
+
+ return ins->config->evl;
+}
+
+/* Override a configuration property for the given input_instance plugin */
+int flb_input_set_property(struct flb_input_instance *ins,
+ const char *k, const char *v)
+{
+ int len;
+ int ret;
+ int enabled;
+ ssize_t limit;
+ flb_sds_t tmp = NULL;
+ struct flb_kv *kv;
+
+ len = strlen(k);
+ tmp = flb_env_var_translate(ins->config->env, v);
+ if (tmp) {
+ if (flb_sds_len(tmp) == 0) {
+ flb_sds_destroy(tmp);
+ tmp = NULL;
+ }
+ }
+
+ /* Check if the key is a known/shared property */
+ if (prop_key_check("tag", k, len) == 0 && tmp) {
+ flb_utils_set_plugin_string_property("tag", &ins->tag, tmp);
+ ins->tag_len = flb_sds_len(tmp);
+ ins->tag_default = FLB_FALSE;
+ }
+ else if (prop_key_check("log_level", k, len) == 0 && tmp) {
+ ret = flb_log_get_level_str(tmp);
+ flb_sds_destroy(tmp);
+ if (ret == -1) {
+ return -1;
+ }
+ ins->log_level = ret;
+ }
+ else if (prop_key_check("log_suppress_interval", k, len) == 0 && tmp) {
+ ret = flb_utils_time_to_seconds(tmp);
+ flb_sds_destroy(tmp);
+ if (ret == -1) {
+ return -1;
+ }
+ ins->log_suppress_interval = ret;
+ }
+ else if (prop_key_check("routable", k, len) == 0 && tmp) {
+ ins->routable = flb_utils_bool(tmp);
+ flb_sds_destroy(tmp);
+ }
+ else if (prop_key_check("alias", k, len) == 0 && tmp) {
+ flb_utils_set_plugin_string_property("alias", &ins->alias, tmp);
+ }
+ else if (prop_key_check("mem_buf_limit", k, len) == 0 && tmp) {
+ limit = flb_utils_size_to_bytes(tmp);
+ flb_sds_destroy(tmp);
+ if (limit == -1) {
+ return -1;
+ }
+ ins->mem_buf_limit = (size_t) limit;
+ }
+ else if (prop_key_check("listen", k, len) == 0) {
+ flb_utils_set_plugin_string_property("listen", &ins->host.listen, tmp);
+ }
+ else if (prop_key_check("host", k, len) == 0) {
+ flb_utils_set_plugin_string_property("host", &ins->host.name, tmp);
+ }
+ else if (prop_key_check("port", k, len) == 0) {
+ if (tmp) {
+ ins->host.port = atoi(tmp);
+ flb_sds_destroy(tmp);
+ }
+ }
+ else if (prop_key_check("ipv6", k, len) == 0 && tmp) {
+ ins->host.ipv6 = flb_utils_bool(tmp);
+ flb_sds_destroy(tmp);
+ }
+ else if (strncasecmp("net.", k, 4) == 0 && tmp) {
+ kv = flb_kv_item_create(&ins->net_properties, (char *) k, NULL);
+ if (!kv) {
+ if (tmp) {
+ flb_sds_destroy(tmp);
+ }
+ return -1;
+ }
+ kv->val = tmp;
+ }
+
+#ifdef FLB_HAVE_TLS
+ else if (prop_key_check("tls", k, len) == 0 && tmp) {
+ ins->use_tls = flb_utils_bool(tmp);
+ if (ins->use_tls == FLB_TRUE && ((ins->flags & FLB_IO_TLS) == 0)) {
+ flb_error("[config] %s does not support TLS", ins->name);
+ flb_sds_destroy(tmp);
+ return -1;
+ }
+ flb_sds_destroy(tmp);
+ }
+ else if (prop_key_check("tls.verify", k, len) == 0 && tmp) {
+ ins->tls_verify = flb_utils_bool(tmp);
+ flb_sds_destroy(tmp);
+ }
+ else if (prop_key_check("tls.debug", k, len) == 0 && tmp) {
+ ins->tls_debug = atoi(tmp);
+ flb_sds_destroy(tmp);
+ }
+ else if (prop_key_check("tls.vhost", k, len) == 0) {
+ flb_utils_set_plugin_string_property("tls.vhost", &ins->tls_vhost, tmp);
+ }
+ else if (prop_key_check("tls.ca_path", k, len) == 0) {
+ flb_utils_set_plugin_string_property("tls.ca_path", &ins->tls_ca_path, tmp);
+ }
+ else if (prop_key_check("tls.ca_file", k, len) == 0) {
+ flb_utils_set_plugin_string_property("tls.ca_file", &ins->tls_ca_file, tmp);
+ }
+ else if (prop_key_check("tls.crt_file", k, len) == 0) {
+ flb_utils_set_plugin_string_property("tls.crt_file", &ins->tls_crt_file, tmp);
+ }
+ else if (prop_key_check("tls.key_file", k, len) == 0) {
+ flb_utils_set_plugin_string_property("tls.key_file", &ins->tls_key_file, tmp);
+ }
+ else if (prop_key_check("tls.key_passwd", k, len) == 0) {
+ flb_utils_set_plugin_string_property("tls.key_passwd", &ins->tls_key_passwd, tmp);
+ }
+#endif
+ else if (prop_key_check("storage.type", k, len) == 0 && tmp) {
+ /* Set the storage type */
+ if (strcasecmp(tmp, "filesystem") == 0) {
+ ins->storage_type = FLB_STORAGE_FS;
+ }
+ else if (strcasecmp(tmp, "memory") == 0) {
+ ins->storage_type = FLB_STORAGE_MEM;
+ }
+ else if (strcasecmp(tmp, "memrb") == 0) {
+ ins->storage_type = FLB_STORAGE_MEMRB;
+ }
+ else {
+ flb_sds_destroy(tmp);
+ return -1;
+ }
+ flb_sds_destroy(tmp);
+ }
+ else if (prop_key_check("threaded", k, len) == 0 && tmp) {
+ enabled = flb_utils_bool(tmp);
+ flb_sds_destroy(tmp);
+
+ if (enabled == -1) {
+ return -1;
+ }
+
+ ins->is_threaded = enabled;
+ }
+ else if (prop_key_check("storage.pause_on_chunks_overlimit", k, len) == 0 && tmp) {
+ if (ins->storage_type == FLB_STORAGE_FS) {
+ ret = flb_utils_bool(tmp);
+ flb_sds_destroy(tmp);
+ if (ret == -1) {
+ return -1;
+ }
+ ins->storage_pause_on_chunks_overlimit = ret;
+ }
+ }
+ else {
+ /*
+ * Create the property, we don't pass the value since we will
+ * map it directly to avoid an extra memory allocation.
+ */
+ kv = flb_kv_item_create(&ins->properties, (char *) k, NULL);
+ if (!kv) {
+ if (tmp) {
+ flb_sds_destroy(tmp);
+ }
+ return -1;
+ }
+ kv->val = tmp;
+ }
+
+ return 0;
+}
+
+const char *flb_input_get_property(const char *key,
+ struct flb_input_instance *ins)
+{
+ return flb_config_prop_get(key, &ins->properties);
+}
+
+#ifdef FLB_HAVE_METRICS
+void *flb_input_get_cmt_instance(struct flb_input_instance *ins)
+{
+ return (void *)ins->cmt;
+}
+#endif
+
+/* Return an instance name or alias */
+const char *flb_input_name(struct flb_input_instance *ins)
+{
+ if (ins->alias) {
+ return ins->alias;
+ }
+
+ return ins->name;
+}
+
+void flb_input_instance_destroy(struct flb_input_instance *ins)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_input_collector *collector;
+
+ if (ins->alias) {
+ flb_sds_destroy(ins->alias);
+ }
+
+ /* Remove URI context */
+ if (ins->host.uri) {
+ flb_uri_destroy(ins->host.uri);
+ }
+
+ if (ins->host.name) {
+ flb_sds_destroy(ins->host.name);
+ }
+ if (ins->host.address) {
+ flb_sds_destroy(ins->host.address);
+ }
+ if (ins->host.listen) {
+ flb_sds_destroy(ins->host.listen);
+ }
+
+#ifdef FLB_HAVE_TLS
+ if (ins->use_tls) {
+ if (ins->tls != NULL) {
+ flb_tls_destroy(ins->tls);
+ }
+ }
+
+ if (ins->tls_config_map) {
+ flb_config_map_destroy(ins->tls_config_map);
+ }
+#endif
+
+ if (ins->tls_vhost) {
+ flb_sds_destroy(ins->tls_vhost);
+ }
+
+ if (ins->tls_ca_path) {
+ flb_sds_destroy(ins->tls_ca_path);
+ }
+
+ if (ins->tls_ca_file) {
+ flb_sds_destroy(ins->tls_ca_file);
+ }
+
+ if (ins->tls_crt_file) {
+ flb_sds_destroy(ins->tls_crt_file);
+ }
+
+ if (ins->tls_key_file) {
+ flb_sds_destroy(ins->tls_key_file);
+ }
+
+ if (ins->tls_key_passwd) {
+ flb_sds_destroy(ins->tls_key_passwd);
+ }
+
+ /* release the tag if any */
+ flb_sds_destroy(ins->tag);
+
+ /* Let the engine remove any pending task */
+ flb_engine_destroy_tasks(&ins->tasks);
+
+ /* release properties */
+ flb_kv_release(&ins->properties);
+ flb_kv_release(&ins->net_properties);
+
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+ flb_chunk_trace_context_destroy(ins);
+#endif /* FLB_HAVE_CHUNK_TRACE */
+
+ /* Remove metrics */
+#ifdef FLB_HAVE_METRICS
+ if (ins->cmt) {
+ cmt_destroy(ins->cmt);
+ }
+
+ if (ins->metrics) {
+ flb_metrics_destroy(ins->metrics);
+ }
+#endif
+
+ if (ins->storage) {
+ flb_storage_input_destroy(ins);
+ }
+
+ /* destroy config map */
+ if (ins->config_map) {
+ flb_config_map_destroy(ins->config_map);
+ }
+
+ if (ins->net_config_map) {
+ flb_config_map_destroy(ins->net_config_map);
+ }
+
+ /* hash table for chunks */
+ if (ins->ht_log_chunks) {
+ flb_hash_table_destroy(ins->ht_log_chunks);
+ }
+
+ if (ins->ht_metric_chunks) {
+ flb_hash_table_destroy(ins->ht_metric_chunks);
+ }
+
+ if (ins->ht_trace_chunks) {
+ flb_hash_table_destroy(ins->ht_trace_chunks);
+ }
+
+ if (ins->ch_events[0] > 0) {
+ mk_event_closesocket(ins->ch_events[0]);
+ }
+
+ if (ins->ch_events[1] > 0) {
+ mk_event_closesocket(ins->ch_events[1]);
+ }
+
+ /* Collectors */
+ mk_list_foreach_safe(head, tmp, &ins->collectors) {
+ collector = mk_list_entry(head, struct flb_input_collector, _head);
+ mk_list_del(&collector->_head);
+ flb_input_collector_destroy(collector);
+ }
+
+ /* delete storage context */
+ flb_storage_input_destroy(ins);
+
+ mk_list_del(&ins->_head);
+
+ /* ring buffer */
+ if (ins->rb) {
+ flb_input_chunk_ring_buffer_cleanup(ins);
+ flb_ring_buffer_destroy(ins->rb);
+ }
+
+ /* processor */
+ if (ins->processor) {
+ flb_processor_destroy(ins->processor);
+ }
+ flb_free(ins);
+}
+
+int flb_input_coro_id_get(struct flb_input_instance *ins)
+{
+ int id;
+ int max = (2 << 13) - 1; /* max for 14 bits */
+
+ id = ins->input_coro_id;
+ ins->input_coro_id++;
+
+ /* reset once it reach the maximum allowed */
+ if (ins->input_coro_id > max) {
+ ins->input_coro_id = 0;
+ }
+
+ return id;
+}
+
+static int input_instance_channel_events_init(struct flb_input_instance *ins)
+{
+ int ret;
+ struct mk_event_loop *evl;
+
+ evl = flb_input_event_loop_get(ins);
+
+ /* Input event channel: used for co-routines to report return status */
+ ret = mk_event_channel_create(evl,
+ &ins->ch_events[0],
+ &ins->ch_events[1],
+ ins);
+ if (ret != 0) {
+ flb_error("could not create events channels for '%s'",
+ flb_input_name(ins));
+ return -1;
+ }
+
+ flb_debug("[%s:%s] created event channels: read=%i write=%i",
+ ins->p->name, flb_input_name(ins),
+ ins->ch_events[0], ins->ch_events[1]);
+
+ /*
+ * Note: mk_event_channel_create() sets a type = MK_EVENT_NOTIFICATION by
+ * default, we need to overwrite this value so we can do a clean check
+ * into the Engine when the event is triggered.
+ */
+ ins->event.type = FLB_ENGINE_EV_INPUT;
+
+ return 0;
+}
+
+int flb_input_net_property_check(struct flb_input_instance *ins,
+ struct flb_config *config)
+{
+ int ret = 0;
+
+ /* Get Downstream net_setup configmap */
+ ins->net_config_map = flb_downstream_get_config_map(config);
+ if (!ins->net_config_map) {
+ flb_input_instance_destroy(ins);
+ return -1;
+ }
+
+ /*
+ * Validate 'net.*' properties: if the plugin use the Downstream interface,
+ * it might receive some networking settings.
+ */
+ if (mk_list_size(&ins->net_properties) > 0) {
+ ret = flb_config_map_properties_check(ins->p->name,
+ &ins->net_properties,
+ ins->net_config_map);
+ if (ret == -1) {
+ if (config->program_name) {
+ flb_helper("try the command: %s -i %s -h\n",
+ config->program_name, ins->p->name);
+ }
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int flb_input_plugin_property_check(struct flb_input_instance *ins,
+ struct flb_config *config)
+{
+ int ret = 0;
+ struct mk_list *config_map;
+ struct flb_input_plugin *p = ins->p;
+
+ if (p->config_map) {
+ /*
+ * Create a dynamic version of the configmap that will be used by the specific
+ * instance in question.
+ */
+ config_map = flb_config_map_create(config, p->config_map);
+ if (!config_map) {
+ flb_error("[input] error loading config map for '%s' plugin",
+ p->name);
+ flb_input_instance_destroy(ins);
+ return -1;
+ }
+ ins->config_map = config_map;
+
+ /* Validate incoming properties against config map */
+ ret = flb_config_map_properties_check(ins->p->name,
+ &ins->properties, ins->config_map);
+ if (ret == -1) {
+ if (config->program_name) {
+ flb_helper("try the command: %s -i %s -h\n",
+ config->program_name, ins->p->name);
+ }
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int flb_input_instance_init(struct flb_input_instance *ins,
+ struct flb_config *config)
+{
+ int ret;
+ struct flb_config *ctx = ins->config;
+ struct flb_input_plugin *p = ins->p;
+ int tls_session_mode;
+
+ if (ins->log_level == -1 && config->log != NULL) {
+ ins->log_level = config->log->level;
+ }
+
+ /* Skip pseudo input plugins */
+ if (!p) {
+ return 0;
+ }
+
+
+#ifdef FLB_HAVE_METRICS
+ uint64_t ts;
+ char *name;
+
+ name = (char *) flb_input_name(ins);
+ ts = cfl_time_now();
+
+ /* CMetrics */
+ ins->cmt = cmt_create();
+ if (!ins->cmt) {
+ flb_error("[input] could not create cmetrics context: %s",
+ flb_input_name(ins));
+ return -1;
+ }
+
+ /*
+ * Register generic input plugin metrics
+ * -------------------------------------
+ */
+
+ /* fluentbit_input_bytes_total */
+ ins->cmt_bytes = \
+ cmt_counter_create(ins->cmt,
+ "fluentbit", "input", "bytes_total",
+ "Number of input bytes.",
+ 1, (char *[]) {"name"});
+ cmt_counter_set(ins->cmt_bytes, ts, 0, 1, (char *[]) {name});
+
+ /* fluentbit_input_records_total */
+ ins->cmt_records = \
+ cmt_counter_create(ins->cmt,
+ "fluentbit", "input", "records_total",
+ "Number of input records.",
+ 1, (char *[]) {"name"});
+ cmt_counter_set(ins->cmt_records, ts, 0, 1, (char *[]) {name});
+
+ /* Storage Metrics */
+ if (ctx->storage_metrics == FLB_TRUE) {
+ /* fluentbit_input_storage_overlimit */
+ ins->cmt_storage_overlimit = \
+ cmt_gauge_create(ins->cmt,
+ "fluentbit", "input",
+ "storage_overlimit",
+ "Is the input memory usage overlimit ?.",
+ 1, (char *[]) {"name"});
+ cmt_gauge_set(ins->cmt_storage_overlimit, ts, 0, 1, (char *[]) {name});
+
+ /* fluentbit_input_storage_memory_bytes */
+ ins->cmt_storage_memory_bytes = \
+ cmt_gauge_create(ins->cmt,
+ "fluentbit", "input",
+ "storage_memory_bytes",
+ "Memory bytes used by the chunks.",
+ 1, (char *[]) {"name"});
+ cmt_gauge_set(ins->cmt_storage_memory_bytes, ts, 0, 1, (char *[]) {name});
+
+ /* fluentbit_input_storage_chunks */
+ ins->cmt_storage_chunks = \
+ cmt_gauge_create(ins->cmt,
+ "fluentbit", "input",
+ "storage_chunks",
+ "Total number of chunks.",
+ 1, (char *[]) {"name"});
+ cmt_gauge_set(ins->cmt_storage_chunks, ts, 0, 1, (char *[]) {name});
+
+ /* fluentbit_input_storage_chunks_up */
+ ins->cmt_storage_chunks_up = \
+ cmt_gauge_create(ins->cmt,
+ "fluentbit", "input",
+ "storage_chunks_up",
+ "Total number of chunks up in memory.",
+ 1, (char *[]) {"name"});
+ cmt_gauge_set(ins->cmt_storage_chunks_up, ts, 0, 1, (char *[]) {name});
+
+ /* fluentbit_input_storage_chunks_down */
+ ins->cmt_storage_chunks_down = \
+ cmt_gauge_create(ins->cmt,
+ "fluentbit", "input",
+ "storage_chunks_down",
+ "Total number of chunks down.",
+ 1, (char *[]) {"name"});
+ cmt_gauge_set(ins->cmt_storage_chunks_down, ts, 0, 1, (char *[]) {name});
+
+ /* fluentbit_input_storage_chunks_busy */
+ ins->cmt_storage_chunks_busy = \
+ cmt_gauge_create(ins->cmt,
+ "fluentbit", "input",
+ "storage_chunks_busy",
+ "Total number of chunks in a busy state.",
+ 1, (char *[]) {"name"});
+ cmt_gauge_set(ins->cmt_storage_chunks_busy, ts, 0, 1, (char *[]) {name});
+
+ /* fluentbit_input_storage_chunks_busy_bytes */
+ ins->cmt_storage_chunks_busy_bytes = \
+ cmt_gauge_create(ins->cmt,
+ "fluentbit", "input",
+ "storage_chunks_busy_bytes",
+ "Total number of bytes used by chunks in a busy state.",
+ 1, (char *[]) {"name"});
+ cmt_gauge_set(ins->cmt_storage_chunks_busy_bytes, ts, 0, 1, (char *[]) {name});
+ }
+
+ if (ins->storage_type == FLB_STORAGE_MEMRB) {
+ /* fluentbit_input_memrb_dropped_chunks */
+ ins->cmt_memrb_dropped_chunks = cmt_counter_create(ins->cmt,
+ "fluentbit", "input",
+ "memrb_dropped_chunks",
+ "Number of memrb dropped chunks.",
+ 1, (char *[]) {"name"});
+ cmt_counter_set(ins->cmt_memrb_dropped_chunks, ts, 0, 1, (char *[]) {name});
+
+
+ /* fluentbit_input_memrb_dropped_bytes */
+ ins->cmt_memrb_dropped_bytes = cmt_counter_create(ins->cmt,
+ "fluentbit", "input",
+ "memrb_dropped_bytes",
+ "Number of memrb dropped bytes.",
+ 1, (char *[]) {"name"});
+
+ cmt_counter_set(ins->cmt_memrb_dropped_bytes, ts, 0, 1, (char *[]) {name});
+ }
+
+ /* OLD Metrics */
+ ins->metrics = flb_metrics_create(name);
+ if (ins->metrics) {
+ flb_metrics_add(FLB_METRIC_N_RECORDS, "records", ins->metrics);
+ flb_metrics_add(FLB_METRIC_N_BYTES, "bytes", ins->metrics);
+ }
+#endif
+
+ /*
+ * Before to call the initialization callback, make sure that the received
+ * configuration parameters are valid if the plugin is registering a config map.
+ */
+ if (flb_input_plugin_property_check(ins, config) == -1) {
+ return -1;
+ }
+
+#ifdef FLB_HAVE_TLS
+ if (ins->use_tls == FLB_TRUE) {
+ if ((p->flags & FLB_INPUT_NET_SERVER) != 0) {
+ if (ins->tls_crt_file == NULL) {
+ flb_error("[input %s] error initializing TLS context "
+ "(certificate file missing)",
+ ins->name);
+
+ return -1;
+ }
+ else if (ins->tls_key_file == NULL) {
+ flb_error("[input %s] error initializing TLS context "
+ "(private key file missing)",
+ ins->name);
+
+ return -1;
+ }
+
+ tls_session_mode = FLB_TLS_SERVER_MODE;
+ }
+ else {
+ tls_session_mode = FLB_TLS_CLIENT_MODE;
+ }
+
+ ins->tls = flb_tls_create(tls_session_mode,
+ ins->tls_verify,
+ ins->tls_debug,
+ ins->tls_vhost,
+ ins->tls_ca_path,
+ ins->tls_ca_file,
+ ins->tls_crt_file,
+ ins->tls_key_file,
+ ins->tls_key_passwd);
+
+ if (ins->tls == NULL) {
+ flb_error("[input %s] error initializing TLS context",
+ ins->name);
+
+ return -1;
+ }
+ }
+
+ struct flb_config_map *m;
+
+ /* TLS config map (just for 'help' formatting purposes) */
+ ins->tls_config_map = flb_tls_get_config_map(config);
+
+ if (ins->tls_config_map == NULL) {
+ return -1;
+ }
+
+ /* Override first configmap value based on it plugin flag */
+ m = mk_list_entry_first(ins->tls_config_map, struct flb_config_map, _head);
+ if (p->flags & FLB_IO_TLS) {
+ m->value.val.boolean = FLB_TRUE;
+ }
+ else {
+ m->value.val.boolean = FLB_FALSE;
+ }
+#endif
+
+ /* Init network defaults */
+ flb_net_setup_init(&ins->net_setup);
+
+ if (flb_input_net_property_check(ins, config) == -1) {
+ return -1;
+ }
+
+ /* Initialize the input */
+ if (p->cb_init) {
+ flb_plg_info(ins, "initializing");
+ flb_plg_info(ins, "storage_strategy=%s", flb_storage_get_type(ins->storage_type));
+
+ /* Sanity check: all non-dynamic tag input plugins must have a tag */
+ if (!ins->tag) {
+ flb_input_set_property(ins, "tag", ins->name);
+ ins->tag_default = FLB_TRUE;
+ }
+
+ if (flb_input_is_threaded(ins)) {
+ /*
+ * Create a thread for a new instance. Now the plugin initialization callback will be invoked and report an early failure
+ * or an 'ok' status, we will wait for that return value on flb_input_thread_instance_get_status() below.
+ */
+ ret = flb_input_thread_instance_init(config, ins);
+ if (ret != 0) {
+ flb_error("failed initialize input %s",
+ ins->name);
+ return -1;
+ }
+
+ /* initialize channel events */
+ ret = input_instance_channel_events_init(ins);
+ if (ret != 0) {
+ flb_error("failed initialize channel events on input %s",
+ ins->name);
+ return -1;
+ }
+
+ /* register the ring buffer */
+ ret = flb_ring_buffer_add_event_loop(ins->rb, config->evl, FLB_INPUT_RING_BUFFER_WINDOW);
+ if (ret) {
+ flb_error("failed while registering ring buffer events on input %s",
+ ins->name);
+ return -1;
+ }
+ }
+ else {
+ /* initialize channel events */
+ ret = input_instance_channel_events_init(ins);
+ if (ret != 0) {
+ flb_error("failed initialize channel events on input %s",
+ ins->name);
+ }
+ ret = p->cb_init(ins, config, ins->data);
+ if (ret != 0) {
+ flb_error("failed initialize input %s",
+ ins->name);
+ return -1;
+ }
+ }
+ }
+
+ /* initialize processors */
+ ret = flb_processor_init(ins->processor);
+ if (ret == -1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int flb_input_instance_pre_run(struct flb_input_instance *ins, struct flb_config *config)
+{
+ int ret;
+
+ if (flb_input_is_threaded(ins)) {
+ return flb_input_thread_instance_pre_run(config, ins);
+ }
+ else if (ins->p->cb_pre_run) {
+ ret = ins->p->cb_pre_run(ins, config, ins->context);
+ if (ret == -1) {
+ return -1;
+ }
+ return 0;
+ }
+
+ return 0;
+}
+
+/* Initialize all inputs */
+int flb_input_init_all(struct flb_config *config)
+{
+ int ret;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_input_instance *ins;
+ struct flb_input_plugin *p;
+
+ /* Initialize thread-id table */
+ memset(&config->in_table_id, '\0', sizeof(config->in_table_id));
+
+ /* Iterate all active input instance plugins */
+ mk_list_foreach_safe(head, tmp, &config->inputs) {
+ ins = mk_list_entry(head, struct flb_input_instance, _head);
+ p = ins->p;
+
+ /* Skip pseudo input plugins */
+ if (!p) {
+ continue;
+ }
+
+ /* Initialize instance */
+ ret = flb_input_instance_init(ins, config);
+ if (ret == -1) {
+ flb_input_instance_destroy(ins);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* Invoke all pre-run input callbacks */
+void flb_input_pre_run_all(struct flb_config *config)
+{
+ struct mk_list *head;
+ struct flb_input_instance *ins;
+ struct flb_input_plugin *p;
+
+ mk_list_foreach(head, &config->inputs) {
+ ins = mk_list_entry(head, struct flb_input_instance, _head);
+ p = ins->p;
+ if (!p) {
+ continue;
+ }
+
+ flb_input_instance_pre_run(ins, config);
+ }
+}
+
+void flb_input_instance_exit(struct flb_input_instance *ins,
+ struct flb_config *config)
+{
+ struct flb_input_plugin *p;
+
+ /* if the instance runs in a separate thread, signal the thread */
+ if (flb_input_is_threaded(ins)) {
+ flb_input_thread_instance_exit(ins);
+ return;
+ }
+
+ p = ins->p;
+ if (p->cb_exit && ins->context) {
+ /* Multi-threaded input plugins use the same function signature for exit callbacks. */
+ p->cb_exit(ins->context, config);
+ }
+}
+
+/* Invoke all exit input callbacks */
+void flb_input_exit_all(struct flb_config *config)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_input_instance *ins;
+ struct flb_input_plugin *p;
+
+ /* Iterate instances */
+ mk_list_foreach_safe_r(head, tmp, &config->inputs) {
+ ins = mk_list_entry(head, struct flb_input_instance, _head);
+ p = ins->p;
+ if (!p) {
+ continue;
+ }
+
+ /* invoke plugin instance exit callback */
+ flb_input_instance_exit(ins, config);
+
+ /* destroy the instance */
+ flb_input_instance_destroy(ins);
+ }
+}
+
+/* Check that at least one Input is enabled */
+int flb_input_check(struct flb_config *config)
+{
+ if (mk_list_is_empty(&config->inputs) == 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * API for Input plugins
+ * =====================
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ * The Input interface provides a certain number of functions that can be
+ * used by Input plugins to configure it own behavior and request specific
+ *
+ * 1. flb_input_set_context()
+ *
+ * let an Input plugin set a context data reference that can be used
+ * later when invoking other callbacks.
+ *
+ * 2. flb_input_set_collector_time()
+ *
+ * request the Engine to trigger a specific collector callback at a
+ * certain interval time. Note that this callback will run in the main
+ * thread so it computing time must be short, otherwise it will block
+ * the main loop.
+ *
+ * The collector can runs in timeouts of the order of seconds.nanoseconds
+ *
+ * note: 1 Second = 1000000000 Nanosecond
+ *
+ * 3. flb_input_set_collector_event()
+ *
+ * for a registered file descriptor, associate the READ events to a
+ * specified plugin. Every time there is some data to read, the collector
+ * callback will be triggered. Oriented to a file descriptor that already
+ * have information that may be read through iotctl(..FIONREAD..);
+ *
+ * 4. flb_input_set_collector_server()
+ *
+ * it register a collector based on TCP socket events. It register a socket
+ * who did bind() and listen() and for each event on the socket it triggers
+ * the registered callbacks.
+ */
+
+/* Assign an Configuration context to an Input */
+void flb_input_set_context(struct flb_input_instance *in, void *context)
+{
+ in->context = context;
+}
+
+int flb_input_channel_init(struct flb_input_instance *in)
+{
+ return flb_pipe_create(in->channel);
+}
+
+static struct flb_input_collector *collector_create(int type,
+ struct flb_input_instance *ins,
+ int (*cb) (
+ struct flb_input_instance *,
+ struct flb_config *, void *),
+ struct flb_config *config)
+{
+ struct flb_input_collector *coll;
+ struct flb_input_thread_instance *thi;
+
+ coll = flb_calloc(1, sizeof(struct flb_input_collector));
+ if (!coll) {
+ flb_errno();
+ return NULL;
+ }
+
+ coll->id = collector_id(ins);
+ coll->type = type;
+ coll->running = FLB_FALSE;
+ coll->fd_event = -1;
+ coll->fd_timer = -1;
+ coll->seconds = -1;
+ coll->nanoseconds = -1;
+ coll->cb_collect = cb;
+ coll->instance = ins;
+ MK_EVENT_ZERO(&coll->event);
+
+ if (flb_input_is_threaded(ins)) {
+ thi = ins->thi;
+ coll->evl = thi->evl;
+ }
+ else {
+ coll->evl = config->evl;
+ }
+
+ /*
+ * Collectors created from a threaded input instance are only added to the
+ * instance `collectors` list. For instances in non-threaded mode, they are
+ * added to both lists, the global config collectors list and the instance
+ * list.
+ */
+ mk_list_add(&coll->_head, &ins->collectors);
+
+ return coll;
+}
+
+
+int flb_input_set_collector_time(struct flb_input_instance *ins,
+ int (*cb_collect) (struct flb_input_instance *,
+ struct flb_config *, void *),
+ time_t seconds,
+ long nanoseconds,
+ struct flb_config *config)
+{
+ struct flb_input_collector *coll;
+
+ coll = collector_create(FLB_COLLECT_TIME, ins, cb_collect, config);
+ if (!coll) {
+ return -1;
+ }
+
+ /* specific collector initialization */
+ coll->seconds = seconds;
+ coll->nanoseconds = nanoseconds;
+
+ return coll->id;
+}
+
+int flb_input_set_collector_event(struct flb_input_instance *ins,
+ int (*cb_collect) (struct flb_input_instance *,
+ struct flb_config *, void *),
+ flb_pipefd_t fd,
+ struct flb_config *config)
+{
+ struct flb_input_collector *coll;
+
+ coll = collector_create(FLB_COLLECT_FD_EVENT, ins, cb_collect, config);
+ if (!coll) {
+ return -1;
+ }
+
+ /* specific collector initialization */
+ coll->fd_event = fd;
+
+ return coll->id;
+}
+
+int flb_input_set_collector_socket(struct flb_input_instance *ins,
+ int (*cb_new_connection) (struct flb_input_instance *,
+ struct flb_config *,
+ void *),
+ flb_pipefd_t fd,
+ struct flb_config *config)
+{
+ struct flb_input_collector *coll;
+
+
+ coll = collector_create(FLB_COLLECT_FD_SERVER, ins, cb_new_connection, config);
+ if (!coll) {
+ return -1;
+ }
+
+ /* specific collector initialization */
+ coll->fd_event = fd;
+
+ return coll->id;
+}
+
+
+static int collector_start(struct flb_input_collector *coll,
+ struct flb_config *config)
+{
+ int fd;
+ int ret;
+ struct mk_event *event;
+
+ if (coll->running == FLB_TRUE) {
+ return 0;
+ }
+
+ event = &coll->event;
+ event->mask = MK_EVENT_EMPTY;
+ event->status = MK_EVENT_NONE;
+
+ if (coll->type == FLB_COLLECT_TIME) {
+ fd = mk_event_timeout_create(coll->evl, coll->seconds,
+ coll->nanoseconds, event);
+ if (fd == -1) {
+ flb_error("[input collector] COLLECT_TIME registration failed");
+ coll->running = FLB_FALSE;
+ return -1;
+ }
+ coll->fd_timer = fd;
+ }
+ else if (coll->type & (FLB_COLLECT_FD_EVENT | FLB_COLLECT_FD_SERVER)) {
+ event->fd = coll->fd_event;
+ ret = mk_event_add(coll->evl,
+ coll->fd_event,
+ FLB_ENGINE_EV_CORE,
+ MK_EVENT_READ, event);
+ if (ret == -1) {
+ flb_error("[input collector] COLLECT_EVENT registration failed");
+ mk_event_closesocket(coll->fd_event);
+ coll->running = FLB_FALSE;
+ return -1;
+ }
+ }
+
+ coll->running = FLB_TRUE;
+ return 0;
+}
+
+int flb_input_collector_start(int coll_id, struct flb_input_instance *in)
+{
+ int ret;
+ struct mk_list *head;
+ struct flb_input_collector *coll;
+
+ mk_list_foreach(head, &in->collectors) {
+ coll = mk_list_entry(head, struct flb_input_collector, _head);
+ if (coll->id == coll_id) {
+ ret = collector_start(coll, in->config);
+ if (ret == -1) {
+ flb_error("[input] error starting collector #%i: %s",
+ coll_id, in->name);
+ }
+ return ret;
+ }
+ }
+
+ return -1;
+}
+
+/* start collectors for main thread, no threaded plugins */
+int flb_input_collectors_signal_start(struct flb_input_instance *ins)
+{
+ int ret;
+ struct mk_list *head;
+ struct flb_input_collector *coll;
+
+ if (flb_input_is_threaded(ins)) {
+ flb_error("input plugin '%s' is threaded", flb_input_name(ins));
+ return -1;
+ }
+
+ mk_list_foreach(head, &ins->collectors) {
+ coll = mk_list_entry(head, struct flb_input_collector, _head);
+ ret = flb_input_collector_start(coll->id, ins);
+ if (ret < 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Start all collectors: this function is invoked from the engine interface and aim
+ * to start the local collectors and also signal the threaded input plugins to start
+ * their own collectors.
+ */
+int flb_input_collectors_start(struct flb_config *config)
+{
+ int ret;
+ struct mk_list *head;
+ struct flb_input_instance *ins;
+
+ /* Signal threaded input plugins to start their collectors */
+ mk_list_foreach(head, &config->inputs) {
+ ins = mk_list_entry(head, struct flb_input_instance, _head);
+ if (flb_input_is_threaded(ins)) {
+ ret = flb_input_thread_collectors_signal_start(ins);
+ if (ret != 0) {
+ flb_error("could not start collectors for threaded plugin '%s'",
+ flb_input_name(ins));
+ }
+ }
+ else {
+ ret = flb_input_collectors_signal_start(ins);
+ if (ret != 0) {
+ flb_error("could not start collectors for plugin '%s'",
+ flb_input_name(ins));
+ }
+ }
+ }
+
+ return 0;
+}
+
+static struct flb_input_collector *get_collector(int id,
+ struct flb_input_instance *in)
+{
+ struct mk_list *head;
+ struct flb_input_collector *coll;
+
+ mk_list_foreach(head, &in->collectors) {
+ coll = mk_list_entry(head, struct flb_input_collector, _head);
+ if (coll->id == id) {
+ return coll;
+ }
+ }
+
+ return NULL;
+}
+
+int flb_input_collector_running(int coll_id, struct flb_input_instance *in)
+{
+ struct flb_input_collector *coll;
+
+ coll = get_collector(coll_id, in);
+ if (!coll) {
+ return FLB_FALSE;
+ }
+
+ return coll->running;
+}
+
+struct mk_event *flb_input_collector_get_event(int coll_id,
+ struct flb_input_instance *ins)
+{
+ struct flb_input_collector *collector;
+
+ collector = get_collector(coll_id, ins);
+
+ if (collector == NULL) {
+ return NULL;
+ }
+
+ return &collector->event;
+}
+
+/*
+ * TEST: this is a test function that can be used by input plugins to check the
+ * 'pause' and 'resume' callback operations.
+ *
+ * After is invoked, it will schedule an internal event to wake up the instance
+ * after 'sleep_seconds'.
+ */
+int flb_input_test_pause_resume(struct flb_input_instance *ins, int sleep_seconds)
+{
+ /*
+ * This is a fake pause/resume implementation since it's only used to test the plugin
+ * callbacks for such purposes.
+ */
+
+ /* pause the instance */
+ flb_input_pause(ins);
+
+ /* wait */
+ sleep(sleep_seconds);
+
+ /* resume again */
+ flb_input_resume(ins);
+
+ return 0;
+}
+
+int flb_input_pause(struct flb_input_instance *ins)
+{
+ /* if the instance is already paused, just return */
+ if (flb_input_buf_paused(ins)) {
+ return -1;
+ }
+
+ /* Pause only if a callback is set and a local context exists */
+ if (ins->p->cb_pause && ins->context) {
+ if (flb_input_is_threaded(ins)) {
+ /* signal the thread event loop about the 'pause' operation */
+ flb_input_thread_instance_pause(ins);
+ }
+ else {
+ flb_info("[input] pausing %s", flb_input_name(ins));
+ ins->p->cb_pause(ins->context, ins->config);
+ }
+ }
+
+ return 0;
+}
+
+int flb_input_resume(struct flb_input_instance *ins)
+{
+ if (ins->p->cb_resume) {
+ if (flb_input_is_threaded(ins)) {
+ /* signal the thread event loop about the 'resume' operation */
+ flb_input_thread_instance_resume(ins);
+ }
+ else {
+ ins->p->cb_resume(ins->context, ins->config);
+ }
+ }
+
+ return 0;
+}
+
+int flb_input_pause_all(struct flb_config *config)
+{
+ int ret;
+ int paused = 0;
+ struct mk_list *head;
+ struct flb_input_instance *ins;
+
+ mk_list_foreach(head, &config->inputs) {
+ ins = mk_list_entry(head, struct flb_input_instance, _head);
+ /*
+ * Inform the plugin that is being paused, the source type is set to 'FLB_INPUT_PAUSE_MEM_BUF', no real reason, we
+ * just need to get it paused.
+ */
+ ret = flb_input_pause(ins);
+ if (ret == 0) {
+ paused++;
+ }
+ }
+
+ return paused;
+}
+
+int flb_input_collector_destroy(struct flb_input_collector *coll)
+{
+ struct flb_config *config = coll->instance->config;
+
+ if (coll->type == FLB_COLLECT_TIME) {
+ if (coll->fd_timer > 0) {
+ mk_event_timeout_destroy(config->evl, &coll->event);
+ mk_event_closesocket(coll->fd_timer);
+ }
+ }
+ else {
+ mk_event_del(config->evl, &coll->event);
+ }
+
+ flb_free(coll);
+
+ return 0;
+}
+
+int flb_input_collector_pause(int coll_id, struct flb_input_instance *in)
+{
+ int ret;
+ flb_pipefd_t fd;
+ struct flb_input_collector *coll;
+
+ coll = get_collector(coll_id, in);
+ if (!coll) {
+ return -1;
+ }
+
+ if (coll->running == FLB_FALSE) {
+ return 0;
+ }
+
+ if (coll->type == FLB_COLLECT_TIME) {
+ /*
+ * For a collector time, it's better to just remove the file
+ * descriptor associated to the time out, when resumed a new
+ * one can be created.
+ *
+ * Note: Invalidate fd_timer first in case closing a socket
+ * invokes another event.
+ */
+ fd = coll->fd_timer;
+ coll->fd_timer = -1;
+ mk_event_timeout_destroy(coll->evl, &coll->event);
+ mk_event_closesocket(fd);
+ }
+ else if (coll->type & (FLB_COLLECT_FD_SERVER | FLB_COLLECT_FD_EVENT)) {
+ ret = mk_event_del(coll->evl, &coll->event);
+ if (ret != 0) {
+ flb_warn("[input] cannot disable event for %s", in->name);
+ return -1;
+ }
+ }
+
+ coll->running = FLB_FALSE;
+
+ return 0;
+}
+
+int flb_input_collector_delete(int coll_id, struct flb_input_instance *in)
+{
+ struct flb_input_collector *coll;
+
+ coll = get_collector(coll_id, in);
+ if (!coll) {
+ return -1;
+ }
+ if (flb_input_collector_pause(coll_id, in) < 0) {
+ return -1;
+ }
+
+
+ pthread_mutex_lock(&in->config->collectors_mutex);
+ mk_list_del(&coll->_head);
+ pthread_mutex_unlock(&in->config->collectors_mutex);
+
+ flb_free(coll);
+ return 0;
+}
+
+int flb_input_collector_resume(int coll_id, struct flb_input_instance *in)
+{
+ int fd;
+ int ret;
+ struct flb_input_collector *coll;
+ struct flb_config *config;
+ struct mk_event *event;
+
+ coll = get_collector(coll_id, in);
+ if (!coll) {
+ return -1;
+ }
+
+ if (coll->running == FLB_TRUE) {
+ flb_error("[input] cannot resume collector %s:%i, already running",
+ in->name, coll_id);
+ return -1;
+ }
+
+ config = in->config;
+ event = &coll->event;
+
+ /* If data ingestion has been paused, the collector cannot resume */
+ if (config->is_ingestion_active == FLB_FALSE) {
+ return 0;
+ }
+
+ if (coll->type == FLB_COLLECT_TIME) {
+ event->mask = MK_EVENT_EMPTY;
+ event->status = MK_EVENT_NONE;
+ fd = mk_event_timeout_create(coll->evl, coll->seconds,
+ coll->nanoseconds, event);
+ if (fd == -1) {
+ flb_error("[input collector] resume COLLECT_TIME failed");
+ return -1;
+ }
+ coll->fd_timer = fd;
+ }
+ else if (coll->type & (FLB_COLLECT_FD_SERVER | FLB_COLLECT_FD_EVENT)) {
+ event->fd = coll->fd_event;
+ event->mask = MK_EVENT_EMPTY;
+ event->status = MK_EVENT_NONE;
+
+ ret = mk_event_add(coll->evl,
+ coll->fd_event,
+ FLB_ENGINE_EV_CORE,
+ MK_EVENT_READ, event);
+ if (ret == -1) {
+ flb_error("[input] cannot disable/pause event for %s", in->name);
+ return -1;
+ }
+ }
+
+ coll->running = FLB_TRUE;
+
+ return 0;
+}
+
+int flb_input_collector_fd(flb_pipefd_t fd, struct flb_config *config)
+{
+ struct mk_list *head;
+ struct mk_list *head_coll;
+ struct flb_input_instance *ins;
+ struct flb_input_collector *collector = NULL;
+ struct flb_input_coro *input_coro;
+
+ mk_list_foreach(head, &config->inputs) {
+ ins = mk_list_entry(head, struct flb_input_instance, _head);
+ mk_list_foreach(head_coll, &ins->collectors) {
+ collector = mk_list_entry(head_coll, struct flb_input_collector, _head);
+ if (collector->fd_event == fd) {
+ break;
+ }
+ else if (collector->fd_timer == fd) {
+ flb_utils_timer_consume(fd);
+ break;
+ }
+ collector = NULL;
+ }
+
+ if (collector) {
+ break;
+ }
+ }
+
+ /* No matches */
+ if (!collector) {
+ return -1;
+ }
+
+ if (collector->running == FLB_FALSE) {
+ return -1;
+ }
+
+ /* Trigger the collector callback */
+ if (collector->instance->runs_in_coroutine) {
+ input_coro = flb_input_coro_collect(collector, config);
+ if (!input_coro) {
+ return -1;
+ }
+ flb_input_coro_resume(input_coro);
+ }
+ else {
+ if (collector->cb_collect(collector->instance, config,
+ collector->instance->context) == -1) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int flb_input_upstream_set(struct flb_upstream *u, struct flb_input_instance *ins)
+{
+ if (!u) {
+ return -1;
+ }
+
+ /*
+ * if the input instance runs in threaded mode, make sure to flag the
+ * upstream context so the lists operations are done in thread safe mode
+ */
+ if (flb_input_is_threaded(ins)) {
+ flb_upstream_thread_safe(u);
+ mk_list_add(&u->base._head, &ins->upstreams);
+ }
+
+ /* Set networking options 'net.*' received through instance properties */
+ memcpy(&u->base.net, &ins->net_setup, sizeof(struct flb_net_setup));
+
+ return 0;
+}
+
+int flb_input_downstream_set(struct flb_downstream *stream,
+ struct flb_input_instance *ins)
+{
+ if (stream == NULL) {
+ return -1;
+ }
+
+ /*
+ * If the input plugin will run in multiple threads, enable
+ * the thread safe mode for the Downstream context.
+ */
+ if (flb_input_is_threaded(ins)) {
+ flb_stream_enable_thread_safety(&stream->base);
+
+ mk_list_add(&stream->base._head, &ins->downstreams);
+ }
+
+ return 0;
+}
diff --git a/fluent-bit/src/flb_input_chunk.c b/fluent-bit/src/flb_input_chunk.c
new file mode 100644
index 000000000..c71ae3ef0
--- /dev/null
+++ b/fluent-bit/src/flb_input_chunk.c
@@ -0,0 +1,2009 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+#define FS_CHUNK_SIZE_DEBUG(op) {flb_trace("[%d] %s -> fs_chunks_size = %zu", \
+ __LINE__, op->name, op->fs_chunks_size);}
+#define FS_CHUNK_SIZE_DEBUG_MOD(op, chunk, mod) {flb_trace( \
+ "[%d] %s -> fs_chunks_size = %zu mod=%zd chunk=%s", __LINE__, \
+ op->name, op->fs_chunks_size, mod, flb_input_chunk_get_name(chunk));}
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_input_chunk.h>
+#include <fluent-bit/flb_input_plugin.h>
+#include <fluent-bit/flb_storage.h>
+#include <fluent-bit/flb_time.h>
+#include <fluent-bit/flb_router.h>
+#include <fluent-bit/flb_task.h>
+#include <fluent-bit/flb_routes_mask.h>
+#include <fluent-bit/flb_metrics.h>
+#include <fluent-bit/stream_processor/flb_sp.h>
+#include <fluent-bit/flb_ring_buffer.h>
+#include <chunkio/chunkio.h>
+#include <monkey/mk_core.h>
+
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+#include <fluent-bit/flb_chunk_trace.h>
+#endif /* FLB_HAVE_CHUNK_TRACE */
+
+
+#define BLOCK_UNTIL_KEYPRESS() {char temp_keypress_buffer; read(0, &temp_keypress_buffer, 1);}
+
+#define FLB_INPUT_CHUNK_RELEASE_SCOPE_LOCAL 0
+#define FLB_INPUT_CHUNK_RELEASE_SCOPE_GLOBAL 1
+
+struct input_chunk_raw {
+ struct flb_input_instance *ins;
+ int event_type;
+ size_t records;
+ flb_sds_t tag;
+ void *buf_data;
+ size_t buf_size;
+};
+
+#ifdef FLB_HAVE_IN_STORAGE_BACKLOG
+
+extern ssize_t sb_get_releasable_output_queue_space(struct flb_output_instance *output_plugin,
+ size_t required_space);
+
+extern int sb_release_output_queue_space(struct flb_output_instance *output_plugin,
+ ssize_t *required_space);
+
+
+#else
+
+ssize_t sb_get_releasable_output_queue_space(struct flb_output_instance *output_plugin,
+ size_t required_space)
+{
+ return 0;
+}
+
+int sb_release_output_queue_space(struct flb_output_instance *output_plugin,
+ ssize_t *required_space)
+{
+ return 0;
+}
+
+#endif
+
+static int flb_input_chunk_safe_delete(struct flb_input_chunk *ic,
+ struct flb_input_chunk *old_ic,
+ uint64_t o_id);
+
+static int flb_input_chunk_is_task_safe_delete(struct flb_task *task);
+
+static int flb_input_chunk_drop_task_route(
+ struct flb_task *task,
+ struct flb_output_instance *o_ins);
+
+static ssize_t flb_input_chunk_get_real_size(struct flb_input_chunk *ic);
+
+static int flb_input_chunk_release_space(
+ struct flb_input_chunk *new_input_chunk,
+ struct flb_input_instance *input_plugin,
+ struct flb_output_instance *output_plugin,
+ ssize_t *required_space,
+ int release_scope)
+{
+ struct mk_list *input_chunk_iterator_tmp;
+ struct mk_list *input_chunk_iterator;
+ int chunk_destroy_flag;
+ struct flb_input_chunk *old_input_chunk;
+ ssize_t released_space;
+ int chunk_released;
+ ssize_t chunk_size;
+
+ released_space = 0;
+
+ mk_list_foreach_safe(input_chunk_iterator, input_chunk_iterator_tmp,
+ &input_plugin->chunks) {
+ old_input_chunk = mk_list_entry(input_chunk_iterator,
+ struct flb_input_chunk, _head);
+
+ if (!flb_routes_mask_get_bit(old_input_chunk->routes_mask,
+ output_plugin->id)) {
+ continue;
+ }
+
+ if (flb_input_chunk_safe_delete(new_input_chunk,
+ old_input_chunk,
+ output_plugin->id) == FLB_FALSE) {
+ continue;
+ }
+
+ if (flb_input_chunk_drop_task_route(old_input_chunk->task,
+ output_plugin) == FLB_FALSE) {
+ continue;
+ }
+
+ chunk_size = flb_input_chunk_get_real_size(old_input_chunk);
+ chunk_released = FLB_FALSE;
+ chunk_destroy_flag = FLB_FALSE;
+
+ if (release_scope == FLB_INPUT_CHUNK_RELEASE_SCOPE_LOCAL) {
+ flb_routes_mask_clear_bit(old_input_chunk->routes_mask,
+ output_plugin->id);
+
+ FS_CHUNK_SIZE_DEBUG_MOD(output_plugin, old_input_chunk, chunk_size);
+ output_plugin->fs_chunks_size -= chunk_size;
+
+ chunk_destroy_flag = flb_routes_mask_is_empty(
+ old_input_chunk->routes_mask);
+
+ chunk_released = FLB_TRUE;
+ }
+ else if (release_scope == FLB_INPUT_CHUNK_RELEASE_SCOPE_GLOBAL) {
+ chunk_destroy_flag = FLB_TRUE;
+ }
+
+ if (chunk_destroy_flag) {
+ if (old_input_chunk->task != NULL) {
+ /*
+ * If the chunk is referenced by a task and task has no active route,
+ * we need to destroy the task as well.
+ */
+ if (old_input_chunk->task->users == 0) {
+ flb_debug("[task] drop task_id %d with no active route from input plugin %s",
+ old_input_chunk->task->id, new_input_chunk->in->name);
+ flb_task_destroy(old_input_chunk->task, FLB_TRUE);
+
+ chunk_released = FLB_TRUE;
+ }
+ }
+ else {
+ flb_debug("[input chunk] drop chunk %s with no output route from input plugin %s",
+ flb_input_chunk_get_name(old_input_chunk), new_input_chunk->in->name);
+
+ flb_input_chunk_destroy(old_input_chunk, FLB_TRUE);
+
+ chunk_released = FLB_TRUE;
+ }
+ }
+
+ if (chunk_released) {
+ released_space += chunk_size;
+ }
+
+ if (released_space >= *required_space) {
+ break;
+ }
+ }
+
+ *required_space -= released_space;
+
+ return 0;
+}
+
+static void generate_chunk_name(struct flb_input_instance *in,
+ char *out_buf, int buf_size)
+{
+ struct flb_time tm;
+ (void) in;
+
+ flb_time_get(&tm);
+ snprintf(out_buf, buf_size - 1,
+ "%i-%lu.%4lu.flb",
+ getpid(),
+ tm.tm.tv_sec, tm.tm.tv_nsec);
+}
+
+ssize_t flb_input_chunk_get_size(struct flb_input_chunk *ic)
+{
+ return cio_chunk_get_content_size(ic->chunk);
+}
+
+/*
+ * When chunk is set to DOWN from memory, data_size is set to 0 and
+ * cio_chunk_get_content_size(1) returns the data_size. fs_chunks_size
+ * is used to track the size of chunks in filesystem so we need to call
+ * cio_chunk_get_real_size to return the original size in the file system
+ */
+static ssize_t flb_input_chunk_get_real_size(struct flb_input_chunk *ic)
+{
+ ssize_t meta_size;
+ ssize_t size;
+
+ size = cio_chunk_get_real_size(ic->chunk);
+
+ if (size != 0) {
+ return size;
+ }
+
+ // Real size is not synced to chunk yet
+ size = flb_input_chunk_get_size(ic);
+ if (size == 0) {
+ flb_debug("[input chunk] no data in the chunk %s",
+ flb_input_chunk_get_name(ic));
+ return -1;
+ }
+
+ meta_size = cio_meta_size(ic->chunk);
+ size += meta_size
+ /* See https://github.com/edsiper/chunkio#file-layout for more details */
+ + 2 /* HEADER BYTES */
+ + 4 /* CRC32 */
+ + 16 /* PADDING */
+ + 2; /* METADATA LENGTH BYTES */
+
+ return size;
+}
+
+int flb_input_chunk_write(void *data, const char *buf, size_t len)
+{
+ int ret;
+ struct flb_input_chunk *ic;
+
+ ic = (struct flb_input_chunk *) data;
+
+ ret = cio_chunk_write(ic->chunk, buf, len);
+ return ret;
+}
+
+int flb_input_chunk_write_at(void *data, off_t offset,
+ const char *buf, size_t len)
+{
+ int ret;
+ struct flb_input_chunk *ic;
+
+ ic = (struct flb_input_chunk *) data;
+
+ ret = cio_chunk_write_at(ic->chunk, offset, buf, len);
+ return ret;
+}
+
+static int flb_input_chunk_drop_task_route(
+ struct flb_task *task,
+ struct flb_output_instance *output_plugin)
+{
+ int route_status;
+ int result;
+
+ if (task == NULL) {
+ return FLB_TRUE;
+ }
+
+ result = FLB_TRUE;
+
+ if (task->users != 0) {
+ result = FLB_FALSE;
+
+ if (output_plugin != NULL) {
+ flb_task_acquire_lock(task);
+
+ route_status = flb_task_get_route_status(task, output_plugin);
+
+ if (route_status == FLB_TASK_ROUTE_INACTIVE) {
+ flb_task_set_route_status(task,
+ output_plugin,
+ FLB_TASK_ROUTE_DROPPED);
+
+ result = FLB_TRUE;
+ }
+
+ flb_task_release_lock(task);
+ }
+ }
+
+ return result;
+}
+
+
+/*
+ * For input_chunk referenced by an outgoing task, we need to check
+ * whether the chunk is in the middle of output flush callback
+ */
+static int flb_input_chunk_is_task_safe_delete(struct flb_task *task)
+{
+ if (!task) {
+ return FLB_TRUE;
+ }
+
+ if (task->users != 0) {
+ return FLB_FALSE;
+ }
+
+ return FLB_TRUE;
+}
+
+static int flb_input_chunk_safe_delete(struct flb_input_chunk *ic,
+ struct flb_input_chunk *old_ic,
+ uint64_t o_id)
+{
+ /* The chunk we want to drop should not be the incoming chunk */
+ if (ic == old_ic) {
+ return FLB_FALSE;
+ }
+
+ /*
+ * Even if chunks from same input plugin have same routes_mask when created,
+ * the routes_mask could be modified when new chunks is ingested. Therefore,
+ * we still need to do the validation on the routes_mask with o_id.
+ */
+ if (flb_routes_mask_get_bit(old_ic->routes_mask, o_id) == 0) {
+ return FLB_FALSE;
+ }
+
+ return FLB_TRUE;
+}
+
+int flb_input_chunk_release_space_compound(
+ struct flb_input_chunk *new_input_chunk,
+ struct flb_output_instance *output_plugin,
+ size_t *local_release_requirement,
+ int release_local_space)
+{
+ ssize_t required_space_remainder;
+ struct flb_input_instance *storage_backlog_instance;
+ struct flb_input_instance *input_plugin_instance;
+ struct mk_list *iterator;
+ int result;
+
+ storage_backlog_instance = output_plugin->config->storage_input_plugin;
+
+ *local_release_requirement = flb_input_chunk_get_real_size(new_input_chunk);
+ required_space_remainder = (ssize_t) *local_release_requirement;
+
+ if (required_space_remainder > 0) {
+ result = flb_input_chunk_release_space(new_input_chunk,
+ storage_backlog_instance,
+ output_plugin,
+ &required_space_remainder,
+ FLB_INPUT_CHUNK_RELEASE_SCOPE_GLOBAL);
+ }
+
+ if (required_space_remainder > 0) {
+ result = sb_release_output_queue_space(output_plugin,
+ &required_space_remainder);
+ }
+
+ if (release_local_space) {
+ if (required_space_remainder > 0) {
+ result = flb_input_chunk_release_space(new_input_chunk,
+ new_input_chunk->in,
+ output_plugin,
+ &required_space_remainder,
+ FLB_INPUT_CHUNK_RELEASE_SCOPE_LOCAL);
+ }
+ }
+
+ if (required_space_remainder > 0) {
+ mk_list_foreach(iterator, &output_plugin->config->inputs) {
+ input_plugin_instance = \
+ mk_list_entry(iterator, struct flb_input_instance, _head);
+
+ if (input_plugin_instance != new_input_chunk->in) {
+ result = flb_input_chunk_release_space(
+ new_input_chunk,
+ input_plugin_instance,
+ output_plugin,
+ &required_space_remainder,
+ FLB_INPUT_CHUNK_RELEASE_SCOPE_LOCAL);
+ }
+
+ if (required_space_remainder <= 0) {
+ break;
+ }
+ }
+ }
+
+ if (required_space_remainder < 0) {
+ required_space_remainder = 0;
+ }
+
+ *local_release_requirement = (size_t) required_space_remainder;
+
+ (void) result;
+
+ return 0;
+}
+
+/*
+ * Find a slot in the output instance to append the new data with size chunk_size, it
+ * will drop the the oldest chunks when the limitation on local disk is reached.
+ */
+int flb_input_chunk_find_space_new_data(struct flb_input_chunk *ic,
+ size_t chunk_size, int overlimit)
+{
+ int count;
+ int result;
+ struct mk_list *head;
+ struct flb_output_instance *o_ins;
+ size_t local_release_requirement;
+
+ /*
+ * For each output instances that will be over the limit after adding the new chunk,
+ * we have to determine how many chunks needs to be removed. We will adjust the
+ * routes_mask to only route to the output plugin that have enough space after
+ * deleting some chunks fome the queue.
+ */
+ count = 0;
+
+ mk_list_foreach(head, &ic->in->config->outputs) {
+ o_ins = mk_list_entry(head, struct flb_output_instance, _head);
+
+ if ((o_ins->total_limit_size == -1) || ((1 << o_ins->id) & overlimit) == 0 ||
+ (flb_routes_mask_get_bit(ic->routes_mask, o_ins->id) == 0)) {
+ continue;
+ }
+
+ local_release_requirement = 0;
+
+ result = flb_input_chunk_release_space_compound(
+ ic, o_ins,
+ &local_release_requirement,
+ FLB_TRUE);
+
+ if (result != 0 ||
+ local_release_requirement != 0) {
+ count++;
+ }
+ }
+
+ if (count != 0) {
+ flb_error("[input chunk] fail to drop enough chunks in order to place new data");
+ exit(0);
+ }
+
+ return 0;
+}
+
+/*
+ * Returns a non-zero result if any output instances will reach the limit
+ * after buffering the new data
+ */
+int flb_input_chunk_has_overlimit_routes(struct flb_input_chunk *ic,
+ size_t chunk_size)
+{
+ int overlimit = 0;
+ struct mk_list *head;
+ struct flb_output_instance *o_ins;
+
+ mk_list_foreach(head, &ic->in->config->outputs) {
+ o_ins = mk_list_entry(head, struct flb_output_instance, _head);
+
+ if ((o_ins->total_limit_size == -1) ||
+ (flb_routes_mask_get_bit(ic->routes_mask, o_ins->id) == 0)) {
+ continue;
+ }
+
+ FS_CHUNK_SIZE_DEBUG(o_ins);
+ flb_debug("[input chunk] chunk %s required %ld bytes and %ld bytes left "
+ "in plugin %s", flb_input_chunk_get_name(ic), chunk_size,
+ o_ins->total_limit_size -
+ o_ins->fs_backlog_chunks_size -
+ o_ins->fs_chunks_size,
+ o_ins->name);
+
+ if ((o_ins->fs_chunks_size +
+ o_ins->fs_backlog_chunks_size +
+ chunk_size) > o_ins->total_limit_size) {
+ overlimit |= (1 << o_ins->id);
+ }
+ }
+
+ return overlimit;
+}
+
+/* Find a slot for the incoming data to buffer it in local file system
+ * returns 0 if none of the routes can be written to
+ */
+int flb_input_chunk_place_new_chunk(struct flb_input_chunk *ic, size_t chunk_size)
+{
+ int overlimit;
+ overlimit = flb_input_chunk_has_overlimit_routes(ic, chunk_size);
+ if (overlimit != 0) {
+ flb_input_chunk_find_space_new_data(ic, chunk_size, overlimit);
+ }
+
+ return !flb_routes_mask_is_empty(ic->routes_mask);
+}
+
+/* Create an input chunk using a Chunk I/O */
+struct flb_input_chunk *flb_input_chunk_map(struct flb_input_instance *in,
+ int event_type,
+ void *chunk)
+{
+ int records = 0;
+ int tag_len;
+ int has_routes;
+ int ret;
+ uint64_t ts;
+ char *buf_data;
+ size_t buf_size;
+ size_t offset;
+ ssize_t bytes;
+ const char *tag_buf;
+ struct flb_input_chunk *ic;
+
+ /* Create context for the input instance */
+ ic = flb_calloc(1, sizeof(struct flb_input_chunk));
+ if (!ic) {
+ flb_errno();
+ return NULL;
+ }
+ ic->event_type = event_type;
+ ic->busy = FLB_FALSE;
+ ic->fs_counted = FLB_FALSE;
+ ic->fs_backlog = FLB_TRUE;
+ ic->chunk = chunk;
+ ic->in = in;
+ msgpack_packer_init(&ic->mp_pck, ic, flb_input_chunk_write);
+
+ ret = cio_chunk_get_content(ic->chunk, &buf_data, &buf_size);
+ if (ret != CIO_OK) {
+ flb_error("[input chunk] error retrieving content for metrics");
+ flb_free(ic);
+ return NULL;
+ }
+
+ if (ic->event_type == FLB_INPUT_LOGS) {
+ /* Validate records in the chunk */
+ ret = flb_mp_validate_log_chunk(buf_data, buf_size, &records, &offset);
+ if (ret == -1) {
+ /* If there are valid records, truncate the chunk size */
+ if (records <= 0) {
+ flb_plg_error(in,
+ "chunk validation failed, data might be corrupted. "
+ "No valid records found, the chunk will be discarded.");
+ flb_free(ic);
+ return NULL;
+ }
+ if (records > 0 && offset > 32) {
+ flb_plg_warn(in,
+ "chunk validation failed, data might be corrupted. "
+ "Found %d valid records, failed content starts "
+ "right after byte %lu. Recovering valid records.",
+ records, offset);
+
+ /* truncate the chunk to recover valid records */
+ cio_chunk_write_at(chunk, offset, NULL, 0);
+ }
+ else {
+ flb_plg_error(in,
+ "chunk validation failed, data might be corrupted. "
+ "Found %d valid records, failed content starts "
+ "right after byte %lu. Cannot recover chunk,",
+ records, offset);
+ flb_free(ic);
+ return NULL;
+ }
+ }
+ }
+ else if (ic->event_type == FLB_INPUT_METRICS) {
+ ret = flb_mp_validate_metric_chunk(buf_data, buf_size, &records, &offset);
+ if (ret == -1) {
+ if (records <= 0) {
+ flb_plg_error(in,
+ "metrics chunk validation failed, data might be corrupted. "
+ "No valid records found, the chunk will be discarded.");
+ flb_free(ic);
+ return NULL;
+ }
+ if (records > 0 && offset > 32) {
+ flb_plg_warn(in,
+ "metrics chunk validation failed, data might be corrupted. "
+ "Found %d valid records, failed content starts "
+ "right after byte %lu. Recovering valid records.",
+ records, offset);
+
+ /* truncate the chunk to recover valid records */
+ cio_chunk_write_at(chunk, offset, NULL, 0);
+ }
+ else {
+ flb_plg_error(in,
+ "metrics chunk validation failed, data might be corrupted. "
+ "Found %d valid records, failed content starts "
+ "right after byte %lu. Cannot recover chunk,",
+ records, offset);
+ flb_free(ic);
+ return NULL;
+ }
+
+ }
+ }
+ else if (ic->event_type == FLB_INPUT_TRACES) {
+
+ }
+
+ /* Skip chunks without content data */
+ if (records == 0) {
+ flb_plg_error(in,
+ "chunk validation failed, data might be corrupted. "
+ "No valid records found, the chunk will be discarded.");
+ flb_free(ic);
+ return NULL;
+ }
+
+ /*
+ * If the content is valid and the chunk has extra padding zeros, just
+ * perform an adjustment.
+ */
+ bytes = cio_chunk_get_content_size(chunk);
+ if (bytes == -1) {
+ flb_free(ic);
+ return NULL;
+ }
+ if (offset < bytes) {
+ cio_chunk_write_at(chunk, offset, NULL, 0);
+ }
+
+ /* Update metrics */
+#ifdef FLB_HAVE_METRICS
+ ic->total_records = records;
+ if (ic->total_records > 0) {
+ /* timestamp */
+ ts = cfl_time_now();
+
+ /* fluentbit_input_records_total */
+ cmt_counter_add(in->cmt_records, ts, ic->total_records,
+ 1, (char *[]) {(char *) flb_input_name(in)});
+
+ /* fluentbit_input_bytes_total */
+ cmt_counter_add(in->cmt_bytes, ts, buf_size,
+ 1, (char *[]) {(char *) flb_input_name(in)});
+
+ /* OLD metrics */
+ flb_metrics_sum(FLB_METRIC_N_RECORDS, ic->total_records, in->metrics);
+ flb_metrics_sum(FLB_METRIC_N_BYTES, buf_size, in->metrics);
+ }
+#endif
+
+ /* Get the the tag reference (chunk metadata) */
+ ret = flb_input_chunk_get_tag(ic, &tag_buf, &tag_len);
+ if (ret == -1) {
+ flb_error("[input chunk] error retrieving tag of input chunk");
+ flb_free(ic);
+ return NULL;
+ }
+
+ bytes = flb_input_chunk_get_real_size(ic);
+ if (bytes < 0) {
+ flb_warn("[input chunk] could not retrieve chunk real size");
+ flb_free(ic);
+ return NULL;
+ }
+
+ has_routes = flb_routes_mask_set_by_tag(ic->routes_mask, tag_buf, tag_len, in);
+ if (has_routes == 0) {
+ flb_warn("[input chunk] no matching route for backoff log chunk %s",
+ flb_input_chunk_get_name(ic));
+ }
+
+ mk_list_add(&ic->_head, &in->chunks);
+
+ flb_input_chunk_update_output_instances(ic, bytes);
+
+ return ic;
+}
+
+static int input_chunk_write_header(struct cio_chunk *chunk, int event_type,
+ char *tag, int tag_len)
+
+{
+ int ret;
+ int meta_size;
+ char *meta;
+
+ /*
+ * Prepare the Chunk metadata header
+ * ----------------------------------
+ * m[0] = FLB_INPUT_CHUNK_MAGIC_BYTE_0
+ * m[1] = FLB_INPUT_CHUNK_MAGIC_BYTE_1
+ * m[2] = type (FLB_INPUT_CHUNK_TYPE_LOG or FLB_INPUT_CHUNK_TYPE_METRIC or FLB_INPUT_CHUNK_TYPE_TRACE
+ * m[3] = 0 (unused for now)
+ */
+
+ /* write metadata (tag) */
+ if (tag_len > (65535 - FLB_INPUT_CHUNK_META_HEADER)) {
+ /* truncate length */
+ tag_len = 65535 - FLB_INPUT_CHUNK_META_HEADER;
+ }
+ meta_size = FLB_INPUT_CHUNK_META_HEADER + tag_len;
+
+ /* Allocate buffer for metadata header */
+ meta = flb_calloc(1, meta_size);
+ if (!meta) {
+ flb_errno();
+ return -1;
+ }
+
+ /*
+ * Write chunk header in a temporary buffer
+ * ----------------------------------------
+ */
+
+ /* magic bytes */
+ meta[0] = FLB_INPUT_CHUNK_MAGIC_BYTE_0;
+ meta[1] = FLB_INPUT_CHUNK_MAGIC_BYTE_1;
+
+ /* event type */
+ if (event_type == FLB_INPUT_LOGS) {
+ meta[2] = FLB_INPUT_CHUNK_TYPE_LOGS;
+ }
+ else if (event_type == FLB_INPUT_METRICS) {
+ meta[2] = FLB_INPUT_CHUNK_TYPE_METRICS;
+ }
+ else if (event_type == FLB_INPUT_TRACES) {
+ meta[2] = FLB_INPUT_CHUNK_TYPE_TRACES;
+ }
+
+ /* unused byte */
+ meta[3] = 0;
+
+ /* copy the tag after magic bytes */
+ memcpy(meta + FLB_INPUT_CHUNK_META_HEADER, tag, tag_len);
+
+ /* Write tag into metadata section */
+ ret = cio_meta_write(chunk, (char *) meta, meta_size);
+ if (ret == -1) {
+ flb_error("[input chunk] could not write metadata");
+ flb_free(meta);
+ return -1;
+ }
+ flb_free(meta);
+
+ return 0;
+}
+
+struct flb_input_chunk *flb_input_chunk_create(struct flb_input_instance *in, int event_type,
+ const char *tag, int tag_len)
+{
+ int ret;
+ int err;
+ int set_down = FLB_FALSE;
+ int has_routes;
+ char name[64];
+ struct cio_chunk *chunk;
+ struct flb_storage_input *storage;
+ struct flb_input_chunk *ic;
+
+ storage = in->storage;
+
+ /* chunk name */
+ generate_chunk_name(in, name, sizeof(name) - 1);
+
+ /* open/create target chunk file */
+ chunk = cio_chunk_open(storage->cio, storage->stream, name,
+ CIO_OPEN, FLB_INPUT_CHUNK_SIZE, &err);
+ if (!chunk) {
+ flb_error("[input chunk] could not create chunk file: %s:%s",
+ storage->stream->name, name);
+ return NULL;
+ }
+ /*
+ * If the returned chunk at open is 'down', just put it up, write the
+ * content and set it down again.
+ */
+ ret = cio_chunk_is_up(chunk);
+ if (ret == CIO_FALSE) {
+ ret = cio_chunk_up_force(chunk);
+ if (ret == -1) {
+ cio_chunk_close(chunk, CIO_TRUE);
+ return NULL;
+ }
+ set_down = FLB_TRUE;
+ }
+
+ /* Write chunk header */
+ ret = input_chunk_write_header(chunk, event_type, (char *) tag, tag_len);
+ if (ret == -1) {
+ cio_chunk_close(chunk, CIO_TRUE);
+ return NULL;
+ }
+
+ /* Create context for the input instance */
+ ic = flb_calloc(1, sizeof(struct flb_input_chunk));
+ if (!ic) {
+ flb_errno();
+ cio_chunk_close(chunk, CIO_TRUE);
+ return NULL;
+ }
+
+ /*
+ * Check chunk content type to be created: depending of the value set by
+ * the input plugin, this can be FLB_INPUT_LOGS, FLB_INPUT_METRICS or
+ * FLB_INPUT_TRACES.
+ */
+ ic->event_type = event_type;
+ ic->busy = FLB_FALSE;
+ ic->fs_counted = FLB_FALSE;
+ ic->chunk = chunk;
+ ic->fs_backlog = FLB_FALSE;
+ ic->in = in;
+ ic->stream_off = 0;
+ ic->task = NULL;
+#ifdef FLB_HAVE_METRICS
+ ic->total_records = 0;
+#endif
+
+ /* Calculate the routes_mask for the input chunk */
+ has_routes = flb_routes_mask_set_by_tag(ic->routes_mask, tag, tag_len, in);
+ if (has_routes == 0) {
+ flb_trace("[input chunk] no matching route for input chunk '%s' with tag '%s'",
+ flb_input_chunk_get_name(ic), tag);
+ }
+
+ msgpack_packer_init(&ic->mp_pck, ic, flb_input_chunk_write);
+ mk_list_add(&ic->_head, &in->chunks);
+
+ if (set_down == FLB_TRUE) {
+ cio_chunk_down(chunk);
+ }
+
+ if (event_type == FLB_INPUT_LOGS) {
+ flb_hash_table_add(in->ht_log_chunks, tag, tag_len, ic, 0);
+ }
+ else if (event_type == FLB_INPUT_METRICS) {
+ flb_hash_table_add(in->ht_metric_chunks, tag, tag_len, ic, 0);
+ }
+ else if (event_type == FLB_INPUT_TRACES) {
+ flb_hash_table_add(in->ht_trace_chunks, tag, tag_len, ic, 0);
+ }
+
+ return ic;
+}
+
+int flb_input_chunk_destroy_corrupted(struct flb_input_chunk *ic,
+ const char *tag_buf, int tag_len,
+ int del)
+{
+ ssize_t bytes;
+ struct mk_list *head;
+ struct flb_output_instance *o_ins;
+
+ mk_list_foreach(head, &ic->in->config->outputs) {
+ o_ins = mk_list_entry(head, struct flb_output_instance, _head);
+
+ if (o_ins->total_limit_size == -1) {
+ continue;
+ }
+
+ bytes = flb_input_chunk_get_real_size(ic);
+ if (bytes == -1) {
+ // no data in the chunk
+ continue;
+ }
+
+ if (flb_routes_mask_get_bit(ic->routes_mask, o_ins->id) != 0) {
+ if (ic->fs_counted == FLB_TRUE) {
+ FS_CHUNK_SIZE_DEBUG_MOD(o_ins, ic, -bytes);
+ o_ins->fs_chunks_size -= bytes;
+ flb_debug("[input chunk] remove chunk %s with %ld bytes from plugin %s, "
+ "the updated fs_chunks_size is %ld bytes", flb_input_chunk_get_name(ic),
+ bytes, o_ins->name, o_ins->fs_chunks_size);
+ }
+ }
+ }
+
+ if (del == CIO_TRUE && tag_buf) {
+ /*
+ * "TRY" to delete any reference to this chunk ('ic') from the hash
+ * table. Note that maybe the value is not longer available in the
+ * entries if it was replaced: note that we always keep the last
+ * chunk for a specific Tag.
+ */
+ if (ic->event_type == FLB_INPUT_LOGS) {
+ flb_hash_table_del_ptr(ic->in->ht_log_chunks,
+ tag_buf, tag_len, (void *) ic);
+ }
+ else if (ic->event_type == FLB_INPUT_METRICS) {
+ flb_hash_table_del_ptr(ic->in->ht_metric_chunks,
+ tag_buf, tag_len, (void *) ic);
+ }
+ else if (ic->event_type == FLB_INPUT_TRACES) {
+ flb_hash_table_del_ptr(ic->in->ht_trace_chunks,
+ tag_buf, tag_len, (void *) ic);
+ }
+ }
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+ if (ic->trace != NULL) {
+ flb_chunk_trace_destroy(ic->trace);
+ }
+#endif /* FLB_HAVE_CHUNK_TRACE */
+
+ cio_chunk_close(ic->chunk, del);
+ mk_list_del(&ic->_head);
+ flb_free(ic);
+
+ return 0;
+}
+
+
+int flb_input_chunk_destroy(struct flb_input_chunk *ic, int del)
+{
+ int tag_len;
+ int ret;
+ ssize_t bytes;
+ const char *tag_buf = NULL;
+ struct mk_list *head;
+ struct flb_output_instance *o_ins;
+
+ if (flb_input_chunk_is_up(ic) == FLB_FALSE) {
+ flb_input_chunk_set_up(ic);
+ }
+
+ mk_list_foreach(head, &ic->in->config->outputs) {
+ o_ins = mk_list_entry(head, struct flb_output_instance, _head);
+
+ if (o_ins->total_limit_size == -1) {
+ continue;
+ }
+
+ bytes = flb_input_chunk_get_real_size(ic);
+ if (bytes == -1) {
+ // no data in the chunk
+ continue;
+ }
+
+ if (flb_routes_mask_get_bit(ic->routes_mask, o_ins->id) != 0) {
+ if (ic->fs_counted == FLB_TRUE) {
+ FS_CHUNK_SIZE_DEBUG_MOD(o_ins, ic, -bytes);
+ o_ins->fs_chunks_size -= bytes;
+ flb_debug("[input chunk] remove chunk %s with %ld bytes from plugin %s, "
+ "the updated fs_chunks_size is %ld bytes", flb_input_chunk_get_name(ic),
+ bytes, o_ins->name, o_ins->fs_chunks_size);
+ }
+ }
+ }
+
+ /*
+ * When a chunk is going to be destroyed, this can be in a down state,
+ * since the next step is to retrieve the Tag we need to have the
+ * content up.
+ */
+ ret = flb_input_chunk_is_up(ic);
+ if (ret == FLB_FALSE) {
+ ret = cio_chunk_up_force(ic->chunk);
+ if (ret == -1) {
+ flb_error("[input chunk] cannot load chunk: %s",
+ flb_input_chunk_get_name(ic));
+ }
+ }
+
+ /* Retrieve Tag */
+ ret = flb_input_chunk_get_tag(ic, &tag_buf, &tag_len);
+ if (ret == -1) {
+ flb_trace("[input chunk] could not retrieve chunk tag: %s",
+ flb_input_chunk_get_name(ic));
+ }
+
+ if (del == CIO_TRUE && tag_buf) {
+ /*
+ * "TRY" to delete any reference to this chunk ('ic') from the hash
+ * table. Note that maybe the value is not longer available in the
+ * entries if it was replaced: note that we always keep the last
+ * chunk for a specific Tag.
+ */
+ if (ic->event_type == FLB_INPUT_LOGS) {
+ flb_hash_table_del_ptr(ic->in->ht_log_chunks,
+ tag_buf, tag_len, (void *) ic);
+ }
+ else if (ic->event_type == FLB_INPUT_METRICS) {
+ flb_hash_table_del_ptr(ic->in->ht_metric_chunks,
+ tag_buf, tag_len, (void *) ic);
+ }
+ else if (ic->event_type == FLB_INPUT_TRACES) {
+ flb_hash_table_del_ptr(ic->in->ht_trace_chunks,
+ tag_buf, tag_len, (void *) ic);
+ }
+ }
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+ if (ic->trace != NULL) {
+ flb_chunk_trace_destroy(ic->trace);
+ }
+#endif /* FLB_HAVE_CHUNK_TRACE */
+
+ cio_chunk_close(ic->chunk, del);
+ mk_list_del(&ic->_head);
+ flb_free(ic);
+
+ return 0;
+}
+
+/* Return or create an available chunk to write data */
+static struct flb_input_chunk *input_chunk_get(struct flb_input_instance *in,
+ int event_type,
+ const char *tag, int tag_len,
+ size_t chunk_size, int *set_down)
+{
+ int id = -1;
+ int ret;
+ int new_chunk = FLB_FALSE;
+ size_t out_size;
+ struct flb_input_chunk *ic = NULL;
+
+ if (tag_len > FLB_INPUT_CHUNK_TAG_MAX) {
+ flb_plg_warn(in,
+ "Tag set exceeds limit, truncating from %i to %i bytes",
+ tag_len, FLB_INPUT_CHUNK_TAG_MAX);
+ tag_len = FLB_INPUT_CHUNK_TAG_MAX;
+ }
+
+ if (event_type == FLB_INPUT_LOGS) {
+ id = flb_hash_table_get(in->ht_log_chunks, tag, tag_len,
+ (void *) &ic, &out_size);
+ }
+ else if (event_type == FLB_INPUT_METRICS) {
+ id = flb_hash_table_get(in->ht_metric_chunks, tag, tag_len,
+ (void *) &ic, &out_size);
+ }
+ else if (event_type == FLB_INPUT_TRACES) {
+ id = flb_hash_table_get(in->ht_trace_chunks, tag, tag_len,
+ (void *) &ic, &out_size);
+ }
+
+ if (id >= 0) {
+ if (ic->busy == FLB_TRUE || cio_chunk_is_locked(ic->chunk)) {
+ ic = NULL;
+ }
+ else if (cio_chunk_is_up(ic->chunk) == CIO_FALSE) {
+ ret = cio_chunk_up_force(ic->chunk);
+
+ if (ret == CIO_CORRUPTED) {
+ if (in->config->storage_del_bad_chunks) {
+ /* If the chunk is corrupted we need to discard it and
+ * set ic to NULL so the system tries to allocate a new
+ * chunk.
+ */
+
+ flb_error("[input chunk] discarding corrupted chunk");
+ }
+
+ flb_input_chunk_destroy_corrupted(ic,
+ tag, tag_len,
+ in->config->storage_del_bad_chunks);
+
+ ic = NULL;
+ }
+ else if (ret != CIO_OK) {
+ ic = NULL;
+ }
+
+ *set_down = FLB_TRUE;
+ }
+ }
+
+ /* No chunk was found, we need to create a new one */
+ if (!ic) {
+ ic = flb_input_chunk_create(in, event_type, (char *) tag, tag_len);
+ new_chunk = FLB_TRUE;
+ if (!ic) {
+ return NULL;
+ }
+ ic->event_type = event_type;
+ }
+
+ /*
+ * If buffering this block of data will exceed one of the limit among all output instances
+ * that the chunk will flush to, we need to modify the routes_mask of the oldest chunks
+ * (based in creation time) to get enough space for the incoming chunk.
+ */
+ if (!flb_routes_mask_is_empty(ic->routes_mask)
+ && flb_input_chunk_place_new_chunk(ic, chunk_size) == 0) {
+ /*
+ * If the chunk is not newly created, the chunk might already have logs inside.
+ * We cannot delete (reused) chunks here.
+ * If the routes_mask is cleared after trying to append new data, we destroy
+ * the chunk.
+ */
+ if (new_chunk || flb_routes_mask_is_empty(ic->routes_mask) == FLB_TRUE) {
+ flb_input_chunk_destroy(ic, FLB_TRUE);
+ }
+ return NULL;
+ }
+
+ return ic;
+}
+
+static inline int flb_input_chunk_is_mem_overlimit(struct flb_input_instance *i)
+{
+ if (i->mem_buf_limit <= 0) {
+ return FLB_FALSE;
+ }
+
+ if (i->mem_chunks_size >= i->mem_buf_limit) {
+ return FLB_TRUE;
+ }
+
+ return FLB_FALSE;
+}
+
+static inline int flb_input_chunk_is_storage_overlimit(struct flb_input_instance *i)
+{
+ struct flb_storage_input *storage = (struct flb_storage_input *)i->storage;
+
+ if (storage->type == FLB_STORAGE_FS) {
+ if (i->storage_pause_on_chunks_overlimit == FLB_TRUE) {
+ if (storage->cio->total_chunks_up >= storage->cio->max_chunks_up) {
+ return FLB_TRUE;
+ }
+ }
+ }
+
+ return FLB_FALSE;
+}
+
+/*
+ * Check all chunks associated to the input instance and summarize
+ * the number of bytes in use.
+ */
+size_t flb_input_chunk_total_size(struct flb_input_instance *in)
+{
+ size_t total = 0;
+ struct flb_storage_input *storage;
+
+ storage = (struct flb_storage_input *) in->storage;
+ total = cio_stream_size_chunks_up(storage->stream);
+ return total;
+}
+
+/*
+ * Count and update the number of bytes being used by the instance. Also
+ * check if the instance is paused, if so, check if it can be resumed if
+ * is not longer over the limits.
+ *
+ * It always returns the number of bytes in use.
+ */
+size_t flb_input_chunk_set_limits(struct flb_input_instance *in)
+{
+ size_t total;
+
+ /* Gather total number of enqueued bytes */
+ total = flb_input_chunk_total_size(in);
+
+ /* Register the total into the context variable */
+ in->mem_chunks_size = total;
+
+ /*
+ * After the adjustments, validate if the plugin is overlimit or paused
+ * and perform further adjustments.
+ */
+ if (flb_input_chunk_is_mem_overlimit(in) == FLB_FALSE &&
+ in->config->is_running == FLB_TRUE &&
+ in->config->is_ingestion_active == FLB_TRUE &&
+ in->mem_buf_status == FLB_INPUT_PAUSED) {
+ in->mem_buf_status = FLB_INPUT_RUNNING;
+ if (in->p->cb_resume) {
+ flb_input_resume(in);
+ flb_info("[input] %s resume (mem buf overlimit)",
+ in->name);
+ }
+ }
+ if (flb_input_chunk_is_storage_overlimit(in) == FLB_FALSE &&
+ in->config->is_running == FLB_TRUE &&
+ in->config->is_ingestion_active == FLB_TRUE &&
+ in->storage_buf_status == FLB_INPUT_PAUSED) {
+ in->storage_buf_status = FLB_INPUT_RUNNING;
+ if (in->p->cb_resume) {
+ flb_input_resume(in);
+ flb_info("[input] %s resume (storage buf overlimit %zu/%zu)",
+ in->name,
+ ((struct flb_storage_input *)in->storage)->cio->total_chunks_up,
+ ((struct flb_storage_input *)in->storage)->cio->max_chunks_up);
+ }
+ }
+
+ return total;
+}
+
+/*
+ * If the number of bytes in use by the chunks are over the imposed limit
+ * by configuration, pause the instance.
+ */
+static inline int flb_input_chunk_protect(struct flb_input_instance *i)
+{
+ struct flb_storage_input *storage = i->storage;
+
+ if (flb_input_chunk_is_storage_overlimit(i) == FLB_TRUE) {
+ flb_warn("[input] %s paused (storage buf overlimit %zu/%zu)",
+ i->name,
+ storage->cio->total_chunks_up,
+ storage->cio->max_chunks_up);
+ flb_input_pause(i);
+ i->storage_buf_status = FLB_INPUT_PAUSED;
+ return FLB_TRUE;
+ }
+
+ if (storage->type == FLB_STORAGE_FS) {
+ return FLB_FALSE;
+ }
+
+ if (flb_input_chunk_is_mem_overlimit(i) == FLB_TRUE) {
+ /*
+ * if the plugin is already overlimit and the strategy is based on
+ * a memory-ring-buffer logic, do not pause the plugin, upon next
+ * try of ingestion 'memrb' will make sure to release some bytes.
+ */
+ if (i->storage_type == FLB_STORAGE_MEMRB) {
+ return FLB_FALSE;
+ }
+
+ /*
+ * The plugin is using 'memory' buffering only and already reached
+ * it limit, just pause the ingestion.
+ */
+ flb_warn("[input] %s paused (mem buf overlimit)",
+ i->name);
+ flb_input_pause(i);
+ i->mem_buf_status = FLB_INPUT_PAUSED;
+ return FLB_TRUE;
+ }
+
+ return FLB_FALSE;
+}
+
+/*
+ * Validate if the chunk coming from the input plugin based on config and
+ * resources usage must be 'up' or 'down' (applicable for filesystem storage
+ * type).
+ *
+ * FIXME: can we find a better name for this function ?
+ */
+int flb_input_chunk_set_up_down(struct flb_input_chunk *ic)
+{
+ size_t total;
+ struct flb_input_instance *in;
+
+ in = ic->in;
+
+ /* Gather total number of enqueued bytes */
+ total = flb_input_chunk_total_size(in);
+
+ /* Register the total into the context variable */
+ in->mem_chunks_size = total;
+
+ if (flb_input_chunk_is_mem_overlimit(in) == FLB_TRUE) {
+ if (cio_chunk_is_up(ic->chunk) == CIO_TRUE) {
+ cio_chunk_down(ic->chunk);
+
+ /* Adjust new counters */
+ total = flb_input_chunk_total_size(ic->in);
+ in->mem_chunks_size = total;
+
+ return FLB_FALSE;
+ }
+ }
+
+ return FLB_TRUE;
+}
+
+int flb_input_chunk_is_up(struct flb_input_chunk *ic)
+{
+ return cio_chunk_is_up(ic->chunk);
+}
+
+int flb_input_chunk_down(struct flb_input_chunk *ic)
+{
+ if (cio_chunk_is_up(ic->chunk) == CIO_TRUE) {
+ return cio_chunk_down(ic->chunk);
+ }
+
+ return 0;
+}
+
+int flb_input_chunk_set_up(struct flb_input_chunk *ic)
+{
+ if (cio_chunk_is_up(ic->chunk) == CIO_FALSE) {
+ return cio_chunk_up(ic->chunk);
+ }
+
+ return 0;
+}
+
+static int memrb_input_chunk_release_space(struct flb_input_instance *ins,
+ size_t required_space,
+ size_t *dropped_chunks, size_t *dropped_bytes)
+{
+ int ret;
+ int released;
+ size_t removed_chunks = 0;
+ ssize_t chunk_size;
+ ssize_t released_space = 0;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_input_chunk *ic;
+
+ mk_list_foreach_safe(head, tmp, &ins->chunks) {
+ ic = mk_list_entry(head, struct flb_input_chunk, _head);
+
+ /* check if is there any task or no users associated */
+ ret = flb_input_chunk_is_task_safe_delete(ic->task);
+ if (ret == FLB_FALSE) {
+ continue;
+ }
+
+ /* get chunk size */
+ chunk_size = flb_input_chunk_get_real_size(ic);
+
+ released = FLB_FALSE;
+ if (ic->task != NULL) {
+ if (ic->task->users == 0) {
+ flb_task_destroy(ic->task, FLB_TRUE);
+ released = FLB_TRUE;
+ }
+ }
+ else {
+ flb_input_chunk_destroy(ic, FLB_TRUE);
+ released = FLB_TRUE;
+ }
+
+ if (released) {
+ released_space += chunk_size;
+ removed_chunks++;
+ }
+
+ if (released_space >= required_space) {
+ break;
+ }
+ }
+
+ /* no matter if we succeeded or not, set the counters */
+ *dropped_bytes = released_space;
+ *dropped_chunks = removed_chunks;
+
+ /* set the final status of the operation */
+ if (released_space >= required_space) {
+ return 0;
+ }
+
+ return -1;
+}
+
+/* Append a RAW MessagPack buffer to the input instance */
+static int input_chunk_append_raw(struct flb_input_instance *in,
+ int event_type,
+ size_t n_records,
+ const char *tag, size_t tag_len,
+ const void *buf, size_t buf_size)
+{
+ int ret;
+ int set_down = FLB_FALSE;
+ int min;
+ int new_chunk = FLB_FALSE;
+ uint64_t ts;
+ char *name;
+ size_t dropped_chunks;
+ size_t dropped_bytes;
+ size_t content_size;
+ size_t real_diff;
+ size_t real_size;
+ size_t pre_real_size;
+ struct flb_input_chunk *ic;
+ struct flb_storage_input *si;
+
+ /* memory ring-buffer checker */
+ if (in->storage_type == FLB_STORAGE_MEMRB) {
+ /* check if we are overlimit */
+ ret = flb_input_chunk_is_mem_overlimit(in);
+ if (ret) {
+ /* reset counters */
+ dropped_chunks = 0;
+ dropped_bytes = 0;
+
+ /* try to release 'buf_size' */
+ ret = memrb_input_chunk_release_space(in, buf_size,
+ &dropped_chunks, &dropped_bytes);
+
+ /* update metrics if required */
+ if (dropped_chunks > 0 || dropped_bytes > 0) {
+ /* timestamp and input plugin name for label */
+ ts = cfl_time_now();
+ name = (char *) flb_input_name(in);
+
+ /* update counters */
+ cmt_counter_add(in->cmt_memrb_dropped_chunks, ts,
+ dropped_chunks, 1, (char *[]) {name});
+
+ cmt_counter_add(in->cmt_memrb_dropped_bytes, ts,
+ dropped_bytes, 1, (char *[]) {name});
+ }
+
+ if (ret != 0) {
+ /* we could not allocate the required space, just return */
+ return -1;
+ }
+ }
+ }
+
+ /* Check if the input plugin has been paused */
+ if (flb_input_buf_paused(in) == FLB_TRUE) {
+ flb_debug("[input chunk] %s is paused, cannot append records",
+ in->name);
+ return -1;
+ }
+
+ if (buf_size == 0) {
+ flb_debug("[input chunk] skip ingesting data with 0 bytes");
+ return -1;
+ }
+
+ /*
+ * Some callers might not set a custom tag, on that case just inherit
+ * the fixed instance tag or instance name.
+ */
+ if (!tag) {
+ if (in->tag && in->tag_len > 0) {
+ tag = in->tag;
+ tag_len = in->tag_len;
+ }
+ else {
+ tag = in->name;
+ tag_len = strlen(in->name);
+ }
+ }
+
+ /*
+ * Get a target input chunk, can be one with remaining space available
+ * or a new one.
+ */
+ ic = input_chunk_get(in, event_type, tag, tag_len, buf_size, &set_down);
+ if (!ic) {
+ flb_error("[input chunk] no available chunk");
+ return -1;
+ }
+
+ /* newly created chunk */
+ if (flb_input_chunk_get_size(ic) == 0) {
+ new_chunk = FLB_TRUE;
+ }
+
+ /* We got the chunk, validate if is 'up' or 'down' */
+ ret = flb_input_chunk_is_up(ic);
+ if (ret == FLB_FALSE) {
+ ret = cio_chunk_up_force(ic->chunk);
+ if (ret == -1) {
+ flb_error("[input chunk] cannot retrieve temporary chunk");
+ return -1;
+ }
+ set_down = FLB_TRUE;
+ }
+
+ /*
+ * Keep the previous real size to calculate the real size
+ * difference for flb_input_chunk_update_output_instances(),
+ * use 0 when the chunk is new since it's size will never
+ * have been calculated before.
+ */
+ if (new_chunk == FLB_TRUE) {
+ pre_real_size = 0;
+ }
+ else {
+ pre_real_size = flb_input_chunk_get_real_size(ic);
+ }
+
+ /* Write the new data */
+ ret = flb_input_chunk_write(ic, buf, buf_size);
+ if (ret == -1) {
+ flb_error("[input chunk] error writing data from %s instance",
+ in->name);
+ cio_chunk_tx_rollback(ic->chunk);
+ return -1;
+ }
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+ flb_chunk_trace_do_input(ic);
+#endif /* FLB_HAVE_CHUNK_TRACE */
+
+ /* Update 'input' metrics */
+#ifdef FLB_HAVE_METRICS
+ if (ret == CIO_OK) {
+ ic->added_records = n_records;
+ ic->total_records += n_records;
+ }
+
+ if (ic->total_records > 0) {
+ /* timestamp */
+ ts = cfl_time_now();
+
+ /* fluentbit_input_records_total */
+ cmt_counter_add(in->cmt_records, ts, ic->added_records,
+ 1, (char *[]) {(char *) flb_input_name(in)});
+
+ /* fluentbit_input_bytes_total */
+ cmt_counter_add(in->cmt_bytes, ts, buf_size,
+ 1, (char *[]) {(char *) flb_input_name(in)});
+
+ /* OLD api */
+ flb_metrics_sum(FLB_METRIC_N_RECORDS, ic->added_records, in->metrics);
+ flb_metrics_sum(FLB_METRIC_N_BYTES, buf_size, in->metrics);
+ }
+#endif
+
+ /* Apply filters */
+ if (event_type == FLB_INPUT_LOGS) {
+ flb_filter_do(ic,
+ buf, buf_size,
+ tag, tag_len, in->config);
+ }
+
+ /* get the chunks content size */
+ content_size = cio_chunk_get_content_size(ic->chunk);
+
+ /*
+ * There is a case that rewrite_tag will modify the tag and keep rule is set
+ * to drop the original record. The original record will still go through the
+ * flb_input_chunk_update_output_instances(2) to update the fs_chunks_size by
+ * metadata bytes (consisted by metadata bytes of the file chunk). This condition
+ * sets the diff to 0 in order to not update the fs_chunks_size.
+ */
+ if (flb_input_chunk_get_size(ic) == 0) {
+ real_diff = 0;
+ }
+
+ /* Lock buffers where size > 2MB */
+ if (content_size > FLB_INPUT_CHUNK_FS_MAX_SIZE) {
+ cio_chunk_lock(ic->chunk);
+ }
+
+ /* Make sure the data was not filtered out and the buffer size is zero */
+ if (content_size == 0) {
+ flb_input_chunk_destroy(ic, FLB_TRUE);
+ flb_input_chunk_set_limits(in);
+ return 0;
+ }
+#ifdef FLB_HAVE_STREAM_PROCESSOR
+ else if (in->config->stream_processor_ctx &&
+ ic->event_type == FLB_INPUT_LOGS) {
+ char *c_data;
+ size_t c_size;
+
+ /* Retrieve chunk (filtered) output content */
+ cio_chunk_get_content(ic->chunk, &c_data, &c_size);
+
+ /* Invoke stream processor */
+ flb_sp_do(in->config->stream_processor_ctx,
+ in,
+ tag, tag_len,
+ c_data + ic->stream_off, c_size - ic->stream_off);
+ ic->stream_off += (c_size - ic->stream_off);
+ }
+#endif
+
+ if (set_down == FLB_TRUE) {
+ cio_chunk_down(ic->chunk);
+ }
+
+ /*
+ * If the instance is not routable, there is no need to keep the
+ * content in the storage engine, just get rid of it.
+ */
+ if (in->routable == FLB_FALSE) {
+ flb_input_chunk_destroy(ic, FLB_TRUE);
+ return 0;
+ }
+
+ /* Update memory counters and adjust limits if any */
+ flb_input_chunk_set_limits(in);
+
+ /*
+ * Check if we are overlimit and validate if is there any filesystem
+ * storage type asociated to this input instance, if so, unload the
+ * chunk content from memory to respect imposed limits.
+ *
+ * Calling cio_chunk_down() the memory map associated and the file
+ * descriptor will be released. At any later time, it must be bring up
+ * for I/O operations.
+ */
+ si = (struct flb_storage_input *) in->storage;
+ if (flb_input_chunk_is_mem_overlimit(in) == FLB_TRUE &&
+ si->type == FLB_STORAGE_FS) {
+ if (cio_chunk_is_up(ic->chunk) == CIO_TRUE) {
+ /*
+ * If we are already over limit, a sub-sequent data ingestion
+ * might need a Chunk to write data in. As an optimization we
+ * will put this Chunk down ONLY IF it has less than 1% of
+ * it capacity as available space, otherwise keep it 'up' so
+ * it available space can be used.
+ */
+ content_size = cio_chunk_get_content_size(ic->chunk);
+
+ /* Do we have less than 1% available ? */
+ min = (FLB_INPUT_CHUNK_FS_MAX_SIZE * 0.01);
+ if (FLB_INPUT_CHUNK_FS_MAX_SIZE - content_size < min) {
+ cio_chunk_down(ic->chunk);
+ }
+ }
+ }
+
+ real_size = flb_input_chunk_get_real_size(ic);
+ real_diff = real_size - pre_real_size;
+ if (real_diff != 0) {
+ flb_debug("[input chunk] update output instances with new chunk size diff=%zd, records=%zu, input=%s",
+ real_diff, n_records, flb_input_name(in));
+ flb_input_chunk_update_output_instances(ic, real_diff);
+ }
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+ if (ic->trace) {
+ flb_chunk_trace_pre_output(ic->trace);
+ }
+#endif /* FLB_HAVE_CHUNK_TRACE */
+
+ flb_input_chunk_protect(in);
+ return 0;
+}
+
+static void destroy_chunk_raw(struct input_chunk_raw *cr)
+{
+ if (cr->buf_data) {
+ flb_free(cr->buf_data);
+ }
+
+ if (cr->tag) {
+ flb_sds_destroy(cr->tag);
+ }
+
+ flb_free(cr);
+}
+
+static int append_to_ring_buffer(struct flb_input_instance *ins,
+ int event_type,
+ size_t records,
+ const char *tag,
+ size_t tag_len,
+ const void *buf,
+ size_t buf_size)
+
+{
+ int ret;
+ int retries = 0;
+ int retry_limit = 10;
+ struct input_chunk_raw *cr;
+
+ cr = flb_calloc(1, sizeof(struct input_chunk_raw));
+ if (!cr) {
+ flb_errno();
+ return -1;
+ }
+ cr->ins = ins;
+ cr->event_type = event_type;
+
+ if (tag && tag_len > 0) {
+ cr->tag = flb_sds_create_len(tag, tag_len);
+ if (!cr->tag) {
+ flb_free(cr);
+ return -1;
+ }
+ }
+ else {
+ cr->tag = NULL;
+ }
+
+ cr->records = records;
+ cr->buf_data = flb_malloc(buf_size);
+ if (!cr->buf_data) {
+ flb_errno();
+ destroy_chunk_raw(cr);
+ return -1;
+ }
+
+ /*
+ * this memory copy is just a simple overhead, the problem we have is that
+ * input instances always assume that they have to release their buffer since
+ * the append raw operation already did a copy. Not a big issue but maybe this
+ * is a tradeoff...
+ */
+ memcpy(cr->buf_data, buf, buf_size);
+ cr->buf_size = buf_size;
+
+
+
+retry:
+ /*
+ * There is a little chance that the ring buffer is full or due to saturation
+ * from the main thread the data is not being consumed. On this scenario we
+ * retry up to 'retry_limit' times with a little wait time.
+ */
+ if (retries >= retry_limit) {
+ flb_plg_error(ins, "could not enqueue records into the ring buffer");
+ destroy_chunk_raw(cr);
+ return -1;
+ }
+
+ /* append chunk raw context to the ring buffer */
+ ret = flb_ring_buffer_write(ins->rb, (void *) &cr, sizeof(cr));
+ if (ret == -1) {
+ flb_plg_debug(ins, "failed buffer write, retries=%i\n",
+ retries);
+
+ /* sleep for 100000 microseconds (100 milliseconds) */
+ usleep(100000);
+ retries++;
+ goto retry;
+ }
+
+ return 0;
+}
+
+/* iterate input instance ring buffer and remove any enqueued input_chunk_raw */
+void flb_input_chunk_ring_buffer_cleanup(struct flb_input_instance *ins)
+{
+ int ret;
+ struct input_chunk_raw *cr;
+
+ if (!ins->rb) {
+ return;
+ }
+
+ while ((ret = flb_ring_buffer_read(ins->rb, (void *) &cr, sizeof(cr))) == 0) {
+ if (cr) {
+ destroy_chunk_raw(cr);
+ cr = NULL;
+ }
+ }
+}
+
+void flb_input_chunk_ring_buffer_collector(struct flb_config *ctx, void *data)
+{
+ int ret;
+ int tag_len = 0;
+ struct mk_list *head;
+ struct flb_input_instance *ins;
+ struct input_chunk_raw *cr;
+
+ mk_list_foreach(head, &ctx->inputs) {
+ ins = mk_list_entry(head, struct flb_input_instance, _head);
+ cr = NULL;
+
+ while (1) {
+ if (flb_input_buf_paused(ins) == FLB_TRUE) {
+ break;
+ }
+
+ ret = flb_ring_buffer_read(ins->rb,
+ (void *) &cr,
+ sizeof(cr));
+ if (ret != 0) {
+ break;
+ }
+
+ if (cr) {
+ if (cr->tag) {
+ tag_len = flb_sds_len(cr->tag);
+ }
+ else {
+ tag_len = 0;
+ }
+
+ input_chunk_append_raw(cr->ins, cr->event_type, cr->records,
+ cr->tag, tag_len,
+ cr->buf_data, cr->buf_size);
+ destroy_chunk_raw(cr);
+ }
+ cr = NULL;
+ }
+
+ ins->rb->flush_pending = FLB_FALSE;
+ }
+}
+
+int flb_input_chunk_append_raw(struct flb_input_instance *in,
+ int event_type,
+ size_t records,
+ const char *tag, size_t tag_len,
+ const void *buf, size_t buf_size)
+{
+ int ret;
+
+ /*
+ * If the plugin instance registering the data runs in a separate thread, we must
+ * add the data reference to the ring buffer.
+ */
+ if (flb_input_is_threaded(in)) {
+ ret = append_to_ring_buffer(in, event_type, records,
+ tag, tag_len,
+ buf, buf_size);
+ }
+ else {
+ ret = input_chunk_append_raw(in, event_type, records,
+ tag, tag_len, buf, buf_size);
+ }
+
+ return ret;
+}
+
+/* Retrieve a raw buffer from a dyntag node */
+const void *flb_input_chunk_flush(struct flb_input_chunk *ic, size_t *size)
+{
+ int ret;
+ size_t pre_size;
+ size_t post_size;
+ ssize_t diff_size;
+ char *buf = NULL;
+
+ pre_size = flb_input_chunk_get_real_size(ic);
+
+ if (cio_chunk_is_up(ic->chunk) == CIO_FALSE) {
+ ret = cio_chunk_up(ic->chunk);
+ if (ret == -1) {
+ return NULL;
+ }
+ }
+
+ /* Lock the internal chunk
+ *
+ * This operation has to be performed before getting the chunk data
+ * pointer because in certain situations it could cause the chunk
+ * mapping to be relocated (ie. macos / windows on trim)
+ */
+ cio_chunk_lock(ic->chunk);
+
+ /*
+ * msgpack-c internal use a raw buffer for it operations, since we
+ * already appended data we just can take out the references to avoid
+ * a new memory allocation and skip a copy operation.
+ */
+ ret = cio_chunk_get_content(ic->chunk, &buf, size);
+
+ if (ret == -1) {
+ flb_error("[input chunk] error retrieving chunk content");
+ return NULL;
+ }
+
+ if (!buf) {
+ *size = 0;
+ return NULL;
+ }
+
+ /* Set it busy as it likely it's a reference for an outgoing task */
+ ic->busy = FLB_TRUE;
+
+ post_size = flb_input_chunk_get_real_size(ic);
+ if (post_size != pre_size) {
+ diff_size = post_size - pre_size;
+ flb_input_chunk_update_output_instances(ic, diff_size);
+ }
+ return buf;
+}
+
+int flb_input_chunk_release_lock(struct flb_input_chunk *ic)
+{
+ if (ic->busy == FLB_FALSE) {
+ return -1;
+ }
+
+ ic->busy = FLB_FALSE;
+ return 0;
+}
+
+flb_sds_t flb_input_chunk_get_name(struct flb_input_chunk *ic)
+{
+ struct cio_chunk *ch;
+
+ ch = (struct cio_chunk *) ic->chunk;
+ return ch->name;
+}
+
+static inline int input_chunk_has_magic_bytes(char *buf, int len)
+{
+ unsigned char *p;
+
+ if (len < FLB_INPUT_CHUNK_META_HEADER) {
+ return FLB_FALSE;
+ }
+
+ p = (unsigned char *) buf;
+ if (p[0] == FLB_INPUT_CHUNK_MAGIC_BYTE_0 &&
+ p[1] == FLB_INPUT_CHUNK_MAGIC_BYTE_1 && p[3] == 0) {
+ return FLB_TRUE;
+ }
+
+ return FLB_FALSE;
+}
+
+/*
+ * Get the event type by retrieving metadata header. NOTE: this function only event type discovery by looking at the
+ * headers bytes of a chunk that exists on disk.
+ */
+int flb_input_chunk_get_event_type(struct flb_input_chunk *ic)
+{
+ int len;
+ int ret;
+ int type = -1;
+ char *buf = NULL;
+
+ ret = cio_meta_read(ic->chunk, &buf, &len);
+ if (ret == -1) {
+ return -1;
+ }
+
+ /* Check metadata header / magic bytes */
+ if (input_chunk_has_magic_bytes(buf, len)) {
+ if (buf[2] == FLB_INPUT_CHUNK_TYPE_LOGS) {
+ type = FLB_INPUT_LOGS;
+ }
+ else if (buf[2] == FLB_INPUT_CHUNK_TYPE_METRICS) {
+ type = FLB_INPUT_METRICS;
+ }
+ else if (buf[2] == FLB_INPUT_CHUNK_TYPE_TRACES) {
+ type = FLB_INPUT_TRACES;
+ }
+ }
+ else {
+ type = FLB_INPUT_LOGS;
+ }
+
+
+ return type;
+}
+
+int flb_input_chunk_get_tag(struct flb_input_chunk *ic,
+ const char **tag_buf, int *tag_len)
+{
+ int len;
+ int ret;
+ char *buf;
+
+ ret = cio_meta_read(ic->chunk, &buf, &len);
+ if (ret == -1) {
+ *tag_len = -1;
+ *tag_buf = NULL;
+ return -1;
+ }
+
+ /* If magic bytes exists, just set the offset */
+ if (input_chunk_has_magic_bytes(buf, len)) {
+ *tag_len = len - FLB_INPUT_CHUNK_META_HEADER;
+ *tag_buf = buf + FLB_INPUT_CHUNK_META_HEADER;
+ }
+ else {
+ /* Old Chunk version without magic bytes */
+ *tag_len = len;
+ *tag_buf = buf;
+ }
+
+ return ret;
+}
+
+/*
+ * Iterates all output instances that the chunk will be flushing to and summarize
+ * the total number of bytes in use after ingesting the new data.
+ */
+void flb_input_chunk_update_output_instances(struct flb_input_chunk *ic,
+ size_t chunk_size)
+{
+ struct mk_list *head;
+ struct flb_output_instance *o_ins;
+
+ /* for each output plugin, we update the fs_chunks_size */
+ mk_list_foreach(head, &ic->in->config->outputs) {
+ o_ins = mk_list_entry(head, struct flb_output_instance, _head);
+ if (o_ins->total_limit_size == -1) {
+ continue;
+ }
+
+ if (flb_routes_mask_get_bit(ic->routes_mask, o_ins->id) != 0) {
+ /*
+ * if there is match on any index of 1's in the binary, it indicates
+ * that the input chunk will flush to this output instance
+ */
+ FS_CHUNK_SIZE_DEBUG_MOD(o_ins, ic, chunk_size);
+ o_ins->fs_chunks_size += chunk_size;
+ ic->fs_counted = FLB_TRUE;
+
+ flb_debug("[input chunk] chunk %s update plugin %s fs_chunks_size by %ld bytes, "
+ "the current fs_chunks_size is %ld bytes", flb_input_chunk_get_name(ic),
+ o_ins->name, chunk_size, o_ins->fs_chunks_size);
+ }
+ }
+}
diff --git a/fluent-bit/src/flb_input_log.c b/fluent-bit/src/flb_input_log.c
new file mode 100644
index 000000000..ed8fa8aa2
--- /dev/null
+++ b/fluent-bit/src/flb_input_log.c
@@ -0,0 +1,123 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_input_chunk.h>
+#include <fluent-bit/flb_input_log.h>
+#include <fluent-bit/flb_input_plugin.h>
+#include <fluent-bit/flb_processor.h>
+
+static int input_log_append(struct flb_input_instance *ins,
+ size_t processor_starting_stage,
+ size_t records,
+ const char *tag, size_t tag_len,
+ const void *buf, size_t buf_size)
+{
+ int ret;
+ int processor_is_active;
+ void *out_buf = (void *) buf;
+ size_t out_size = buf_size;
+
+ processor_is_active = flb_processor_is_active(ins->processor);
+ if (processor_is_active) {
+ if (!tag) {
+ if (ins->tag && ins->tag_len > 0) {
+ tag = ins->tag;
+ tag_len = ins->tag_len;
+ }
+ else {
+ tag = ins->name;
+ tag_len = strlen(ins->name);
+ }
+ }
+
+ ret = flb_processor_run(ins->processor,
+ processor_starting_stage,
+ FLB_PROCESSOR_LOGS,
+ tag, tag_len,
+ (char *) buf, buf_size,
+ &out_buf, &out_size);
+ if (ret == -1) {
+ return -1;
+ }
+
+ if (out_size == 0) {
+ return 0;
+ }
+
+ if (buf != out_buf) {
+ /* a new buffer was created, re-count the number of records */
+ records = flb_mp_count(out_buf, out_size);
+ }
+ }
+
+ ret = flb_input_chunk_append_raw(ins, FLB_INPUT_LOGS, records,
+ tag, tag_len, out_buf, out_size);
+
+
+ if (processor_is_active && buf != out_buf) {
+ flb_free(out_buf);
+ }
+ return ret;
+}
+
+/* Take a msgpack serialized record and enqueue it as a chunk */
+int flb_input_log_append(struct flb_input_instance *ins,
+ const char *tag, size_t tag_len,
+ const void *buf, size_t buf_size)
+{
+ int ret;
+ size_t records;
+
+ records = flb_mp_count(buf, buf_size);
+ ret = input_log_append(ins, 0, records, tag, tag_len, buf, buf_size);
+ return ret;
+}
+
+/* Take a msgpack serialized record and enqueue it as a chunk */
+int flb_input_log_append_skip_processor_stages(struct flb_input_instance *ins,
+ size_t processor_starting_stage,
+ const char *tag,
+ size_t tag_len,
+ const void *buf,
+ size_t buf_size)
+{
+ return input_log_append(ins,
+ processor_starting_stage,
+ flb_mp_count(buf, buf_size),
+ tag,
+ tag_len,
+ buf,
+ buf_size);
+}
+
+/* Take a msgpack serialized record and enqueue it as a chunk */
+int flb_input_log_append_records(struct flb_input_instance *ins,
+ size_t records,
+ const char *tag, size_t tag_len,
+ const void *buf, size_t buf_size)
+{
+ int ret;
+
+ ret = input_log_append(ins, 0, records, tag, tag_len, buf, buf_size);
+ return ret;
+}
+
+
diff --git a/fluent-bit/src/flb_input_metric.c b/fluent-bit/src/flb_input_metric.c
new file mode 100644
index 000000000..f332d4203
--- /dev/null
+++ b/fluent-bit/src/flb_input_metric.c
@@ -0,0 +1,101 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_input_chunk.h>
+#include <fluent-bit/flb_input_metric.h>
+#include <fluent-bit/flb_input_plugin.h>
+
+static int input_metrics_append(struct flb_input_instance *ins,
+ size_t processor_starting_stage,
+ const char *tag, size_t tag_len,
+ struct cmt *cmt)
+{
+ int ret;
+ char *mt_buf;
+ size_t mt_size;
+ int processor_is_active;
+
+ processor_is_active = flb_processor_is_active(ins->processor);
+ if (processor_is_active) {
+ if (!tag) {
+ if (ins->tag && ins->tag_len > 0) {
+ tag = ins->tag;
+ tag_len = ins->tag_len;
+ }
+ else {
+ tag = ins->name;
+ tag_len = strlen(ins->name);
+ }
+ }
+
+ ret = flb_processor_run(ins->processor,
+ processor_starting_stage,
+ FLB_PROCESSOR_METRICS,
+ tag,
+ tag_len,
+ (char *) cmt,
+ 0, NULL, NULL);
+
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ /* Convert metrics to msgpack */
+ ret = cmt_encode_msgpack_create(cmt, &mt_buf, &mt_size);
+ if (ret != 0) {
+ flb_plg_error(ins, "could not encode metrics");
+ return -1;
+
+ }
+
+ /* Append packed metrics */
+ ret = flb_input_chunk_append_raw(ins, FLB_INPUT_METRICS, 0,
+ tag, tag_len, mt_buf, mt_size);
+
+ cmt_encode_msgpack_destroy(mt_buf);
+
+ return ret;
+}
+
+/* Take a metric context and enqueue it as a Metric's Chunk */
+int flb_input_metrics_append(struct flb_input_instance *ins,
+ const char *tag, size_t tag_len,
+ struct cmt *cmt)
+{
+ return input_metrics_append(ins,
+ 0,
+ tag, tag_len,
+ cmt);
+}
+
+/* Take a metric context and enqueue it as a Metric's Chunk */
+int flb_input_metrics_append_skip_processor_stages(
+ struct flb_input_instance *ins,
+ size_t processor_starting_stage,
+ const char *tag, size_t tag_len,
+ struct cmt *cmt)
+{
+ return input_metrics_append(ins,
+ processor_starting_stage,
+ tag, tag_len,
+ cmt);
+}
diff --git a/fluent-bit/src/flb_input_thread.c b/fluent-bit/src/flb_input_thread.c
new file mode 100644
index 000000000..bf073296d
--- /dev/null
+++ b/fluent-bit/src/flb_input_thread.c
@@ -0,0 +1,745 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2019-2021 The Fluent Bit Authors
+ * Copyright (C) 2015-2018 Treasure Data Inc.
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_pipe.h>
+#include <fluent-bit/flb_engine.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_mp.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_event_loop.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_scheduler.h>
+#include <fluent-bit/flb_downstream.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_input_plugin.h>
+#include <fluent-bit/flb_input_thread.h>
+
+static int input_thread_instance_set_status(struct flb_input_instance *ins, uint32_t status);
+static int input_thread_instance_get_status(struct flb_input_instance *ins);
+
+/* Cleanup function that runs every 1.5 second */
+static void cb_thread_sched_timer(struct flb_config *ctx, void *data)
+{
+ struct flb_input_instance *ins;
+
+ (void) ctx;
+
+ /* Downstream timeout handling */
+ ins = (struct flb_input_instance *) data;
+
+ flb_upstream_conn_timeouts(&ins->upstreams);
+ flb_downstream_conn_timeouts(&ins->downstreams);
+}
+
+static inline int handle_input_event(flb_pipefd_t fd, struct flb_input_instance *ins)
+{
+ int bytes;
+ int ins_id;
+ uint32_t type;
+ uint32_t operation;
+ uint64_t val;
+ struct flb_config *config = ins->config;
+
+ bytes = read(fd, &val, sizeof(val));
+ if (bytes == -1) {
+ flb_errno();
+ return -1;
+ }
+
+ type = FLB_BITS_U64_HIGH(val);
+ operation = FLB_BITS_U64_LOW(val);
+
+ /* At the moment we only support events coming from an input coroutine */
+ if (type == FLB_ENGINE_IN_CORO) {
+ ins_id = ins->id;
+ flb_input_coro_finished(config, ins_id);
+ }
+ else if (type == FLB_INPUT_THREAD_TO_THREAD) {
+ if (operation == FLB_INPUT_THREAD_PAUSE) {
+ if (ins->p->cb_pause && ins->context) {
+ ins->p->cb_pause(ins->context, ins->config);
+ }
+ }
+ else if (operation == FLB_INPUT_THREAD_RESUME) {
+ if (ins->p->cb_resume) {
+ ins->p->cb_resume(ins->context, ins->config);
+ }
+ }
+ else if (operation == FLB_INPUT_THREAD_EXIT) {
+ return FLB_INPUT_THREAD_EXIT;
+ }
+ }
+ else {
+ flb_error("[thread event loop] it happends on fd=%i, invalid type=%i", fd, type);
+ return -1;
+ }
+
+ return 0;
+}
+
+static inline int handle_input_thread_event(flb_pipefd_t fd, struct flb_config *config)
+{
+ int bytes;
+ uint32_t type;
+ uint32_t ins_id;
+ uint64_t val;
+
+ bytes = flb_pipe_r(fd, &val, sizeof(val));
+ if (bytes == -1) {
+ flb_errno();
+ return -1;
+ }
+
+ /* Get type and key */
+ type = FLB_BITS_U64_HIGH(val);
+ ins_id = FLB_BITS_U64_LOW(val);
+
+ /* At the moment we only support events coming from an input coroutine */
+ if (type == FLB_ENGINE_IN_CORO) {
+ flb_input_coro_finished(config, ins_id);
+ }
+ else {
+ flb_error("[thread event loop] invalid thread event type %i for input handler",
+ type);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int input_collector_fd(flb_pipefd_t fd, struct flb_input_instance *ins)
+{
+ struct mk_list *head;
+ struct flb_input_collector *collector = NULL;
+ struct flb_input_coro *input_coro;
+ struct flb_config *config = ins->config;
+
+ mk_list_foreach(head, &ins->collectors) {
+ collector = mk_list_entry(head, struct flb_input_collector, _head);
+ if (collector->fd_event == fd) {
+ break;
+ }
+ else if (collector->fd_timer == fd) {
+ flb_utils_timer_consume(fd);
+ break;
+ }
+ collector = NULL;
+ }
+
+ /* No matches */
+ if (!collector) {
+ return -1;
+ }
+
+ if (collector->running == FLB_FALSE) {
+ return -1;
+ }
+
+ /* Trigger the collector callback */
+ if (collector->instance->runs_in_coroutine) {
+ input_coro = flb_input_coro_collect(collector, config);
+ if (!input_coro) {
+ return -1;
+ }
+ flb_input_coro_resume(input_coro);
+ }
+ else {
+ collector->cb_collect(collector->instance, config,
+ collector->instance->context);
+ }
+
+ return 0;
+}
+
+static FLB_INLINE int engine_handle_event(flb_pipefd_t fd, int mask,
+ struct flb_input_instance *ins,
+ struct flb_config *config)
+{
+ int ret;
+
+ if (mask & MK_EVENT_READ) {
+ /* Try to match the file descriptor with a collector event */
+ ret = input_collector_fd(fd, ins);
+ if (ret != -1) {
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void input_thread_instance_destroy(struct flb_input_thread_instance *thi)
+{
+ if (thi->evl) {
+ mk_event_loop_destroy(thi->evl);
+ }
+
+ /* ch_parent_events */
+ if (thi->ch_parent_events[0] > 0) {
+ mk_event_closesocket(thi->ch_parent_events[0]);
+ }
+ if (thi->ch_parent_events[1] > 0) {
+ mk_event_closesocket(thi->ch_parent_events[1]);
+ }
+
+ /* ch_thread_events */
+ if (thi->ch_thread_events[0] > 0) {
+ mk_event_closesocket(thi->ch_thread_events[0]);
+ }
+ if (thi->ch_thread_events[1] > 0) {
+ mk_event_closesocket(thi->ch_thread_events[1]);
+ }
+
+ flb_tp_destroy(thi->tp);
+ flb_free(thi);
+}
+
+static struct flb_input_thread_instance *input_thread_instance_create(struct flb_input_instance *ins)
+{
+ int ret;
+ struct flb_input_thread_instance *thi;
+
+ /* context for thread */
+ thi = flb_calloc(1, sizeof(struct flb_input_thread_instance));
+ if (!thi) {
+ flb_errno();
+ return NULL;
+ }
+ thi->ins = ins;
+ thi->config = ins->config;
+
+ /* init status */
+ thi->init_status = 0;
+ pthread_mutex_init(&thi->init_mutex, NULL);
+
+ /* init condition */
+ pthread_cond_init(&thi->init_condition, NULL);
+
+ /* initialize lists */
+ mk_list_init(&thi->input_coro_list);
+ mk_list_init(&thi->input_coro_list_destroy);
+
+ /* event loop */
+ thi->evl = mk_event_loop_create(256);
+ if (!thi->evl) {
+ input_thread_instance_destroy(thi);
+ return NULL;
+ }
+
+ /* channel to receive parent (engine) notifications */
+ ret = mk_event_channel_create(thi->evl,
+ &thi->ch_parent_events[0],
+ &thi->ch_parent_events[1],
+ &thi->event);
+ if (ret == -1) {
+ flb_error("could not initialize parent channels for %s",
+ flb_input_name(ins));
+ input_thread_instance_destroy(thi);
+ return NULL;
+ }
+ thi->event.type = FLB_ENGINE_EV_INPUT;
+
+ /* channel to send messages to local event loop */
+ ret = mk_event_channel_create(thi->evl,
+ &thi->ch_thread_events[0],
+ &thi->ch_thread_events[1],
+ &thi->event_local);
+ if (ret == -1) {
+ flb_error("could not initialize parent channels for %s",
+ flb_input_name(ins));
+ input_thread_instance_destroy(thi);
+ return NULL;
+ }
+ thi->event_local.type = FLB_ENGINE_EV_THREAD_INPUT;
+
+ /* create thread pool, just one worker */
+ thi->tp = flb_tp_create(ins->config);
+ if (!thi->tp) {
+ flb_error("could not create thread pool on input instance '%s'",
+ flb_input_name(ins));
+ input_thread_instance_destroy(thi);
+ return NULL;
+ }
+
+ return thi;
+}
+
+
+static void input_thread(void *data)
+{
+ int ret;
+ int thread_id;
+ char tmp[64];
+ int instance_exit = FLB_FALSE;
+ struct mk_event *event;
+ struct flb_input_instance *ins;
+ struct flb_bucket_queue *evl_bktq = NULL;
+ struct flb_input_thread_instance *thi;
+ struct flb_input_plugin *p;
+ struct flb_sched *sched = NULL;
+ struct flb_net_dns dns_ctx = {0};
+
+ thi = (struct flb_input_thread_instance *) data;
+ ins = thi->ins;
+ p = ins->p;
+
+ flb_engine_evl_set(thi->evl);
+
+ /* Create a scheduler context */
+ sched = flb_sched_create(ins->config, thi->evl);
+ if (!sched) {
+ flb_plg_error(ins, "could not create thread scheduler");
+ return;
+ }
+ flb_sched_ctx_set(sched);
+
+ /*
+ * Sched a permanent callback triggered every 1.5 second to let other
+ * components of this thread run tasks at that interval.
+ */
+ ret = flb_sched_timer_cb_create(sched,
+ FLB_SCHED_TIMER_CB_PERM,
+ 1500, cb_thread_sched_timer, ins, NULL);
+ if (ret == -1) {
+ flb_error("could not schedule input thread permanent callback");
+ return;
+ }
+
+ flb_coro_thread_init();
+
+ flb_net_ctx_init(&dns_ctx);
+ flb_net_dns_ctx_set(&dns_ctx);
+
+ thread_id = thi->th->id;
+ snprintf(tmp, sizeof(tmp) - 1, "flb-in-%s-w%i", ins->name, thread_id);
+ mk_utils_worker_rename(tmp);
+
+ /* invoke plugin 'init' callback */
+ ret = p->cb_init(ins, ins->config, ins->data);
+ if (ret == -1) {
+ flb_error("failed initialize input %s", flb_input_name(ins));
+ /* message the parent thread that this thread could not be initialized */
+ input_thread_instance_set_status(ins, FLB_INPUT_THREAD_ERROR);
+ return;
+ }
+
+ flb_plg_debug(ins, "[thread init] initialization OK");
+ input_thread_instance_set_status(ins, FLB_INPUT_THREAD_OK);
+
+ /*
+ * Wait for parent thread to signal this thread so we can start collectors and
+ * get into the event loop
+ */
+ ret = flb_input_thread_collectors_signal_wait(ins);
+ if (ret == -1) {
+ flb_error("could not retrieve collectors signal from parent thread on '%s'",
+ flb_input_name(ins));
+ return;
+ }
+
+ /* event loop queue */
+ evl_bktq = flb_bucket_queue_create(FLB_ENGINE_PRIORITY_COUNT);
+
+ /* Start collectors */
+ flb_input_thread_collectors_start(ins);
+
+ /* If the plugin contains a 'pre_run' callback, invoke it */
+ if (p->cb_pre_run) {
+ ret = p->cb_pre_run(ins, ins->config, ins->context);
+ if (ret == -1) {
+ /*
+ * FIXME: how do we report a failed pre-run status to the parent thread ?,
+ * as of know it does not seems to be necessary since the only plugins
+ * using that callback are tail and systemd, but those are just writing a
+ * byte to a recently created pipe in the initialization.
+ */
+ }
+ }
+
+ while (1) {
+ mk_event_wait(thi->evl);
+ flb_event_priority_live_foreach(event, evl_bktq, thi->evl, FLB_ENGINE_LOOP_MAX_ITER) {
+ if (event->type == FLB_ENGINE_EV_CORE) {
+ ret = engine_handle_event(event->fd, event->mask,
+ ins, thi->config);
+ if (ret == FLB_ENGINE_STOP) {
+ continue;
+ }
+ else if (ret == FLB_ENGINE_SHUTDOWN) {
+ continue;
+ }
+ }
+ else if (event->type & FLB_ENGINE_EV_SCHED) {
+ /* Event type registered by the Scheduler */
+ flb_sched_event_handler(ins->config, event);
+ }
+ else if (event->type == FLB_ENGINE_EV_THREAD_ENGINE) {
+ struct flb_output_flush *output_flush;
+
+ /* Read the coroutine reference */
+ ret = flb_pipe_r(event->fd, &output_flush, sizeof(struct flb_output_flush *));
+ if (ret <= 0 || output_flush == 0) {
+ flb_errno();
+ continue;
+ }
+
+ /* Init coroutine */
+ flb_coro_resume(output_flush->coro);
+ }
+ else if (event->type == FLB_ENGINE_EV_CUSTOM) {
+ event->handler(event);
+ }
+ else if (event->type == FLB_ENGINE_EV_THREAD) {
+ struct flb_connection *connection;
+
+ /*
+ * Check if we have some co-routine associated to this event,
+ * if so, resume the co-routine
+ */
+ connection = (struct flb_connection *) event;
+
+ if (connection->coroutine != NULL) {
+ flb_trace("[engine] resuming coroutine=%p",
+ connection->coroutine);
+
+ flb_coro_resume(connection->coroutine);
+ }
+ }
+ else if (event->type == FLB_ENGINE_EV_INPUT) {
+ ret = handle_input_event(event->fd, ins);
+ if (ret == FLB_INPUT_THREAD_EXIT) {
+ instance_exit = FLB_TRUE;
+ }
+ }
+ else if (event->type == FLB_ENGINE_EV_THREAD_INPUT) {
+ handle_input_thread_event(event->fd, ins->config);
+ }
+ }
+
+ flb_net_dns_lookup_context_cleanup(&dns_ctx);
+
+ /* Destroy upstream connections from the 'pending destroy list' */
+ flb_upstream_conn_pending_destroy_list(&ins->upstreams);
+
+ /* Destroy downstream connections from the 'pending destroy list' */
+ flb_downstream_conn_pending_destroy_list(&ins->downstreams);
+ flb_sched_timer_cleanup(sched);
+
+ /* Check if the instance must exit */
+ if (instance_exit) {
+ /* Invoke exit callback */
+ if (ins->p->cb_exit && ins->context) {
+ ins->p->cb_exit(ins->context, ins->config);
+ }
+
+ /* break the loop */
+ break;
+ }
+ }
+
+ /* Create the bucket queue (FLB_ENGINE_PRIORITY_COUNT priorities) */
+ flb_bucket_queue_destroy(evl_bktq);
+ flb_sched_destroy(sched);
+ input_thread_instance_destroy(thi);
+}
+
+
+/*
+ * Signal the thread event loop to pause the running plugin instance. This function
+ * must be called only from the main thread/pipeline.
+ */
+int flb_input_thread_instance_pause(struct flb_input_instance *ins)
+{
+ int ret;
+ uint64_t val;
+ struct flb_input_thread_instance *thi = ins->thi;
+
+ flb_plg_debug(ins, "thread pause instance");
+
+ /* compose message to pause the thread */
+ val = FLB_BITS_U64_SET(FLB_INPUT_THREAD_TO_THREAD,
+ FLB_INPUT_THREAD_PAUSE);
+
+ ret = flb_pipe_w(thi->ch_parent_events[1], &val, sizeof(val));
+ if (ret <= 0) {
+ flb_errno();
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Signal the thread event loop to resume the running plugin instance. This function
+ * must be called only from the main thread/pipeline.
+ */
+int flb_input_thread_instance_resume(struct flb_input_instance *ins)
+{
+ int ret;
+ uint64_t val;
+ struct flb_input_thread_instance *thi = ins->thi;
+
+ flb_plg_debug(ins, "thread resume instance");
+
+ /* compose message to resume the thread */
+ val = FLB_BITS_U64_SET(FLB_INPUT_THREAD_TO_THREAD,
+ FLB_INPUT_THREAD_RESUME);
+
+ ret = flb_pipe_w(thi->ch_parent_events[1], &val, sizeof(val));
+ if (ret <= 0) {
+ flb_errno();
+ return -1;
+ }
+
+ return 0;
+}
+
+int flb_input_thread_instance_exit(struct flb_input_instance *ins)
+{
+ int ret;
+ uint64_t val;
+ struct flb_input_thread_instance *thi = ins->thi;
+ pthread_t tid;
+
+ memcpy(&tid, &thi->th->tid, sizeof(pthread_t));
+
+ /* compose message to pause the thread */
+ val = FLB_BITS_U64_SET(FLB_INPUT_THREAD_TO_THREAD,
+ FLB_INPUT_THREAD_EXIT);
+
+ ret = flb_pipe_w(thi->ch_parent_events[1], &val, sizeof(val));
+ if (ret <= 0) {
+ flb_errno();
+ return -1;
+ }
+
+ pthread_join(tid, NULL);
+ flb_plg_debug(ins, "thread exit instance");
+
+ return 0;
+}
+
+
+/* Initialize a plugin under a threaded context */
+int flb_input_thread_instance_init(struct flb_config *config, struct flb_input_instance *ins)
+{
+ int ret;
+ struct flb_tp_thread *th;
+ struct flb_input_thread_instance *thi;
+
+ /* Create the threaded context for the instance in question */
+ thi = input_thread_instance_create(ins);
+ if (!thi) {
+ return -1;
+ }
+ ins->thi = thi;
+
+ /* Spawn the thread */
+ th = flb_tp_thread_create(thi->tp, input_thread, thi, config);
+ if (!th) {
+ flb_plg_error(ins, "could not register worker thread");
+ input_thread_instance_destroy(thi);
+ return -1;
+ }
+ thi->th = th;
+
+ /* start the thread */
+ ret = flb_tp_thread_start(thi->tp, thi->th);
+ if (ret != 0) {
+ return -1;
+ }
+
+ ret = input_thread_instance_get_status(ins);
+ if (ret == -1) {
+ flb_plg_error(ins, "unexpected error loading plugin instance");
+ }
+ else if (ret == FLB_FALSE) {
+ flb_plg_error(ins, "could not initialize threaded plugin instance");
+ }
+ else if (ret == FLB_TRUE) {
+ flb_plg_info(ins, "thread instance initialized");
+ }
+
+ return 0;
+}
+
+int flb_input_thread_instance_pre_run(struct flb_config *config, struct flb_input_instance *ins)
+{
+ int ret;
+
+ if (ins->p->cb_pre_run) {
+ /*
+ * the pre_run callback is invoked automatically from the instance thread. we just need to check for the
+ * final status.
+ */
+ ret = input_thread_instance_get_status(ins);
+ if (ret == -1) {
+ return -1;
+ }
+ else if (ret == FLB_FALSE) {
+ return -1;
+ }
+ else if (ret == FLB_TRUE) {
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static int input_thread_instance_set_status(struct flb_input_instance *ins, uint32_t status)
+{
+ struct flb_input_thread_instance *thi;
+
+ thi = ins->thi;
+
+ pthread_mutex_lock(&thi->init_mutex);
+
+ thi->init_status = status;
+
+ pthread_cond_signal(&thi->init_condition);
+ pthread_mutex_unlock(&thi->init_mutex);
+
+ return 0;
+}
+
+static int input_thread_instance_get_status(struct flb_input_instance *ins)
+{
+
+ uint32_t status;
+ struct flb_input_thread_instance *thi;
+
+ thi = ins->thi;
+
+ /* Wait for thread to report a status */
+ pthread_mutex_lock(&thi->init_mutex);
+ while (thi->init_status == 0) {
+ pthread_cond_wait(&thi->init_condition, &thi->init_mutex);
+ }
+ pthread_mutex_unlock(&thi->init_mutex);
+
+ /* re-initialize condition */
+ pthread_cond_destroy(&thi->init_condition);
+ pthread_cond_init(&thi->init_condition, NULL);
+
+ /* get the final status */
+ status = thi->init_status;
+ if (status == FLB_INPUT_THREAD_OK) {
+ return FLB_TRUE;
+ }
+ else if (status == FLB_INPUT_THREAD_ERROR) {
+ return FLB_FALSE;;
+ }
+
+ return -1;
+}
+
+/* Wait for an input thread instance to become ready, or a failure status */
+int flb_input_thread_wait_until_is_ready(struct flb_input_instance *ins)
+{
+ uint64_t status = 0;
+ size_t bytes;
+ struct flb_input_thread_instance *thi;
+
+ thi = ins->thi;
+
+ bytes = read(thi->ch_parent_events[0], &status, sizeof(uint64_t));
+ if (bytes <= 0) {
+ flb_errno();
+ return -1;
+ }
+
+ if (status == 0) {
+ return -1;
+ }
+
+ return FLB_TRUE;
+}
+
+
+/*
+ * Invoked from the main 'input' interface to signal the threaded plugin instance so
+ * it can start the collectors.
+ */
+int flb_input_thread_collectors_signal_start(struct flb_input_instance *ins)
+{
+ int ret;
+ uint64_t val;
+ struct flb_input_thread_instance *thi;
+
+ thi = ins->thi;
+
+ /* compose message */
+ val = FLB_BITS_U64_SET(FLB_INPUT_THREAD_TO_THREAD,
+ FLB_INPUT_THREAD_START_COLLECTORS);
+
+ ret = flb_pipe_w(thi->ch_parent_events[1], &val, sizeof(uint64_t));
+ if (ret <= 0) {
+ flb_errno();
+ return -1;
+ }
+
+ return 0;
+}
+
+int flb_input_thread_collectors_signal_wait(struct flb_input_instance *ins)
+{
+ size_t bytes;
+ uint32_t type;
+ uint32_t op;
+ uint64_t val = 0;
+ struct flb_input_thread_instance *thi;
+
+ thi = ins->thi;
+ bytes = flb_pipe_r(thi->ch_parent_events[0], &val, sizeof(uint64_t));
+ if (bytes <= 0) {
+ flb_errno();
+ return -1;
+ }
+
+ /* Get type and status */
+ type = FLB_BITS_U64_HIGH(val);
+ op = FLB_BITS_U64_LOW(val);
+
+ if (type != FLB_INPUT_THREAD_TO_THREAD || op != FLB_INPUT_THREAD_START_COLLECTORS) {
+ flb_plg_error(ins, "wrong event, type=%i op=%i\n", type, op); fflush(stdout);
+ return -1;
+ }
+
+ return 0;
+}
+
+int flb_input_thread_collectors_start(struct flb_input_instance *ins)
+{
+ int ret;
+ struct mk_list *head;
+ struct flb_input_collector *coll;
+
+ mk_list_foreach(head, &ins->collectors) {
+ coll = mk_list_entry(head, struct flb_input_collector, _head);
+ ret = flb_input_collector_start(coll->id, ins);
+ if (ret < 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
diff --git a/fluent-bit/src/flb_input_trace.c b/fluent-bit/src/flb_input_trace.c
new file mode 100644
index 000000000..ced8e9ee2
--- /dev/null
+++ b/fluent-bit/src/flb_input_trace.c
@@ -0,0 +1,102 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_input_chunk.h>
+#include <fluent-bit/flb_input_trace.h>
+#include <fluent-bit/flb_input_plugin.h>
+
+#include <ctraces/ctraces.h>
+#include <ctraces/ctr_decode_msgpack.h>
+
+static int input_trace_append(struct flb_input_instance *ins,
+ size_t processor_starting_stage,
+ const char *tag, size_t tag_len,
+ struct ctrace *ctr)
+{
+ int ret;
+ char *out_buf;
+ size_t out_size;
+ int processor_is_active;
+
+ processor_is_active = flb_processor_is_active(ins->processor);
+ if (processor_is_active) {
+ if (!tag) {
+ if (ins->tag && ins->tag_len > 0) {
+ tag = ins->tag;
+ tag_len = ins->tag_len;
+ }
+ else {
+ tag = ins->name;
+ tag_len = strlen(ins->name);
+ }
+ }
+
+ ret = flb_processor_run(ins->processor,
+ processor_starting_stage,
+ FLB_PROCESSOR_TRACES,
+ tag, tag_len,
+ (char *) ctr,
+ 0, NULL, NULL);
+
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ /* Convert trace context to msgpack */
+ ret = ctr_encode_msgpack_create(ctr, &out_buf, &out_size);
+ if (ret != 0) {
+ flb_plg_error(ins, "could not encode traces");
+ return -1;
+ }
+
+ /* Append packed metrics */
+ ret = flb_input_chunk_append_raw(ins, FLB_INPUT_TRACES, 0,
+ tag, tag_len, out_buf, out_size);
+
+ ctr_encode_msgpack_destroy(out_buf);
+
+ return ret;
+}
+
+/* Take a CTrace context and enqueue it as a Trace chunk */
+int flb_input_trace_append(struct flb_input_instance *ins,
+ const char *tag, size_t tag_len,
+ struct ctrace *ctr)
+{
+ return input_trace_append(ins,
+ 0,
+ tag, tag_len,
+ ctr);
+}
+
+/* Take a CTrace context and enqueue it as a Trace chunk */
+int flb_input_trace_append_skip_processor_stages(
+ struct flb_input_instance *ins,
+ size_t processor_starting_stage,
+ const char *tag, size_t tag_len,
+ struct ctrace *ctr)
+{
+ return input_trace_append(ins,
+ processor_starting_stage,
+ tag, tag_len,
+ ctr);
+}
diff --git a/fluent-bit/src/flb_io.c b/fluent-bit/src/flb_io.c
new file mode 100644
index 000000000..81303459c
--- /dev/null
+++ b/fluent-bit/src/flb_io.c
@@ -0,0 +1,749 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+/*
+ * FLB_IO
+ * ======
+ * This interface is used by the output plugins which needs to write over
+ * the network in plain communication or through the TLS support. When dealing
+ * with network operation there are a few things to keep in mind:
+ *
+ * - TCP hosts can be down.
+ * - Network can be slow.
+ * - If the amount of data to flush requires multiple 'write' operations, we
+ * should not block the main thread, instead use event-driven mechanism to
+ * write when is possible.
+ *
+ * Output plugins that flag themselves with FLB_OUTPUT_TCP or FLB_OUTPUT_TLS
+ * can take advantage of this interface.
+ *
+ * The workflow to use this is the following:
+ *
+ * - A connection and data flow requires an flb_io_upstream context.
+ * - We write/read data through the flb_io_write()/flb_io_read() interfaces.
+ *
+ * Note that Upstreams context may define how network operations will work,
+ * basically synchronous or asynchronous (non-blocking).
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <assert.h>
+
+#include <monkey/mk_core.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_io.h>
+#include <fluent-bit/tls/flb_tls.h>
+#include <fluent-bit/flb_socket.h>
+#include <fluent-bit/flb_upstream.h>
+#include <fluent-bit/flb_downstream.h>
+
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_macros.h>
+#include <fluent-bit/flb_network.h>
+#include <fluent-bit/flb_engine.h>
+#include <fluent-bit/flb_coro.h>
+#include <fluent-bit/flb_http_client.h>
+
+int flb_io_net_accept(struct flb_connection *connection,
+ struct flb_coro *coro)
+{
+ int ret;
+
+ if (connection->fd != FLB_INVALID_SOCKET) {
+ flb_socket_close(connection->fd);
+
+ connection->fd = FLB_INVALID_SOCKET;
+ connection->event.fd = FLB_INVALID_SOCKET;
+ }
+
+ /* Accept the new connection */
+ connection->fd = flb_net_accept(connection->downstream->server_fd);
+
+ if (connection->fd == -1) {
+ connection->fd = FLB_INVALID_SOCKET;
+
+ return -1;
+ }
+
+#ifdef FLB_HAVE_TLS
+ /* Check if TLS was enabled, if so perform the handshake */
+ if (flb_stream_is_secure(connection->stream) &&
+ connection->stream->tls_context != NULL) {
+ ret = flb_tls_session_create(connection->stream->tls_context,
+ connection,
+ coro);
+
+ if (ret != 0) {
+ return -1;
+ }
+ }
+#endif
+
+ flb_trace("[io] connection OK");
+
+ return 0;
+}
+
+int flb_io_net_connect(struct flb_connection *connection,
+ struct flb_coro *coro)
+{
+ int ret;
+ int async = FLB_FALSE;
+ flb_sockfd_t fd = -1;
+ // struct flb_upstream *u = u_conn->u;
+
+ if (connection->fd > 0) {
+ flb_socket_close(connection->fd);
+
+ connection->fd = -1;
+ connection->event.fd = -1;
+ }
+
+ /* Check which connection mode must be done */
+ if (coro) {
+ async = flb_upstream_is_async(connection->upstream);
+ }
+ else {
+ async = FLB_FALSE;
+ }
+
+ /* Perform TCP connection */
+ fd = flb_net_tcp_connect(connection->upstream->tcp_host,
+ connection->upstream->tcp_port,
+ connection->stream->net.source_address,
+ connection->stream->net.connect_timeout,
+ async, coro, connection);
+ if (fd == -1) {
+ return -1;
+ }
+
+ if (connection->upstream->proxied_host) {
+ ret = flb_http_client_proxy_connect(connection);
+
+ if (ret == -1) {
+ flb_debug("[http_client] flb_http_client_proxy_connect connection #%i failed to %s:%i.",
+ connection->fd,
+ connection->upstream->tcp_host,
+ connection->upstream->tcp_port);
+
+ flb_socket_close(fd);
+
+ return -1;
+ }
+ flb_debug("[http_client] flb_http_client_proxy_connect connection #%i connected to %s:%i.",
+ connection->fd,
+ connection->upstream->tcp_host,
+ connection->upstream->tcp_port);
+ }
+
+#ifdef FLB_HAVE_TLS
+ /* Check if TLS was enabled, if so perform the handshake */
+ if (flb_stream_is_secure(connection->stream) &&
+ connection->stream->tls_context != NULL) {
+ ret = flb_tls_session_create(connection->stream->tls_context,
+ connection,
+ coro);
+
+ if (ret != 0) {
+ return -1;
+ }
+ }
+#endif
+
+ flb_trace("[io] connection OK");
+
+ return 0;
+}
+
+static void net_io_propagate_critical_error(
+ struct flb_connection *connection)
+{
+ switch (errno) {
+ case EBADF:
+ case ECONNRESET:
+ case EDESTADDRREQ:
+ case ENOTCONN:
+ case EPIPE:
+ case EACCES:
+ case ENOTTY:
+ case ENETDOWN:
+ case ENETUNREACH:
+ connection->net_error = errno;
+ }
+}
+
+static int fd_io_write(int fd, struct sockaddr_storage *address,
+ const void *data, size_t len, size_t *out_len);
+static int net_io_write(struct flb_connection *connection,
+ const void *data, size_t len, size_t *out_len)
+{
+ struct sockaddr_storage *address;
+ int ret;
+
+ if (connection->fd <= 0) {
+ if (connection->type != FLB_UPSTREAM_CONNECTION) {
+ return -1;
+ }
+
+ ret = flb_io_net_connect((struct flb_connection *) connection,
+ flb_coro_get());
+
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ address = NULL;
+
+ if (connection->type == FLB_DOWNSTREAM_CONNECTION) {
+ if (connection->stream->transport == FLB_TRANSPORT_UDP ||
+ connection->stream->transport == FLB_TRANSPORT_UNIX_DGRAM) {
+ address = &connection->raw_remote_host;
+ }
+ }
+
+ ret = fd_io_write(connection->fd, address, data, len, out_len);
+
+ if (ret == -1) {
+ net_io_propagate_critical_error(connection);
+ }
+
+ return ret;
+}
+
+static int fd_io_write(int fd, struct sockaddr_storage *address,
+ const void *data, size_t len, size_t *out_len)
+{
+ int ret;
+ int tries = 0;
+ size_t total = 0;
+
+ while (total < len) {
+ if (address != NULL) {
+ ret = sendto(fd, (char *) data + total, len - total, 0,
+ (struct sockaddr *) address,
+ flb_network_address_size(address));
+ }
+ else {
+ ret = send(fd, (char *) data + total, len - total, 0);
+ }
+
+ if (ret == -1) {
+ if (FLB_WOULDBLOCK()) {
+ /*
+ * FIXME: for now we are handling this in a very lazy way,
+ * just sleep for a second and retry (for a max of 30 tries).
+ */
+ sleep(1);
+ tries++;
+
+ if (tries == 30) {
+ /* Since we're aborting after 30 failures we want the
+ * caller to know how much data we were able to send
+ */
+
+ *out_len = total;
+
+ return -1;
+ }
+
+ continue;
+ }
+
+ return -1;
+ }
+
+ tries = 0;
+ total += ret;
+ }
+
+ *out_len = total;
+
+ return total;
+}
+
+static FLB_INLINE void net_io_backup_event(struct flb_connection *connection,
+ struct mk_event *backup)
+{
+ if (connection != NULL && backup != NULL) {
+ memcpy(backup, &connection->event, sizeof(struct mk_event));
+ }
+}
+
+static FLB_INLINE void net_io_restore_event(struct flb_connection *connection,
+ struct mk_event *backup)
+{
+ int result;
+
+ if (connection != NULL && backup != NULL) {
+ if (MK_EVENT_IS_REGISTERED((&connection->event))) {
+ result = mk_event_del(connection->evl, &connection->event);
+
+ assert(result == 0);
+ }
+
+ if (MK_EVENT_IS_REGISTERED(backup)) {
+ connection->event.priority = backup->priority;
+ connection->event.handler = backup->handler;
+
+ result = mk_event_add(connection->evl,
+ connection->fd,
+ backup->type,
+ backup->mask,
+ &connection->event);
+
+ assert(result == 0);
+ }
+ }
+}
+
+/*
+ * Perform Async socket write(2) operations. This function depends on a main
+ * event-loop and the co-routines interface to yield/resume once sockets are
+ * ready to continue.
+ *
+ * Intentionally we register/de-register the socket file descriptor from
+ * the event loop each time when we require to do some work.
+ */
+static FLB_INLINE int net_io_write_async(struct flb_coro *co,
+ struct flb_connection *connection,
+ const void *data, size_t len, size_t *out_len)
+{
+ int ret = 0;
+ int error;
+ uint32_t mask;
+ ssize_t bytes;
+ size_t total = 0;
+ size_t to_send;
+ char so_error_buf[256];
+ struct mk_event event_backup;
+ int event_restore_needed;
+
+ event_restore_needed = FLB_FALSE;
+
+ net_io_backup_event(connection, &event_backup);
+
+retry:
+ error = 0;
+
+ if (len - total > 524288) {
+ to_send = 524288;
+ }
+ else {
+ to_send = (len - total);
+ }
+
+ bytes = send(connection->fd, (char *) data + total, to_send, 0);
+
+#ifdef FLB_HAVE_TRACE
+ if (bytes > 0) {
+ flb_trace("[io coro=%p] [fd %i] write_async(2)=%d (%lu/%lu)",
+ co, connection->fd, bytes, total + bytes, len);
+ }
+ else {
+ flb_trace("[io coro=%p] [fd %i] write_async(2)=%d (%lu/%lu)",
+ co, connection->fd, bytes, total, len);
+ }
+#endif
+
+ if (bytes == -1) {
+ if (FLB_WOULDBLOCK()) {
+ event_restore_needed = FLB_TRUE;
+
+ ret = mk_event_add(connection->evl,
+ connection->fd,
+ FLB_ENGINE_EV_THREAD,
+ MK_EVENT_WRITE,
+ &connection->event);
+
+ connection->event.priority = FLB_ENGINE_PRIORITY_SEND_RECV;
+
+ if (ret == -1) {
+ /*
+ * If we failed here there no much that we can do, just
+ * let the caller we failed
+ */
+ *out_len = total;
+
+ net_io_restore_event(connection, &event_backup);
+
+ return -1;
+ }
+
+ connection->coroutine = co;
+
+ /*
+ * Return the control to the parent caller, we need to wait for
+ * the event loop to get back to us.
+ */
+ flb_coro_yield(co, FLB_FALSE);
+
+ /* We want this field to hold NULL at all times unless we are explicitly
+ * waiting to be resumed.
+ */
+ connection->coroutine = NULL;
+
+ /* Save events mask since mk_event_del() will reset it */
+ mask = connection->event.mask;
+
+ /* We got a notification, remove the event registered */
+ ret = mk_event_del(connection->evl, &connection->event);
+
+ if (ret == -1) {
+ *out_len = total;
+
+ net_io_restore_event(connection, &event_backup);
+
+ return -1;
+ }
+
+ /* Check the connection status */
+ if (mask & MK_EVENT_WRITE) {
+ error = flb_socket_error(connection->fd);
+
+ if (error != 0) {
+ /* Connection is broken, not much to do here */
+ strerror_r(error, so_error_buf, sizeof(so_error_buf) - 1);
+
+ flb_error("[io fd=%i] error sending data to: %s (%s)",
+ connection->fd,
+ flb_connection_get_remote_address(connection),
+ so_error_buf);
+
+ *out_len = total;
+
+ net_io_restore_event(connection, &event_backup);
+
+ return -1;
+ }
+
+ MK_EVENT_NEW(&connection->event);
+
+ goto retry;
+ }
+ else {
+ *out_len = total;
+
+ net_io_restore_event(connection, &event_backup);
+
+ return -1;
+ }
+
+ }
+ else {
+ *out_len = total;
+
+ net_io_restore_event(connection, &event_backup);
+ net_io_propagate_critical_error(connection);
+
+ return -1;
+ }
+ }
+
+ /* Update counters */
+ total += bytes;
+ if (total < len) {
+ if ((connection->event.mask & MK_EVENT_WRITE) == 0) {
+ ret = mk_event_add(connection->evl,
+ connection->fd,
+ FLB_ENGINE_EV_THREAD,
+ MK_EVENT_WRITE,
+ &connection->event);
+
+ connection->event.priority = FLB_ENGINE_PRIORITY_SEND_RECV;
+
+ if (ret == -1) {
+ /*
+ * If we failed here there no much that we can do, just
+ * let the caller we failed
+ */
+ *out_len = total;
+
+ net_io_restore_event(connection, &event_backup);
+
+ return -1;
+ }
+ }
+
+ connection->coroutine = co;
+
+ flb_coro_yield(co, MK_FALSE);
+
+ /* We want this field to hold NULL at all times unless we are explicitly
+ * waiting to be resumed.
+ */
+ connection->coroutine = NULL;
+
+ goto retry;
+ }
+
+ if (event_restore_needed) {
+ /* If we enter here it means we registered this connection
+ * in the event loop, in which case we need to unregister it
+ * and restore the original registration if there was one.
+ *
+ * We do it conditionally because in those cases in which
+ * send succeeds on the first try we don't touch the event
+ * and it wouldn't make sense to unregister and register for
+ * the same event.
+ */
+
+ net_io_restore_event(connection, &event_backup);
+ }
+
+ *out_len = total;
+
+ return bytes;
+}
+
+static ssize_t fd_io_read(int fd, struct sockaddr_storage *address,
+ void *buf, size_t len);
+static ssize_t net_io_read(struct flb_connection *connection,
+ void *buf, size_t len)
+{
+ struct sockaddr_storage *address;
+ int ret;
+
+ address = NULL;
+
+ if (connection->type == FLB_DOWNSTREAM_CONNECTION) {
+ if (connection->stream->transport == FLB_TRANSPORT_UDP ||
+ connection->stream->transport == FLB_TRANSPORT_UNIX_DGRAM) {
+ address = &connection->raw_remote_host;
+ }
+ }
+
+ ret = fd_io_read(connection->fd, address, buf, len);
+
+ if (ret == -1) {
+ ret = FLB_WOULDBLOCK();
+ if (ret) {
+ /* timeout caused error */
+ flb_warn("[net] sync io_read #%i timeout after %i seconds from: %s",
+ connection->fd,
+ connection->net->io_timeout,
+ flb_connection_get_remote_address(connection));
+ }
+ else {
+ net_io_propagate_critical_error(connection);
+ }
+
+ return -1;
+ }
+
+ return ret;
+}
+
+static ssize_t fd_io_read(int fd, struct sockaddr_storage *address,
+ void *buf, size_t len)
+{
+ socklen_t address_size;
+ int ret;
+
+ if (address != NULL) {
+ address_size = sizeof(struct sockaddr_storage);
+ ret = recvfrom(fd, buf, len, 0,
+ (struct sockaddr *) address,
+ &address_size);
+ }
+ else {
+ ret = recv(fd, buf, len, 0);
+ }
+
+ if (ret == -1) {
+ return -1;
+ }
+
+ return ret;
+}
+
+static FLB_INLINE ssize_t net_io_read_async(struct flb_coro *co,
+ struct flb_connection *connection,
+ void *buf, size_t len)
+{
+ struct mk_event event_backup;
+ int event_restore_needed;
+ int ret;
+
+ event_restore_needed = FLB_FALSE;
+
+ net_io_backup_event(connection, &event_backup);
+
+ retry_read:
+ ret = recv(connection->fd, buf, len, 0);
+
+ if (ret == -1) {
+ if (FLB_WOULDBLOCK()) {
+ event_restore_needed = FLB_TRUE;
+
+ ret = mk_event_add(connection->evl,
+ connection->fd,
+ FLB_ENGINE_EV_THREAD,
+ MK_EVENT_READ,
+ &connection->event);
+
+ connection->event.priority = FLB_ENGINE_PRIORITY_SEND_RECV;
+
+ if (ret == -1) {
+ /*
+ * If we failed here there no much that we can do, just
+ * let the caller we failed
+ */
+ net_io_restore_event(connection, &event_backup);
+
+ return -1;
+ }
+
+ connection->coroutine = co;
+
+ flb_coro_yield(co, MK_FALSE);
+
+ /* We want this field to hold NULL at all times unless we are explicitly
+ * waiting to be resumed.
+ */
+ connection->coroutine = NULL;
+
+ goto retry_read;
+ }
+ else {
+ net_io_propagate_critical_error(connection);
+ }
+
+ ret = -1;
+ }
+ else if (ret <= 0) {
+ ret = -1;
+ }
+
+ if (event_restore_needed) {
+ /* If we enter here it means we registered this connection
+ * in the event loop, in which case we need to unregister it
+ * and restore the original registration if there was one.
+ *
+ * We do it conditionally because in those cases in which
+ * send succeeds on the first try we don't touch the event
+ * and it wouldn't make sense to unregister and register for
+ * the same event.
+ */
+
+ net_io_restore_event(connection, &event_backup);
+ }
+
+ return ret;
+}
+
+/* Write data to fd. For unix socket. */
+int flb_io_fd_write(int fd, const void *data, size_t len, size_t *out_len)
+{
+ /* TODO: support async mode */
+ return fd_io_write(fd, NULL, data, len, out_len);
+}
+
+/* Write data to an upstream connection/server */
+int flb_io_net_write(struct flb_connection *connection, const void *data,
+ size_t len, size_t *out_len)
+{
+ int flags;
+ struct flb_coro *coro;
+ int ret;
+
+ ret = -1;
+ coro = flb_coro_get();
+ flags = flb_connection_get_flags(connection);
+
+ flb_trace("[io coro=%p] [net_write] trying %zd bytes", coro, len);
+
+ if (connection->tls_session == NULL) {
+ if (flags & FLB_IO_ASYNC) {
+ ret = net_io_write_async(coro, connection, data, len, out_len);
+ }
+ else {
+ ret = net_io_write(connection, data, len, out_len);
+ }
+ }
+#ifdef FLB_HAVE_TLS
+ else if (flags & FLB_IO_TLS) {
+ if (flags & FLB_IO_ASYNC) {
+ ret = flb_tls_net_write_async(coro, connection->tls_session, data, len, out_len);
+ }
+ else {
+ ret = flb_tls_net_write(connection->tls_session, data, len, out_len);
+ }
+ }
+#endif
+
+ if (ret > 0) {
+ flb_connection_reset_io_timeout(connection);
+ }
+
+ flb_trace("[io coro=%p] [net_write] ret=%i total=%lu/%lu",
+ coro, ret, *out_len, len);
+
+ return ret;
+}
+
+ssize_t flb_io_fd_read(int fd, void *buf, size_t len)
+{
+ /* TODO: support async mode */
+ return fd_io_read(fd, NULL, buf, len);
+}
+
+ssize_t flb_io_net_read(struct flb_connection *connection, void *buf, size_t len)
+{
+ int ret;
+ int flags;
+ struct flb_coro *coro;
+
+ ret = -1;
+ coro = flb_coro_get();
+
+ flb_trace("[io coro=%p] [net_read] try up to %zd bytes", coro, len);
+
+ flags = flb_connection_get_flags(connection);
+
+ if (!connection->tls_session) {
+ if (flags & FLB_IO_ASYNC) {
+ ret = net_io_read_async(coro, connection, buf, len);
+ }
+ else {
+ ret = net_io_read(connection, buf, len);
+ }
+ }
+#ifdef FLB_HAVE_TLS
+ else if (flags & FLB_IO_TLS) {
+ if (flags & FLB_IO_ASYNC) {
+ ret = flb_tls_net_read_async(coro, connection->tls_session, buf, len);
+ }
+ else {
+ ret = flb_tls_net_read(connection->tls_session, buf, len);
+ }
+ }
+#endif
+
+ if (ret > 0) {
+ flb_connection_reset_io_timeout(connection);
+ }
+
+ flb_trace("[io coro=%p] [net_read] ret=%i", coro, ret);
+
+ return ret;
+}
diff --git a/fluent-bit/src/flb_kafka.c b/fluent-bit/src/flb_kafka.c
new file mode 100644
index 000000000..a3b69a9c8
--- /dev/null
+++ b/fluent-bit/src/flb_kafka.c
@@ -0,0 +1,216 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2019-2021 The Fluent Bit Authors
+ * Copyright (C) 2015-2018 Treasure Data Inc.
+ *
+ * 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 "fluent-bit/flb_config.h"
+#include "fluent-bit/flb_mem.h"
+#include "fluent-bit/flb_str.h"
+#include "fluent-bit/flb_utils.h"
+#include "monkey/mk_core/mk_list.h"
+#include <fluent-bit/flb_kafka.h>
+#include <fluent-bit/flb_kv.h>
+
+#include <rdkafka.h>
+
+rd_kafka_conf_t *flb_kafka_conf_create(struct flb_kafka *kafka,
+ struct mk_list *properties,
+ int with_group_id)
+{
+ struct mk_list *head;
+ struct flb_kv *kv;
+ const char *conf;
+ rd_kafka_conf_t *kafka_cfg;
+ char errstr[512];
+
+ kafka_cfg = rd_kafka_conf_new();
+ if (!kafka_cfg) {
+ flb_error("[flb_kafka] Could not initialize kafka config object");
+ goto err;
+ }
+
+ conf = flb_config_prop_get("client_id", properties);
+ if (!conf) {
+ conf = "fluent-bit";
+ }
+ if (rd_kafka_conf_set(kafka_cfg, "client.id", conf,
+ errstr, sizeof(errstr)) != RD_KAFKA_CONF_OK) {
+ flb_error("[flb_kafka] cannot configure client id: %s", errstr);
+ }
+
+ if (with_group_id) {
+ conf = flb_config_prop_get("group_id", properties);
+ if (!conf) {
+ conf = "fluent-bit";
+ }
+ if (rd_kafka_conf_set(kafka_cfg, "group.id", conf,
+ errstr, sizeof(errstr)) != RD_KAFKA_CONF_OK) {
+ flb_error("[flb_kafka] cannot configure group id: %s", errstr);
+ }
+ }
+
+ conf = flb_config_prop_get("brokers", properties);
+ if (conf) {
+ if (rd_kafka_conf_set(kafka_cfg, "bootstrap.servers", conf,
+ errstr, sizeof(errstr)) != RD_KAFKA_CONF_OK) {
+ flb_error("[flb_kafka] failed to configure brokers: %s", errstr);
+ goto err;
+ }
+ kafka->brokers = flb_strdup(conf);
+ }
+ else {
+ flb_error("config: no brokers defined");
+ goto err;
+ }
+
+ /* Iterate custom rdkafka properties */
+ mk_list_foreach(head, properties) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ if (strncasecmp(kv->key, "rdkafka.", 8) == 0 &&
+ flb_sds_len(kv->key) > 8) {
+ if (rd_kafka_conf_set(kafka_cfg, kv->key + 8, kv->val,
+ errstr, sizeof(errstr)) != RD_KAFKA_CONF_OK) {
+ flb_error("[flb_kafka] cannot configure '%s' property", kv->key + 8);
+ }
+ }
+ }
+
+ return kafka_cfg;
+
+err:
+ if (kafka_cfg) {
+ flb_free(kafka_cfg);
+ }
+ return NULL;
+}
+
+static int add_topic_partitions(rd_kafka_topic_partition_list_t *list,
+ const char *topic_str,
+ const char *partitions_str)
+{
+ int ret = -1;
+ struct mk_list *split;
+ char *str, *end;
+ int start, stop;
+ size_t len;
+ split = flb_utils_split(partitions_str, '-', -1);
+ if (!split) {
+ flb_error("[flb_kafka] Failed to split partitions string");
+ goto end;
+ }
+
+ len = mk_list_size(split);
+ if (len == 1) {
+ str = mk_list_entry(split->next, struct flb_split_entry, _head)->value;
+ start = strtol(str, &end, 10);
+ if (end == str || *end != '\0') {
+ flb_error("[flb_kafka] invalid partition \"%s\"", str);
+ goto end;
+ }
+ // single partition
+ rd_kafka_topic_partition_list_add(list, topic_str, start);
+ } else if (len == 2) {
+ str = mk_list_entry(split->next, struct flb_split_entry, _head)->value;
+ start = strtol(str, &end, 10);
+ if (end == str || *end != '\0') {
+ flb_error("[flb_kafka] invalid partition \"%s\"", str);
+ goto end;
+ }
+ str = mk_list_entry(split->next->next, struct flb_split_entry, _head)->value;
+ stop = strtol(str, &end, 10);
+ if (end == str || *end != '\0') {
+ flb_error("[flb_kafka] invalid partition \"%s\"", str);
+ goto end;
+ }
+ rd_kafka_topic_partition_list_add_range(list, topic_str, start, stop);
+ } else {
+ flb_error("[flb_kafka] invalid partition range string \"%s\"", partitions_str);
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ if (split) {
+ flb_utils_split_free(split);
+ }
+ return ret;
+}
+
+rd_kafka_topic_partition_list_t *flb_kafka_parse_topics(const char *topics_str)
+{
+ rd_kafka_topic_partition_list_t *ret;
+ struct mk_list *split = NULL;
+ struct mk_list *partitions = NULL;
+ struct mk_list *curr;
+ struct flb_split_entry *entry;
+ struct flb_split_entry *topic_entry;
+ struct flb_split_entry *partitions_entry;
+ size_t len;
+
+ ret = rd_kafka_topic_partition_list_new(1);
+ if (!ret) {
+ flb_error("[flb_kafka] Failed to allocate topic list");
+ goto err;
+ }
+
+ split = flb_utils_split(topics_str, ',', -1);
+ if (!split) {
+ flb_error("[flb_kafka] Failed to split topics string");
+ goto err;
+ }
+
+ mk_list_foreach(curr, split) {
+ entry = mk_list_entry(curr, struct flb_split_entry, _head);
+ partitions = flb_utils_split(entry->value, ':', -1);
+ if (!partitions) {
+ flb_error("[flb_kafka] Failed to split topic string");
+ goto err;
+ }
+ len = mk_list_size(partitions);
+ if (len == 1) {
+ rd_kafka_topic_partition_list_add(ret, entry->value, 0);
+ } else if (len == 2) {
+ topic_entry = mk_list_entry(
+ partitions->next, struct flb_split_entry, _head);
+ partitions_entry = mk_list_entry(
+ partitions->next->next, struct flb_split_entry, _head);
+ if (add_topic_partitions(ret, topic_entry->value, partitions_entry->value)) {
+ goto err;
+ }
+ } else {
+ flb_error("[flb_kafka] Failed to parse topic/partition string");
+ goto err;
+ }
+ flb_utils_split_free(partitions);
+ }
+ flb_utils_split_free(split);
+ return ret;
+
+err:
+ if (ret) {
+ rd_kafka_topic_partition_list_destroy(ret);
+ }
+ if (split) {
+ flb_utils_split_free(split);
+ }
+ if (partitions) {
+ flb_utils_split_free(partitions);
+ }
+ return NULL;
+}
diff --git a/fluent-bit/src/flb_kernel.c b/fluent-bit/src/flb_kernel.c
new file mode 100644
index 000000000..d29ba922f
--- /dev/null
+++ b/fluent-bit/src/flb_kernel.c
@@ -0,0 +1,161 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_kernel.h>
+#include <fluent-bit/flb_utils.h>
+
+#ifdef _WIN32
+
+/* Dummy function for Windows environment */
+struct flb_kernel *flb_kernel_info()
+{
+ int len;
+ struct flb_kernel *kernel;
+
+ kernel = flb_malloc(sizeof(struct flb_kernel));
+ if (!kernel) {
+ flb_errno();
+ return NULL;
+ }
+
+ kernel->minor = 0;
+ kernel->major = 0;
+ kernel->patch = 0;
+ kernel->s_version.data = flb_malloc(16);
+
+ if (!kernel->s_version.data) {
+ flb_errno();
+ flb_free(kernel);
+ return NULL;
+ }
+
+
+ len = snprintf(kernel->s_version.data, 16, "0.0.0");
+ if (len == -1) {
+ perror("snprintf");
+ return NULL;
+ }
+ kernel->s_version.len = len;
+ kernel->n_version = 0;
+
+ return kernel;
+}
+#else
+
+#include <ctype.h>
+#include <sys/utsname.h>
+
+/*
+ * Routine taken from Monkey Project, Eduardo says it's ok ;)
+ */
+struct flb_kernel *flb_kernel_info()
+{
+
+ int a, b, c;
+ int len;
+ int pos;
+ char *p, *t;
+ char *tmp;
+ struct utsname uts;
+ struct flb_kernel *kernel;
+
+ if (uname(&uts) == -1) {
+ flb_errno();
+ return NULL;
+ }
+ len = strlen(uts.release);
+
+ /* Fixme: this don't support Linux Kernel 10.x.x :P */
+ a = (*uts.release - '0');
+
+ /* Second number */
+ p = (uts.release) + 2;
+ pos = mk_string_char_search(p, '.', len - 2);
+ if (pos <= 0) {
+ /* Some Debian systems uses a different notation, e.g: 3.14-2-amd64 */
+ pos = mk_string_char_search(p, '-', len - 2);
+ if (pos <= 0) {
+ return NULL;
+ }
+ }
+
+ tmp = mk_string_copy_substr(p, 0, pos);
+ if (!tmp) {
+ return NULL;
+ }
+ b = atoi(tmp);
+ mk_mem_free(tmp);
+
+ /* Last number (it needs filtering) */
+ t = p = p + pos + 1;
+ do {
+ t++;
+ } while (isdigit(*t));
+
+ tmp = mk_string_copy_substr(p, 0, t - p);
+ if (!tmp) {
+ return NULL;
+ }
+ c = atoi(tmp);
+ mk_mem_free(tmp);
+
+ kernel = flb_malloc(sizeof(struct flb_kernel));
+ if (!kernel) {
+ flb_errno();
+ return NULL;
+ }
+ kernel->minor = a;
+ kernel->major = b;
+ kernel->patch = c;
+ kernel->s_version.data = flb_malloc(16);
+
+ if (!kernel->s_version.data) {
+ flb_errno();
+ flb_free(kernel);
+ return NULL;
+ }
+
+ len = snprintf(kernel->s_version.data, 16, "%i.%i.%i", a, b, c);
+ if (len == -1) {
+ flb_errno();
+ flb_free(kernel->s_version.data);
+ flb_free(kernel);
+ return NULL;
+ }
+ kernel->s_version.len = len;
+ kernel->n_version = FLB_KERNEL_VERSION(a, b, c);
+
+ return kernel;
+}
+
+#endif
+
+void flb_kernel_destroy(struct flb_kernel *kernel)
+{
+ if (kernel == NULL) {
+ return;
+ }
+
+ if (kernel->s_version.data) {
+ flb_free(kernel->s_version.data);
+ }
+ flb_free(kernel);
+}
diff --git a/fluent-bit/src/flb_kv.c b/fluent-bit/src/flb_kv.c
new file mode 100644
index 000000000..9be276685
--- /dev/null
+++ b/fluent-bit/src/flb_kv.c
@@ -0,0 +1,132 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+
+void flb_kv_init(struct mk_list *list)
+{
+ mk_list_init(list);
+}
+
+struct flb_kv *flb_kv_item_create_len(struct mk_list *list,
+ char *k_buf, size_t k_len,
+ char *v_buf, size_t v_len)
+{
+ struct flb_kv *kv;
+
+ kv = flb_calloc(1, sizeof(struct flb_kv));
+ if (!kv) {
+ flb_errno();
+ return NULL;
+ }
+
+ kv->key = flb_sds_create_len(k_buf, k_len);
+ if (!kv->key) {
+ flb_free(kv);
+ return NULL;
+ }
+
+ if (v_len > 0) {
+ kv->val = flb_sds_create_len(v_buf, v_len);
+ if (!kv->val) {
+ flb_sds_destroy(kv->key);
+ flb_free(kv);
+ return NULL;
+ }
+ }
+
+ mk_list_add(&kv->_head, list);
+ return kv;
+}
+
+struct flb_kv *flb_kv_item_create(struct mk_list *list,
+ char *k_buf, char *v_buf)
+{
+ int k_len;
+ int v_len = 0;
+
+ if (!k_buf) {
+ return NULL;
+ }
+ k_len = strlen(k_buf);
+
+ if (v_buf) {
+ v_len = strlen(v_buf);
+ }
+
+ return flb_kv_item_create_len(list, k_buf, k_len, v_buf, v_len);
+}
+
+void flb_kv_item_destroy(struct flb_kv *kv)
+{
+ if (kv->key) {
+ flb_sds_destroy(kv->key);
+ }
+
+ if (kv->val) {
+ flb_sds_destroy(kv->val);
+ }
+
+ mk_list_del(&kv->_head);
+ flb_free(kv);
+}
+
+void flb_kv_release(struct mk_list *list)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_kv *kv;
+
+ mk_list_foreach_safe(head, tmp, list) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ flb_kv_item_destroy(kv);
+ }
+}
+
+const char *flb_kv_get_key_value(const char *key, struct mk_list *list)
+{
+ int len;
+ struct mk_list *head;
+ struct flb_kv *kv;
+
+ if (!key) {
+ return NULL;
+ }
+
+ len = strlen(key);
+ if (len == 0) {
+ return NULL;
+ }
+
+ mk_list_foreach(head, list) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ if (flb_sds_len(kv->key) != len) {
+ continue;
+ }
+
+ if (strncasecmp(kv->key, key, len) == 0) {
+ return kv->val;
+ }
+ }
+
+ return NULL;
+}
diff --git a/fluent-bit/src/flb_lib.c b/fluent-bit/src/flb_lib.c
new file mode 100644
index 000000000..882faa547
--- /dev/null
+++ b/fluent-bit/src/flb_lib.c
@@ -0,0 +1,815 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit Demo
+ * ===============
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_lib.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_pipe.h>
+#include <fluent-bit/flb_engine.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_filter.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_time.h>
+#include <fluent-bit/flb_coro.h>
+#include <fluent-bit/flb_callback.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_metrics.h>
+#include <fluent-bit/flb_upstream.h>
+#include <fluent-bit/flb_downstream.h>
+#include <fluent-bit/tls/flb_tls.h>
+
+#include <signal.h>
+#include <stdarg.h>
+
+#ifdef FLB_HAVE_MTRACE
+#include <mcheck.h>
+#endif
+
+#ifdef FLB_HAVE_AWS_ERROR_REPORTER
+#include <fluent-bit/aws/flb_aws_error_reporter.h>
+
+struct flb_aws_error_reporter *error_reporter;
+#endif
+
+/* thread initializator */
+static pthread_once_t flb_lib_once = PTHREAD_ONCE_INIT;
+
+/* reference to the last 'flb_lib_ctx' context started through flb_start() */
+FLB_TLS_DEFINE(flb_ctx_t, flb_lib_active_context);
+
+/* reference to the last 'flb_cf' context started through flb_start() */
+FLB_TLS_DEFINE(struct flb_cf, flb_lib_active_cf_context);
+
+#ifdef FLB_SYSTEM_WINDOWS
+static inline int flb_socket_init_win32(void)
+{
+ WSADATA wsaData;
+ int err;
+
+ err = WSAStartup(MAKEWORD(2, 2), &wsaData);
+ if (err != 0) {
+ fprintf(stderr, "WSAStartup failed with error: %d\n", err);
+ return err;
+ }
+ return 0;
+}
+#endif
+
+static inline struct flb_input_instance *in_instance_get(flb_ctx_t *ctx,
+ int ffd)
+{
+ struct mk_list *head;
+ struct flb_input_instance *i_ins;
+
+ mk_list_foreach(head, &ctx->config->inputs) {
+ i_ins = mk_list_entry(head, struct flb_input_instance, _head);
+ if (i_ins->id == ffd) {
+ return i_ins;
+ }
+ }
+
+ return NULL;
+}
+
+static inline struct flb_output_instance *out_instance_get(flb_ctx_t *ctx,
+ int ffd)
+{
+ struct mk_list *head;
+ struct flb_output_instance *o_ins;
+
+ mk_list_foreach(head, &ctx->config->outputs) {
+ o_ins = mk_list_entry(head, struct flb_output_instance, _head);
+ if (o_ins->id == ffd) {
+ return o_ins;
+ }
+ }
+
+ return NULL;
+}
+
+static inline struct flb_filter_instance *filter_instance_get(flb_ctx_t *ctx,
+ int ffd)
+{
+ struct mk_list *head;
+ struct flb_filter_instance *f_ins;
+
+ mk_list_foreach(head, &ctx->config->filters) {
+ f_ins = mk_list_entry(head, struct flb_filter_instance, _head);
+ if (f_ins->id == ffd) {
+ return f_ins;
+ }
+ }
+
+ return NULL;
+}
+
+void flb_init_env()
+{
+ flb_tls_init();
+ flb_coro_init();
+ flb_upstream_init();
+ flb_downstream_init();
+ flb_output_prepare();
+
+ FLB_TLS_INIT(flb_lib_active_context);
+ FLB_TLS_INIT(flb_lib_active_cf_context);
+
+ /* libraries */
+ cmt_initialize();
+}
+
+flb_ctx_t *flb_create()
+{
+ int ret;
+ flb_ctx_t *ctx;
+ struct flb_config *config;
+
+#ifdef FLB_HAVE_MTRACE
+ /* Start tracing malloc and free */
+ mtrace();
+#endif
+
+#ifdef FLB_SYSTEM_WINDOWS
+ /* Ensure we initialized Windows Sockets */
+ if (flb_socket_init_win32()) {
+ return NULL;
+ }
+#endif
+
+ ctx = flb_calloc(1, sizeof(flb_ctx_t));
+ if (!ctx) {
+ perror("malloc");
+ return NULL;
+ }
+
+ config = flb_config_init();
+ if (!config) {
+ flb_free(ctx);
+ return NULL;
+ }
+ ctx->config = config;
+ ctx->status = FLB_LIB_NONE;
+
+ /*
+ * Initialize our pipe to send data to our worker, used
+ * by 'lib' input plugin.
+ */
+ ret = flb_pipe_create(config->ch_data);
+ if (ret == -1) {
+ perror("pipe");
+ flb_config_exit(ctx->config);
+ flb_free(ctx);
+ return NULL;
+ }
+
+ /* Create the event loop to receive notifications */
+ ctx->event_loop = mk_event_loop_create(256);
+ if (!ctx->event_loop) {
+ flb_config_exit(ctx->config);
+ flb_free(ctx);
+ return NULL;
+ }
+ config->ch_evl = ctx->event_loop;
+
+ /* Prepare the notification channels */
+ ctx->event_channel = flb_calloc(1, sizeof(struct mk_event));
+ if (!ctx->event_channel) {
+ perror("calloc");
+ flb_config_exit(ctx->config);
+ flb_free(ctx);
+ return NULL;
+ }
+
+ MK_EVENT_ZERO(ctx->event_channel);
+
+ ret = mk_event_channel_create(config->ch_evl,
+ &config->ch_notif[0],
+ &config->ch_notif[1],
+ ctx->event_channel);
+ if (ret != 0) {
+ flb_error("[lib] could not create notification channels");
+ flb_stop(ctx);
+ flb_destroy(ctx);
+ return NULL;
+ }
+
+ #ifdef FLB_HAVE_AWS_ERROR_REPORTER
+ if (is_error_reporting_enabled()) {
+ error_reporter = flb_aws_error_reporter_create();
+ }
+ #endif
+
+ return ctx;
+}
+
+/* Release resources associated to the library context */
+void flb_destroy(flb_ctx_t *ctx)
+{
+ if (!ctx) {
+ return;
+ }
+
+ if (ctx->event_channel) {
+ mk_event_del(ctx->event_loop, ctx->event_channel);
+ flb_free(ctx->event_channel);
+ }
+
+ /* Remove resources from the event loop */
+ mk_event_loop_destroy(ctx->event_loop);
+
+ /* cfg->is_running is set to false when flb_engine_shutdown has been invoked (event loop) */
+ if (ctx->config) {
+ if (ctx->config->is_running == FLB_TRUE) {
+ flb_engine_shutdown(ctx->config);
+ }
+ flb_config_exit(ctx->config);
+ }
+
+ #ifdef FLB_HAVE_AWS_ERROR_REPORTER
+ if (is_error_reporting_enabled()) {
+ flb_aws_error_reporter_destroy(error_reporter);
+ }
+ #endif
+
+ flb_free(ctx);
+ ctx = NULL;
+
+#ifdef FLB_HAVE_MTRACE
+ /* Stop tracing malloc and free */
+ muntrace();
+#endif
+}
+
+/* Defines a new input instance */
+int flb_input(flb_ctx_t *ctx, const char *input, void *data)
+{
+ struct flb_input_instance *i_ins;
+
+ i_ins = flb_input_new(ctx->config, input, data, FLB_TRUE);
+ if (!i_ins) {
+ return -1;
+ }
+
+ return i_ins->id;
+}
+
+/* Defines a new output instance */
+int flb_output(flb_ctx_t *ctx, const char *output, struct flb_lib_out_cb *cb)
+{
+ struct flb_output_instance *o_ins;
+
+ o_ins = flb_output_new(ctx->config, output, cb, FLB_TRUE);
+ if (!o_ins) {
+ return -1;
+ }
+
+ return o_ins->id;
+}
+
+/* Defines a new filter instance */
+int flb_filter(flb_ctx_t *ctx, const char *filter, void *data)
+{
+ struct flb_filter_instance *f_ins;
+
+ f_ins = flb_filter_new(ctx->config, filter, data);
+ if (!f_ins) {
+ return -1;
+ }
+
+ return f_ins->id;
+}
+
+/* Set an input interface property */
+int flb_input_set(flb_ctx_t *ctx, int ffd, ...)
+{
+ int ret;
+ char *key;
+ char *value;
+ va_list va;
+ struct flb_input_instance *i_ins;
+
+ i_ins = in_instance_get(ctx, ffd);
+ if (!i_ins) {
+ return -1;
+ }
+
+ va_start(va, ffd);
+ while ((key = va_arg(va, char *))) {
+ value = va_arg(va, char *);
+ if (!value) {
+ /* Wrong parameter */
+ va_end(va);
+ return -1;
+ }
+ ret = flb_input_set_property(i_ins, key, value);
+ if (ret != 0) {
+ va_end(va);
+ return -1;
+ }
+ }
+
+ va_end(va);
+ return 0;
+}
+
+static inline int flb_config_map_property_check(char *plugin_name, struct mk_list *config_map, char *key, char *val)
+{
+ struct flb_kv *kv;
+ struct mk_list properties;
+ int r;
+
+ mk_list_init(&properties);
+
+ kv = flb_kv_item_create(&properties, (char *) key, (char *) val);
+ if (!kv) {
+ return FLB_LIB_ERROR;
+ }
+
+ r = flb_config_map_properties_check(plugin_name, &properties, config_map);
+ flb_kv_item_destroy(kv);
+ return r;
+}
+
+/* Check if a given k, v is a valid config directive for the given output plugin */
+int flb_output_property_check(flb_ctx_t *ctx, int ffd, char *key, char *val)
+{
+ struct flb_output_instance *o_ins;
+ struct mk_list *config_map;
+ struct flb_output_plugin *p;
+ int r;
+
+ o_ins = out_instance_get(ctx, ffd);
+ if (!o_ins) {
+ return FLB_LIB_ERROR;
+ }
+
+ p = o_ins->p;
+ if (!p->config_map) {
+ return FLB_LIB_NO_CONFIG_MAP;
+ }
+
+ config_map = flb_config_map_create(ctx->config, p->config_map);
+ if (!config_map) {
+ return FLB_LIB_ERROR;
+ }
+
+ r = flb_config_map_property_check(p->name, config_map, key, val);
+ flb_config_map_destroy(config_map);
+ return r;
+}
+
+/* Check if a given k, v is a valid config directive for the given input plugin */
+int flb_input_property_check(flb_ctx_t *ctx, int ffd, char *key, char *val)
+{
+ struct flb_input_instance *i_ins;
+ struct flb_input_plugin *p;
+ struct mk_list *config_map;
+ int r;
+
+ i_ins = in_instance_get(ctx, ffd);
+ if (!i_ins) {
+ return FLB_LIB_ERROR;
+ }
+
+ p = i_ins->p;
+ if (!p->config_map) {
+ return FLB_LIB_NO_CONFIG_MAP;
+ }
+
+ config_map = flb_config_map_create(ctx->config, p->config_map);
+ if (!config_map) {
+ return FLB_LIB_ERROR;
+ }
+
+ r = flb_config_map_property_check(p->name, config_map, key, val);
+ flb_config_map_destroy(config_map);
+ return r;
+}
+
+/* Check if a given k, v is a valid config directive for the given filter plugin */
+int flb_filter_property_check(flb_ctx_t *ctx, int ffd, char *key, char *val)
+{
+ struct flb_filter_instance *f_ins;
+ struct flb_filter_plugin *p;
+ struct mk_list *config_map;
+ int r;
+
+ f_ins = filter_instance_get(ctx, ffd);
+ if (!f_ins) {
+ return FLB_LIB_ERROR;
+ }
+
+ p = f_ins->p;
+ if (!p->config_map) {
+ return FLB_LIB_NO_CONFIG_MAP;
+ }
+
+ config_map = flb_config_map_create(ctx->config, p->config_map);
+ if (!config_map) {
+ return FLB_LIB_ERROR;
+ }
+
+ r = flb_config_map_property_check(p->name, config_map, key, val);
+ flb_config_map_destroy(config_map);
+ return r;
+}
+
+/* Set an output interface property */
+int flb_output_set(flb_ctx_t *ctx, int ffd, ...)
+{
+ int ret;
+ char *key;
+ char *value;
+ va_list va;
+ struct flb_output_instance *o_ins;
+
+ o_ins = out_instance_get(ctx, ffd);
+ if (!o_ins) {
+ return -1;
+ }
+
+ va_start(va, ffd);
+ while ((key = va_arg(va, char *))) {
+ value = va_arg(va, char *);
+ if (!value) {
+ /* Wrong parameter */
+ va_end(va);
+ return -1;
+ }
+
+ ret = flb_output_set_property(o_ins, key, value);
+ if (ret != 0) {
+ va_end(va);
+ return -1;
+ }
+ }
+
+ va_end(va);
+ return 0;
+}
+
+int flb_output_set_callback(flb_ctx_t *ctx, int ffd, char *name,
+ void (*cb)(char *, void *, void *))
+{
+ struct flb_output_instance *o_ins;
+
+ o_ins = out_instance_get(ctx, ffd);
+ if (!o_ins) {
+ return -1;
+ }
+
+ return flb_callback_set(o_ins->callback, name, cb);
+}
+
+int flb_output_set_test(flb_ctx_t *ctx, int ffd, char *test_name,
+ void (*out_callback) (void *, int, int, void *, size_t, void *),
+ void *out_callback_data,
+ void *test_ctx)
+{
+ struct flb_output_instance *o_ins;
+
+ o_ins = out_instance_get(ctx, ffd);
+ if (!o_ins) {
+ return -1;
+ }
+
+ /*
+ * Enabling a test, set the output instance in 'test' mode, so no real
+ * flush callback is invoked, only the desired implemented test.
+ */
+
+ /* Formatter test */
+ if (strcmp(test_name, "formatter") == 0) {
+ o_ins->test_mode = FLB_TRUE;
+ o_ins->test_formatter.rt_ctx = ctx;
+ o_ins->test_formatter.rt_ffd = ffd;
+ o_ins->test_formatter.rt_out_callback = out_callback;
+ o_ins->test_formatter.rt_data = out_callback_data;
+ o_ins->test_formatter.flush_ctx = test_ctx;
+ }
+ else {
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Set an filter interface property */
+int flb_filter_set(flb_ctx_t *ctx, int ffd, ...)
+{
+ int ret;
+ char *key;
+ char *value;
+ va_list va;
+ struct flb_filter_instance *f_ins;
+
+ f_ins = filter_instance_get(ctx, ffd);
+ if (!f_ins) {
+ return -1;
+ }
+
+ va_start(va, ffd);
+ while ((key = va_arg(va, char *))) {
+ value = va_arg(va, char *);
+ if (!value) {
+ /* Wrong parameter */
+ va_end(va);
+ return -1;
+ }
+
+ ret = flb_filter_set_property(f_ins, key, value);
+ if (ret != 0) {
+ va_end(va);
+ return -1;
+ }
+ }
+
+ va_end(va);
+ return 0;
+}
+
+/* Set a service property */
+int flb_service_set(flb_ctx_t *ctx, ...)
+{
+ int ret;
+ char *key;
+ char *value;
+ va_list va;
+
+ va_start(va, ctx);
+
+ while ((key = va_arg(va, char *))) {
+ value = va_arg(va, char *);
+ if (!value) {
+ /* Wrong parameter */
+ va_end(va);
+ return -1;
+ }
+
+ ret = flb_config_set_property(ctx->config, key, value);
+ if (ret != 0) {
+ va_end(va);
+ return -1;
+ }
+ }
+
+ va_end(va);
+ return 0;
+}
+
+/* Load a configuration file that may be used by the input or output plugin */
+int flb_lib_config_file(struct flb_lib_ctx *ctx, const char *path)
+{
+ if (access(path, R_OK) != 0) {
+ perror("access");
+ return -1;
+ }
+
+ ctx->config->file = mk_rconf_open(path);
+ if (!ctx->config->file) {
+ fprintf(stderr, "Error reading configuration file: %s\n", path);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* This is a wrapper to release a buffer which comes from out_lib_flush() */
+int flb_lib_free(void* data)
+{
+ if (data == NULL) {
+ return -1;
+ }
+ flb_free(data);
+ return 0;
+}
+
+
+/* Push some data into the Engine */
+int flb_lib_push(flb_ctx_t *ctx, int ffd, const void *data, size_t len)
+{
+ int ret;
+ struct flb_input_instance *i_ins;
+
+ if (ctx->status == FLB_LIB_NONE || ctx->status == FLB_LIB_ERROR) {
+ flb_error("[lib] cannot push data, engine is not running");
+ return -1;
+ }
+
+ i_ins = in_instance_get(ctx, ffd);
+ if (!i_ins) {
+ return -1;
+ }
+
+ ret = flb_pipe_w(i_ins->channel[1], data, len);
+ if (ret == -1) {
+ flb_errno();
+ return -1;
+ }
+ return ret;
+}
+
+static void flb_lib_worker(void *data)
+{
+ int ret;
+ flb_ctx_t *ctx = data;
+ struct flb_config *config;
+
+ config = ctx->config;
+ flb_context_set(ctx);
+ mk_utils_worker_rename("flb-pipeline");
+ ret = flb_engine_start(config);
+ if (ret == -1) {
+ flb_engine_failed(config);
+ flb_engine_shutdown(config);
+ }
+ config->exit_status_code = ret;
+ ctx->status = FLB_LIB_NONE;
+}
+
+/* Return the current time to be used by lib callers */
+double flb_time_now()
+{
+ struct flb_time t;
+
+ flb_time_get(&t);
+ return flb_time_to_double(&t);
+}
+
+int static do_start(flb_ctx_t *ctx)
+{
+ int fd;
+ int bytes;
+ int ret;
+ uint64_t val;
+ pthread_t tid;
+ struct mk_event *event;
+ struct flb_config *config;
+
+ pthread_once(&flb_lib_once, flb_init_env);
+
+ flb_debug("[lib] context set: %p", ctx);
+
+ /* set context as the last active one */
+
+ /* spawn worker thread */
+ config = ctx->config;
+ ret = mk_utils_worker_spawn(flb_lib_worker, ctx, &tid);
+ if (ret == -1) {
+ return -1;
+ }
+ config->worker = tid;
+
+ /* Wait for the started signal so we can return to the caller */
+ mk_event_wait(config->ch_evl);
+ mk_event_foreach(event, config->ch_evl) {
+ fd = event->fd;
+ bytes = flb_pipe_r(fd, &val, sizeof(uint64_t));
+ if (bytes <= 0) {
+#if defined(FLB_SYSTEM_MACOS)
+ pthread_cancel(tid);
+#endif
+ pthread_join(tid, NULL);
+ ctx->status = FLB_LIB_ERROR;
+ return -1;
+ }
+
+ if (val == FLB_ENGINE_STARTED) {
+ flb_debug("[lib] backend started");
+ ctx->status = FLB_LIB_OK;
+ break;
+ }
+ else if (val == FLB_ENGINE_FAILED) {
+ flb_error("[lib] backend failed");
+#if defined(FLB_SYSTEM_MACOS)
+ pthread_cancel(tid);
+#endif
+ pthread_join(tid, NULL);
+ ctx->status = FLB_LIB_ERROR;
+ return -1;
+ }
+ else {
+ flb_error("[lib] other error");
+ }
+ }
+
+ return 0;
+}
+
+/* Start the engine */
+int flb_start(flb_ctx_t *ctx)
+{
+ int ret;
+
+ ret = do_start(ctx);
+ if (ret == 0) {
+ /* set context as the last active one */
+ flb_context_set(ctx);
+ }
+
+ return ret;
+}
+
+/* Start the engine without setting the global context */
+int flb_start_trace(flb_ctx_t *ctx)
+{
+ return do_start(ctx);
+}
+
+int flb_loop(flb_ctx_t *ctx)
+{
+ while (ctx->status == FLB_LIB_OK) {
+ sleep(1);
+ }
+ return 0;
+}
+
+/* Stop the engine */
+int flb_stop(flb_ctx_t *ctx)
+{
+ int ret;
+ pthread_t tid;
+
+ flb_debug("[lib] ctx stop address: %p, config context=%p\n", ctx, ctx->config);
+
+ tid = ctx->config->worker;
+
+ if (ctx->status == FLB_LIB_NONE || ctx->status == FLB_LIB_ERROR) {
+ /*
+ * There is a chance the worker thread is still active while
+ * the service exited for some reason (plugin action). Always
+ * wait and double check that the child thread is not running.
+ */
+#if defined(FLB_SYSTEM_MACOS)
+ pthread_cancel(tid);
+#endif
+ pthread_join(tid, NULL);
+ return 0;
+ }
+
+ if (!ctx->config) {
+ return 0;
+ }
+
+ if (ctx->config->file) {
+ mk_rconf_free(ctx->config->file);
+ }
+
+ flb_debug("[lib] sending STOP signal to the engine");
+
+ flb_engine_exit(ctx->config);
+#if defined(FLB_SYSTEM_MACOS)
+ pthread_cancel(tid);
+#endif
+ ret = pthread_join(tid, NULL);
+ if (ret != 0) {
+ flb_errno();
+ }
+ flb_debug("[lib] Fluent Bit engine stopped");
+
+ return ret;
+}
+
+
+void flb_context_set(flb_ctx_t *ctx)
+{
+ FLB_TLS_SET(flb_lib_active_context, ctx);
+}
+
+flb_ctx_t *flb_context_get()
+{
+ flb_ctx_t *ctx;
+
+ ctx = FLB_TLS_GET(flb_lib_active_context);
+ return ctx;
+}
+
+void flb_cf_context_set(struct flb_cf *cf)
+{
+ FLB_TLS_SET(flb_lib_active_cf_context, cf);
+}
+
+struct flb_cf *flb_cf_context_get()
+{
+ struct flb_cf *cf;
+
+ cf = FLB_TLS_GET(flb_lib_active_cf_context);
+ return cf;
+}
diff --git a/fluent-bit/src/flb_log.c b/fluent-bit/src/flb_log.c
new file mode 100644
index 000000000..d004af8af
--- /dev/null
+++ b/fluent-bit/src/flb_log.c
@@ -0,0 +1,696 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <monkey/mk_core.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_pipe.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_worker.h>
+#include <fluent-bit/flb_mem.h>
+
+#ifdef FLB_HAVE_AWS_ERROR_REPORTER
+#include <fluent-bit/aws/flb_aws_error_reporter.h>
+
+extern struct flb_aws_error_reporter *error_reporter;
+#endif
+
+FLB_TLS_DEFINE(struct flb_log, flb_log_ctx)
+
+/* Simple structure to dispatch messages to the log collector */
+struct log_message {
+ size_t size;
+ char msg[4096 - sizeof(size_t)];
+};
+
+static inline int consume_byte(flb_pipefd_t fd)
+{
+ int ret;
+ uint64_t val;
+
+ /* We need to consume the byte */
+ ret = flb_pipe_r(fd, &val, sizeof(val));
+ if (ret <= 0) {
+ flb_errno();
+ return -1;
+ }
+
+ return 0;
+}
+
+static inline int log_push(struct log_message *msg, struct flb_log *log)
+{
+ int fd;
+ int ret = -1;
+
+ if (log->type == FLB_LOG_STDERR) {
+ return write(STDERR_FILENO, msg->msg, msg->size);
+ }
+ else if (log->type == FLB_LOG_FILE) {
+ fd = open(log->out, O_CREAT | O_WRONLY | O_APPEND, 0666);
+ if (fd == -1) {
+ fprintf(stderr, "[log] error opening log file %s. Using stderr.\n",
+ log->out);
+ return write(STDERR_FILENO, msg->msg, msg->size);
+ }
+ ret = write(fd, msg->msg, msg->size);
+ close(fd);
+ }
+
+ return ret;
+}
+
+static inline int log_read(flb_pipefd_t fd, struct flb_log *log)
+{
+ int bytes;
+ struct log_message msg;
+
+ /*
+ * Since write operations to the pipe are always atomic 'if' they are
+ * under the PIPE_BUF limit (4KB on Linux) and our messages are always 1KB,
+ * we can trust we will always get a full message on each read(2).
+ */
+ bytes = flb_pipe_read_all(fd, &msg, sizeof(struct log_message));
+ if (bytes <= 0) {
+ flb_errno();
+ return -1;
+ }
+ if (msg.size > sizeof(msg.msg)) {
+ fprintf(stderr, "[log] message too long: %zi > %zi",
+ msg.size, sizeof(msg.msg));
+ return -1;
+ }
+ log_push(&msg, log);
+
+ return bytes;
+}
+
+/* Central collector of messages */
+static void log_worker_collector(void *data)
+{
+ int run = FLB_TRUE;
+ struct mk_event *event = NULL;
+ struct flb_log *log = data;
+
+ FLB_TLS_INIT(flb_log_ctx);
+ FLB_TLS_SET(flb_log_ctx, log);
+
+ mk_utils_worker_rename("flb-logger");
+
+ /* Signal the caller */
+ pthread_mutex_lock(&log->pth_mutex);
+ log->pth_init = FLB_TRUE;
+ pthread_cond_signal(&log->pth_cond);
+ pthread_mutex_unlock(&log->pth_mutex);
+
+ while (run) {
+ mk_event_wait(log->evl);
+ mk_event_foreach(event, log->evl) {
+ if (event->type == FLB_LOG_EVENT) {
+ log_read(event->fd, log);
+ }
+ else if (event->type == FLB_LOG_MNG) {
+ consume_byte(event->fd);
+ run = FLB_FALSE;
+ }
+ }
+ }
+
+ pthread_exit(NULL);
+}
+
+struct flb_log_cache *flb_log_cache_create(int timeout_seconds, int size)
+{
+ int i;
+ struct flb_log_cache *cache;
+ struct flb_log_cache_entry *entry;
+
+ if (size <= 0) {
+ return NULL;
+ }
+
+ cache = flb_calloc(1, sizeof(struct flb_log_cache));
+ if (!cache) {
+ flb_errno();
+ return NULL;
+ }
+ cache->timeout = timeout_seconds;
+ mk_list_init(&cache->entries);
+
+ for (i = 0; i < size; i++) {
+ entry = flb_calloc(1, sizeof(struct flb_log_cache_entry));
+ if (!entry) {
+ flb_errno();
+ flb_log_cache_destroy(cache);
+ return NULL;
+ }
+
+ entry->buf = flb_sds_create_size(FLB_LOG_CACHE_TEXT_BUF_SIZE);
+ if (!entry->buf) {
+ flb_errno();
+ flb_log_cache_destroy(cache);
+ }
+ entry->timestamp = 0; /* unset for now */
+ mk_list_add(&entry->_head, &cache->entries);
+ }
+
+ return cache;
+}
+
+void flb_log_cache_destroy(struct flb_log_cache *cache)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_log_cache_entry *entry;
+
+ if (!cache) {
+ return;
+ }
+
+ mk_list_foreach_safe(head, tmp, &cache->entries) {
+ entry = mk_list_entry(head, struct flb_log_cache_entry, _head);
+ flb_sds_destroy(entry->buf);
+ mk_list_del(&entry->_head);
+ flb_free(entry);
+ }
+ flb_free(cache);
+}
+
+struct flb_log_cache_entry *flb_log_cache_exists(struct flb_log_cache *cache, char *msg_buf, size_t msg_size)
+{
+ size_t size;
+ struct mk_list *head;
+ struct flb_log_cache_entry *entry;
+
+ if (msg_size <= 1) {
+ return NULL;
+ }
+
+ /* number of bytes to compare */
+ size = msg_size / 2;
+
+ mk_list_foreach(head, &cache->entries) {
+ entry = mk_list_entry(head, struct flb_log_cache_entry, _head);
+ if (entry->timestamp == 0) {
+ continue;
+ }
+
+ if (flb_sds_len(entry->buf) < size) {
+ continue;
+ }
+
+ if (strncmp(entry->buf, msg_buf, size) == 0) {
+ return entry;
+ }
+ }
+
+ return NULL;
+}
+
+
+/* returns an unused entry or the oldest one */
+struct flb_log_cache_entry *flb_log_cache_get_target(struct flb_log_cache *cache, uint64_t ts)
+{
+ struct mk_list *head;
+ struct flb_log_cache_entry *entry;
+ struct flb_log_cache_entry *target = NULL;
+
+ mk_list_foreach(head, &cache->entries) {
+ entry = mk_list_entry(head, struct flb_log_cache_entry, _head);
+
+ /* unused entry */
+ if (entry->timestamp == 0) {
+ return entry;
+ }
+
+ /* expired entry */
+ if (entry->timestamp + cache->timeout < ts) {
+ return entry;
+ }
+
+ /* keep a reference to the oldest entry to sacrifice it */
+ if (!target || entry->timestamp < target->timestamp) {
+ target = entry;
+ }
+ }
+
+ return target;
+}
+
+/*
+ * should the incoming message to be suppressed because already one similar exists in
+ * the cache ?
+ *
+ * if no similar message exists, then the incoming message is added to the cache.
+ */
+int flb_log_cache_check_suppress(struct flb_log_cache *cache, char *msg_buf, size_t msg_size)
+{
+ uint64_t now = 0;
+ struct flb_log_cache_entry *entry;
+
+ now = time(NULL);
+ entry = flb_log_cache_exists(cache, msg_buf, msg_size);
+
+ /* if no similar message found, add the incoming message to the cache */
+ if (!entry) {
+ /* look for an unused entry or the oldest one */
+ entry = flb_log_cache_get_target(cache, now);
+
+ /* if no target entry is available just return, do not suppress the message */
+ if (!entry) {
+ return FLB_FALSE;
+ }
+
+ /* add the message to the cache */
+ flb_sds_len_set(entry->buf, 0);
+ entry->buf = flb_sds_copy(entry->buf, msg_buf, msg_size);
+ entry->timestamp = now;
+ return FLB_FALSE;
+ }
+ else {
+ if (entry->timestamp + cache->timeout > now) {
+ return FLB_TRUE;
+ }
+ else {
+ entry->timestamp = now;
+ return FLB_FALSE;
+ }
+ }
+ return FLB_TRUE;
+}
+
+int flb_log_worker_init(struct flb_worker *worker)
+{
+ int ret;
+ struct flb_config *config = worker->config;
+ struct flb_log *log = config->log;
+ struct flb_log_cache *cache;
+
+ /* Pipe to communicate Thread with worker log-collector */
+ ret = flb_pipe_create(worker->log);
+ if (ret == -1) {
+ flb_errno();
+ return -1;
+ }
+
+ /* Register the read-end of the pipe (log[0]) into the event loop */
+ ret = mk_event_add(log->evl, worker->log[0],
+ FLB_LOG_EVENT, MK_EVENT_READ, &worker->event);
+ if (ret == -1) {
+ close(worker->log[0]);
+ close(worker->log[1]);
+ return -1;
+ }
+
+ /* Log cache to reduce noise */
+ cache = flb_log_cache_create(10, FLB_LOG_CACHE_ENTRIES);
+ if (!cache) {
+ close(worker->log[0]);
+ close(worker->log[1]);
+ return -1;
+ }
+ worker->log_cache = cache;
+ return 0;
+}
+
+int flb_log_set_level(struct flb_config *config, int level)
+{
+ config->log->level = level;
+ return 0;
+}
+
+int flb_log_get_level_str(char *str)
+{
+ if (strcasecmp(str, "off") == 0) {
+ return FLB_LOG_OFF;
+ }
+ else if (strcasecmp(str, "error") == 0) {
+ return FLB_LOG_ERROR;
+ }
+ else if (strcasecmp(str, "warn") == 0 || strcasecmp(str, "warning") == 0) {
+ return FLB_LOG_WARN;
+ }
+ else if (strcasecmp(str, "info") == 0) {
+ return FLB_LOG_INFO;
+ }
+ else if (strcasecmp(str, "debug") == 0) {
+ return FLB_LOG_DEBUG;
+ }
+ else if (strcasecmp(str, "trace") == 0) {
+ return FLB_LOG_TRACE;
+ }
+
+ return -1;
+}
+
+int flb_log_set_file(struct flb_config *config, char *out)
+{
+ struct flb_log *log = config->log;
+
+ if (out) {
+ log->type = FLB_LOG_FILE;
+ log->out = out;
+ }
+ else {
+ log->type = FLB_LOG_STDERR;
+ log->out = NULL;
+ }
+
+ return 0;
+}
+
+struct flb_log *flb_log_create(struct flb_config *config, int type,
+ int level, char *out)
+{
+ int ret;
+ struct flb_log *log;
+ struct flb_worker *worker;
+ struct mk_event_loop *evl;
+
+ log = flb_calloc(1, sizeof(struct flb_log));
+ if (!log) {
+ flb_errno();
+ return NULL;
+ }
+ config->log = log;
+
+ /* Create event loop to be used by the collector worker */
+ evl = mk_event_loop_create(32);
+ if (!evl) {
+ fprintf(stderr, "[log] could not create event loop\n");
+ flb_free(log);
+ config->log = NULL;
+ return NULL;
+ }
+
+ /* Prepare logging context */
+ log->type = type;
+ log->level = level;
+ log->out = out;
+ log->evl = evl;
+ log->tid = 0;
+
+ ret = flb_pipe_create(log->ch_mng);
+ if (ret == -1) {
+ fprintf(stderr, "[log] could not create pipe(2)");
+ mk_event_loop_destroy(log->evl);
+ flb_free(log);
+ config->log = NULL;
+ return NULL;
+ }
+ MK_EVENT_ZERO(&log->event);
+
+ /* Register channel manager into the event loop */
+ ret = mk_event_add(log->evl, log->ch_mng[0],
+ FLB_LOG_MNG, MK_EVENT_READ, &log->event);
+ if (ret == -1) {
+ fprintf(stderr, "[log] could not register event\n");
+ mk_event_loop_destroy(log->evl);
+ flb_free(log);
+ config->log = NULL;
+ return NULL;
+ }
+
+ /*
+ * Since the main process/thread might want to write log messages,
+ * it will need a 'worker-like' context, here we create a fake worker
+ * context just for messaging purposes.
+ */
+ worker = flb_worker_context_create(NULL, NULL, config);
+ if (!worker) {
+ flb_errno();
+ mk_event_loop_destroy(log->evl);
+ flb_free(log);
+ config->log = NULL;
+ }
+
+ /* Set the worker context global */
+ FLB_TLS_INIT(flb_worker_ctx);
+ FLB_TLS_SET(flb_worker_ctx, worker);
+
+ ret = flb_log_worker_init(worker);
+ if (ret == -1) {
+ flb_errno();
+ mk_event_loop_destroy(log->evl);
+ flb_free(log);
+ config->log = NULL;
+ flb_free(worker);
+ return NULL;
+ }
+ log->worker = worker;
+
+ /*
+ * This lock is used for the 'pth_cond' conditional. Once the worker
+ * thread is ready will signal the condition.
+ */
+ pthread_mutex_init(&log->pth_mutex, NULL);
+ pthread_cond_init(&log->pth_cond, NULL);
+ log->pth_init = FLB_FALSE;
+
+ pthread_mutex_lock(&log->pth_mutex);
+
+ ret = flb_worker_create(log_worker_collector, log, &log->tid, config);
+ if (ret == -1) {
+ pthread_mutex_unlock(&log->pth_mutex);
+ mk_event_loop_destroy(log->evl);
+ flb_free(log->worker);
+ flb_free(log);
+ config->log = NULL;
+ return NULL;
+ }
+
+ /* Block until the child thread is ready */
+ while (!log->pth_init) {
+ pthread_cond_wait(&log->pth_cond, &log->pth_mutex);
+ }
+ pthread_mutex_unlock(&log->pth_mutex);
+
+ return log;
+}
+
+int flb_log_construct(struct log_message *msg, int *ret_len,
+ int type, const char *file, int line, const char *fmt, va_list *args)
+{
+ int body_size;
+ int ret;
+ int len;
+ int total;
+ time_t now;
+ const char *header_color = NULL;
+ const char *header_title = NULL;
+ const char *bold_color = ANSI_BOLD;
+ const char *reset_color = ANSI_RESET;
+ struct tm result;
+ struct tm *current;
+
+ switch (type) {
+ case FLB_LOG_HELP:
+ header_title = "help";
+ header_color = ANSI_CYAN;
+ break;
+ case FLB_LOG_INFO:
+ header_title = "info";
+ header_color = ANSI_GREEN;
+ break;
+ case FLB_LOG_WARN:
+ header_title = "warn";
+ header_color = ANSI_YELLOW;
+ break;
+ case FLB_LOG_ERROR:
+ header_title = "error";
+ header_color = ANSI_RED;
+ break;
+ case FLB_LOG_DEBUG:
+ header_title = "debug";
+ header_color = ANSI_YELLOW;
+ break;
+ case FLB_LOG_IDEBUG:
+ header_title = "debug";
+ header_color = ANSI_CYAN;
+ break;
+ case FLB_LOG_TRACE:
+ header_title = "trace";
+ header_color = ANSI_BLUE;
+ break;
+ }
+
+ #ifdef FLB_LOG_NO_CONTROL_CHARS
+ header_color = "";
+ bold_color = "";
+ reset_color = "";
+ #else
+ /* Only print colors to a terminal */
+ if (!isatty(STDOUT_FILENO)) {
+ header_color = "";
+ bold_color = "";
+ reset_color = "";
+ }
+ #endif // FLB_LOG_NO_CONTROL_CHARS
+
+ now = time(NULL);
+ current = localtime_r(&now, &result);
+
+ if (current == NULL) {
+ return -1;
+ }
+
+ len = snprintf(msg->msg, sizeof(msg->msg) - 1,
+ "%s[%s%i/%02i/%02i %02i:%02i:%02i%s]%s [%s%5s%s] ",
+ /* time */ /* type */
+
+ /* time variables */
+ bold_color, reset_color,
+ current->tm_year + 1900,
+ current->tm_mon + 1,
+ current->tm_mday,
+ current->tm_hour,
+ current->tm_min,
+ current->tm_sec,
+ bold_color, reset_color,
+
+ /* type format */
+ header_color, header_title, reset_color);
+
+ body_size = (sizeof(msg->msg) - 2) - len;
+ total = vsnprintf(msg->msg + len,
+ body_size,
+ fmt, *args);
+ if (total < 0) {
+ return -1;
+ }
+ ret = total; /* ret means a buffer size need to save log body */
+
+ total = strlen(msg->msg + len) + len;
+ msg->msg[total++] = '\n';
+ msg->msg[total] = '\0';
+ msg->size = total;
+
+ *ret_len = len;
+
+ if (ret >= body_size) {
+ /* log is truncated */
+ return ret - body_size;
+ }
+
+ return 0;
+}
+
+/**
+ * flb_log_is_truncated tries to construct log and returns that the log is truncated.
+ *
+ * @param same as flb_log_print
+ * @return 0: log is not truncated. -1: some error occurs.
+ * positive number: truncated log size.
+ *
+ */
+int flb_log_is_truncated(int type, const char *file, int line, const char *fmt, ...)
+{
+ int ret;
+ int len;
+ struct log_message msg = {0};
+ va_list args;
+
+ va_start(args, fmt);
+ ret = flb_log_construct(&msg, &len, type, file, line, fmt, &args);
+ va_end(args);
+
+ if (ret < 0) {
+ return -1;
+ }
+
+ return ret;
+}
+
+void flb_log_print(int type, const char *file, int line, const char *fmt, ...)
+{
+ int n;
+ int len;
+ int ret;
+ struct log_message msg = {0};
+ va_list args;
+
+ struct flb_worker *w;
+
+ va_start(args, fmt);
+ ret = flb_log_construct(&msg, &len, type, file, line, fmt, &args);
+ va_end(args);
+
+ if (ret < 0) {
+ return;
+ }
+
+ w = flb_worker_get();
+ if (w) {
+ n = flb_pipe_write_all(w->log[1], &msg, sizeof(msg));
+ if (n == -1) {
+ fprintf(stderr, "%s", (char *) msg.msg);
+ perror("write");
+ }
+ }
+ else {
+ fprintf(stderr, "%s", (char *) msg.msg);
+ }
+
+ #ifdef FLB_HAVE_AWS_ERROR_REPORTER
+ if (is_error_reporting_enabled()) {
+ if (type == FLB_LOG_ERROR) {
+ flb_aws_error_reporter_write(error_reporter, msg.msg + len);
+ }
+
+ flb_aws_error_reporter_clean(error_reporter);
+ }
+ #endif
+}
+
+int flb_errno_print(int errnum, const char *file, int line)
+{
+ char buf[256];
+
+ strerror_r(errnum, buf, sizeof(buf) - 1);
+ flb_error("[%s:%i errno=%i] %s", file, line, errnum, buf);
+ return 0;
+}
+
+int flb_log_destroy(struct flb_log *log, struct flb_config *config)
+{
+ uint64_t val = FLB_TRUE;
+
+ /* Signal the child worker, stop working */
+ flb_pipe_w(log->ch_mng[1], &val, sizeof(val));
+ pthread_join(log->tid, NULL);
+
+ /* Release resources */
+ mk_event_loop_destroy(log->evl);
+ flb_pipe_destroy(log->ch_mng);
+ if (log->worker->log_cache) {
+ flb_log_cache_destroy(log->worker->log_cache);
+ }
+ flb_free(log->worker);
+ flb_free(log);
+
+ return 0;
+}
diff --git a/fluent-bit/src/flb_log_event_decoder.c b/fluent-bit/src/flb_log_event_decoder.c
new file mode 100644
index 000000000..00a778e3d
--- /dev/null
+++ b/fluent-bit/src/flb_log_event_decoder.c
@@ -0,0 +1,383 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_log_event_decoder.h>
+#include <fluent-bit/flb_byteswap.h>
+
+static int create_empty_map(struct flb_log_event_decoder *context) {
+ msgpack_packer packer;
+ msgpack_sbuffer buffer;
+ int result;
+ size_t offset;
+
+ result = FLB_EVENT_DECODER_SUCCESS;
+
+ context->empty_map = NULL;
+
+ msgpack_sbuffer_init(&buffer);
+ msgpack_packer_init(&packer, &buffer, msgpack_sbuffer_write);
+
+ result = msgpack_pack_map(&packer, 0);
+
+ if (result != 0) {
+ result = FLB_EVENT_DECODER_ERROR_INITIALIZATION_FAILURE;
+ }
+ else {
+ offset = 0;
+
+ msgpack_unpacked_init(&context->unpacked_empty_map);
+
+ result = msgpack_unpack_next(&context->unpacked_empty_map,
+ buffer.data,
+ buffer.size,
+ &offset);
+
+ if (result != MSGPACK_UNPACK_SUCCESS) {
+ result = FLB_EVENT_DECODER_ERROR_INITIALIZATION_FAILURE;
+ }
+ else {
+ context->empty_map = &context->unpacked_empty_map.data;
+
+ result = FLB_EVENT_DECODER_SUCCESS;
+ }
+ }
+
+ msgpack_sbuffer_destroy(&buffer);
+
+ return result;
+}
+
+void flb_log_event_decoder_reset(struct flb_log_event_decoder *context,
+ char *input_buffer,
+ size_t input_length)
+{
+ context->offset = 0;
+ context->buffer = input_buffer;
+ context->length = input_length;
+ context->last_result = FLB_EVENT_DECODER_ERROR_INSUFFICIENT_DATA;
+
+ msgpack_unpacked_destroy(&context->unpacked_event);
+ msgpack_unpacked_init(&context->unpacked_event);
+
+}
+
+int flb_log_event_decoder_init(struct flb_log_event_decoder *context,
+ char *input_buffer,
+ size_t input_length)
+{
+ if (context == NULL) {
+ return FLB_EVENT_DECODER_ERROR_INVALID_CONTEXT;
+ }
+
+ memset(context, 0, sizeof(struct flb_log_event_decoder));
+
+ context->dynamically_allocated = FLB_FALSE;
+ context->initialized = FLB_TRUE;
+
+ flb_log_event_decoder_reset(context, input_buffer, input_length);
+
+ return create_empty_map(context);
+}
+
+struct flb_log_event_decoder *flb_log_event_decoder_create(
+ char *input_buffer,
+ size_t input_length)
+{
+ struct flb_log_event_decoder *context;
+ int result;
+
+ context = (struct flb_log_event_decoder *) \
+ flb_calloc(1, sizeof(struct flb_log_event_decoder));
+
+ result = flb_log_event_decoder_init(context,
+ input_buffer,
+ input_length);
+
+ if (context != NULL) {
+ context->dynamically_allocated = FLB_TRUE;
+
+ if (result != FLB_EVENT_DECODER_SUCCESS) {
+ flb_log_event_decoder_destroy(context);
+
+ context = NULL;
+ }
+ }
+
+ return context;
+}
+
+void flb_log_event_decoder_destroy(struct flb_log_event_decoder *context)
+{
+ int dynamically_allocated;
+
+ if (context != NULL) {
+ if (context->initialized) {
+ msgpack_unpacked_destroy(&context->unpacked_empty_map);
+ msgpack_unpacked_destroy(&context->unpacked_event);
+ }
+
+ dynamically_allocated = context->dynamically_allocated;
+
+ memset(context, 0, sizeof(struct flb_log_event_decoder));
+
+ /* This might look silly and with most of the codebase including
+ * this module as context it might be but just in case we choose
+ * to stray away from the assumption of FLB_FALSE being zero and
+ * FLB_TRUE being one in favor of explicitly comparing variables to
+ * the the constants I will leave this here.
+ */
+ context->initialized = FLB_FALSE;
+
+ if (dynamically_allocated) {
+ flb_free(context);
+ }
+ }
+}
+
+int flb_log_event_decoder_decode_timestamp(msgpack_object *input,
+ struct flb_time *output)
+{
+ flb_time_zero(output);
+
+ if (input->type == MSGPACK_OBJECT_POSITIVE_INTEGER) {
+ output->tm.tv_sec = input->via.u64;
+ }
+ else if(input->type == MSGPACK_OBJECT_FLOAT) {
+ output->tm.tv_sec = input->via.f64;
+ output->tm.tv_nsec = ((input->via.f64 - output->tm.tv_sec) * 1000000000);
+ }
+ else if(input->type == MSGPACK_OBJECT_EXT) {
+ if (input->via.ext.type != 0 || input->via.ext.size != 8) {
+ return FLB_EVENT_DECODER_ERROR_WRONG_TIMESTAMP_TYPE;
+ }
+
+ output->tm.tv_sec = FLB_BSWAP_32(*((uint32_t *) &input->via.ext.ptr[0]));
+ output->tm.tv_nsec = FLB_BSWAP_32(*((uint32_t *) &input->via.ext.ptr[4]));
+ }
+ else {
+ return FLB_EVENT_DECODER_ERROR_WRONG_TIMESTAMP_TYPE;
+ }
+
+ return FLB_EVENT_DECODER_SUCCESS;
+}
+
+int flb_event_decoder_decode_object(struct flb_log_event_decoder *context,
+ struct flb_log_event *event,
+ msgpack_object *input)
+{
+ msgpack_object *timestamp;
+ msgpack_object *metadata;
+ int result;
+ int format;
+ msgpack_object *header;
+ msgpack_object *body;
+ msgpack_object *root;
+
+ memset(event, 0, sizeof(struct flb_log_event));
+
+ /* Ensure that the root element is a 2 element array*/
+ root = input;
+
+ if (root->type != MSGPACK_OBJECT_ARRAY) {
+ return FLB_EVENT_DECODER_ERROR_WRONG_ROOT_TYPE;
+ }
+
+ if (root->via.array.size != \
+ FLB_LOG_EVENT_EXPECTED_ROOT_ELEMENT_COUNT) {
+ return FLB_EVENT_DECODER_ERROR_WRONG_ROOT_SIZE;
+ }
+
+ header = &root->via.array.ptr[0];
+
+ /* Determine if the first element is the header or
+ * a legacy timestamp (int, float or ext).
+ */
+ if (header->type == MSGPACK_OBJECT_ARRAY) {
+ if (header->via.array.size != \
+ FLB_LOG_EVENT_EXPECTED_HEADER_ELEMENT_COUNT) {
+ return FLB_EVENT_DECODER_ERROR_WRONG_HEADER_SIZE;
+ }
+
+ timestamp = &header->via.array.ptr[0];
+ metadata = &header->via.array.ptr[1];
+
+ format = FLB_LOG_EVENT_FORMAT_FLUENT_BIT_V2;
+ }
+ else {
+ header = NULL;
+ timestamp = &root->via.array.ptr[0];
+ metadata = context->empty_map;
+
+ format = FLB_LOG_EVENT_FORMAT_FORWARD;
+ }
+
+ if (timestamp->type != MSGPACK_OBJECT_POSITIVE_INTEGER &&
+ timestamp->type != MSGPACK_OBJECT_FLOAT &&
+ timestamp->type != MSGPACK_OBJECT_EXT) {
+ return FLB_EVENT_DECODER_ERROR_WRONG_TIMESTAMP_TYPE;
+ }
+
+ if (metadata->type != MSGPACK_OBJECT_MAP) {
+ return FLB_EVENT_DECODER_ERROR_WRONG_METADATA_TYPE;
+ }
+
+ body = &root->via.array.ptr[1];
+
+ if (body->type != MSGPACK_OBJECT_MAP) {
+ return FLB_EVENT_DECODER_ERROR_WRONG_BODY_TYPE;
+ }
+
+ result = flb_log_event_decoder_decode_timestamp(timestamp, &event->timestamp);
+
+ if (result != FLB_EVENT_DECODER_SUCCESS) {
+ return result;
+ }
+
+ event->raw_timestamp = timestamp;
+ event->metadata = metadata;
+ event->format = format;
+ event->body = body;
+ event->root = root;
+
+ context->record_base = \
+ (const char *) &context->buffer[context->previous_offset];
+ context->record_length = context->offset - context->previous_offset;
+
+ return FLB_EVENT_DECODER_SUCCESS;
+}
+
+int flb_log_event_decoder_get_last_result(struct flb_log_event_decoder *context)
+{
+ if (context->last_result == FLB_EVENT_DECODER_ERROR_INSUFFICIENT_DATA &&
+ context->offset == context->length) {
+ context->last_result = FLB_EVENT_DECODER_SUCCESS;
+ }
+
+ return context->last_result;
+}
+
+int flb_log_event_decoder_next(struct flb_log_event_decoder *context,
+ struct flb_log_event *event)
+{
+ size_t previous_offset;
+ int result;
+
+ if (context == NULL) {
+ return FLB_EVENT_DECODER_ERROR_INVALID_CONTEXT;
+ }
+ if (context->length == 0) {
+ context->last_result = FLB_EVENT_DECODER_ERROR_INSUFFICIENT_DATA;
+ return context->last_result;
+ }
+
+ context->record_base = NULL;
+ context->record_length = 0;
+
+ if (event == NULL) {
+ context->last_result = FLB_EVENT_DECODER_ERROR_INVALID_ARGUMENT;
+ return context->last_result;
+ }
+
+ memset(event, 0, sizeof(struct flb_log_event));
+
+ previous_offset = context->offset;
+
+ result = msgpack_unpack_next(&context->unpacked_event,
+ context->buffer,
+ context->length,
+ &context->offset);
+
+ if (result == MSGPACK_UNPACK_CONTINUE) {
+ context->last_result = FLB_EVENT_DECODER_ERROR_INSUFFICIENT_DATA;
+ return context->last_result;
+ }
+ else if (result != MSGPACK_UNPACK_SUCCESS) {
+ context->last_result = FLB_EVENT_DECODER_ERROR_DESERIALIZATION_FAILURE;
+ return context->last_result;
+ }
+
+ context->previous_offset = previous_offset;
+ context->last_result = flb_event_decoder_decode_object(context,
+ event,
+ &context->unpacked_event.data);
+ return context->last_result;
+}
+
+const char *flb_log_event_decoder_get_error_description(int error_code)
+{
+ const char *ret;
+
+ switch (error_code) {
+ case FLB_EVENT_DECODER_SUCCESS:
+ ret = "Success";
+ break;
+
+ case FLB_EVENT_DECODER_ERROR_INITIALIZATION_FAILURE:
+ ret = "Initialization failure";
+ break;
+
+ case FLB_EVENT_DECODER_ERROR_INVALID_CONTEXT:
+ ret = "Invalid context";
+ break;
+
+ case FLB_EVENT_DECODER_ERROR_INVALID_ARGUMENT:
+ ret = "Invalid argument";
+ break;
+
+ case FLB_EVENT_DECODER_ERROR_WRONG_ROOT_TYPE:
+ ret = "Wrong root type";
+ break;
+
+ case FLB_EVENT_DECODER_ERROR_WRONG_ROOT_SIZE:
+ ret = "Wrong root size";
+ break;
+
+ case FLB_EVENT_DECODER_ERROR_WRONG_HEADER_TYPE:
+ ret = "Wrong header type";
+ break;
+
+ case FLB_EVENT_DECODER_ERROR_WRONG_HEADER_SIZE:
+ ret = "Wrong header size";
+ break;
+
+ case FLB_EVENT_DECODER_ERROR_WRONG_TIMESTAMP_TYPE:
+ ret = "Wrong timestamp type";
+ break;
+
+ case FLB_EVENT_DECODER_ERROR_WRONG_METADATA_TYPE:
+ ret = "Wrong metadata type";
+ break;
+
+ case FLB_EVENT_DECODER_ERROR_WRONG_BODY_TYPE:
+ ret = "Wrong body type";
+ break;
+
+ case FLB_EVENT_DECODER_ERROR_DESERIALIZATION_FAILURE:
+ ret = "Deserialization failure";
+ break;
+
+ case FLB_EVENT_DECODER_ERROR_INSUFFICIENT_DATA:
+ ret = "Insufficient data";
+ break;
+
+ default:
+ ret = "Unknown error";
+ }
+ return ret;
+}
diff --git a/fluent-bit/src/flb_log_event_encoder.c b/fluent-bit/src/flb_log_event_encoder.c
new file mode 100644
index 000000000..bb6507b8c
--- /dev/null
+++ b/fluent-bit/src/flb_log_event_encoder.c
@@ -0,0 +1,391 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_log_event_encoder.h>
+#include <fluent-bit/flb_log_event_encoder_primitives.h>
+#include <stdarg.h>
+
+void static inline flb_log_event_encoder_update_internal_state(
+ struct flb_log_event_encoder *context)
+{
+ context->output_buffer = context->buffer.data;
+ context->output_length = context->buffer.size;
+}
+
+void flb_log_event_encoder_reset(struct flb_log_event_encoder *context)
+{
+ flb_log_event_encoder_dynamic_field_reset(&context->metadata);
+ flb_log_event_encoder_dynamic_field_reset(&context->body);
+ flb_log_event_encoder_dynamic_field_reset(&context->root);
+
+ msgpack_sbuffer_clear(&context->buffer);
+
+ flb_log_event_encoder_update_internal_state(context);
+}
+
+int flb_log_event_encoder_init(struct flb_log_event_encoder *context, int format)
+{
+ if (context == NULL) {
+ return FLB_EVENT_ENCODER_ERROR_INVALID_CONTEXT;
+ }
+
+ if (format < FLB_LOG_EVENT_FORMAT_FORWARD ||
+ format > FLB_LOG_EVENT_FORMAT_FLUENT_BIT_V2) {
+ return FLB_EVENT_ENCODER_ERROR_INVALID_ARGUMENT;
+ }
+
+ memset(context, 0, sizeof(struct flb_log_event_encoder));
+
+ context->dynamically_allocated = FLB_FALSE;
+ context->initialized = FLB_TRUE;
+ context->format = format;
+
+ msgpack_sbuffer_init(&context->buffer);
+ msgpack_packer_init(&context->packer,
+ &context->buffer,
+ msgpack_sbuffer_write);
+
+ flb_log_event_encoder_dynamic_field_init(&context->metadata,
+ MSGPACK_OBJECT_MAP);
+
+ flb_log_event_encoder_dynamic_field_init(&context->body,
+ MSGPACK_OBJECT_MAP);
+
+ flb_log_event_encoder_dynamic_field_init(&context->root,
+ MSGPACK_OBJECT_ARRAY);
+
+ return FLB_EVENT_ENCODER_SUCCESS;
+}
+
+struct flb_log_event_encoder *flb_log_event_encoder_create(int format)
+{
+ struct flb_log_event_encoder *context;
+ int result;
+
+ context = (struct flb_log_event_encoder *) \
+ flb_calloc(1, sizeof(struct flb_log_event_encoder));
+
+ result = flb_log_event_encoder_init(context, format);
+
+ if (context != NULL) {
+ context->dynamically_allocated = FLB_TRUE;
+
+ if (result != FLB_EVENT_ENCODER_SUCCESS) {
+ flb_log_event_encoder_destroy(context);
+
+ context = NULL;
+ }
+ }
+
+ return context;
+}
+
+void flb_log_event_encoder_destroy(struct flb_log_event_encoder *context)
+{
+ if (context != NULL) {
+ if (context->initialized) {
+ flb_log_event_encoder_dynamic_field_destroy(&context->metadata);
+ flb_log_event_encoder_dynamic_field_destroy(&context->body);
+ flb_log_event_encoder_dynamic_field_destroy(&context->root);
+
+ msgpack_sbuffer_destroy(&context->buffer);
+
+ context->initialized = FLB_FALSE;
+ }
+
+ if (context->dynamically_allocated) {
+ flb_free(context);
+ }
+ }
+}
+
+void flb_log_event_encoder_claim_internal_buffer_ownership(
+ struct flb_log_event_encoder *context)
+{
+ if (context != NULL) {
+ msgpack_sbuffer_release(&context->buffer);
+ }
+}
+
+int flb_log_event_encoder_emit_raw_record(struct flb_log_event_encoder *context,
+ const char *buffer,
+ size_t length)
+{
+ int result;
+
+ result = msgpack_pack_str_body(&context->packer, buffer, length);
+
+ if (result != 0) {
+ result = FLB_EVENT_ENCODER_ERROR_SERIALIZATION_FAILURE;
+ }
+ else {
+ result = FLB_EVENT_ENCODER_SUCCESS;
+ }
+
+ flb_log_event_encoder_update_internal_state(context);
+ flb_log_event_encoder_reset_record(context);
+
+ return result;
+}
+
+int flb_log_event_encoder_emit_record(struct flb_log_event_encoder *context)
+{
+ int result;
+
+ if (context == NULL) {
+ return FLB_EVENT_ENCODER_ERROR_INVALID_CONTEXT;
+ }
+
+ result = FLB_EVENT_ENCODER_SUCCESS;
+
+ /* This function needs to be improved and optimized to avoid excessive
+ * memory copying operations.
+ */
+
+ /* This conditional accounts for external raw record emission as
+ * performed by some filters using either
+ * flb_log_event_encoder_set_root_from_raw_msgpack
+ * or
+ * flb_log_event_encoder_set_root_from_msgpack_object
+ */
+ if (context->root.size == 0) {
+ result = flb_log_event_encoder_root_begin_array(context);
+
+ if (context->format == FLB_LOG_EVENT_FORMAT_FLUENT_BIT_V2) {
+ if (result == FLB_EVENT_ENCODER_SUCCESS) {
+ result = flb_log_event_encoder_root_begin_array(context);
+ }
+ }
+
+ if (result == FLB_EVENT_ENCODER_SUCCESS) {
+ result = flb_log_event_encoder_append_root_timestamp(
+ context, &context->timestamp);
+ }
+
+ if (context->format == FLB_LOG_EVENT_FORMAT_FLUENT_BIT_V2) {
+ if (result == FLB_EVENT_ENCODER_SUCCESS) {
+ result = flb_log_event_encoder_append_root_raw_msgpack(
+ context,
+ context->metadata.data,
+ context->metadata.size);
+ }
+
+ /* We need to explicitly commit the current array (which
+ * holds the timestamp and metadata elements so we leave
+ * that scope and go back to the root scope where we can
+ * append the body element.
+ */
+ if (result == FLB_EVENT_ENCODER_SUCCESS) {
+ result = flb_log_event_encoder_root_commit_array(context);
+ }
+ }
+
+ if (result == FLB_EVENT_ENCODER_SUCCESS) {
+ result = flb_log_event_encoder_append_root_raw_msgpack(
+ context,
+ context->body.data,
+ context->body.size);
+ }
+
+ if (result == FLB_EVENT_ENCODER_SUCCESS) {
+ result = flb_log_event_encoder_dynamic_field_flush(&context->root);
+ }
+ }
+
+ if (result == FLB_EVENT_ENCODER_SUCCESS) {
+ result = msgpack_pack_str_body(&context->packer,
+ context->root.data,
+ context->root.size);
+
+ if (result != 0) {
+ result = FLB_EVENT_ENCODER_ERROR_SERIALIZATION_FAILURE;
+ }
+ else {
+ result = FLB_EVENT_ENCODER_SUCCESS;
+ }
+ }
+
+ flb_log_event_encoder_update_internal_state(context);
+ flb_log_event_encoder_reset_record(context);
+
+ return result;
+}
+
+int flb_log_event_encoder_reset_record(struct flb_log_event_encoder *context)
+{
+ flb_log_event_encoder_dynamic_field_reset(&context->metadata);
+ flb_log_event_encoder_dynamic_field_reset(&context->body);
+ flb_log_event_encoder_dynamic_field_reset(&context->root);
+
+ flb_time_zero(&context->timestamp);
+
+ return FLB_EVENT_ENCODER_SUCCESS;
+}
+
+int flb_log_event_encoder_rollback_record(struct flb_log_event_encoder *context)
+{
+ return flb_log_event_encoder_reset_record(context);
+}
+
+int flb_log_event_encoder_begin_record(struct flb_log_event_encoder *context)
+{
+ flb_log_event_encoder_reset_record(context);
+
+ flb_log_event_encoder_metadata_begin_map(context);
+ flb_log_event_encoder_body_begin_map(context);
+
+ return FLB_EVENT_ENCODER_SUCCESS;
+}
+
+int flb_log_event_encoder_commit_record(struct flb_log_event_encoder *context)
+{
+ int result;
+
+ result = flb_log_event_encoder_dynamic_field_flush(&context->metadata);
+
+ if (result == FLB_EVENT_ENCODER_SUCCESS) {
+ result = flb_log_event_encoder_dynamic_field_flush(&context->body);
+ }
+
+ if (result == FLB_EVENT_ENCODER_SUCCESS) {
+ result = flb_log_event_encoder_emit_record(context);
+ }
+ else {
+ flb_log_event_encoder_reset_record(context);
+ }
+
+ return result;
+}
+
+int flb_log_event_encoder_set_timestamp(
+ struct flb_log_event_encoder *context,
+ struct flb_time *timestamp)
+{
+ if (timestamp != NULL) {
+ flb_time_copy(&context->timestamp, timestamp);
+ }
+ else {
+ flb_time_get(&context->timestamp);
+ }
+
+ return FLB_EVENT_ENCODER_SUCCESS;
+}
+
+int flb_log_event_encoder_set_current_timestamp(
+ struct flb_log_event_encoder *context)
+{
+ return flb_log_event_encoder_set_timestamp(context, NULL);
+}
+
+int flb_log_event_encoder_append_metadata_values_unsafe(
+ struct flb_log_event_encoder *context,
+ ...)
+{
+ va_list arguments;
+ int result;
+
+ va_start(arguments, context);
+
+ result = flb_log_event_encoder_append_values_unsafe(
+ context,
+ FLB_LOG_EVENT_METADATA,
+ arguments);
+
+ va_end(arguments);
+
+ return result;
+}
+
+int flb_log_event_encoder_append_body_values_unsafe(
+ struct flb_log_event_encoder *context,
+ ...)
+{
+ va_list arguments;
+ int result;
+
+ va_start(arguments, context);
+
+ result = flb_log_event_encoder_append_values_unsafe(
+ context,
+ FLB_LOG_EVENT_BODY,
+ arguments);
+
+ va_end(arguments);
+
+ return result;
+}
+
+int flb_log_event_encoder_append_root_values_unsafe(
+ struct flb_log_event_encoder *context,
+ ...)
+{
+ va_list arguments;
+ int result;
+
+ va_start(arguments, context);
+
+ result = flb_log_event_encoder_append_values_unsafe(
+ context,
+ FLB_LOG_EVENT_ROOT,
+ arguments);
+
+ va_end(arguments);
+
+ return result;
+}
+
+const char *flb_log_event_encoder_get_error_description(int error_code)
+{
+ const char *ret;
+
+ switch (error_code) {
+ case FLB_EVENT_ENCODER_SUCCESS:
+ ret = "Success";
+ break;
+
+ case FLB_EVENT_ENCODER_ERROR_UNSPECIFIED:
+ ret = "Unspecified";
+ break;
+
+ case FLB_EVENT_ENCODER_ERROR_ALLOCATION_ERROR:
+ ret = "Allocation error";
+ break;
+
+ case FLB_EVENT_ENCODER_ERROR_INVALID_CONTEXT:
+ ret = "Invalid context";
+ break;
+
+ case FLB_EVENT_ENCODER_ERROR_INVALID_ARGUMENT:
+ ret = "Invalid argument";
+ break;
+
+ case FLB_EVENT_ENCODER_ERROR_SERIALIZATION_FAILURE:
+ ret = "Serialization failure";
+ break;
+
+ case FLB_EVENT_ENCODER_ERROR_INVALID_VALUE_TYPE:
+ ret = "Invalid value type";
+ break;
+
+ default:
+ ret = "Unknown error";
+ }
+
+ return ret;
+}
diff --git a/fluent-bit/src/flb_log_event_encoder_dynamic_field.c b/fluent-bit/src/flb_log_event_encoder_dynamic_field.c
new file mode 100644
index 000000000..fd8be44a7
--- /dev/null
+++ b/fluent-bit/src/flb_log_event_encoder_dynamic_field.c
@@ -0,0 +1,272 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_log_event_encoder.h>
+#include <fluent-bit/flb_log_event_encoder_dynamic_field.h>
+
+struct flb_log_event_encoder_dynamic_field_scope *
+ flb_log_event_encoder_dynamic_field_scope_current(
+ struct flb_log_event_encoder_dynamic_field *field)
+{
+ if (cfl_list_is_empty(&field->scopes)) {
+ return NULL;
+ }
+
+ return cfl_list_entry_first(
+ &field->scopes,
+ struct flb_log_event_encoder_dynamic_field_scope,
+ _head);
+}
+
+int flb_log_event_encoder_dynamic_field_scope_enter(
+ struct flb_log_event_encoder_dynamic_field *field,
+ int type)
+{
+ int result;
+ struct flb_log_event_encoder_dynamic_field_scope *scope;
+
+ if (type != MSGPACK_OBJECT_MAP &&
+ type != MSGPACK_OBJECT_ARRAY) {
+ return FLB_EVENT_ENCODER_ERROR_INVALID_ARGUMENT;
+ }
+
+ result = flb_log_event_encoder_dynamic_field_append(field);
+
+ if (result != FLB_EVENT_ENCODER_SUCCESS) {
+ return result;
+ }
+
+ scope = flb_calloc(1,
+ sizeof(struct flb_log_event_encoder_dynamic_field_scope));
+
+ if (scope == NULL) {
+ return FLB_EVENT_ENCODER_ERROR_ALLOCATION_ERROR;
+ }
+
+ cfl_list_entry_init(&scope->_head);
+
+ scope->type = type;
+ scope->offset = field->buffer.size;
+
+ cfl_list_prepend(&scope->_head, &field->scopes);
+
+ if (type == MSGPACK_OBJECT_MAP) {
+ flb_mp_map_header_init(&scope->header, &field->packer);
+ }
+ else if (type == MSGPACK_OBJECT_ARRAY) {
+ flb_mp_array_header_init(&scope->header, &field->packer);
+ }
+
+ return FLB_EVENT_ENCODER_SUCCESS;
+}
+
+int flb_log_event_encoder_dynamic_field_scope_leave(
+ struct flb_log_event_encoder_dynamic_field *field,
+ struct flb_log_event_encoder_dynamic_field_scope *scope,
+ int commit)
+{
+ if (scope == NULL) {
+ return FLB_EVENT_ENCODER_ERROR_INVALID_ARGUMENT;
+ }
+
+ if (commit) {
+ /* We increment the entry count on each append because
+ * we don't discriminate based on the scope type so
+ * we need to divide the entry count by two for maps
+ * to ensure the entry count matches the kv pair count
+ */
+
+ if (scope->type == MSGPACK_OBJECT_MAP) {
+ scope->header.entries /= 2;
+ flb_mp_map_header_end(&scope->header);
+ }
+ else {
+ flb_mp_array_header_end(&scope->header);
+ }
+ }
+ else {
+ field->buffer.size = scope->offset;
+ }
+
+ cfl_list_del(&scope->_head);
+
+ flb_free(scope);
+
+ return FLB_EVENT_ENCODER_SUCCESS;
+}
+
+int flb_log_event_encoder_dynamic_field_begin_map(
+ struct flb_log_event_encoder_dynamic_field *field)
+{
+ return flb_log_event_encoder_dynamic_field_scope_enter(field,
+ MSGPACK_OBJECT_MAP);
+}
+
+int flb_log_event_encoder_dynamic_field_begin_array(
+ struct flb_log_event_encoder_dynamic_field *field)
+{
+ return flb_log_event_encoder_dynamic_field_scope_enter(field,
+ MSGPACK_OBJECT_ARRAY);
+}
+
+int flb_log_event_encoder_dynamic_field_commit_map(
+ struct flb_log_event_encoder_dynamic_field *field)
+{
+ struct flb_log_event_encoder_dynamic_field_scope *scope;
+
+ scope = flb_log_event_encoder_dynamic_field_scope_current(field);
+
+ return flb_log_event_encoder_dynamic_field_scope_leave(field,
+ scope,
+ FLB_TRUE);
+}
+
+int flb_log_event_encoder_dynamic_field_commit_array(
+ struct flb_log_event_encoder_dynamic_field *field)
+{
+ struct flb_log_event_encoder_dynamic_field_scope *scope;
+
+ scope = flb_log_event_encoder_dynamic_field_scope_current(field);
+
+ return flb_log_event_encoder_dynamic_field_scope_leave(field,
+ scope,
+ FLB_TRUE);
+}
+
+int flb_log_event_encoder_dynamic_field_rollback_map(
+ struct flb_log_event_encoder_dynamic_field *field)
+{
+ struct flb_log_event_encoder_dynamic_field_scope *scope;
+
+ scope = flb_log_event_encoder_dynamic_field_scope_current(field);
+
+ return flb_log_event_encoder_dynamic_field_scope_leave(field,
+ scope,
+ FLB_FALSE);
+}
+
+int flb_log_event_encoder_dynamic_field_rollback_array(
+ struct flb_log_event_encoder_dynamic_field *field)
+{
+ struct flb_log_event_encoder_dynamic_field_scope *scope;
+
+ scope = flb_log_event_encoder_dynamic_field_scope_current(field);
+
+ return flb_log_event_encoder_dynamic_field_scope_leave(field,
+ scope,
+ FLB_TRUE);
+}
+
+int flb_log_event_encoder_dynamic_field_append(
+ struct flb_log_event_encoder_dynamic_field *field)
+{
+ struct flb_log_event_encoder_dynamic_field_scope *scope;
+
+ scope = flb_log_event_encoder_dynamic_field_scope_current(field);
+
+ if (scope == NULL) {
+ if (cfl_list_is_empty(&field->scopes)) {
+ return FLB_EVENT_ENCODER_SUCCESS;
+ }
+
+ return FLB_EVENT_ENCODER_ERROR_INVALID_ARGUMENT;
+ }
+
+ flb_mp_map_header_append(&scope->header);
+
+ return FLB_EVENT_ENCODER_SUCCESS;
+}
+
+
+static int flb_log_event_encoder_dynamic_field_flush_scopes(
+ struct flb_log_event_encoder_dynamic_field *field,
+ int commit)
+{
+ int result;
+ struct flb_log_event_encoder_dynamic_field_scope *scope;
+
+ result = FLB_EVENT_ENCODER_SUCCESS;
+
+ do {
+ scope = flb_log_event_encoder_dynamic_field_scope_current(field);
+
+ if (scope != NULL) {
+ result = flb_log_event_encoder_dynamic_field_scope_leave(field,
+ scope,
+ commit);
+ }
+ } while (scope != NULL &&
+ result == FLB_EVENT_ENCODER_SUCCESS);
+
+ return result;
+}
+
+int flb_log_event_encoder_dynamic_field_flush(
+ struct flb_log_event_encoder_dynamic_field *field)
+{
+ int result;
+
+ result = flb_log_event_encoder_dynamic_field_flush_scopes(field, FLB_TRUE);
+
+ if (result == FLB_EVENT_ENCODER_SUCCESS) {
+ field->data = field->buffer.data;
+ field->size = field->buffer.size;
+ }
+
+ return result;
+}
+
+int flb_log_event_encoder_dynamic_field_reset(
+ struct flb_log_event_encoder_dynamic_field *field)
+{
+ msgpack_sbuffer_clear(&field->buffer);
+
+ flb_log_event_encoder_dynamic_field_flush_scopes(field, FLB_FALSE);
+
+ field->data = NULL;
+ field->size = 0;
+
+ return FLB_EVENT_ENCODER_SUCCESS;
+}
+
+int flb_log_event_encoder_dynamic_field_init(
+ struct flb_log_event_encoder_dynamic_field *field,
+ int type)
+{
+ msgpack_sbuffer_init(&field->buffer);
+ msgpack_packer_init(&field->packer,
+ &field->buffer,
+ msgpack_sbuffer_write);
+
+ field->initialized = FLB_TRUE;
+ field->type = type;
+
+ cfl_list_init(&field->scopes);
+ flb_log_event_encoder_dynamic_field_reset(field);
+
+ return FLB_EVENT_ENCODER_SUCCESS;
+}
+
+void flb_log_event_encoder_dynamic_field_destroy(
+ struct flb_log_event_encoder_dynamic_field *field)
+{
+ msgpack_sbuffer_destroy(&field->buffer);
+
+ field->initialized = FLB_FALSE;
+}
diff --git a/fluent-bit/src/flb_log_event_encoder_primitives.c b/fluent-bit/src/flb_log_event_encoder_primitives.c
new file mode 100644
index 000000000..ca395e390
--- /dev/null
+++ b/fluent-bit/src/flb_log_event_encoder_primitives.c
@@ -0,0 +1,721 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_log_event_encoder.h>
+#include <fluent-bit/flb_log_event_encoder_primitives.h>
+#include <fluent-bit/flb_byteswap.h>
+#include <stdarg.h>
+
+static inline \
+int translate_msgpack_encoder_result(int value)
+{
+ if (value != 0) {
+ return FLB_EVENT_ENCODER_ERROR_SERIALIZATION_FAILURE;
+ }
+
+ return FLB_EVENT_ENCODER_SUCCESS;
+}
+
+int flb_log_event_encoder_append_value(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ int increment_entry_count,
+ int value_type,
+ char *value_buffer,
+ size_t value_length)
+{
+ int result;
+ struct flb_log_event_encoder_dynamic_field *field;
+
+ if (value_type < FLB_LOG_EVENT_STRING_MIN_VALUE_TYPE ||
+ value_type > FLB_LOG_EVENT_STRING_MAX_VALUE_TYPE) {
+ return FLB_EVENT_ENCODER_ERROR_INVALID_ARGUMENT;
+ }
+
+ result = flb_log_event_encoder_get_field(context, target_field, &field);
+
+ if (result == FLB_EVENT_ENCODER_SUCCESS) {
+ if (increment_entry_count) {
+ result = flb_log_event_encoder_dynamic_field_append(field);
+ }
+
+ if (result == FLB_EVENT_ENCODER_SUCCESS) {
+ if (value_type == FLB_LOG_EVENT_STRING_LENGTH_VALUE_TYPE) {
+ result = msgpack_pack_str(&field->packer, value_length);
+ }
+ else if (value_type == FLB_LOG_EVENT_BINARY_LENGTH_VALUE_TYPE) {
+ result = msgpack_pack_bin(&field->packer, value_length);
+ }
+ else if (value_type == FLB_LOG_EVENT_EXT_LENGTH_VALUE_TYPE) {
+ result = msgpack_pack_ext(&field->packer, value_length,
+ *((int8_t *) value_buffer));
+ }
+ else if (value_type == FLB_LOG_EVENT_NULL_VALUE_TYPE) {
+ result = msgpack_pack_nil(&field->packer);
+ }
+ else {
+ if (value_buffer == NULL) {
+ return FLB_EVENT_ENCODER_ERROR_INVALID_ARGUMENT;
+ }
+
+ if (value_type == FLB_LOG_EVENT_STRING_BODY_VALUE_TYPE) {
+ result = msgpack_pack_str_body(&field->packer,
+ value_buffer,
+ value_length);
+ }
+ else if (value_type == FLB_LOG_EVENT_BINARY_BODY_VALUE_TYPE) {
+ result = msgpack_pack_bin_body(&field->packer,
+ value_buffer,
+ value_length);
+ }
+ else if (value_type == FLB_LOG_EVENT_EXT_BODY_VALUE_TYPE) {
+ result = msgpack_pack_ext_body(&field->packer,
+ value_buffer,
+ value_length);
+ }
+ else if (value_type == FLB_LOG_EVENT_CHAR_VALUE_TYPE) {
+ result = msgpack_pack_char(&field->packer,
+ *((char *) value_buffer));
+ }
+ else if (value_type == FLB_LOG_EVENT_INT8_VALUE_TYPE) {
+ result = msgpack_pack_int8(&field->packer,
+ *((int8_t *) value_buffer));
+ }
+ else if (value_type == FLB_LOG_EVENT_INT16_VALUE_TYPE) {
+ result = msgpack_pack_int16(&field->packer,
+ *((int16_t *) value_buffer));
+ }
+ else if (value_type == FLB_LOG_EVENT_INT32_VALUE_TYPE) {
+ result = msgpack_pack_int32(&field->packer,
+ *((int32_t *) value_buffer));
+ }
+ else if (value_type == FLB_LOG_EVENT_INT64_VALUE_TYPE) {
+ result = msgpack_pack_int64(&field->packer,
+ *((int64_t *) value_buffer));
+ }
+ else if (value_type == FLB_LOG_EVENT_UINT8_VALUE_TYPE) {
+ result = msgpack_pack_uint8(&field->packer,
+ *((uint8_t *) value_buffer));
+ }
+ else if (value_type == FLB_LOG_EVENT_UINT16_VALUE_TYPE) {
+ result = msgpack_pack_uint16(&field->packer,
+ *((uint16_t *) value_buffer));
+ }
+ else if (value_type == FLB_LOG_EVENT_UINT32_VALUE_TYPE) {
+ result = msgpack_pack_uint32(&field->packer,
+ *((uint32_t *) value_buffer));
+ }
+ else if (value_type == FLB_LOG_EVENT_UINT64_VALUE_TYPE) {
+ result = msgpack_pack_uint64(&field->packer,
+ *((uint64_t *) value_buffer));
+ }
+ else if (value_type == FLB_LOG_EVENT_DOUBLE_VALUE_TYPE) {
+ result = msgpack_pack_double(&field->packer,
+ *((double *) value_buffer));
+ }
+ else if (value_type == FLB_LOG_EVENT_BOOLEAN_VALUE_TYPE) {
+ if (*((int *) value_buffer)) {
+ result = msgpack_pack_true(&field->packer);
+ }
+ else {
+ result = msgpack_pack_false(&field->packer);
+ }
+ }
+ else if (value_type == FLB_LOG_EVENT_MSGPACK_OBJECT_VALUE_TYPE) {
+ result = msgpack_pack_object(
+ &field->packer,
+ *((msgpack_object *) value_buffer));
+ }
+ else if (value_type == FLB_LOG_EVENT_MSGPACK_RAW_VALUE_TYPE) {
+ result = msgpack_pack_str_body(&field->packer,
+ value_buffer,
+ value_length);
+ }
+ else {
+ return FLB_EVENT_ENCODER_ERROR_INVALID_CONTEXT;
+ }
+
+ result = translate_msgpack_encoder_result(result);
+ }
+ }
+ }
+
+ return result;
+}
+
+int flb_log_event_encoder_append_binary_length(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ size_t length)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_TRUE,
+ FLB_LOG_EVENT_BINARY_LENGTH_VALUE_TYPE,
+ NULL, length);
+}
+
+int flb_log_event_encoder_append_binary_body(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ char *value,
+ size_t length)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_FALSE,
+ FLB_LOG_EVENT_BINARY_BODY_VALUE_TYPE,
+ value, length);
+}
+
+int flb_log_event_encoder_append_ext_length(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ int8_t type,
+ size_t length)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_TRUE,
+ FLB_LOG_EVENT_EXT_LENGTH_VALUE_TYPE,
+ (char *) &type, length);
+}
+
+int flb_log_event_encoder_append_ext_body(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ char *value,
+ size_t length)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_FALSE,
+ FLB_LOG_EVENT_EXT_BODY_VALUE_TYPE,
+ value, length);
+}
+
+int flb_log_event_encoder_append_string_length(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ size_t length)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_TRUE,
+ FLB_LOG_EVENT_STRING_LENGTH_VALUE_TYPE,
+ NULL, length);
+}
+
+int flb_log_event_encoder_append_string_body(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ char *value,
+ size_t length)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_FALSE,
+ FLB_LOG_EVENT_STRING_BODY_VALUE_TYPE,
+ value, length);
+}
+int flb_log_event_encoder_append_int8(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ int8_t value)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_TRUE,
+ FLB_LOG_EVENT_INT8_VALUE_TYPE,
+ (char *) &value, 0);
+}
+
+int flb_log_event_encoder_append_int16(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ int16_t value)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_TRUE,
+ FLB_LOG_EVENT_INT16_VALUE_TYPE,
+ (char *) &value, 0);
+}
+
+int flb_log_event_encoder_append_int32(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ int32_t value)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_TRUE,
+ FLB_LOG_EVENT_INT32_VALUE_TYPE,
+ (char *) &value, 0);
+}
+
+int flb_log_event_encoder_append_int64(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ int64_t value)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_TRUE,
+ FLB_LOG_EVENT_INT64_VALUE_TYPE,
+ (char *) &value, 0);
+}
+
+int flb_log_event_encoder_append_uint8(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ uint8_t value)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_TRUE,
+ FLB_LOG_EVENT_UINT8_VALUE_TYPE,
+ (char *) &value, 0);
+}
+
+int flb_log_event_encoder_append_uint16(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ uint16_t value)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_TRUE,
+ FLB_LOG_EVENT_UINT16_VALUE_TYPE,
+ (char *) &value, 0);
+}
+
+int flb_log_event_encoder_append_uint32(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ uint32_t value)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_TRUE,
+ FLB_LOG_EVENT_UINT32_VALUE_TYPE,
+ (char *) &value, 0);
+}
+
+int flb_log_event_encoder_append_uint64(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ uint64_t value)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_TRUE,
+ FLB_LOG_EVENT_UINT64_VALUE_TYPE,
+ (char *) &value, 0);
+}
+
+int flb_log_event_encoder_append_double(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ double value)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_TRUE,
+ FLB_LOG_EVENT_DOUBLE_VALUE_TYPE,
+ (char *) &value, 0);
+}
+
+int flb_log_event_encoder_append_boolean(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ int value)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_TRUE,
+ FLB_LOG_EVENT_BOOLEAN_VALUE_TYPE,
+ (char *) &value, 0);
+}
+
+int flb_log_event_encoder_append_null(
+ struct flb_log_event_encoder *context,
+ int target_field)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_TRUE,
+ FLB_LOG_EVENT_NULL_VALUE_TYPE,
+ NULL, 0);
+}
+
+int flb_log_event_encoder_append_character(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ char value)
+{
+ return flb_log_event_encoder_append_value(
+ context, target_field, FLB_TRUE,
+ FLB_LOG_EVENT_CHAR_VALUE_TYPE,
+ (char *) &value, 0);
+}
+
+int flb_log_event_encoder_append_binary(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ char *value,
+ size_t length)
+{
+ int result;
+
+ result = flb_log_event_encoder_append_binary_length(
+ context,
+ target_field,
+ length);
+
+ if (result == FLB_EVENT_ENCODER_SUCCESS) {
+ result = flb_log_event_encoder_append_binary_body(
+ context,
+ target_field,
+ value,
+ length);
+ }
+
+ return result;
+}
+
+int flb_log_event_encoder_append_string(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ char *value,
+ size_t length)
+{
+ int result;
+
+ result = flb_log_event_encoder_append_string_length(
+ context,
+ target_field,
+ length);
+
+ if (result == FLB_EVENT_ENCODER_SUCCESS) {
+ result = flb_log_event_encoder_append_string_body(
+ context,
+ target_field,
+ value,
+ length);
+ }
+
+ return result;
+}
+
+int flb_log_event_encoder_append_ext(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ int8_t type,
+ char *value,
+ size_t length)
+{
+ int result;
+
+ result = flb_log_event_encoder_append_ext_length(
+ context,
+ target_field,
+ type,
+ length);
+
+ if (result == FLB_EVENT_ENCODER_SUCCESS) {
+ result = flb_log_event_encoder_append_ext_body(
+ context,
+ target_field,
+ value,
+ length);
+ }
+
+ return result;
+}
+
+int flb_log_event_encoder_append_cstring(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ char *value)
+{
+ return flb_log_event_encoder_append_string(
+ context,
+ target_field,
+ value,
+ strlen(value));
+}
+
+int flb_log_event_encoder_append_msgpack_object(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ msgpack_object *value)
+{
+ const int value_type = FLB_LOG_EVENT_MSGPACK_OBJECT_VALUE_TYPE;
+
+ return flb_log_event_encoder_append_value(context, target_field,
+ FLB_TRUE, value_type,
+ (char *) value, 0);
+}
+
+int flb_log_event_encoder_append_raw_msgpack(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ char *value_buffer,
+ size_t value_size)
+{
+ const int value_type = FLB_LOG_EVENT_MSGPACK_RAW_VALUE_TYPE;
+
+ return flb_log_event_encoder_append_value(context, target_field,
+ FLB_TRUE, value_type,
+ value_buffer, value_size);
+}
+
+
+int flb_log_event_encoder_append_timestamp(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ struct flb_time *value)
+{
+ if (context->format == FLB_LOG_EVENT_FORMAT_FORWARD_LEGACY) {
+ return flb_log_event_encoder_append_legacy_timestamp(
+ context, target_field, value);
+ }
+ else if (context->format == FLB_LOG_EVENT_FORMAT_FORWARD) {
+ return flb_log_event_encoder_append_forward_v1_timestamp(
+ context, target_field, value);
+ }
+ else if (context->format == FLB_LOG_EVENT_FORMAT_FLUENT_BIT_V1) {
+ return flb_log_event_encoder_append_fluent_bit_v1_timestamp(
+ context, target_field, value);
+ }
+ else if (context->format == FLB_LOG_EVENT_FORMAT_FLUENT_BIT_V2) {
+ return flb_log_event_encoder_append_fluent_bit_v2_timestamp(
+ context, target_field, value);
+ }
+ else {
+ return FLB_EVENT_ENCODER_ERROR_INVALID_ARGUMENT;
+ }
+}
+
+int flb_log_event_encoder_append_legacy_timestamp(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ struct flb_time *value)
+{
+ const int value_type = FLB_LOG_EVENT_UINT64_VALUE_TYPE;
+ uint64_t timestamp;
+
+ timestamp = value->tm.tv_sec;
+
+ return flb_log_event_encoder_append_value(context, target_field,
+ FLB_TRUE, value_type,
+ (char *) &timestamp, 0);
+}
+
+int flb_log_event_encoder_append_forward_v1_timestamp(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ struct flb_time *timestamp)
+{
+ uint32_t value[2];
+
+ value[0] = FLB_BSWAP_32((uint32_t) timestamp->tm.tv_sec);
+ value[1] = FLB_BSWAP_32((uint32_t) timestamp->tm.tv_nsec);
+
+ return flb_log_event_encoder_append_ext(context, target_field,
+ 0, (char *) value, 8);
+}
+
+int flb_log_event_encoder_append_fluent_bit_v1_timestamp(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ struct flb_time *value)
+{
+ return flb_log_event_encoder_append_forward_v1_timestamp(context,
+ target_field,
+ value);
+}
+
+int flb_log_event_encoder_append_fluent_bit_v2_timestamp(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ struct flb_time *value)
+{
+ return flb_log_event_encoder_append_fluent_bit_v1_timestamp(context,
+ target_field,
+ value);
+}
+
+int flb_log_event_encoder_append_values_unsafe(
+ struct flb_log_event_encoder *context,
+ int target_field,
+ va_list arguments)
+{
+ int8_t current_ext_type;
+ size_t processed_values;
+ char *buffer_address;
+ int value_type;
+ int result;
+
+ processed_values = 0;
+ result = FLB_EVENT_ENCODER_SUCCESS;
+
+ for (processed_values = 0 ;
+ processed_values < FLB_EVENT_ENCODER_VALUE_LIMIT &&
+ result == FLB_EVENT_ENCODER_SUCCESS ;
+ processed_values++) {
+ value_type = va_arg(arguments, int);
+
+ if (value_type == FLB_LOG_EVENT_APPEND_TERMINATOR_VALUE_TYPE) {
+ break;
+ }
+ else if (value_type == FLB_LOG_EVENT_STRING_LENGTH_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_string_length(context,
+ target_field,
+ va_arg(arguments, size_t));
+ }
+ else if (value_type == FLB_LOG_EVENT_STRING_BODY_VALUE_TYPE) {
+ buffer_address = va_arg(arguments, char *);
+
+ result = flb_log_event_encoder_append_string_body(context,
+ target_field,
+ buffer_address,
+ va_arg(arguments, size_t));
+ }
+ else if (value_type == FLB_LOG_EVENT_BINARY_LENGTH_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_binary_length(context,
+ target_field,
+ va_arg(arguments, size_t));
+ }
+ else if (value_type == FLB_LOG_EVENT_BINARY_BODY_VALUE_TYPE) {
+ buffer_address = va_arg(arguments, char *);
+
+ result = flb_log_event_encoder_append_binary_body(context,
+ target_field,
+ buffer_address,
+ va_arg(arguments, size_t));
+ }
+ else if (value_type == FLB_LOG_EVENT_EXT_LENGTH_VALUE_TYPE) {
+ current_ext_type = (int8_t) va_arg(arguments, int);
+
+ result = flb_log_event_encoder_append_ext_length(context,
+ target_field,
+ current_ext_type,
+ va_arg(arguments, size_t));
+ }
+ else if (value_type == FLB_LOG_EVENT_EXT_BODY_VALUE_TYPE) {
+ buffer_address = va_arg(arguments, char *);
+
+ result = flb_log_event_encoder_append_ext_body(context,
+ target_field,
+ buffer_address,
+ va_arg(arguments, size_t));
+ }
+ else if (value_type == FLB_LOG_EVENT_NULL_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_null(context,
+ target_field);
+ }
+ else if (value_type == FLB_LOG_EVENT_CHAR_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_character(context,
+ target_field,
+ (char) va_arg(arguments, int));
+ }
+ else if (value_type == FLB_LOG_EVENT_INT8_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_int8(context,
+ target_field,
+ (int8_t) va_arg(arguments, int));
+ }
+ else if (value_type == FLB_LOG_EVENT_INT16_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_int16(context,
+ target_field,
+ (int16_t) va_arg(arguments, int));
+ }
+ else if (value_type == FLB_LOG_EVENT_INT32_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_int32(context,
+ target_field,
+ va_arg(arguments, int32_t));
+ }
+ else if (value_type == FLB_LOG_EVENT_INT64_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_int64(context,
+ target_field,
+ va_arg(arguments, int64_t));
+ }
+ else if (value_type == FLB_LOG_EVENT_UINT8_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_uint8(context,
+ target_field,
+ (uint8_t) va_arg(arguments, unsigned int));
+ }
+ else if (value_type == FLB_LOG_EVENT_UINT16_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_uint16(context,
+ target_field,
+ (uint16_t) va_arg(arguments, unsigned int));
+ }
+ else if (value_type == FLB_LOG_EVENT_UINT32_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_uint32(context,
+ target_field,
+ va_arg(arguments, uint32_t));
+ }
+ else if (value_type == FLB_LOG_EVENT_UINT64_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_uint64(context,
+ target_field,
+ va_arg(arguments, uint64_t));
+ }
+ else if (value_type == FLB_LOG_EVENT_DOUBLE_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_double(context,
+ target_field,
+ va_arg(arguments, double));
+ }
+ else if (value_type == FLB_LOG_EVENT_BOOLEAN_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_boolean(context,
+ target_field,
+ va_arg(arguments, int));
+ }
+ else if (value_type == FLB_LOG_EVENT_MSGPACK_OBJECT_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_msgpack_object(context,
+ target_field,
+ va_arg(arguments, msgpack_object *));
+ }
+ else if (value_type == FLB_LOG_EVENT_MSGPACK_RAW_VALUE_TYPE) {
+ buffer_address = va_arg(arguments, char *);
+
+ result = flb_log_event_encoder_append_raw_msgpack(context,
+ target_field,
+ buffer_address,
+ va_arg(arguments, size_t));
+ }
+ else if (value_type == FLB_LOG_EVENT_TIMESTAMP_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_timestamp(context,
+ target_field,
+ va_arg(arguments, struct flb_time *));
+ }
+ else if (value_type == FLB_LOG_EVENT_LEGACY_TIMESTAMP_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_legacy_timestamp(context,
+ target_field,
+ va_arg(arguments, struct flb_time *));
+ }
+ else if (value_type == FLB_LOG_EVENT_FORWARD_V1_TIMESTAMP_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_forward_v1_timestamp(context,
+ target_field,
+ va_arg(arguments, struct flb_time *));
+ }
+ else if (value_type == FLB_LOG_EVENT_FLUENT_BIT_V1_TIMESTAMP_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_fluent_bit_v1_timestamp(context,
+ target_field,
+ va_arg(arguments, struct flb_time *));
+ }
+ else if (value_type == FLB_LOG_EVENT_FLUENT_BIT_V2_TIMESTAMP_VALUE_TYPE) {
+ result = flb_log_event_encoder_append_fluent_bit_v2_timestamp(context,
+ target_field,
+ va_arg(arguments, struct flb_time *));
+ }
+ else {
+ result = FLB_EVENT_ENCODER_ERROR_INVALID_VALUE_TYPE;
+ }
+ }
+
+ if (processed_values >= FLB_EVENT_ENCODER_VALUE_LIMIT) {
+ flb_error("Log event encoder : value count limit exceeded");
+ }
+
+ return result;
+}
diff --git a/fluent-bit/src/flb_lua.c b/fluent-bit/src/flb_lua.c
new file mode 100644
index 000000000..e4df896c5
--- /dev/null
+++ b/fluent-bit/src/flb_lua.c
@@ -0,0 +1,841 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 "lua.h"
+#include "mpack/mpack.h"
+#include "msgpack/unpack.h"
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_time.h>
+#include <fluent-bit/flb_lua.h>
+#include <stdint.h>
+
+int flb_lua_enable_flb_null(lua_State *l)
+{
+ /* set flb.null */
+ lua_pushlightuserdata(l, NULL);
+
+ flb_info("[%s] set %s", __FUNCTION__, FLB_LUA_VAR_FLB_NULL);
+ lua_setglobal(l, FLB_LUA_VAR_FLB_NULL);
+
+ return 0;
+}
+
+void flb_lua_pushtimetable(lua_State *l, struct flb_time *tm)
+{
+ lua_createtable(l, 0, 2);
+
+ /* seconds */
+ lua_pushlstring(l, "sec", 3);
+ lua_pushinteger(l, tm->tm.tv_sec);
+ lua_settable(l, -3);
+
+ /* nanoseconds */
+ lua_pushlstring(l, "nsec", 4);
+ lua_pushinteger(l, tm->tm.tv_nsec);
+ lua_settable(l, -3);
+}
+
+int flb_lua_is_valid_func(lua_State *lua, flb_sds_t func)
+{
+ int ret = FLB_FALSE;
+
+ lua_getglobal(lua, func);
+ if (lua_isfunction(lua, -1)) {
+ ret = FLB_TRUE;
+ }
+ lua_pop(lua, -1); /* discard return value of isfunction */
+
+ return ret;
+}
+
+static int flb_lua_setmetatable(lua_State *l, struct flb_lua_metadata *meta, int index)
+{
+ int abs_index;
+
+ if (meta->initialized != FLB_TRUE) {
+ return -1;
+ }
+ abs_index = flb_lua_absindex(l, index);
+
+ lua_createtable(l, 0, 1);
+
+ /* data type */
+ lua_pushlstring(l, "type", 4);
+ lua_pushinteger(l, meta->data_type);
+ lua_settable(l, -3); /* point created table */
+
+ lua_setmetatable(l, abs_index);
+
+ return 0;
+}
+
+int flb_lua_pushmpack(lua_State *l, mpack_reader_t *reader)
+{
+ int ret = 0;
+ mpack_tag_t tag;
+ uint32_t length;
+ uint32_t i;
+ int index;
+ struct flb_lua_metadata meta;
+
+ tag = mpack_read_tag(reader);
+ switch (mpack_tag_type(&tag)) {
+ case mpack_type_nil:
+ lua_getglobal(l, FLB_LUA_VAR_FLB_NULL);
+ break;
+ case mpack_type_bool:
+ lua_pushboolean(l, mpack_tag_bool_value(&tag));
+ break;
+ case mpack_type_int:
+ lua_pushinteger(l, mpack_tag_int_value(&tag));
+ break;
+ case mpack_type_uint:
+ lua_pushinteger(l, mpack_tag_uint_value(&tag));
+ break;
+ case mpack_type_float:
+ lua_pushnumber(l, mpack_tag_float_value(&tag));
+ break;
+ case mpack_type_double:
+ lua_pushnumber(l, mpack_tag_double_value(&tag));
+ break;
+ case mpack_type_str:
+ case mpack_type_bin:
+ case mpack_type_ext:
+ length = mpack_tag_bytes(&tag);
+ lua_pushlstring(l, reader->data, length);
+ reader->data += length;
+ break;
+ case mpack_type_array:
+ flb_lua_metadata_init(&meta);
+ meta.data_type = FLB_LUA_L2C_TYPE_ARRAY;
+
+ length = mpack_tag_array_count(&tag);
+ lua_createtable(l, length, 0);
+ index = lua_gettop(l); /* save index of created table */
+ for (i = 0; i < length; i++) {
+ ret = flb_lua_pushmpack(l, reader);
+ if (ret) {
+ return ret;
+ }
+ lua_rawseti(l, -2, i+1);
+ }
+ flb_lua_setmetatable(l, &meta, index);
+
+ break;
+ case mpack_type_map:
+ flb_lua_metadata_init(&meta);
+ meta.data_type = FLB_LUA_L2C_TYPE_MAP;
+
+ length = mpack_tag_map_count(&tag);
+ lua_createtable(l, length, 0);
+ index = lua_gettop(l); /* save index of created table */
+ for (i = 0; i < length; i++) {
+ ret = flb_lua_pushmpack(l, reader);
+ if (ret) {
+ return ret;
+ }
+ ret = flb_lua_pushmpack(l, reader);
+ if (ret) {
+ return ret;
+ }
+ lua_settable(l, -3);
+ }
+ flb_lua_setmetatable(l, &meta, index);
+
+ break;
+ default:
+ return -1;
+ }
+ return 0;
+}
+
+void flb_lua_pushmsgpack(lua_State *l, msgpack_object *o)
+{
+ int i;
+ int size;
+ int index;
+ struct flb_lua_metadata meta;
+
+ lua_checkstack(l, 3);
+
+ switch(o->type) {
+ case MSGPACK_OBJECT_NIL:
+ lua_getglobal(l, FLB_LUA_VAR_FLB_NULL);
+ break;
+
+ case MSGPACK_OBJECT_BOOLEAN:
+ lua_pushboolean(l, o->via.boolean);
+ break;
+
+ case MSGPACK_OBJECT_POSITIVE_INTEGER:
+ lua_pushinteger(l, (double) o->via.u64);
+ break;
+
+ case MSGPACK_OBJECT_NEGATIVE_INTEGER:
+ lua_pushinteger(l, (double) o->via.i64);
+ break;
+
+ case MSGPACK_OBJECT_FLOAT32:
+ case MSGPACK_OBJECT_FLOAT64:
+ lua_pushnumber(l, (double) o->via.f64);
+ break;
+
+ case MSGPACK_OBJECT_STR:
+ lua_pushlstring(l, o->via.str.ptr, o->via.str.size);
+ break;
+
+ case MSGPACK_OBJECT_BIN:
+ lua_pushlstring(l, o->via.bin.ptr, o->via.bin.size);
+ break;
+
+ case MSGPACK_OBJECT_EXT:
+ lua_pushlstring(l, o->via.ext.ptr, o->via.ext.size);
+ break;
+
+ case MSGPACK_OBJECT_ARRAY:
+ flb_lua_metadata_init(&meta);
+ meta.data_type = FLB_LUA_L2C_TYPE_ARRAY;
+
+ size = o->via.array.size;
+ lua_createtable(l, size, 0);
+ index = lua_gettop(l); /* save index of created table */
+ if (size != 0) {
+ msgpack_object *p = o->via.array.ptr;
+ for (i = 0; i < size; i++) {
+ flb_lua_pushmsgpack(l, p+i);
+ lua_rawseti (l, index, i+1);
+ }
+ }
+ flb_lua_setmetatable(l, &meta, index);
+ break;
+
+ case MSGPACK_OBJECT_MAP:
+ flb_lua_metadata_init(&meta);
+ meta.data_type = FLB_LUA_L2C_TYPE_MAP;
+
+ size = o->via.map.size;
+ lua_createtable(l, 0, size);
+ index = lua_gettop(l); /* save index of created table */
+ if (size != 0) {
+ msgpack_object_kv *p = o->via.map.ptr;
+ for (i = 0; i < size; i++) {
+ flb_lua_pushmsgpack(l, &(p+i)->key);
+ flb_lua_pushmsgpack(l, &(p+i)->val);
+ lua_settable(l, index);
+ }
+ }
+ flb_lua_setmetatable(l, &meta, index);
+ break;
+ }
+}
+
+static int lua_isinteger(lua_State *L, int index)
+{
+ lua_Number n;
+ lua_Integer i;
+
+ if (lua_type(L, index) == LUA_TNUMBER) {
+ n = lua_tonumber(L, index);
+ i = lua_tointeger(L, index);
+
+ if (i == n) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * This function is to call lua function table.maxn.
+ * CAUTION: table.maxn is removed from Lua 5.2.
+ * If we update luajit which is based Lua 5.2+,
+ * this function should be removed.
+*/
+static int lua_table_maxn(lua_State *l, int index)
+{
+#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM < 520
+ int ret = -1;
+ if (lua_type(l, index) != LUA_TTABLE) {
+ return -1;
+ }
+
+ lua_getglobal(l, "table");
+ lua_getfield(l, -1, "maxn");
+ lua_remove(l, -2); /* remove table (lua_getglobal(L, "table")) */
+ lua_pushvalue(l, index); /* copy record to top of stack */
+ ret = lua_pcall(l, 1, 1, 0);
+ if (ret < 0) {
+ flb_error("[filter_lua] failed to exec table.maxn ret=%d", ret);
+ return -1;
+ }
+ if (lua_type(l, -1) != LUA_TNUMBER) {
+ flb_error("[filter_lua] not LUA_TNUMBER");
+ lua_pop(l, 1);
+ return -1;
+ }
+
+ if (lua_isinteger(l, -1)) {
+ ret = lua_tointeger(l, -1);
+ }
+ lua_pop(l, 1);
+
+ return ret;
+#else
+ return (int)lua_rawlen(l, index);
+#endif
+}
+
+int flb_lua_arraylength(lua_State *l, int index)
+{
+ lua_Integer n;
+ int count = 0;
+ int max = 0;
+ int ret = 0;
+
+ index = flb_lua_absindex(l, index);
+
+ ret = lua_table_maxn(l, index);
+ if (ret > 0) {
+ return ret;
+ }
+
+ lua_pushnil(l);
+ while (lua_next(l, index) != 0) {
+ if (lua_type(l, -2) == LUA_TNUMBER) {
+ n = lua_tonumber(l, -2);
+ if (n > 0) {
+ max = n > max ? n : max;
+ count++;
+ lua_pop(l, 1);
+ continue;
+ }
+ }
+ lua_pop(l, 2);
+ return -1;
+ }
+ if (max != count) {
+ return -1;
+ }
+ return max;
+}
+
+static void lua_toarray_msgpack(lua_State *l,
+ msgpack_packer *pck,
+ int index,
+ struct flb_lua_l2c_config *l2cc)
+{
+ int len;
+ int i;
+
+ lua_pushnumber(l, (lua_Number)lua_objlen(l, -1)); // lua_len
+ len = (int)lua_tointeger(l, -1);
+ lua_pop(l, 1);
+
+ msgpack_pack_array(pck, len);
+ for (i = 1; i <= len; i++) {
+ lua_rawgeti(l, -1, i);
+ flb_lua_tomsgpack(l, pck, 0, l2cc);
+ lua_pop(l, 1);
+ }
+}
+
+static void lua_toarray_mpack(lua_State *l,
+ mpack_writer_t *writer,
+ int index,
+ struct flb_lua_l2c_config *l2cc)
+{
+ int len;
+ int i;
+
+ lua_pushnumber(l, (lua_Number)lua_objlen(l, -1)); // lua_len
+ len = (int)lua_tointeger(l, -1);
+ lua_pop(l, 1);
+
+ mpack_write_tag(writer, mpack_tag_array(len));
+ for (i = 1; i <= len; i++) {
+ lua_rawgeti(l, -1, i);
+ flb_lua_tompack(l, writer, 0, l2cc);
+ lua_pop(l, 1);
+ }
+}
+
+static void try_to_convert_data_type(lua_State *l,
+ msgpack_packer *pck,
+ struct flb_lua_l2c_config *l2cc)
+{
+ size_t len;
+ const char *tmp = NULL;
+
+ struct mk_list *tmp_list = NULL;
+ struct mk_list *head = NULL;
+ struct flb_lua_l2c_type *l2c = NULL;
+
+ // convert to int
+ if ((lua_type(l, -2) == LUA_TSTRING)
+ && lua_type(l, -1) == LUA_TNUMBER){
+ tmp = lua_tolstring(l, -2, &len);
+
+ mk_list_foreach_safe(head, tmp_list, &l2cc->l2c_types) {
+ l2c = mk_list_entry(head, struct flb_lua_l2c_type, _head);
+ if (!strncmp(l2c->key, tmp, len) && l2c->type == FLB_LUA_L2C_TYPE_INT) {
+ flb_lua_tomsgpack(l, pck, -1, l2cc);
+ msgpack_pack_int64(pck, (int64_t)lua_tonumber(l, -1));
+ return;
+ }
+ }
+ }
+ else if ((lua_type(l, -2) == LUA_TSTRING)
+ && lua_type(l, -1) == LUA_TTABLE){
+ tmp = lua_tolstring(l, -2, &len);
+
+ mk_list_foreach_safe(head, tmp_list, &l2cc->l2c_types) {
+ l2c = mk_list_entry(head, struct flb_lua_l2c_type, _head);
+ if (!strncmp(l2c->key, tmp, len) && l2c->type == FLB_LUA_L2C_TYPE_ARRAY) {
+ flb_lua_tomsgpack(l, pck, -1, l2cc);
+ lua_toarray_msgpack(l, pck, 0, l2cc);
+ return;
+ }
+ }
+ }
+
+ /* not matched */
+ flb_lua_tomsgpack(l, pck, -1, l2cc);
+ flb_lua_tomsgpack(l, pck, 0, l2cc);
+}
+
+static void try_to_convert_data_type_mpack(lua_State *l,
+ mpack_writer_t *writer,
+ struct flb_lua_l2c_config *l2cc)
+{
+ size_t len;
+ const char *tmp = NULL;
+
+ struct mk_list *tmp_list = NULL;
+ struct mk_list *head = NULL;
+ struct flb_lua_l2c_type *l2c = NULL;
+
+ // convert to int
+ if ((lua_type(l, -2) == LUA_TSTRING)
+ && lua_type(l, -1) == LUA_TNUMBER){
+ tmp = lua_tolstring(l, -2, &len);
+
+ mk_list_foreach_safe(head, tmp_list, &l2cc->l2c_types) {
+ l2c = mk_list_entry(head, struct flb_lua_l2c_type, _head);
+ if (!strncmp(l2c->key, tmp, len) && l2c->type == FLB_LUA_L2C_TYPE_INT) {
+ flb_lua_tompack(l, writer, -1, l2cc);
+ mpack_write_int(writer, (int64_t)lua_tonumber(l, -1));
+ return;
+ }
+ }
+ }
+ else if ((lua_type(l, -2) == LUA_TSTRING)
+ && lua_type(l, -1) == LUA_TTABLE){
+ tmp = lua_tolstring(l, -2, &len);
+
+ mk_list_foreach_safe(head, tmp_list, &l2cc->l2c_types) {
+ l2c = mk_list_entry(head, struct flb_lua_l2c_type, _head);
+ if (!strncmp(l2c->key, tmp, len) && l2c->type == FLB_LUA_L2C_TYPE_ARRAY) {
+ flb_lua_tompack(l, writer, -1, l2cc);
+ lua_toarray_mpack(l, writer, 0, l2cc);
+ return;
+ }
+ }
+ }
+
+ /* not matched */
+ flb_lua_tompack(l, writer, -1, l2cc);
+ flb_lua_tompack(l, writer, 0, l2cc);
+}
+
+static int flb_lua_getmetatable(lua_State *l, int index, struct flb_lua_metadata *meta)
+{
+ int lua_ret;
+ int abs_index;
+ const char *str;
+ size_t len;
+
+ if (meta->initialized != FLB_TRUE) {
+ return -1;
+ }
+
+ lua_ret = lua_getmetatable(l, index);
+ if (lua_ret == 0) {
+ /* no metadata */
+ return -1;
+ }
+ else if (lua_type(l, -1) != LUA_TTABLE) {
+ /* invalid metatable? */
+ lua_pop(l, 1);
+ return -1;
+ }
+
+ lua_pushnil(l);
+ abs_index = flb_lua_absindex(l, -2);
+ while (lua_next(l, abs_index) != 0) {
+ if (lua_type(l, -2) != LUA_TSTRING) {
+ /* key is not a string */
+ flb_debug("key is not a string");
+ lua_pop(l, 1);
+ continue;
+ }
+
+ str = lua_tolstring(l, -2, &len); /* key */
+
+ if (len == 4 && strncmp(str, "type", 4) == 0) {
+ /* data_type */
+ if (lua_type(l, -1) != LUA_TNUMBER) {
+ /* value is not data type */
+ flb_debug("type is not num. type=%s", lua_typename(l, lua_type(l, -1)));
+ lua_pop(l, 1);
+ continue;
+ }
+ meta->data_type = (int)lua_tointeger(l, -1);
+ }
+ lua_pop(l, 1);
+ }
+ lua_pop(l, 1); /* pop metatable */
+
+ return 0;
+}
+
+static void lua_tomap_mpack(lua_State *l,
+ mpack_writer_t *writer,
+ int index,
+ struct flb_lua_l2c_config *l2cc)
+{
+ int len;
+
+ len = 0;
+ lua_pushnil(l);
+ while (lua_next(l, -2) != 0) {
+ lua_pop(l, 1);
+ len++;
+ }
+ mpack_write_tag(writer, mpack_tag_map(len));
+
+ lua_pushnil(l);
+
+ if (l2cc->l2c_types_num > 0) {
+ /* type conversion */
+ while (lua_next(l, -2) != 0) {
+ try_to_convert_data_type_mpack(l, writer, l2cc);
+ lua_pop(l, 1);
+ }
+ } else {
+ while (lua_next(l, -2) != 0) {
+ flb_lua_tompack(l, writer, -1, l2cc);
+ flb_lua_tompack(l, writer, 0, l2cc);
+ lua_pop(l, 1);
+ }
+ }
+}
+
+void flb_lua_tompack(lua_State *l,
+ mpack_writer_t *writer,
+ int index,
+ struct flb_lua_l2c_config *l2cc)
+{
+ int len;
+ int i;
+ int use_metatable = FLB_FALSE;
+ struct flb_lua_metadata meta;
+
+ switch (lua_type(l, -1 + index)) {
+ case LUA_TSTRING:
+ {
+ const char *str;
+ size_t len;
+
+ str = lua_tolstring(l, -1 + index, &len);
+
+ mpack_write_str(writer, str, len);
+ }
+ break;
+ case LUA_TNUMBER:
+ {
+ if (lua_isinteger(l, -1 + index)) {
+ int64_t num = lua_tointeger(l, -1 + index);
+ mpack_write_int(writer, num);
+ }
+ else {
+ double num = lua_tonumber(l, -1 + index);
+ mpack_write_double(writer, num);
+ }
+ }
+ break;
+ case LUA_TBOOLEAN:
+ if (lua_toboolean(l, -1 + index))
+ mpack_write_true(writer);
+ else
+ mpack_write_false(writer);
+ break;
+ case LUA_TTABLE:
+ flb_lua_metadata_init(&meta);
+ if (flb_lua_getmetatable(l, -1 + index, &meta) == 0 &&
+ meta.data_type >= 0) {
+ use_metatable = FLB_TRUE;
+ }
+ if (use_metatable) {
+ if (meta.data_type == FLB_LUA_L2C_TYPE_ARRAY) {
+ /* array */
+ lua_toarray_mpack(l, writer, 0, l2cc);
+ }
+ else {
+ /* map */
+ lua_tomap_mpack(l, writer, -1 + index, l2cc);
+ }
+ break;
+ }
+
+ len = flb_lua_arraylength(l, -1 + index);
+ if (len > 0) {
+ mpack_write_tag(writer, mpack_tag_array(len));
+ for (i = 1; i <= len; i++) {
+ lua_rawgeti(l, -1, i);
+ flb_lua_tompack(l, writer, 0, l2cc);
+ lua_pop(l, 1);
+ }
+ }
+ else {
+ lua_tomap_mpack(l, writer, -1 + index, l2cc);
+ }
+ break;
+ case LUA_TNIL:
+ mpack_write_nil(writer);
+ break;
+
+ case LUA_TLIGHTUSERDATA:
+ if (lua_touserdata(l, -1 + index) == NULL) {
+ mpack_write_nil(writer);
+ break;
+ }
+ case LUA_TFUNCTION:
+ case LUA_TUSERDATA:
+ case LUA_TTHREAD:
+ /* cannot serialize */
+ break;
+ }
+}
+
+static inline void lua_tomap_msgpack(lua_State *l,
+ msgpack_packer *pck,
+ int index,
+ struct flb_lua_l2c_config *l2cc)
+{
+ int len;
+ int abs_index;
+
+ abs_index = flb_lua_absindex(l, index);
+
+ len = 0;
+ lua_pushnil(l);
+ while (lua_next(l, abs_index) != 0) {
+ lua_pop(l, 1);
+ len++;
+ }
+ msgpack_pack_map(pck, len);
+
+ lua_pushnil(l);
+
+ if (l2cc->l2c_types_num > 0) {
+ /* type conversion */
+ while (lua_next(l, abs_index) != 0) {
+ try_to_convert_data_type(l, pck, l2cc);
+ lua_pop(l, 1);
+ }
+ } else {
+ while (lua_next(l, abs_index) != 0) {
+ flb_lua_tomsgpack(l, pck, -1, l2cc);
+ flb_lua_tomsgpack(l, pck, 0, l2cc);
+ lua_pop(l, 1);
+ }
+ }
+}
+
+void flb_lua_tomsgpack(lua_State *l,
+ msgpack_packer *pck,
+ int index,
+ struct flb_lua_l2c_config *l2cc)
+{
+ int len;
+ int i;
+ int use_metatable = FLB_FALSE;
+ struct flb_lua_metadata meta;
+
+ switch (lua_type(l, -1 + index)) {
+ case LUA_TSTRING:
+ {
+ const char *str;
+ size_t len;
+
+ str = lua_tolstring(l, -1 + index, &len);
+
+ msgpack_pack_str(pck, len);
+ msgpack_pack_str_body(pck, str, len);
+ }
+ break;
+ case LUA_TNUMBER:
+ {
+ if (lua_isinteger(l, -1 + index)) {
+ int64_t num = lua_tointeger(l, -1 + index);
+ msgpack_pack_int64(pck, num);
+ }
+ else {
+ double num = lua_tonumber(l, -1 + index);
+ msgpack_pack_double(pck, num);
+ }
+ }
+ break;
+ case LUA_TBOOLEAN:
+ if (lua_toboolean(l, -1 + index))
+ msgpack_pack_true(pck);
+ else
+ msgpack_pack_false(pck);
+ break;
+ case LUA_TTABLE:
+ flb_lua_metadata_init(&meta);
+ if (flb_lua_getmetatable(l, -1 + index, &meta) == 0 &&
+ meta.data_type >= 0) {
+ use_metatable = FLB_TRUE;
+ }
+ if (use_metatable) {
+ if (meta.data_type == FLB_LUA_L2C_TYPE_ARRAY) {
+ /* array */
+ lua_toarray_msgpack(l, pck, 0, l2cc);
+ }
+ else {
+ /* map */
+ lua_tomap_msgpack(l, pck, -1 + index, l2cc);
+ }
+ break;
+ }
+
+ len = flb_lua_arraylength(l, -1 + index);
+ if (len > 0) {
+ msgpack_pack_array(pck, len);
+ for (i = 1; i <= len; i++) {
+ lua_rawgeti(l, -1, i);
+ flb_lua_tomsgpack(l, pck, 0, l2cc);
+ lua_pop(l, 1);
+ }
+ }
+ else {
+ lua_tomap_msgpack(l, pck, -1 + index, l2cc);
+ }
+ break;
+ case LUA_TNIL:
+ msgpack_pack_nil(pck);
+ break;
+
+ case LUA_TLIGHTUSERDATA:
+ if (lua_touserdata(l, -1 + index) == NULL) {
+ msgpack_pack_nil(pck);
+ break;
+ }
+ case LUA_TFUNCTION:
+ case LUA_TUSERDATA:
+ case LUA_TTHREAD:
+ /* cannot serialize */
+ break;
+ }
+}
+
+static void print_lua_value(FILE *out, lua_State *l, int index, int depth)
+{
+ int i;
+ int i_depth;
+ int type;
+ size_t len_s;
+ double val_d;
+ int64_t val_i;
+ int len_t;
+
+ index = flb_lua_absindex(l, index);
+
+ type = lua_type(l, index);
+ fprintf(out, "%s:", lua_typename(l, type));
+ switch(type){
+ case LUA_TSTRING:
+ fprintf(out, " %s\n", lua_tolstring(l,index, &len_s));
+ break;
+ case LUA_TBOOLEAN:
+ fprintf(out, " %s\n", lua_toboolean(l, index) ? "true":"false");
+ break;
+ case LUA_TNUMBER:
+ val_i = lua_tointeger(l, index);
+ val_d = lua_tonumber(l, index);
+ fprintf(out, " d=%lf i=%ld\n", val_d, val_i);
+ break;
+ case LUA_TTABLE:
+ len_t = flb_lua_arraylength(l, index);
+ fprintf(out, " size=%d ", len_t);
+ if (len_t > 0) {
+ fprintf(out, "array\n");
+ for (i=1; i<=len_t; i++) {
+ for (i_depth=0; i_depth<depth; i_depth++) {
+ fputc(' ', stdout);
+ }
+ fprintf(out, "%03d: ", i);
+ lua_rawgeti(l, index, i);
+ print_lua_value(out, l, -1, depth+2);
+ lua_pop(l, 1);
+ }
+ fprintf(out, "\n");
+ break;
+ }
+
+ lua_pushnil(l);
+ fprintf(out, "map\n");
+ while (lua_next(l, index) != 0) {
+ for (i_depth=0; i_depth<depth; i_depth++) {
+ fputc(' ', stdout);
+ }
+ fprintf(out, "val: ");
+ print_lua_value(out, l,-1, depth+2); /* val */
+ for (i_depth=0; i_depth<depth; i_depth++) {
+ fputc(' ', stdout);
+ }
+ fprintf(out, "key: ");
+ print_lua_value(out, l,-2, depth+2); /* key */
+ lua_pop(l, 1); /* pop value */
+ }
+
+ break;
+ default:
+ fprintf(out, " (not supported value)\n");
+ }
+}
+
+void flb_lua_dump_stack(FILE *out, lua_State *l)
+{
+ int top;
+ int i;
+
+ top = lua_gettop(l);
+ if (top == 0) {
+ fprintf(out, "stack is empty\n");
+ return;
+ }
+ fprintf(out, "top index =%d ======\n", top);
+ for (i=top; i>=1; i--) {
+ fprintf(out, "%03d: ", i);
+ print_lua_value(out, l, i, 2);
+ }
+ fprintf(out, "======\n");
+}
diff --git a/fluent-bit/src/flb_luajit.c b/fluent-bit/src/flb_luajit.c
new file mode 100644
index 000000000..9c8372f87
--- /dev/null
+++ b/fluent-bit/src/flb_luajit.c
@@ -0,0 +1,97 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_luajit.h>
+
+struct flb_luajit *flb_luajit_create(struct flb_config *config)
+{
+ struct flb_luajit *lj;
+
+ lj = flb_malloc(sizeof(struct flb_luajit));
+ if (!lj) {
+ flb_errno();
+ return NULL;
+ }
+
+ lj->state = luaL_newstate();
+ if (!lj->state) {
+ flb_error("[luajit] error creating new context");
+ flb_free(lj);
+ return NULL;
+ }
+ luaL_openlibs(lj->state);
+ lj->config = config;
+ mk_list_add(&lj->_head, &config->luajit_list);
+
+ return lj;
+}
+
+int flb_luajit_load_script(struct flb_luajit *lj, char *script)
+{
+ int ret;
+
+ ret = luaL_loadfile(lj->state, script);
+ if (ret != 0) {
+ flb_error("[luajit] error loading script: %s",
+ lua_tostring(lj->state, -1));
+ return -1;
+ }
+
+ return 0;
+}
+
+int flb_luajit_load_buffer(struct flb_luajit *lj, char *string, size_t len, char *name)
+{
+ int ret;
+
+ ret = luaL_loadbuffer(lj->state, string, len, name);
+ if (ret != 0) {
+ flb_error("[luajit] error loading buffer: %s",
+ lua_tostring(lj->state, -1));
+ return -1;
+ }
+
+ return 0;
+}
+
+void flb_luajit_destroy(struct flb_luajit *lj)
+{
+ lua_close(lj->state);
+ mk_list_del(&lj->_head);
+ flb_free(lj);
+}
+
+int flb_luajit_destroy_all(struct flb_config *ctx)
+{
+ int c = 0;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_luajit *lj;
+
+ mk_list_foreach_safe(head, tmp, &ctx->luajit_list) {
+ lj = mk_list_entry(head, struct flb_luajit, _head);
+ flb_luajit_destroy(lj);
+ c++;
+ }
+
+ return c;
+}
diff --git a/fluent-bit/src/flb_meta.c b/fluent-bit/src/flb_meta.c
new file mode 100644
index 000000000..ed3f52ffc
--- /dev/null
+++ b/fluent-bit/src/flb_meta.c
@@ -0,0 +1,79 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_env.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_meta.h>
+
+/*
+ * A meta is a way to extend the configuration through specific commands, e.g:
+ *
+ * @SET a=b
+ *
+ * the meta command is prefixed with @, the command it self is 'SET' and have
+ * the parameters 'a=b'.
+ *
+ * Each command have their own handler function: meta_cmd_ABC().
+ */
+
+/* @SET command: register a key/value as a configuration variable */
+static int meta_cmd_set(struct flb_config *ctx, const char *params)
+{
+ int ret;
+ int len;
+ char *p;
+ char *key;
+ char *val;
+
+ p = strchr(params, '=');
+ if (!p) {
+ fprintf(stderr, "[meta SET] invalid parameter '%s'\n", params);
+ return -1;
+ }
+
+ len = strlen(params);
+ key = mk_string_copy_substr(params, 0, p - params);
+ if (!key) {
+ return -1;
+ }
+
+ val = mk_string_copy_substr(params, (p - params) + 1, len);
+ if (!val) {
+ flb_free(key);
+ return -1;
+ }
+
+ /* Set the variable in our local environment */
+ ret = flb_env_set(ctx->env, key, val);
+ flb_free(key);
+ flb_free(val);
+
+ return ret;
+}
+
+/* Run a specific command */
+int flb_meta_run(struct flb_config *ctx, const char *cmd, const char *params)
+{
+ if (strcasecmp(cmd, "SET") == 0) {
+ return meta_cmd_set(ctx, params);
+ }
+
+ return -1;
+}
diff --git a/fluent-bit/src/flb_metrics.c b/fluent-bit/src/flb_metrics.c
new file mode 100644
index 000000000..9a9e9c7e7
--- /dev/null
+++ b/fluent-bit/src/flb_metrics.c
@@ -0,0 +1,365 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+/*
+ * Metrics interface is a helper to gather general metrics from the core or
+ * plugins at runtime.
+ */
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_version.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_metrics.h>
+#include <msgpack.h>
+
+static int id_exists(int id, struct flb_metrics *metrics)
+{
+ struct mk_list *head;
+ struct flb_metric *metric;
+
+ mk_list_foreach(head, &metrics->list) {
+ metric = mk_list_entry(head, struct flb_metric, _head);
+ if (metric->id == id) {
+ return FLB_TRUE;
+ }
+ }
+
+ return FLB_FALSE;
+}
+
+static int id_get(struct flb_metrics *metrics)
+{
+ int id;
+ int ret = FLB_FALSE;
+
+ /* Try to use 'count' as an id */
+ id = metrics->count;
+
+ while ((ret = id_exists(id, metrics)) == FLB_TRUE) {
+ id++;
+ }
+
+ return id;
+}
+
+struct flb_metric *flb_metrics_get_id(int id, struct flb_metrics *metrics)
+{
+ struct mk_list *head;
+ struct flb_metric *m;
+
+ mk_list_foreach(head, &metrics->list) {
+ m = mk_list_entry(head, struct flb_metric, _head);
+ if (m->id == id) {
+ return m;
+ }
+ }
+
+ return NULL;
+}
+
+struct flb_metrics *flb_metrics_create(const char *title)
+{
+ int ret;
+ struct flb_metrics *metrics;
+
+ /* Create a metrics parent context */
+ metrics = flb_malloc(sizeof(struct flb_metrics));
+ if (!metrics) {
+ flb_errno();
+ return NULL;
+ }
+ metrics->count = 0;
+
+ /* Set metrics title */
+ ret = flb_metrics_title(title, metrics);
+ if (ret == -1) {
+ flb_free(metrics);
+ return NULL;
+ }
+
+ /* List head for specific metrics under the context */
+ mk_list_init(&metrics->list);
+ return metrics;
+}
+
+int flb_metrics_title(const char *title, struct flb_metrics *metrics)
+{
+ int ret;
+ size_t size = sizeof(metrics->title) - 1;
+
+ ret = snprintf(metrics->title, size, "%s", title);
+ if (ret == -1) {
+ flb_errno();
+ return -1;
+ }
+ else if (ret >= size){
+ flb_warn("[%s] title '%s' was truncated", __FUNCTION__, title);
+ }
+ metrics->title_len = strlen(metrics->title);
+ return 0;
+}
+
+int flb_metrics_add(int id, const char *title, struct flb_metrics *metrics)
+{
+ int ret;
+ struct flb_metric *m;
+ size_t size;
+
+ /* Create context */
+ m = flb_malloc(sizeof(struct flb_metric));
+ if (!m) {
+ flb_errno();
+ return -1;
+ }
+ m->val = 0;
+ size = sizeof(m->title) - 1;
+
+ /* Write title */
+ ret = snprintf(m->title, size, "%s", title);
+ if (ret == -1) {
+ flb_errno();
+ flb_free(m);
+ return -1;
+ }
+ else if (ret >= size) {
+ flb_warn("[%s] title '%s' was truncated", __FUNCTION__, title);
+ }
+
+ m->title_len = strlen(m->title);
+
+ /* Assign an ID */
+ if (id >= 0) {
+ /* Check this new ID is available */
+ if (id_exists(id, metrics) == FLB_TRUE) {
+ flb_error("[metrics] id=%i already exists for metric '%s'",
+ id, metrics->title);
+ flb_free(m);
+ return -1;
+ }
+ }
+ else {
+ id = id_get(metrics);
+ }
+
+ /* Link to parent list */
+ mk_list_add(&m->_head, &metrics->list);
+ m->id = id;
+ metrics->count++;
+
+ return id;
+}
+
+int flb_metrics_sum(int id, size_t val, struct flb_metrics *metrics)
+{
+ struct flb_metric *m;
+
+ m = flb_metrics_get_id(id, metrics);
+ if (!m) {
+ return -1;
+ }
+
+ m->val += val;
+ return 0;
+}
+
+int flb_metrics_destroy(struct flb_metrics *metrics)
+{
+ int count = 0;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_metric *m;
+
+ mk_list_foreach_safe(head, tmp, &metrics->list) {
+ m = mk_list_entry(head, struct flb_metric, _head);
+ mk_list_del(&m->_head);
+ flb_free(m);
+ count++;
+ }
+
+ flb_free(metrics);
+ return count;
+}
+
+int flb_metrics_print(struct flb_metrics *metrics)
+{
+ struct mk_list *head;
+ struct flb_metric *m;
+
+ printf("[metric dump] title => '%s'", metrics->title);
+
+ mk_list_foreach(head, &metrics->list) {
+ m = mk_list_entry(head, struct flb_metric, _head);
+ printf(", '%s' => %lu", m->title, m->val);
+ }
+ printf("\n");
+
+ return 0;
+}
+
+/* Write metrics in messagepack format */
+int flb_metrics_dump_values(char **out_buf, size_t *out_size,
+ struct flb_metrics *me)
+{
+ struct mk_list *head;
+ struct flb_metric *m;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+
+ /* Prepare new outgoing buffer */
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ msgpack_pack_map(&mp_pck, me->count);
+
+ mk_list_foreach(head, &me->list) {
+ m = mk_list_entry(head, struct flb_metric, _head);
+ msgpack_pack_str(&mp_pck, m->title_len);
+ msgpack_pack_str_body(&mp_pck, m->title, m->title_len);
+ msgpack_pack_uint64(&mp_pck, m->val);
+ }
+
+ *out_buf = mp_sbuf.data;
+ *out_size = mp_sbuf.size;
+
+ return 0;
+}
+
+static int attach_uptime(struct flb_config *ctx, struct cmt *cmt,
+ uint64_t ts, char *hostname)
+{
+ double uptime;
+ struct cmt_counter *c;
+
+ /* uptime */
+ c = cmt_counter_create(cmt, "fluentbit", "", "uptime",
+ "Number of seconds that Fluent Bit has been running.",
+ 1, (char *[]) {"hostname"});
+ if (!c) {
+ return -1;
+ }
+
+ uptime = time(NULL) - ctx->init_time;
+
+ cmt_counter_set(c, ts, uptime, 1, (char *[]) {hostname});
+ return 0;
+}
+
+static int attach_process_start_time_seconds(struct flb_config *ctx,
+ struct cmt *cmt,
+ uint64_t ts, char *hostname)
+{
+ double val;
+ struct cmt_gauge *g;
+
+ g = cmt_gauge_create(cmt, "fluentbit", "", "process_start_time_seconds",
+ "Start time of the process since unix epoch in seconds.",
+ 1, (char *[]) {"hostname"});
+ if (!g) {
+ return -1;
+ }
+
+ val = (double) ctx->init_time;
+ cmt_gauge_set(g, ts, val, 1, (char *[]) {hostname});
+ return 0;
+}
+
+static char *get_os_name()
+{
+#ifdef _WIN64
+ return "win64";
+#elif _WIN32
+ return "win32";
+#elif __APPLE__ || __MACH__
+ return "macos";
+#elif __linux__
+ return "linux";
+#elif __FreeBSD__
+ return "freebsd";
+#elif __unix || __unix__
+ return "unix";
+#else
+ return "other";
+#endif
+}
+
+static int attach_build_info(struct flb_config *ctx, struct cmt *cmt, uint64_t ts,
+ char *hostname)
+{
+ double val;
+ char *os;
+ struct cmt_gauge *g;
+
+ g = cmt_gauge_create(cmt, "fluentbit", "build", "info",
+ "Build version information.",
+ 3, (char *[]) {"hostname", "version", "os"});
+ if (!g) {
+ return -1;
+ }
+
+ val = (double) ctx->init_time;
+ os = get_os_name();
+
+ cmt_gauge_set(g, ts, val, 3, (char *[]) {hostname, FLB_VERSION_STR, os});
+ return 0;
+}
+
+static int attach_hot_reload_info(struct flb_config *ctx, struct cmt *cmt, uint64_t ts,
+ char *hostname)
+{
+ double val;
+ struct cmt_gauge *g;
+
+ g = cmt_gauge_create(cmt, "fluentbit", "", "hot_reloaded_times",
+ "Collect the count of hot reloaded times.",
+ 1, (char *[]) {"hostname"});
+ if (!g) {
+ return -1;
+ }
+
+ val = (double) ctx->hot_reloaded_count;
+
+ cmt_gauge_set(g, ts, val, 1, (char *[]) {hostname});
+ return 0;
+}
+
+/* Append internal Fluent Bit metrics to context */
+int flb_metrics_fluentbit_add(struct flb_config *ctx, struct cmt *cmt)
+{
+ int ret;
+ size_t ts;
+ char hostname[128];
+
+ /* current timestamp */
+ ts = cfl_time_now();
+
+ /* get hostname */
+ ret = gethostname(hostname, sizeof(hostname) - 1);
+ if (ret == -1) {
+ strcpy(hostname, "unknown");
+ }
+
+ /* Attach metrics to cmetrics context */
+ attach_uptime(ctx, cmt, ts, hostname);
+ attach_process_start_time_seconds(ctx, cmt, ts, hostname);
+ attach_build_info(ctx, cmt, ts, hostname);
+ attach_hot_reload_info(ctx, cmt, ts, hostname);
+
+ return 0;
+}
diff --git a/fluent-bit/src/flb_metrics_exporter.c b/fluent-bit/src/flb_metrics_exporter.c
new file mode 100644
index 000000000..8560fe6ee
--- /dev/null
+++ b/fluent-bit/src/flb_metrics_exporter.c
@@ -0,0 +1,336 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+/*
+ * Metrics exporter go around each Fluent Bit subsystem and collect metrics
+ * in a fixed interval of time. This operation is atomic and happens as one
+ * event handled by the main event loop.
+ */
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_http_server.h>
+#include <fluent-bit/flb_storage.h>
+#include <fluent-bit/flb_metrics.h>
+#include <fluent-bit/flb_metrics_exporter.h>
+
+static int collect_inputs(msgpack_sbuffer *mp_sbuf, msgpack_packer *mp_pck,
+ struct flb_config *ctx)
+{
+ int total = 0;
+ size_t s;
+ char *buf;
+ struct mk_list *head;
+ struct flb_input_instance *i;
+
+ msgpack_pack_str(mp_pck, 5);
+ msgpack_pack_str_body(mp_pck, "input", 5);
+
+ mk_list_foreach(head, &ctx->inputs) {
+ i = mk_list_entry(head, struct flb_input_instance, _head);
+ if (!i->metrics) {
+ continue;
+ }
+ total++; /* FIXME: keep total number in cache */
+ }
+
+ msgpack_pack_map(mp_pck, total);
+ mk_list_foreach(head, &ctx->inputs) {
+ i = mk_list_entry(head, struct flb_input_instance, _head);
+ if (!i->metrics) {
+ continue;
+ }
+
+ flb_metrics_dump_values(&buf, &s, i->metrics);
+ msgpack_pack_str(mp_pck, i->metrics->title_len);
+ msgpack_pack_str_body(mp_pck, i->metrics->title, i->metrics->title_len);
+ msgpack_sbuffer_write(mp_sbuf, buf, s);
+ flb_free(buf);
+ }
+
+ return 0;
+}
+
+static int collect_filters(msgpack_sbuffer *mp_sbuf, msgpack_packer *mp_pck,
+ struct flb_config *ctx)
+{
+ int total = 0;
+ size_t s;
+ char *buf;
+ struct mk_list *head;
+ struct flb_filter_instance *i;
+
+ msgpack_pack_str(mp_pck, 6);
+ msgpack_pack_str_body(mp_pck, "filter", 6);
+
+ mk_list_foreach(head, &ctx->filters) {
+ i = mk_list_entry(head, struct flb_filter_instance, _head);
+ if (!i->metrics) {
+ continue;
+ }
+ total++;
+ }
+
+ msgpack_pack_map(mp_pck, total);
+ mk_list_foreach(head, &ctx->filters) {
+ i = mk_list_entry(head, struct flb_filter_instance, _head);
+ if (!i->metrics) {
+ continue;
+ }
+
+ flb_metrics_dump_values(&buf, &s, i->metrics);
+ msgpack_pack_str(mp_pck, i->metrics->title_len);
+ msgpack_pack_str_body(mp_pck, i->metrics->title, i->metrics->title_len);
+ msgpack_sbuffer_write(mp_sbuf, buf, s);
+ flb_free(buf);
+ }
+
+ return 0;
+}
+
+static int collect_outputs(msgpack_sbuffer *mp_sbuf, msgpack_packer *mp_pck,
+ struct flb_config *ctx)
+{
+ int total = 0;
+ size_t s;
+ char *buf;
+ struct mk_list *head;
+ struct flb_output_instance *i;
+
+ msgpack_pack_str(mp_pck, 6);
+ msgpack_pack_str_body(mp_pck, "output", 6);
+
+ mk_list_foreach(head, &ctx->outputs) {
+ i = mk_list_entry(head, struct flb_output_instance, _head);
+ if (!i->metrics) {
+ continue;
+ }
+ total++; /* FIXME: keep total number in cache */
+ }
+
+ msgpack_pack_map(mp_pck, total);
+ mk_list_foreach(head, &ctx->outputs) {
+ i = mk_list_entry(head, struct flb_output_instance, _head);
+ if (!i->metrics) {
+ continue;
+ }
+
+ flb_metrics_dump_values(&buf, &s, i->metrics);
+ msgpack_pack_str(mp_pck, i->metrics->title_len);
+ msgpack_pack_str_body(mp_pck, i->metrics->title, i->metrics->title_len);
+ msgpack_sbuffer_write(mp_sbuf, buf, s);
+ flb_free(buf);
+ }
+
+ return 0;
+}
+
+static int collect_metrics(struct flb_me *me)
+{
+ int ret;
+ int keys;
+ char *buf_data;
+ size_t buf_size;
+ struct flb_config *ctx = me->config;
+ struct cmt *cmt;
+
+ /*
+ * msgpack buffer for old-style /v1/metrics
+ * ----------------------------------------
+ */
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+
+ /* Prepare new outgoing buffer */
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ keys = 3; /* input, filter, output */
+ msgpack_pack_map(&mp_pck, keys);
+
+ /* Collect metrics from input instances */
+ collect_inputs(&mp_sbuf, &mp_pck, me->config);
+ collect_filters(&mp_sbuf, &mp_pck, me->config);
+ collect_outputs(&mp_sbuf, &mp_pck, me->config);
+
+ /*
+ * If the built-in HTTP server is enabled, push metrics and health checks
+ * ---------------------------------------------------------------------
+ */
+
+#ifdef FLB_HAVE_HTTP_SERVER
+ if (ctx->http_server == FLB_TRUE) {
+ /* /v1/metrics (old) */
+ flb_hs_push_pipeline_metrics(ctx->http_ctx, mp_sbuf.data, mp_sbuf.size);
+
+ /* /v1/health */
+ if (ctx->health_check == FLB_TRUE) {
+ flb_hs_push_health_metrics(ctx->http_ctx, mp_sbuf.data, mp_sbuf.size);
+ }
+
+ /* /v2/metrics: retrieve a CMetrics context with internal metrics */
+ cmt = flb_me_get_cmetrics(ctx);
+ if (cmt) {
+ /* encode context to msgpack */
+ ret = cmt_encode_msgpack_create(cmt, &buf_data, &buf_size);
+ if (ret == 0) {
+ flb_hs_push_metrics(ctx->http_ctx, buf_data, buf_size);
+ cmt_encode_msgpack_destroy(buf_data);
+ }
+ cmt_destroy(cmt);
+ }
+ }
+#endif
+
+ /* destroy msgpack buffer for old-style /v1/metrics */
+ msgpack_sbuffer_destroy(&mp_sbuf);
+
+
+ return 0;
+}
+
+/* Create metrics exporter context */
+struct flb_me *flb_me_create(struct flb_config *ctx)
+{
+ int fd;
+ struct mk_event *event;
+ struct flb_me *me;
+
+ /* Context */
+ me = flb_calloc(1, sizeof(struct flb_me));
+ if (!me) {
+ flb_errno();
+ return NULL;
+ }
+ me->config = ctx;
+
+ /* Initialize event loop context */
+ event = &me->event;
+ MK_EVENT_ZERO(event);
+
+ /* Run every one second */
+ fd = mk_event_timeout_create(ctx->evl, 1, 0, &me->event);
+ if (fd == -1) {
+ flb_error("[metrics_exporter] registration failed");
+ flb_free(me);
+ return NULL;
+ }
+ me->fd = fd;
+
+ return me;
+
+}
+
+/* Handle the event loop notification: "it's time to collect metrics" */
+int flb_me_fd_event(int fd, struct flb_me *me)
+{
+ if (fd != me->fd) {
+ return -1;
+ }
+
+ flb_utils_timer_consume(fd);
+ collect_metrics(me);
+
+ return 0;
+}
+
+int flb_me_destroy(struct flb_me *me)
+{
+ mk_event_timeout_destroy(me->config->evl, &me->event);
+ flb_free(me);
+ return 0;
+}
+
+/* Export all metrics as CMetrics context */
+struct cmt *flb_me_get_cmetrics(struct flb_config *ctx)
+{
+ int ret;
+ struct mk_list *head;
+ struct flb_input_instance *i; /* inputs */
+ struct flb_filter_instance *f; /* filter */
+ struct flb_output_instance *o; /* output */
+ struct cmt *cmt;
+
+ cmt = cmt_create();
+ if (!cmt) {
+ return NULL;
+ }
+
+ /* Fluent Bit metrics */
+ flb_metrics_fluentbit_add(ctx, cmt);
+
+ if (ctx->storage_metrics == FLB_TRUE) {
+ /*
+ * Storage metrics are updated in two places:
+ *
+ * - global metrics: updated by using flb_storage_metrics_update()
+ * - input: flb_storage callback update the metrics automatically every 5 seconds
+ *
+ * In this part, we only take care about the global storage metrics.
+ */
+ flb_storage_metrics_update(ctx, ctx->storage_metrics_ctx);
+ ret = cmt_cat(cmt, ctx->storage_metrics_ctx->cmt);
+ if (ret == -1) {
+ flb_error("[metrics exporter] could not append global storage_metrics");
+ cmt_destroy(cmt);
+ return NULL;
+ }
+ }
+
+ /* Pipeline metrics: input, filters, outputs */
+ mk_list_foreach(head, &ctx->inputs) {
+ i = mk_list_entry(head, struct flb_input_instance, _head);
+ ret = cmt_cat(cmt, i->cmt);
+ if (ret == -1) {
+ flb_error("[metrics exporter] could not append metrics from %s",
+ flb_input_name(i));
+ cmt_destroy(cmt);
+ return NULL;
+ }
+ }
+
+ mk_list_foreach(head, &ctx->filters) {
+ f = mk_list_entry(head, struct flb_filter_instance, _head);
+ ret = cmt_cat(cmt, f->cmt);
+ if (ret == -1) {
+ flb_error("[metrics exporter] could not append metrics from %s",
+ flb_filter_name(f));
+ cmt_destroy(cmt);
+ return NULL;
+ }
+ }
+
+ mk_list_foreach(head, &ctx->outputs) {
+ o = mk_list_entry(head, struct flb_output_instance, _head);
+ ret = cmt_cat(cmt, o->cmt);
+ if (ret == -1) {
+ flb_error("[metrics exporter] could not append metrics from %s",
+ flb_output_name(o));
+ cmt_destroy(cmt);
+ return NULL;
+ }
+ }
+
+ return cmt;
+}
diff --git a/fluent-bit/src/flb_mp.c b/fluent-bit/src/flb_mp.c
new file mode 100644
index 000000000..50cd251c8
--- /dev/null
+++ b/fluent-bit/src/flb_mp.c
@@ -0,0 +1,645 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mp.h>
+#include <fluent-bit/flb_slist.h>
+#include <fluent-bit/flb_record_accessor.h>
+#include <fluent-bit/flb_metrics.h>
+
+#include <msgpack.h>
+#include <mpack/mpack.h>
+
+/* don't do this at home */
+#define pack_uint16(buf, d) _msgpack_store16(buf, (uint16_t) d)
+#define pack_uint32(buf, d) _msgpack_store32(buf, (uint32_t) d)
+
+/* Return the number of msgpack serialized events in the buffer */
+int flb_mp_count(const void *data, size_t bytes)
+{
+ return flb_mp_count_remaining(data, bytes, NULL);
+}
+
+int flb_mp_count_remaining(const void *data, size_t bytes, size_t *remaining_bytes)
+{
+ size_t remaining;
+ int count = 0;
+ mpack_reader_t reader;
+
+ mpack_reader_init_data(&reader, (const char *) data, bytes);
+ for (;;) {
+ remaining = mpack_reader_remaining(&reader, NULL);
+ if (!remaining) {
+ break;
+ }
+ mpack_discard(&reader);
+ if (mpack_reader_error(&reader)) {
+ break;
+ }
+ count++;
+ }
+
+ if (remaining_bytes) {
+ *remaining_bytes = remaining;
+ }
+ mpack_reader_destroy(&reader);
+ return count;
+}
+
+int flb_mp_validate_metric_chunk(const void *data, size_t bytes,
+ int *out_series, size_t *processed_bytes)
+{
+ int ret;
+ int ok = CMT_DECODE_MSGPACK_SUCCESS;
+ int count = 0;
+ size_t off = 0;
+ size_t pre_off = 0;
+ struct cmt *cmt;
+
+ while ((ret = cmt_decode_msgpack_create(&cmt,
+ (char *) data, bytes, &off)) == ok) {
+ cmt_destroy(cmt);
+ count++;
+ pre_off = off;
+ }
+
+ switch (ret) {
+ case CMT_DECODE_MSGPACK_INVALID_ARGUMENT_ERROR:
+ case CMT_DECODE_MSGPACK_CORRUPT_INPUT_DATA_ERROR:
+ case CMT_DECODE_MSGPACK_CONSUME_ERROR:
+ case CMT_DECODE_MSGPACK_ENGINE_ERROR:
+ case CMT_DECODE_MSGPACK_PENDING_MAP_ENTRIES:
+ case CMT_DECODE_MSGPACK_PENDING_ARRAY_ENTRIES:
+ case CMT_DECODE_MSGPACK_UNEXPECTED_KEY_ERROR:
+ case CMT_DECODE_MSGPACK_UNEXPECTED_DATA_TYPE_ERROR:
+ case CMT_DECODE_MSGPACK_DICTIONARY_LOOKUP_ERROR:
+ case CMT_DECODE_MSGPACK_VERSION_ERROR:
+ goto error;
+ }
+
+ if (ret == CMT_DECODE_MSGPACK_INSUFFICIENT_DATA && off == bytes) {
+ *out_series = count;
+ *processed_bytes = pre_off;
+ return 0;
+ }
+
+error:
+ *out_series = count;
+ *processed_bytes = pre_off;
+
+ return -1;
+}
+
+int flb_mp_validate_log_chunk(const void *data, size_t bytes,
+ int *out_records, size_t *processed_bytes)
+{
+ int ret;
+ int count = 0;
+ size_t off = 0;
+ size_t pre_off = 0;
+ size_t ptr_size;
+ unsigned char *ptr;
+ msgpack_object array;
+ msgpack_object ts;
+ msgpack_object header;
+ msgpack_object record;
+ msgpack_object metadata;
+ msgpack_unpacked result;
+
+ msgpack_unpacked_init(&result);
+ while (msgpack_unpack_next(&result, data, bytes, &off) == MSGPACK_UNPACK_SUCCESS) {
+ array = result.data;
+
+ if (array.type != MSGPACK_OBJECT_ARRAY) {
+ /*
+ * Sometimes there is a special case: Chunks might have extra zero
+ * bytes at the end of a record, meaning: no more records. This is not
+ * an error and actually it happens if a previous run of Fluent Bit
+ * was stopped/killed before to adjust the file size.
+ *
+ * Just validate if all bytes are zero, if so, adjust counters
+ * and return zero.
+ */
+ ptr = (unsigned char *) (data);
+ ptr += pre_off;
+ if (ptr[0] != 0) {
+ goto error;
+ }
+
+ ptr_size = bytes - pre_off;
+ ret = memcmp(ptr, ptr + 1, ptr_size - 1);
+ if (ret == 0) {
+ /*
+ * The chunk is valid, just let the caller know the last processed
+ * valid byte.
+ */
+ msgpack_unpacked_destroy(&result);
+ *out_records = count;
+ *processed_bytes = pre_off;
+ return 0;
+ }
+ goto error;
+ }
+
+ if (array.via.array.size != 2) {
+ goto error;
+ }
+
+ header = array.via.array.ptr[0];
+ record = array.via.array.ptr[1];
+
+ if (header.type == MSGPACK_OBJECT_ARRAY) {
+ if (header.via.array.size != 2) {
+ goto error;
+ }
+
+ ts = header.via.array.ptr[0];
+ metadata = header.via.array.ptr[1];
+
+ if (metadata.type != MSGPACK_OBJECT_MAP) {
+ goto error;
+ }
+ }
+ else {
+ ts = header;
+ }
+
+ if (ts.type != MSGPACK_OBJECT_POSITIVE_INTEGER &&
+ ts.type != MSGPACK_OBJECT_FLOAT &&
+ ts.type != MSGPACK_OBJECT_EXT) {
+ goto error;
+ }
+
+ if (record.type != MSGPACK_OBJECT_MAP) {
+ goto error;
+ }
+
+ count++;
+ pre_off = off;
+ }
+
+ msgpack_unpacked_destroy(&result);
+ *out_records = count;
+ *processed_bytes = pre_off;
+ return 0;
+
+ error:
+ msgpack_unpacked_destroy(&result);
+ *out_records = count;
+ *processed_bytes = pre_off;
+
+ return -1;
+}
+
+/* Adjust a mspack header buffer size */
+void flb_mp_set_map_header_size(char *buf, int size)
+{
+ uint8_t h;
+ char *tmp = buf;
+
+ h = tmp[0];
+ if (h >> 4 == 0x8) { /* 1000xxxx */
+ *tmp = (uint8_t) 0x8 << 4 | ((uint8_t) size);
+ }
+ else if (h == 0xde) {
+ tmp++;
+ pack_uint16(tmp, size);
+ }
+ else if (h == 0xdf) {
+ tmp++;
+ pack_uint32(tmp, size);
+ }
+}
+
+void flb_mp_set_array_header_size(char *buf, int size)
+{
+ uint8_t h;
+ char *tmp = buf;
+
+ h = tmp[0];
+ if (h >> 4 == 0x9) { /* 1001xxxx */
+ *tmp = (uint8_t) 0x9 << 4 | ((uint8_t) size);
+ }
+ else if (h == 0xdc) {
+ tmp++;
+ pack_uint16(tmp, size);
+ }
+ else if (h == 0xdd) {
+ tmp++;
+ pack_uint32(tmp, size);
+ }
+}
+
+/*
+ * msgpack-c requires to set the number of the entries in a map beforehand. For our
+ * use case this adds some complexity, having developers to count all possible
+ * entries that might be added.
+ *
+ * As a workaround and to avoid map's recomposition over and over, this simple API
+ * allows to initialize the array header, 'register' new entries (as counters) and
+ * finalize, upon finalization the proper array header size is adjusted.
+ *
+ * To make things easier, we make sure msgpack-c always register an array type of
+ * 32 bits (identified by 0xdf, for number of entries >= 65536). Yes, for every
+ * array using this API it will use 2 more bytes, not a big ideal. So whoever
+ * uses this API, use it only if you don't know the exact number of entries to add.
+ *
+ * MANDATORY: make sure to always initialize, register every entry and finalize,
+ * otherwise you will get a corrupted or incomplete msgpack buffer.
+ *
+ * Usage example
+ * =============
+ *
+ * struct flb_mp_map_header mh;
+ *
+ * flb_mp_map_header_init(&mh, mp_pck);
+ *
+ * -- First key/value entry --
+ * flb_mp_map_header_append(&mh);
+ * msgpack_pack_str(mp_pck, 4);
+ * msgpack_pack_str_body(mp_pck, "cool", 4);
+ * msgpack_pack_true(mp_pck);
+ *
+ * -- Second key/value entry --
+ * flb_mp_map_header_append(&mh);
+ * msgpack_pack_str(mp_pck, 4);
+ * msgpack_pack_str_body(mp_pck, "slow", 4);
+ * msgpack_pack_false(mp_pck);
+ *
+ * -- Finalize Map --
+ * flb_mp_map_header_end(&mh);
+ */
+
+static inline void mp_header_type_init(struct flb_mp_map_header *mh,
+ msgpack_packer *mp_pck,
+ int type)
+{
+ msgpack_sbuffer *mp_sbuf;
+
+ mp_sbuf = (msgpack_sbuffer *) mp_pck->data;
+
+ /* map sbuffer */
+ mh->data = mp_pck->data;
+
+ /* Reset entries */
+ mh->entries = 0;
+
+ /* Store the next byte available */
+ mh->offset = mp_sbuf->size;
+}
+
+int flb_mp_map_header_init(struct flb_mp_map_header *mh, msgpack_packer *mp_pck)
+{
+ /* Initialize context for a map */
+ mp_header_type_init(mh, mp_pck, FLB_MP_MAP);
+
+ /*
+ * Pack a map with size = 65536, so we force the underlaying msgpack-c
+ * to use a 32 bit buffer size (0xdf), reference:
+ *
+ * - https://github.com/msgpack/msgpack/blob/master/spec.md#map-format-family
+ */
+ return msgpack_pack_map(mp_pck, 65536);
+}
+
+int flb_mp_array_header_init(struct flb_mp_map_header *mh, msgpack_packer *mp_pck)
+{
+ /* Initialize context for a map */
+ mp_header_type_init(mh, mp_pck, FLB_MP_ARRAY);
+
+ /*
+ * Pack a map with size = 65536, so we force the underlaying msgpack-c
+ * to use a 32 bit buffer size (0xdf), reference:
+ *
+ * - https://github.com/msgpack/msgpack/blob/master/spec.md#map-format-family
+ */
+ return msgpack_pack_array(mp_pck, 65536);
+}
+
+
+int flb_mp_map_header_append(struct flb_mp_map_header *mh)
+{
+ mh->entries++;
+ return mh->entries;
+}
+
+int flb_mp_array_header_append(struct flb_mp_map_header *mh)
+{
+ mh->entries++;
+ return mh->entries;
+}
+
+void flb_mp_map_header_end(struct flb_mp_map_header *mh)
+{
+ char *ptr;
+ msgpack_sbuffer *mp_sbuf;
+
+ mp_sbuf = mh->data;
+ ptr = (char *) mp_sbuf->data + mh->offset;
+ flb_mp_set_map_header_size(ptr, mh->entries);
+}
+
+void flb_mp_array_header_end(struct flb_mp_map_header *mh)
+{
+ char *ptr;
+ msgpack_sbuffer *mp_sbuf;
+
+ mp_sbuf = mh->data;
+ ptr = (char *) mp_sbuf->data + mh->offset;
+ flb_mp_set_array_header_size(ptr, mh->entries);
+}
+
+static int insert_by_subkey_count(struct flb_record_accessor *ra, struct flb_mp_accessor *mpa)
+{
+ int subkey_count;
+ int count;
+ struct mk_list *h;
+ struct flb_record_accessor *val_ra;
+
+ /*
+ * sort flb_record_accessor by number of subkey
+ *
+ * e.g.
+ * $kubernetes
+ * $kubernetes[2]['a']
+ * $kubernetes[2]['annotations']['fluentbit.io/tag']
+ */
+ subkey_count = flb_ra_subkey_count(ra);
+ mk_list_foreach(h, &mpa->ra_list) {
+ val_ra = mk_list_entry(h, struct flb_record_accessor, _head);
+ count = flb_ra_subkey_count(val_ra);
+ if (count >= subkey_count) {
+ mk_list_add_before(&ra->_head, &val_ra->_head, &mpa->ra_list);
+ return 0;
+ }
+ }
+
+ /* add to tail of list */
+ mk_list_add(&ra->_head, &mpa->ra_list);
+ return 0;
+}
+
+
+/*
+ * Create an 'mp accessor' context: this context allows to create a list of
+ * record accessor patterns based on a 'slist' context, where every slist string
+ * buffer represents a key accessor.
+ */
+struct flb_mp_accessor *flb_mp_accessor_create(struct mk_list *slist_patterns)
+{
+ size_t size;
+ struct mk_list *head;
+ struct flb_slist_entry *entry;
+ struct flb_record_accessor *ra;
+ struct flb_mp_accessor *mpa;
+
+ /* Allocate context */
+ mpa = flb_calloc(1, sizeof(struct flb_mp_accessor));
+ if (!mpa) {
+ flb_errno();
+ return NULL;
+ }
+ mk_list_init(&mpa->ra_list);
+
+ mk_list_foreach(head, slist_patterns) {
+ entry = mk_list_entry(head, struct flb_slist_entry, _head);
+
+ /* Create the record accessor context */
+ ra = flb_ra_create(entry->str, FLB_TRUE);
+ if (!ra) {
+ flb_error("[mp accessor] could not create entry for pattern '%s'",
+ entry->str);
+ flb_mp_accessor_destroy(mpa);
+ return NULL;
+ }
+ insert_by_subkey_count(ra, mpa);
+ }
+
+ if (mk_list_size(&mpa->ra_list) == 0) {
+ return mpa;
+ }
+
+ size = sizeof(struct flb_mp_accessor_match) * mk_list_size(&mpa->ra_list);
+ mpa->matches_size = size;
+ mpa->matches = flb_calloc(1, size);
+ if (!mpa->matches) {
+ flb_errno();
+ flb_mp_accessor_destroy(mpa);
+ return NULL;
+ }
+
+ return mpa;
+}
+
+static inline int accessor_key_find_match(struct flb_mp_accessor *mpa,
+ msgpack_object *key)
+{
+ int i;
+ int count;
+ struct flb_mp_accessor_match *match;
+
+ count = mk_list_size(&mpa->ra_list);
+ for (i = 0; i < count; i++) {
+ match = &mpa->matches[i];
+ if (match->matched == FLB_FALSE) {
+ continue;
+ }
+
+ if (match->start_key == key) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static inline int accessor_sub_pack(struct flb_mp_accessor_match *match,
+ msgpack_packer *mp_pck,
+ msgpack_object *key,
+ msgpack_object *val)
+{
+ int i;
+ int ret;
+ msgpack_object *k;
+ msgpack_object *v;
+ struct flb_mp_map_header mh;
+
+ if (match->key == key || match->key == val) {
+ return FLB_FALSE;
+ }
+
+ if (key) {
+ msgpack_pack_object(mp_pck, *key);
+ }
+
+ if (val->type == MSGPACK_OBJECT_MAP) {
+ flb_mp_map_header_init(&mh, mp_pck);
+ for (i = 0; i < val->via.map.size; i++) {
+ k = &val->via.map.ptr[i].key;
+ v = &val->via.map.ptr[i].val;
+
+ ret = accessor_sub_pack(match, mp_pck, k, v);
+ if (ret == FLB_TRUE) {
+ flb_mp_map_header_append(&mh);
+ }
+ }
+ flb_mp_map_header_end(&mh);
+ }
+ else if (val->type == MSGPACK_OBJECT_ARRAY) {
+ flb_mp_array_header_init(&mh, mp_pck);
+ for (i = 0; i < val->via.array.size; i++) {
+ v = &val->via.array.ptr[i];
+ ret = accessor_sub_pack(match, mp_pck, NULL, v);
+ if (ret == FLB_TRUE) {
+ flb_mp_array_header_append(&mh);
+ }
+ }
+ flb_mp_array_header_end(&mh);
+ }
+ else {
+ msgpack_pack_object(mp_pck, *val);
+ }
+
+ return FLB_TRUE;
+}
+
+/*
+ * Remove keys or nested keys from a map. It compose the final result in a
+ * new buffer. On error, it returns -1, if the map was modified it returns FLB_TRUE,
+ * if no modification was required it returns FLB_FALSE.
+ */
+int flb_mp_accessor_keys_remove(struct flb_mp_accessor *mpa,
+ msgpack_object *map,
+ void **out_buf, size_t *out_size)
+{
+ int i;
+ int ret;
+ int rule_id = 0;
+ int matches = 0;
+ msgpack_object *key;
+ msgpack_object *val;
+ msgpack_object *s_key;
+ msgpack_object *o_key;
+ msgpack_object *o_val;
+ struct mk_list *head;
+ struct flb_record_accessor *ra;
+ struct flb_mp_accessor_match *match;
+ struct flb_mp_map_header mh;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+
+ if (map->via.map.size == 0) {
+ return FLB_FALSE;
+ }
+
+ /* Reset matches cache */
+ memset(mpa->matches, '\0', mpa->matches_size);
+
+ mk_list_foreach(head, &mpa->ra_list) {
+ ra = mk_list_entry(head, struct flb_record_accessor, _head);
+
+ /* Apply the record accessor rule against the map */
+ ret = flb_ra_get_kv_pair(ra, *map, &s_key, &o_key, &o_val);
+ if (ret == 0) {
+ /* There is a match, register in the matches table */
+ match = &mpa->matches[rule_id];
+ match->matched = FLB_TRUE;
+ match->start_key = s_key; /* Initial key path that matched */
+ match->key = o_key; /* Final key that matched */
+ match->val = o_val; /* Final value */
+ match->ra = ra; /* Record accessor context */
+ matches++;
+ }
+ rule_id++;
+ }
+
+ /* If no matches, no modifications were made */
+ if (matches == 0) {
+ return FLB_FALSE;
+ }
+
+ /* Some rules matched, compose a new outgoing buffer */
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ /* Initialize map */
+ flb_mp_map_header_init(&mh, &mp_pck);
+
+ for (i = 0; i < map->via.map.size; i++) {
+ key = &map->via.map.ptr[i].key;
+ val = &map->via.map.ptr[i].val;
+
+ /*
+ * For every entry on the path, check if we should do a step-by-step
+ * repackaging or just pack the whole object.
+ *
+ * Just check: does this 'key' exists on any path of the record
+ * accessor patterns ?
+ *
+ * Find if the active key in the map, matches an accessor rule, if
+ * if match we get the match id as return value, otherwise -1.
+ */
+ ret = accessor_key_find_match(mpa, key);
+ if (ret == -1) {
+ /* No matches, it's ok to pack the kv pair */
+ flb_mp_map_header_append(&mh);
+ msgpack_pack_object(&mp_pck, *key);
+ msgpack_pack_object(&mp_pck, *val);
+ }
+ else {
+ /* The key has a match. Now we do a step-by-step packaging */
+ match = &mpa->matches[ret];
+ ret = accessor_sub_pack(match, &mp_pck, key, val);
+ if (ret == FLB_TRUE) {
+ flb_mp_map_header_append(&mh);
+ }
+ }
+ }
+ flb_mp_map_header_end(&mh);
+
+ *out_buf = mp_sbuf.data;
+ *out_size = mp_sbuf.size;
+
+ return FLB_TRUE;
+}
+
+void flb_mp_accessor_destroy(struct flb_mp_accessor *mpa)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_record_accessor *ra;
+
+ if (!mpa) {
+ return;
+ }
+
+ mk_list_foreach_safe(head, tmp, &mpa->ra_list) {
+ ra = mk_list_entry(head, struct flb_record_accessor, _head);
+ mk_list_del(&ra->_head);
+ flb_ra_destroy(ra);
+ }
+
+ if (mpa->matches) {
+ flb_free(mpa->matches);
+ }
+ flb_free(mpa);
+}
diff --git a/fluent-bit/src/flb_network.c b/fluent-bit/src/flb_network.c
new file mode 100644
index 000000000..9609e5a03
--- /dev/null
+++ b/fluent-bit/src/flb_network.c
@@ -0,0 +1,2168 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+
+#ifdef FLB_SYSTEM_WINDOWS
+#define poll WSAPoll
+#else
+#include <sys/poll.h>
+#endif
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_compat.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_socket.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_network.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_macros.h>
+#include <fluent-bit/flb_upstream.h>
+#include <fluent-bit/flb_scheduler.h>
+
+#include <monkey/mk_core.h>
+#include <ares.h>
+
+#ifndef SOL_TCP
+#define SOL_TCP IPPROTO_TCP
+#endif
+
+static pthread_once_t local_thread_net_dns_ctx_init = PTHREAD_ONCE_INIT;
+FLB_TLS_DEFINE(struct flb_net_dns, flb_net_dns_ctx);
+
+/*
+ * Initialize thread-local-storage, every worker thread has it owns
+ * dns context with relevant info populated inside the thread.
+ */
+
+static void flb_net_dns_ctx_init_private()
+{
+ FLB_TLS_INIT(flb_net_dns_ctx);
+}
+
+void flb_net_dns_ctx_init()
+{
+ pthread_once(&local_thread_net_dns_ctx_init, flb_net_dns_ctx_init_private);
+}
+
+struct flb_net_dns *flb_net_dns_ctx_get()
+{
+ return FLB_TLS_GET(flb_net_dns_ctx);
+}
+
+void flb_net_dns_ctx_set(struct flb_net_dns *dns_ctx)
+{
+ FLB_TLS_SET(flb_net_dns_ctx, dns_ctx);
+}
+
+void flb_net_lib_init()
+{
+ int result;
+
+ result = ares_library_init_mem(ARES_LIB_INIT_ALL, flb_malloc, flb_free, flb_realloc);
+
+ if(0 != result) {
+ flb_error("[network] c-ares memory settings initialization error : %s",
+ ares_strerror(result));
+ }
+}
+
+void flb_net_ctx_init(struct flb_net_dns *dns_ctx)
+{
+ mk_list_init(&dns_ctx->lookups);
+ mk_list_init(&dns_ctx->lookups_drop);
+}
+
+void flb_net_setup_init(struct flb_net_setup *net)
+{
+ net->dns_mode = NULL;
+ net->dns_resolver = NULL;
+ net->dns_prefer_ipv4 = FLB_FALSE;
+ net->keepalive = FLB_TRUE;
+ net->keepalive_idle_timeout = 30;
+ net->keepalive_max_recycle = 0;
+ net->accept_timeout = 10;
+ net->connect_timeout = 10;
+ net->io_timeout = 0; /* Infinite time */
+ net->source_address = NULL;
+}
+
+int flb_net_host_set(const char *plugin_name, struct flb_net_host *host, const char *address)
+{
+ int len;
+ int olen;
+ const char *s, *e, *u;
+
+ memset(host, '\0', sizeof(struct flb_net_host));
+
+ olen = strlen(address);
+ if (olen == strlen(plugin_name)) {
+ return 0;
+ }
+
+ len = strlen(plugin_name) + 3;
+ if (olen < len) {
+ return -1;
+ }
+
+ s = address + len;
+ if (*s == '[') {
+ /* IPv6 address (RFC 3986) */
+ e = strchr(++s, ']');
+ if (!e) {
+ return -1;
+ }
+ host->name = flb_sds_create_len(s, e - s);
+ host->ipv6 = FLB_TRUE;
+ s = e + 1;
+ }
+ else {
+ e = s;
+ while (!(*e == '\0' || *e == ':' || *e == '/')) {
+ ++e;
+ }
+ if (e == s) {
+ return -1;
+ }
+ host->name = flb_sds_create_len(s, e - s);
+ s = e;
+ }
+
+ if (*s == ':') {
+ host->port = atoi(++s);
+ }
+
+ u = strchr(s, '/');
+ if (u) {
+ host->uri = flb_uri_create(u);
+ }
+ host->address = flb_sds_create(address);
+
+ if (host->name) {
+ host->listen = flb_sds_create(host->name);
+ }
+
+ return 0;
+}
+
+int flb_net_socket_reset(flb_sockfd_t fd)
+{
+ int status = 1;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &status, sizeof(int)) == -1) {
+ flb_errno();
+ return -1;
+ }
+
+ return 0;
+}
+
+int flb_net_socket_tcp_nodelay(flb_sockfd_t fd)
+{
+ int on = 1;
+ int ret;
+
+ ret = setsockopt(fd, SOL_TCP, TCP_NODELAY, &on, sizeof(on));
+ if (ret == -1) {
+ flb_errno();
+ return -1;
+ }
+
+ return 0;
+}
+
+int flb_net_socket_nonblocking(flb_sockfd_t fd)
+{
+#ifdef _WIN32
+ unsigned long on = 1;
+ if (ioctlsocket(fd, FIONBIO, &on) != 0) {
+#else
+ if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) == -1) {
+#endif
+ flb_errno();
+ return -1;
+ }
+
+ return 0;
+}
+
+int flb_net_socket_rcv_buffer(flb_sockfd_t fd, int rcvbuf)
+{
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) != 0) {
+ flb_errno();
+ return -1;
+ }
+
+ return 0;
+}
+
+int flb_net_socket_blocking(flb_sockfd_t fd)
+{
+#ifdef _WIN32
+ unsigned long off = 0;
+ if (ioctlsocket(fd, FIONBIO, &off) != 0) {
+#else
+ if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK) == -1) {
+#endif
+ flb_errno();
+ return -1;
+ }
+
+ return 0;
+}
+
+int flb_net_socket_set_rcvtimeout(flb_sockfd_t fd, int timeout_in_seconds)
+{
+#ifdef FLB_SYSTEM_WINDOWS
+ /* WINDOWS */
+ DWORD timeout = timeout_in_seconds * 1000;
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof timeout)
+ == -1) {
+#else
+ /* LINUX and MAC OS X */
+ struct timeval tv;
+ tv.tv_sec = timeout_in_seconds;
+ tv.tv_usec = 0;
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv) == -1) {
+#endif
+ flb_errno();
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Enable the TCP_FASTOPEN feature for server side implemented in Linux Kernel >= 3.7,
+ * for more details read here:
+ *
+ * TCP Fast Open: expediting web services: http://lwn.net/Articles/508865/
+ */
+int flb_net_socket_tcp_fastopen(flb_sockfd_t fd)
+{
+ int qlen = 5;
+ return setsockopt(fd, SOL_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen));
+}
+
+flb_sockfd_t flb_net_socket_create(int family, int nonblock)
+{
+ flb_sockfd_t fd;
+
+ /* create the socket and set the nonblocking flag status */
+ fd = socket(family, SOCK_STREAM, 0);
+ if (fd == -1) {
+ flb_errno();
+ return -1;
+ }
+
+ if (nonblock) {
+ flb_net_socket_nonblocking(fd);
+ }
+
+ return fd;
+}
+
+flb_sockfd_t flb_net_socket_create_udp(int family, int nonblock)
+{
+ flb_sockfd_t fd;
+
+ /* create the socket and set the nonblocking flag status */
+ fd = socket(family, SOCK_DGRAM, 0);
+ if (fd == -1) {
+ flb_errno();
+ return -1;
+ }
+
+ if (nonblock) {
+ flb_net_socket_nonblocking(fd);
+ }
+
+ return fd;
+}
+
+/*
+ * Perform TCP connection for a blocking socket. This interface set's the socket
+ * to non-blocking mode temporary in order to add a timeout to the connection,
+ * the blocking mode is restored at the end.
+ */
+static int net_connect_sync(int fd, const struct sockaddr *addr, socklen_t addrlen,
+ char *host, int port, int connect_timeout)
+{
+ int ret;
+ int err;
+ int socket_errno;
+ struct pollfd pfd_read;
+
+ /* Set socket to non-blocking mode */
+ flb_net_socket_nonblocking(fd);
+
+ /* connect(2) */
+ ret = connect(fd, addr, addrlen);
+ if (ret == -1) {
+ /*
+ * An asynchronous connect can return -1, but what is important is the
+ * socket status, getting a EINPROGRESS is expected, but any other case
+ * means a failure.
+ */
+#ifdef FLB_SYSTEM_WINDOWS
+ socket_errno = flb_socket_error(fd);
+ err = 0;
+#else
+ socket_errno = errno;
+ err = flb_socket_error(fd);
+#endif
+
+ if (!FLB_EINPROGRESS(socket_errno) || err != 0) {
+ goto exit_error;
+ }
+
+ /* The connection is still in progress, implement a socket timeout */
+ flb_trace("[net] connection #%i in process to %s:%i",
+ fd, host, port);
+
+ /*
+ * Prepare a timeout using poll(2): we could use our own
+ * event loop mechanism for this, but it will require an
+ * extra file descriptor, the poll(2) call is straightforward
+ * for this use case.
+ */
+
+ pfd_read.fd = fd;
+ pfd_read.events = POLLOUT;
+ ret = poll(&pfd_read, 1, connect_timeout * 1000);
+ if (ret == 0) {
+ /* Timeout */
+ flb_error("[net] connection #%i timeout after %i seconds to: "
+ "%s:%i",
+ fd, connect_timeout, host, port);
+ goto exit_error;
+ }
+ else if (ret < 0) {
+ /* Generic error */
+ flb_errno();
+ flb_error("[net] connection #%i failed to: %s:%i",
+ fd, host, port);
+ goto exit_error;
+ }
+ }
+
+ /*
+ * No exception, the connection succeeded, return the normal
+ * non-blocking mode to the socket.
+ */
+ flb_net_socket_blocking(fd);
+ return 0;
+
+ exit_error:
+ flb_net_socket_blocking(fd);
+ return -1;
+}
+
+
+/*
+ * Asynchronous socket connection: this interface might be called from a co-routine,
+ * so in order to perform a real async connection and get notified back, it needs
+ * access to the event loop context and the connection context 'upstream connection.
+ */
+static int net_connect_async(int fd,
+ const struct sockaddr *addr, socklen_t addrlen,
+ char *host, int port, int connect_timeout,
+ void *async_ctx, struct flb_connection *u_conn)
+{
+ int ret;
+ int err;
+ int error = 0;
+ int socket_errno;
+ uint32_t mask;
+ char so_error_buf[256];
+ char *str;
+ struct flb_upstream *u;
+
+ u = u_conn->upstream;
+
+ /* connect(2) */
+ ret = connect(fd, addr, addrlen);
+ if (ret == 0) {
+ return 0;
+ }
+
+ /*
+ * An asynchronous connect can return -1, but what is important is the
+ * socket status, getting a EINPROGRESS is expected, but any other case
+ * means a failure.
+ */
+#ifdef FLB_SYSTEM_WINDOWS
+ socket_errno = flb_socket_error(fd);
+ err = 0;
+#else
+ socket_errno = errno;
+ err = flb_socket_error(fd);
+#endif
+ /* The logic behind this check is that when establishing a connection
+ * errno should be EINPROGRESS with no additional information in order
+ * for it to be a healthy attempt. However, when errno is EINPROGRESS
+ * and an error occurs it could be saved in the so_error socket field
+ * which has to be accessed through getsockopt(... SO_ERROR ...) so
+ * in order to preserve that behavior while also properly detecting
+ * other errno values as error conditions the comparison was changed.
+ *
+ * Windows note : flb_socket_error returns either the value returned
+ * by WSAGetLastError or the value returned by getsockopt(... SO_ERROR ...)
+ * if WSAGetLastError returns WSAEWOULDBLOCK as per libevents code.
+ *
+ * General note : according to the connect syscall man page (not libc)
+ * there could be a timing issue with checking SO_ERROR here because
+ * the suggested use involves checking it after a select or poll call
+ * returns the socket as writable which is not the case here.
+ */
+
+ if (!FLB_EINPROGRESS(socket_errno) || err != 0) {
+ return -1;
+ }
+
+ /* The connection is still in progress, implement a socket timeout */
+ flb_trace("[net] connection #%i in process to %s:%i",
+ fd, host, port);
+
+ /* Register the connection socket into the main event loop */
+ MK_EVENT_ZERO(&u_conn->event);
+
+ ret = mk_event_add(u_conn->evl,
+ fd,
+ FLB_ENGINE_EV_THREAD,
+ MK_EVENT_WRITE,
+ &u_conn->event);
+
+ u_conn->event.priority = FLB_ENGINE_PRIORITY_CONNECT;
+
+ if (ret == -1) {
+ /*
+ * If we failed here there no much that we can do, just
+ * let the caller know that we failed.
+ */
+ return -1;
+ }
+
+ u_conn->coroutine = async_ctx;
+
+ /*
+ * Return the control to the parent caller, we need to wait for
+ * the event loop to get back to us.
+ */
+ flb_coro_yield(async_ctx, FLB_FALSE);
+
+ /* We want this field to hold NULL at all times unless we are explicitly
+ * waiting to be resumed.
+ */
+ u_conn->coroutine = NULL;
+
+ /* Save the mask before the event handler do a reset */
+ mask = u_conn->event.mask;
+
+ /*
+ * If the socket has been invalidated (e.g: timeout or shutdown), just
+ * print a debug message and return.
+ */
+ if (u_conn->fd == -1) {
+ flb_debug("[net] TCP connection not longer available: %s:%i",
+ u->tcp_host, u->tcp_port);
+ return -1;
+ }
+
+ /* We got a notification, remove the event registered */
+ ret = mk_event_del(u_conn->evl, &u_conn->event);
+ if (ret == -1) {
+ flb_error("[io] connect event handler error");
+ return -1;
+ }
+
+ if (u_conn->net_error == ETIMEDOUT) {
+ flb_debug("[net] TCP connection timed out: %s:%i",
+ u->tcp_host, u->tcp_port);
+ return -1;
+ }
+
+ /* Check the connection status */
+ if (mask & MK_EVENT_WRITE) {
+ error = flb_socket_error(u_conn->fd);
+
+ /* Check the exception */
+ if (error != 0) {
+ /*
+ * The upstream connection might want to override the
+ * exception (mostly used for local timeouts: ETIMEDOUT.
+ */
+ if (u_conn->net_error > 0) {
+ error = u_conn->net_error;
+ }
+
+ /* Connection is broken, not much to do here */
+ str = strerror_r(error, so_error_buf, sizeof(so_error_buf));
+ flb_error("[net] TCP connection failed: %s:%i (%s)",
+ u->tcp_host, u->tcp_port, str);
+ return -1;
+ }
+ }
+ else {
+ flb_error("[net] TCP connection, unexpected error: %s:%i",
+ u->tcp_host, u->tcp_port);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void flb_net_dns_lookup_context_destroy(struct flb_dns_lookup_context *lookup_context)
+{
+ mk_list_del(&lookup_context->_head);
+ ares_destroy(lookup_context->ares_channel);
+ flb_free(lookup_context);
+}
+
+static void flb_net_dns_lookup_context_drop(struct flb_dns_lookup_context *lookup_context)
+{
+ if (!lookup_context->dropped) {
+ lookup_context->dropped = FLB_TRUE;
+
+ mk_list_del(&lookup_context->_head);
+ mk_list_add(&lookup_context->_head, &lookup_context->dns_ctx->lookups_drop);
+
+ if (lookup_context->udp_timer != NULL &&
+ lookup_context->udp_timer->active) {
+ flb_sched_timer_invalidate(lookup_context->udp_timer);
+
+ lookup_context->udp_timer = NULL;
+ }
+ }
+}
+
+void flb_net_dns_lookup_context_cleanup(struct flb_net_dns *dns_ctx)
+{
+ struct flb_dns_lookup_context *lookup_context;
+ struct flb_coro *coroutine;
+ struct mk_list *head;
+ struct mk_list *tmp;
+
+ mk_list_foreach_safe(head, tmp, &dns_ctx->lookups_drop) {
+ lookup_context = mk_list_entry(head, struct flb_dns_lookup_context, _head);
+
+ coroutine = lookup_context->coroutine;
+
+ flb_net_dns_lookup_context_destroy(lookup_context);
+
+ if (coroutine != NULL) {
+ flb_coro_resume(coroutine);
+ }
+ }
+}
+
+static void flb_net_free_translated_addrinfo(struct addrinfo *input)
+{
+ struct addrinfo *current_record;
+ struct addrinfo *next_record;
+
+ if (input != NULL) {
+ next_record = NULL;
+
+ for (current_record = input ;
+ current_record != NULL ;
+ current_record = next_record) {
+
+ if (current_record->ai_addr != NULL) {
+ flb_free(current_record->ai_addr);
+ }
+
+ next_record = current_record->ai_next;
+
+ flb_free(current_record);
+ }
+ }
+}
+
+static void flb_net_append_addrinfo_entry(struct addrinfo **head,
+ struct addrinfo **tail,
+ struct addrinfo *entry)
+{
+ if (*head == NULL) {
+ *head = entry;
+ }
+ else {
+ (*tail)->ai_next = entry;
+ }
+
+ *tail = entry;
+}
+
+static struct addrinfo *flb_net_sort_addrinfo_list(struct addrinfo *input,
+ int preferred_family)
+{
+ struct addrinfo *preferred_results_head;
+ struct addrinfo *remainder_results_head;
+ struct addrinfo *preferred_results_tail;
+ struct addrinfo *remainder_results_tail;
+ struct addrinfo *current_record;
+ struct addrinfo *next_record;
+
+ remainder_results_head = NULL;
+ preferred_results_head = NULL;
+ remainder_results_tail = NULL;
+ preferred_results_tail = NULL;
+ current_record = NULL;
+ next_record = NULL;
+
+ for (current_record = input ;
+ current_record != NULL ;
+ current_record = next_record) {
+ next_record = current_record->ai_next;
+ current_record->ai_next = NULL;
+
+ if (preferred_family == current_record->ai_family) {
+ flb_net_append_addrinfo_entry(&preferred_results_head,
+ &preferred_results_tail,
+ current_record);
+ }
+ else
+ {
+ flb_net_append_addrinfo_entry(&remainder_results_head,
+ &remainder_results_tail,
+ current_record);
+ }
+ }
+
+ if (preferred_results_tail != NULL) {
+ preferred_results_tail->ai_next = remainder_results_head;
+ }
+
+ if (preferred_results_head == NULL) {
+ return remainder_results_head;
+ }
+
+ return preferred_results_head;
+}
+
+static struct addrinfo *flb_net_translate_ares_addrinfo(struct ares_addrinfo *input)
+{
+ struct addrinfo *previous_output_record;
+ struct addrinfo *current_output_record;
+ struct ares_addrinfo_node *current_ares_record;
+ int failure_detected;
+ struct addrinfo *output;
+
+ output = NULL;
+ failure_detected = 0;
+ current_output_record = NULL;
+ previous_output_record = NULL;
+
+ if (input != NULL) {
+ for (current_ares_record = input->nodes ;
+ current_ares_record != NULL ;
+ current_ares_record = current_ares_record->ai_next) {
+
+ current_output_record = flb_calloc(1, sizeof(struct addrinfo));
+
+ if (current_output_record == NULL) {
+ flb_errno();
+ failure_detected = 1;
+ break;
+ }
+
+ if (output == NULL) {
+ output = current_output_record;
+ }
+
+ current_output_record->ai_flags = current_ares_record->ai_flags;
+ current_output_record->ai_family = current_ares_record->ai_family;
+ current_output_record->ai_socktype = current_ares_record->ai_socktype;
+ current_output_record->ai_protocol = current_ares_record->ai_protocol;
+ current_output_record->ai_addrlen = current_ares_record->ai_addrlen;
+
+ current_output_record->ai_addr = flb_malloc(current_output_record->ai_addrlen);
+
+ if (current_output_record->ai_addr == NULL) {
+ flb_errno();
+ failure_detected = 1;
+ break;
+ }
+
+ memcpy(current_output_record->ai_addr,
+ current_ares_record->ai_addr,
+ current_output_record->ai_addrlen);
+
+ if (previous_output_record != NULL) {
+ previous_output_record->ai_next = current_output_record;
+ }
+
+ previous_output_record = current_output_record;
+ }
+ }
+
+ if (failure_detected) {
+ if (output != NULL) {
+ flb_net_free_translated_addrinfo(output);
+
+ output = NULL;
+ }
+ }
+
+ return output;
+}
+
+
+static void flb_net_getaddrinfo_callback(void *arg, int status, int timeouts,
+ struct ares_addrinfo *res)
+{
+ struct flb_dns_lookup_context *lookup_context;
+
+ lookup_context = (struct flb_dns_lookup_context *) arg;
+
+ if (lookup_context->finished ||
+ lookup_context->dropped) {
+ return;
+ }
+
+ if (ARES_SUCCESS == status) {
+ *(lookup_context->result) = flb_net_translate_ares_addrinfo(res);
+
+ if (*(lookup_context->result) == NULL) {
+ /* Translation fails only when calloc fails. */
+
+ *(lookup_context->result_code) = ARES_ENOMEM;
+ }
+ else {
+ *(lookup_context->result_code) = ARES_SUCCESS;
+ }
+
+ ares_freeaddrinfo(res);
+ }
+ else {
+ *(lookup_context->result_code) = status;
+ }
+
+ lookup_context->finished = 1;
+}
+
+static int flb_net_getaddrinfo_event_handler(void *arg)
+{
+ struct flb_dns_lookup_context *lookup_context;
+
+ lookup_context = FLB_DNS_LOOKUP_CONTEXT_FOR_EVENT(arg);
+
+ if (lookup_context->finished ||
+ lookup_context->dropped) {
+ return 0;
+ }
+
+ ares_process_fd(lookup_context->ares_channel,
+ lookup_context->response_event.fd,
+ lookup_context->response_event.fd);
+
+ if (lookup_context->finished) {
+ flb_net_dns_lookup_context_drop(lookup_context);
+ }
+
+ return 0;
+}
+
+static void flb_net_getaddrinfo_timeout_handler(struct flb_config *config, void *data)
+{
+ struct flb_dns_lookup_context *lookup_context;
+
+ (void) config;
+
+ lookup_context = (struct flb_dns_lookup_context *) data;
+
+ if (lookup_context->finished ||
+ lookup_context->dropped) {
+ return;
+ }
+
+ *(lookup_context->udp_timeout_detected) = FLB_TRUE;
+ lookup_context->finished = FLB_TRUE;
+ lookup_context->udp_timer = NULL;
+
+ /* We deliverately set udp_timer because we don't want flb_net_dns_lookup_context_drop
+ * to call flb_sched_timer_invalidate on the timer which was already disabled and
+ * is about to be destroyed after this this callback returns.
+ */
+
+ ares_cancel(lookup_context->ares_channel);
+
+ *(lookup_context->result_code) = ARES_ETIMEOUT;
+
+ flb_net_dns_lookup_context_drop(lookup_context);
+}
+
+static ares_socket_t flb_dns_ares_socket(int af, int type, int protocol, void *userdata)
+{
+ struct flb_dns_lookup_context *lookup_context;
+ int event_mask;
+ ares_socket_t sockfd;
+ int result;
+
+ lookup_context = (struct flb_dns_lookup_context *) userdata;
+
+ if (lookup_context->ares_socket_created) {
+ /* This context already had a connection established and the code is not ready
+ * to handle multiple connections so we abort the process.
+ */
+ errno = EACCES;
+
+ return -1;
+ }
+
+ sockfd = socket(af, type, protocol);
+
+ if (sockfd == -1) {
+ return -1;
+ }
+
+ /* According to configure_socket in ares_process.c:970 if we provide our own socket
+ * functions we need to set the socket up ourselves but the only specific thing we
+ * need is for the socket to be set to non blocking mode so that's all we do here.
+ */
+
+ result = flb_net_socket_nonblocking(sockfd);
+
+ if (result) {
+ flb_socket_close(sockfd);
+
+ return -1;
+ }
+
+ lookup_context->ares_socket_type = type;
+ lookup_context->ares_socket_created = FLB_TRUE;
+
+ lookup_context->response_event.mask = MK_EVENT_EMPTY;
+ lookup_context->response_event.status = MK_EVENT_NONE;
+ lookup_context->response_event.data = &lookup_context->response_event;
+ lookup_context->response_event.handler = flb_net_getaddrinfo_event_handler;
+ lookup_context->response_event.fd = sockfd;
+
+ event_mask = MK_EVENT_READ;
+
+ if (SOCK_STREAM == type) {
+ event_mask |= MK_EVENT_WRITE;
+ }
+
+ result = mk_event_add(lookup_context->event_loop, sockfd, FLB_ENGINE_EV_CUSTOM,
+ event_mask, &lookup_context->response_event);
+ lookup_context->response_event.priority = FLB_ENGINE_PRIORITY_DNS;
+ if (result) {
+ flb_socket_close(sockfd);
+
+ return -1;
+ }
+
+ lookup_context->response_event.type = FLB_ENGINE_EV_CUSTOM;
+ lookup_context->ares_socket_registered = FLB_TRUE;
+
+ return sockfd;
+}
+
+static int flb_dns_ares_close(ares_socket_t sockfd, void *userdata)
+{
+ struct flb_dns_lookup_context *lookup_context;
+ int result;
+
+ lookup_context = (struct flb_dns_lookup_context *) userdata;
+
+ if (lookup_context->ares_socket_registered) {
+ lookup_context->ares_socket_registered = FLB_FALSE;
+
+ mk_event_del(lookup_context->event_loop, &lookup_context->response_event);
+ }
+
+ result = flb_socket_close(sockfd);
+
+ return result;
+}
+
+static int flb_dns_ares_connect(ares_socket_t sockfd, const struct sockaddr *addr,
+ ares_socklen_t addrlen, void *userdata)
+{
+ return connect(sockfd, addr, addrlen);
+}
+
+static ares_ssize_t flb_dns_ares_recvfrom(ares_socket_t sockfd, void *data,
+ size_t data_len, int flags,
+ struct sockaddr *from, ares_socklen_t *from_len,
+ void *userdata)
+{
+ return recvfrom(sockfd, data, data_len, flags, from, from_len);
+}
+
+static ares_ssize_t flb_dns_ares_send(ares_socket_t sockfd, const struct iovec *vec,
+ int len, void *userdata)
+{
+ return writev(sockfd, vec, len);
+}
+
+static struct flb_dns_lookup_context *flb_net_dns_lookup_context_create(
+ struct flb_net_dns *dns_ctx,
+ struct mk_event_loop *evl,
+ struct flb_coro *coroutine,
+ char dns_mode,
+ int *result)
+{
+ struct flb_dns_lookup_context *lookup_context;
+ int local_result;
+ int optmask;
+ struct ares_options opts = {0};
+
+ local_result = 0;
+ optmask = 0;
+
+ if (result == NULL) {
+ result = &local_result;
+ }
+
+ /* The initialization order here is important since it makes it easier to handle
+ * failures
+ */
+ lookup_context = flb_calloc(1, sizeof(struct flb_dns_lookup_context));
+
+ if (!lookup_context) {
+ flb_errno();
+
+ *result = ARES_ENOMEM;
+
+ return NULL;
+ }
+
+ /* c-ares options: Set the transport layer to the desired protocol and
+ * the number of retries to 2
+ */
+
+ optmask = ARES_OPT_FLAGS;
+ opts.tries = 2;
+
+ if (dns_mode == FLB_DNS_USE_TCP) {
+ opts.flags = ARES_FLAG_USEVC;
+ }
+
+ *result = ares_init_options((ares_channel *) &lookup_context->ares_channel,
+ &opts, optmask);
+
+ if (*result != ARES_SUCCESS) {
+ flb_free(lookup_context);
+
+ return NULL;
+ }
+
+ lookup_context->ares_socket_functions.asocket = flb_dns_ares_socket;
+ lookup_context->ares_socket_functions.aclose = flb_dns_ares_close;
+ lookup_context->ares_socket_functions.aconnect = flb_dns_ares_connect;
+ lookup_context->ares_socket_functions.arecvfrom = flb_dns_ares_recvfrom;
+ lookup_context->ares_socket_functions.asendv = flb_dns_ares_send;
+ lookup_context->ares_socket_created = 0;
+ lookup_context->event_loop = evl;
+ lookup_context->udp_timer = NULL;
+ lookup_context->coroutine = coroutine;
+ lookup_context->finished = 0;
+ lookup_context->dropped = 0;
+ lookup_context->dns_ctx = dns_ctx;
+
+ ares_set_socket_functions(lookup_context->ares_channel,
+ &lookup_context->ares_socket_functions,
+ lookup_context);
+
+ *result = ARES_SUCCESS;
+
+ mk_list_add(&lookup_context->_head, &dns_ctx->lookups);
+
+ return lookup_context;
+}
+
+int flb_net_getaddrinfo(const char *node, const char *service, struct addrinfo *hints,
+ struct addrinfo **res, char *dns_mode_textual, int timeout)
+{
+ int udp_timeout_detected;
+ struct flb_dns_lookup_context *lookup_context;
+ int errno_backup;
+ int result_code;
+ struct addrinfo *result_data;
+ struct ares_addrinfo_hints ares_hints;
+ struct mk_event_loop *event_loop;
+ struct flb_coro *coroutine;
+ char dns_mode;
+ struct flb_net_dns *dns_ctx;
+ int result;
+ struct flb_sched *sched;
+
+ errno_backup = errno;
+
+ dns_mode = FLB_DNS_USE_UDP;
+
+ if (dns_mode_textual != NULL) {
+ dns_mode = toupper(dns_mode_textual[0]);
+ }
+
+ event_loop = flb_engine_evl_get();
+ assert(event_loop != NULL);
+
+ coroutine = flb_coro_get();
+ assert(coroutine != NULL);
+
+ dns_ctx = flb_net_dns_ctx_get();
+ assert(dns_ctx != NULL);
+
+ lookup_context = flb_net_dns_lookup_context_create(dns_ctx, event_loop, coroutine,
+ dns_mode, &result);
+
+ if (result != ARES_SUCCESS) {
+ errno = errno_backup;
+ return result;
+ }
+
+ lookup_context->udp_timeout_detected = &udp_timeout_detected;
+ lookup_context->result_code = &result_code;
+ lookup_context->result = &result_data;
+
+ /* We think that either the callback or the timeout handler should be executed always
+ * but just in case that there is a corner case we initialize result_code with an
+ * error code so in case none of those is invoked (which shouldn't happen) the code
+ * is not ARES_SUCCESS and thus cause a NULL pointer to be returned.
+ */
+ result_code = ARES_ESERVFAIL;
+ result_data = NULL;
+ udp_timeout_detected = 0;
+
+ /* The timeout we get is expressed in seconds so we need to convert it to
+ * milliseconds
+ */
+ timeout *= 1000;
+
+ /* We need to ensure that our timer won't overlap with the upstream timeout handler.
+ */
+ if (timeout > 3000) {
+ timeout -= 1000;
+ }
+ else {
+ timeout -= (timeout / 3);
+ }
+
+ ares_hints.ai_flags = hints->ai_flags;
+ ares_hints.ai_family = hints->ai_family;
+ ares_hints.ai_socktype = hints->ai_socktype;
+ ares_hints.ai_protocol = hints->ai_protocol;
+
+ ares_getaddrinfo(lookup_context->ares_channel, node, service, &ares_hints,
+ flb_net_getaddrinfo_callback, lookup_context);
+
+ if (!lookup_context->finished) {
+ if (lookup_context->ares_socket_created) {
+ if (lookup_context->ares_socket_type == SOCK_DGRAM) {
+ /* If the socket type created by c-ares is UDP then we need to create our
+ * own timeout mechanism before yielding and cancel it if things go as
+ * expected.
+ */
+
+ sched = flb_sched_ctx_get();
+ assert(sched != NULL);
+
+ result = flb_sched_timer_cb_create(sched, FLB_SCHED_TIMER_CB_ONESHOT,
+ timeout,
+ flb_net_getaddrinfo_timeout_handler,
+ lookup_context,
+ &lookup_context->udp_timer);
+ if (result == -1) {
+ /* Timer creation failed, it happen because of file descriptor or memory
+ * exhaustion (ulimits usually)
+ */
+
+ result_code = ARES_ENOMEM;
+
+ ares_cancel(lookup_context->ares_channel);
+
+ lookup_context->coroutine = NULL;
+
+ flb_net_dns_lookup_context_drop(lookup_context);
+ }
+ else {
+ flb_coro_yield(coroutine, FLB_FALSE);
+ }
+ }
+ else {
+ flb_coro_yield(coroutine, FLB_FALSE);
+ }
+ }
+ else {
+ /* Do we want to do anything special for this condition? */
+ }
+ }
+ else {
+ lookup_context->coroutine = NULL;
+
+ flb_net_dns_lookup_context_drop(lookup_context);
+ }
+
+ if (!result_code) {
+ *res = result_data;
+ }
+
+ result = result_code;
+ errno = errno_backup;
+
+ return result;
+}
+
+int flb_net_bind_address(int fd, char *source_addr)
+{
+ int ret;
+ struct addrinfo hint;
+ struct addrinfo *res = NULL;
+ struct sockaddr_storage addr;
+
+ memset(&hint, '\0', sizeof hint);
+
+ hint.ai_family = PF_UNSPEC;
+ hint.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV | AI_PASSIVE;
+
+ ret = getaddrinfo(source_addr, NULL, &hint, &res);
+ if (ret == -1) {
+ flb_errno();
+ flb_error("[net] cannot read source_address=%s", source_addr);
+ return -1;
+ }
+
+ /* Bind the address */
+ memcpy(&addr, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ ret = bind(fd, (struct sockaddr *) &addr, sizeof(addr));
+ if (ret == -1) {
+ flb_errno();
+ flb_error("[net] could not bind source_address=%s", source_addr);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void set_ip_family(const char *host, struct addrinfo *hints)
+{
+
+ int ret;
+ struct in6_addr serveraddr;
+
+ /* check if the given 'host' is a network address, adjust ai_flags */
+ ret = inet_pton(AF_INET, host, &serveraddr);
+ if (ret == 1) { /* valid IPv4 text address ? */
+ hints->ai_family = AF_INET;
+ hints->ai_flags |= AI_NUMERICHOST;
+ }
+ else {
+ ret = inet_pton(AF_INET6, host, &serveraddr);
+ if (ret == 1) { /* valid IPv6 text address ? */
+ hints->ai_family = AF_INET6;
+ hints->ai_flags |= AI_NUMERICHOST;
+ }
+ }
+}
+
+/* Connect to a TCP socket server and returns the file descriptor */
+flb_sockfd_t flb_net_tcp_connect(const char *host, unsigned long port,
+ char *source_addr, int connect_timeout,
+ int is_async,
+ void *async_ctx,
+ struct flb_connection *u_conn)
+{
+ int ret;
+ int use_async_dns;
+ char resolver_initial;
+ flb_sockfd_t fd = -1;
+ char _port[6];
+ char address[41];
+ struct addrinfo hints;
+ struct addrinfo *sorted_res, *res, *rp;
+
+ if (is_async == FLB_TRUE && !u_conn) {
+ flb_error("[net] invalid async mode with not set upstream connection");
+ return -1;
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ /* Set hints */
+ set_ip_family(host, &hints);
+
+ /* fomart the TCP port */
+ snprintf(_port, sizeof(_port), "%lu", port);
+
+ use_async_dns = is_async;
+
+ if (u_conn->net->dns_resolver != NULL) {
+ resolver_initial = toupper(u_conn->net->dns_resolver[0]);
+
+ if (resolver_initial == FLB_DNS_LEGACY) {
+ use_async_dns = FLB_FALSE;
+ }
+ }
+
+ /* retrieve DNS info */
+ if (use_async_dns) {
+ ret = flb_net_getaddrinfo(host, _port, &hints, &res,
+ u_conn->net->dns_mode,
+ connect_timeout);
+ }
+ else {
+ ret = getaddrinfo(host, _port, &hints, &res);
+ }
+
+ if (ret) {
+ if (use_async_dns) {
+ flb_warn("[net] getaddrinfo(host='%s', err=%d): %s", host, ret, ares_strerror(ret));
+ }
+ else {
+ flb_warn("[net] getaddrinfo(host='%s', err=%d): %s", host, ret, gai_strerror(ret));
+ }
+
+ return -1;
+ }
+
+ if (u_conn->net_error > 0) {
+ if (u_conn->net_error == ETIMEDOUT) {
+ flb_warn("[net] timeout detected between DNS lookup and connection attempt");
+ }
+
+ if (use_async_dns) {
+ flb_net_free_translated_addrinfo(res);
+ }
+ else {
+ freeaddrinfo(res);
+ }
+
+ return -1;
+ }
+
+ sorted_res = res;
+
+ if (u_conn->net->dns_prefer_ipv4) {
+ sorted_res = flb_net_sort_addrinfo_list(res, AF_INET);
+
+ if (sorted_res == NULL) {
+ flb_debug("[net] error sorting getaddrinfo results");
+
+ if (use_async_dns) {
+ flb_net_free_translated_addrinfo(res);
+ }
+ else {
+ freeaddrinfo(res);
+ }
+
+ return -1;
+ }
+ }
+
+ /*
+ * Try to connect: on this iteration we try to connect to the first
+ * available address.
+ */
+ for (rp = sorted_res; rp != NULL; rp = rp->ai_next) {
+ if (u_conn->net_error > 0) {
+ if (u_conn->net_error == ETIMEDOUT) {
+ flb_warn("[net] timeout detected between connection attempts");
+ }
+ }
+
+ /* create socket */
+ fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (fd == -1) {
+ flb_error("[net] coult not create client socket, retrying");
+ continue;
+ }
+
+ /* asynchronous socket ? */
+ if (is_async == FLB_TRUE) {
+ flb_net_socket_nonblocking(fd);
+ }
+
+ /* Bind a specific network interface ? */
+ if (source_addr != NULL) {
+ ret = flb_net_bind_address(fd, source_addr);
+
+ if (ret == -1) {
+ flb_warn("[net] falling back to random interface");
+ }
+ else {
+ flb_trace("[net] client connect bind address: %s", source_addr);
+ }
+ }
+
+ /* Disable Nagle's algorithm */
+ flb_net_socket_tcp_nodelay(fd);
+
+ /* Set receive timeout */
+ flb_net_socket_set_rcvtimeout(fd, u_conn->net->io_timeout);
+
+ if (u_conn) {
+ u_conn->fd = fd;
+ u_conn->event.fd = fd;
+ }
+
+ flb_connection_set_remote_host(u_conn, rp->ai_addr);
+
+ /* Perform TCP connection */
+ if (is_async == FLB_TRUE) {
+ ret = net_connect_async(fd, rp->ai_addr, rp->ai_addrlen,
+ (char *) host, port, connect_timeout,
+ async_ctx, u_conn);
+
+ }
+ else {
+ ret = net_connect_sync(fd, rp->ai_addr, rp->ai_addrlen,
+ (char *) host, port, connect_timeout);
+ }
+
+ if (u_conn->net_error == ETIMEDOUT) {
+ /* flb_upstream_conn_timeouts called prepare_destroy_conn which
+ * closed the file descriptor and removed it from the event so
+ * we can safely ignore it.
+ */
+
+ fd = -1;
+
+ break;
+ }
+
+ if (ret == -1) {
+ address[0] = '\0';
+
+ ret = flb_net_address_to_str(rp->ai_family, rp->ai_addr,
+ address, sizeof(address));
+
+ /* If the connection failed, just abort and report the problem */
+ flb_debug("[net] socket #%i could not connect to %s:%s",
+ fd, address, _port);
+
+ if (u_conn) {
+ u_conn->fd = -1;
+ u_conn->event.fd = -1;
+ }
+
+ flb_socket_close(fd);
+ fd = -1;
+
+ continue;
+ }
+
+ break;
+ }
+
+ if (fd == -1) {
+ flb_debug("[net] could not connect to %s:%s",
+ host, _port);
+ }
+
+ if (use_async_dns) {
+ flb_net_free_translated_addrinfo(res);
+ }
+ else {
+ freeaddrinfo(res);
+ }
+
+ if (rp == NULL) {
+ return -1;
+ }
+
+ return fd;
+}
+
+/* "Connect" to a UDP socket server and returns the file descriptor */
+flb_sockfd_t flb_net_udp_connect(const char *host, unsigned long port,
+ char *source_addr)
+{
+ int ret;
+ flb_sockfd_t fd = -1;
+ char _port[6];
+ struct addrinfo hints;
+ struct addrinfo *res, *rp;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+
+ /* Set hints */
+ set_ip_family(host, &hints);
+
+ /* Format UDP port */
+ snprintf(_port, sizeof(_port), "%lu", port);
+
+ /* retrieve DNS info */
+ ret = getaddrinfo(host, _port, &hints, &res);
+ if (ret != 0) {
+ flb_warn("net]: getaddrinfo(host='%s'): %s",
+ host, gai_strerror(ret));
+ return -1;
+ }
+
+ for (rp = res; rp != NULL; rp = rp->ai_next) {
+ /* create socket */
+ fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (fd == -1) {
+ flb_error("[net] coult not create client socket, retrying");
+ continue;
+ }
+
+ /* Bind a specific network interface ? */
+ if (source_addr != NULL) {
+ ret = flb_net_bind_address(fd, source_addr);
+ if (ret == -1) {
+ flb_warn("[net] falling back to random interface");
+ }
+ else {
+ flb_trace("[net] client connect bind address: %s", source_addr);
+ }
+ }
+
+ /*
+ * Why do we connect(2) an UDP socket ?, is this useful ?: Yes. Despite
+ * an UDP socket it's not in a connection state, connecting through the
+ * API it helps the Kernel to configure the destination address and
+ * is totally valid, so then you don't need to use sendto(2).
+ *
+ * For our use case this is quite helpful, since the caller keeps using
+ * the same Fluent Bit I/O API to deliver a message.
+ */
+ if (connect(fd, rp->ai_addr, rp->ai_addrlen) == -1) {
+ flb_error("[net] UDP socket %i could connect to %s:%s",
+ fd, host, _port);
+ flb_socket_close(fd);
+ fd = -1;
+ break;
+ }
+ break;
+ }
+
+ freeaddrinfo(res);
+
+ if (rp == NULL) {
+ return -1;
+ }
+
+ return fd;
+}
+
+/* Connect to a TCP socket server and returns the file descriptor */
+int flb_net_tcp_fd_connect(flb_sockfd_t fd, const char *host, unsigned long port)
+{
+ int ret;
+ struct addrinfo hints;
+ struct addrinfo *res;
+ char _port[6];
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ snprintf(_port, sizeof(_port), "%lu", port);
+ ret = getaddrinfo(host, _port, &hints, &res);
+ if (ret != 0) {
+ flb_warn("net_tcp_fd_connect: getaddrinfo(host='%s'): %s",
+ host, gai_strerror(ret));
+ return -1;
+ }
+
+ ret = connect(fd, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+
+ return ret;
+}
+
+flb_sockfd_t flb_net_server(const char *port, const char *listen_addr)
+{
+ flb_sockfd_t fd = -1;
+ int ret;
+ struct addrinfo hints;
+ struct addrinfo *res, *rp;
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+
+ ret = getaddrinfo(listen_addr, port, &hints, &res);
+ if (ret != 0) {
+ flb_warn("net_server: getaddrinfo(listen='%s:%s'): %s",
+ listen_addr, port, gai_strerror(ret));
+ return -1;
+ }
+
+ for (rp = res; rp != NULL; rp = rp->ai_next) {
+ fd = flb_net_socket_create(rp->ai_family, 1);
+ if (fd == -1) {
+ flb_error("Error creating server socket, retrying");
+ continue;
+ }
+
+ flb_net_socket_tcp_nodelay(fd);
+ flb_net_socket_reset(fd);
+
+ ret = flb_net_bind(fd, rp->ai_addr, rp->ai_addrlen, 128);
+ if(ret == -1) {
+ flb_warn("Cannot listen on %s port %s", listen_addr, port);
+ flb_socket_close(fd);
+ continue;
+ }
+ break;
+ }
+ freeaddrinfo(res);
+
+ if (rp == NULL) {
+ return -1;
+ }
+
+ return fd;
+}
+
+flb_sockfd_t flb_net_server_udp(const char *port, const char *listen_addr)
+{
+ flb_sockfd_t fd = -1;
+ int ret;
+ struct addrinfo hints;
+ struct addrinfo *res, *rp;
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE;
+
+ ret = getaddrinfo(listen_addr, port, &hints, &res);
+ if (ret != 0) {
+ flb_warn("net_server_udp: getaddrinfo(listen='%s:%s'): %s",
+ listen_addr, port, gai_strerror(ret));
+ return -1;
+ }
+
+ for (rp = res; rp != NULL; rp = rp->ai_next) {
+ fd = flb_net_socket_create_udp(rp->ai_family, 0);
+ if (fd == -1) {
+ flb_error("Error creating server socket, retrying");
+ continue;
+ }
+
+ ret = flb_net_bind_udp(fd, rp->ai_addr, rp->ai_addrlen);
+ if(ret == -1) {
+ flb_warn("Cannot listen on %s port %s", listen_addr, port);
+ flb_socket_close(fd);
+ continue;
+ }
+ break;
+ }
+ freeaddrinfo(res);
+
+ if (rp == NULL) {
+ return -1;
+ }
+
+ return fd;
+}
+
+#ifdef FLB_HAVE_UNIX_SOCKET
+flb_sockfd_t flb_net_server_unix(const char *listen_path,
+ int stream_mode,
+ int backlog)
+{
+ size_t address_length;
+ size_t path_length;
+ struct sockaddr_un address;
+ int ret;
+ flb_sockfd_t fd;
+
+ if (stream_mode) {
+ fd = flb_net_socket_create(AF_UNIX, FLB_TRUE);
+ }
+ else {
+ fd = flb_net_socket_create_udp(AF_UNIX, FLB_TRUE);
+ }
+
+ if (fd != -1) {
+ memset(&address, 0, sizeof(struct sockaddr_un));
+
+ path_length = strlen(listen_path);
+
+ address_length = offsetof(struct sockaddr_un, sun_path) +
+ path_length +
+ 1;
+
+ address.sun_family = AF_UNIX;
+
+ strncpy(address.sun_path, listen_path, sizeof(address.sun_path));
+
+ if (stream_mode) {
+ ret = flb_net_bind(fd,
+ (const struct sockaddr *) &address,
+ address_length,
+ backlog);
+ }
+ else {
+ ret = flb_net_bind_udp(fd,
+ (const struct sockaddr *) &address,
+ address_length);
+ }
+
+ if(ret == -1) {
+ flb_warn("Cannot bind to or listen on %s", listen_path);
+
+ flb_socket_close(fd);
+ }
+ }
+ else {
+ flb_error("Error creating server socket");
+ }
+
+ return fd;
+}
+#else
+flb_sockfd_t flb_net_server_unix(const char *listen_path,
+ int stream_mode,
+ int backlog)
+{
+ flb_error("Unix sockets are not available in this platform");
+
+ return -1;
+}
+#endif
+
+int flb_net_bind(flb_sockfd_t fd, const struct sockaddr *addr,
+ socklen_t addrlen, int backlog)
+{
+ int ret;
+
+ ret = bind(fd, addr, addrlen);
+ if( ret == -1 ) {
+ flb_error("Error binding socket");
+ return ret;
+ }
+
+ ret = listen(fd, backlog);
+ if(ret == -1 ) {
+ flb_error("Error setting up the listener");
+ return -1;
+ }
+
+ return ret;
+}
+
+int flb_net_bind_udp(flb_sockfd_t fd, const struct sockaddr *addr,
+ socklen_t addrlen)
+{
+ int ret;
+
+ ret = bind(fd, addr, addrlen);
+ if( ret == -1 ) {
+ flb_error("Error binding socket");
+ return ret;
+ }
+
+ return ret;
+}
+
+flb_sockfd_t flb_net_accept(flb_sockfd_t server_fd)
+{
+ flb_sockfd_t remote_fd;
+ struct sockaddr sock_addr;
+ socklen_t socket_size = sizeof(struct sockaddr);
+
+ // return accept(server_fd, &sock_addr, &socket_size);
+
+#ifdef FLB_HAVE_ACCEPT4
+ remote_fd = accept4(server_fd, &sock_addr, &socket_size,
+ SOCK_NONBLOCK | SOCK_CLOEXEC);
+#else
+ remote_fd = accept(server_fd, &sock_addr, &socket_size);
+ flb_net_socket_nonblocking(remote_fd);
+#endif
+
+ if (remote_fd == -1) {
+ perror("accept4");
+ }
+
+ return remote_fd;
+}
+
+int flb_net_address_to_str(int family, const struct sockaddr *addr,
+ char *output_buffer, size_t output_buffer_size)
+{
+ struct sockaddr *proper_addr;
+ const char *result;
+
+ if (family == AF_INET) {
+ proper_addr = (struct sockaddr *) &((struct sockaddr_in *) addr)->sin_addr;
+ }
+ else if (family == AF_INET6) {
+ proper_addr = (struct sockaddr *) &((struct sockaddr_in6 *) addr)->sin6_addr;
+ }
+ else {
+ strncpy(output_buffer,
+ "CONVERSION ERROR 1",
+ output_buffer_size);
+
+ return -1;
+ }
+
+ result = inet_ntop(family, proper_addr, output_buffer, output_buffer_size);
+
+ if (result == NULL) {
+ strncpy(output_buffer,
+ "CONVERSION ERROR 2",
+ output_buffer_size);
+
+ return -2;
+ }
+
+ return 0;
+}
+
+#ifdef FLB_COMPILE_UNUSED_FUNCTIONS
+static int net_socket_get_local_address(flb_sockfd_t fd,
+ struct sockaddr_storage *address)
+{
+ socklen_t buffer_size;
+ int result;
+
+ buffer_size = sizeof(struct sockaddr_storage);
+
+ result = getsockname(fd, (struct sockaddr *) &address, &buffer_size);
+
+ if (result == -1) {
+ return -1;
+ }
+
+ return 0;
+}
+#endif
+
+static int net_socket_get_peer_address(flb_sockfd_t fd,
+ struct sockaddr_storage *address)
+{
+ socklen_t buffer_size;
+ int result;
+
+ buffer_size = sizeof(struct sockaddr_storage);
+
+ result = getpeername(fd, (struct sockaddr *) address, &buffer_size);
+
+ if (result == -1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static unsigned short int net_address_port(struct sockaddr_storage *address)
+{
+ unsigned short int port;
+
+ if (address->ss_family == AF_INET) {
+ port = ((struct sockaddr_in *) address)->sin_port;
+ }
+ else if (address->ss_family == AF_INET6) {
+ port = ((struct sockaddr_in6 *) address)->sin6_port;
+ }
+ else {
+ port = 0;
+ }
+
+ return ntohs(port);
+}
+
+#ifdef FLB_HAVE_UNIX_SOCKET
+static int net_address_unix_socket_peer_pid_raw(flb_sockfd_t fd,
+ struct sockaddr_storage *address,
+ char *output_buffer,
+ int output_buffer_size,
+ size_t *output_data_size)
+{
+#if !defined(FLB_SYSTEM_MACOS) && !defined(FLB_SYSTEM_FREEBSD)
+ unsigned int peer_credentials_size;
+ struct ucred peer_credentials;
+#endif
+ size_t required_buffer_size;
+ int result = 0;
+
+ if (address->ss_family != AF_UNIX) {
+ return -1;
+ }
+
+ required_buffer_size = 11; /* maximum 32 bit signed integer */
+ required_buffer_size += 1; /* string terminator */
+
+ if (required_buffer_size > output_buffer_size) {
+ return -1;
+ }
+
+#if !defined(FLB_SYSTEM_MACOS) && !defined(FLB_SYSTEM_FREEBSD)
+ peer_credentials_size = sizeof(struct ucred);
+
+ result = getsockopt(fd,
+ SOL_SOCKET,
+ SO_PEERCRED,
+ &peer_credentials,
+ &peer_credentials_size);
+
+ if (result != -1) {
+ *output_data_size = snprintf(output_buffer,
+ output_buffer_size,
+ "%ld",
+ (long) peer_credentials.pid);
+ }
+#else
+ *output_data_size = snprintf(output_buffer,
+ output_buffer_size,
+ FLB_NETWORK_ADDRESS_UNAVAILABLE);
+#endif
+
+ return result;
+}
+
+static int net_address_unix_socket_peer_pid_str(flb_sockfd_t fd,
+ struct sockaddr_storage *address,
+ char *output_buffer,
+ int output_buffer_size,
+ size_t *output_data_size)
+{
+ size_t required_buffer_size;
+ size_t peer_pid_length;
+ char peer_pid[12];
+ int result;
+
+ if (address->ss_family != AF_UNIX) {
+ return -1;
+ }
+
+ result = net_address_unix_socket_peer_pid_raw(fd,
+ address,
+ peer_pid,
+ sizeof(peer_pid),
+ &peer_pid_length);
+
+ if (result != 0) {
+ return -1;
+ }
+
+ required_buffer_size = strlen(FLB_NETWORK_UNIX_SOCKET_PEER_ADDRESS_TEMPLATE);
+ required_buffer_size += peer_pid_length;
+ required_buffer_size -= 2; /* format string specifiers */
+ required_buffer_size += 1; /* string terminator */
+
+ if (required_buffer_size > output_buffer_size) {
+ *output_data_size = required_buffer_size;
+
+ return -1;
+ }
+
+ *output_data_size = snprintf(output_buffer,
+ output_buffer_size,
+ FLB_NETWORK_UNIX_SOCKET_PEER_ADDRESS_TEMPLATE,
+ peer_pid);
+
+ return 0;
+}
+#endif
+
+size_t flb_network_address_size(struct sockaddr_storage *address)
+{
+ if (address->ss_family == AF_INET) {
+ return sizeof(struct sockaddr_in);
+ }
+ else if (address->ss_family == AF_INET6) {
+ return sizeof(struct sockaddr_in6);
+ }
+#ifdef FLB_HAVE_UNIX_SOCKET
+ else if (address->ss_family == AF_UNIX) {
+ return sizeof(struct sockaddr_un);
+ }
+#endif
+
+ return 0;
+}
+
+static int net_address_ip_raw(flb_sockfd_t fd,
+ struct sockaddr_storage *address,
+ char *output_buffer,
+ int output_buffer_size,
+ size_t *output_data_size)
+{
+ char peer_pid[12];
+ char *address_data;
+ size_t address_size;
+ int result;
+
+ errno = 0;
+
+ if (address->ss_family == AF_UNSPEC) {
+ flb_debug("socket_ip_raw: uninitialized address");
+
+ return -1;
+ }
+ if (address->ss_family == AF_INET) {
+ address_data = ((char *) &((struct sockaddr_in *) address)->sin_addr);
+ address_size = sizeof(struct in_addr);
+ }
+ else if (address->ss_family == AF_INET6) {
+ address_data = ((char *) &((struct sockaddr_in6 *) address)->sin6_addr);
+ address_size = sizeof(struct in6_addr);
+ }
+#ifdef FLB_HAVE_UNIX_SOCKET
+ else if (address->ss_family == AF_UNIX) {
+ result = net_address_unix_socket_peer_pid_raw(fd,
+ address,
+ peer_pid,
+ sizeof(peer_pid),
+ &address_size);
+
+ if (result != 0) {
+ flb_debug("socket_ip_raw: error getting client process pid");
+
+ return -1;
+ }
+
+ address_data = peer_pid;
+ }
+#endif
+ else {
+ flb_debug("socket_ip_raw: unsupported address type (%i)",
+ address->ss_family);
+
+ return -1;
+ }
+
+ if (output_buffer_size < address_size) {
+ flb_debug("socket_ip_raw: insufficient buffer size (%i < %zu)",
+ output_buffer_size, address_size);
+
+ return -1;
+ }
+
+ memcpy(output_buffer, address_data, address_size);
+
+ if (output_data_size != NULL) {
+ *output_data_size = address_size;
+ }
+
+ return 0;
+}
+
+static int net_address_ip_str(flb_sockfd_t fd,
+ struct sockaddr_storage *address,
+ char *output_buffer,
+ int output_buffer_size,
+ size_t *output_data_size)
+{
+ void *address_data;
+ int result;
+
+ errno = 0;
+
+ if (address->ss_family == AF_UNSPEC) {
+ *output_data_size = snprintf(output_buffer,
+ output_buffer_size,
+ FLB_NETWORK_ADDRESS_UNAVAILABLE);
+
+ return 0;
+ }
+ else if (address->ss_family == AF_INET) {
+ address_data = (void *) &((struct sockaddr_in *) address)->sin_addr;
+ }
+ else if (address->ss_family == AF_INET6) {
+ address_data = (void *) &((struct sockaddr_in6 *) address)->sin6_addr;
+ }
+#ifdef FLB_HAVE_UNIX_SOCKET
+ else if (address->ss_family == AF_UNIX) {
+ result = net_address_unix_socket_peer_pid_str(fd,
+ address,
+ output_buffer,
+ output_buffer_size,
+ output_data_size);
+
+ if (result != 0) {
+ flb_debug("socket_ip_str: error getting client process pid");
+ }
+
+ return result;
+ }
+#endif
+ else {
+ flb_debug("socket_ip_str: unsupported address type (%i)",
+ address->ss_family);
+
+ return -1;
+ }
+
+ if ((inet_ntop(address->ss_family,
+ address_data,
+ output_buffer,
+ output_buffer_size)) == NULL) {
+ flb_debug("socket_ip_str: Can't get the IP text form (%i)", errno);
+
+ return -1;
+ }
+
+ *output_data_size = strlen(output_buffer);
+
+ return 0;
+}
+
+int flb_net_socket_peer_address(flb_sockfd_t fd,
+ struct sockaddr_storage *output_buffer)
+{
+ return net_socket_get_peer_address(fd, output_buffer);
+}
+
+int flb_net_socket_address_info(flb_sockfd_t fd,
+ struct sockaddr_storage *address,
+ unsigned short int *port_output_buffer,
+ char *str_output_buffer,
+ int str_output_buffer_size,
+ size_t *str_output_data_size)
+{
+ int result;
+
+ result = net_address_ip_str(fd, address,
+ str_output_buffer,
+ str_output_buffer_size,
+ str_output_data_size);
+
+ if (result == 0) {
+ if (port_output_buffer != NULL) {
+ *port_output_buffer = net_address_port(address);
+ }
+ }
+
+ return result;
+}
+
+int flb_net_socket_ip_peer_str(flb_sockfd_t fd,
+ char *output_buffer,
+ int output_buffer_size,
+ size_t *output_data_size,
+ int *output_address_family)
+{
+ struct sockaddr_storage address;
+ int result;
+
+ result = net_socket_get_peer_address(fd, &address);
+
+ if (result != 0) {
+ return -1;
+ }
+
+ if (address.ss_family == AF_UNIX) {
+
+ }
+
+ result = net_address_ip_str(fd, &address,
+ output_buffer,
+ output_buffer_size,
+ output_data_size);
+
+ if (result == 0) {
+ if (output_address_family != NULL) {
+ *output_address_family = address.ss_family;
+ }
+ }
+
+ return result;
+}
+
+int flb_net_socket_peer_ip_raw(flb_sockfd_t fd,
+ char *output_buffer,
+ int output_buffer_size,
+ size_t *output_data_size,
+ int *output_address_family)
+{
+ struct sockaddr_storage address;
+ int result;
+
+ result = net_socket_get_peer_address(fd, &address);
+
+ if (result != 0) {
+ return -1;
+ }
+
+ result = net_address_ip_raw(fd, &address,
+ output_buffer,
+ output_buffer_size,
+ output_data_size);
+
+ if (result == 0) {
+ if (output_address_family != NULL) {
+ *output_address_family = address.ss_family;
+ }
+ }
+
+ return result;
+}
+
+int flb_net_socket_peer_port(flb_sockfd_t fd,
+ unsigned short int *output_buffer)
+{
+ struct sockaddr_storage address;
+ int result;
+
+ result = net_socket_get_peer_address(fd, &address);
+
+ if (result != 0) {
+ return -1;
+ }
+
+ *output_buffer = net_address_port(&address);
+
+ return 0;
+}
+
+int flb_net_socket_peer_info(flb_sockfd_t fd,
+ unsigned short int *port_output_buffer,
+ struct sockaddr_storage *raw_output_buffer,
+ char *str_output_buffer,
+ int str_output_buffer_size,
+ size_t *str_output_data_size)
+{
+ struct sockaddr_storage address;
+ int result;
+
+ result = net_socket_get_peer_address(fd, &address);
+
+ if (result != 0) {
+ return -1;
+ }
+
+ memcpy(raw_output_buffer,
+ &address,
+ sizeof(struct sockaddr_storage));
+
+ return flb_net_socket_address_info(fd,
+ &address,
+ port_output_buffer,
+ str_output_buffer,
+ str_output_buffer_size,
+ str_output_data_size);
+}
diff --git a/fluent-bit/src/flb_oauth2.c b/fluent-bit/src/flb_oauth2.c
new file mode 100644
index 000000000..b507adc87
--- /dev/null
+++ b/fluent-bit/src/flb_oauth2.c
@@ -0,0 +1,437 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_oauth2.h>
+#include <fluent-bit/flb_upstream.h>
+#include <fluent-bit/flb_http_client.h>
+#include <fluent-bit/flb_jsmn.h>
+
+#define free_temporary_buffers() \
+ if (prot) { \
+ flb_free(prot); \
+ } \
+ if (host) { \
+ flb_free(host); \
+ } \
+ if (port) { \
+ flb_free(port); \
+ } \
+ if (uri) { \
+ flb_free(uri); \
+ }
+
+static inline int key_cmp(const char *str, int len, const char *cmp) {
+
+ if (strlen(cmp) != len) {
+ return -1;
+ }
+
+ return strncasecmp(str, cmp, len);
+}
+
+int flb_oauth2_parse_json_response(const char *json_data, size_t json_size,
+ struct flb_oauth2 *ctx)
+{
+ int i;
+ int ret;
+ int key_len;
+ int val_len;
+ int tokens_size = 32;
+ const char *key;
+ const char *val;
+ jsmn_parser parser;
+ jsmntok_t *t;
+ jsmntok_t *tokens;
+
+ jsmn_init(&parser);
+ tokens = flb_calloc(1, sizeof(jsmntok_t) * tokens_size);
+ if (!tokens) {
+ flb_errno();
+ return -1;
+ }
+
+ ret = jsmn_parse(&parser, json_data, json_size, tokens, tokens_size);
+ if (ret <= 0) {
+ flb_error("[oauth2] cannot parse payload:\n%s", json_data);
+ flb_free(tokens);
+ return -1;
+ }
+
+ t = &tokens[0];
+ if (t->type != JSMN_OBJECT) {
+ flb_error("[oauth2] invalid JSON response:\n%s", json_data);
+ flb_free(tokens);
+ return -1;
+ }
+
+ /* Parse JSON tokens */
+ for (i = 1; i < ret; i++) {
+ t = &tokens[i];
+
+ if (t->type != JSMN_STRING) {
+ continue;
+ }
+
+ if (t->start == -1 || t->end == -1 || (t->start == 0 && t->end == 0)){
+ break;
+ }
+
+ /* Key */
+ key = json_data + t->start;
+ key_len = (t->end - t->start);
+
+ /* Value */
+ i++;
+ t = &tokens[i];
+ val = json_data + t->start;
+ val_len = (t->end - t->start);
+
+ if (key_cmp(key, key_len, "access_token") == 0) {
+ ctx->access_token = flb_sds_create_len(val, val_len);
+ }
+ else if (key_cmp(key, key_len, "token_type") == 0) {
+ ctx->token_type = flb_sds_create_len(val, val_len);
+ }
+ else if (key_cmp(key, key_len, "expires_in") == 0) {
+ ctx->expires_in = atol(val);
+
+ /*
+ * Our internal expiration time must be lower that the one set
+ * by the remote end-point, so we can use valid cached values
+ * if a token renewal is in place. So we decrease the expire
+ * interval -10%.
+ */
+ ctx->expires_in -= (ctx->expires_in * 0.10);
+ }
+ }
+
+ flb_free(tokens);
+ if (!ctx->access_token || !ctx->token_type || ctx->expires_in < 60) {
+ flb_sds_destroy(ctx->access_token);
+ flb_sds_destroy(ctx->token_type);
+ ctx->expires_in = 0;
+ return -1;
+ }
+
+ return 0;
+}
+
+struct flb_oauth2 *flb_oauth2_create(struct flb_config *config,
+ const char *auth_url, int expire_sec)
+{
+ int ret;
+ char *prot = NULL;
+ char *host = NULL;
+ char *port = NULL;
+ char *uri = NULL;
+ struct flb_oauth2 *ctx;
+
+ /* allocate context */
+ ctx = flb_calloc(1, sizeof(struct flb_oauth2));
+ if (!ctx) {
+ flb_errno();
+ return NULL;
+ }
+
+ /* register token url */
+ ctx->auth_url = flb_sds_create(auth_url);
+ if (!ctx->auth_url) {
+ flb_errno();
+ flb_free(ctx);
+ return NULL;
+ }
+
+ /* default payload size to 1kb */
+ ctx->payload = flb_sds_create_size(1024);
+ if (!ctx->payload) {
+ flb_errno();
+ flb_oauth2_destroy(ctx);
+ return NULL;
+ }
+
+ ctx->issued = time(NULL);
+ ctx->expires = ctx->issued + expire_sec;
+
+ /* Parse and split URL */
+ ret = flb_utils_url_split(auth_url, &prot, &host, &port, &uri);
+ if (ret == -1) {
+ flb_error("[oauth2] invalid URL: %s", auth_url);
+ goto error;
+ }
+
+ if (!prot || strcmp(prot, "https") != 0) {
+ flb_error("[oauth2] invalid endpoint protocol: %s", auth_url);
+ goto error;
+ }
+
+ if (!host) {
+ flb_error("[oauth2] invalid URL host: %s", auth_url);
+ goto error;
+ }
+
+ /* Populate context */
+ ctx->host = flb_sds_create(host);
+ if (!ctx->host) {
+ flb_errno();
+ goto error;
+ }
+ if (port) {
+ ctx->port = flb_sds_create(port);
+ }
+ else {
+ ctx->port = flb_sds_create(FLB_OAUTH2_PORT);
+ }
+ if (!ctx->port) {
+ flb_errno();
+ goto error;
+ }
+ ctx->uri = flb_sds_create(uri);
+ if (!ctx->uri) {
+ flb_errno();
+ goto error;
+ }
+
+ /* Create TLS context */
+ ctx->tls = flb_tls_create(FLB_TLS_CLIENT_MODE,
+ FLB_TRUE, /* verify */
+ -1, /* debug */
+ NULL, /* vhost */
+ NULL, /* ca_path */
+ NULL, /* ca_file */
+ NULL, /* crt_file */
+ NULL, /* key_file */
+ NULL); /* key_passwd */
+ if (!ctx->tls) {
+ flb_error("[oauth2] error initializing TLS context");
+ goto error;
+ }
+
+ /* Create Upstream context */
+ ctx->u = flb_upstream_create_url(config, auth_url,
+ FLB_IO_TLS, ctx->tls);
+ if (!ctx->u) {
+ flb_error("[oauth2] error creating upstream context");
+ goto error;
+ }
+
+ /* Remove Upstream Async flag */
+ flb_stream_disable_async_mode(&ctx->u->base);
+
+ free_temporary_buffers();
+ return ctx;
+
+ error:
+ free_temporary_buffers();
+ flb_oauth2_destroy(ctx);
+
+ return NULL;
+}
+
+/* Clear the current payload and token */
+void flb_oauth2_payload_clear(struct flb_oauth2 *ctx)
+{
+ flb_sds_len_set(ctx->payload, 0);
+ ctx->payload[0] = '\0';
+ ctx->expires_in = 0;
+ if (ctx->access_token){
+ flb_sds_destroy(ctx->access_token);
+ ctx->access_token = NULL;
+ }
+ if (ctx->token_type){
+ flb_sds_destroy(ctx->token_type);
+ ctx->token_type = NULL;
+ }
+}
+
+/* Append a key/value to the request body */
+int flb_oauth2_payload_append(struct flb_oauth2 *ctx,
+ const char *key_str, int key_len,
+ const char *val_str, int val_len)
+{
+ int size;
+ flb_sds_t tmp;
+
+ if (key_len == -1) {
+ key_len = strlen(key_str);
+ }
+ if (val_len == -1) {
+ val_len = strlen(val_str);
+ }
+
+ /*
+ * Make sure we have enough space in the sds buffer, otherwise
+ * add more capacity (so further flb_sds_cat calls do not
+ * realloc().
+ */
+ size = key_len + val_len + 2;
+ if (flb_sds_avail(ctx->payload) < size) {
+ tmp = flb_sds_increase(ctx->payload, size);
+ if (!tmp) {
+ flb_errno();
+ return -1;
+ }
+
+ if (tmp != ctx->payload) {
+ ctx->payload = tmp;
+ }
+ }
+
+ if (flb_sds_len(ctx->payload) > 0) {
+ flb_sds_cat(ctx->payload, "&", 1);
+ }
+
+ /* Append key and value */
+ flb_sds_cat(ctx->payload, key_str, key_len);
+ flb_sds_cat(ctx->payload, "=", 1);
+ flb_sds_cat(ctx->payload, val_str, val_len);
+
+ return 0;
+}
+
+void flb_oauth2_destroy(struct flb_oauth2 *ctx)
+{
+ flb_sds_destroy(ctx->auth_url);
+ flb_sds_destroy(ctx->payload);
+
+ flb_sds_destroy(ctx->host);
+ flb_sds_destroy(ctx->port);
+ flb_sds_destroy(ctx->uri);
+
+ flb_sds_destroy(ctx->access_token);
+ flb_sds_destroy(ctx->token_type);
+
+ flb_upstream_destroy(ctx->u);
+ flb_tls_destroy(ctx->tls);
+
+ flb_free(ctx);
+}
+
+char *flb_oauth2_token_get(struct flb_oauth2 *ctx)
+{
+ int ret;
+ size_t b_sent;
+ time_t now;
+ struct flb_connection *u_conn;
+ struct flb_http_client *c;
+
+ now = time(NULL);
+ if (ctx->access_token) {
+ /* validate unexpired token */
+ if (ctx->expires > now && flb_sds_len(ctx->access_token) > 0) {
+ return ctx->access_token;
+ }
+ }
+
+ /* Get Token and store it in the context */
+ u_conn = flb_upstream_conn_get(ctx->u);
+ if (!u_conn) {
+ flb_stream_enable_flags(&ctx->u->base, FLB_IO_IPV6);
+ u_conn = flb_upstream_conn_get(ctx->u);
+ if (!u_conn) {
+ flb_error("[oauth2] could not get an upstream connection to %s:%i",
+ ctx->u->tcp_host, ctx->u->tcp_port);
+ flb_stream_disable_flags(&ctx->u->base, FLB_IO_IPV6);
+ return NULL;
+ }
+ }
+
+ /* Create HTTP client context */
+ c = flb_http_client(u_conn, FLB_HTTP_POST, ctx->uri,
+ ctx->payload, flb_sds_len(ctx->payload),
+ ctx->host, atoi(ctx->port),
+ NULL, 0);
+ if (!c) {
+ flb_error("[oauth2] error creating HTTP client context");
+ flb_upstream_conn_release(u_conn);
+ return NULL;
+ }
+
+ /* Append HTTP Header */
+ flb_http_add_header(c,
+ FLB_HTTP_HEADER_CONTENT_TYPE,
+ sizeof(FLB_HTTP_HEADER_CONTENT_TYPE) -1,
+ FLB_OAUTH2_HTTP_ENCODING,
+ sizeof(FLB_OAUTH2_HTTP_ENCODING) - 1);
+
+ /* Issue request */
+ ret = flb_http_do(c, &b_sent);
+ if (ret != 0) {
+ flb_warn("[oauth2] cannot issue request, http_do=%i", ret);
+ }
+ else {
+ flb_info("[oauth2] HTTP Status=%i", c->resp.status);
+ if (c->resp.payload_size > 0) {
+ if (c->resp.status == 200) {
+ flb_debug("[oauth2] payload:\n%s", c->resp.payload);
+ }
+ else {
+ flb_info("[oauth2] payload:\n%s", c->resp.payload);
+ }
+ }
+ }
+
+ /* Extract token */
+ if (c->resp.payload_size > 0 && c->resp.status == 200) {
+ ret = flb_oauth2_parse_json_response(c->resp.payload,
+ c->resp.payload_size, ctx);
+ if (ret == 0) {
+ flb_info("[oauth2] access token from '%s:%s' retrieved",
+ ctx->host, ctx->port);
+ flb_http_client_destroy(c);
+ flb_upstream_conn_release(u_conn);
+ ctx->issued = time(NULL);
+ ctx->expires = ctx->issued + ctx->expires_in;
+ return ctx->access_token;
+ }
+ }
+
+ flb_http_client_destroy(c);
+ flb_upstream_conn_release(u_conn);
+
+ return NULL;
+}
+
+int flb_oauth2_token_len(struct flb_oauth2 *ctx)
+{
+ if (!ctx->access_token) {
+ return -1;
+ }
+
+ return flb_sds_len(ctx->access_token);
+}
+
+int flb_oauth2_token_expired(struct flb_oauth2 *ctx)
+{
+ time_t now;
+
+ if (!ctx->access_token) {
+ return FLB_TRUE;
+ }
+
+ now = time(NULL);
+ if (ctx->expires <= now) {
+ return FLB_TRUE;
+ }
+
+ return FLB_FALSE;
+}
diff --git a/fluent-bit/src/flb_output.c b/fluent-bit/src/flb_output.c
new file mode 100644
index 000000000..b1548f60d
--- /dev/null
+++ b/fluent-bit/src/flb_output.c
@@ -0,0 +1,1445 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_env.h>
+#include <fluent-bit/flb_coro.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_io.h>
+#include <fluent-bit/flb_uri.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_macros.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_plugin.h>
+#include <fluent-bit/flb_plugin_proxy.h>
+#include <fluent-bit/flb_http_client_debug.h>
+#include <fluent-bit/flb_output_thread.h>
+#include <fluent-bit/flb_mp.h>
+#include <fluent-bit/flb_pack.h>
+
+FLB_TLS_DEFINE(struct flb_out_flush_params, out_flush_params);
+
+void flb_output_prepare()
+{
+ FLB_TLS_INIT(out_flush_params);
+}
+
+/* Validate the the output address protocol */
+static int check_protocol(const char *prot, const char *output)
+{
+ int len;
+ char *p;
+
+ p = strstr(output, "://");
+ if (p && p != output) {
+ len = p - output;
+ }
+ else {
+ len = strlen(output);
+ }
+
+ if (strlen(prot) != len) {
+ return 0;
+ }
+
+ /* Output plugin match */
+ if (strncasecmp(prot, output, len) == 0) {
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* Invoke pre-run call for the output plugin */
+void flb_output_pre_run(struct flb_config *config)
+{
+ struct mk_list *head;
+ struct flb_output_instance *ins;
+ struct flb_output_plugin *p;
+
+ mk_list_foreach(head, &config->outputs) {
+ ins = mk_list_entry(head, struct flb_output_instance, _head);
+ p = ins->p;
+ if (p->cb_pre_run) {
+ p->cb_pre_run(ins->context, config);
+ }
+ }
+}
+
+static void flb_output_free_properties(struct flb_output_instance *ins)
+{
+
+ flb_kv_release(&ins->properties);
+ flb_kv_release(&ins->net_properties);
+
+#ifdef FLB_HAVE_TLS
+ if (ins->tls_vhost) {
+ flb_sds_destroy(ins->tls_vhost);
+ }
+ if (ins->tls_ca_path) {
+ flb_sds_destroy(ins->tls_ca_path);
+ }
+ if (ins->tls_ca_file) {
+ flb_sds_destroy(ins->tls_ca_file);
+ }
+ if (ins->tls_crt_file) {
+ flb_sds_destroy(ins->tls_crt_file);
+ }
+ if (ins->tls_key_file) {
+ flb_sds_destroy(ins->tls_key_file);
+ }
+ if (ins->tls_key_passwd) {
+ flb_sds_destroy(ins->tls_key_passwd);
+ }
+#endif
+}
+
+void flb_output_flush_prepare_destroy(struct flb_output_flush *out_flush)
+{
+ struct flb_output_instance *ins = out_flush->o_ins;
+ struct flb_out_thread_instance *th_ins;
+
+ /* Move output coroutine context from active list to the destroy one */
+ if (flb_output_is_threaded(ins) == FLB_TRUE) {
+ th_ins = flb_output_thread_instance_get();
+ pthread_mutex_lock(&th_ins->flush_mutex);
+ mk_list_del(&out_flush->_head);
+ mk_list_add(&out_flush->_head, &th_ins->flush_list_destroy);
+ pthread_mutex_unlock(&th_ins->flush_mutex);
+ }
+ else {
+ mk_list_del(&out_flush->_head);
+ mk_list_add(&out_flush->_head, &ins->flush_list_destroy);
+ }
+}
+
+int flb_output_flush_id_get(struct flb_output_instance *ins)
+{
+ int id;
+ int max = (2 << 13) - 1; /* max for 14 bits */
+ struct flb_out_thread_instance *th_ins;
+
+ if (flb_output_is_threaded(ins) == FLB_TRUE) {
+ th_ins = flb_output_thread_instance_get();
+ id = th_ins->flush_id;
+ th_ins->flush_id++;
+
+ /* reset once it reach the maximum allowed */
+ if (th_ins->flush_id > max) {
+ th_ins->flush_id = 0;
+ }
+ }
+ else {
+ id = ins->flush_id;
+ ins->flush_id++;
+
+ /* reset once it reach the maximum allowed */
+ if (ins->flush_id > max) {
+ ins->flush_id = 0;
+ }
+ }
+
+ return id;
+}
+
+void flb_output_coro_add(struct flb_output_instance *ins, struct flb_coro *coro)
+{
+ struct flb_output_flush *out_flush;
+
+ out_flush = (struct flb_output_flush *) FLB_CORO_DATA(coro);
+ mk_list_add(&out_flush->_head, &ins->flush_list);
+}
+
+/*
+ * Queue a task to be flushed at a later time
+ * Deletes retry context if enqueue fails
+ */
+static int flb_output_task_queue_enqueue(struct flb_task_queue *queue,
+ struct flb_task_retry *retry,
+ struct flb_task *task,
+ struct flb_output_instance *out_ins,
+ struct flb_config *config)
+{
+ struct flb_task_enqueued *queued_task;
+
+ queued_task = flb_malloc(sizeof(struct flb_task_enqueued));
+ if (!queued_task) {
+ flb_errno();
+ if (retry) {
+ flb_task_retry_destroy(retry);
+ }
+ return -1;
+ }
+ queued_task->retry = retry;
+ queued_task->out_instance = out_ins;
+ queued_task->task = task;
+ queued_task->config = config;
+
+ mk_list_add(&queued_task->_head, &queue->pending);
+ return 0;
+}
+
+/*
+ * Pop task from pending queue and flush it
+ * Will delete retry context if flush fails
+ */
+static int flb_output_task_queue_flush_one(struct flb_task_queue *queue)
+{
+ struct flb_task_enqueued *queued_task;
+ int ret;
+ int is_empty;
+
+ is_empty = mk_list_is_empty(&queue->pending) == 0;
+ if (is_empty) {
+ flb_error("Attempting to flush task from an empty in_progress queue");
+ return -1;
+ }
+
+ queued_task = mk_list_entry_first(&queue->pending, struct flb_task_enqueued, _head);
+ mk_list_del(&queued_task->_head);
+ mk_list_add(&queued_task->_head, &queue->in_progress);
+
+ /*
+ * Remove temporary user now that task is out of singleplex queue.
+ * Flush will add back the user representing queued_task->out_instance if it succeeds.
+ */
+ flb_task_users_dec(queued_task->task, FLB_FALSE);
+ ret = flb_output_task_flush(queued_task->task,
+ queued_task->out_instance,
+ queued_task->config);
+
+ /* Destroy retry context if needed */
+ if (ret == -1) {
+ if (queued_task->retry) {
+ flb_task_retry_destroy(queued_task->retry);
+ }
+ /* Flush the next task */
+ flb_output_task_singleplex_flush_next(queue);
+ return -1;
+ }
+
+ return ret;
+}
+
+/*
+ * Will either run or queue running a single task
+ * Deletes retry context if enqueue fails
+ */
+int flb_output_task_singleplex_enqueue(struct flb_task_queue *queue,
+ struct flb_task_retry *retry,
+ struct flb_task *task,
+ struct flb_output_instance *out_ins,
+ struct flb_config *config)
+{
+ int ret;
+ int is_empty;
+
+ /*
+ * Add temporary user to preserve task while in singleplex queue.
+ * Temporary user will be removed when task is removed from queue.
+ *
+ * Note: if we fail to increment now, then the task may be prematurely
+ * deleted if the task's users go to 0 while we are waiting in the
+ * queue.
+ */
+ flb_task_users_inc(task);
+
+ /* Enqueue task */
+ ret = flb_output_task_queue_enqueue(queue, retry, task, out_ins, config);
+ if (ret == -1) {
+ return -1;
+ }
+
+ /* Launch task if nothing is running */
+ is_empty = mk_list_is_empty(&out_ins->singleplex_queue->in_progress) == 0;
+ if (is_empty) {
+ return flb_output_task_queue_flush_one(out_ins->singleplex_queue);
+ }
+
+ return 0;
+}
+
+/*
+ * Clear in progress task and flush a single queued task if exists
+ * Deletes retry context on next flush if flush fails
+ */
+int flb_output_task_singleplex_flush_next(struct flb_task_queue *queue)
+{
+ int is_empty;
+ struct flb_task_enqueued *ended_task;
+
+ /* Remove in progress task */
+ is_empty = mk_list_is_empty(&queue->in_progress) == 0;
+ if (!is_empty) {
+ ended_task = mk_list_entry_first(&queue->in_progress,
+ struct flb_task_enqueued, _head);
+ mk_list_del(&ended_task->_head);
+ flb_free(ended_task);
+ }
+
+ /* Flush if there is a pending task queued */
+ is_empty = mk_list_is_empty(&queue->pending) == 0;
+ if (!is_empty) {
+ return flb_output_task_queue_flush_one(queue);
+ }
+ return 0;
+}
+
+/*
+ * Flush a task through the output plugin, either using a worker thread + coroutine
+ * or a simple co-routine in the current thread.
+ */
+int flb_output_task_flush(struct flb_task *task,
+ struct flb_output_instance *out_ins,
+ struct flb_config *config)
+{
+ int ret;
+ struct flb_output_flush *out_flush;
+
+ if (flb_output_is_threaded(out_ins) == FLB_TRUE) {
+ flb_task_users_inc(task);
+
+ /* Dispatch the task to the thread pool */
+ ret = flb_output_thread_pool_flush(task, out_ins, config);
+ if (ret == -1) {
+ flb_task_users_dec(task, FLB_FALSE);
+
+ /* If we are in synchronous mode, flush one waiting task */
+ if (out_ins->flags & FLB_OUTPUT_SYNCHRONOUS) {
+ flb_output_task_singleplex_flush_next(out_ins->singleplex_queue);
+ }
+ }
+ }
+ else {
+ /* Queue co-routine handling */
+ out_flush = flb_output_flush_create(task,
+ task->i_ins,
+ out_ins,
+ config);
+ if (!out_flush) {
+ return -1;
+ }
+
+ flb_task_users_inc(task);
+ ret = flb_pipe_w(config->ch_self_events[1], &out_flush,
+ sizeof(struct flb_output_flush*));
+ if (ret == -1) {
+ flb_errno();
+ flb_output_flush_destroy(out_flush);
+ flb_task_users_dec(task, FLB_FALSE);
+
+ /* If we are in synchronous mode, flush one waiting task */
+ if (out_ins->flags & FLB_OUTPUT_SYNCHRONOUS) {
+ flb_output_task_singleplex_flush_next(out_ins->singleplex_queue);
+ }
+
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int flb_output_instance_destroy(struct flb_output_instance *ins)
+{
+ if (ins->alias) {
+ flb_sds_destroy(ins->alias);
+ }
+
+ /* Remove URI context */
+ if (ins->host.uri) {
+ flb_uri_destroy(ins->host.uri);
+ }
+
+ flb_sds_destroy(ins->host.name);
+ flb_sds_destroy(ins->host.address);
+ flb_sds_destroy(ins->host.listen);
+ flb_sds_destroy(ins->match);
+
+#ifdef FLB_HAVE_REGEX
+ if (ins->match_regex) {
+ flb_regex_destroy(ins->match_regex);
+ }
+#endif
+
+#ifdef FLB_HAVE_TLS
+ if (ins->use_tls == FLB_TRUE) {
+ if (ins->tls) {
+ flb_tls_destroy(ins->tls);
+ }
+ }
+
+ if (ins->tls_config_map) {
+ flb_config_map_destroy(ins->tls_config_map);
+ }
+#endif
+
+ /* Remove metrics */
+#ifdef FLB_HAVE_METRICS
+ if (ins->cmt) {
+ cmt_destroy(ins->cmt);
+ }
+
+ if (ins->metrics) {
+ flb_metrics_destroy(ins->metrics);
+ }
+#endif
+
+ /* destroy callback context */
+ if (ins->callback) {
+ flb_callback_destroy(ins->callback);
+ }
+
+ /* destroy config map */
+ if (ins->config_map) {
+ flb_config_map_destroy(ins->config_map);
+ }
+
+ if (ins->net_config_map) {
+ flb_config_map_destroy(ins->net_config_map);
+ }
+
+ if (ins->ch_events[0] > 0) {
+ mk_event_closesocket(ins->ch_events[0]);
+ }
+
+ if (ins->ch_events[1] > 0) {
+ mk_event_closesocket(ins->ch_events[1]);
+ }
+
+ /* release properties */
+ flb_output_free_properties(ins);
+
+ /* free singleplex queue */
+ if (ins->flags & FLB_OUTPUT_SYNCHRONOUS) {
+ flb_task_queue_destroy(ins->singleplex_queue);
+ }
+
+ mk_list_del(&ins->_head);
+
+ /* processor */
+ if (ins->processor) {
+ flb_processor_destroy(ins->processor);
+ }
+
+ flb_free(ins);
+
+ return 0;
+}
+
+/* Invoke exit call for the output plugin */
+void flb_output_exit(struct flb_config *config)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_output_instance *ins;
+ struct flb_output_plugin *p;
+ void *params;
+
+ mk_list_foreach_safe(head, tmp, &config->outputs) {
+ ins = mk_list_entry(head, struct flb_output_instance, _head);
+ p = ins->p;
+
+ /* Stop any worker thread */
+ if (flb_output_is_threaded(ins) == FLB_TRUE) {
+ flb_output_thread_pool_destroy(ins);
+ }
+
+ /* Check a exit callback */
+ if (p->cb_exit) {
+ p->cb_exit(ins->context, config);
+ }
+ flb_output_instance_destroy(ins);
+ }
+
+ params = FLB_TLS_GET(out_flush_params);
+ if (params) {
+ flb_free(params);
+ }
+}
+
+static inline int instance_id(struct flb_config *config)
+{
+ struct flb_output_instance *ins;
+
+ if (mk_list_size(&config->outputs) == 0) {
+ return 0;
+ }
+
+ ins = mk_list_entry_last(&config->outputs, struct flb_output_instance,
+ _head);
+ return (ins->id + 1);
+}
+
+struct flb_output_instance *flb_output_get_instance(struct flb_config *config,
+ int out_id)
+{
+ struct mk_list *head;
+ struct flb_output_instance *ins;
+
+ mk_list_foreach(head, &config->outputs) {
+ ins = mk_list_entry(head, struct flb_output_instance, _head);
+ if (ins->id == out_id) {
+ break;
+ }
+ ins = NULL;
+ }
+
+ if (!ins) {
+ return NULL;
+ }
+
+ return ins;
+}
+
+/*
+ * Invoked everytime a flush callback has finished (returned). This function
+ * is called from the event loop.
+ */
+int flb_output_flush_finished(struct flb_config *config, int out_id)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct mk_list *list;
+ struct flb_output_instance *ins;
+ struct flb_output_flush *out_flush;
+ struct flb_out_thread_instance *th_ins;
+
+ ins = flb_output_get_instance(config, out_id);
+ if (!ins) {
+ return -1;
+ }
+
+ if (flb_output_is_threaded(ins) == FLB_TRUE) {
+ th_ins = flb_output_thread_instance_get();
+ list = &th_ins->flush_list_destroy;
+ }
+ else {
+ list = &ins->flush_list_destroy;
+ }
+
+ /* Look for output coroutines that needs to be destroyed */
+ mk_list_foreach_safe(head, tmp, list) {
+ out_flush = mk_list_entry(head, struct flb_output_flush, _head);
+ flb_output_flush_destroy(out_flush);
+ }
+
+ return 0;
+}
+
+
+/*
+ * It validate an output type given the string, it return the
+ * proper type and if valid, populate the global config.
+ */
+struct flb_output_instance *flb_output_new(struct flb_config *config,
+ const char *output, void *data,
+ int public_only)
+{
+ int ret = -1;
+ int flags = 0;
+ struct mk_list *head;
+ struct flb_output_plugin *plugin;
+ struct flb_output_instance *instance = NULL;
+
+ if (!output) {
+ return NULL;
+ }
+
+ mk_list_foreach(head, &config->out_plugins) {
+ plugin = mk_list_entry(head, struct flb_output_plugin, _head);
+ if (!check_protocol(plugin->name, output)) {
+ plugin = NULL;
+ continue;
+ }
+
+ if (public_only && plugin->flags & FLB_OUTPUT_PRIVATE) {
+ return NULL;
+ }
+ break;
+ }
+
+ if (!plugin) {
+ return NULL;
+ }
+
+ /* Create and load instance */
+ instance = flb_calloc(1, sizeof(struct flb_output_instance));
+ if (!instance) {
+ flb_errno();
+ return NULL;
+ }
+
+ /* Initialize event type, if not set, default to FLB_OUTPUT_LOGS */
+ if (plugin->event_type == 0) {
+ instance->event_type = FLB_OUTPUT_LOGS;
+ }
+ else {
+ instance->event_type = plugin->event_type;
+ }
+ instance->config = config;
+ instance->log_level = -1;
+ instance->log_suppress_interval = -1;
+ instance->test_mode = FLB_FALSE;
+ instance->is_threaded = FLB_FALSE;
+ instance->tp_workers = plugin->workers;
+
+ /* Retrieve an instance id for the output instance */
+ instance->id = instance_id(config);
+
+ /* format name (with instance id) */
+ snprintf(instance->name, sizeof(instance->name) - 1,
+ "%s.%i", plugin->name, instance->id);
+ instance->p = plugin;
+ instance->callback = flb_callback_create(instance->name);
+ if (!instance->callback) {
+ if (instance->flags & FLB_OUTPUT_SYNCHRONOUS) {
+ flb_task_queue_destroy(instance->singleplex_queue);
+ }
+ flb_free(instance);
+ return NULL;
+ }
+
+ if (plugin->type == FLB_OUTPUT_PLUGIN_CORE) {
+ instance->context = NULL;
+ }
+ else {
+ struct flb_plugin_proxy_context *ctx;
+
+ ctx = flb_calloc(1, sizeof(struct flb_plugin_proxy_context));
+ if (!ctx) {
+ flb_errno();
+ if (instance->flags & FLB_OUTPUT_SYNCHRONOUS) {
+ flb_task_queue_destroy(instance->singleplex_queue);
+ }
+ flb_free(instance);
+ return NULL;
+ }
+
+ ctx->proxy = plugin->proxy;
+
+ instance->context = ctx;
+ }
+
+ instance->alias = NULL;
+ instance->flags = instance->p->flags;
+ instance->data = data;
+ instance->match = NULL;
+#ifdef FLB_HAVE_REGEX
+ instance->match_regex = NULL;
+#endif
+ instance->retry_limit = 1;
+ instance->host.name = NULL;
+ instance->host.address = NULL;
+ instance->net_config_map = NULL;
+
+ /* Storage */
+ instance->total_limit_size = -1;
+
+ /* Parent plugin flags */
+ flags = instance->flags;
+ if (flags & FLB_IO_TCP) {
+ instance->use_tls = FLB_FALSE;
+ }
+ else if (flags & FLB_IO_TLS) {
+ instance->use_tls = FLB_TRUE;
+ }
+ else if (flags & FLB_IO_OPT_TLS) {
+ /* TLS must be enabled manually in the config */
+ instance->use_tls = FLB_FALSE;
+ instance->flags |= FLB_IO_TLS;
+ }
+
+#ifdef FLB_HAVE_TLS
+ instance->tls = NULL;
+ instance->tls_debug = -1;
+ instance->tls_verify = FLB_TRUE;
+ instance->tls_vhost = NULL;
+ instance->tls_ca_path = NULL;
+ instance->tls_ca_file = NULL;
+ instance->tls_crt_file = NULL;
+ instance->tls_key_file = NULL;
+ instance->tls_key_passwd = NULL;
+#endif
+
+ if (plugin->flags & FLB_OUTPUT_NET) {
+ ret = flb_net_host_set(plugin->name, &instance->host, output);
+ if (ret != 0) {
+ if (instance->flags & FLB_OUTPUT_SYNCHRONOUS) {
+ flb_task_queue_destroy(instance->singleplex_queue);
+ }
+ flb_free(instance);
+ return NULL;
+ }
+ }
+
+ /* Create singleplex queue if SYNCHRONOUS mode is used */
+ instance->singleplex_queue = NULL;
+ if (instance->flags & FLB_OUTPUT_SYNCHRONOUS) {
+ instance->singleplex_queue = flb_task_queue_create();
+ if (!instance->singleplex_queue) {
+ flb_free(instance);
+ flb_errno();
+ return NULL;
+ }
+ }
+
+ flb_kv_init(&instance->properties);
+ flb_kv_init(&instance->net_properties);
+ mk_list_init(&instance->upstreams);
+ mk_list_init(&instance->flush_list);
+ mk_list_init(&instance->flush_list_destroy);
+
+ mk_list_add(&instance->_head, &config->outputs);
+
+ /* processor instance */
+ instance->processor = flb_processor_create(config, instance->name, instance, FLB_PLUGIN_OUTPUT);
+
+ /* Tests */
+ instance->test_formatter.callback = plugin->test_formatter.callback;
+
+
+ return instance;
+}
+
+static inline int prop_key_check(const char *key, const char *kv, int k_len)
+{
+ int len;
+
+ len = strlen(key);
+ if (strncasecmp(key, kv, k_len) == 0 && len == k_len) {
+ return 0;
+ }
+
+ return -1;
+}
+
+/* Override a configuration property for the given input_instance plugin */
+int flb_output_set_property(struct flb_output_instance *ins,
+ const char *k, const char *v)
+{
+ int len;
+ int ret;
+ ssize_t limit;
+ flb_sds_t tmp;
+ struct flb_kv *kv;
+ struct flb_config *config = ins->config;
+
+ len = strlen(k);
+ tmp = flb_env_var_translate(config->env, v);
+ if (tmp) {
+ if (strlen(tmp) == 0) {
+ flb_sds_destroy(tmp);
+ tmp = NULL;
+ }
+ }
+
+ /* Check if the key is a known/shared property */
+ if (prop_key_check("match", k, len) == 0) {
+ flb_utils_set_plugin_string_property("match", &ins->match, tmp);
+ }
+#ifdef FLB_HAVE_REGEX
+ else if (prop_key_check("match_regex", k, len) == 0 && tmp) {
+ ins->match_regex = flb_regex_create(tmp);
+ flb_sds_destroy(tmp);
+ }
+#endif
+ else if (prop_key_check("alias", k, len) == 0 && tmp) {
+ flb_utils_set_plugin_string_property("alias", &ins->alias, tmp);
+ }
+ else if (prop_key_check("log_level", k, len) == 0 && tmp) {
+ ret = flb_log_get_level_str(tmp);
+ flb_sds_destroy(tmp);
+ if (ret == -1) {
+ return -1;
+ }
+ ins->log_level = ret;
+ }
+ else if (prop_key_check("log_suppress_interval", k, len) == 0 && tmp) {
+ ret = flb_utils_time_to_seconds(tmp);
+ flb_sds_destroy(tmp);
+ if (ret == -1) {
+ return -1;
+ }
+ ins->log_suppress_interval = ret;
+ }
+ else if (prop_key_check("host", k, len) == 0) {
+ flb_utils_set_plugin_string_property("host", &ins->host.name, tmp);
+ }
+ else if (prop_key_check("port", k, len) == 0) {
+ if (tmp) {
+ ins->host.port = atoi(tmp);
+ flb_sds_destroy(tmp);
+ }
+ else {
+ ins->host.port = 0;
+ }
+ }
+ else if (prop_key_check("ipv6", k, len) == 0 && tmp) {
+ ins->host.ipv6 = flb_utils_bool(tmp);
+ flb_sds_destroy(tmp);
+ }
+ else if (prop_key_check("retry_limit", k, len) == 0) {
+ if (tmp) {
+ if (strcasecmp(tmp, "no_limits") == 0 ||
+ strcasecmp(tmp, "false") == 0 ||
+ strcasecmp(tmp, "off") == 0) {
+ /* No limits for retries */
+ ins->retry_limit = FLB_OUT_RETRY_UNLIMITED;
+ }
+ else if (strcasecmp(tmp, "no_retries") == 0) {
+ ins->retry_limit = FLB_OUT_RETRY_NONE;
+ }
+ else {
+ ins->retry_limit = atoi(tmp);
+ if (ins->retry_limit <= 0) {
+ flb_warn("[config] invalid retry_limit. set default.");
+ /* set default when input is invalid number */
+ ins->retry_limit = 1;
+ }
+ }
+ flb_sds_destroy(tmp);
+ }
+ else {
+ ins->retry_limit = 1;
+ }
+ }
+ else if (strncasecmp("net.", k, 4) == 0 && tmp) {
+ kv = flb_kv_item_create(&ins->net_properties, (char *) k, NULL);
+ if (!kv) {
+ if (tmp) {
+ flb_sds_destroy(tmp);
+ }
+ return -1;
+ }
+ kv->val = tmp;
+ }
+#ifdef FLB_HAVE_HTTP_CLIENT_DEBUG
+ else if (strncasecmp("_debug.http.", k, 12) == 0 && tmp) {
+ ret = flb_http_client_debug_property_is_valid((char *) k, tmp);
+ if (ret == FLB_TRUE) {
+ kv = flb_kv_item_create(&ins->properties, (char *) k, NULL);
+ if (!kv) {
+ if (tmp) {
+ flb_sds_destroy(tmp);
+ }
+ return -1;
+ }
+ kv->val = tmp;
+ }
+ else {
+ flb_error("[config] invalid property '%s' on instance '%s'",
+ k, flb_output_name(ins));
+ flb_sds_destroy(tmp);
+ }
+ }
+#endif
+#ifdef FLB_HAVE_TLS
+ else if (prop_key_check("tls", k, len) == 0 && tmp) {
+ ins->use_tls = flb_utils_bool(tmp);
+ if (ins->use_tls == FLB_TRUE && ((ins->flags & FLB_IO_TLS) == 0)) {
+ flb_error("[config] %s does not support TLS", ins->name);
+ flb_sds_destroy(tmp);
+ return -1;
+ }
+ flb_sds_destroy(tmp);
+ }
+ else if (prop_key_check("tls.verify", k, len) == 0 && tmp) {
+ ins->tls_verify = flb_utils_bool(tmp);
+ flb_sds_destroy(tmp);
+ }
+ else if (prop_key_check("tls.debug", k, len) == 0 && tmp) {
+ ins->tls_debug = atoi(tmp);
+ flb_sds_destroy(tmp);
+ }
+ else if (prop_key_check("tls.vhost", k, len) == 0) {
+ flb_utils_set_plugin_string_property("tls.vhost", &ins->tls_vhost, tmp);
+ }
+ else if (prop_key_check("tls.ca_path", k, len) == 0) {
+ flb_utils_set_plugin_string_property("tls.ca_path", &ins->tls_ca_path, tmp);
+ }
+ else if (prop_key_check("tls.ca_file", k, len) == 0) {
+ flb_utils_set_plugin_string_property("tls.ca_file", &ins->tls_ca_file, tmp);
+ }
+ else if (prop_key_check("tls.crt_file", k, len) == 0) {
+ flb_utils_set_plugin_string_property("tls.crt_file", &ins->tls_crt_file, tmp);
+ }
+ else if (prop_key_check("tls.key_file", k, len) == 0) {
+ flb_utils_set_plugin_string_property("tls.key_file", &ins->tls_key_file, tmp);
+ }
+ else if (prop_key_check("tls.key_passwd", k, len) == 0) {
+ flb_utils_set_plugin_string_property("tls.key_passwd", &ins->tls_key_passwd, tmp);
+ }
+#endif
+ else if (prop_key_check("storage.total_limit_size", k, len) == 0 && tmp) {
+ if (strcasecmp(tmp, "off") == 0 ||
+ flb_utils_bool(tmp) == FLB_FALSE) {
+ /* no limit for filesystem storage */
+ limit = -1;
+ flb_info("[config] unlimited filesystem buffer for %s plugin",
+ ins->name);
+ }
+ else {
+ limit = flb_utils_size_to_bytes(tmp);
+ if (limit == -1) {
+ flb_sds_destroy(tmp);
+ return -1;
+ }
+
+ if (limit == 0) {
+ limit = -1;
+ }
+ }
+
+ flb_sds_destroy(tmp);
+ ins->total_limit_size = (size_t) limit;
+ }
+ else if (prop_key_check("workers", k, len) == 0 && tmp) {
+ /* Set the number of workers */
+ ins->tp_workers = atoi(tmp);
+ flb_sds_destroy(tmp);
+ }
+ else {
+ /*
+ * Create the property, we don't pass the value since we will
+ * map it directly to avoid an extra memory allocation.
+ */
+ kv = flb_kv_item_create(&ins->properties, (char *) k, NULL);
+ if (!kv) {
+ if (tmp) {
+ flb_sds_destroy(tmp);
+ }
+ return -1;
+ }
+ kv->val = tmp;
+ }
+
+ return 0;
+}
+
+/* Configure a default hostname and TCP port if they are not set */
+void flb_output_net_default(const char *host, const int port,
+ struct flb_output_instance *ins)
+{
+ /* Set default network configuration */
+ if (!ins->host.name) {
+ ins->host.name = flb_sds_create(host);
+ }
+ if (ins->host.port == 0) {
+ ins->host.port = port;
+ }
+}
+
+/* Add thread pool for output plugin if configured with workers */
+int flb_output_enable_multi_threading(struct flb_output_instance *ins, struct flb_config *config)
+{
+ /* Multi-threading enabled ? (through 'workers' property) */
+ if (ins->tp_workers > 0) {
+ if(flb_output_thread_pool_create(config, ins) != 0) {
+ flb_output_instance_destroy(ins);
+ return -1;
+ }
+ flb_output_thread_pool_start(ins);
+ }
+
+ return 0;
+}
+
+/* Return an instance name or alias */
+const char *flb_output_name(struct flb_output_instance *ins)
+{
+ if (ins->alias) {
+ return ins->alias;
+ }
+
+ return ins->name;
+}
+
+const char *flb_output_get_property(const char *key, struct flb_output_instance *ins)
+{
+ return flb_config_prop_get(key, &ins->properties);
+}
+
+#ifdef FLB_HAVE_METRICS
+void *flb_output_get_cmt_instance(struct flb_output_instance *ins)
+{
+ return (void *)ins->cmt;
+}
+#endif
+
+int flb_output_net_property_check(struct flb_output_instance *ins,
+ struct flb_config *config)
+{
+ int ret = 0;
+
+ /* Get Upstream net_setup configmap */
+ ins->net_config_map = flb_upstream_get_config_map(config);
+ if (!ins->net_config_map) {
+ flb_output_instance_destroy(ins);
+ return -1;
+ }
+
+ /*
+ * Validate 'net.*' properties: if the plugin use the Upstream interface,
+ * it might receive some networking settings.
+ */
+ if (mk_list_size(&ins->net_properties) > 0) {
+ ret = flb_config_map_properties_check(ins->p->name,
+ &ins->net_properties,
+ ins->net_config_map);
+ if (ret == -1) {
+ if (config->program_name) {
+ flb_helper("try the command: %s -o %s -h\n",
+ config->program_name, ins->p->name);
+ }
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int flb_output_plugin_property_check(struct flb_output_instance *ins,
+ struct flb_config *config)
+{
+ int ret = 0;
+ struct mk_list *config_map;
+ struct flb_output_plugin *p = ins->p;
+
+ if (p->config_map) {
+ /*
+ * Create a dynamic version of the configmap that will be used by the specific
+ * instance in question.
+ */
+ config_map = flb_config_map_create(config, p->config_map);
+ if (!config_map) {
+ flb_error("[output] error loading config map for '%s' plugin",
+ p->name);
+ flb_output_instance_destroy(ins);
+ return -1;
+ }
+ ins->config_map = config_map;
+
+ /* Validate incoming properties against config map */
+ ret = flb_config_map_properties_check(ins->p->name,
+ &ins->properties, ins->config_map);
+ if (ret == -1) {
+ if (config->program_name) {
+ flb_helper("try the command: %s -o %s -h\n",
+ config->program_name, ins->p->name);
+ }
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* Trigger the output plugins setup callbacks to prepare them. */
+int flb_output_init_all(struct flb_config *config)
+{
+ int ret;
+#ifdef FLB_HAVE_METRICS
+ char *name;
+#endif
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_output_instance *ins;
+ struct flb_output_plugin *p;
+ uint64_t ts;
+
+ /* Retrieve the plugin reference */
+ mk_list_foreach_safe(head, tmp, &config->outputs) {
+ ins = mk_list_entry(head, struct flb_output_instance, _head);
+ if (ins->log_level == -1) {
+ ins->log_level = config->log->level;
+ }
+ p = ins->p;
+
+ /* Output Events Channel */
+ ret = mk_event_channel_create(config->evl,
+ &ins->ch_events[0],
+ &ins->ch_events[1],
+ ins);
+ if (ret != 0) {
+ flb_error("could not create events channels for '%s'",
+ flb_output_name(ins));
+ flb_output_instance_destroy(ins);
+ return -1;
+ }
+ flb_debug("[%s:%s] created event channels: read=%i write=%i",
+ ins->p->name, flb_output_name(ins),
+ ins->ch_events[0], ins->ch_events[1]);
+
+ /*
+ * Note: mk_event_channel_create() sets a type = MK_EVENT_NOTIFICATION by
+ * default, we need to overwrite this value so we can do a clean check
+ * into the Engine when the event is triggered.
+ */
+ ins->event.type = FLB_ENGINE_EV_OUTPUT;
+
+ /* Metrics */
+#ifdef FLB_HAVE_METRICS
+ /* Get name or alias for the instance */
+ name = (char *) flb_output_name(ins);
+
+ /* get timestamp */
+ ts = cfl_time_now();
+
+ /* CMetrics */
+ ins->cmt = cmt_create();
+ if (!ins->cmt) {
+ flb_error("[output] could not create cmetrics context");
+ return -1;
+ }
+
+ /*
+ * Register generic output plugin metrics
+ */
+
+ /* fluentbit_output_proc_records_total */
+ ins->cmt_proc_records = cmt_counter_create(ins->cmt, "fluentbit",
+ "output", "proc_records_total",
+ "Number of processed output records.",
+ 1, (char *[]) {"name"});
+ cmt_counter_set(ins->cmt_proc_records, ts, 0, 1, (char *[]) {name});
+
+
+ /* fluentbit_output_proc_bytes_total */
+ ins->cmt_proc_bytes = cmt_counter_create(ins->cmt, "fluentbit",
+ "output", "proc_bytes_total",
+ "Number of processed output bytes.",
+ 1, (char *[]) {"name"});
+ cmt_counter_set(ins->cmt_proc_bytes, ts, 0, 1, (char *[]) {name});
+
+
+ /* fluentbit_output_errors_total */
+ ins->cmt_errors = cmt_counter_create(ins->cmt, "fluentbit",
+ "output", "errors_total",
+ "Number of output errors.",
+ 1, (char *[]) {"name"});
+ cmt_counter_set(ins->cmt_errors, ts, 0, 1, (char *[]) {name});
+
+
+ /* fluentbit_output_retries_total */
+ ins->cmt_retries = cmt_counter_create(ins->cmt, "fluentbit",
+ "output", "retries_total",
+ "Number of output retries.",
+ 1, (char *[]) {"name"});
+ cmt_counter_set(ins->cmt_retries, ts, 0, 1, (char *[]) {name});
+
+ /* fluentbit_output_retries_failed_total */
+ ins->cmt_retries_failed = cmt_counter_create(ins->cmt, "fluentbit",
+ "output", "retries_failed_total",
+ "Number of abandoned batches because "
+ "the maximum number of re-tries was "
+ "reached.",
+ 1, (char *[]) {"name"});
+ cmt_counter_set(ins->cmt_retries_failed, ts, 0, 1, (char *[]) {name});
+
+
+ /* fluentbit_output_dropped_records_total */
+ ins->cmt_dropped_records = cmt_counter_create(ins->cmt, "fluentbit",
+ "output", "dropped_records_total",
+ "Number of dropped records.",
+ 1, (char *[]) {"name"});
+ cmt_counter_set(ins->cmt_dropped_records, ts, 0, 1, (char *[]) {name});
+
+ /* fluentbit_output_retried_records_total */
+ ins->cmt_retried_records = cmt_counter_create(ins->cmt, "fluentbit",
+ "output", "retried_records_total",
+ "Number of retried records.",
+ 1, (char *[]) {"name"});
+ cmt_counter_set(ins->cmt_retried_records, ts, 0, 1, (char *[]) {name});
+
+ /* output_upstream_total_connections */
+ ins->cmt_upstream_total_connections = cmt_gauge_create(ins->cmt,
+ "fluentbit",
+ "output",
+ "upstream_total_connections",
+ "Total Connection count.",
+ 1, (char *[]) {"name"});
+ cmt_gauge_set(ins->cmt_upstream_total_connections,
+ ts,
+ 0,
+ 1, (char *[]) {name});
+
+ /* output_upstream_total_connections */
+ ins->cmt_upstream_busy_connections = cmt_gauge_create(ins->cmt,
+ "fluentbit",
+ "output",
+ "upstream_busy_connections",
+ "Busy Connection count.",
+ 1, (char *[]) {"name"});
+ cmt_gauge_set(ins->cmt_upstream_busy_connections,
+ ts,
+ 0,
+ 1, (char *[]) {name});
+
+ /* old API */
+ ins->metrics = flb_metrics_create(name);
+ if (ins->metrics) {
+ flb_metrics_add(FLB_METRIC_OUT_OK_RECORDS,
+ "proc_records", ins->metrics);
+ flb_metrics_add(FLB_METRIC_OUT_OK_BYTES,
+ "proc_bytes", ins->metrics);
+ flb_metrics_add(FLB_METRIC_OUT_ERROR,
+ "errors", ins->metrics);
+ flb_metrics_add(FLB_METRIC_OUT_RETRY,
+ "retries", ins->metrics);
+ flb_metrics_add(FLB_METRIC_OUT_RETRY_FAILED,
+ "retries_failed", ins->metrics);
+ flb_metrics_add(FLB_METRIC_OUT_DROPPED_RECORDS,
+ "dropped_records", ins->metrics);
+ flb_metrics_add(FLB_METRIC_OUT_RETRIED_RECORDS,
+ "retried_records", ins->metrics);
+ }
+#endif
+
+#ifdef FLB_HAVE_PROXY_GO
+ /* Proxy plugins have their own initialization */
+ if (p->type == FLB_OUTPUT_PLUGIN_PROXY) {
+ ret = flb_plugin_proxy_output_init(p->proxy, ins, config);
+ if (ret == -1) {
+ flb_output_instance_destroy(ins);
+ return -1;
+ }
+
+ /* Multi-threading enabled if configured */
+ ret = flb_output_enable_multi_threading(ins, config);
+ if (ret == -1) {
+ flb_error("[output] could not start thread pool for '%s' plugin",
+ p->name);
+ return -1;
+ }
+
+ continue;
+ }
+#endif
+
+#ifdef FLB_HAVE_TLS
+ if (ins->use_tls == FLB_TRUE) {
+ ins->tls = flb_tls_create(FLB_TLS_CLIENT_MODE,
+ ins->tls_verify,
+ ins->tls_debug,
+ ins->tls_vhost,
+ ins->tls_ca_path,
+ ins->tls_ca_file,
+ ins->tls_crt_file,
+ ins->tls_key_file,
+ ins->tls_key_passwd);
+ if (!ins->tls) {
+ flb_error("[output %s] error initializing TLS context",
+ ins->name);
+ flb_output_instance_destroy(ins);
+ return -1;
+ }
+ }
+#endif
+ /*
+ * Before to call the initialization callback, make sure that the received
+ * configuration parameters are valid if the plugin is registering a config map.
+ */
+ if (flb_output_plugin_property_check(ins, config) == -1) {
+ flb_output_instance_destroy(ins);
+ return -1;
+ }
+
+#ifdef FLB_HAVE_TLS
+ struct flb_config_map *m;
+
+ /* TLS config map (just for 'help' formatting purposes) */
+ ins->tls_config_map = flb_tls_get_config_map(config);
+ if (!ins->tls_config_map) {
+ flb_output_instance_destroy(ins);
+ return -1;
+ }
+
+ /* Override first configmap value based on it plugin flag */
+ m = mk_list_entry_first(ins->tls_config_map, struct flb_config_map, _head);
+ if (p->flags & FLB_IO_TLS) {
+ m->value.val.boolean = FLB_TRUE;
+ }
+ else {
+ m->value.val.boolean = FLB_FALSE;
+ }
+#endif
+
+ /* Init network defaults */
+ flb_net_setup_init(&ins->net_setup);
+
+ if (flb_output_net_property_check(ins, config) == -1) {
+ flb_output_instance_destroy(ins);
+ return -1;
+ }
+
+ /* Initialize plugin through it 'init callback' */
+ ret = p->cb_init(ins, config, ins->data);
+ if (ret == -1) {
+ flb_error("[output] failed to initialize '%s' plugin",
+ p->name);
+ flb_output_instance_destroy(ins);
+ return -1;
+ }
+
+ /* Multi-threading enabled if configured */
+ ret = flb_output_enable_multi_threading(ins, config);
+ if (ret == -1) {
+ flb_error("[output] could not start thread pool for '%s' plugin",
+ flb_output_name(ins));
+ return -1;
+ }
+
+ /* initialize processors */
+ ret = flb_processor_init(ins->processor);
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* Assign an Configuration context to an Output */
+void flb_output_set_context(struct flb_output_instance *ins, void *context)
+{
+ ins->context = context;
+}
+
+/* Check that at least one Output is enabled */
+int flb_output_check(struct flb_config *config)
+{
+ if (mk_list_is_empty(&config->outputs) == 0) {
+ return -1;
+ }
+ return 0;
+}
+
+/* Check output plugin's log level.
+ * Not for core plugins but for Golang plugins.
+ * Golang plugins do not have thread-local flb_worker_ctx information. */
+int flb_output_log_check(struct flb_output_instance *ins, int l)
+{
+ if (ins->log_level < l) {
+ return FLB_FALSE;
+ }
+
+ return FLB_TRUE;
+}
+
+/*
+ * Output plugins might have enabled certain features that have not been passed
+ * directly to the upstream context. In order to avoid let plugins validate specific
+ * variables from the instance context like tls, tls.x, keepalive, etc, we populate
+ * them directly through this function.
+ */
+int flb_output_upstream_set(struct flb_upstream *u, struct flb_output_instance *ins)
+{
+ int flags = 0;
+
+ if (!u) {
+ return -1;
+ }
+
+ /* TLS */
+#ifdef FLB_HAVE_TLS
+ if (ins->use_tls == FLB_TRUE) {
+ flags |= FLB_IO_TLS;
+ }
+ else {
+ flags |= FLB_IO_TCP;
+ }
+#else
+ flags |= FLB_IO_TCP;
+#endif
+
+ /* IPv6 */
+ if (ins->host.ipv6 == FLB_TRUE) {
+ flags |= FLB_IO_IPV6;
+ }
+ /* keepalive */
+ if (ins->net_setup.keepalive == FLB_TRUE) {
+ flags |= FLB_IO_TCP_KA;
+ }
+
+ /* Set flags */
+ flb_stream_enable_flags(&u->base, flags);
+
+ flb_upstream_set_total_connections_label(u,
+ flb_output_name(ins));
+
+ flb_upstream_set_total_connections_gauge(u,
+ ins->cmt_upstream_total_connections);
+
+ flb_upstream_set_busy_connections_label(u,
+ flb_output_name(ins));
+
+ flb_upstream_set_busy_connections_gauge(u,
+ ins->cmt_upstream_busy_connections);
+
+ /*
+ * If the output plugin flush callbacks will run in multiple threads, enable
+ * the thread safe mode for the Upstream context.
+ */
+ if (ins->tp_workers > 0) {
+ flb_stream_enable_thread_safety(&u->base);
+
+ mk_list_add(&u->base._head, &ins->upstreams);
+ }
+
+ /* Set networking options 'net.*' received through instance properties */
+ memcpy(&u->base.net, &ins->net_setup, sizeof(struct flb_net_setup));
+
+ return 0;
+}
+
+int flb_output_upstream_ha_set(void *ha, struct flb_output_instance *ins)
+{
+ struct mk_list *head;
+ struct flb_upstream_node *node;
+ struct flb_upstream_ha *upstream_ha = ha;
+
+ mk_list_foreach(head, &upstream_ha->nodes) {
+ node = mk_list_entry(head, struct flb_upstream_node, _head);
+ flb_output_upstream_set(node->u, ins);
+ }
+
+ return 0;
+}
+
+/*
+ * Helper function to set HTTP callbacks using the output instance 'callback'
+ * context.
+ */
+int flb_output_set_http_debug_callbacks(struct flb_output_instance *ins)
+{
+#ifdef FLB_HAVE_HTTP_CLIENT_DEBUG
+ return flb_http_client_debug_setup(ins->callback, &ins->properties);
+#else
+ return 0;
+#endif
+}
diff --git a/fluent-bit/src/flb_output_thread.c b/fluent-bit/src/flb_output_thread.c
new file mode 100644
index 000000000..52d1f5795
--- /dev/null
+++ b/fluent-bit/src/flb_output_thread.c
@@ -0,0 +1,568 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_event_loop.h>
+#include <fluent-bit/flb_network.h>
+#include <fluent-bit/flb_scheduler.h>
+#include <fluent-bit/flb_output_plugin.h>
+#include <fluent-bit/flb_output_thread.h>
+#include <fluent-bit/flb_thread_pool.h>
+
+static pthread_once_t local_thread_instance_init = PTHREAD_ONCE_INIT;
+FLB_TLS_DEFINE(struct flb_out_thread_instance, local_thread_instance);
+
+void flb_output_thread_instance_init()
+{
+ FLB_TLS_INIT(local_thread_instance);
+}
+
+struct flb_out_thread_instance *flb_output_thread_instance_get()
+{
+ struct flb_out_thread_instance *th_ins;
+
+ th_ins = FLB_TLS_GET(local_thread_instance);
+ return th_ins;
+}
+
+void flb_output_thread_instance_set(struct flb_out_thread_instance *th_ins)
+{
+ FLB_TLS_SET(local_thread_instance, th_ins);
+}
+
+/* Cleanup function that runs every 1.5 second */
+static void cb_thread_sched_timer(struct flb_config *ctx, void *data)
+{
+ (void) ctx;
+ struct flb_output_instance *ins;
+
+ /* Upstream connections timeouts handling */
+ ins = (struct flb_output_instance *) data;
+ flb_upstream_conn_timeouts(&ins->upstreams);
+}
+
+static inline int handle_output_event(struct flb_config *config,
+ int ch_parent, flb_pipefd_t fd)
+{
+ int ret;
+ int bytes;
+ int out_id;
+ uint32_t type;
+ uint32_t key;
+ uint64_t val;
+
+ bytes = flb_pipe_r(fd, &val, sizeof(val));
+ if (bytes == -1) {
+ flb_errno();
+ return -1;
+ }
+
+ /* Get type and key */
+ type = FLB_BITS_U64_HIGH(val);
+ key = FLB_BITS_U64_LOW(val);
+
+ if (type != FLB_ENGINE_TASK) {
+ flb_error("[engine] invalid event type %i for output handler",
+ type);
+ return -1;
+ }
+
+ ret = FLB_TASK_RET(key);
+ out_id = FLB_TASK_OUT(key);
+
+ /* Destroy the output co-routine context */
+ flb_output_flush_finished(config, out_id);
+
+ /*
+ * Notify the parent event loop the return status, just forward the same
+ * 64 bits value.
+ */
+ ret = flb_pipe_w(ch_parent, &val, sizeof(val));
+ if (ret == -1) {
+ flb_errno();
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * For every upstream registered, creates a local mapping for the thread. This is
+ * done to provide local queues of connections so we can use our event loop and I/O
+ * totally independently without the need of any syncrhonization across threads
+ */
+static int upstream_thread_create(struct flb_out_thread_instance *th_ins,
+ struct flb_output_instance *ins)
+{
+ struct mk_list *head;
+ struct flb_upstream *u;
+ struct flb_upstream *th_u;
+
+ mk_list_foreach(head, &ins->upstreams) {
+ u = mk_list_entry(head, struct flb_upstream, base._head);
+
+ th_u = flb_calloc(1, sizeof(struct flb_upstream));
+ if (!th_u) {
+ flb_errno();
+ return -1;
+ }
+ th_u->parent_upstream = u;
+ flb_upstream_queue_init(&th_u->queue);
+ mk_list_add(&th_u->base._head, &th_ins->upstreams);
+ }
+
+ return 0;
+}
+
+int count_upstream_busy_connections(struct flb_out_thread_instance *th_ins)
+{
+ int c = 0;
+ struct mk_list *head;
+ struct flb_upstream *u;
+
+ mk_list_foreach(head, &th_ins->upstreams) {
+ u = mk_list_entry(head, struct flb_upstream, base._head);
+ c += mk_list_size(&u->queue.busy_queue);
+ }
+
+ return c;
+}
+
+static void upstream_thread_destroy(struct flb_out_thread_instance *th_ins)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_upstream *th_u;
+
+ mk_list_foreach_safe(head, tmp, &th_ins->upstreams) {
+ th_u = mk_list_entry(head, struct flb_upstream, base._head);
+ flb_upstream_destroy(th_u);
+ }
+}
+
+/*
+ * This is the worker function that creates an event loop and synchronize
+ * messages from the engine like 'flush' requests. Note that the running
+ * plugin flush callback has not notion about it threaded context.
+ *
+ * Each worker spawn a co-routine per flush request.
+ */
+static void output_thread(void *data)
+{
+ int n;
+ int ret;
+ int running = FLB_TRUE;
+ int stopping = FLB_FALSE;
+ int thread_id;
+ char tmp[64];
+ struct mk_event event_local;
+ struct mk_event *event;
+ struct flb_sched *sched;
+ struct flb_task *task;
+ struct flb_connection *u_conn;
+ struct flb_output_instance *ins;
+ struct flb_output_flush *out_flush;
+ struct flb_out_thread_instance *th_ins = data;
+ struct flb_out_flush_params *params;
+ struct flb_net_dns dns_ctx;
+
+ /* Register thread instance */
+ flb_output_thread_instance_set(th_ins);
+
+ ins = th_ins->ins;
+ thread_id = th_ins->th->id;
+
+ flb_coro_thread_init();
+
+ flb_net_ctx_init(&dns_ctx);
+ flb_net_dns_ctx_set(&dns_ctx);
+
+ /*
+ * Expose the event loop to the I/O interfaces: since we are in a separate
+ * thread, the upstream connection interfaces need access to the event
+ * loop for event notifications. Invoking the flb_engine_evl_set() function
+ * it sets the event loop reference in a TLS (thread local storage) variable
+ * of the scope of this thread.
+ */
+ flb_engine_evl_set(th_ins->evl);
+
+ /* Set the upstream queue */
+ flb_upstream_list_set(&th_ins->upstreams);
+
+ /* Create a scheduler context */
+ sched = flb_sched_create(ins->config, th_ins->evl);
+ if (!sched) {
+ flb_plg_error(ins, "could not create thread scheduler");
+ return;
+ }
+ flb_sched_ctx_set(sched);
+
+ /*
+ * Sched a permanent callback triggered every 1.5 second to let other
+ * components of this thread run tasks at that interval.
+ */
+ ret = flb_sched_timer_cb_create(sched,
+ FLB_SCHED_TIMER_CB_PERM,
+ 1500, cb_thread_sched_timer, ins, NULL);
+ if (ret == -1) {
+ flb_plg_error(ins, "could not schedule permanent callback");
+ return;
+ }
+
+ snprintf(tmp, sizeof(tmp) - 1, "flb-out-%s-w%i", ins->name, thread_id);
+ mk_utils_worker_rename(tmp);
+
+ memset(&event_local, 0, sizeof(struct mk_event));
+
+ /* Channel used by flush callbacks to notify it return status */
+ ret = mk_event_channel_create(th_ins->evl,
+ &th_ins->ch_thread_events[0],
+ &th_ins->ch_thread_events[1],
+ &event_local);
+ if (ret == -1) {
+ flb_plg_error(th_ins->ins, "could not create thread channel");
+ flb_engine_evl_set(NULL);
+ return;
+ }
+ event_local.type = FLB_ENGINE_EV_OUTPUT;
+
+ flb_plg_info(th_ins->ins, "worker #%i started", thread_id);
+
+ /* Thread event loop */
+ while (running) {
+ mk_event_wait(th_ins->evl);
+ flb_event_priority_live_foreach(event, th_ins->evl_bktq, th_ins->evl,
+ FLB_ENGINE_LOOP_MAX_ITER) {
+ /*
+ * FIXME
+ * -----
+ * - handle return status by plugin flush callback.
+ */
+ if (event->type == FLB_ENGINE_EV_CORE) {
+
+ }
+ else if (event->type & FLB_ENGINE_EV_SCHED) {
+ /*
+ * Note that this scheduler event handler has more features
+ * designed to be used from the parent thread, on this specific
+ * use case we just care about simple timers created on this
+ * thread or threaded by some output plugin.
+ */
+ flb_sched_event_handler(sched->config, event);
+ }
+ else if (event->type == FLB_ENGINE_EV_THREAD_OUTPUT) {
+ /* Read the task reference */
+ n = flb_pipe_r(event->fd, &task, sizeof(struct flb_task *));
+ if (n <= 0) {
+ flb_errno();
+ continue;
+ }
+
+ /*
+ * If the address receives 0xdeadbeef, means the thread must
+ * be terminated.
+ */
+ if (task == (struct flb_task *) 0xdeadbeef) {
+ stopping = FLB_TRUE;
+ flb_plg_info(th_ins->ins, "thread worker #%i stopping...",
+ thread_id);
+ continue;
+ }
+
+ /* Start the co-routine with the flush callback */
+ out_flush = flb_output_flush_create(task,
+ task->i_ins,
+ th_ins->ins,
+ th_ins->config);
+ if (!out_flush) {
+ continue;
+ }
+ flb_coro_resume(out_flush->coro);
+ }
+ else if (event->type == FLB_ENGINE_EV_CUSTOM) {
+ event->handler(event);
+ }
+ else if (event->type == FLB_ENGINE_EV_THREAD) {
+ /*
+ * Check if we have some co-routine associated to this event,
+ * if so, resume the co-routine
+ */
+ u_conn = (struct flb_connection *) event;
+
+ if (u_conn->coroutine) {
+ flb_trace("[engine] resuming coroutine=%p", u_conn->coroutine);
+ flb_coro_resume(u_conn->coroutine);
+ }
+ }
+ else if (event->type == FLB_ENGINE_EV_OUTPUT) {
+ /*
+ * The flush callback has finished working and delivered it
+ * return status. At this intermediary step we cleanup the
+ * co-routine resources created before and then forward
+ * the return message to the parent event loop so the Task
+ * can be updated.
+ */
+ handle_output_event(th_ins->config, ins->ch_events[1], event->fd);
+ }
+ else {
+ flb_plg_warn(ins, "unhandled event type => %i\n", event->type);
+ }
+ }
+
+ flb_net_dns_lookup_context_cleanup(&dns_ctx);
+
+ /* Destroy upstream connections from the 'pending destroy list' */
+ flb_upstream_conn_pending_destroy_list(&th_ins->upstreams);
+ flb_sched_timer_cleanup(sched);
+
+ /* Check if we should stop the event loop */
+ if (stopping == FLB_TRUE && mk_list_size(&th_ins->flush_list) == 0) {
+ /*
+ * If there are no busy network connections (and no coroutines) its
+ * safe to stop it.
+ */
+ if (count_upstream_busy_connections(th_ins) == 0) {
+ running = FLB_FALSE;
+ }
+ }
+ }
+
+ /*
+ * Final cleanup, destroy all resources associated with:
+ *
+ * - local upstream maps
+ * - available connections, likely these are unused keepalive connections
+ * - any 'new' connection in the pending destroy list
+ * - event loop context
+ * - scheduler context
+ * - parameters helper for coroutines
+ */
+ upstream_thread_destroy(th_ins);
+ flb_upstream_conn_active_destroy_list(&th_ins->upstreams);
+ flb_upstream_conn_pending_destroy_list(&th_ins->upstreams);
+
+ flb_sched_destroy(sched);
+ params = FLB_TLS_GET(out_flush_params);
+ if (params) {
+ flb_free(params);
+ }
+ mk_event_loop_destroy(th_ins->evl);
+ flb_bucket_queue_destroy(th_ins->evl_bktq);
+
+ flb_plg_info(ins, "thread worker #%i stopped", thread_id);
+}
+
+int flb_output_thread_pool_flush(struct flb_task *task,
+ struct flb_output_instance *out_ins,
+ struct flb_config *config)
+{
+ int n;
+ struct flb_tp_thread *th;
+ struct flb_out_thread_instance *th_ins;
+
+ /* Choose the worker that will handle the Task (round-robin) */
+ th = flb_tp_thread_get_rr(out_ins->tp);
+ if (!th) {
+ return -1;
+ }
+
+ th_ins = th->params.data;
+
+ flb_plg_debug(out_ins, "task_id=%i assigned to thread #%i",
+ task->id, th->id);
+
+ n = flb_pipe_w(th_ins->ch_parent_events[1], &task, sizeof(struct flb_task*));
+
+ if (n == -1) {
+ flb_errno();
+ return -1;
+ }
+
+ return 0;
+}
+
+int flb_output_thread_pool_create(struct flb_config *config,
+ struct flb_output_instance *ins)
+{
+ int i;
+ int ret;
+ struct flb_tp *tp;
+ struct flb_tp_thread *th;
+ struct mk_event_loop *evl;
+ struct flb_bucket_queue *evl_bktq;
+ struct flb_out_thread_instance *th_ins;
+
+ /* Create the thread pool context */
+ tp = flb_tp_create(config);
+ if (!tp) {
+ return -1;
+ }
+ ins->tp = tp;
+ ins->is_threaded = FLB_TRUE;
+
+ /*
+ * Initialize thread-local-storage, every worker thread has it owns
+ * context with relevant info populated inside the thread.
+ */
+ pthread_once(&local_thread_instance_init, flb_output_thread_instance_init);
+
+ /* Create workers */
+ for (i = 0; i < ins->tp_workers; i++) {
+ th_ins = flb_malloc(sizeof(struct flb_out_thread_instance));
+ if (!th_ins) {
+ flb_errno();
+ continue;
+ }
+ memset(th_ins, 0, sizeof(struct flb_out_thread_instance));
+
+ th_ins->config = config;
+ th_ins->ins = ins;
+ th_ins->flush_id = 0;
+ mk_list_init(&th_ins->flush_list);
+ mk_list_init(&th_ins->flush_list_destroy);
+ pthread_mutex_init(&th_ins->flush_mutex, NULL);
+ mk_list_init(&th_ins->upstreams);
+
+ upstream_thread_create(th_ins, ins);
+
+ /* Create the event loop for this thread */
+ evl = mk_event_loop_create(64);
+ if (!evl) {
+ flb_plg_error(ins, "could not create thread event loop");
+ flb_free(th_ins);
+ continue;
+ }
+ evl_bktq = flb_bucket_queue_create(FLB_ENGINE_PRIORITY_COUNT);
+ if (!evl_bktq) {
+ flb_plg_error(ins, "could not create thread event loop bucket queue");
+ flb_free(evl);
+ flb_free(th_ins);
+ continue;
+ }
+ th_ins->evl = evl;
+ th_ins->evl_bktq = evl_bktq;
+
+ /*
+ * Event loop setup between parent engine and this thread
+ *
+ * - FLB engine uses 'ch_parent_events[1]' to dispatch tasks to this thread
+ * - Thread receive message on ch_parent_events[0]
+ *
+ * The mk_event_channel_create() will attach the pipe read end ch_parent_events[0]
+ * to the local event loop 'evl'.
+ */
+ ret = mk_event_channel_create(th_ins->evl,
+ &th_ins->ch_parent_events[0],
+ &th_ins->ch_parent_events[1],
+ th_ins);
+ if (ret == -1) {
+ flb_plg_error(th_ins->ins, "could not create thread channel");
+ mk_event_loop_destroy(th_ins->evl);
+ flb_bucket_queue_destroy(th_ins->evl_bktq);
+ flb_free(th_ins);
+ continue;
+ }
+ /* Signal type to indicate a "flush" request */
+ th_ins->event.type = FLB_ENGINE_EV_THREAD_OUTPUT;
+ th_ins->event.priority = FLB_ENGINE_PRIORITY_THREAD;
+
+ /* Spawn the thread */
+ th = flb_tp_thread_create(tp, output_thread, th_ins, config);
+ if (!th) {
+ flb_plg_error(ins, "could not register worker thread #%i", i);
+ continue;
+ }
+ th_ins->th = th;
+ }
+
+ return 0;
+}
+
+int flb_output_thread_pool_coros_size(struct flb_output_instance *ins)
+{
+ int n;
+ int size = 0;
+ struct mk_list *head;
+ struct flb_tp *tp = ins->tp;
+ struct flb_tp_thread *th;
+ struct flb_out_thread_instance *th_ins;
+
+ /* Signal each worker thread that needs to stop doing work */
+ mk_list_foreach(head, &tp->list_threads) {
+ th = mk_list_entry(head, struct flb_tp_thread, _head);
+ if (th->status != FLB_THREAD_POOL_RUNNING) {
+ continue;
+ }
+
+ th_ins = th->params.data;
+
+ pthread_mutex_lock(&th_ins->flush_mutex);
+ n = mk_list_size(&th_ins->flush_list);
+ pthread_mutex_unlock(&th_ins->flush_mutex);
+
+ size += n;
+ }
+
+ return size;
+}
+
+void flb_output_thread_pool_destroy(struct flb_output_instance *ins)
+{
+ int n;
+ struct flb_task *stop = (struct flb_task *) 0xdeadbeef;
+ struct flb_tp *tp = ins->tp;
+ struct mk_list *head;
+ struct flb_out_thread_instance *th_ins;
+ struct flb_tp_thread *th;
+
+ if (!tp) {
+ return;
+ }
+
+ /* Signal each worker thread that needs to stop doing work */
+ mk_list_foreach(head, &tp->list_threads) {
+ th = mk_list_entry(head, struct flb_tp_thread, _head);
+ if (th->status != FLB_THREAD_POOL_RUNNING) {
+ continue;
+ }
+
+ th_ins = th->params.data;
+ n = flb_pipe_w(th_ins->ch_parent_events[1], &stop, sizeof(stop));
+ if (n < 0) {
+ flb_errno();
+ flb_plg_error(th_ins->ins, "could not signal worker thread");
+ flb_free(th_ins);
+ continue;
+ }
+ pthread_join(th->tid, NULL);
+ flb_free(th_ins);
+ }
+
+ flb_tp_destroy(ins->tp);
+ ins->tp = NULL;
+}
+
+int flb_output_thread_pool_start(struct flb_output_instance *ins)
+{
+ struct flb_tp *tp = ins->tp;
+
+ flb_tp_thread_start_all(tp);
+ return 0;
+}
diff --git a/fluent-bit/src/flb_pack.c b/fluent-bit/src/flb_pack.c
new file mode 100644
index 000000000..adcaa22c9
--- /dev/null
+++ b/fluent-bit/src/flb_pack.c
@@ -0,0 +1,1270 @@
+/*-*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_error.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_time.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_unescape.h>
+
+/* cmetrics */
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_decode_msgpack.h>
+#include <cmetrics/cmt_encode_text.h>
+
+#include <msgpack.h>
+#include <math.h>
+#include <jsmn/jsmn.h>
+
+#define try_to_write_str flb_utils_write_str
+
+static int convert_nan_to_null = FLB_FALSE;
+
+static int flb_pack_set_null_as_nan(int b) {
+ if (b == FLB_TRUE || b == FLB_FALSE) {
+ convert_nan_to_null = b;
+ }
+ return convert_nan_to_null;
+}
+
+int flb_json_tokenise(const char *js, size_t len,
+ struct flb_pack_state *state)
+{
+ int ret;
+ int new_tokens = 256;
+ size_t old_size;
+ size_t new_size;
+ void *tmp;
+
+ ret = jsmn_parse(&state->parser, js, len,
+ state->tokens, state->tokens_size);
+ while (ret == JSMN_ERROR_NOMEM) {
+ /* Get current size of the array in bytes */
+ old_size = state->tokens_size * sizeof(jsmntok_t);
+
+ /* New size: add capacity for new 256 entries */
+ new_size = old_size + (sizeof(jsmntok_t) * new_tokens);
+
+ tmp = flb_realloc(state->tokens, new_size);
+ if (!tmp) {
+ flb_errno();
+ return -1;
+ }
+ state->tokens = tmp;
+ state->tokens_size += new_tokens;
+
+ ret = jsmn_parse(&state->parser, js, len,
+ state->tokens, state->tokens_size);
+ }
+
+ if (ret == JSMN_ERROR_INVAL) {
+ return FLB_ERR_JSON_INVAL;
+ }
+
+ if (ret == JSMN_ERROR_PART) {
+ /* This is a partial JSON message, just stop */
+ flb_trace("[json tokenise] incomplete");
+ return FLB_ERR_JSON_PART;
+ }
+
+ state->tokens_count += ret;
+ return 0;
+}
+
+static inline int is_float(const char *buf, int len)
+{
+ const char *end = buf + len;
+ const char *p = buf;
+
+ while (p <= end) {
+ if (*p == 'e' && p < end && *(p + 1) == '-') {
+ return 1;
+ }
+ else if (*p == '.') {
+ return 1;
+ }
+ p++;
+ }
+
+ return 0;
+}
+
+/* Sanitize incoming JSON string */
+static inline int pack_string_token(struct flb_pack_state *state,
+ const char *str, int len,
+ msgpack_packer *pck)
+{
+ int s;
+ int out_len;
+ char *tmp;
+ char *out_buf;
+
+ if (state->buf_size < len + 1) {
+ s = len + 1;
+ tmp = flb_realloc(state->buf_data, s);
+ if (!tmp) {
+ flb_errno();
+ return -1;
+ }
+ else {
+ state->buf_data = tmp;
+ state->buf_size = s;
+ }
+ }
+ out_buf = state->buf_data;
+
+ /* Always decode any UTF-8 or special characters */
+ out_len = flb_unescape_string_utf8(str, len, out_buf);
+
+ /* Pack decoded text */
+ msgpack_pack_str(pck, out_len);
+ msgpack_pack_str_body(pck, out_buf, out_len);
+
+ return out_len;
+}
+
+/* Receive a tokenized JSON message and convert it to MsgPack */
+static char *tokens_to_msgpack(struct flb_pack_state *state,
+ const char *js,
+ int *out_size, int *last_byte,
+ int *out_records)
+{
+ int i;
+ int flen;
+ int arr_size;
+ int records = 0;
+ const char *p;
+ char *buf;
+ const jsmntok_t *t;
+ msgpack_packer pck;
+ msgpack_sbuffer sbuf;
+ jsmntok_t *tokens;
+
+ tokens = state->tokens;
+ arr_size = state->tokens_count;
+
+ if (arr_size == 0) {
+ return NULL;
+ }
+
+ /* initialize buffers */
+ msgpack_sbuffer_init(&sbuf);
+ msgpack_packer_init(&pck, &sbuf, msgpack_sbuffer_write);
+
+ for (i = 0; i < arr_size ; i++) {
+ t = &tokens[i];
+
+ if (t->start == -1 || t->end == -1 || (t->start == 0 && t->end == 0)) {
+ break;
+ }
+
+ if (t->parent == -1) {
+ *last_byte = t->end;
+ records++;
+ }
+
+ flen = (t->end - t->start);
+ switch (t->type) {
+ case JSMN_OBJECT:
+ msgpack_pack_map(&pck, t->size);
+ break;
+ case JSMN_ARRAY:
+ msgpack_pack_array(&pck, t->size);
+ break;
+ case JSMN_STRING:
+ pack_string_token(state, js + t->start, flen, &pck);
+ break;
+ case JSMN_PRIMITIVE:
+ p = js + t->start;
+ if (*p == 'f') {
+ msgpack_pack_false(&pck);
+ }
+ else if (*p == 't') {
+ msgpack_pack_true(&pck);
+ }
+ else if (*p == 'n') {
+ msgpack_pack_nil(&pck);
+ }
+ else {
+ if (is_float(p, flen)) {
+ msgpack_pack_double(&pck, atof(p));
+ }
+ else {
+ msgpack_pack_int64(&pck, atoll(p));
+ }
+ }
+ break;
+ case JSMN_UNDEFINED:
+ msgpack_sbuffer_destroy(&sbuf);
+ return NULL;
+ }
+ }
+
+ *out_size = sbuf.size;
+ *out_records = records;
+ buf = sbuf.data;
+
+ return buf;
+}
+
+/*
+ * It parse a JSON string and convert it to MessagePack format, this packer is
+ * useful when a complete JSON message exists, otherwise it will fail until
+ * the message is complete.
+ *
+ * This routine do not keep a state in the parser, do not use it for big
+ * JSON messages.
+ */
+static int pack_json_to_msgpack(const char *js, size_t len, char **buffer,
+ size_t *size, int *root_type, int *records,
+ size_t *consumed)
+{
+ int ret = -1;
+ int n_records;
+ int out;
+ int last;
+ char *buf = NULL;
+ struct flb_pack_state state;
+
+ ret = flb_pack_state_init(&state);
+ if (ret != 0) {
+ return -1;
+ }
+ ret = flb_json_tokenise(js, len, &state);
+ if (ret != 0) {
+ ret = -1;
+ goto flb_pack_json_end;
+ }
+
+ if (state.tokens_count == 0) {
+ ret = -1;
+ goto flb_pack_json_end;
+ }
+
+ buf = tokens_to_msgpack(&state, js, &out, &last, &n_records);
+ if (!buf) {
+ ret = -1;
+ goto flb_pack_json_end;
+ }
+
+ *root_type = state.tokens[0].type;
+ *size = out;
+ *buffer = buf;
+ *records = n_records;
+
+ if (consumed != NULL) {
+ *consumed = last;
+ }
+
+ ret = 0;
+
+ flb_pack_json_end:
+ flb_pack_state_reset(&state);
+ return ret;
+}
+
+/* Pack unlimited serialized JSON messages into msgpack */
+int flb_pack_json(const char *js, size_t len, char **buffer, size_t *size,
+ int *root_type, size_t *consumed)
+{
+ int records;
+
+ return pack_json_to_msgpack(js, len, buffer, size, root_type, &records, consumed);
+}
+
+/*
+ * Pack unlimited serialized JSON messages into msgpack, finally it writes on
+ * 'out_records' the number of messages.
+ */
+int flb_pack_json_recs(const char *js, size_t len, char **buffer, size_t *size,
+ int *root_type, int *out_records, size_t *consumed)
+{
+ return pack_json_to_msgpack(js, len, buffer, size, root_type, out_records, consumed);
+}
+
+/* Initialize a JSON packer state */
+int flb_pack_state_init(struct flb_pack_state *s)
+{
+ int tokens = 256;
+ size_t size = 256;
+
+ jsmn_init(&s->parser);
+
+ size = sizeof(jsmntok_t) * tokens;
+ s->tokens = flb_malloc(size);
+ if (!s->tokens) {
+ flb_errno();
+ return -1;
+ }
+ s->tokens_size = tokens;
+ s->tokens_count = 0;
+ s->last_byte = 0;
+ s->multiple = FLB_FALSE;
+
+ s->buf_data = flb_malloc(size);
+ if (!s->buf_data) {
+ flb_errno();
+ flb_free(s->tokens);
+ s->tokens = NULL;
+ return -1;
+ }
+ s->buf_size = size;
+ s->buf_len = 0;
+
+ return 0;
+}
+
+void flb_pack_state_reset(struct flb_pack_state *s)
+{
+ flb_free(s->tokens);
+ s->tokens = NULL;
+ s->tokens_size = 0;
+ s->tokens_count = 0;
+ s->last_byte = 0;
+ s->buf_size = 0;
+ flb_free(s->buf_data);
+ s->buf_data = NULL;
+}
+
+
+/*
+ * It parse a JSON string and convert it to MessagePack format. The main
+ * difference of this function and the previous flb_pack_json() is that it
+ * keeps a parser and tokens state, allowing to process big messages and
+ * resume the parsing process instead of start from zero.
+ */
+int flb_pack_json_state(const char *js, size_t len,
+ char **buffer, int *size,
+ struct flb_pack_state *state)
+{
+ int ret;
+ int out;
+ int delim = 0;
+ int last = 0;
+ int records;
+ char *buf;
+ jsmntok_t *t;
+
+ ret = flb_json_tokenise(js, len, state);
+ state->multiple = FLB_TRUE;
+ if (ret == FLB_ERR_JSON_PART && state->multiple == FLB_TRUE) {
+ /*
+ * If the caller enabled 'multiple' flag, it means that the incoming
+ * JSON message may have multiple messages concatenated and likely
+ * the last one is only incomplete.
+ *
+ * The following routine aims to determinate how many JSON messages
+ * are OK in the array of tokens, if any, process them and adjust
+ * the JSMN context/buffers.
+ */
+
+ /*
+ * jsmn_parse updates jsmn_parser members. (state->parser)
+ * A member 'toknext' points next incomplete object token.
+ * We use toknext - 1 as an index of last member of complete JSON.
+ */
+ int i;
+ int found = 0;
+
+ if (state->parser.toknext == 0) {
+ return ret;
+ }
+
+ for (i = (int)state->parser.toknext - 1; i >= 1; i--) {
+ t = &state->tokens[i];
+
+ if (t->parent == -1 && (t->end != 0)) {
+ found++;
+ delim = i;
+ break;
+ }
+ }
+
+ if (found == 0) {
+ return ret; /* FLB_ERR_JSON_PART */
+ }
+ state->tokens_count += delim;
+ }
+ else if (ret != 0) {
+ return ret;
+ }
+
+ if (state->tokens_count == 0 || state->tokens == NULL) {
+ state->last_byte = last;
+ return FLB_ERR_JSON_INVAL;
+ }
+
+ buf = tokens_to_msgpack(state, js, &out, &last, &records);
+ if (!buf) {
+ return -1;
+ }
+
+ *size = out;
+ *buffer = buf;
+ state->last_byte = last;
+
+ return 0;
+}
+
+int flb_metadata_pop_from_msgpack(msgpack_object **metadata, msgpack_unpacked *upk,
+ msgpack_object **map)
+{
+ if (metadata == NULL || upk == NULL) {
+ return -1;
+ }
+
+ if (upk->data.type != MSGPACK_OBJECT_ARRAY) {
+ return -1;
+ }
+
+ *metadata = &upk->data.via.array.ptr[0].via.array.ptr[1];
+ *map = &upk->data.via.array.ptr[1];
+
+ return 0;
+}
+
+static int pack_print_fluent_record(size_t cnt, msgpack_unpacked result)
+{
+ msgpack_object *metadata;
+ msgpack_object root;
+ msgpack_object *obj;
+ struct flb_time tms;
+ msgpack_object o;
+
+ root = result.data;
+ if (root.type != MSGPACK_OBJECT_ARRAY) {
+ return -1;
+ }
+
+ o = root.via.array.ptr[0];
+ if (o.type != MSGPACK_OBJECT_ARRAY) {
+ return -1;
+ }
+
+ /* decode expected timestamp only (integer, float or ext) */
+ o = o.via.array.ptr[0];
+ if (o.type != MSGPACK_OBJECT_POSITIVE_INTEGER &&
+ o.type != MSGPACK_OBJECT_FLOAT &&
+ o.type != MSGPACK_OBJECT_EXT) {
+ return -1;
+ }
+
+ /* This is a Fluent Bit record, just do the proper unpacking/printing */
+ flb_time_pop_from_msgpack(&tms, &result, &obj);
+ flb_metadata_pop_from_msgpack(&metadata, &result, &obj);
+
+ fprintf(stdout, "[%zd] [%"PRIu32".%09lu, ", cnt,
+ (uint32_t) tms.tm.tv_sec, tms.tm.tv_nsec);
+
+ msgpack_object_print(stdout, *metadata);
+
+ fprintf(stdout, ", ");
+
+ msgpack_object_print(stdout, *obj);
+
+ fprintf(stdout, "]\n");
+
+ return 0;
+}
+
+void flb_pack_print(const char *data, size_t bytes)
+{
+ int ret;
+ msgpack_unpacked result;
+ size_t off = 0, cnt = 0;
+
+ msgpack_unpacked_init(&result);
+ while (msgpack_unpack_next(&result, data, bytes, &off) == MSGPACK_UNPACK_SUCCESS) {
+ /* Check if we are processing an internal Fluent Bit record */
+ ret = pack_print_fluent_record(cnt, result);
+ if (ret == 0) {
+ continue;
+ }
+
+ printf("[%zd] ", cnt++);
+ msgpack_object_print(stdout, result.data);
+ printf("\n");
+ }
+ msgpack_unpacked_destroy(&result);
+}
+
+void flb_pack_print_metrics(const char *data, size_t bytes)
+{
+ int ret;
+ size_t off = 0;
+ cfl_sds_t text;
+ struct cmt *cmt = NULL;
+
+ /* get cmetrics context */
+ ret = cmt_decode_msgpack_create(&cmt, (char *) data, bytes, &off);
+ if (ret != 0) {
+ flb_error("could not process metrics payload");
+ return;
+ }
+
+ /* convert to text representation */
+ text = cmt_encode_text_create(cmt);
+
+ /* destroy cmt context */
+ cmt_destroy(cmt);
+
+ printf("%s", text);
+ fflush(stdout);
+
+ cmt_encode_text_destroy(text);
+}
+
+static inline int try_to_write(char *buf, int *off, size_t left,
+ const char *str, size_t str_len)
+{
+ if (str_len <= 0){
+ str_len = strlen(str);
+ }
+ if (left <= *off+str_len) {
+ return FLB_FALSE;
+ }
+ memcpy(buf+*off, str, str_len);
+ *off += str_len;
+ return FLB_TRUE;
+}
+
+
+/*
+ * Check if a key exists in the map using the 'offset' as an index to define
+ * which element needs to start looking from
+ */
+static inline int key_exists_in_map(msgpack_object key, msgpack_object map, int offset)
+{
+ int i;
+ msgpack_object p;
+
+ if (key.type != MSGPACK_OBJECT_STR) {
+ return FLB_FALSE;
+ }
+
+ for (i = offset; i < map.via.map.size; i++) {
+ p = map.via.map.ptr[i].key;
+ if (p.type != MSGPACK_OBJECT_STR) {
+ continue;
+ }
+
+ if (key.via.str.size != p.via.str.size) {
+ continue;
+ }
+
+ if (memcmp(key.via.str.ptr, p.via.str.ptr, p.via.str.size) == 0) {
+ return FLB_TRUE;
+ }
+ }
+
+ return FLB_FALSE;
+}
+
+static int msgpack2json(char *buf, int *off, size_t left,
+ const msgpack_object *o)
+{
+ int i;
+ int dup;
+ int ret = FLB_FALSE;
+ int loop;
+ int packed;
+
+ switch(o->type) {
+ case MSGPACK_OBJECT_NIL:
+ ret = try_to_write(buf, off, left, "null", 4);
+ break;
+
+ case MSGPACK_OBJECT_BOOLEAN:
+ ret = try_to_write(buf, off, left,
+ (o->via.boolean ? "true":"false"),0);
+
+ break;
+
+ case MSGPACK_OBJECT_POSITIVE_INTEGER:
+ {
+ char temp[32] = {0};
+ i = snprintf(temp, sizeof(temp)-1, "%"PRIu64, o->via.u64);
+ ret = try_to_write(buf, off, left, temp, i);
+ }
+ break;
+
+ case MSGPACK_OBJECT_NEGATIVE_INTEGER:
+ {
+ char temp[32] = {0};
+ i = snprintf(temp, sizeof(temp)-1, "%"PRId64, o->via.i64);
+ ret = try_to_write(buf, off, left, temp, i);
+ }
+ break;
+ case MSGPACK_OBJECT_FLOAT32:
+ case MSGPACK_OBJECT_FLOAT64:
+ {
+ char temp[512] = {0};
+ if (o->via.f64 == (double)(long long int)o->via.f64) {
+ i = snprintf(temp, sizeof(temp)-1, "%.1f", o->via.f64);
+ }
+ else if (convert_nan_to_null && isnan(o->via.f64) ) {
+ i = snprintf(temp, sizeof(temp)-1, "null");
+ }
+ else {
+ i = snprintf(temp, sizeof(temp)-1, "%.16g", o->via.f64);
+ }
+ ret = try_to_write(buf, off, left, temp, i);
+ }
+ break;
+
+ case MSGPACK_OBJECT_STR:
+ if (try_to_write(buf, off, left, "\"", 1) &&
+ (o->via.str.size > 0 ?
+ try_to_write_str(buf, off, left, o->via.str.ptr, o->via.str.size)
+ : 1/* nothing to do */) &&
+ try_to_write(buf, off, left, "\"", 1)) {
+ ret = FLB_TRUE;
+ }
+ break;
+
+ case MSGPACK_OBJECT_BIN:
+ if (try_to_write(buf, off, left, "\"", 1) &&
+ (o->via.bin.size > 0 ?
+ try_to_write_str(buf, off, left, o->via.bin.ptr, o->via.bin.size)
+ : 1 /* nothing to do */) &&
+ try_to_write(buf, off, left, "\"", 1)) {
+ ret = FLB_TRUE;
+ }
+ break;
+
+ case MSGPACK_OBJECT_EXT:
+ if (!try_to_write(buf, off, left, "\"", 1)) {
+ goto msg2json_end;
+ }
+ /* ext body. fortmat is similar to printf(1) */
+ {
+ char temp[32] = {0};
+ int len;
+ loop = o->via.ext.size;
+ for(i=0; i<loop; i++) {
+ len = snprintf(temp, sizeof(temp)-1, "\\x%02x", (char)o->via.ext.ptr[i]);
+ if (!try_to_write(buf, off, left, temp, len)) {
+ goto msg2json_end;
+ }
+ }
+ }
+ if (!try_to_write(buf, off, left, "\"", 1)) {
+ goto msg2json_end;
+ }
+ ret = FLB_TRUE;
+ break;
+
+ case MSGPACK_OBJECT_ARRAY:
+ loop = o->via.array.size;
+
+ if (!try_to_write(buf, off, left, "[", 1)) {
+ goto msg2json_end;
+ }
+ if (loop != 0) {
+ msgpack_object* p = o->via.array.ptr;
+ if (!msgpack2json(buf, off, left, p)) {
+ goto msg2json_end;
+ }
+ for (i=1; i<loop; i++) {
+ if (!try_to_write(buf, off, left, ",", 1) ||
+ !msgpack2json(buf, off, left, p+i)) {
+ goto msg2json_end;
+ }
+ }
+ }
+
+ ret = try_to_write(buf, off, left, "]", 1);
+ break;
+
+ case MSGPACK_OBJECT_MAP:
+ loop = o->via.map.size;
+ if (!try_to_write(buf, off, left, "{", 1)) {
+ goto msg2json_end;
+ }
+ if (loop != 0) {
+ msgpack_object k;
+ msgpack_object_kv *p = o->via.map.ptr;
+
+ packed = 0;
+ dup = FLB_FALSE;
+
+ k = o->via.map.ptr[0].key;
+ for (i = 0; i < loop; i++) {
+ k = o->via.map.ptr[i].key;
+ dup = key_exists_in_map(k, *o, i + 1);
+ if (dup == FLB_TRUE) {
+ continue;
+ }
+
+ if (packed > 0) {
+ if (!try_to_write(buf, off, left, ",", 1)) {
+ goto msg2json_end;
+ }
+ }
+
+ if (
+ !msgpack2json(buf, off, left, &(p+i)->key) ||
+ !try_to_write(buf, off, left, ":", 1) ||
+ !msgpack2json(buf, off, left, &(p+i)->val) ) {
+ goto msg2json_end;
+ }
+ packed++;
+ }
+ }
+
+ ret = try_to_write(buf, off, left, "}", 1);
+ break;
+
+ default:
+ flb_warn("[%s] unknown msgpack type %i", __FUNCTION__, o->type);
+ }
+
+ msg2json_end:
+ return ret;
+}
+
+/**
+ * convert msgpack to JSON string.
+ * This API is similar to snprintf.
+ *
+ * @param json_str The buffer to fill JSON string.
+ * @param json_size The size of json_str.
+ * @param data The msgpack_unpacked data.
+ * @return success ? a number characters filled : negative value
+ */
+int flb_msgpack_to_json(char *json_str, size_t json_size,
+ const msgpack_object *obj)
+{
+ int ret = -1;
+ int off = 0;
+
+ if (json_str == NULL || obj == NULL) {
+ return -1;
+ }
+
+ ret = msgpack2json(json_str, &off, json_size - 1, obj);
+ json_str[off] = '\0';
+ return ret ? off: ret;
+}
+
+flb_sds_t flb_msgpack_raw_to_json_sds(const void *in_buf, size_t in_size)
+{
+ int ret;
+ size_t off = 0;
+ size_t out_size;
+ size_t realloc_size;
+
+ msgpack_unpacked result;
+ msgpack_object *root;
+ flb_sds_t out_buf;
+ flb_sds_t tmp_buf;
+
+ /* buffer size strategy */
+ out_size = in_size * FLB_MSGPACK_TO_JSON_INIT_BUFFER_SIZE;
+ realloc_size = in_size * FLB_MSGPACK_TO_JSON_REALLOC_BUFFER_SIZE;
+ if (realloc_size < 256) {
+ realloc_size = 256;
+ }
+
+ out_buf = flb_sds_create_size(out_size);
+ if (!out_buf) {
+ flb_errno();
+ return NULL;
+ }
+
+ msgpack_unpacked_init(&result);
+ ret = msgpack_unpack_next(&result, in_buf, in_size, &off);
+ if (ret != MSGPACK_UNPACK_SUCCESS) {
+ flb_sds_destroy(out_buf);
+ msgpack_unpacked_destroy(&result);
+ return NULL;
+ }
+
+ root = &result.data;
+ while (1) {
+ ret = flb_msgpack_to_json(out_buf, out_size, root);
+ if (ret <= 0) {
+ tmp_buf = flb_sds_increase(out_buf, realloc_size);
+ if (tmp_buf) {
+ out_buf = tmp_buf;
+ out_size += realloc_size;
+ }
+ else {
+ flb_errno();
+ flb_sds_destroy(out_buf);
+ msgpack_unpacked_destroy(&result);
+ return NULL;
+ }
+ }
+ else {
+ break;
+ }
+ }
+
+ msgpack_unpacked_destroy(&result);
+ flb_sds_len_set(out_buf, ret);
+
+ return out_buf;
+}
+
+/*
+ * Given a 'format' string type, return it integer representation. This
+ * is used by output plugins that uses pack functions to convert
+ * msgpack records to JSON.
+ */
+int flb_pack_to_json_format_type(const char *str)
+{
+ if (strcasecmp(str, "msgpack") == 0) {
+ return FLB_PACK_JSON_FORMAT_NONE;
+ }
+ else if (strcasecmp(str, "json") == 0) {
+ return FLB_PACK_JSON_FORMAT_JSON;
+ }
+ else if (strcasecmp(str, "json_stream") == 0) {
+ return FLB_PACK_JSON_FORMAT_STREAM;
+ }
+ else if (strcasecmp(str, "json_lines") == 0) {
+ return FLB_PACK_JSON_FORMAT_LINES;
+ }
+
+ return -1;
+}
+
+/* Given a 'date string type', return it integer representation */
+int flb_pack_to_json_date_type(const char *str)
+{
+ if (strcasecmp(str, "double") == 0) {
+ return FLB_PACK_JSON_DATE_DOUBLE;
+ }
+ else if (strcasecmp(str, "java_sql_timestamp") == 0) {
+ return FLB_PACK_JSON_DATE_JAVA_SQL_TIMESTAMP;
+ }
+ else if (strcasecmp(str, "iso8601") == 0) {
+ return FLB_PACK_JSON_DATE_ISO8601;
+ }
+ else if (strcasecmp(str, "epoch") == 0) {
+ return FLB_PACK_JSON_DATE_EPOCH;
+ }
+ else if (strcasecmp(str, "epoch_ms") == 0 ||
+ strcasecmp(str, "epoch_millis") == 0 ||
+ strcasecmp(str, "epoch_milliseconds") == 0) {
+ return FLB_PACK_JSON_DATE_EPOCH_MS;
+ }
+
+ return -1;
+}
+
+
+static int msgpack_pack_formatted_datetime(flb_sds_t out_buf, char time_formatted[], int max_len,
+ msgpack_packer* tmp_pck, struct flb_time* tms,
+ const char *date_format,
+ const char *time_format)
+{
+ int len;
+ size_t s;
+ struct tm tm;
+
+ gmtime_r(&tms->tm.tv_sec, &tm);
+
+ s = strftime(time_formatted, max_len,
+ date_format, &tm);
+ if (!s) {
+ flb_debug("strftime failed in flb_pack_msgpack_to_json_format");
+ return 1;
+ }
+
+ /* Format the time, use microsecond precision not nanoseconds */
+ max_len -= s;
+ len = snprintf(&time_formatted[s],
+ max_len,
+ time_format,
+ (uint64_t) tms->tm.tv_nsec / 1000);
+ if (len >= max_len) {
+ flb_debug("snprintf: %d >= %d in flb_pack_msgpack_to_json_format", len, max_len);
+ return 2;
+ }
+ s += len;
+ msgpack_pack_str(tmp_pck, s);
+ msgpack_pack_str_body(tmp_pck, time_formatted, s);
+ return 0;
+}
+
+flb_sds_t flb_pack_msgpack_to_json_format(const char *data, uint64_t bytes,
+ int json_format, int date_format,
+ flb_sds_t date_key)
+{
+ int i;
+ int ok = MSGPACK_UNPACK_SUCCESS;
+ int records = 0;
+ int map_size;
+ size_t off = 0;
+ char time_formatted[38];
+ flb_sds_t out_tmp;
+ flb_sds_t out_js;
+ flb_sds_t out_buf = NULL;
+ msgpack_unpacked result;
+ msgpack_object root;
+ msgpack_object map;
+ msgpack_sbuffer tmp_sbuf;
+ msgpack_packer tmp_pck;
+ msgpack_object *obj;
+ msgpack_object *k;
+ msgpack_object *v;
+ struct flb_time tms;
+
+ /* For json lines and streams mode we need a pre-allocated buffer */
+ if (json_format == FLB_PACK_JSON_FORMAT_LINES ||
+ json_format == FLB_PACK_JSON_FORMAT_STREAM) {
+ out_buf = flb_sds_create_size(bytes + bytes / 4);
+ if (!out_buf) {
+ flb_errno();
+ return NULL;
+ }
+ }
+
+ /* Create temporary msgpack buffer */
+ msgpack_sbuffer_init(&tmp_sbuf);
+ msgpack_packer_init(&tmp_pck, &tmp_sbuf, msgpack_sbuffer_write);
+
+ /*
+ * If the format is the original msgpack style of one big array,
+ * registrate the array, otherwise is not necessary. FYI, original format:
+ *
+ * [
+ * [timestamp, map],
+ * [timestamp, map],
+ * [T, M]...
+ * ]
+ */
+ if (json_format == FLB_PACK_JSON_FORMAT_JSON) {
+ records = flb_mp_count(data, bytes);
+ if (records <= 0) {
+ msgpack_sbuffer_destroy(&tmp_sbuf);
+ return NULL;
+ }
+ msgpack_pack_array(&tmp_pck, records);
+ }
+
+ msgpack_unpacked_init(&result);
+ while (msgpack_unpack_next(&result, data, bytes, &off) == ok) {
+ /* Each array must have two entries: time and record */
+ root = result.data;
+ if (root.type != MSGPACK_OBJECT_ARRAY) {
+ continue;
+ }
+ if (root.via.array.size != 2) {
+ continue;
+ }
+
+ /* Unpack time */
+ flb_time_pop_from_msgpack(&tms, &result, &obj);
+
+ /* Get the record/map */
+ map = root.via.array.ptr[1];
+ if (map.type != MSGPACK_OBJECT_MAP) {
+ continue;
+ }
+ map_size = map.via.map.size;
+
+ if (date_key != NULL) {
+ msgpack_pack_map(&tmp_pck, map_size + 1);
+ }
+ else {
+ msgpack_pack_map(&tmp_pck, map_size);
+ }
+
+ if (date_key != NULL) {
+ /* Append date key */
+ msgpack_pack_str(&tmp_pck, flb_sds_len(date_key));
+ msgpack_pack_str_body(&tmp_pck, date_key, flb_sds_len(date_key));
+
+ /* Append date value */
+ switch (date_format) {
+ case FLB_PACK_JSON_DATE_DOUBLE:
+ msgpack_pack_double(&tmp_pck, flb_time_to_double(&tms));
+ break;
+ case FLB_PACK_JSON_DATE_JAVA_SQL_TIMESTAMP:
+ if (msgpack_pack_formatted_datetime(out_buf, time_formatted, sizeof(time_formatted), &tmp_pck, &tms,
+ FLB_PACK_JSON_DATE_JAVA_SQL_TIMESTAMP_FMT, ".%06" PRIu64)) {
+ flb_sds_destroy(out_buf);
+ msgpack_sbuffer_destroy(&tmp_sbuf);
+ msgpack_unpacked_destroy(&result);
+ return NULL;
+ }
+ break;
+ case FLB_PACK_JSON_DATE_ISO8601:
+ if (msgpack_pack_formatted_datetime(out_buf, time_formatted, sizeof(time_formatted), &tmp_pck, &tms,
+ FLB_PACK_JSON_DATE_ISO8601_FMT, ".%06" PRIu64 "Z")) {
+ flb_sds_destroy(out_buf);
+ msgpack_sbuffer_destroy(&tmp_sbuf);
+ msgpack_unpacked_destroy(&result);
+ return NULL;
+ }
+ break;
+ case FLB_PACK_JSON_DATE_EPOCH:
+ msgpack_pack_uint64(&tmp_pck, (long long unsigned)(tms.tm.tv_sec));
+ break;
+ case FLB_PACK_JSON_DATE_EPOCH_MS:
+ msgpack_pack_uint64(&tmp_pck, flb_time_to_millisec(&tms));
+ break;
+ }
+ }
+
+ /* Append remaining keys/values */
+ for (i = 0; i < map_size; i++) {
+ k = &map.via.map.ptr[i].key;
+ v = &map.via.map.ptr[i].val;
+ msgpack_pack_object(&tmp_pck, *k);
+ msgpack_pack_object(&tmp_pck, *v);
+ }
+
+ /*
+ * If the format is the original msgpack style, just continue since
+ * we don't care about separator or JSON convertion at this point.
+ */
+ if (json_format == FLB_PACK_JSON_FORMAT_JSON) {
+ continue;
+ }
+
+ /*
+ * Here we handle two types of records concatenation:
+ *
+ * FLB_PACK_JSON_FORMAT_LINES: add breakline (\n) after each record
+ *
+ *
+ * {'ts':abc,'k1':1}
+ * {'ts':abc,'k1':2}
+ * {N}
+ *
+ * FLB_PACK_JSON_FORMAT_STREAM: no separators, e.g:
+ *
+ * {'ts':abc,'k1':1}{'ts':abc,'k1':2}{N}
+ */
+ if (json_format == FLB_PACK_JSON_FORMAT_LINES ||
+ json_format == FLB_PACK_JSON_FORMAT_STREAM) {
+
+ /* Encode current record into JSON in a temporary variable */
+ out_js = flb_msgpack_raw_to_json_sds(tmp_sbuf.data, tmp_sbuf.size);
+ if (!out_js) {
+ flb_sds_destroy(out_buf);
+ msgpack_sbuffer_destroy(&tmp_sbuf);
+ msgpack_unpacked_destroy(&result);
+ return NULL;
+ }
+
+ /*
+ * One map record has been converted, now append it to the
+ * outgoing out_buf sds variable.
+ */
+ out_tmp = flb_sds_cat(out_buf, out_js, flb_sds_len(out_js));
+ if (!out_tmp) {
+ flb_sds_destroy(out_js);
+ flb_sds_destroy(out_buf);
+ msgpack_sbuffer_destroy(&tmp_sbuf);
+ msgpack_unpacked_destroy(&result);
+ return NULL;
+ }
+
+ /* Release temporary json sds buffer */
+ flb_sds_destroy(out_js);
+
+ /* If a realloc happened, check the returned address */
+ if (out_tmp != out_buf) {
+ out_buf = out_tmp;
+ }
+
+ /* Append the breakline only for json lines mode */
+ if (json_format == FLB_PACK_JSON_FORMAT_LINES) {
+ out_tmp = flb_sds_cat(out_buf, "\n", 1);
+ if (!out_tmp) {
+ flb_sds_destroy(out_buf);
+ msgpack_sbuffer_destroy(&tmp_sbuf);
+ msgpack_unpacked_destroy(&result);
+ return NULL;
+ }
+ if (out_tmp != out_buf) {
+ out_buf = out_tmp;
+ }
+ }
+ msgpack_sbuffer_clear(&tmp_sbuf);
+ }
+ }
+
+ /* Release the unpacker */
+ msgpack_unpacked_destroy(&result);
+
+ /* Format to JSON */
+ if (json_format == FLB_PACK_JSON_FORMAT_JSON) {
+ out_buf = flb_msgpack_raw_to_json_sds(tmp_sbuf.data, tmp_sbuf.size);
+ msgpack_sbuffer_destroy(&tmp_sbuf);
+
+ if (!out_buf) {
+ return NULL;
+ }
+ }
+ else {
+ msgpack_sbuffer_destroy(&tmp_sbuf);
+ }
+
+ if (out_buf && flb_sds_len(out_buf) == 0) {
+ flb_sds_destroy(out_buf);
+ return NULL;
+ }
+
+ return out_buf;
+}
+
+/**
+ * convert msgpack to JSON string.
+ * This API is similar to snprintf.
+ * @param size Estimated length of json str.
+ * @param data The msgpack_unpacked data.
+ * @return success ? allocated json str ptr : NULL
+ */
+char *flb_msgpack_to_json_str(size_t size, const msgpack_object *obj)
+{
+ int ret;
+ char *buf = NULL;
+ char *tmp;
+
+ if (obj == NULL) {
+ return NULL;
+ }
+
+ if (size <= 0) {
+ size = 128;
+ }
+
+ buf = flb_malloc(size);
+ if (!buf) {
+ flb_errno();
+ return NULL;
+ }
+
+ while (1) {
+ ret = flb_msgpack_to_json(buf, size, obj);
+ if (ret <= 0) {
+ /* buffer is small. retry.*/
+ size += 128;
+ tmp = flb_realloc(buf, size);
+ if (tmp) {
+ buf = tmp;
+ }
+ else {
+ flb_free(buf);
+ flb_errno();
+ return NULL;
+ }
+ }
+ else {
+ break;
+ }
+ }
+
+ return buf;
+}
+
+int flb_pack_time_now(msgpack_packer *pck)
+{
+ int ret;
+ struct flb_time t;
+
+ flb_time_get(&t);
+ ret = flb_time_append_to_msgpack(&t, pck, 0);
+
+ return ret;
+}
+
+int flb_msgpack_expand_map(char *map_data, size_t map_size,
+ msgpack_object_kv **kv_arr, int kv_arr_len,
+ char** out_buf, int* out_size)
+{
+ msgpack_sbuffer sbuf;
+ msgpack_packer pck;
+ msgpack_unpacked result;
+ size_t off = 0;
+ char *ret_buf;
+ int map_num;
+ int i;
+ int len;
+
+ if (map_data == NULL){
+ return -1;
+ }
+
+ msgpack_unpacked_init(&result);
+ if ((i=msgpack_unpack_next(&result, map_data, map_size, &off)) !=
+ MSGPACK_UNPACK_SUCCESS ) {
+ msgpack_unpacked_destroy(&result);
+ return -1;
+ }
+ if (result.data.type != MSGPACK_OBJECT_MAP) {
+ msgpack_unpacked_destroy(&result);
+ return -1;
+ }
+
+ len = result.data.via.map.size;
+ map_num = kv_arr_len + len;
+
+ msgpack_sbuffer_init(&sbuf);
+ msgpack_packer_init(&pck, &sbuf, msgpack_sbuffer_write);
+ msgpack_pack_map(&pck, map_num);
+
+ for (i=0; i<len; i++) {
+ msgpack_pack_object(&pck, result.data.via.map.ptr[i].key);
+ msgpack_pack_object(&pck, result.data.via.map.ptr[i].val);
+ }
+ for (i=0; i<kv_arr_len; i++){
+ msgpack_pack_object(&pck, kv_arr[i]->key);
+ msgpack_pack_object(&pck, kv_arr[i]->val);
+ }
+ msgpack_unpacked_destroy(&result);
+
+ *out_size = sbuf.size;
+ ret_buf = flb_malloc(sbuf.size);
+ *out_buf = ret_buf;
+ if (*out_buf == NULL) {
+ flb_errno();
+ msgpack_sbuffer_destroy(&sbuf);
+ return -1;
+ }
+ memcpy(*out_buf, sbuf.data, sbuf.size);
+ msgpack_sbuffer_destroy(&sbuf);
+
+ return 0;
+}
+
+int flb_pack_init(struct flb_config *config)
+{
+ int ret;
+
+ if (config == NULL) {
+ return -1;
+ }
+ ret = flb_pack_set_null_as_nan(config->convert_nan_to_null);
+
+ return ret;
+}
diff --git a/fluent-bit/src/flb_pack_gelf.c b/fluent-bit/src/flb_pack_gelf.c
new file mode 100644
index 000000000..0aac9ee81
--- /dev/null
+++ b/fluent-bit/src/flb_pack_gelf.c
@@ -0,0 +1,826 @@
+/*-*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_pack.h>
+
+static flb_sds_t flb_msgpack_gelf_key(flb_sds_t *s, int in_array,
+ const char *prefix_key, int prefix_key_len,
+ int concat,
+ const char *key, int key_len)
+{
+ int i;
+ flb_sds_t tmp;
+ static char valid_char[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ int start_len, end_len;
+
+ if (in_array == FLB_FALSE) {
+ tmp = flb_sds_cat(*s, ", \"", 3);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+
+ if (prefix_key_len > 0) {
+ start_len = flb_sds_len(*s);
+
+ tmp = flb_sds_cat(*s, prefix_key, prefix_key_len);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+
+ end_len = flb_sds_len(*s);
+ for(i=start_len; i < end_len; i++) {
+ if (!valid_char[(unsigned char)(*s)[i]]) {
+ (*s)[i] = '_';
+ }
+ }
+ }
+
+ if (concat == FLB_TRUE) {
+ tmp = flb_sds_cat(*s, "_", 1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+
+ if (key_len > 0) {
+ start_len = flb_sds_len(*s);
+
+ tmp = flb_sds_cat(*s, key, key_len);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+
+ end_len = flb_sds_len(*s);
+ for(i=start_len; i < end_len; i++) {
+ if (!valid_char[(unsigned char)(*s)[i]]) {
+ (*s)[i] = '_';
+ }
+ }
+ }
+
+ if (in_array == FLB_FALSE) {
+ tmp = flb_sds_cat(*s, "\":", 2);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+ else {
+ tmp = flb_sds_cat(*s, "=", 1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+
+ return *s;
+}
+
+static flb_sds_t flb_msgpack_gelf_value(flb_sds_t *s, int quote,
+ const char *val, int val_len)
+{
+ flb_sds_t tmp;
+
+ if (quote == FLB_TRUE) {
+ tmp = flb_sds_cat(*s, "\"", 1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+
+ if (val_len > 0) {
+ tmp = flb_sds_cat_utf8(s, val, val_len);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+
+ tmp = flb_sds_cat(*s, "\"", 1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+ else {
+ tmp = flb_sds_cat(*s, val, val_len);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+
+ return *s;
+}
+
+static flb_sds_t flb_msgpack_gelf_value_ext(flb_sds_t *s, int quote,
+ const char *val, int val_len)
+{
+ static const char int2hex[] = "0123456789abcdef";
+ flb_sds_t tmp;
+
+ if (quote == FLB_TRUE) {
+ tmp = flb_sds_cat(*s, "\"", 1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+ /* ext body. fortmat is similar to printf(1) */
+ {
+ int i;
+ char temp[5];
+ for(i=0; i < val_len; i++) {
+ char c = (char)val[i];
+ temp[0] = '\\';
+ temp[1] = 'x';
+ temp[2] = int2hex[ (unsigned char) ((c & 0xf0) >> 4)];
+ temp[3] = int2hex[ (unsigned char) (c & 0x0f)];
+ temp[4] = '\0';
+ tmp = flb_sds_cat(*s, temp, 4);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+ }
+ if (quote == FLB_TRUE) {
+ tmp = flb_sds_cat(*s, "\"", 1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+
+ return *s;
+}
+
+static flb_sds_t flb_msgpack_gelf_flatten(flb_sds_t *s, msgpack_object *o,
+ const char *prefix, int prefix_len,
+ int in_array)
+{
+ int i;
+ int loop;
+ flb_sds_t tmp;
+
+ switch(o->type) {
+ case MSGPACK_OBJECT_NIL:
+ tmp = flb_sds_cat(*s, "null", 4);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ break;
+
+ case MSGPACK_OBJECT_BOOLEAN:
+ if (o->via.boolean) {
+ tmp = flb_msgpack_gelf_value(s, !in_array, "true", 4);
+ }
+ else {
+ tmp = flb_msgpack_gelf_value(s, !in_array, "false", 5);
+ }
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ break;
+
+ case MSGPACK_OBJECT_POSITIVE_INTEGER:
+ tmp = flb_sds_printf(s, "%lu", (unsigned long)o->via.u64);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ break;
+
+ case MSGPACK_OBJECT_NEGATIVE_INTEGER:
+ tmp = flb_sds_printf(s, "%ld", (signed long)o->via.i64);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ break;
+
+ case MSGPACK_OBJECT_FLOAT32:
+ case MSGPACK_OBJECT_FLOAT64:
+ tmp = flb_sds_printf(s, "%f", o->via.f64);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ break;
+
+ case MSGPACK_OBJECT_STR:
+ tmp = flb_msgpack_gelf_value(s, !in_array,
+ o->via.str.ptr,
+ o->via.str.size);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ break;
+
+ case MSGPACK_OBJECT_BIN:
+ tmp = flb_msgpack_gelf_value(s, !in_array,
+ o->via.bin.ptr,
+ o->via.bin.size);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ break;
+
+ case MSGPACK_OBJECT_EXT:
+ tmp = flb_msgpack_gelf_value_ext(s, !in_array,
+ o->via.ext.ptr,
+ o->via.ext.size);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ break;
+
+ case MSGPACK_OBJECT_ARRAY:
+ loop = o->via.array.size;
+
+ if (!in_array) {
+ tmp = flb_sds_cat(*s, "\"", 1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+ if (loop != 0) {
+ msgpack_object* p = o->via.array.ptr;
+ for (i=0; i<loop; i++) {
+ if (i > 0) {
+ tmp = flb_sds_cat(*s, ", ", 2);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+ tmp = flb_msgpack_gelf_flatten(s, p+i,
+ prefix, prefix_len,
+ FLB_TRUE);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+ }
+
+ if (!in_array) {
+ tmp = flb_sds_cat(*s, "\"", 1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+ break;
+
+ case MSGPACK_OBJECT_MAP:
+ loop = o->via.map.size;
+ if (loop != 0) {
+ msgpack_object_kv *p = o->via.map.ptr;
+ for (i = 0; i < loop; i++) {
+ msgpack_object *k = &((p+i)->key);
+ msgpack_object *v = &((p+i)->val);
+
+ if (k->type != MSGPACK_OBJECT_STR) {
+ continue;
+ }
+
+ const char *key = k->via.str.ptr;
+ int key_len = k->via.str.size;
+
+ if (v->type == MSGPACK_OBJECT_MAP) {
+ char *obj_prefix = NULL;
+ int obj_prefix_len = 0;
+
+ obj_prefix_len = key_len;
+ if (prefix_len > 0) {
+ obj_prefix_len += prefix_len + 1;
+ }
+
+ obj_prefix = flb_malloc(obj_prefix_len + 1);
+ if (obj_prefix == NULL) {
+ return NULL;
+ }
+
+ if (prefix_len > 0) {
+ memcpy(obj_prefix, prefix, prefix_len);
+ obj_prefix[prefix_len] = '_';
+ memcpy(obj_prefix + prefix_len + 1, key, key_len);
+ }
+ else {
+ memcpy(obj_prefix, key, key_len);
+ }
+ obj_prefix[obj_prefix_len] = '\0';
+
+ tmp = flb_msgpack_gelf_flatten(s, v,
+ obj_prefix, obj_prefix_len,
+ in_array);
+ if (tmp == NULL) {
+ flb_free(obj_prefix);
+ return NULL;
+ }
+ *s = tmp;
+
+ flb_free(obj_prefix);
+ }
+ else {
+ if (in_array == FLB_TRUE && i > 0) {
+ tmp = flb_sds_cat(*s, " ", 1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+ if (in_array && prefix_len <= 0) {
+ tmp = flb_msgpack_gelf_key(s, in_array,
+ NULL, 0,
+ FLB_FALSE,
+ key, key_len);
+ }
+ else {
+ tmp = flb_msgpack_gelf_key(s, in_array,
+ prefix, prefix_len,
+ FLB_TRUE,
+ key, key_len);
+ }
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+
+ tmp = flb_msgpack_gelf_flatten(s, v, NULL, 0, in_array);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+ }
+ }
+ break;
+
+ default:
+ flb_warn("[%s] unknown msgpack type %i", __FUNCTION__, o->type);
+ }
+
+ return *s;
+}
+
+flb_sds_t flb_msgpack_to_gelf(flb_sds_t *s, msgpack_object *o,
+ struct flb_time *tm,
+ struct flb_gelf_fields *fields)
+{
+ int i;
+ int loop;
+ flb_sds_t tmp;
+
+ int host_key_found = FLB_FALSE;
+ int timestamp_key_found = FLB_FALSE;
+ int level_key_found = FLB_FALSE;
+ int short_message_key_found = FLB_FALSE;
+ int full_message_key_found = FLB_FALSE;
+
+ char *host_key = NULL;
+ char *timestamp_key = NULL;
+ char *level_key = NULL;
+ char *short_message_key = NULL;
+ char *full_message_key = NULL;
+
+ int host_key_len = 0;
+ int timestamp_key_len = false;
+ int level_key_len = 0;
+ int short_message_key_len = 0;
+ int full_message_key_len = 0;
+
+ if (s == NULL || o == NULL) {
+ return NULL;
+ }
+
+ /* Make sure the incoming object is a map */
+ if (o->type != MSGPACK_OBJECT_MAP) {
+ return NULL;
+ }
+
+ if (fields != NULL && fields->host_key != NULL) {
+ host_key = fields->host_key;
+ host_key_len = flb_sds_len(fields->host_key);
+ }
+ else {
+ host_key = "host";
+ host_key_len = 4;
+ }
+
+ if (fields != NULL && fields->timestamp_key != NULL) {
+ timestamp_key = fields->timestamp_key;
+ timestamp_key_len = flb_sds_len(fields->timestamp_key);
+ }
+ else {
+ timestamp_key = "timestamp";
+ timestamp_key_len = 9;
+ }
+
+ if (fields != NULL && fields->level_key != NULL) {
+ level_key = fields->level_key;
+ level_key_len = flb_sds_len(fields->level_key);
+ }
+ else {
+ level_key = "level";
+ level_key_len = 5;
+ }
+
+ if (fields != NULL && fields->short_message_key != NULL) {
+ short_message_key = fields->short_message_key;
+ short_message_key_len = flb_sds_len(fields->short_message_key);
+ }
+ else {
+ short_message_key = "short_message";
+ short_message_key_len = 13;
+ }
+
+ if (fields != NULL && fields->full_message_key != NULL) {
+ full_message_key = fields->full_message_key;
+ full_message_key_len = flb_sds_len(fields->full_message_key);
+ }
+ else {
+ full_message_key = "full_message";
+ full_message_key_len = 12;
+ }
+
+ tmp = flb_sds_cat(*s, "{\"version\":\"1.1\"", 16);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+
+ loop = o->via.map.size;
+ if (loop != 0) {
+ msgpack_object_kv *p = o->via.map.ptr;
+
+ for (i = 0; i < loop; i++) {
+ const char *key = NULL;
+ int key_len;
+ const char *val = NULL;
+ int val_len = 0;
+ int quote = FLB_FALSE;
+ int custom_key = FLB_FALSE;
+
+ msgpack_object *k = &p[i].key;
+ msgpack_object *v = &p[i].val;
+ msgpack_object vtmp; // used when converting level value from string to int
+
+ if (k->type != MSGPACK_OBJECT_BIN && k->type != MSGPACK_OBJECT_STR) {
+ continue;
+ }
+
+ if (k->type == MSGPACK_OBJECT_STR) {
+ key = k->via.str.ptr;
+ key_len = k->via.str.size;
+ }
+ else {
+ key = k->via.bin.ptr;
+ key_len = k->via.bin.size;
+ }
+
+ if ((key_len == host_key_len) &&
+ !strncmp(key, host_key, host_key_len)) {
+ if (host_key_found == FLB_TRUE) {
+ continue;
+ }
+ host_key_found = FLB_TRUE;
+ key = "host";
+ key_len = 4;
+ }
+ else if ((key_len == short_message_key_len) &&
+ !strncmp(key, short_message_key, short_message_key_len)) {
+ if (short_message_key_found == FLB_TRUE) {
+ continue;
+ }
+ short_message_key_found = FLB_TRUE;
+ key = "short_message";
+ key_len = 13;
+ }
+ else if ((key_len == timestamp_key_len) &&
+ !strncmp(key, timestamp_key, timestamp_key_len)) {
+ if (timestamp_key_found == FLB_TRUE) {
+ continue;
+ }
+ timestamp_key_found = FLB_TRUE;
+ key = "timestamp";
+ key_len = 9;
+ }
+ else if ((key_len == level_key_len) &&
+ !strncmp(key, level_key, level_key_len )) {
+ if (level_key_found == FLB_TRUE) {
+ continue;
+ }
+ level_key_found = FLB_TRUE;
+ key = "level";
+ key_len = 5;
+ if (v->type == MSGPACK_OBJECT_POSITIVE_INTEGER) {
+ if ( v->via.u64 > 7 ) {
+ flb_warn("[flb_msgpack_to_gelf] level is %" PRIu64 ", "
+ "but should be in 0..7 or a syslog keyword", v->via.u64);
+ }
+ }
+ else if (v->type == MSGPACK_OBJECT_STR) {
+ val = v->via.str.ptr;
+ val_len = v->via.str.size;
+ if (val_len == 1 && val[0] >= '0' && val[0] <= '7') {
+ v = &vtmp;
+ v->type = MSGPACK_OBJECT_POSITIVE_INTEGER;
+ v->via.u64 = (uint64_t)(val[0] - '0');
+ }
+ else {
+ int n;
+ char* allowed_levels[] = {
+ "emerg", "alert", "crit", "err",
+ "warning", "notice", "info", "debug",
+ NULL
+ };
+ for (n = 0; allowed_levels[n] != NULL; ++n) {
+ if (val_len == strlen(allowed_levels[n]) &&
+ !strncasecmp(val, allowed_levels[n], val_len)) {
+ v = &vtmp;
+ v->type = MSGPACK_OBJECT_POSITIVE_INTEGER;
+ v->via.u64 = (uint64_t)n;
+ break;
+ }
+ }
+ if (allowed_levels[n] == NULL) {
+ flb_warn("[flb_msgpack_to_gelf] level is '%.*s', "
+ "but should be in 0..7 or a syslog keyword", val_len, val);
+ }
+ }
+ }
+ else {
+ flb_error("[flb_msgpack_to_gelf] level must be a non-negative integer or a string");
+ return NULL;
+ }
+ }
+ else if ((key_len == full_message_key_len) &&
+ !strncmp(key, full_message_key, full_message_key_len)) {
+ if (full_message_key_found == FLB_TRUE) {
+ continue;
+ }
+ full_message_key_found = FLB_TRUE;
+ key = "full_message";
+ key_len = 12;
+ }
+ else if ((key_len == 2) && !strncmp(key, "id", 2)) {
+ /* _id key not allowed */
+ continue;
+ }
+ else {
+ custom_key = FLB_TRUE;
+ }
+
+ if (v->type == MSGPACK_OBJECT_MAP) {
+ char *prefix = NULL;
+ int prefix_len = 0;
+
+ prefix_len = key_len + 1;
+ prefix = flb_calloc(1, prefix_len + 1);
+ if (prefix == NULL) {
+ return NULL;
+ }
+
+ prefix[0] = '_';
+ strncpy(prefix + 1, key, key_len);
+ prefix[prefix_len] = '\0';
+
+ tmp = flb_msgpack_gelf_flatten(s, v,
+ prefix, prefix_len, FLB_FALSE);
+ if (tmp == NULL) {
+ flb_free(prefix);
+ return NULL;
+ }
+ *s = tmp;
+ flb_free(prefix);
+
+ }
+ else if (v->type == MSGPACK_OBJECT_ARRAY) {
+ if (custom_key == FLB_TRUE) {
+ tmp = flb_msgpack_gelf_key(s, FLB_FALSE, "_", 1, FLB_FALSE,
+ key, key_len);
+ }
+ else {
+ tmp = flb_msgpack_gelf_key(s, FLB_FALSE, NULL, 0, FLB_FALSE,
+ key, key_len);
+ }
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+
+ tmp = flb_msgpack_gelf_flatten(s, v, NULL, 0, FLB_FALSE);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+ else {
+ char temp[48] = {0};
+ if (v->type == MSGPACK_OBJECT_NIL) {
+ val = "null";
+ val_len = 4;
+ continue;
+ }
+ else if (v->type == MSGPACK_OBJECT_BOOLEAN) {
+ quote = FLB_TRUE;
+ val = v->via.boolean ? "true" : "false";
+ val_len = v->via.boolean ? 4 : 5;
+ }
+ else if (v->type == MSGPACK_OBJECT_POSITIVE_INTEGER) {
+ val = temp;
+ val_len = snprintf(temp, sizeof(temp) - 1,
+ "%" PRIu64, v->via.u64);
+ /*
+ * Check if the value length is larger than our string.
+ * this is needed to avoid stack-based overflows.
+ */
+ if (val_len > sizeof(temp)) {
+ return NULL;
+ }
+ }
+ else if (v->type == MSGPACK_OBJECT_NEGATIVE_INTEGER) {
+ val = temp;
+ val_len = snprintf(temp, sizeof(temp) - 1,
+ "%" PRId64, v->via.i64);
+ /*
+ * Check if the value length is larger than our string.
+ * this is needed to avoid stack-based overflows.
+ */
+ if (val_len > sizeof(temp)) {
+ return NULL;
+ }
+ }
+ else if (v->type == MSGPACK_OBJECT_FLOAT) {
+ val = temp;
+ val_len = snprintf(temp, sizeof(temp) - 1,
+ "%f", v->via.f64);
+ /*
+ * Check if the value length is larger than our string.
+ * this is needed to avoid stack-based overflows.
+ */
+ if (val_len > sizeof(temp)) {
+ return NULL;
+ }
+ }
+ else if (v->type == MSGPACK_OBJECT_STR) {
+ /* String value */
+ quote = FLB_TRUE;
+ val = v->via.str.ptr;
+ val_len = v->via.str.size;
+ }
+ else if (v->type == MSGPACK_OBJECT_BIN) {
+ /* Bin value */
+ quote = FLB_TRUE;
+ val = v->via.bin.ptr;
+ val_len = v->via.bin.size;
+ }
+ else if (v->type == MSGPACK_OBJECT_EXT) {
+ quote = FLB_TRUE;
+ val = v->via.ext.ptr;
+ val_len = v->via.ext.size;
+ }
+
+ if (!val || !key) {
+ continue;
+ }
+
+ if (custom_key == FLB_TRUE) {
+ tmp = flb_msgpack_gelf_key(s, FLB_FALSE, "_", 1, FLB_FALSE,
+ key, key_len);
+ }
+ else {
+ tmp = flb_msgpack_gelf_key(s, FLB_FALSE, NULL, 0, FLB_FALSE,
+ key, key_len);
+ }
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+
+ if (v->type == MSGPACK_OBJECT_EXT) {
+ tmp = flb_msgpack_gelf_value_ext(s, quote, val, val_len);
+ }
+ else {
+ tmp = flb_msgpack_gelf_value(s, quote, val, val_len);
+ }
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+ }
+ }
+
+ if (timestamp_key_found == FLB_FALSE && tm != NULL) {
+ tmp = flb_msgpack_gelf_key(s, FLB_FALSE, NULL, 0, FLB_FALSE,
+ "timestamp", 9);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+
+ /* gelf supports milliseconds */
+ tmp = flb_sds_printf(s, "%li.%03lu",
+ (long)tm->tm.tv_sec, tm->tm.tv_nsec / 1000000);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+ }
+
+ if (short_message_key_found == FLB_FALSE) {
+ flb_error("[flb_msgpack_to_gelf] missing short_message key");
+ return NULL;
+ }
+
+ tmp = flb_sds_cat(*s, "}", 1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *s = tmp;
+
+ return *s;
+}
+
+flb_sds_t flb_msgpack_raw_to_gelf(char *buf, size_t buf_size,
+ struct flb_time *tm, struct flb_gelf_fields *fields)
+{
+ int ret;
+ size_t off = 0;
+ size_t gelf_size;
+ msgpack_unpacked result;
+ flb_sds_t s;
+ flb_sds_t tmp;
+
+ if (!buf || buf_size <= 0) {
+ return NULL;
+ }
+
+ msgpack_unpacked_init(&result);
+ ret = msgpack_unpack_next(&result, buf, buf_size, &off);
+ if (ret != MSGPACK_UNPACK_SUCCESS) {
+ msgpack_unpacked_destroy(&result);
+ return NULL;
+ }
+
+ gelf_size = (buf_size * 1.3);
+ s = flb_sds_create_size(gelf_size);
+ if (s == NULL) {
+ msgpack_unpacked_destroy(&result);
+ return NULL;
+ }
+
+ tmp = flb_msgpack_to_gelf(&s, &result.data, tm, fields);
+ if (tmp == NULL) {
+ flb_sds_destroy(s);
+ msgpack_unpacked_destroy(&result);
+ return NULL;
+ }
+ s = tmp;
+
+ msgpack_unpacked_destroy(&result);
+
+ return s;
+}
diff --git a/fluent-bit/src/flb_parser.c b/fluent-bit/src/flb_parser.c
new file mode 100644
index 000000000..4ccecc91b
--- /dev/null
+++ b/fluent-bit/src/flb_parser.c
@@ -0,0 +1,1304 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_parser.h>
+#include <fluent-bit/flb_parser_decoder.h>
+#include <fluent-bit/flb_time.h>
+#include <fluent-bit/flb_error.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_strptime.h>
+#include <fluent-bit/flb_env.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_config_format.h>
+#include <fluent-bit/multiline/flb_ml.h>
+#include <fluent-bit/multiline/flb_ml_parser.h>
+#include <fluent-bit/multiline/flb_ml_rule.h>
+
+#include <cfl/cfl.h>
+#include <cfl/cfl_kvlist.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <string.h>
+
+static inline uint32_t digits10(uint64_t v) {
+ if (v < 10) return 1;
+ if (v < 100) return 2;
+ if (v < 1000) return 3;
+ if (v < 1000000000000UL) {
+ if (v < 100000000UL) {
+ if (v < 1000000) {
+ if (v < 10000) return 4;
+ return 5 + (v >= 100000);
+ }
+ return 7 + (v >= 10000000UL);
+ }
+ if (v < 10000000000UL) {
+ return 9 + (v >= 1000000000UL);
+ }
+ return 11 + (v >= 100000000000UL);
+ }
+ return 12 + digits10(v / 1000000000000UL);
+}
+
+static unsigned u64_to_str(uint64_t value, char* dst) {
+ static const char digits[201] =
+ "0001020304050607080910111213141516171819"
+ "2021222324252627282930313233343536373839"
+ "4041424344454647484950515253545556575859"
+ "6061626364656667686970717273747576777879"
+ "8081828384858687888990919293949596979899";
+ uint32_t const length = digits10(value);
+ uint32_t next = length - 1;
+ while (value >= 100) {
+ int const i = (value % 100) * 2;
+ value /= 100;
+ dst[next] = digits[i + 1];
+ dst[next - 1] = digits[i];
+ next -= 2;
+ }
+
+ /* Handle last 1-2 digits */
+ if (value < 10) {
+ dst[next] = '0' + (uint32_t) value;
+ } else {
+ int i = (uint32_t) value * 2;
+ dst[next] = digits[i + 1];
+ dst[next - 1] = digits[i];
+ }
+ return length;
+}
+
+int flb_parser_regex_do(struct flb_parser *parser,
+ const char *buf, size_t length,
+ void **out_buf, size_t *out_size,
+ struct flb_time *out_time);
+
+int flb_parser_json_do(struct flb_parser *parser,
+ const char *buf, size_t length,
+ void **out_buf, size_t *out_size,
+ struct flb_time *out_time);
+
+int flb_parser_ltsv_do(struct flb_parser *parser,
+ const char *buf, size_t length,
+ void **out_buf, size_t *out_size,
+ struct flb_time *out_time);
+
+int flb_parser_logfmt_do(struct flb_parser *parser,
+ const char *buf, size_t length,
+ void **out_buf, size_t *out_size,
+ struct flb_time *out_time);
+
+/*
+ * This function is used to free all aspects of a parser
+ * which is provided by the caller of flb_create_parser.
+ * Specifically, this function frees all but parser.types and
+ * parser.decoders from a parser.
+ *
+ * This function is only to be used in parser creation routines.
+ */
+static void flb_interim_parser_destroy(struct flb_parser *parser)
+{
+ if (parser->type == FLB_PARSER_REGEX) {
+ flb_regex_destroy(parser->regex);
+ flb_free(parser->p_regex);
+ }
+
+ flb_free(parser->name);
+ if (parser->time_fmt) {
+ flb_free(parser->time_fmt);
+ }
+ if (parser->time_fmt_year) {
+ flb_free(parser->time_fmt_year);
+ }
+ if (parser->time_fmt_full) {
+ flb_free(parser->time_fmt_full);
+ }
+ if (parser->time_key) {
+ flb_free(parser->time_key);
+ }
+
+ mk_list_del(&parser->_head);
+ flb_free(parser);
+}
+
+struct flb_parser *flb_parser_create(const char *name, const char *format,
+ const char *p_regex,
+ int skip_empty,
+ const char *time_fmt, const char *time_key,
+ const char *time_offset,
+ int time_keep,
+ int time_strict,
+ int logfmt_no_bare_keys,
+ struct flb_parser_types *types,
+ int types_len,
+ struct mk_list *decoders,
+ struct flb_config *config)
+{
+ int ret;
+ int len;
+ int diff = 0;
+ int size;
+ int is_epoch = FLB_FALSE;
+ char *tmp;
+ char *timeptr;
+ struct mk_list *head;
+ struct flb_parser *p;
+ struct flb_regex *regex;
+
+ /* Iterate current parsers and make sure the new one don't exists */
+ mk_list_foreach(head, &config->parsers) {
+ p = mk_list_entry(head, struct flb_parser, _head);
+ if (p->name && strcmp(p->name, name) == 0) {
+ flb_error("[parser] parser named '%s' already exists, skip.",
+ name);
+ return NULL;
+ }
+ }
+
+ /* Allocate context */
+ p = flb_calloc(1, sizeof(struct flb_parser));
+ if (!p) {
+ flb_errno();
+ return NULL;
+ }
+ p->decoders = decoders;
+ mk_list_add(&p->_head, &config->parsers);
+
+ /* Format lookup */
+ if (strcasecmp(format, "regex") == 0) {
+ p->type = FLB_PARSER_REGEX;
+ }
+ else if (strcasecmp(format, "json") == 0) {
+ p->type = FLB_PARSER_JSON;
+ }
+ else if (strcasecmp(format, "ltsv") == 0) {
+ p->type = FLB_PARSER_LTSV;
+ }
+ else if (strcasecmp(format, "logfmt") == 0) {
+ p->type = FLB_PARSER_LOGFMT;
+ }
+ else {
+ flb_error("[parser:%s] Invalid format %s", name, format);
+ mk_list_del(&p->_head);
+ flb_free(p);
+ return NULL;
+ }
+
+ if (p->type == FLB_PARSER_REGEX) {
+ if (!p_regex) {
+ flb_error("[parser:%s] Invalid regex pattern", name);
+ mk_list_del(&p->_head);
+ flb_free(p);
+ return NULL;
+ }
+
+ regex = flb_regex_create(p_regex);
+ if (!regex) {
+ flb_error("[parser:%s] Invalid regex pattern %s", name, p_regex);
+ mk_list_del(&p->_head);
+ flb_free(p);
+ return NULL;
+ }
+ p->regex = regex;
+ p->skip_empty = skip_empty;
+ p->p_regex = flb_strdup(p_regex);
+ }
+
+ p->name = flb_strdup(name);
+
+ if (time_fmt) {
+ p->time_fmt_full = flb_strdup(time_fmt);
+ if (!p->time_fmt_full) {
+ flb_error("[parser:%s] could not duplicate time fmt full", name);
+ flb_interim_parser_destroy(p);
+ return NULL;
+ }
+ p->time_fmt = flb_strdup(time_fmt);
+ if (!p->time_fmt) {
+ flb_error("[parser:%s] could not duplicate time fmt", name);
+ flb_interim_parser_destroy(p);
+ return NULL;
+ }
+
+ /* Check if the format is considering the year */
+ if (strstr(p->time_fmt, "%Y") || strstr(p->time_fmt, "%y")) {
+ p->time_with_year = FLB_TRUE;
+ }
+ else if (strstr(p->time_fmt, "%s")) {
+ is_epoch = FLB_TRUE;
+ p->time_with_year = FLB_TRUE;
+ }
+ else {
+ size = strlen(p->time_fmt);
+ p->time_with_year = FLB_FALSE;
+ p->time_fmt_year = flb_malloc(size + 4);
+ if (!p->time_fmt_year) {
+ flb_errno();
+ flb_interim_parser_destroy(p);
+ return NULL;
+ }
+
+ /* Append the year at the beginning */
+ tmp = p->time_fmt_year;
+ *tmp++ = '%';
+ *tmp++ = 'Y';
+ *tmp++ = ' ';
+
+ memcpy(tmp, p->time_fmt, size);
+ tmp += size;
+ *tmp++ = '\0';
+ }
+
+ /* Check if the format contains a timezone (%z) */
+ if (strstr(p->time_fmt, "%z") || strstr(p->time_fmt, "%Z") ||
+ strstr(p->time_fmt, "%SZ") || strstr(p->time_fmt, "%S.%LZ")) {
+#if defined(FLB_HAVE_GMTOFF) || !defined(FLB_HAVE_SYSTEM_STRPTIME)
+ p->time_with_tz = FLB_TRUE;
+#else
+ flb_error("[parser] timezone offset not supported");
+ flb_error("[parser] you cannot use %%z/%%Z on this platform");
+ flb_interim_parser_destroy(p);
+ return NULL;
+#endif
+ }
+
+ /*
+ * Check if the format expect fractional seconds
+ *
+ * Since strptime(3) does not support fractional seconds, this
+ * requires a workaround/hack in our parser. This is a known
+ * issue and addressed in different ways in other languages.
+ *
+ * The following links are a good reference:
+ *
+ * - http://stackoverflow.com/questions/7114690/how-to-parse-syslog-timestamp
+ * - http://code.activestate.com/lists/python-list/521885
+ */
+ if (is_epoch == FLB_TRUE || p->time_with_year == FLB_TRUE) {
+ timeptr = p->time_fmt;
+ }
+ else {
+ timeptr = p->time_fmt_year;
+ }
+
+ tmp = strstr(timeptr, "%L");
+ if (tmp) {
+ tmp[0] = '\0';
+ tmp[1] = '\0';
+ p->time_frac_secs = (tmp + 2);
+ }
+
+ /* Optional fixed timezone offset */
+ if (time_offset) {
+ diff = 0;
+ len = strlen(time_offset);
+ ret = flb_parser_tzone_offset(time_offset, len, &diff);
+ if (ret == -1) {
+ flb_interim_parser_destroy(p);
+ return NULL;
+ }
+ p->time_offset = diff;
+ }
+ }
+
+ if (time_key) {
+ p->time_key = flb_strdup(time_key);
+ }
+
+ p->time_keep = time_keep;
+ p->time_strict = time_strict;
+ p->logfmt_no_bare_keys = logfmt_no_bare_keys;
+ p->types = types;
+ p->types_len = types_len;
+ return p;
+}
+
+void flb_parser_destroy(struct flb_parser *parser)
+{
+ int i = 0;
+
+ if (parser->type == FLB_PARSER_REGEX) {
+ flb_regex_destroy(parser->regex);
+ flb_free(parser->p_regex);
+ }
+
+ flb_free(parser->name);
+ if (parser->time_fmt) {
+ flb_free(parser->time_fmt);
+ flb_free(parser->time_fmt_full);
+ }
+ if (parser->time_fmt_year) {
+ flb_free(parser->time_fmt_year);
+ }
+ if (parser->time_key) {
+ flb_free(parser->time_key);
+ }
+ if (parser->types_len != 0) {
+ for (i=0; i<parser->types_len; i++){
+ flb_free(parser->types[i].key);
+ }
+ flb_free(parser->types);
+ }
+
+ if (parser->decoders) {
+ flb_parser_decoder_list_destroy(parser->decoders);
+ }
+
+ mk_list_del(&parser->_head);
+ flb_free(parser);
+}
+
+void flb_parser_exit(struct flb_config *config)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_parser *parser;
+
+ /* release 'parsers' */
+ mk_list_foreach_safe(head, tmp, &config->parsers) {
+ parser = mk_list_entry(head, struct flb_parser, _head);
+ flb_parser_destroy(parser);
+ }
+
+ /* release 'multiline parsers' */
+ flb_ml_exit(config);
+}
+
+static int proc_types_str(const char *types_str, struct flb_parser_types **types)
+{
+ int i = 0;
+ int types_num = 0;
+ char *type_str = NULL;
+ size_t len;
+ struct mk_list *split;
+ struct mk_list *head;
+ struct flb_split_entry *sentry;
+
+ split = flb_utils_split(types_str, ' ', 256);
+ types_num = mk_list_size(split);
+ *types = flb_malloc(sizeof(struct flb_parser_types) * types_num);
+
+ for(i=0; i<types_num; i++){
+ (*types)[i].key = NULL;
+ (*types)[i].type = FLB_PARSER_TYPE_STRING;
+ }
+ i = 0;
+ mk_list_foreach(head ,split) {
+ sentry = mk_list_entry(head, struct flb_split_entry ,_head);
+ type_str = strchr(sentry->value ,':');
+
+ if (type_str == NULL) {
+ i++;
+ continue;
+ }
+ len = type_str - sentry->value;
+ (*types)[i].key = flb_strndup(sentry->value, len);
+ (*types)[i].key_len = len;
+
+ type_str++;
+ if (!strcasecmp(type_str, "integer")) {
+ (*types)[i].type = FLB_PARSER_TYPE_INT;
+ }
+ else if(!strcasecmp(type_str, "bool")) {
+ (*types)[i].type = FLB_PARSER_TYPE_BOOL;
+ }
+ else if(!strcasecmp(type_str, "float")){
+ (*types)[i].type = FLB_PARSER_TYPE_FLOAT;
+ }
+ else if(!strcasecmp(type_str, "hex")){
+ (*types)[i].type = FLB_PARSER_TYPE_HEX;
+ }
+ else {
+ (*types)[i].type = FLB_PARSER_TYPE_STRING;
+ }
+ i++;
+ }
+ flb_utils_split_free(split);
+
+ return i;
+}
+
+static flb_sds_t get_parser_key(struct flb_config *config,
+ struct flb_cf *cf, struct flb_cf_section *s,
+ char *key)
+
+{
+ flb_sds_t tmp;
+ flb_sds_t val;
+
+ tmp = flb_cf_section_property_get_string(cf, s, key);
+ if (!tmp) {
+ return NULL;
+ }
+
+ val = flb_env_var_translate(config->env, tmp);
+ if (!val) {
+ flb_sds_destroy(tmp);
+ return NULL;
+ }
+
+ if (flb_sds_len(val) == 0) {
+ flb_sds_destroy(val);
+ flb_sds_destroy(tmp);
+ return NULL;
+ }
+
+ flb_sds_destroy(tmp);
+ return val;
+}
+
+/* Config file: read 'parser' definitions */
+static int parser_conf_file(const char *cfg, struct flb_cf *cf,
+ struct flb_config *config)
+{
+ int i = 0;
+ flb_sds_t name;
+ flb_sds_t format;
+ flb_sds_t regex;
+ flb_sds_t time_fmt;
+ flb_sds_t time_key;
+ flb_sds_t time_offset;
+ flb_sds_t types_str;
+ flb_sds_t tmp_str;
+ int skip_empty;
+ int time_keep;
+ int time_strict;
+ int logfmt_no_bare_keys;
+ int types_len;
+ struct mk_list *head;
+ struct mk_list *decoders = NULL;
+ struct flb_cf_section *s;
+ struct flb_parser_types *types = NULL;
+
+ /* Read all 'parser' sections */
+ mk_list_foreach(head, &cf->parsers) {
+ name = NULL;
+ format = NULL;
+ regex = NULL;
+ time_fmt = NULL;
+ time_key = NULL;
+ time_offset = NULL;
+ types_str = NULL;
+ tmp_str = NULL;
+
+ /* retrieve the section context */
+ s = mk_list_entry(head, struct flb_cf_section, _head_section);
+
+ /* name */
+ name = get_parser_key(config, cf, s, "name");
+ if (!name) {
+ flb_error("[parser] no parser 'name' found in file '%s'", cfg);
+ goto fconf_early_error;
+ }
+
+ /* format */
+ format = get_parser_key(config, cf, s, "format");
+ if (!format) {
+ flb_error("[parser] no parser 'format' found for '%s' in file '%s'",
+ name, cfg);
+ goto fconf_early_error;
+ }
+
+ /* regex (if 'format' == 'regex') */
+ regex = get_parser_key(config, cf, s, "regex");
+ if (!regex && strcmp(format, "regex") == 0) {
+ flb_error("[parser] no parser 'regex' found for '%s' in file '%s",
+ name, cfg);
+ goto fconf_early_error;
+ }
+
+ /* skip_empty_values */
+ skip_empty = FLB_TRUE;
+ tmp_str = get_parser_key(config, cf, s, "skip_empty_values");
+ if (tmp_str) {
+ skip_empty = flb_utils_bool(tmp_str);
+ flb_sds_destroy(tmp_str);
+ }
+
+ /* time_format */
+ time_fmt = get_parser_key(config, cf, s, "time_format");
+
+ /* time_key */
+ time_key = get_parser_key(config, cf, s, "time_key");
+
+ /* time_keep */
+ time_keep = FLB_FALSE;
+ tmp_str = get_parser_key(config, cf, s, "time_keep");
+ if (tmp_str) {
+ time_keep = flb_utils_bool(tmp_str);
+ flb_sds_destroy(tmp_str);
+ }
+
+ /* time_strict */
+ time_strict = FLB_TRUE;
+ tmp_str = get_parser_key(config, cf, s, "time_strict");
+ if (tmp_str) {
+ time_strict = flb_utils_bool(tmp_str);
+ flb_sds_destroy(tmp_str);
+ }
+
+ /* time_offset (UTC offset) */
+ time_offset = get_parser_key(config, cf, s, "time_offset");
+
+ /* logfmt_no_bare_keys */
+ logfmt_no_bare_keys = FLB_FALSE;
+ tmp_str = get_parser_key(config, cf, s, "logfmt_no_bare_keys");
+ if (tmp_str) {
+ logfmt_no_bare_keys = flb_utils_bool(tmp_str);
+ flb_sds_destroy(tmp_str);
+ }
+
+ /* types */
+ types_str = get_parser_key(config, cf, s, "types");
+ if (types_str) {
+ types_len = proc_types_str(types_str, &types);
+ }
+ else {
+ types_len = 0;
+ }
+
+ /* Decoders */
+ decoders = flb_parser_decoder_list_create(s);
+
+ /* Create the parser context */
+ if (!flb_parser_create(name, format, regex, skip_empty,
+ time_fmt, time_key, time_offset, time_keep, time_strict,
+ logfmt_no_bare_keys, types, types_len, decoders, config)) {
+ goto fconf_error;
+ }
+
+ flb_debug("[parser] new parser registered: %s", name);
+
+ flb_sds_destroy(name);
+ flb_sds_destroy(format);
+
+ if (regex) {
+ flb_sds_destroy(regex);
+ }
+ if (time_fmt) {
+ flb_sds_destroy(time_fmt);
+ }
+ if (time_key) {
+ flb_sds_destroy(time_key);
+ }
+ if (time_offset) {
+ flb_sds_destroy(time_offset);
+ }
+ if (types_str) {
+ flb_sds_destroy(types_str);
+ }
+ decoders = NULL;
+ }
+
+ return 0;
+
+ /* Use early exit before call to flb_parser_create */
+ fconf_early_error:
+ if (name) {
+ flb_sds_destroy(name);
+ }
+ if (format) {
+ flb_sds_destroy(format);
+ }
+ if (regex) {
+ flb_sds_destroy(regex);
+ }
+ return -1;
+
+ fconf_error:
+ flb_sds_destroy(name);
+ flb_sds_destroy(format);
+ if (regex) {
+ flb_sds_destroy(regex);
+ }
+ if (time_fmt) {
+ flb_sds_destroy(time_fmt);
+ }
+ if (time_key) {
+ flb_sds_destroy(time_key);
+ }
+ if (time_offset) {
+ flb_sds_destroy(time_offset);
+ }
+ if (types_str) {
+ flb_sds_destroy(types_str);
+ }
+ if (types_len) {
+ for (i=0; i<types_len; i++){
+ if (types[i].key != NULL) {
+ flb_free(types[i].key);
+ }
+ }
+ flb_free(types);
+ }
+ if (decoders) {
+ flb_parser_decoder_list_destroy(decoders);
+ }
+ return -1;
+}
+
+static int multiline_load_regex_rules(struct flb_ml_parser *ml_parser,
+ struct flb_cf_section *section,
+ struct flb_config *config)
+{
+ int ret;
+ char *to_state = NULL;
+ struct mk_list list;
+ struct cfl_list *head;
+ struct cfl_kvpair *entry;
+ struct flb_slist_entry *from_state;
+ struct flb_slist_entry *regex_pattern;
+ struct flb_slist_entry *tmp;
+
+ cfl_list_foreach(head, &section->properties->list) {
+ entry = cfl_list_entry(head, struct cfl_kvpair, _head);
+
+ /* only process 'rule' keys */
+ if (strcasecmp(entry->key, "rule") != 0) {
+ continue;
+ }
+
+ mk_list_init(&list);
+ ret = flb_slist_split_tokens(&list, entry->val->data.as_string, 3);
+ if (ret == -1) {
+ flb_error("[multiline parser: %s] invalid section on key '%s'",
+ ml_parser->name, entry->key);
+ return -1;
+ }
+
+ /* Get entries from the line */
+ from_state = flb_slist_entry_get(&list, 0);
+ regex_pattern = flb_slist_entry_get(&list, 1);
+ tmp = flb_slist_entry_get(&list, 2);
+ if (tmp) {
+ to_state = tmp->str;
+ }
+ else {
+ to_state = NULL;
+ }
+
+ if (!from_state) {
+ flb_error("[multiline parser: %s] 'from_state' is mandatory",
+ ml_parser->name);
+ flb_slist_destroy(&list);
+ return -1;
+ }
+
+ if (!regex_pattern) {
+ flb_error("[multiline parser: %s] 'regex_pattern' is mandatory",
+ ml_parser->name);
+ flb_slist_destroy(&list);
+ return -1;
+ }
+
+ ret = flb_ml_rule_create(ml_parser,
+ from_state->str,
+ regex_pattern->str,
+ to_state,
+ NULL);
+ if (ret == -1) {
+ flb_error("[multiline parser: %s] error creating rule",
+ ml_parser->name);
+ flb_slist_destroy(&list);
+ return -1;
+ }
+
+ flb_slist_destroy(&list);
+ }
+
+ /* Map the rules (mandatory for regex rules) */
+ ret = flb_ml_parser_init(ml_parser);
+ if (ret != 0) {
+ flb_error("[multiline parser: %s] invalid mapping rules, check the states",
+ ml_parser->name);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/* config file: read 'multiline_parser' sections */
+static int multiline_parser_conf_file(const char *cfg, struct flb_cf *cf,
+ struct flb_config *config)
+{
+ int ret;
+ int type;
+ flb_sds_t name;
+ flb_sds_t match_string;
+ int negate;
+ flb_sds_t key_content;
+ flb_sds_t key_pattern;
+ flb_sds_t key_group;
+ flb_sds_t parser;
+ flb_sds_t tmp;
+ int flush_timeout;
+ struct flb_parser *parser_ctx = NULL;
+ struct mk_list *head;
+ struct flb_cf_section *s;
+ struct flb_ml_parser *ml_parser;
+
+ /* read all 'multiline_parser' sections */
+ mk_list_foreach(head, &cf->multiline_parsers) {
+ ml_parser = NULL;
+ name = NULL;
+ type = -1;
+ match_string = NULL;
+ negate = FLB_FALSE;
+ key_content = NULL;
+ key_pattern = NULL;
+ key_group = NULL;
+ parser = NULL;
+ flush_timeout = -1;
+ tmp = NULL;
+
+ s = mk_list_entry(head, struct flb_cf_section, _head_section);
+
+ /* name */
+ name = get_parser_key(config, cf, s, "name");
+ if (!name) {
+ flb_error("[multiline_parser] no 'name' defined in file '%s'", cfg);
+ goto fconf_error;
+ }
+
+ /* type */
+ tmp = get_parser_key(config, cf, s, "type");
+ if (!tmp) {
+ flb_error("[multiline_parser] no 'type' defined in file '%s'", cfg);
+ goto fconf_error;
+ }
+ else {
+ type = flb_ml_type_lookup(tmp);
+ if (type == -1) {
+ flb_error("[multiline_parser] invalid type '%s'", tmp);
+ goto fconf_error;
+ }
+ flb_sds_destroy(tmp);
+ }
+
+ /* match_string */
+ match_string = get_parser_key(config, cf, s, "match_string");
+
+ /* negate */
+ tmp = get_parser_key(config, cf, s, "negate");
+ if (tmp) {
+ negate = flb_utils_bool(tmp);
+ flb_sds_destroy(tmp);
+ }
+
+ /* key_content */
+ key_content = get_parser_key(config, cf, s, "key_content");
+
+ /* key_pattern */
+ key_pattern = get_parser_key(config, cf, s, "key_pattern");
+
+ /* key_group */
+ key_group = get_parser_key(config, cf, s, "key_group");
+
+ /* parser */
+ parser = get_parser_key(config, cf, s, "parser");
+
+ /* flush_timeout */
+ tmp = get_parser_key(config, cf, s, "flush_timeout");
+ if (tmp) {
+ flush_timeout = atoi(tmp);
+ }
+
+ if (parser) {
+ parser_ctx = flb_parser_get(parser, config);
+ }
+ ml_parser = flb_ml_parser_create(config, name, type, match_string,
+ negate, flush_timeout, key_content,
+ key_group, key_pattern,
+ parser_ctx, parser);
+ if (!ml_parser) {
+ goto fconf_error;
+ }
+
+ /* if type is regex, process rules */
+ if (type == FLB_ML_REGEX) {
+ ret = multiline_load_regex_rules(ml_parser, s, config);
+ if (ret != 0) {
+ goto fconf_error;
+ }
+ }
+
+ flb_sds_destroy(name);
+ flb_sds_destroy(match_string);
+ flb_sds_destroy(key_content);
+ flb_sds_destroy(key_pattern);
+ flb_sds_destroy(key_group);
+ flb_sds_destroy(parser);
+ flb_sds_destroy(tmp);
+ }
+
+ return 0;
+
+ fconf_error:
+ if (ml_parser) {
+ flb_ml_parser_destroy(ml_parser);
+ }
+ flb_sds_destroy(name);
+ flb_sds_destroy(match_string);
+ flb_sds_destroy(key_content);
+ flb_sds_destroy(key_pattern);
+ flb_sds_destroy(key_group);
+ flb_sds_destroy(parser);
+ flb_sds_destroy(tmp);
+
+ return -1;
+}
+
+int flb_parser_conf_file_stat(const char *file, struct flb_config *config)
+{
+ int ret;
+ struct stat st;
+
+ ret = stat(file, &st);
+ if (ret == -1 && errno == ENOENT) {
+ /* Try to resolve the real path (if exists) */
+ if (file[0] == '/') {
+ flb_utils_error(FLB_ERR_CFG_PARSER_FILE);
+ return -1;
+ }
+
+ if (config->conf_path) {
+ /* Handle as special case here. */
+ return -2;
+ }
+
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Load parsers from a configuration file */
+int flb_parser_conf_file(const char *file, struct flb_config *config)
+{
+ int ret;
+ char tmp[PATH_MAX + 1];
+ char *cfg = NULL;
+ struct flb_cf *cf = NULL;
+
+#ifndef FLB_HAVE_STATIC_CONF
+ ret = flb_parser_conf_file_stat(file, config);
+ if (ret == -1) {
+ return -1;
+ }
+ else if (ret == -2) {
+ snprintf(tmp, PATH_MAX, "%s%s", config->conf_path, file);
+ cfg = tmp;
+ }
+ else {
+ cfg = (char *) file;
+ }
+
+ cf = flb_cf_create_from_file(NULL, cfg);
+#else
+ cf = flb_config_static_open(file);
+#endif
+
+ if (!cf) {
+ return -1;
+ }
+
+ /* process 'parser' sections */
+ ret = parser_conf_file(cfg, cf, config);
+ if (ret == -1) {
+ flb_cf_destroy(cf);
+ return -1;
+ }
+
+ /* processs 'multiline_parser' sections */
+ ret = multiline_parser_conf_file(cfg, cf, config);
+ if (ret == -1) {
+ flb_cf_destroy(cf);
+ return -1;
+ }
+
+ /* link the 'cf parser' context to the config list */
+ mk_list_add(&cf->_head, &config->cf_parsers_list);
+ return 0;
+}
+
+struct flb_parser *flb_parser_get(const char *name, struct flb_config *config)
+{
+ struct mk_list *head;
+ struct flb_parser *parser;
+
+ if (config == NULL || mk_list_size(&config->parsers) <= 0) {
+ return NULL;
+ }
+
+ mk_list_foreach(head, &config->parsers) {
+ parser = mk_list_entry(head, struct flb_parser, _head);
+ if (parser == NULL || parser->name == NULL) {
+ continue;
+ }
+ if (strcmp(parser->name, name) == 0) {
+ return parser;
+ }
+ }
+
+ return NULL;
+}
+
+int flb_parser_do(struct flb_parser *parser, const char *buf, size_t length,
+ void **out_buf, size_t *out_size, struct flb_time *out_time)
+{
+
+ if (parser->type == FLB_PARSER_REGEX) {
+ return flb_parser_regex_do(parser, buf, length,
+ out_buf, out_size, out_time);
+ }
+ else if (parser->type == FLB_PARSER_JSON) {
+ return flb_parser_json_do(parser, buf, length,
+ out_buf, out_size, out_time);
+ }
+ else if (parser->type == FLB_PARSER_LTSV) {
+ return flb_parser_ltsv_do(parser, buf, length,
+ out_buf, out_size, out_time);
+ }
+ else if (parser->type == FLB_PARSER_LOGFMT) {
+ return flb_parser_logfmt_do(parser, buf, length,
+ out_buf, out_size, out_time);
+ }
+
+ return -1;
+}
+
+/* Given a timezone string, return it numeric offset */
+int flb_parser_tzone_offset(const char *str, int len, int *tmdiff)
+{
+ int neg;
+ long hour;
+ long min;
+ const char *end;
+ const char *p = str;
+
+ /* Check timezones */
+ if (*p == 'Z') {
+ /* This is UTC, no changes required */
+ *tmdiff = 0;
+ return 0;
+ }
+
+ /* Unexpected timezone string */
+ if (*p != '+' && *p != '-') {
+ *tmdiff = 0;
+ return -1;
+ }
+
+ /* Ensure there is enough data */
+ if (len < 4) {
+ *tmdiff = 0;
+ return -1;
+ }
+
+ /* Negative value ? */
+ neg = (*p++ == '-');
+
+ /* Locate end */
+ end = str + len;
+
+ /* Gather hours and minutes */
+ hour = ((p[0] - '0') * 10) + (p[1] - '0');
+ if (end - p == 5 && p[2] == ':') {
+ /* Ensure there is enough data */
+ if (len < 5) {
+ *tmdiff = 0;
+ return -1;
+ }
+ min = ((p[3] - '0') * 10) + (p[4] - '0');
+ }
+ else {
+ min = ((p[2] - '0') * 10) + (p[3] - '0');
+ }
+
+ if (hour < 0 || hour > 59 || min < 0 || min > 59) {
+ return -1;
+ }
+
+ *tmdiff = ((hour * 3600) + (min * 60));
+ if (neg) {
+ *tmdiff = -*tmdiff;
+ }
+
+ return 0;
+}
+
+/*
+ * Parse the '%L' (subseconds) part into `subsec`.
+ *
+ * 2020-10-23 12:00:31.415213 JST
+ * ----------
+ *
+ * Return the number of characters consumed, or -1 on error.
+ */
+static int parse_subseconds(char *str, int len, double *subsec)
+{
+ char buf[16];
+ char *end;
+ int consumed;
+ int digits = 9; /* 1 ns = 000000001 (9 digits) */
+
+ if (len < digits) {
+ digits = len;
+ }
+ memcpy(buf, "0.", 2);
+ memcpy(buf + 2, str, digits);
+ buf[digits + 2] = '\0';
+
+ *subsec = strtod(buf, &end);
+
+ consumed = end - buf - 2;
+ if (consumed <= 0) {
+ return -1;
+ }
+ return consumed;
+}
+
+int flb_parser_time_lookup(const char *time_str, size_t tsize,
+ time_t now,
+ struct flb_parser *parser,
+ struct flb_tm *tm, double *ns)
+{
+ int ret;
+ time_t time_now;
+ char *p = NULL;
+ char *fmt;
+ int time_len = tsize;
+ const char *time_ptr = time_str;
+ char tmp[64];
+ struct tm tmy;
+
+ *ns = 0;
+
+ if (tsize > sizeof(tmp) - 1) {
+ flb_error("[parser] time string length is too long");
+ return -1;
+ }
+
+ /*
+ * Some records coming from old Syslog messages do not contain the
+ * year, so it's required to ingest this information in the value
+ * to be parsed.
+ */
+ if (parser->time_with_year == FLB_FALSE) {
+ /* Given time string is too long */
+ if (time_len + 6 >= sizeof(tmp)) {
+ return -1;
+ }
+
+ /*
+ * This is not the most elegant way but for now it let
+ * get the work done.
+ */
+ if (now <= 0) {
+ time_now = time(NULL);
+ }
+ else {
+ time_now = now;
+ }
+
+ gmtime_r(&time_now, &tmy);
+
+ /* Make the timestamp default to today */
+ tm->tm.tm_mon = tmy.tm_mon;
+ tm->tm.tm_mday = tmy.tm_mday;
+
+ uint64_t t = tmy.tm_year + 1900;
+
+ fmt = tmp;
+ u64_to_str(t, fmt);
+ fmt += 4;
+ *fmt++ = ' ';
+
+ memcpy(fmt, time_ptr, time_len);
+ fmt += time_len;
+ *fmt++ = '\0';
+
+ time_ptr = tmp;
+ time_len = strlen(tmp);
+ p = flb_strptime(time_ptr, parser->time_fmt_year, tm);
+ }
+ else {
+ /*
+ * We must ensure string passed to flb_strptime is
+ * null-terminated, which time_ptr is not guaranteed
+ * to be. So we use tmp to hold our string.
+ */
+ if (time_len >= sizeof(tmp)) {
+ return -1;
+ }
+ memcpy(tmp, time_ptr, time_len);
+ tmp[time_len] = '\0';
+ time_ptr = tmp;
+ time_len = strlen(tmp);
+
+ p = flb_strptime(time_ptr, parser->time_fmt, tm);
+ }
+
+ if (p == NULL) {
+ if (parser->time_strict) {
+ flb_error("[parser] cannot parse '%.*s'", (int)tsize, time_str);
+ return -1;
+ }
+ flb_debug("[parser] non-exact match '%.*s'", (int)tsize, time_str);
+ return 0;
+ }
+
+ if (parser->time_frac_secs) {
+ ret = parse_subseconds(p, time_len - (p - time_ptr), ns);
+ if (ret < 0) {
+ if (parser->time_strict) {
+ flb_error("[parser] cannot parse %%L for '%.*s'", (int)tsize, time_str);
+ return -1;
+ }
+ flb_debug("[parser] non-exact match on %%L '%.*s'", (int)tsize, time_str);
+ return 0;
+ }
+ p += ret;
+
+ /* Parse the remaining part after %L */
+ p = flb_strptime(p, parser->time_frac_secs, tm);
+ if (p == NULL) {
+ if (parser->time_strict) {
+ flb_error("[parser] cannot parse '%.*s' after %%L", (int)tsize, time_str);
+ return -1;
+ }
+ flb_debug("[parser] non-exact match after %%L '%.*s'", (int)tsize, time_str);
+ return 0;
+ }
+ }
+
+ if (parser->time_with_tz == FLB_FALSE) {
+ flb_tm_gmtoff(tm) = parser->time_offset;
+ }
+
+ return 0;
+}
+
+int flb_parser_typecast(const char *key, int key_len,
+ const char *val, int val_len,
+ msgpack_packer *pck,
+ struct flb_parser_types *types,
+ int types_len)
+{
+ int i;
+ int error = FLB_FALSE;
+ char *tmp_str;
+ int casted = FLB_FALSE;
+
+ for(i=0; i<types_len; i++){
+ if (types[i].key != NULL
+ && key_len == types[i].key_len &&
+ !strncmp(key, types[i].key, key_len)) {
+
+ casted = FLB_TRUE;
+
+ msgpack_pack_str(pck, key_len);
+ msgpack_pack_str_body(pck, key, key_len);
+
+ switch (types[i].type) {
+ case FLB_PARSER_TYPE_INT:
+ {
+ long long lval;
+
+ /* msgpack char is not null terminated.
+ So make a temporary copy.
+ */
+ tmp_str = flb_strndup(val, val_len);
+ lval = atoll(tmp_str);
+ flb_free(tmp_str);
+ msgpack_pack_int64(pck, lval);
+ }
+ break;
+ case FLB_PARSER_TYPE_HEX:
+ {
+ unsigned long long lval;
+ tmp_str = flb_strndup(val, val_len);
+ lval = strtoull(tmp_str, NULL, 16);
+ flb_free(tmp_str);
+ msgpack_pack_uint64(pck, lval);
+ }
+ break;
+
+ case FLB_PARSER_TYPE_FLOAT:
+ {
+ double dval;
+ tmp_str = flb_strndup(val, val_len);
+ dval = atof(tmp_str);
+ flb_free(tmp_str);
+ msgpack_pack_double(pck, dval);
+ }
+ break;
+ case FLB_PARSER_TYPE_BOOL:
+ if (val_len >= 4 && !strncasecmp(val, "true", 4)) {
+ msgpack_pack_true(pck);
+ }
+ else if(val_len >= 5 && !strncasecmp(val, "false", 5)){
+ msgpack_pack_false(pck);
+ }
+ else {
+ error = FLB_TRUE;
+ }
+ break;
+ case FLB_PARSER_TYPE_STRING:
+ msgpack_pack_str(pck, val_len);
+ msgpack_pack_str_body(pck, val, val_len);
+ break;
+ default:
+ error = FLB_TRUE;
+ }
+ if (error == FLB_TRUE) {
+ /* We need to null-terminate key for flb_warn, as it expects
+ * a null-terminated string, which key is not guaranteed
+ * to be */
+ char *nt_key = flb_malloc(key_len + 1);
+ if (nt_key != NULL) {
+ memcpy(nt_key, key, key_len);
+ nt_key[key_len] = '\0';
+ flb_warn("[PARSER] key=%s cast error. save as string.", nt_key);
+ flb_free(nt_key);
+ }
+ msgpack_pack_str(pck, val_len);
+ msgpack_pack_str_body(pck, val, val_len);
+ }
+ break;
+ }
+ }
+
+ if (casted == FLB_FALSE) {
+ msgpack_pack_str(pck, key_len);
+ msgpack_pack_str_body(pck, key, key_len);
+ msgpack_pack_str(pck, val_len);
+ msgpack_pack_str_body(pck, val, val_len);
+ }
+ return 0;
+}
diff --git a/fluent-bit/src/flb_parser_decoder.c b/fluent-bit/src/flb_parser_decoder.c
new file mode 100644
index 000000000..ddcb950e0
--- /dev/null
+++ b/fluent-bit/src/flb_parser_decoder.c
@@ -0,0 +1,777 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_parser_decoder.h>
+#include <fluent-bit/flb_unescape.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_kv.h>
+
+#include <cfl/cfl.h>
+#include <cfl/cfl_kvlist.h>
+
+#include <msgpack.h>
+
+#define TYPE_OUT_STRING 0 /* unstructured text */
+#define TYPE_OUT_OBJECT 1 /* structured msgpack object */
+
+/* Decode a stringified JSON message */
+static int decode_json(struct flb_parser_dec *dec,
+ const char *in_buf, size_t in_size,
+ char **out_buf, size_t *out_size, int *out_type)
+{
+ int ret;
+ int root_type;
+ int records;
+ char *buf;
+ const char *p;
+ size_t size;
+ size_t len;
+
+ p = in_buf;
+ while (*p == ' ') p++;
+
+ len = in_size - (p - in_buf);
+
+ /* It must be a map or array */
+ if (p[0] != '{' && p[0] != '[') {
+ return -1;
+ }
+
+ ret = flb_pack_json_recs(p, len, &buf, &size, &root_type, &records, NULL);
+ if (ret != 0) {
+ return -1;
+ }
+
+ /* We expect to decode only one JSON element */
+ if (records != 1) {
+ flb_free(buf);
+ return -1;
+ }
+
+ /* Only process a packed JSON object */
+ if (root_type != FLB_PACK_JSON_OBJECT) {
+ flb_free(buf);
+ return -1;
+ }
+
+ *out_buf = buf;
+ *out_size = size;
+ *out_type = TYPE_OUT_OBJECT;
+
+ return 0;
+}
+
+static int decode_escaped(struct flb_parser_dec *dec,
+ const char *in_buf, size_t in_size,
+ char **out_buf, size_t *out_size, int *out_type)
+{
+ int len;
+
+ /* Unescape string */
+ len = flb_unescape_string(in_buf, in_size, &dec->buffer);
+ *out_buf = dec->buffer;
+ *out_size = len;
+ *out_type = TYPE_OUT_STRING;
+
+ return 0;
+}
+
+static int decode_escaped_utf8(struct flb_parser_dec *dec,
+ const char *in_buf, size_t in_size,
+ char **out_buf, size_t *out_size, int *out_type)
+{
+ int len;
+
+ len = flb_unescape_string_utf8(in_buf, in_size, dec->buffer);
+ *out_buf = dec->buffer;
+ *out_size = len;
+ *out_type = TYPE_OUT_STRING;
+
+ return 0;
+}
+
+static int decode_mysql_quoted(struct flb_parser_dec *dec,
+ char *in_buf, size_t in_size,
+ char **out_buf, size_t *out_size, int *out_type)
+{
+ int len;
+ if(in_size < 2) {
+ dec->buffer[0] = in_buf[0];
+ dec->buffer[1] = 0;
+ *out_buf = dec->buffer;
+ *out_size = in_size;
+ *out_type = TYPE_OUT_STRING;
+ }
+ else if(in_buf[0] == '\'' && in_buf[in_size-1] == '\'') {
+ len = flb_mysql_unquote_string(in_buf+1, in_size-2, &dec->buffer);
+ *out_buf = dec->buffer;
+ *out_size = len;
+ *out_type = TYPE_OUT_STRING;
+ }
+ else if(in_buf[0] == '\"' && in_buf[in_size-1] == '\"') {
+ len = flb_mysql_unquote_string(in_buf+1, in_size-2, &dec->buffer);
+ *out_buf = dec->buffer;
+ *out_size = len;
+ *out_type = TYPE_OUT_STRING;
+ }
+ else {
+ memcpy(dec->buffer, in_buf, in_size);
+ dec->buffer[in_size] = 0;
+ *out_buf = dec->buffer;
+ *out_size = in_size;
+ *out_type = TYPE_OUT_STRING;
+ }
+
+ return 0;
+}
+
+static int merge_record_and_extra_keys(const char *in_buf, size_t in_size,
+ const char *extra_buf, size_t extra_size,
+ char **out_buf, size_t *out_size)
+{
+ int i;
+ int ret;
+ int map_size = 0;
+ size_t in_off = 0;
+ size_t extra_off = 0;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+ msgpack_unpacked in_result;
+ msgpack_unpacked extra_result;
+ msgpack_object k;
+ msgpack_object v;
+ msgpack_object map;
+
+ msgpack_unpacked_init(&in_result);
+ msgpack_unpacked_init(&extra_result);
+
+ /* Check if the extra buffer have some serialized data */
+ ret = msgpack_unpack_next(&extra_result, extra_buf, extra_size, &extra_off);
+ if (ret != MSGPACK_UNPACK_SUCCESS) {
+ msgpack_unpacked_destroy(&in_result);
+ msgpack_unpacked_destroy(&extra_result);
+ return -1;
+ }
+ msgpack_unpack_next(&in_result, in_buf, in_size, &in_off);
+
+
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ map_size = in_result.data.via.map.size;
+ map_size += extra_result.data.via.map.size;
+
+ msgpack_pack_map(&mp_pck, map_size);
+ map = in_result.data;
+ for (i = 0; i < map.via.map.size; i++) {
+ k = map.via.map.ptr[i].key;
+ v = map.via.map.ptr[i].val;
+ msgpack_pack_object(&mp_pck, k);
+ msgpack_pack_object(&mp_pck, v);
+ }
+
+ map = extra_result.data;
+ for (i = 0; i < map.via.map.size; i++) {
+ k = map.via.map.ptr[i].key;
+ v = map.via.map.ptr[i].val;
+ msgpack_pack_object(&mp_pck, k);
+ msgpack_pack_object(&mp_pck, v);
+ }
+
+ msgpack_unpacked_destroy(&in_result);
+ msgpack_unpacked_destroy(&extra_result);
+
+ *out_buf = mp_sbuf.data;
+ *out_size = mp_sbuf.size;
+
+ return 0;
+}
+
+/*
+ * Given a msgpack map, apply the parser-decoder rules defined and generate
+ * a new msgpack buffer.
+ */
+int flb_parser_decoder_do(struct mk_list *decoders,
+ const char *in_buf, size_t in_size,
+ char **out_buf, size_t *out_size)
+{
+ int i;
+ int ret;
+ int matched;
+ int is_decoded;
+ int is_decoded_as;
+ int in_type;
+ int out_type;
+ int dec_type;
+ int extra_keys = FLB_FALSE;
+ size_t off = 0;
+ char *dec_buf;
+ size_t dec_size;
+ flb_sds_t tmp_sds = NULL;
+ flb_sds_t data_sds = NULL;
+ flb_sds_t in_sds = NULL;
+ flb_sds_t out_sds = NULL;
+ struct mk_list *head;
+ struct mk_list *r_head;
+ struct flb_parser_dec *dec = NULL;
+ struct flb_parser_dec_rule *rule;
+ msgpack_object k;
+ msgpack_object v;
+ msgpack_object map;
+ msgpack_unpacked result;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+ /* Contexts to handle extra keys to be appended at the end of the log */
+ msgpack_sbuffer extra_mp_sbuf;
+ msgpack_packer extra_mp_pck;
+
+ /* Initialize unpacker */
+ msgpack_unpacked_init(&result);
+ msgpack_unpack_next(&result, in_buf, in_size, &off);
+ map = result.data;
+
+ if (map.type != MSGPACK_OBJECT_MAP) {
+ msgpack_unpacked_destroy(&result);
+ return -1;
+ }
+
+ /*
+ * First check if any field in the record matches a decoder rule. It's
+ * better to check this before hand otherwise we need to jump directly
+ * to create a "possible new outgoing buffer".
+ */
+ matched = -1;
+ for (i = 0; i < map.via.map.size; i++) {
+ k = map.via.map.ptr[i].key;
+ if (k.type != MSGPACK_OBJECT_STR) {
+ continue;
+ }
+
+ /* Try to match this key name with decoder's rule */
+ mk_list_foreach(head, decoders) {
+ dec = mk_list_entry(head, struct flb_parser_dec, _head);
+ if (flb_sds_cmp(dec->key, k.via.str.ptr,
+ k.via.str.size) == 0) {
+ /* we have a match, stop the check */
+ matched = i;
+ break;
+ }
+ else {
+ matched = -1;
+ }
+ }
+
+ if (matched >= 0) {
+ break;
+ }
+ }
+
+ /* No matches, no need to continue */
+ if (matched == -1) {
+ msgpack_unpacked_destroy(&result);
+ return -1;
+ }
+
+ /* Create new outgoing buffer */
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ /* Register the map (same size) */
+ msgpack_pack_map(&mp_pck, map.via.map.size);
+
+ /* Compose new outgoing buffer */
+ for (i = 0; i < map.via.map.size; i++) {
+ k = map.via.map.ptr[i].key;
+ v = map.via.map.ptr[i].val;
+
+ /* Pack right away previous fields in the map */
+ if (i < matched) {
+ msgpack_pack_object(&mp_pck, k);
+ msgpack_pack_object(&mp_pck, v);
+ continue;
+ }
+
+ /* Process current key names and decoder rules */
+ if (k.type != MSGPACK_OBJECT_STR || v.type != MSGPACK_OBJECT_STR) {
+ msgpack_pack_object(&mp_pck, k);
+ msgpack_pack_object(&mp_pck, v);
+ continue;
+ }
+
+ /*
+ * Per key, we allow only one successful 'Decode_Field' and one
+ * successful 'Decode_Field_As' rules. Otherwise it may lead
+ * to duplicated entries in the final map.
+ *
+ * is_decoded => Decode_Field successul ?
+ * is_decoded_as => Decode_Field_As successful ?
+ */
+ is_decoded = FLB_FALSE;
+ is_decoded_as = FLB_FALSE;
+
+ /* Lookup for decoders associated to the current 'key' */
+ mk_list_foreach(head, decoders) {
+ dec = mk_list_entry(head, struct flb_parser_dec, _head);
+ if (flb_sds_cmp(dec->key, k.via.str.ptr,
+ k.via.str.size) == 0) {
+ break;
+ }
+ dec = NULL;
+ }
+
+ /* No decoder found, pack content */
+ if (!dec) {
+ msgpack_pack_object(&mp_pck, k);
+ msgpack_pack_object(&mp_pck, v);
+ continue;
+ }
+
+ if (!in_sds) {
+ in_sds = flb_sds_create_size(v.via.str.size);
+ if (!in_sds) {
+ break;
+ }
+ out_sds = flb_sds_create_size(v.via.str.size);
+ if (!out_sds) {
+ break;
+ }
+ data_sds = flb_sds_create_size(v.via.str.size);
+ }
+
+ /* Copy original content */
+ tmp_sds = flb_sds_copy(data_sds, v.via.str.ptr,
+ v.via.str.size);
+ if (tmp_sds != data_sds) {
+ data_sds = tmp_sds;
+ }
+
+ /*
+ * We got a match: 'key name' == 'decoder field name', validate
+ * that we have enough space in our temporary buffer.
+ */
+ if (flb_sds_alloc(dec->buffer) < flb_sds_alloc(data_sds)) {
+ /* Increase buffer size */
+ size_t diff;
+ diff = (flb_sds_alloc(data_sds) - flb_sds_alloc(dec->buffer));
+ tmp_sds = flb_sds_increase(dec->buffer, diff);
+ if (!tmp_sds) {
+ flb_errno();
+ break;
+ }
+ if (tmp_sds != dec->buffer) {
+ dec->buffer = tmp_sds;
+ }
+ }
+
+ /* Process decoder rules */
+ ret = -1;
+ dec_buf = NULL;
+
+ /*
+ * If some rule type is FLB_PARSER_DEC_DEFAULT, means that it will
+ * try to register some extra fields as part of the record. For such
+ * case we prepare a temporary buffer to hold these extra keys.
+ *
+ * The content of this buffer is just a serialized number of maps.
+ */
+ if (dec->add_extra_keys == FLB_TRUE) {
+ /* We need to clean up already allocated extra buffers */
+ if (extra_keys == FLB_TRUE) {
+ msgpack_sbuffer_destroy(&extra_mp_sbuf);
+ }
+ extra_keys = FLB_TRUE;
+ msgpack_sbuffer_init(&extra_mp_sbuf);
+ msgpack_packer_init(&extra_mp_pck, &extra_mp_sbuf,
+ msgpack_sbuffer_write);
+ }
+
+ mk_list_foreach(r_head, &dec->rules) {
+ rule = mk_list_entry(r_head, struct flb_parser_dec_rule, _head);
+
+ if (rule->type == FLB_PARSER_DEC_DEFAULT &&
+ rule->action == FLB_PARSER_ACT_DO_NEXT &&
+ is_decoded == FLB_TRUE) {
+ continue;
+ }
+
+ if (is_decoded_as == FLB_TRUE && in_type != TYPE_OUT_STRING) {
+ continue;
+ }
+
+ /* Process using defined decoder backend */
+ if (rule->backend == FLB_PARSER_DEC_JSON) {
+ ret = decode_json(dec, (char *) data_sds, flb_sds_len(data_sds),
+ &dec_buf, &dec_size, &dec_type);
+ }
+ else if (rule->backend == FLB_PARSER_DEC_ESCAPED) {
+ ret = decode_escaped(dec,
+ (char *) data_sds, flb_sds_len(data_sds),
+ &dec_buf, &dec_size, &dec_type);
+ }
+ else if (rule->backend == FLB_PARSER_DEC_ESCAPED_UTF8) {
+ ret = decode_escaped_utf8(dec,
+ (char *) data_sds, flb_sds_len(data_sds),
+ &dec_buf, &dec_size, &dec_type);
+ }
+ else if (rule->backend == FLB_PARSER_DEC_MYSQL_QUOTED) {
+ ret = decode_mysql_quoted(dec,
+ (char *) data_sds, flb_sds_len(data_sds),
+ &dec_buf, &dec_size, &dec_type);
+ }
+
+ /* Check decoder status */
+ if (ret == -1) {
+ /* Current decoder failed, should we try the next one ? */
+ if (rule->action == FLB_PARSER_ACT_TRY_NEXT ||
+ rule->action == FLB_PARSER_ACT_DO_NEXT) {
+ continue;
+ }
+
+ /* Stop: no more rules should be applied */
+ break;
+ }
+
+ /* Internal packing: replace value content in the same key */
+ if (rule->type == FLB_PARSER_DEC_AS) {
+ tmp_sds = flb_sds_copy(in_sds, dec_buf, dec_size);
+ if (tmp_sds != in_sds) {
+ in_sds = tmp_sds;
+ }
+ tmp_sds = flb_sds_copy(data_sds, dec_buf, dec_size);
+ if (tmp_sds != data_sds) {
+ data_sds = tmp_sds;
+ }
+ in_type = dec_type;
+ is_decoded_as = FLB_TRUE;
+ }
+ else if (rule->type == FLB_PARSER_DEC_DEFAULT) {
+ tmp_sds = flb_sds_copy(out_sds, dec_buf, dec_size);
+ if (tmp_sds != out_sds) {
+ out_sds = tmp_sds;
+ }
+ out_type = dec_type;
+ is_decoded = FLB_TRUE;
+ }
+
+
+ if (dec_buf != dec->buffer) {
+ flb_free(dec_buf);
+ }
+ dec_buf = NULL;
+ dec_size = 0;
+
+ /* Apply more rules ? */
+ if (rule->action == FLB_PARSER_ACT_DO_NEXT) {
+ continue;
+ }
+ break;
+ }
+
+ /* Package the key */
+ msgpack_pack_object(&mp_pck, k);
+
+ /* We need to place some value for the key in question */
+ if (is_decoded_as == FLB_TRUE) {
+ if (in_type == TYPE_OUT_STRING) {
+ msgpack_pack_str(&mp_pck, flb_sds_len(in_sds));
+ msgpack_pack_str_body(&mp_pck,
+ in_sds, flb_sds_len(in_sds));
+ }
+ else if (in_type == TYPE_OUT_OBJECT) {
+ msgpack_sbuffer_write(&mp_sbuf,
+ in_sds, flb_sds_len(in_sds));
+ }
+ }
+ else {
+ /* Pack original value */
+ msgpack_pack_object(&mp_pck, v);
+ }
+
+ /* Package as external keys */
+ if (is_decoded == FLB_TRUE) {
+ if (out_type == TYPE_OUT_STRING) {
+ flb_error("[parser_decoder] string type is not allowed");
+ }
+ else if (out_type == TYPE_OUT_OBJECT) {
+ msgpack_sbuffer_write(&extra_mp_sbuf,
+ out_sds, flb_sds_len(out_sds));
+ }
+ }
+ }
+
+ if (in_sds) {
+ flb_sds_destroy(in_sds);
+ }
+ if (out_sds) {
+ flb_sds_destroy(out_sds);
+ }
+ if (data_sds) {
+ flb_sds_destroy(data_sds);
+ }
+
+ msgpack_unpacked_destroy(&result);
+ *out_buf = mp_sbuf.data;
+ *out_size = mp_sbuf.size;
+
+ if (extra_keys == FLB_TRUE) {
+ ret = merge_record_and_extra_keys(mp_sbuf.data, mp_sbuf.size,
+ extra_mp_sbuf.data, extra_mp_sbuf.size,
+ out_buf, out_size);
+ msgpack_sbuffer_destroy(&extra_mp_sbuf);
+ if (ret == 0) {
+ msgpack_sbuffer_destroy(&mp_sbuf);
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Iterate decoders list and lookup for an existing context for 'key_name',
+ * if it does not exists, create and link a new one
+ */
+static struct flb_parser_dec *get_decoder_key_context(const char *key_name, int key_len,
+ struct mk_list *list)
+{
+ struct mk_list *head;
+ struct flb_parser_dec *dec = NULL;
+
+ mk_list_foreach(head, list) {
+ dec = mk_list_entry(head, struct flb_parser_dec, _head);
+
+ /* Check if the decoder matches the requested key name */
+ if (flb_sds_cmp(dec->key, key_name, key_len) != 0) {
+ dec = NULL;
+ continue;
+ }
+ else {
+ break;
+ }
+ }
+
+ if (!dec) {
+ dec = flb_malloc(sizeof(struct flb_parser_dec));
+ if (!dec) {
+ flb_errno();
+ return NULL;
+ }
+
+ dec->key = flb_sds_create_len(key_name, key_len);
+ if (!dec->key) {
+ flb_errno();
+ flb_free(dec);
+ return NULL;
+ }
+
+ dec->buffer = flb_sds_create_size(FLB_PARSER_DEC_BUF_SIZE);
+ if (!dec->buffer) {
+ flb_errno();
+ flb_sds_destroy(dec->key);
+ flb_free(dec);
+ return NULL;
+ }
+ dec->add_extra_keys = FLB_FALSE;
+ mk_list_init(&dec->rules);
+ mk_list_add(&dec->_head, list);
+ }
+
+ return dec;
+}
+
+struct mk_list *flb_parser_decoder_list_create(struct flb_cf_section *section)
+{
+ int c = 0;
+ int type;
+ int backend;
+ int size;
+ struct cfl_list *head;
+ struct mk_list *list = NULL;
+ struct mk_list *split;
+ struct flb_split_entry *decoder;
+ struct flb_split_entry *field;
+ struct flb_split_entry *action;
+ struct flb_parser_dec *dec;
+ struct flb_parser_dec_rule *dec_rule;
+ struct cfl_kvpair *entry;
+
+ /* Global list to be referenced by parent parser definition */
+ list = flb_malloc(sizeof(struct mk_list));
+ if (!list) {
+ flb_errno();
+ return NULL;
+ }
+ mk_list_init(list);
+
+ cfl_list_foreach(head, &section->properties->list) {
+ entry = cfl_list_entry(head, struct cfl_kvpair, _head);
+
+ /* Lookup for specific Decode rules */
+ if (strcasecmp(entry->key, "decode_field") == 0) {
+ type = FLB_PARSER_DEC_DEFAULT;
+ }
+ else if (strcasecmp(entry->key, "decode_field_as") == 0) {
+ type = FLB_PARSER_DEC_AS;
+ }
+ else {
+ continue;
+ }
+
+ /* Split the value */
+ split = flb_utils_split(entry->val->data.as_string, ' ', 3);
+ if (!split) {
+ flb_error("[parser] invalid number of parameters in decoder");
+ flb_parser_decoder_list_destroy(list);
+ return NULL;
+ }
+
+ /* We expect at least two values: decoder name and target field */
+ size = mk_list_size(split);
+ if (size < 2) {
+ flb_error("[parser] invalid number of parameters in decoder");
+ flb_utils_split_free(split);
+ flb_parser_decoder_list_destroy(list);
+ return NULL;
+ }
+
+ /*
+ * Get the rule/entry references:
+ *
+ * decoder: specify the backend that handle decoding (json, escaped..)
+ * field : the 'key' where decoding should happen
+ * action : optional rules to follow on success or failure
+ */
+ decoder = mk_list_entry_first(split, struct flb_split_entry, _head);
+ field = mk_list_entry_next(&decoder->_head, struct flb_split_entry,
+ _head, list);
+ if (size >= 3) {
+ action = mk_list_entry_next(&field->_head, struct flb_split_entry,
+ _head, list);
+ }
+ else {
+ action = NULL;
+ }
+
+ /* Get decoder */
+ if (strcasecmp(decoder->value, "json") == 0) {
+ backend = FLB_PARSER_DEC_JSON;
+ }
+ else if (strcasecmp(decoder->value, "escaped") == 0) {
+ backend = FLB_PARSER_DEC_ESCAPED;
+ }
+ else if (strcasecmp(decoder->value, "escaped_utf8") == 0) {
+ backend = FLB_PARSER_DEC_ESCAPED_UTF8;
+ }
+ else if (strcasecmp(decoder->value, "mysql_quoted") == 0) {
+ backend = FLB_PARSER_DEC_MYSQL_QUOTED;
+ }
+ else {
+ flb_error("[parser] field decoder '%s' unknown", decoder->value);
+ flb_utils_split_free(split);
+ flb_parser_decoder_list_destroy(list);
+ return NULL;
+ }
+
+ /* Get the parent decoder that will hold the rules defined */
+ dec = get_decoder_key_context(field->value, strlen(field->value), list);
+ if (!dec) {
+ /* Unexpected error */
+ flb_error("[parser] unexpected error, could not get a decoder");
+ flb_utils_split_free(split);
+ flb_parser_decoder_list_destroy(list);
+ return NULL;
+ }
+
+ /* Create decoder context */
+ dec_rule = flb_calloc(1, sizeof(struct flb_parser_dec_rule));
+ if (!dec_rule) {
+ flb_errno();
+ flb_utils_split_free(split);
+ flb_parser_decoder_list_destroy(list);
+ return NULL;
+ }
+
+ if (type == FLB_PARSER_DEC_DEFAULT) {
+ dec->add_extra_keys = FLB_TRUE;
+ }
+
+ dec_rule->type = type;
+ dec_rule->backend = backend;
+ if (action) {
+ if (strcasecmp(action->value, "try_next") == 0) {
+ dec_rule->action = FLB_PARSER_ACT_TRY_NEXT;
+ }
+ else if (strcasecmp(action->value, "do_next") == 0) {
+ dec_rule->action = FLB_PARSER_ACT_DO_NEXT;
+ }
+ else {
+ dec_rule->action = FLB_PARSER_ACT_NONE;
+ }
+ }
+
+ /* Remove temporary split */
+ flb_utils_split_free(split);
+ mk_list_add(&dec_rule->_head, &dec->rules);
+ c++;
+ }
+
+ if (c == 0) {
+ flb_free(list);
+ return NULL;
+ }
+
+ return list;
+}
+
+int flb_parser_decoder_list_destroy(struct mk_list *list)
+{
+ int c = 0;
+ struct mk_list *head;
+ struct mk_list *r_head;
+ struct mk_list *tmp;
+ struct mk_list *r_tmp;
+ struct flb_parser_dec *dec;
+ struct flb_parser_dec_rule *dec_rule;
+
+ mk_list_foreach_safe(head, tmp, list) {
+ dec = mk_list_entry(head, struct flb_parser_dec, _head);
+
+ /* Destroy rules */
+ mk_list_foreach_safe(r_head, r_tmp, &dec->rules) {
+ dec_rule = mk_list_entry(r_head, struct flb_parser_dec_rule,
+ _head);
+ mk_list_del(&dec_rule->_head);
+ flb_free(dec_rule);
+ }
+
+ mk_list_del(&dec->_head);
+ flb_sds_destroy(dec->key);
+ flb_sds_destroy(dec->buffer);
+ flb_free(dec);
+ c++;
+ }
+
+ flb_free(list);
+ return c;
+}
diff --git a/fluent-bit/src/flb_parser_json.c b/fluent-bit/src/flb_parser_json.c
new file mode 100644
index 000000000..7add06e94
--- /dev/null
+++ b/fluent-bit/src/flb_parser_json.c
@@ -0,0 +1,246 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE
+#include <time.h>
+
+#include <fluent-bit/flb_parser.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_parser_decoder.h>
+
+int flb_parser_json_do(struct flb_parser *parser,
+ const char *in_buf, size_t in_size,
+ void **out_buf, size_t *out_size,
+ struct flb_time *out_time)
+{
+ int i;
+ int skip;
+ int ret;
+ int slen;
+ int root_type;
+ int records;
+ double tmfrac = 0;
+ char *mp_buf = NULL;
+ char *time_key;
+ char *tmp_out_buf = NULL;
+ char tmp[255];
+ size_t tmp_out_size = 0;
+ size_t off = 0;
+ size_t map_size;
+ size_t mp_size;
+ size_t len;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+ msgpack_unpacked result;
+ msgpack_object map;
+ msgpack_object *k = NULL;
+ msgpack_object *v = NULL;
+ time_t time_lookup;
+ struct flb_tm tm = {0};
+ struct flb_time *t;
+ size_t consumed;
+
+ consumed = 0;
+
+ /* Convert incoming in_buf JSON message to message pack format */
+ ret = flb_pack_json_recs(in_buf, in_size, &mp_buf, &mp_size, &root_type,
+ &records, &consumed);
+ if (ret != 0) {
+ return -1;
+ }
+
+ if (records != 1) {
+ flb_free(mp_buf);
+
+ return -1;
+ }
+
+ /* Make sure object is a map */
+ msgpack_unpacked_init(&result);
+ if (msgpack_unpack_next(&result, mp_buf, mp_size, &off) == MSGPACK_UNPACK_SUCCESS) {
+ map = result.data;
+ if (map.type != MSGPACK_OBJECT_MAP) {
+ flb_free(mp_buf);
+ msgpack_unpacked_destroy(&result);
+
+ return -1;
+ }
+ }
+ else {
+ if (mp_size > 0) {
+ flb_free(mp_buf);
+ }
+
+ msgpack_unpacked_destroy(&result);
+
+ return -1;
+ }
+
+ /* Export results (might change later) */
+ tmp_out_buf = mp_buf;
+ tmp_out_size = mp_size;
+
+ /* Do we have some decoders set ? */
+ if (parser->decoders) {
+ ret = flb_parser_decoder_do(parser->decoders,
+ mp_buf, mp_size,
+ &tmp_out_buf, &tmp_out_size);
+ if (ret == 0) {
+ /* re-process the unpack context */
+ off = 0;
+ msgpack_unpacked_destroy(&result);
+ msgpack_unpacked_init(&result);
+ msgpack_unpack_next(&result, tmp_out_buf, tmp_out_size, &off);
+ map = result.data;
+ }
+ }
+
+ /* Set the possible outgoing buffer */
+ *out_buf = tmp_out_buf;
+ *out_size = tmp_out_size;
+ if (mp_buf != tmp_out_buf) {
+ flb_free(mp_buf);
+ mp_buf = NULL;
+ }
+
+ /* Do time resolution ? */
+ if (!parser->time_fmt) {
+ msgpack_unpacked_destroy(&result);
+
+ return (int) consumed;
+ }
+
+ if (parser->time_key) {
+ time_key = parser->time_key;
+ }
+ else {
+ time_key = "time";
+ }
+ slen = strlen(time_key);
+
+ /* Lookup time field */
+ map_size = map.via.map.size;
+ skip = map_size;
+ for (i = 0; i < map_size; i++) {
+ k = &map.via.map.ptr[i].key;
+ v = &map.via.map.ptr[i].val;
+
+ if (k->via.str.size != slen) {
+ continue;
+ }
+
+ /* Ensure the pointer we are about to read is not NULL */
+ if (k->via.str.ptr == NULL) {
+ if (mp_buf == tmp_out_buf) {
+ flb_free(mp_buf);
+ }
+ else {
+ flb_free(mp_buf);
+ flb_free(tmp_out_buf);
+ }
+ *out_buf = NULL;
+ msgpack_unpacked_destroy(&result);
+
+ return -1;
+ }
+
+ if (strncmp(k->via.str.ptr, time_key, k->via.str.size) == 0) {
+ /* We found the key, break the loop and keep the index */
+ if (parser->time_keep == FLB_FALSE) {
+ skip = i;
+ break;
+ }
+ else {
+ skip = -1;
+ }
+ break;
+ }
+
+ k = NULL;
+ v = NULL;
+ }
+
+ /* No time_key field found */
+ if (i >= map_size || !k || !v) {
+ msgpack_unpacked_destroy(&result);
+
+ return (int) consumed;
+ }
+
+ /* Ensure we have an accurate type */
+ if (v->type != MSGPACK_OBJECT_STR) {
+ msgpack_unpacked_destroy(&result);
+
+ return (int) consumed;
+ }
+
+ /* Lookup time */
+ ret = flb_parser_time_lookup(v->via.str.ptr, v->via.str.size,
+ 0, parser, &tm, &tmfrac);
+ if (ret == -1) {
+ len = v->via.str.size;
+ if (len > sizeof(tmp) - 1) {
+ len = sizeof(tmp) - 1;
+ }
+ memcpy(tmp, v->via.str.ptr, len);
+ tmp[len] = '\0';
+ flb_warn("[parser:%s] invalid time format %s for '%s'",
+ parser->name, parser->time_fmt_full, tmp);
+ time_lookup = 0;
+ skip = map_size;
+ }
+ else {
+ time_lookup = flb_parser_tm2time(&tm);
+ }
+
+ /* Compose a new map without the time_key field */
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ if (parser->time_keep == FLB_FALSE && skip < map_size) {
+ msgpack_pack_map(&mp_pck, map_size - 1);
+ }
+ else {
+ msgpack_pack_map(&mp_pck, map_size);
+ }
+
+ for (i = 0; i < map_size; i++) {
+ if (i == skip) {
+ continue;
+ }
+
+ msgpack_pack_object(&mp_pck, map.via.map.ptr[i].key);
+ msgpack_pack_object(&mp_pck, map.via.map.ptr[i].val);
+ }
+
+ /* Export the proper buffer */
+ flb_free(tmp_out_buf);
+
+ *out_buf = mp_sbuf.data;
+ *out_size = mp_sbuf.size;
+
+ t = out_time;
+ t->tm.tv_sec = time_lookup;
+ t->tm.tv_nsec = (tmfrac * 1000000000);
+
+ msgpack_unpacked_destroy(&result);
+
+ return (int) consumed;
+}
diff --git a/fluent-bit/src/flb_parser_logfmt.c b/fluent-bit/src/flb_parser_logfmt.c
new file mode 100644
index 000000000..8e6b46590
--- /dev/null
+++ b/fluent-bit/src/flb_parser_logfmt.c
@@ -0,0 +1,326 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE
+#include <time.h>
+
+#include <fluent-bit/flb_parser.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_parser_decoder.h>
+#include <fluent-bit/flb_unescape.h>
+#include <fluent-bit/flb_mem.h>
+
+/*
+ * https://brandur.org/logfmt
+ * https://godoc.org/github.com/kr/logfmt
+ *
+ * ident_byte = any byte greater than ' ', excluding '=' and '"'
+ * string_byte = any byte excluding '"' and '\'
+ * garbage = !ident_byte
+ * ident = ident_byte, { ident byte }
+ * key = ident
+ * value = ident | '"', { string_byte | '\', '"' }, '"'
+ * pair = key, '=', value | key, '=' | key
+ * message = { garbage, pair }, garbage
+ */
+
+static char ident_byte[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+static int logfmt_parser(struct flb_parser *parser,
+ const char *in_buf, size_t in_size,
+ msgpack_packer *tmp_pck,
+ char *time_key, size_t time_key_len,
+ time_t *time_lookup, double *tmfrac,
+ size_t *map_size)
+{
+ int ret;
+ struct flb_tm tm = {0};
+ const unsigned char *key = NULL;
+ size_t key_len = 0;
+ const unsigned char *value = NULL;
+ size_t value_len = 0;
+ const unsigned char *c = (const unsigned char *)in_buf;
+ const unsigned char *end = c + in_size;
+ int last_byte;
+ int do_pack = FLB_TRUE;
+ int value_set = FLB_FALSE;
+ int value_str = FLB_FALSE;
+ int value_escape = FLB_FALSE;
+
+ /* if map_size is 0 only count the number of k:v */
+ if (*map_size == 0) {
+ do_pack = FLB_FALSE;
+ }
+
+ while (c < end) {
+ /* garbage */
+ while ((c < end) && !ident_byte[*c]) {
+ c++;
+ }
+ if (c == end) {
+ break;
+ }
+ /* key */
+ key = c;
+ while ((c < end) && ident_byte[*c]) {
+ c++;
+ }
+
+ key_len = c - key;
+ /* value */
+ value_len = 0;
+ value_set = FLB_FALSE;
+ value_str = FLB_FALSE;
+ value_escape = FLB_FALSE;
+
+ if (c < end && *c == '=') {
+ value_set = FLB_TRUE;
+ c++;
+ if (c < end) {
+ if (*c == '"') {
+ c++;
+ value = c;
+ value_str = FLB_TRUE;
+ while (c < end) {
+ if (*c != '\\' && *c!= '"') {
+ c++;
+ }
+ else if (*c == '\\') {
+ value_escape = FLB_TRUE;
+ c++;
+ if (c == end) {
+ break;
+ }
+ c++;
+ }
+ else {
+ break;
+ }
+ }
+ value_len = c - value;
+ if (c < end && *c == '\"') {
+ c++;
+ }
+ }
+ else {
+ value = c;
+ while ((c < end) && ident_byte[*c]) {
+ c++;
+ }
+ value_len = c - value;
+ }
+ }
+ }
+
+ if (key_len > 0) {
+ int time_found = FLB_FALSE;
+ if (parser->logfmt_no_bare_keys && value_len == 0 && !value_set) {
+ if (!do_pack) {
+ *map_size = 0;
+ }
+ return 0;
+ }
+
+ if (parser->time_fmt && key_len == time_key_len &&
+ value_len > 0 &&
+ !strncmp((const char *)key, time_key, key_len)) {
+ if (do_pack) {
+ ret = flb_parser_time_lookup((const char *) value, value_len,
+ 0, parser, &tm, tmfrac);
+ if (ret == -1) {
+ flb_error("[parser:%s] Invalid time format %s",
+ parser->name, parser->time_fmt_full);
+ return -1;
+ }
+ *time_lookup = flb_parser_tm2time(&tm);
+ }
+ time_found = FLB_TRUE;
+ }
+
+ if (time_found == FLB_FALSE || parser->time_keep == FLB_TRUE) {
+ if (do_pack) {
+ if (parser->types_len != 0) {
+ flb_parser_typecast((const char*) key, key_len,
+ (const char*) value, value_len,
+ tmp_pck,
+ parser->types,
+ parser->types_len);
+ }
+ else {
+ msgpack_pack_str(tmp_pck, key_len);
+ msgpack_pack_str_body(tmp_pck, (const char *)key, key_len);
+ if (value_len == 0) {
+ if (value_str == FLB_TRUE) {
+ msgpack_pack_str(tmp_pck, 0);
+ }
+ else {
+ msgpack_pack_true(tmp_pck);
+ }
+ }
+ else {
+ if (value_escape == FLB_TRUE) {
+ int out_len;
+ char *out_str;
+
+ out_str = flb_malloc(value_len + 1);
+ if (out_str == NULL) {
+ flb_errno();
+ return -1;
+ }
+ out_str[0] = 0;
+ flb_unescape_string_utf8((const char *)value,
+ value_len,
+ out_str);
+ out_len = strlen(out_str);
+
+ msgpack_pack_str(tmp_pck, out_len);
+ msgpack_pack_str_body(tmp_pck,
+ out_str,
+ out_len);
+
+ flb_free(out_str);
+ }
+ else {
+ msgpack_pack_str(tmp_pck, value_len);
+ msgpack_pack_str_body(tmp_pck,
+ (const char *)value,
+ value_len);
+ }
+ }
+ }
+ }
+ else {
+ (*map_size)++;
+ }
+ }
+ }
+
+ if (c == end) {
+ break;
+ }
+
+ if (*c == '\r') {
+ c++;
+ if (c == end) {
+ break;
+ }
+ if (*c == '\n') {
+ c++;
+ }
+ break;
+ }
+ if (*c == '\n') {
+ c++;
+ break;
+ }
+ }
+ last_byte = (const char *)c - in_buf;
+
+ return last_byte;
+}
+
+int flb_parser_logfmt_do(struct flb_parser *parser,
+ const char *in_buf, size_t in_size,
+ void **out_buf, size_t *out_size,
+ struct flb_time *out_time)
+{
+ int ret;
+ time_t time_lookup;
+ double tmfrac = 0;
+ struct flb_time *t;
+ msgpack_sbuffer tmp_sbuf;
+ msgpack_packer tmp_pck;
+ char *dec_out_buf;
+ size_t dec_out_size;
+ size_t map_size;
+ char *time_key;
+ size_t time_key_len;
+ int last_byte;
+
+ if (parser->time_key) {
+ time_key = parser->time_key;
+ }
+ else {
+ time_key = "time";
+ }
+ time_key_len = strlen(time_key);
+ time_lookup = 0;
+
+ /* count the number of key value pairs */
+ map_size = 0;
+ logfmt_parser(parser, in_buf, in_size, NULL,
+ time_key, time_key_len,
+ &time_lookup, &tmfrac, &map_size);
+ if (map_size == 0) {
+ return -1;
+ }
+
+ /* Prepare new outgoing buffer */
+ msgpack_sbuffer_init(&tmp_sbuf);
+ msgpack_packer_init(&tmp_pck, &tmp_sbuf, msgpack_sbuffer_write);
+ msgpack_pack_map(&tmp_pck, map_size);
+
+ last_byte = logfmt_parser(parser, in_buf, in_size, &tmp_pck,
+ time_key, time_key_len,
+ &time_lookup, &tmfrac, &map_size);
+ if (last_byte < 0) {
+ msgpack_sbuffer_destroy(&tmp_sbuf);
+ return last_byte;
+ }
+
+ /* Export results */
+ *out_buf = tmp_sbuf.data;
+ *out_size = tmp_sbuf.size;
+
+ t = out_time;
+ t->tm.tv_sec = time_lookup;
+ t->tm.tv_nsec = (tmfrac * 1000000000);
+
+ /* Check if some decoder was specified */
+ if (parser->decoders) {
+ ret = flb_parser_decoder_do(parser->decoders,
+ tmp_sbuf.data, tmp_sbuf.size,
+ &dec_out_buf, &dec_out_size);
+ if (ret == 0) {
+ *out_buf = dec_out_buf;
+ *out_size = dec_out_size;
+ msgpack_sbuffer_destroy(&tmp_sbuf);
+ }
+ }
+
+ return last_byte;
+}
diff --git a/fluent-bit/src/flb_parser_ltsv.c b/fluent-bit/src/flb_parser_ltsv.c
new file mode 100644
index 000000000..8f38102cf
--- /dev/null
+++ b/fluent-bit/src/flb_parser_ltsv.c
@@ -0,0 +1,269 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE
+#include <time.h>
+
+#include <fluent-bit/flb_parser.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_parser_decoder.h>
+
+/*
+ * http://ltsv.org
+ *
+ * ltsv = *(record NL) [record]
+ * record = [field *(TAB field)]
+ * field = label ":" field-value
+ * label = 1*lbyte
+ * field-value = *fbyte
+ *
+ * TAB = %x09
+ * NL = [%x0D] %x0A
+ * lbyte = %x30-39 / %x41-5A / %x61-7A / "_" / "." / "-" ;; [0-9A-Za-z_.-]
+ * fbyte = %x01-08 / %x0B / %x0C / %x0E-FF
+ */
+
+static char ltvs_label[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char ltvs_field[256] = {
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+};
+
+
+static int ltsv_parser(struct flb_parser *parser,
+ const char *in_buf, size_t in_size,
+ msgpack_packer *tmp_pck,
+ char *time_key, size_t time_key_len,
+ time_t *time_lookup, double *tmfrac,
+ size_t *map_size)
+{
+ int ret;
+ struct flb_tm tm = {0};
+ const unsigned char *label = NULL;
+ size_t label_len = 0;
+ const unsigned char *field = NULL;
+ size_t field_len = 0;
+ const unsigned char *c = (const unsigned char *)in_buf;
+ const unsigned char *end = c + in_size;
+ int last_byte;
+ int do_pack = FLB_TRUE;
+
+ /* if map_size is 0 only count the number of k:v */
+ if (*map_size == 0) {
+ do_pack = FLB_FALSE;
+ }
+
+ while (c < end) {
+ label = c;
+ while ((c < end) && ltvs_label[*c]) {
+ c++;
+ }
+ label_len = c - label;
+ if (c == end) {
+ break;
+ }
+
+ if (*c != ':') {
+ break;
+ }
+ c++;
+
+ field = c;
+ if (c != end) {
+ while ((c < end) && ltvs_field[*c]) {
+ c++;
+ }
+ }
+ field_len = c - field;
+
+ if (label_len > 0) {
+ int time_found = FLB_FALSE;
+
+ if (parser->time_fmt && label_len == time_key_len &&
+ field_len > 0 &&
+ !strncmp((const char *)label, time_key, label_len)) {
+ if (do_pack) {
+ ret = flb_parser_time_lookup((const char *) field, field_len,
+ 0, parser, &tm, tmfrac);
+ if (ret == -1) {
+ flb_error("[parser:%s] Invalid time format %s",
+ parser->name, parser->time_fmt_full);
+ return -1;
+ }
+ *time_lookup = flb_parser_tm2time(&tm);
+ }
+ time_found = FLB_TRUE;
+ }
+
+ if (time_found == FLB_FALSE || parser->time_keep == FLB_TRUE) {
+ if (do_pack) {
+ if (parser->types_len != 0) {
+ flb_parser_typecast((const char*) label, label_len,
+ (const char*) field, field_len,
+ tmp_pck,
+ parser->types,
+ parser->types_len);
+ }
+ else {
+ msgpack_pack_str(tmp_pck, label_len);
+ msgpack_pack_str_body(tmp_pck, (const char *)label, label_len);
+ msgpack_pack_str(tmp_pck, field_len);
+ msgpack_pack_str_body(tmp_pck, (const char *)field, field_len);
+ }
+ }
+ else {
+ (*map_size)++;
+ }
+ }
+ }
+
+ if (c == end) {
+ break;
+ }
+ if (*c == '\t') {
+ c++;
+ }
+ if (c == end) {
+ break;
+ }
+
+ if (*c == '\r') {
+ c++;
+ if (c == end) {
+ break;
+ }
+ if (*c == '\n') {
+ c++;
+ }
+ break;
+ }
+ if (*c == '\n') {
+ c++;
+ break;
+ }
+ }
+ last_byte = (const char *)c - in_buf;
+
+ return last_byte;
+}
+
+int flb_parser_ltsv_do(struct flb_parser *parser,
+ const char *in_buf, size_t in_size,
+ void **out_buf, size_t *out_size,
+ struct flb_time *out_time)
+{
+ int ret;
+ time_t time_lookup;
+ double tmfrac = 0;
+ struct flb_time *t;
+ msgpack_sbuffer tmp_sbuf;
+ msgpack_packer tmp_pck;
+ char *dec_out_buf;
+ size_t dec_out_size;
+ size_t map_size;
+ char *time_key;
+ size_t time_key_len;
+ int last_byte;
+
+ if (parser->time_key) {
+ time_key = parser->time_key;
+ }
+ else {
+ time_key = "time";
+ }
+ time_key_len = strlen(time_key);
+ time_lookup = 0;
+
+ /* count the number of key value pairs */
+ map_size = 0;
+ ltsv_parser(parser, in_buf, in_size, NULL,
+ time_key, time_key_len,
+ &time_lookup, &tmfrac, &map_size);
+ if (map_size == 0) {
+ return -1;
+ }
+
+ /* Prepare new outgoing buffer */
+ msgpack_sbuffer_init(&tmp_sbuf);
+ msgpack_packer_init(&tmp_pck, &tmp_sbuf, msgpack_sbuffer_write);
+ msgpack_pack_map(&tmp_pck, map_size);
+
+ last_byte = ltsv_parser(parser, in_buf, in_size, &tmp_pck,
+ time_key, time_key_len,
+ &time_lookup, &tmfrac, &map_size);
+ if (last_byte < 0) {
+ msgpack_sbuffer_destroy(&tmp_sbuf);
+ return last_byte;
+ }
+
+ /* Export results */
+ *out_buf = tmp_sbuf.data;
+ *out_size = tmp_sbuf.size;
+
+ t = out_time;
+ t->tm.tv_sec = time_lookup;
+ t->tm.tv_nsec = (tmfrac * 1000000000);
+
+ /* Check if some decoder was specified */
+ if (parser->decoders) {
+ ret = flb_parser_decoder_do(parser->decoders,
+ tmp_sbuf.data, tmp_sbuf.size,
+ &dec_out_buf, &dec_out_size);
+ if (ret == 0) {
+ *out_buf = dec_out_buf;
+ *out_size = dec_out_size;
+ msgpack_sbuffer_destroy(&tmp_sbuf);
+ }
+ }
+
+ return last_byte;
+}
diff --git a/fluent-bit/src/flb_parser_regex.c b/fluent-bit/src/flb_parser_regex.c
new file mode 100644
index 000000000..efcc6fb60
--- /dev/null
+++ b/fluent-bit/src/flb_parser_regex.c
@@ -0,0 +1,227 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE
+#include <time.h>
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_parser.h>
+#include <fluent-bit/flb_parser_decoder.h>
+#include <fluent-bit/flb_regex.h>
+#include <fluent-bit/flb_str.h>
+
+#include <msgpack.h>
+
+/* don't do this at home */
+#define pack_uint16(buf, d) _msgpack_store16(buf, (uint16_t) d)
+#define pack_uint32(buf, d) _msgpack_store32(buf, (uint32_t) d)
+
+struct regex_cb_ctx {
+ int num_skipped;
+ time_t time_lookup;
+ time_t time_now;
+ double time_frac;
+ struct flb_parser *parser;
+ msgpack_packer *pck;
+};
+
+static void cb_results(const char *name, const char *value,
+ size_t vlen, void *data)
+{
+ int len;
+ int ret;
+ double frac = 0;
+ char *time_key;
+ char tmp[255];
+ struct regex_cb_ctx *pcb = data;
+ struct flb_parser *parser = pcb->parser;
+ struct flb_tm tm = {0};
+ (void) data;
+
+ if (vlen == 0 && parser->skip_empty) {
+ pcb->num_skipped++;
+ return;
+ }
+
+ len = strlen(name);
+
+ /* Check if there is a time lookup field */
+ if (parser->time_fmt) {
+ if (parser->time_key) {
+ time_key = parser->time_key;
+ }
+ else {
+ time_key = "time";
+ }
+
+ if (strcmp(name, time_key) == 0) {
+ /* Lookup time */
+ ret = flb_parser_time_lookup(value, vlen,
+ pcb->time_now, parser, &tm, &frac);
+ if (ret == -1) {
+ if (vlen > sizeof(tmp) - 1) {
+ vlen = sizeof(tmp) - 1;
+ }
+ memcpy(tmp, value, vlen);
+ tmp[vlen] = '\0';
+ flb_warn("[parser:%s] invalid time format %s for '%s'",
+ parser->name, parser->time_fmt_full, tmp);
+ pcb->num_skipped++;
+ return;
+ }
+
+ pcb->time_frac = frac;
+ pcb->time_lookup = flb_parser_tm2time(&tm);
+
+ if (parser->time_keep == FLB_FALSE) {
+ pcb->num_skipped++;
+ return;
+ }
+ }
+ }
+
+ if (parser->types_len != 0) {
+ flb_parser_typecast(name, len,
+ value, vlen,
+ pcb->pck,
+ parser->types,
+ parser->types_len);
+ }
+ else {
+ msgpack_pack_str(pcb->pck, len);
+ msgpack_pack_str_body(pcb->pck, name, len);
+ msgpack_pack_str(pcb->pck, vlen);
+ msgpack_pack_str_body(pcb->pck, value, vlen);
+ }
+}
+
+int flb_parser_regex_do(struct flb_parser *parser,
+ const char *buf, size_t length,
+ void **out_buf, size_t *out_size,
+ struct flb_time *out_time)
+{
+ int ret;
+ int arr_size;
+ int last_byte;
+ ssize_t n;
+ size_t dec_out_size;
+ char *dec_out_buf;
+ char *tmp;
+ struct flb_regex_search result;
+ struct regex_cb_ctx pcb;
+ struct flb_time *t;
+ msgpack_sbuffer tmp_sbuf;
+ msgpack_packer tmp_pck;
+
+ n = flb_regex_do(parser->regex, buf, length, &result);
+ if (n <= 0) {
+ return -1;
+ }
+
+ /* Prepare new outgoing buffer */
+ msgpack_sbuffer_init(&tmp_sbuf);
+ msgpack_packer_init(&tmp_pck, &tmp_sbuf, msgpack_sbuffer_write);
+
+ /* Set a Map size with the exact number of matches returned by regex */
+ arr_size = n;
+ msgpack_pack_map(&tmp_pck, arr_size);
+
+ /* Callback context */
+ pcb.pck = &tmp_pck;
+ pcb.parser = parser;
+ pcb.num_skipped = 0;
+ pcb.time_lookup = 0;
+ pcb.time_frac = 0;
+ pcb.time_now = 0;
+
+ /* Iterate results and compose new buffer */
+ last_byte = flb_regex_parse(parser->regex, &result, cb_results, &pcb);
+ if (last_byte == -1) {
+ msgpack_sbuffer_destroy(&tmp_sbuf);
+ return -1;
+ }
+
+ /*
+ * There some special cases when the Parser have a 'time' handling
+ * requirement, meaning: lookup for this 'time' key and resolve the
+ * real date of the record. If so, the parser by default will
+ * keep the original 'time' key field found but in other scenarios
+ * it may ask to skip it.
+ *
+ * If a time lookup is specified and the parser ask to skip the record
+ * and the time key is found, we need to adjust the msgpack header
+ * map size, initially we set a size to include all keys found, but
+ * until now we just know we are not going to include it.
+ *
+ * In addition, keys without associated values are skipped too and we
+ * must take this into account in msgpack header map size adjustment.
+ *
+ * In order to avoid to create a new msgpack buffer and repack the
+ * map entries, we just position at the header byte and do the
+ * proper adjustment in our original buffer. Note that for cases
+ * where the map is large enough '<= 65535' or '> 65535' we have
+ * to use internal msgpack api functions since packing the bytes
+ * in Big-Endian is a requirement.
+ */
+ if (pcb.num_skipped > 0) {
+
+ arr_size = (n - pcb.num_skipped);
+
+ tmp = tmp_sbuf.data;
+ uint8_t h = tmp[0];
+ if (h >> 4 == 0x8) { /* 1000xxxx */
+ *tmp = (uint8_t) 0x8 << 4 | ((uint8_t) arr_size);
+ }
+ else if (h == 0xde) {
+ tmp++;
+ pack_uint16(tmp, arr_size);
+ }
+ else if (h == 0xdf) {
+ tmp++;
+ pack_uint32(tmp, arr_size);
+ }
+ }
+
+ /* Export results */
+ *out_buf = tmp_sbuf.data;
+ *out_size = tmp_sbuf.size;
+
+ t = out_time;
+ t->tm.tv_sec = pcb.time_lookup;
+ t->tm.tv_nsec = (pcb.time_frac * 1000000000);
+
+ /* Check if some decoder was specified */
+ if (parser->decoders) {
+ ret = flb_parser_decoder_do(parser->decoders,
+ tmp_sbuf.data, tmp_sbuf.size,
+ &dec_out_buf, &dec_out_size);
+ if (ret == 0) {
+ *out_buf = dec_out_buf;
+ *out_size = dec_out_size;
+ msgpack_sbuffer_destroy(&tmp_sbuf);
+ }
+ }
+
+ /*
+ * The return the value >= 0, belongs to the LAST BYTE consumed by the
+ * regex engine. If the last byte is lower than string length, means
+ * there is more data to be processed (maybe it's a stream).
+ */
+ return last_byte;
+}
diff --git a/fluent-bit/src/flb_pipe.c b/fluent-bit/src/flb_pipe.c
new file mode 100644
index 000000000..57ed07834
--- /dev/null
+++ b/fluent-bit/src/flb_pipe.c
@@ -0,0 +1,183 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+/*
+ * Fluent Bit core uses unnamed Unix pipes for signaling and general
+ * communication across components. When building on Windows this is
+ * problematic because Windows pipes are not selectable and only
+ * sockets are.
+ *
+ * This file aims to wrap around the required backend calls depending
+ * of the operating system.
+ *
+ * This file provides 4 interfaces:
+ *
+ * - flb_pipe_create : create a pair of connected file descriptors or sockets.
+ * - flb_pipe_destroy : destroy a pair of connected fds or sockets.
+ * - flb_pipe_close : close individual end of a pipe.
+ * - flb_pipe_set_nonblocking : make a socket nonblocking
+ *
+ * we need to have a 'closer' handler because for Windows a file descriptor
+ * is not a socket.
+ */
+
+#include <fluent-bit/flb_compat.h>
+#include <fluent-bit/flb_pipe.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_time.h>
+
+#ifdef _WIN32
+
+/*
+ * Building on Windows means that Monkey library (lib/monkey) and it
+ * core runtime have been build with 'libevent' backend support, that
+ * library provide an abstraction to create a socketpairs.
+ *
+ * Creating a pipe on Fluent Bit @Windows, means create a socket pair.
+ */
+
+int flb_pipe_create(flb_pipefd_t pipefd[2])
+{
+ if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd) == -1) {
+ perror("socketpair");
+ return -1;
+ }
+
+ return 0;
+}
+
+void flb_pipe_destroy(flb_pipefd_t pipefd[2])
+{
+ evutil_closesocket(pipefd[0]);
+ evutil_closesocket(pipefd[1]);
+}
+
+int flb_pipe_close(flb_pipefd_t fd)
+{
+ return evutil_closesocket(fd);
+}
+
+int flb_pipe_set_nonblocking(flb_pipefd_t fd)
+{
+ return evutil_make_socket_nonblocking(fd);
+}
+#else
+/* All other flavors of Unix/BSD are OK */
+
+#include <stdint.h>
+#include <fcntl.h>
+
+int flb_pipe_create(flb_pipefd_t pipefd[2])
+{
+ return pipe(pipefd);
+}
+
+void flb_pipe_destroy(flb_pipefd_t pipefd[2])
+{
+ close(pipefd[0]);
+ close(pipefd[1]);
+}
+
+int flb_pipe_close(flb_pipefd_t fd)
+{
+ /*
+ * when chunk file is destroyed, the fd for file will be -1, we should avoid
+ * deleting chunk file with fd -1
+ */
+ if (fd == -1) {
+ return -1;
+ }
+
+ return close(fd);
+}
+
+int flb_pipe_set_nonblocking(flb_pipefd_t fd)
+{
+ int flags = fcntl(fd, F_GETFL);
+ if (flags < 0)
+ return -1;
+ if (flags & O_NONBLOCK)
+ return 0;
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+}
+#endif
+
+/* Blocking read until receive 'count' bytes */
+ssize_t flb_pipe_read_all(int fd, void *buf, size_t count)
+{
+ ssize_t bytes;
+ size_t total = 0;
+
+ do {
+ bytes = flb_pipe_r(fd, (char *) buf + total, count - total);
+ if (bytes == -1) {
+ if (FLB_PIPE_WOULDBLOCK()) {
+ /*
+ * This could happen, since this function goal is not to
+ * return until all data have been read, just sleep a little
+ * bit (0.05 seconds)
+ */
+ flb_time_msleep(50);
+ continue;
+ }
+ return -1;
+ }
+ else if (bytes == 0) {
+ /* Broken pipe ? */
+ flb_errno();
+ return -1;
+ }
+ total += bytes;
+
+ } while (total < count);
+
+ return total;
+}
+
+/* Blocking write until send 'count bytes */
+ssize_t flb_pipe_write_all(int fd, const void *buf, size_t count)
+{
+ ssize_t bytes;
+ size_t total = 0;
+
+ do {
+ bytes = flb_pipe_w(fd, (const char *) buf + total, count - total);
+ if (bytes == -1) {
+ if (FLB_PIPE_WOULDBLOCK()) {
+ /*
+ * This could happen, since this function goal is not to
+ * return until all data have been read, just sleep a little
+ * bit (0.05 seconds)
+ */
+ flb_time_msleep(50);
+ continue;
+ }
+ return -1;
+ }
+ else if (bytes == 0) {
+ /* Broken pipe ? */
+ flb_errno();
+ return -1;
+ }
+ total += bytes;
+
+ } while (total < count);
+
+ return total;
+}
diff --git a/fluent-bit/src/flb_plugin.c b/fluent-bit/src/flb_plugin.c
new file mode 100644
index 000000000..cd497f179
--- /dev/null
+++ b/fluent-bit/src/flb_plugin.c
@@ -0,0 +1,452 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_compat.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_error.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_config_format.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_plugin.h>
+#include <fluent-bit/flb_plugin_proxy.h>
+
+#include <cfl/cfl_sds.h>
+#include <cfl/cfl_variant.h>
+#include <cfl/cfl_kvlist.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define PLUGIN_PREFIX "flb-"
+#define PLUGIN_EXTENSION ".so"
+#define PLUGIN_STRUCT_SUFFIX "_plugin"
+#define PLUGIN_STR_MIN \
+ ((sizeof(PLUGIN_PREFIX) - 1) + sizeof(PLUGIN_EXTENSION) - 1)
+
+static int is_input(char *name)
+{
+ if (strncmp(name, "in_", 3) == 0) {
+ return FLB_TRUE;
+ }
+
+ return FLB_FALSE;
+}
+
+static int is_filter(char *name)
+{
+ if (strncmp(name, "filter_", 7) == 0) {
+ return FLB_TRUE;
+ }
+
+ return FLB_FALSE;
+}
+
+static int is_processor(char *name)
+{
+ if (strncmp(name, "processor_", 10) == 0) {
+ return FLB_TRUE;
+ }
+
+ return FLB_FALSE;
+}
+
+static int is_output(char *name)
+{
+ if (strncmp(name, "out_", 4) == 0) {
+ return FLB_TRUE;
+ }
+
+ return FLB_FALSE;
+}
+
+static void *get_handle(const char *path)
+{
+ void *handle;
+
+ handle = dlopen(path, RTLD_LAZY);
+ if (!handle) {
+ flb_error("[plugin] dlopen() %s", dlerror());
+ return NULL;
+ }
+
+ return handle;
+}
+
+static void *load_symbol(void *dso_handle, const char *symbol)
+{
+ void *s;
+
+ dlerror();
+ s = dlsym(dso_handle, symbol);
+ if (dlerror() != NULL) {
+ return NULL;
+ }
+ return s;
+}
+
+/*
+ * From a given path file (.so file), retrieve the expected structure name
+ * used to perform the plugin registration.
+ */
+static char *path_to_plugin_name(char *path)
+{
+ int len;
+ int o_len;
+ char *bname;
+ char *name;
+ char *p;
+
+ /* Get the basename of the file */
+ bname = basename(path);
+ if (!bname) {
+ flb_error("[plugin] could not resolve basename(3) of the plugin");
+ return NULL;
+ }
+ len = strlen(bname);
+
+ if (len < PLUGIN_STR_MIN) {
+ flb_error("[plugin] invalid plugin name: %s", bname);
+ return NULL;
+ }
+
+ if (strncmp(bname, PLUGIN_PREFIX, sizeof(PLUGIN_PREFIX) - 1) != 0) {
+ flb_error("[plugin] invalid plugin prefix: %s", bname);
+ return NULL;
+ }
+
+ if (strncmp(bname + len - (sizeof(PLUGIN_EXTENSION) - 1),
+ PLUGIN_EXTENSION, sizeof(PLUGIN_EXTENSION) - 1) != 0) {
+ flb_error("[plugin] invalid plugin extension: %s", bname);
+ return NULL;
+ }
+
+ /* Get the expected structure name */
+ name = flb_malloc(len + (sizeof(PLUGIN_STRUCT_SUFFIX) - 1) + 1);
+ if (!name) {
+ flb_errno();
+ return NULL;
+ }
+
+ /* Name without prefix */
+ p = bname + (sizeof(PLUGIN_PREFIX) - 1);
+ o_len = len - (sizeof(PLUGIN_PREFIX) - 1) - (sizeof(PLUGIN_EXTENSION) - 1);
+ memcpy(name, p, o_len);
+ name[o_len] = '\0';
+
+ /* Validate expected plugin type */
+ if (is_input(name) == FLB_FALSE &&
+ is_processor(name) == FLB_FALSE &&
+ is_filter(name) == FLB_FALSE &&
+ is_output(name) == FLB_FALSE) {
+ flb_error("[plugin] invalid plugin type: %s", name);
+ flb_free(name);
+ return NULL;
+ }
+
+ /* Append struct suffix */
+ p = name + o_len;
+ memcpy(p, PLUGIN_STRUCT_SUFFIX, sizeof(PLUGIN_STRUCT_SUFFIX) - 1);
+ o_len += sizeof(PLUGIN_STRUCT_SUFFIX) - 1;
+ name[o_len] = '\0';
+
+ return name;
+}
+
+static void destroy_plugin(struct flb_plugin *plugin)
+{
+ flb_sds_destroy(plugin->path);
+ dlclose(plugin->dso_handle);
+ mk_list_del(&plugin->_head);
+ flb_free(plugin);
+}
+
+/* Creates the global plugin context for 'dynamic plugins' */
+struct flb_plugins *flb_plugin_create()
+{
+ struct flb_plugins *ctx;
+
+ ctx = flb_malloc(sizeof(struct flb_plugins));
+ if (!ctx) {
+ flb_errno();
+ return NULL;
+ }
+
+ mk_list_init(&ctx->input);
+ mk_list_init(&ctx->processor);
+ mk_list_init(&ctx->filter);
+ mk_list_init(&ctx->output);
+
+ return ctx;
+}
+
+int flb_plugin_load(char *path, struct flb_plugins *ctx,
+ struct flb_config *config)
+{
+ int type = -1;
+ void *dso_handle;
+ void *symbol = NULL;
+ char *plugin_stname;
+ struct flb_plugin *plugin;
+ struct flb_input_plugin *input;
+ struct flb_processor_plugin *processor;
+ struct flb_filter_plugin *filter;
+ struct flb_output_plugin *output;
+
+ /* Open the shared object file: dlopen(3) */
+ dso_handle = get_handle(path);
+ if (!dso_handle) {
+ return -1;
+ }
+
+ /*
+ * Based on the shared object file name, compose the expected
+ * registration structure name.
+ */
+ plugin_stname = path_to_plugin_name(path);
+ if (!plugin_stname) {
+ dlclose(dso_handle);
+ return -1;
+ }
+
+ /* Get the registration structure */
+ symbol = load_symbol(dso_handle, plugin_stname);
+ if (!symbol) {
+ flb_error("[plugin] cannot load plugin '%s', "
+ "registration structure is missing '%s'",
+ path, plugin_stname);
+ flb_free(plugin_stname);
+ dlclose(dso_handle);
+ return -1;
+ }
+
+ /* Detect plugin type and link it to the main context */
+ if (is_input(plugin_stname) == FLB_TRUE) {
+ type = FLB_PLUGIN_INPUT;
+ input = flb_malloc(sizeof(struct flb_input_plugin));
+ if (!input) {
+ flb_errno();
+ flb_free(plugin_stname);
+ dlclose(dso_handle);
+ return -1;
+ }
+ memcpy(input, symbol, sizeof(struct flb_input_plugin));
+ mk_list_add(&input->_head, &config->in_plugins);
+ }
+ else if (is_processor(plugin_stname) == FLB_TRUE) {
+ type = FLB_PLUGIN_PROCESSOR;
+ processor = flb_malloc(sizeof(struct flb_processor_plugin));
+ if (processor == NULL) {
+ flb_errno();
+ flb_free(plugin_stname);
+ dlclose(dso_handle);
+ return -1;
+ }
+ memcpy(processor, symbol, sizeof(struct flb_processor_plugin));
+ mk_list_add(&processor->_head, &config->processor_plugins);
+ }
+ else if (is_filter(plugin_stname) == FLB_TRUE) {
+ type = FLB_PLUGIN_FILTER;
+ filter = flb_malloc(sizeof(struct flb_filter_plugin));
+ if (!filter) {
+ flb_errno();
+ flb_free(plugin_stname);
+ dlclose(dso_handle);
+ return -1;
+ }
+ memcpy(filter, symbol, sizeof(struct flb_filter_plugin));
+ mk_list_add(&filter->_head, &config->filter_plugins);
+ }
+ else if (is_output(plugin_stname) == FLB_TRUE) {
+ type = FLB_PLUGIN_OUTPUT;
+ output = flb_malloc(sizeof(struct flb_output_plugin));
+ if (!output) {
+ flb_errno();
+ flb_free(plugin_stname);
+ dlclose(dso_handle);
+ return -1;
+ }
+ memcpy(output, symbol, sizeof(struct flb_output_plugin));
+ mk_list_add(&output->_head, &config->out_plugins);
+ }
+ flb_free(plugin_stname);
+
+ if (type == -1) {
+ flb_error("[plugin] plugin type not defined on '%s'", path);
+ dlclose(dso_handle);
+ return -1;
+ }
+
+ /* Create plugin context (internal reference only) */
+ plugin = flb_malloc(sizeof(struct flb_plugin));
+ if (!plugin) {
+ flb_errno();
+ dlclose(dso_handle);
+ return -1;
+ }
+
+ plugin->type = type;
+ plugin->path = flb_sds_create(path);
+ plugin->dso_handle = dso_handle;
+
+ /* Link by type to the plugins parent context */
+ if (type == FLB_PLUGIN_INPUT) {
+ mk_list_add(&plugin->_head, &ctx->input);
+ }
+ else if (type == FLB_PLUGIN_PROCESSOR) {
+ mk_list_add(&plugin->_head, &ctx->processor);
+ }
+ else if (type == FLB_PLUGIN_FILTER) {
+ mk_list_add(&plugin->_head, &ctx->filter);
+ }
+ else if (type == FLB_PLUGIN_OUTPUT) {
+ mk_list_add(&plugin->_head, &ctx->output);
+ }
+
+ return 0;
+}
+
+int flb_plugin_load_router(char *path, struct flb_config *config)
+{
+ int ret = -1;
+ char *bname;
+
+ bname = basename(path);
+
+ /* Is this a DSO C plugin ? */
+ if (strncmp(bname, PLUGIN_PREFIX, sizeof(PLUGIN_PREFIX) - 1) == 0) {
+ ret = flb_plugin_load(path, config->dso_plugins, config);
+ if (ret == -1) {
+ flb_error("[plugin] error loading DSO C plugin: %s", path);
+ return -1;
+ }
+ }
+ else {
+#ifdef FLB_HAVE_PROXY_GO
+ if (flb_plugin_proxy_create(path, 0, config) == NULL) {
+ flb_error("[plugin] error loading proxy plugin: %s", path);
+ return -1;
+ }
+#else
+ flb_error("[plugin] unsupported plugin type at: %s", path);
+ return -1;
+#endif
+ }
+
+ return 0;
+}
+
+/* Load plugins from a configuration file */
+int flb_plugin_load_config_file(const char *file, struct flb_config *config)
+{
+ int ret;
+ char tmp[PATH_MAX + 1];
+ char *cfg = NULL;
+ struct mk_list *head;
+ struct cfl_list *head_e;
+ struct stat st;
+ struct flb_cf *cf;
+ struct flb_cf_section *section;
+ struct cfl_kvpair *entry;
+
+#ifndef FLB_HAVE_STATIC_CONF
+ ret = stat(file, &st);
+ if (ret == -1 && errno == ENOENT) {
+ /* Try to resolve the real path (if exists) */
+ if (file[0] == '/') {
+ flb_utils_error(FLB_ERR_CFG_PLUGIN_FILE);
+ return -1;
+ }
+
+ if (config->conf_path) {
+ snprintf(tmp, PATH_MAX, "%s%s", config->conf_path, file);
+ cfg = tmp;
+ }
+ }
+ else {
+ cfg = (char *) file;
+ }
+
+ flb_debug("[plugin] opening configuration file %s", cfg);
+
+ cf = flb_cf_create_from_file(NULL, cfg);
+#else
+ cf = flb_config_static_open(file);
+#endif
+
+ if (!cf) {
+ return -1;
+ }
+
+ /* read all 'plugins' sections */
+ mk_list_foreach(head, &cf->sections) {
+ section = mk_list_entry(head, struct flb_cf_section, _head);
+ if (strcasecmp(section->name, "plugins") != 0) {
+ continue;
+ }
+
+ cfl_list_foreach(head_e, &section->properties->list) {
+ entry = cfl_list_entry(head_e, struct cfl_kvpair, _head);
+ if (strcasecmp(entry->key, "path") != 0) {
+ continue;
+ }
+
+ /* Load plugin with router function */
+ ret = flb_plugin_load_router(entry->val->data.as_string, config);
+ if (ret == -1) {
+ flb_cf_destroy(cf);
+ return -1;
+ }
+ }
+ }
+
+ flb_cf_destroy(cf);
+ return 0;
+}
+
+/* Destroy plugin context */
+void flb_plugin_destroy(struct flb_plugins *ctx)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_plugin *plugin;
+
+ mk_list_foreach_safe(head, tmp, &ctx->input) {
+ plugin = mk_list_entry(head, struct flb_plugin, _head);
+ destroy_plugin(plugin);
+ }
+
+ mk_list_foreach_safe(head, tmp, &ctx->processor) {
+ plugin = mk_list_entry(head, struct flb_plugin, _head);
+ destroy_plugin(plugin);
+ }
+
+ mk_list_foreach_safe(head, tmp, &ctx->filter) {
+ plugin = mk_list_entry(head, struct flb_plugin, _head);
+ destroy_plugin(plugin);
+ }
+
+ mk_list_foreach_safe(head, tmp, &ctx->output) {
+ plugin = mk_list_entry(head, struct flb_plugin, _head);
+ destroy_plugin(plugin);
+ }
+
+ flb_free(ctx);
+}
diff --git a/fluent-bit/src/flb_plugin_proxy.c b/fluent-bit/src/flb_plugin_proxy.c
new file mode 100644
index 000000000..440c54525
--- /dev/null
+++ b/fluent-bit/src/flb_plugin_proxy.c
@@ -0,0 +1,498 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <monkey/mk_core.h>
+#include <fluent-bit/flb_compat.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_api.h>
+#include <fluent-bit/flb_error.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_plugin_proxy.h>
+#include <fluent-bit/flb_input_log.h>
+
+/* Proxies */
+#include "proxy/go/go.h"
+
+#define PROXY_CALLBACK_TIME 1 /* 1 seconds */
+
+static void proxy_cb_flush(struct flb_event_chunk *event_chunk,
+ struct flb_output_flush *out_flush,
+ struct flb_input_instance *i_ins,
+ void *out_context,
+ struct flb_config *config)
+{
+ int ret = FLB_ERROR;
+ struct flb_plugin_proxy_context *ctx = out_context;
+ (void) i_ins;
+ (void) config;
+
+
+#ifdef FLB_HAVE_PROXY_GO
+ if (ctx->proxy->def->proxy == FLB_PROXY_GOLANG) {
+ flb_trace("[GO] entering go_flush()");
+ ret = proxy_go_output_flush(ctx,
+ event_chunk->data,
+ event_chunk->size,
+ event_chunk->tag,
+ flb_sds_len(event_chunk->tag));
+ }
+#else
+ (void) ctx;
+#endif
+
+ if (ret != FLB_OK && ret != FLB_RETRY && ret != FLB_ERROR) {
+ FLB_OUTPUT_RETURN(FLB_ERROR);
+ }
+
+ FLB_OUTPUT_RETURN(ret);
+}
+
+static int flb_proxy_input_cb_collect(struct flb_input_instance *ins,
+ struct flb_config *config, void *in_context)
+{
+ int ret = FLB_OK;
+ size_t len = 0;
+ void *data = NULL;
+ struct flb_plugin_input_proxy_context *ctx = (struct flb_plugin_input_proxy_context *) in_context;
+
+#ifdef FLB_HAVE_PROXY_GO
+ if (ctx->proxy->def->proxy == FLB_PROXY_GOLANG) {
+ flb_trace("[GO] entering go_collect()");
+ ret = proxy_go_input_collect(ctx->proxy, &data, &len);
+
+ if (len == 0) {
+ flb_trace("[GO] No logs are ingested");
+ return -1;
+ }
+
+ if (ret == -1) {
+ flb_errno();
+ return -1;
+ }
+
+ flb_input_log_append(ins, NULL, 0, data, len);
+
+ ret = proxy_go_input_cleanup(ctx->proxy, data);
+ if (ret == -1) {
+ flb_errno();
+ return -1;
+ }
+ }
+#endif
+
+ return 0;
+}
+
+static int flb_proxy_input_cb_init(struct flb_input_instance *ins,
+ struct flb_config *config, void *data)
+{
+ int ret = -1;
+ struct flb_plugin_input_proxy_context *ctx;
+ struct flb_plugin_proxy_context *pc;
+
+ /* Allocate space for the configuration context */
+ ctx = flb_malloc(sizeof(struct flb_plugin_input_proxy_context));
+ if (!ctx) {
+ flb_errno();
+ return -1;
+ }
+
+ /* Before to initialize for proxy, set the proxy instance reference */
+ pc = (struct flb_plugin_proxy_context *)(ins->context);
+ ctx->proxy = pc->proxy;
+
+ /* Before to initialize, set the instance reference */
+ pc->proxy->instance = ins;
+
+ /* Based on 'proxy', use the proper handler */
+ if (pc->proxy->def->proxy == FLB_PROXY_GOLANG) {
+#ifdef FLB_HAVE_PROXY_GO
+ ret = proxy_go_input_init(pc->proxy);
+
+ if (ret == -1) {
+ flb_error("Could not initialize proxy for threaded input plugin");
+ goto init_error;
+ }
+#else
+ flb_error("Could not find initializing function on proxy for threaded input plugin");
+ goto init_error;
+#endif
+ }
+ else {
+ flb_error("[proxy] unrecognized input proxy handler %i",
+ pc->proxy->def->proxy);
+ }
+
+ /* Set the context */
+ flb_input_set_context(ins, ctx);
+
+ /* Collect upon data available on timer */
+ ret = flb_input_set_collector_time(ins,
+ flb_proxy_input_cb_collect,
+ PROXY_CALLBACK_TIME, 0,
+ config);
+
+ if (ret == -1) {
+ flb_error("Could not set collector for threaded proxy input plugin");
+ goto init_error;
+ }
+ ctx->coll_fd = ret;
+
+ return ret;
+
+init_error:
+ flb_free(ctx);
+
+ return -1;
+}
+
+static void flb_proxy_input_cb_pause(void *data, struct flb_config *config)
+{
+ struct flb_plugin_input_proxy_context *ctx = data;
+
+ flb_input_collector_pause(ctx->coll_fd, ctx->proxy->instance);
+}
+
+static void flb_proxy_input_cb_resume(void *data, struct flb_config *config)
+{
+ struct flb_plugin_input_proxy_context *ctx = data;
+
+ flb_input_collector_resume(ctx->coll_fd, ctx->proxy->instance);
+}
+
+static void flb_plugin_proxy_destroy(struct flb_plugin_proxy *proxy);
+
+static int flb_proxy_output_cb_exit(void *out_context, struct flb_config *config)
+{
+ struct flb_plugin_proxy_context *ctx = out_context;
+ struct flb_plugin_proxy *proxy = (ctx->proxy);
+
+ if (!out_context) {
+ return 0;
+ }
+
+ if (proxy->def->proxy == FLB_PROXY_GOLANG) {
+#ifdef FLB_HAVE_PROXY_GO
+ proxy_go_output_destroy(ctx);
+#endif
+ }
+
+ flb_free(ctx);
+ return 0;
+}
+
+static void flb_proxy_output_cb_destroy(struct flb_output_plugin *plugin)
+{
+ struct flb_plugin_proxy *proxy = (struct flb_plugin_proxy *) plugin->proxy;
+ /* cleanup */
+ void (*cb_unregister)(struct flb_plugin_proxy_def *def);
+
+ cb_unregister = flb_plugin_proxy_symbol(proxy, "FLBPluginUnregister");
+ if (cb_unregister != NULL) {
+ cb_unregister(proxy->def);
+ }
+
+ if (proxy->def->proxy == FLB_PROXY_GOLANG) {
+#ifdef FLB_HAVE_PROXY_GO
+ proxy_go_output_unregister(proxy->data);
+#endif
+ }
+
+ flb_plugin_proxy_destroy(proxy);
+}
+
+static int flb_proxy_input_cb_exit(void *in_context, struct flb_config *config)
+{
+ struct flb_plugin_input_proxy_context *ctx = in_context;
+ struct flb_plugin_proxy *proxy = (ctx->proxy);
+
+ if (!in_context) {
+ return 0;
+ }
+
+ if (proxy->def->proxy == FLB_PROXY_GOLANG) {
+#ifdef FLB_HAVE_PROXY_GO
+ proxy_go_input_destroy(ctx);
+#endif
+ }
+
+ flb_free(ctx);
+ return 0;
+}
+
+static void flb_proxy_input_cb_destroy(struct flb_input_plugin *plugin)
+{
+ struct flb_plugin_proxy *proxy = (struct flb_plugin_proxy *) plugin->proxy;
+ /* cleanup */
+ void (*cb_unregister)(struct flb_plugin_proxy_def *def);
+
+ cb_unregister = flb_plugin_proxy_symbol(proxy, "FLBPluginUnregister");
+ if (cb_unregister != NULL) {
+ cb_unregister(proxy->def);
+ }
+
+ if (proxy->def->proxy == FLB_PROXY_GOLANG) {
+#ifdef FLB_HAVE_PROXY_GO
+ proxy_go_input_unregister(proxy->data);
+#endif
+ }
+
+ flb_plugin_proxy_destroy(proxy);
+}
+
+static int flb_proxy_register_output(struct flb_plugin_proxy *proxy,
+ struct flb_plugin_proxy_def *def,
+ struct flb_config *config)
+{
+ struct flb_output_plugin *out;
+
+ out = flb_calloc(1, sizeof(struct flb_output_plugin));
+ if (!out) {
+ flb_errno();
+ return -1;
+ }
+
+ /* Plugin registration */
+ out->type = FLB_OUTPUT_PLUGIN_PROXY;
+ out->proxy = proxy;
+ out->flags = def->flags;
+ out->name = def->name;
+ out->description = def->description;
+ mk_list_add(&out->_head, &config->out_plugins);
+
+ /*
+ * Set proxy callbacks: external plugins which are not following
+ * the core plugins specs, have a different callback approach, so
+ * we put our proxy-middle callbacks to do the translation properly.
+ */
+ out->cb_flush = proxy_cb_flush;
+ out->cb_exit = flb_proxy_output_cb_exit;
+ out->cb_destroy = flb_proxy_output_cb_destroy;
+ return 0;
+}
+
+static int flb_proxy_register_input(struct flb_plugin_proxy *proxy,
+ struct flb_plugin_proxy_def *def,
+ struct flb_config *config)
+{
+ struct flb_input_plugin *in;
+
+ in = flb_calloc(1, sizeof(struct flb_input_plugin));
+ if (!in) {
+ flb_errno();
+ return -1;
+ }
+
+ /* Plugin registration */
+ in->type = FLB_INPUT_PLUGIN_PROXY;
+ in->proxy = proxy;
+ in->flags = def->flags | FLB_INPUT_THREADED;
+ in->name = flb_strdup(def->name);
+ in->description = def->description;
+ mk_list_add(&in->_head, &config->in_plugins);
+
+ /*
+ * Set proxy callbacks: external plugins which are not following
+ * the core plugins specs, have a different callback approach, so
+ * we put our proxy-middle callbacks to do the translation properly.
+ */
+ in->cb_init = flb_proxy_input_cb_init;
+ in->cb_collect = flb_proxy_input_cb_collect;
+ in->cb_flush_buf = NULL;
+ in->cb_exit = flb_proxy_input_cb_exit;
+ in->cb_destroy = flb_proxy_input_cb_destroy;
+ in->cb_pause = flb_proxy_input_cb_pause;
+ in->cb_resume = flb_proxy_input_cb_resume;
+ return 0;
+}
+
+void *flb_plugin_proxy_symbol(struct flb_plugin_proxy *proxy,
+ const char *symbol)
+{
+ void *s;
+
+ dlerror();
+ s = dlsym(proxy->dso_handler, symbol);
+ if (dlerror() != NULL) {
+ return NULL;
+ }
+ return s;
+}
+
+int flb_plugin_proxy_register(struct flb_plugin_proxy *proxy,
+ struct flb_config *config)
+{
+ int ret;
+ int (*cb_register)(struct flb_plugin_proxy_def *);
+ struct flb_plugin_proxy_def *def = proxy->def;
+
+ /* Lookup the registration callback */
+ cb_register = flb_plugin_proxy_symbol(proxy, "FLBPluginRegister");
+ if (!cb_register) {
+ return -1;
+ }
+
+ /*
+ * Create a temporary definition used for registration. This definition
+ * aims to be be populated by plugin in the registration phase with:
+ *
+ * - plugin type (or proxy type, e.g: Golang)
+ * - plugin name
+ * - plugin description
+ */
+
+ /* Do the registration */
+ ret = cb_register(def);
+ if (ret == -1) {
+ flb_free(def);
+ return -1;
+ }
+
+ /*
+ * Each plugin proxy/type, have their own handler, based on the data
+ * provided in the registration invoke the proper handler.
+ */
+ ret = -1;
+ if (def->proxy == FLB_PROXY_GOLANG) {
+#ifdef FLB_HAVE_PROXY_GO
+ if (def->type == FLB_PROXY_OUTPUT_PLUGIN) {
+ ret = proxy_go_output_register(proxy, def);
+ }
+ else if (def->type == FLB_PROXY_INPUT_PLUGIN) {
+ ret = proxy_go_input_register(proxy, def);
+ }
+#endif
+ }
+ if (ret == 0) {
+ /*
+ * We got a plugin that can do it job, now we need to create the
+ * real link to the 'output' interface
+ */
+ if (def->type == FLB_PROXY_OUTPUT_PLUGIN) {
+ flb_proxy_register_output(proxy, def, config);
+ }
+ else if (def->type == FLB_PROXY_INPUT_PLUGIN) {
+ flb_proxy_register_input(proxy, def, config);
+ }
+ }
+
+ return 0;
+}
+
+int flb_plugin_proxy_output_init(struct flb_plugin_proxy *proxy,
+ struct flb_output_instance *o_ins,
+ struct flb_config *config)
+{
+ int ret = -1;
+
+ /* Before to initialize, set the instance reference */
+ proxy->instance = o_ins;
+
+ /* Based on 'proxy', use the proper handler */
+ if (proxy->def->proxy == FLB_PROXY_GOLANG) {
+#ifdef FLB_HAVE_PROXY_GO
+ ret = proxy_go_output_init(proxy);
+#endif
+ }
+ else {
+ flb_error("[proxy] unrecognized proxy handler %i",
+ proxy->def->proxy);
+ }
+
+ return ret;
+}
+
+struct flb_plugin_proxy *flb_plugin_proxy_create(const char *dso_path, int type,
+ struct flb_config *config)
+{
+ void *handle;
+ struct flb_plugin_proxy *proxy;
+
+ /* Load shared library */
+ handle = dlopen(dso_path, RTLD_LAZY);
+ if (!handle) {
+ flb_error("[proxy] error opening plugin %s: '%s'",
+ dso_path, dlerror());
+ return NULL;
+ }
+
+ /* Proxy Context */
+ proxy = flb_malloc(sizeof(struct flb_plugin_proxy));
+ if (!proxy) {
+ flb_errno();
+ dlclose(handle);
+ return NULL;
+ }
+
+ /* API Context */
+ proxy->api = flb_api_create();
+ if (!proxy->api) {
+ dlclose(handle);
+ flb_free(proxy);
+ return NULL;
+ }
+
+ proxy->def = flb_malloc(sizeof(struct flb_plugin_proxy_def));
+ if (!proxy->def) {
+ flb_errno();
+ dlclose(handle);
+ flb_api_destroy(proxy->api);
+ flb_free(proxy);
+ return NULL;
+ }
+
+ /* Set fields and add it to the list */
+ proxy->def->type = type;
+ proxy->dso_handler = handle;
+ proxy->data = NULL;
+ mk_list_add(&proxy->_head, &config->proxies);
+
+ /* Register plugin */
+ flb_plugin_proxy_register(proxy, config);
+
+ return proxy;
+}
+
+static void flb_plugin_proxy_destroy(struct flb_plugin_proxy *proxy)
+{
+ flb_free(proxy->def);
+ flb_api_destroy(proxy->api);
+ dlclose(proxy->dso_handler);
+ mk_list_del(&proxy->_head);
+ flb_free(proxy);
+}
+
+int flb_plugin_proxy_set(struct flb_plugin_proxy_def *def, int type,
+ int proxy, char *name, char *description)
+{
+ def->type = type;
+ def->proxy = proxy;
+ def->name = flb_strdup(name);
+ def->description = flb_strdup(description);
+
+ return 0;
+}
diff --git a/fluent-bit/src/flb_processor.c b/fluent-bit/src/flb_processor.c
new file mode 100644
index 000000000..c46bc16a5
--- /dev/null
+++ b/fluent-bit/src/flb_processor.c
@@ -0,0 +1,1129 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2023 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_env.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_event.h>
+#include <fluent-bit/flb_processor.h>
+#include <fluent-bit/flb_processor_plugin.h>
+#include <fluent-bit/flb_filter.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_log_event_decoder.h>
+#include <fluent-bit/flb_log_event_encoder.h>
+
+static int acquire_lock(pthread_mutex_t *lock,
+ size_t retry_limit,
+ size_t retry_delay)
+{
+ size_t retry_count;
+ int result;
+
+ retry_count = 0;
+
+ do {
+ result = pthread_mutex_lock(lock);
+
+ if (result != 0) {
+
+ if (result == EAGAIN) {
+ retry_count++;
+
+ usleep(retry_delay);
+ }
+ else {
+ break;
+ }
+ }
+ }
+ while (result != 0 &&
+ retry_count < retry_limit);
+
+ if (result != 0) {
+ return FLB_FALSE;
+ }
+
+ return FLB_TRUE;
+}
+
+static int release_lock(pthread_mutex_t *lock,
+ size_t retry_limit,
+ size_t retry_delay)
+{
+ size_t retry_count;
+ int result;
+
+ retry_count = 0;
+
+ do {
+ result = pthread_mutex_unlock(lock);
+
+ if (result != 0) {
+
+ if (result == EAGAIN) {
+ retry_count++;
+
+ usleep(retry_delay);
+ }
+ else {
+ break;
+ }
+ }
+ }
+ while (result != 0 &&
+ retry_count < retry_limit);
+
+ if (result != 0) {
+ return FLB_FALSE;
+ }
+
+ return FLB_TRUE;
+}
+
+/*
+ * A processor creates a chain of processing units for different telemetry data
+ * types such as logs, metrics and traces.
+ *
+ * From a design perspective, a Processor can be run independently from inputs, outputs
+ * or unit tests directly.
+ */
+struct flb_processor *flb_processor_create(struct flb_config *config,
+ char *name,
+ void *source_plugin_instance,
+ int source_plugin_type)
+{
+ struct flb_processor *proc;
+
+ proc = flb_calloc(1, sizeof(struct flb_processor));
+
+ if (!proc) {
+ flb_errno();
+ return NULL;
+ }
+
+ proc->config = config;
+ proc->is_active = FLB_FALSE;
+ proc->data = source_plugin_instance;
+ proc->source_plugin_type = source_plugin_type;
+
+ /* lists for types */
+ mk_list_init(&proc->logs);
+ mk_list_init(&proc->metrics);
+ mk_list_init(&proc->traces);
+
+ return proc;
+}
+
+struct flb_processor_unit *flb_processor_unit_create(struct flb_processor *proc,
+ int event_type,
+ char *unit_name)
+{
+ int result;
+ struct mk_list *head;
+ int filter_event_type;
+ struct flb_filter_plugin *f = NULL;
+ struct flb_filter_instance *f_ins;
+ struct flb_config *config = proc->config;
+ struct flb_processor_unit *pu = NULL;
+ struct flb_processor_instance *processor_instance;
+
+ /*
+ * Looking the processor unit by using it's name and type, the first list we
+ * will iterate are the common pipeline filters.
+ */
+ mk_list_foreach(head, &config->filter_plugins) {
+ f = mk_list_entry(head, struct flb_filter_plugin, _head);
+
+ filter_event_type = f->event_type;
+
+ if (filter_event_type == 0) {
+ filter_event_type = FLB_FILTER_LOGS;
+ }
+
+ /* skip filters which don't handle the required type */
+ if ((event_type & filter_event_type) != 0) {
+ if (strcmp(f->name, unit_name) == 0) {
+ break;
+ }
+ }
+
+ f = NULL;
+ }
+
+ /* allocate and initialize processor unit context */
+ pu = flb_calloc(1, sizeof(struct flb_processor_unit));
+
+ if (!pu) {
+ flb_errno();
+ return NULL;
+ }
+
+ pu->parent = proc;
+ pu->event_type = event_type;
+ pu->name = flb_sds_create(unit_name);
+
+ if (!pu->name) {
+ flb_free(pu);
+ return NULL;
+ }
+ mk_list_init(&pu->unused_list);
+
+ result = pthread_mutex_init(&pu->lock, NULL);
+
+ if (result != 0) {
+ flb_sds_destroy(pu->name);
+ flb_free(pu);
+
+ return NULL;
+ }
+
+ /* If we matched a pipeline filter, create the speacial processing unit for it */
+ if (f) {
+ /* create an instance of the filter */
+ f_ins = flb_filter_new(config, unit_name, NULL);
+
+ if (!f_ins) {
+ pthread_mutex_destroy(&pu->lock);
+ flb_sds_destroy(pu->name);
+ flb_free(pu);
+
+ return NULL;
+ }
+
+ f_ins->parent_processor = (void *) pu;
+
+ /* matching rule: just set to workaround the pipeline initializer */
+ f_ins->match = flb_sds_create("*");
+
+ if (f_ins->match == NULL) {
+ flb_filter_instance_destroy(f_ins);
+
+ pthread_mutex_destroy(&pu->lock);
+ flb_sds_destroy(pu->name);
+ flb_free(pu);
+
+ return NULL;
+ }
+
+ /* unit type and context */
+ pu->unit_type = FLB_PROCESSOR_UNIT_FILTER;
+ pu->ctx = f_ins;
+
+ /*
+ * The filter was added to the linked list config->filters, since this filter
+ * won't run as part of the normal pipeline, we just unlink the node.
+ */
+ mk_list_del(&f_ins->_head);
+
+ /* link the filter to the unused list */
+ mk_list_add(&f_ins->_head, &pu->unused_list);
+ }
+ else {
+ pu->unit_type = FLB_PROCESSOR_UNIT_NATIVE;
+
+ /* create an instance of the processor */
+ processor_instance = flb_processor_instance_create(config, unit_name, NULL);
+
+ if (processor_instance == NULL) {
+ flb_error("[processor] error creating native processor instance %s", pu->name);
+
+ pthread_mutex_destroy(&pu->lock);
+ flb_sds_destroy(pu->name);
+ flb_free(pu);
+
+ return NULL;
+ }
+
+ /* unit type and context */
+ pu->ctx = (void *) processor_instance;
+ }
+
+ /* Link the processor unit to the proper list */
+ if (event_type == FLB_PROCESSOR_LOGS) {
+ mk_list_add(&pu->_head, &proc->logs);
+ }
+ else if (event_type == FLB_PROCESSOR_METRICS) {
+ mk_list_add(&pu->_head, &proc->metrics);
+ }
+ else if (event_type == FLB_PROCESSOR_TRACES) {
+ mk_list_add(&pu->_head, &proc->traces);
+ }
+
+ pu->stage = proc->stage_count;
+ proc->stage_count++;
+
+ return pu;
+}
+
+int flb_processor_unit_set_property(struct flb_processor_unit *pu, const char *k, struct cfl_variant *v)
+{
+ struct cfl_variant *val;
+ int i;
+ int ret;
+
+ if (pu->unit_type == FLB_PROCESSOR_UNIT_FILTER) {
+
+ if (v->type == CFL_VARIANT_STRING) {
+ return flb_filter_set_property(pu->ctx, k, v->data.as_string);
+ }
+ else if (v->type == CFL_VARIANT_ARRAY) {
+
+ for (i = 0; i < v->data.as_array->entry_count; i++) {
+ val = v->data.as_array->entries[i];
+ ret = flb_filter_set_property(pu->ctx, k, val->data.as_string);
+
+ if (ret == -1) {
+ return ret;
+ }
+ }
+ return 0;
+ }
+ }
+
+ return flb_processor_instance_set_property(
+ (struct flb_processor_instance *) pu->ctx,
+ k, v->data.as_string);
+}
+
+void flb_processor_unit_destroy(struct flb_processor_unit *pu)
+{
+ struct flb_processor *proc = pu->parent;
+ struct flb_config *config = proc->config;
+
+ if (pu->unit_type == FLB_PROCESSOR_UNIT_FILTER) {
+ flb_filter_instance_exit(pu->ctx, config);
+ flb_filter_instance_destroy(pu->ctx);
+ }
+ else {
+ flb_processor_instance_exit(
+ (struct flb_processor_instance *) pu->ctx,
+ config);
+
+ flb_processor_instance_destroy(
+ (struct flb_processor_instance *) pu->ctx);
+ }
+
+ pthread_mutex_destroy(&pu->lock);
+
+ flb_sds_destroy(pu->name);
+ flb_free(pu);
+}
+
+/* Initialize a specific unit */
+int flb_processor_unit_init(struct flb_processor_unit *pu)
+{
+ int ret = -1;
+ struct flb_config;
+ struct flb_processor *proc = pu->parent;
+
+ if (pu->unit_type == FLB_PROCESSOR_UNIT_FILTER) {
+ ret = flb_filter_init(proc->config, pu->ctx);
+
+ if (ret == -1) {
+ flb_error("[processor] error initializing unit filter %s", pu->name);
+ return -1;
+ }
+ }
+ else {
+ ret = flb_processor_instance_init(
+ (struct flb_processor_instance *) pu->ctx,
+ proc->data,
+ 0,
+ proc->config);
+
+ if (ret == -1) {
+ flb_error("[processor] error initializing unit native processor "
+ "%s", pu->name);
+
+ return -1;
+ }
+ }
+
+ return ret;
+}
+
+/* Initialize the processor and all the units */
+int flb_processor_init(struct flb_processor *proc)
+{
+ int ret;
+ int count = 0;
+ struct mk_list *head;
+ struct flb_processor_unit *pu;
+
+ /* Go through every unit and initialize it */
+ mk_list_foreach(head, &proc->logs) {
+ pu = mk_list_entry(head, struct flb_processor_unit, _head);
+ ret = flb_processor_unit_init(pu);
+
+ if (ret == -1) {
+ return -1;
+ }
+ count++;
+ }
+
+ mk_list_foreach(head, &proc->metrics) {
+ pu = mk_list_entry(head, struct flb_processor_unit, _head);
+ ret = flb_processor_unit_init(pu);
+
+ if (ret == -1) {
+ return -1;
+ }
+ count++;
+ }
+
+ mk_list_foreach(head, &proc->traces) {
+ pu = mk_list_entry(head, struct flb_processor_unit, _head);
+ ret = flb_processor_unit_init(pu);
+
+ if (ret == -1) {
+ return -1;
+ }
+ count++;
+ }
+
+ if (count > 0) {
+ proc->is_active = FLB_TRUE;
+ }
+ return 0;
+}
+
+int flb_processor_is_active(struct flb_processor *proc)
+{
+ if (proc->is_active) {
+ return FLB_TRUE;
+ }
+
+ return FLB_FALSE;
+}
+
+/*
+ * This function will run all the processor units for the given tag and data, note
+ * that depending of the 'type', 'data' can reference a msgpack for logs, a CMetrics
+ * context for metrics or a 'CTraces' context for traces.
+ */
+int flb_processor_run(struct flb_processor *proc,
+ size_t starting_stage,
+ int type,
+ const char *tag, size_t tag_len,
+ void *data, size_t data_size,
+ void **out_buf, size_t *out_size)
+{
+ int ret;
+ void *cur_buf;
+ size_t cur_size;
+ void *tmp_buf;
+ size_t tmp_size;
+ int decoder_result;
+ struct mk_list *head;
+ struct mk_list *list = NULL;
+ struct flb_log_event log_event;
+ struct flb_processor_unit *pu;
+ struct flb_filter_instance *f_ins;
+ struct flb_processor_instance *p_ins;
+
+ if (type == FLB_PROCESSOR_LOGS) {
+ list = &proc->logs;
+ }
+ else if (type == FLB_PROCESSOR_METRICS) {
+ list = &proc->metrics;
+ }
+ else if (type == FLB_PROCESSOR_TRACES) {
+ list = &proc->traces;
+ }
+
+ /* set current data buffer */
+ cur_buf = data;
+ cur_size = data_size;
+
+ /* iterate list units */
+ mk_list_foreach(head, list) {
+ pu = mk_list_entry(head, struct flb_processor_unit, _head);
+
+ /* This is meant to be used when filters or processors re-inject
+ * records in the pipeline. This way we can ensure that they will
+ * continue the process at the right stage.
+ */
+ if (pu->stage < starting_stage) {
+ continue;
+ }
+
+ tmp_buf = NULL;
+ tmp_size = 0;
+
+ ret = acquire_lock(&pu->lock,
+ FLB_PROCESSOR_LOCK_RETRY_LIMIT,
+ FLB_PROCESSOR_LOCK_RETRY_DELAY);
+
+ if (ret != FLB_TRUE) {
+ return -1;
+ }
+
+ /* run the unit */
+ if (pu->unit_type == FLB_PROCESSOR_UNIT_FILTER) {
+ /* get the filter context */
+ f_ins = pu->ctx;
+
+ /* run the filtering callback */
+ ret = f_ins->p->cb_filter(cur_buf, cur_size, /* msgpack buffer */
+ tag, tag_len, /* tag */
+ &tmp_buf, &tmp_size, /* output buffer */
+ f_ins, /* filter instance */
+ proc->data, /* (input/output) instance context */
+ f_ins->context, /* filter context */
+ proc->config);
+
+ /*
+ * The cb_filter() function return status tells us if something changed
+ * during it process. The possible values are:
+ *
+ * - FLB_FILTER_MODIFIED: the record was modified and the output buffer
+ * contains the new record.
+ *
+ * - FLB_FILTER_NOTOUCH: the record was not modified.
+ *
+ */
+ if (ret == FLB_FILTER_MODIFIED) {
+
+ /* release intermediate buffer */
+ if (cur_buf != data) {
+ flb_free(cur_buf);
+ }
+
+ /*
+ * if the content has been modified and the returned size is zero, it means
+ * the whole content has been dropped, on this case we just return since
+ * no more data exists to be processed.
+ */
+ if (tmp_size == 0) {
+ *out_buf = NULL;
+ *out_size = 0;
+
+ release_lock(&pu->lock,
+ FLB_PROCESSOR_LOCK_RETRY_LIMIT,
+ FLB_PROCESSOR_LOCK_RETRY_DELAY);
+
+ return 0;
+ }
+
+ /* set new buffer */
+ cur_buf = tmp_buf;
+ cur_size = tmp_size;
+ }
+ else if (ret == FLB_FILTER_NOTOUCH) {
+ /* keep original data, do nothing */
+ }
+ }
+ else {
+ /* get the processor context */
+ p_ins = pu->ctx;
+
+ ret = 0;
+
+ /* run the process callback */
+ if (type == FLB_PROCESSOR_LOGS) {
+ if (p_ins->p->cb_process_logs != NULL) {
+ flb_log_event_encoder_reset(p_ins->log_encoder);
+
+ decoder_result = flb_log_event_decoder_init(
+ p_ins->log_decoder, cur_buf, cur_size);
+
+ if (decoder_result != FLB_EVENT_DECODER_SUCCESS) {
+ flb_log_event_decoder_reset(p_ins->log_decoder, NULL, 0);
+
+ if (cur_buf != data) {
+ flb_free(cur_buf);
+ }
+
+ release_lock(&pu->lock,
+ FLB_PROCESSOR_LOCK_RETRY_LIMIT,
+ FLB_PROCESSOR_LOCK_RETRY_DELAY);
+
+ return -1;
+ }
+
+ ret = FLB_PROCESSOR_SUCCESS;
+
+ do {
+ decoder_result = flb_log_event_decoder_next(
+ p_ins->log_decoder,
+ &log_event);
+
+ if (decoder_result == FLB_EVENT_DECODER_SUCCESS) {
+ ret = p_ins->p->cb_process_logs(p_ins,
+ p_ins->log_encoder,
+ &log_event,
+ tag, tag_len);
+ }
+ }
+ while (decoder_result == FLB_EVENT_DECODER_SUCCESS &&
+ ret == FLB_PROCESSOR_SUCCESS);
+
+ flb_log_event_decoder_reset(p_ins->log_decoder, NULL, 0);
+
+ if (cur_buf != data) {
+ flb_free(cur_buf);
+ }
+
+ if (ret != FLB_PROCESSOR_SUCCESS) {
+ flb_log_event_encoder_reset(p_ins->log_encoder);
+
+ release_lock(&pu->lock,
+ FLB_PROCESSOR_LOCK_RETRY_LIMIT,
+ FLB_PROCESSOR_LOCK_RETRY_DELAY);
+
+ return -1;
+ }
+
+ if (p_ins->log_encoder->output_length == 0) {
+ flb_log_event_encoder_reset(p_ins->log_encoder);
+
+ *out_buf = NULL;
+ *out_size = 0;
+
+ release_lock(&pu->lock,
+ FLB_PROCESSOR_LOCK_RETRY_LIMIT,
+ FLB_PROCESSOR_LOCK_RETRY_DELAY);
+
+ return 0;
+ }
+
+ flb_log_event_encoder_claim_internal_buffer_ownership(p_ins->log_encoder);
+
+ /* set new buffer */
+ cur_buf = p_ins->log_encoder->output_buffer;
+ cur_size = p_ins->log_encoder->output_length;
+
+ flb_log_event_encoder_reset(p_ins->log_encoder);
+ }
+ }
+ else if (type == FLB_PROCESSOR_METRICS) {
+
+ if (p_ins->p->cb_process_metrics != NULL) {
+ ret = p_ins->p->cb_process_metrics(p_ins,
+ (struct cmt *) cur_buf,
+ tag,
+ tag_len);
+
+ if (ret != FLB_PROCESSOR_SUCCESS) {
+ release_lock(&pu->lock,
+ FLB_PROCESSOR_LOCK_RETRY_LIMIT,
+ FLB_PROCESSOR_LOCK_RETRY_DELAY);
+
+ return -1;
+ }
+ }
+ }
+ else if (type == FLB_PROCESSOR_TRACES) {
+
+ if (p_ins->p->cb_process_traces != NULL) {
+ ret = p_ins->p->cb_process_traces(p_ins,
+ (struct ctrace *) cur_buf,
+ tag,
+ tag_len);
+
+ if (ret != FLB_PROCESSOR_SUCCESS) {
+ release_lock(&pu->lock,
+ FLB_PROCESSOR_LOCK_RETRY_LIMIT,
+ FLB_PROCESSOR_LOCK_RETRY_DELAY);
+
+ return -1;
+ }
+ }
+ }
+ }
+
+ release_lock(&pu->lock,
+ FLB_PROCESSOR_LOCK_RETRY_LIMIT,
+ FLB_PROCESSOR_LOCK_RETRY_DELAY);
+ }
+
+ /* set output buffer */
+ if (out_buf != NULL) {
+ *out_buf = cur_buf;
+ }
+
+ if (out_size != NULL) {
+ *out_size = cur_size;
+ }
+
+ return 0;
+}
+
+void flb_processor_destroy(struct flb_processor *proc)
+{
+ struct mk_list *head;
+ struct mk_list *tmp;
+ struct flb_processor_unit *pu;
+
+ mk_list_foreach_safe(head, tmp, &proc->logs) {
+ pu = mk_list_entry(head, struct flb_processor_unit, _head);
+ mk_list_del(&pu->_head);
+ flb_processor_unit_destroy(pu);
+ }
+
+ mk_list_foreach_safe(head, tmp, &proc->metrics) {
+ pu = mk_list_entry(head, struct flb_processor_unit, _head);
+ mk_list_del(&pu->_head);
+ flb_processor_unit_destroy(pu);
+ }
+
+ mk_list_foreach_safe(head, tmp, &proc->traces) {
+ pu = mk_list_entry(head, struct flb_processor_unit, _head);
+ mk_list_del(&pu->_head);
+ flb_processor_unit_destroy(pu);
+ }
+ flb_free(proc);
+}
+
+
+static int load_from_config_format_group(struct flb_processor *proc, int type, struct cfl_variant *val)
+{
+ int i;
+ int ret;
+ char *name;
+ struct cfl_variant *tmp;
+ struct cfl_array *array;
+ struct cfl_kvlist *kvlist;
+ struct cfl_kvpair *pair = NULL;
+ struct cfl_list *head;
+ struct flb_processor_unit *pu;
+ struct flb_filter_instance *f_ins;
+
+ if (val->type != CFL_VARIANT_ARRAY) {
+ return -1;
+ }
+
+ array = val->data.as_array;
+ for (i = 0; i < array->entry_count; i++) {
+ /* every entry in the array must be a map */
+ tmp = array->entries[i];
+
+ if (tmp->type != CFL_VARIANT_KVLIST) {
+ return -1;
+ }
+
+ kvlist = tmp->data.as_kvlist;
+
+ /* get the processor name, this is a mandatory config field */
+ tmp = cfl_kvlist_fetch(kvlist, "name");
+
+ if (!tmp) {
+ flb_error("processor configuration don't have a 'name' defined");
+ return -1;
+ }
+
+ /* create the processor unit and load all the properties */
+ name = tmp->data.as_string;
+ pu = flb_processor_unit_create(proc, type, name);
+
+ if (!pu) {
+ flb_error("cannot create '%s' processor unit", name);
+ return -1;
+ }
+
+ /* iterate list of properties and set each one (skip name) */
+ cfl_list_foreach(head, &kvlist->list) {
+ pair = cfl_list_entry(head, struct cfl_kvpair, _head);
+
+ if (strcmp(pair->key, "name") == 0) {
+ continue;
+ }
+
+ /* If filter plugin in processor unit has its own match rule,
+ * we must release the pre-allocated '*' match at first.
+ */
+ if (pu->unit_type == FLB_PROCESSOR_UNIT_FILTER) {
+
+ if (strcmp(pair->key, "match") == 0) {
+ f_ins = (struct flb_filter_instance *)pu->ctx;
+
+ if (f_ins->match != NULL) {
+ flb_sds_destroy(f_ins->match);
+ f_ins->match = NULL;
+ }
+ }
+ }
+
+ ret = flb_processor_unit_set_property(pu, pair->key, pair->val);
+
+ if (ret == -1) {
+ flb_error("cannot set property '%s' for processor '%s'", pair->key, name);
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+
+}
+
+/* Load processors into an input instance */
+int flb_processors_load_from_config_format_group(struct flb_processor *proc, struct flb_cf_group *g)
+{
+ int ret;
+ struct cfl_variant *val;
+
+ /* logs */
+ val = cfl_kvlist_fetch(g->properties, "logs");
+
+ if (val) {
+ ret = load_from_config_format_group(proc, FLB_PROCESSOR_LOGS, val);
+
+ if (ret == -1) {
+ flb_error("failed to load 'logs' processors");
+ return -1;
+ }
+ }
+
+ /* metrics */
+ val = cfl_kvlist_fetch(g->properties, "metrics");
+
+ if (val) {
+ ret = load_from_config_format_group(proc, FLB_PROCESSOR_METRICS, val);
+
+ if (ret == -1) {
+ flb_error("failed to load 'metrics' processors");
+ return -1;
+ }
+ }
+
+ /* traces */
+ val = cfl_kvlist_fetch(g->properties, "traces");
+ if (val) {
+ ret = load_from_config_format_group(proc, FLB_PROCESSOR_TRACES, val);
+
+ if (ret == -1) {
+ flb_error("failed to load 'traces' processors");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+
+
+
+
+
+
+static inline int prop_key_check(const char *key, const char *kv, int k_len)
+{
+ int len;
+
+ len = strlen(key);
+
+ if (strncasecmp(key, kv, k_len) == 0 && len == k_len) {
+ return 0;
+ }
+
+ return -1;
+}
+
+int flb_processor_instance_set_property(struct flb_processor_instance *ins,
+ const char *k, const char *v)
+{
+ int len;
+ int ret;
+ flb_sds_t tmp;
+ struct flb_kv *kv;
+
+ len = strlen(k);
+ tmp = flb_env_var_translate(ins->config->env, v);
+
+ if (!tmp) {
+ return -1;
+ }
+
+ if (prop_key_check("alias", k, len) == 0 && tmp) {
+ ins->alias = tmp;
+ }
+ else if (prop_key_check("log_level", k, len) == 0 && tmp) {
+ ret = flb_log_get_level_str(tmp);
+ flb_sds_destroy(tmp);
+
+ if (ret == -1) {
+ return -1;
+ }
+ ins->log_level = ret;
+ }
+ else {
+ /*
+ * Create the property, we don't pass the value since we will
+ * map it directly to avoid an extra memory allocation.
+ */
+ kv = flb_kv_item_create(&ins->properties, (char *) k, NULL);
+
+ if (!kv) {
+
+ if (tmp) {
+ flb_sds_destroy(tmp);
+ }
+ return -1;
+ }
+ kv->val = tmp;
+ }
+
+ return 0;
+}
+
+const char *flb_processor_instance_get_property(
+ const char *key,
+ struct flb_processor_instance *ins)
+{
+ return flb_kv_get_key_value(key, &ins->properties);
+}
+
+struct flb_processor_instance *flb_processor_instance_create(
+ struct flb_config *config,
+ const char *name, void *data)
+{
+ struct flb_processor_instance *instance;
+ struct flb_processor_plugin *plugin;
+ struct mk_list *head;
+ int id;
+
+ if (name == NULL) {
+ return NULL;
+ }
+
+ mk_list_foreach(head, &config->processor_plugins) {
+ plugin = mk_list_entry(head, struct flb_processor_plugin, _head);
+
+ if (strcasecmp(plugin->name, name) == 0) {
+ break;
+ }
+ plugin = NULL;
+ }
+
+ if (!plugin) {
+ return NULL;
+ }
+
+ instance = flb_calloc(1, sizeof(struct flb_filter_instance));
+
+ if (!instance) {
+ flb_errno();
+ return NULL;
+ }
+
+ instance->config = config;
+
+ /* Get an ID */
+ id = 0;
+
+ /* format name (with instance id) */
+ snprintf(instance->name, sizeof(instance->name) - 1,
+ "%s.%i", plugin->name, id);
+
+ instance->id = id;
+ instance->alias = NULL;
+ instance->p = plugin;
+ instance->data = data;
+ instance->log_level = -1;
+
+ mk_list_init(&instance->properties);
+
+ instance->log_encoder = flb_log_event_encoder_create(
+ FLB_LOG_EVENT_FORMAT_DEFAULT);
+
+ if (instance->log_encoder == NULL) {
+ flb_plg_error(instance, "log event encoder initialization error");
+
+ flb_processor_instance_destroy(instance);
+
+ instance = NULL;
+ }
+
+ instance->log_decoder = flb_log_event_decoder_create(NULL, 0);
+
+ if (instance->log_decoder == NULL) {
+ flb_plg_error(instance, "log event decoder initialization error");
+
+ flb_processor_instance_destroy(instance);
+
+ instance = NULL;
+ }
+
+ return instance;
+}
+
+void flb_processor_instance_exit(
+ struct flb_processor_instance *ins,
+ struct flb_config *config)
+{
+ struct flb_processor_plugin *plugin;
+
+ plugin = ins->p;
+
+ if (plugin->cb_exit != NULL &&
+ ins->context != NULL) {
+ plugin->cb_exit(ins);
+ }
+}
+
+const char *flb_processor_instance_get_name(struct flb_processor_instance *ins)
+{
+ if (ins->alias) {
+ return ins->alias;
+ }
+
+ return ins->name;
+}
+
+int flb_processor_instance_check_properties(
+ struct flb_processor_instance *ins,
+ struct flb_config *config)
+{
+ int ret = 0;
+ struct mk_list *config_map;
+ struct flb_processor_plugin *p = ins->p;
+
+ if (p->config_map) {
+ /*
+ * Create a dynamic version of the configmap that will be used by the specific
+ * instance in question.
+ */
+ config_map = flb_config_map_create(config, p->config_map);
+
+ if (!config_map) {
+ flb_error("[native processor] error loading config map for '%s' plugin",
+ p->name);
+ return -1;
+ }
+ ins->config_map = config_map;
+
+ /* Validate incoming properties against config map */
+ ret = flb_config_map_properties_check(ins->p->name,
+ &ins->properties,
+ ins->config_map);
+
+ if (ret == -1) {
+
+ if (config->program_name) {
+ flb_helper("try the command: %s -F %s -h\n",
+ config->program_name, ins->p->name);
+ }
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int flb_processor_instance_init(
+ struct flb_processor_instance *ins,
+ void *source_plugin_instance,
+ int source_plugin_type,
+ struct flb_config *config)
+{
+ int ret;
+ char *name;
+ struct flb_processor_plugin *p;
+
+ if (ins->log_level == -1 &&
+ config->log != NULL) {
+ ins->log_level = config->log->level;
+ }
+
+ p = ins->p;
+
+ /* Get name or alias for the instance */
+ name = (char *) flb_processor_instance_get_name(ins);
+
+ /* CMetrics */
+ ins->cmt = cmt_create();
+
+ if (!ins->cmt) {
+ flb_error("[processor] could not create cmetrics context: %s",
+ name);
+
+ return -1;
+ }
+
+ /*
+ * Before to call the initialization callback, make sure that the received
+ * configuration parameters are valid if the plugin is registering a config map.
+ */
+ if (flb_processor_instance_check_properties(ins, config) == -1) {
+ return -1;
+ }
+
+ /* Initialize the input */
+ if (p->cb_init != NULL) {
+ ret = p->cb_init(ins,
+ source_plugin_instance,
+ source_plugin_type,
+ config);
+
+ if (ret != 0) {
+ flb_error("[processor] failed initialize filter %s", ins->name);
+
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void flb_processor_instance_set_context(
+ struct flb_processor_instance *ins,
+ void *context)
+{
+ ins->context = context;
+}
+
+void flb_processor_instance_destroy(
+ struct flb_processor_instance *ins)
+{
+ if (ins == NULL) {
+ return;
+ }
+
+ /* destroy config map */
+ if (ins->config_map != NULL) {
+ flb_config_map_destroy(ins->config_map);
+ }
+
+ /* release properties */
+ flb_kv_release(&ins->properties);
+
+ /* Remove metrics */
+#ifdef FLB_HAVE_METRICS
+ if (ins->cmt != NULL) {
+ cmt_destroy(ins->cmt);
+ }
+#endif
+
+ if (ins->alias != NULL) {
+ flb_sds_destroy(ins->alias);
+ }
+
+ if (ins->log_encoder != NULL) {
+ flb_log_event_encoder_destroy(ins->log_encoder);
+ }
+
+ if (ins->log_decoder != NULL) {
+ flb_log_event_decoder_destroy(ins->log_decoder);
+ }
+
+ flb_free(ins);
+}
diff --git a/fluent-bit/src/flb_ra_key.c b/fluent-bit/src/flb_ra_key.c
new file mode 100644
index 000000000..f167a7664
--- /dev/null
+++ b/fluent-bit/src/flb_ra_key.c
@@ -0,0 +1,808 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_regex.h>
+#include <fluent-bit/flb_ra_key.h>
+#include <fluent-bit/record_accessor/flb_ra_parser.h>
+#include <msgpack.h>
+#include <limits.h>
+
+/* Map msgpack object into flb_ra_value representation */
+static int msgpack_object_to_ra_value(msgpack_object o,
+ struct flb_ra_value *result)
+{
+ result->o = o;
+
+ /* Compose result with found value */
+ if (o.type == MSGPACK_OBJECT_BOOLEAN) {
+ result->type = FLB_RA_BOOL;
+ result->val.boolean = o.via.boolean;
+ return 0;
+ }
+ else if (o.type == MSGPACK_OBJECT_POSITIVE_INTEGER ||
+ o.type == MSGPACK_OBJECT_NEGATIVE_INTEGER) {
+ result->type = FLB_RA_INT;
+ result->val.i64 = o.via.i64;
+ return 0;
+ }
+ else if (o.type == MSGPACK_OBJECT_FLOAT32 ||
+ o.type == MSGPACK_OBJECT_FLOAT) {
+ result->type = FLB_RA_FLOAT;
+ result->val.f64 = o.via.f64;
+ return 0;
+ }
+ else if (o.type == MSGPACK_OBJECT_STR) {
+ result->type = FLB_RA_STRING;
+ result->val.string = flb_sds_create_len((char *) o.via.str.ptr,
+ o.via.str.size);
+
+ /* Handle cases where flb_sds_create_len fails */
+ if (result->val.string == NULL) {
+ return -1;
+ }
+ return 0;
+ }
+ else if (o.type == MSGPACK_OBJECT_MAP) {
+ /* return boolean 'true', just denoting the existence of the key */
+ result->type = FLB_RA_BOOL;
+ result->val.boolean = true;
+ return 0;
+ }
+ else if (o.type == MSGPACK_OBJECT_NIL) {
+ result->type = FLB_RA_NULL;
+ return 0;
+ }
+
+ return -1;
+}
+
+/* Return the entry position of key/val in the map */
+static int ra_key_val_id(flb_sds_t ckey, msgpack_object map)
+{
+ int i;
+ int map_size;
+ msgpack_object key;
+
+ if (map.type != MSGPACK_OBJECT_MAP) {
+ return -1;
+ }
+
+ map_size = map.via.map.size;
+ for (i = map_size - 1; i >= 0; i--) {
+ key = map.via.map.ptr[i].key;
+
+ if (key.type != MSGPACK_OBJECT_STR) {
+ continue;
+ }
+
+ /* Compare by length and by key name */
+ if (flb_sds_cmp(ckey, key.via.str.ptr, key.via.str.size) != 0) {
+ continue;
+ }
+
+ return i;
+ }
+
+ return -1;
+}
+
+static int msgpack_object_strcmp(msgpack_object o, char *str, int len)
+{
+ if (o.type != MSGPACK_OBJECT_STR) {
+ return -1;
+ }
+
+ if (o.via.str.size != len) {
+ return -1;
+ }
+
+ return strncmp(o.via.str.ptr, str, len);
+}
+
+/* Lookup perfect match of sub-keys and map content */
+static int subkey_to_object(msgpack_object *map, struct mk_list *subkeys,
+ msgpack_object **out_key, msgpack_object **out_val)
+{
+ int i = 0;
+ int levels;
+ int matched = 0;
+ msgpack_object *found = NULL;
+ msgpack_object *key = NULL;
+ msgpack_object *val = NULL;
+ msgpack_object cur;
+ struct mk_list *head;
+ struct flb_ra_subentry *entry;
+
+ /* Expected number of map levels in the map */
+ levels = mk_list_size(subkeys);
+
+ cur = *map;
+
+ mk_list_foreach(head, subkeys) {
+ /* expected entry */
+ entry = mk_list_entry(head, struct flb_ra_subentry, _head);
+
+ /* Array Handling */
+ if (entry->type == FLB_RA_PARSER_ARRAY_ID) {
+ /* check the current msgpack object is an array */
+ if (cur.type != MSGPACK_OBJECT_ARRAY) {
+ return -1;
+ }
+
+ /* Index limit and ensure no overflow */
+ if (entry->array_id == INT_MAX ||
+ cur.via.array.size < entry->array_id + 1) {
+ return -1;
+ }
+
+ val = &cur.via.array.ptr[entry->array_id];
+ cur = *val;
+ key = NULL; /* fill NULL since the type is array. */
+ goto next;
+ }
+
+ if (cur.type != MSGPACK_OBJECT_MAP) {
+ break;
+ }
+
+ i = ra_key_val_id(entry->str, cur);
+ if (i == -1) {
+ found = NULL;
+ continue;
+ }
+
+ key = &cur.via.map.ptr[i].key;
+ val = &cur.via.map.ptr[i].val;
+
+ /* A bit obvious, but it's better to validate data type */
+ if (key->type != MSGPACK_OBJECT_STR) {
+ found = NULL;
+ continue;
+ }
+
+ found = key;
+ cur = cur.via.map.ptr[i].val;
+
+ next:
+ matched++;
+
+ if (levels == matched) {
+ break;
+ }
+ }
+
+ /* No matches */
+ if (!found || (matched > 0 && levels != matched)) {
+ return -1;
+ }
+
+ *out_key = (msgpack_object *) key;
+ *out_val = (msgpack_object *) val;
+
+ return 0;
+}
+
+struct flb_ra_value *flb_ra_key_to_value(flb_sds_t ckey,
+ msgpack_object map,
+ struct mk_list *subkeys)
+{
+ int i;
+ int ret;
+ msgpack_object val;
+ msgpack_object *out_key;
+ msgpack_object *out_val;
+ struct flb_ra_value *result;
+
+ /* Get the key position in the map */
+ i = ra_key_val_id(ckey, map);
+ if (i == -1) {
+ return NULL;
+ }
+
+ /* Reference entries */
+ val = map.via.map.ptr[i].val;
+
+ /* Create the result context */
+ result = flb_calloc(1, sizeof(struct flb_ra_value));
+ if (!result) {
+ flb_errno();
+ return NULL;
+ }
+ result->o = val;
+
+ if ((val.type == MSGPACK_OBJECT_MAP || val.type == MSGPACK_OBJECT_ARRAY)
+ && subkeys != NULL && mk_list_size(subkeys) > 0) {
+
+ ret = subkey_to_object(&val, subkeys, &out_key, &out_val);
+ if (ret == 0) {
+ ret = msgpack_object_to_ra_value(*out_val, result);
+ if (ret == -1) {
+ flb_free(result);
+ return NULL;
+ }
+ return result;
+ }
+ else {
+ flb_free(result);
+ return NULL;
+ }
+ }
+ else {
+ ret = msgpack_object_to_ra_value(val, result);
+ if (ret == -1) {
+ flb_error("[ra key] cannot process key value");
+ flb_free(result);
+ return NULL;
+ }
+ }
+
+ return result;
+}
+
+int flb_ra_key_value_get(flb_sds_t ckey, msgpack_object map,
+ struct mk_list *subkeys,
+ msgpack_object **start_key,
+ msgpack_object **out_key, msgpack_object **out_val)
+{
+ int i;
+ int ret;
+ msgpack_object val;
+ msgpack_object *o_key;
+ msgpack_object *o_val;
+
+ /* Get the key position in the map */
+ i = ra_key_val_id(ckey, map);
+ if (i == -1) {
+ return -1;
+ }
+
+ /* Reference entries */
+ *start_key = &map.via.map.ptr[i].key;
+ val = map.via.map.ptr[i].val;
+
+ if ((val.type == MSGPACK_OBJECT_MAP || val.type == MSGPACK_OBJECT_ARRAY)
+ && subkeys != NULL && mk_list_size(subkeys) > 0) {
+ ret = subkey_to_object(&val, subkeys, &o_key, &o_val);
+ if (ret == 0) {
+ *out_key = o_key;
+ *out_val = o_val;
+ return 0;
+ }
+ }
+ else {
+ *out_key = &map.via.map.ptr[i].key;
+ *out_val = &map.via.map.ptr[i].val;
+ return 0;
+ }
+
+ return -1;
+}
+
+int flb_ra_key_strcmp(flb_sds_t ckey, msgpack_object map,
+ struct mk_list *subkeys, char *str, int len)
+{
+ int i;
+ int ret;
+ msgpack_object val;
+ msgpack_object *out_key;
+ msgpack_object *out_val;
+
+ /* Get the key position in the map */
+ i = ra_key_val_id(ckey, map);
+ if (i == -1) {
+ return -1;
+ }
+
+ /* Reference map value */
+ val = map.via.map.ptr[i].val;
+
+ if ((val.type == MSGPACK_OBJECT_MAP || val.type == MSGPACK_OBJECT_ARRAY)
+ && subkeys != NULL && mk_list_size(subkeys) > 0) {
+ ret = subkey_to_object(&val, subkeys, &out_key, &out_val);
+ if (ret == 0) {
+ return msgpack_object_strcmp(*out_val, str, len);
+ }
+ else {
+ return -1;
+ }
+ }
+
+ return msgpack_object_strcmp(val, str, len);
+}
+
+int flb_ra_key_regex_match(flb_sds_t ckey, msgpack_object map,
+ struct mk_list *subkeys, struct flb_regex *regex,
+ struct flb_regex_search *result)
+{
+ int i;
+ int ret;
+ msgpack_object val;
+ msgpack_object *out_key;
+ msgpack_object *out_val;
+
+ /* Get the key position in the map */
+ i = ra_key_val_id(ckey, map);
+ if (i == -1) {
+ return -1;
+ }
+
+ /* Reference map value */
+ val = map.via.map.ptr[i].val;
+
+ if ((val.type == MSGPACK_OBJECT_MAP || val.type == MSGPACK_OBJECT_ARRAY)
+ && subkeys != NULL && mk_list_size(subkeys) > 0) {
+ ret = subkey_to_object(&val, subkeys, &out_key, &out_val);
+ if (ret == 0) {
+ if (out_val->type != MSGPACK_OBJECT_STR) {
+ return -1;
+ }
+
+ if (result) {
+ /* Regex + capture mode */
+ return flb_regex_do(regex,
+ (char *) out_val->via.str.ptr,
+ out_val->via.str.size,
+ result);
+ }
+ else {
+ /* No capture */
+ return flb_regex_match(regex,
+ (unsigned char *) out_val->via.str.ptr,
+ out_val->via.str.size);
+ }
+ }
+ return -1;
+ }
+
+ if (val.type != MSGPACK_OBJECT_STR) {
+ return -1;
+ }
+
+ if (result) {
+ /* Regex + capture mode */
+ return flb_regex_do(regex, (char *) val.via.str.ptr, val.via.str.size,
+ result);
+ }
+ else {
+ /* No capture */
+ return flb_regex_match(regex, (unsigned char *) val.via.str.ptr,
+ val.via.str.size);
+ }
+
+ return -1;
+}
+
+static int update_subkey(msgpack_object *obj, struct mk_list *subkeys,
+ int levels, int *matched,
+ msgpack_object *in_key, msgpack_object *in_val,
+ msgpack_packer *mp_pck);
+
+
+static int update_subkey_array(msgpack_object *obj, struct mk_list *subkeys,
+ int levels, int *matched,
+ msgpack_object *in_key, msgpack_object *in_val,
+ msgpack_packer *mp_pck)
+{
+ struct flb_ra_subentry *entry;
+ int i;
+ int ret;
+ int size;
+
+ entry = mk_list_entry_first(subkeys, struct flb_ra_subentry, _head);
+
+ /* check the current msgpack object is an array */
+ if (obj->type != MSGPACK_OBJECT_ARRAY) {
+ flb_error("%s: object is not array", __FUNCTION__);
+ return -1;
+ }
+ size = obj->via.array.size;
+ /* Index limit and ensure no overflow */
+ if (entry->array_id == INT_MAX ||
+ size < entry->array_id + 1) {
+ flb_trace("%s: out of index", __FUNCTION__);
+ return -1;
+ }
+
+ msgpack_pack_array(mp_pck, size);
+ for (i=0; i<size; i++) {
+ if (i != entry->array_id) {
+ msgpack_pack_object(mp_pck, obj->via.array.ptr[i]);
+ continue;
+ }
+ *matched += 1;
+ if (levels == *matched) {
+ flb_trace("%s: update val matched=%d", __FUNCTION__, *matched);
+ /* update value */
+ msgpack_pack_object(mp_pck, *in_val);
+ continue;
+ }
+
+ if (subkeys->next == NULL) {
+ flb_trace("%s: end of subkey", __FUNCTION__);
+ return -1;
+ }
+ ret = update_subkey(&obj->via.array.ptr[i], subkeys->next,
+ levels, matched,
+ in_key, in_val, mp_pck);
+ if (ret < 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int update_subkey_map(msgpack_object *obj, struct mk_list *subkeys,
+ int levels, int *matched,
+ msgpack_object *in_key, msgpack_object *in_val,
+ msgpack_packer *mp_pck)
+{
+ struct flb_ra_subentry *entry;
+ int i;
+ int ret_id;
+ int size;
+ int ret;
+ msgpack_object_kv kv;
+
+ entry = mk_list_entry_first(subkeys, struct flb_ra_subentry, _head);
+ /* check the current msgpack object is a map */
+ if (obj->type != MSGPACK_OBJECT_MAP) {
+ flb_trace("%s: object is not map", __FUNCTION__);
+ return -1;
+ }
+ size = obj->via.map.size;
+
+ ret_id = ra_key_val_id(entry->str, *obj);
+ if (ret_id < 0) {
+ flb_trace("%s: not found", __FUNCTION__);
+ return -1;
+ }
+
+ msgpack_pack_map(mp_pck, size);
+ for (i=0; i<size; i++) {
+ if (i != ret_id) {
+ msgpack_pack_object(mp_pck, obj->via.map.ptr[i].key);
+ msgpack_pack_object(mp_pck, obj->via.map.ptr[i].val);
+ continue;
+ }
+ *matched += 1;
+ if (levels == *matched) {
+ flb_trace("%s update key/val matched=%d", __FUNCTION__, *matched);
+ /* update key/value */
+ kv = obj->via.map.ptr[i];
+ if (in_key != NULL) {
+ kv.key = *in_key;
+ }
+ msgpack_pack_object(mp_pck, kv.key);
+ if (in_val != NULL) {
+ kv.val = *in_val;
+ }
+ msgpack_pack_object(mp_pck, kv.val);
+
+ continue;
+ }
+ if (subkeys->next == NULL) {
+ flb_trace("%s: end of subkey", __FUNCTION__);
+ return -1;
+ }
+ msgpack_pack_object(mp_pck, obj->via.map.ptr[i].key);
+ ret = update_subkey(&(obj->via.map.ptr[i].val), subkeys->next,
+ levels, matched,
+ in_key, in_val, mp_pck);
+ if (ret < 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int update_subkey(msgpack_object *obj, struct mk_list *subkeys,
+ int levels, int *matched,
+ msgpack_object *in_key, msgpack_object *in_val,
+ msgpack_packer *mp_pck)
+{
+ struct flb_ra_subentry *entry;
+
+ entry = mk_list_entry_first(subkeys, struct flb_ra_subentry, _head);
+
+ if (entry->type == FLB_RA_PARSER_ARRAY_ID) {
+ return update_subkey_array(obj, subkeys,
+ levels, matched,
+ in_key, in_val, mp_pck);
+ }
+ return update_subkey_map(obj, subkeys, levels, matched, in_key, in_val, mp_pck);
+}
+
+int flb_ra_key_value_update(struct flb_ra_parser *rp, msgpack_object map,
+ msgpack_object *in_key, msgpack_object *in_val,
+ msgpack_packer *mp_pck)
+{
+ int kv_id;
+ int i;
+ int map_size;
+ int ret;
+ int levels;
+ int matched = 0;
+
+ /* Get the key position in the map */
+ kv_id = ra_key_val_id(rp->key->name, map);
+ if (kv_id == -1) {
+ return -1;
+ }
+
+ levels = mk_list_size(rp->key->subkeys);
+
+ map_size = map.via.map.size;
+
+ msgpack_pack_map(mp_pck, map_size);
+ if (levels == 0) {
+ /* no subkeys */
+ for (i=0; i<map_size; i++) {
+ if (i != kv_id) {
+ /* pack original key/val */
+ msgpack_pack_object(mp_pck, map.via.map.ptr[i].key);
+ msgpack_pack_object(mp_pck, map.via.map.ptr[i].val);
+ continue;
+ }
+
+ /* update key/val */
+ if (in_key != NULL) {
+ msgpack_pack_object(mp_pck, *in_key);
+ }
+ else {
+ msgpack_pack_object(mp_pck, map.via.map.ptr[i].key);
+ }
+ if (in_val != NULL) {
+ msgpack_pack_object(mp_pck, *in_val);
+ }
+ else {
+ msgpack_pack_object(mp_pck, map.via.map.ptr[i].val);
+ }
+ }
+ return 0;
+ }
+
+ for (i=0; i<map_size; i++) {
+ msgpack_pack_object(mp_pck, map.via.map.ptr[i].key);
+ if (i != kv_id) {
+ msgpack_pack_object(mp_pck, map.via.map.ptr[i].val);
+ continue;
+ }
+ ret = update_subkey(&(map.via.map.ptr[i].val), rp->key->subkeys,
+ levels, &matched,
+ in_key, in_val, mp_pck);
+ if (ret < 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int append_subkey(msgpack_object *obj, struct mk_list *subkeys,
+ int levels, int *matched,
+ msgpack_object *in_val,
+ msgpack_packer *mp_pck);
+
+
+static int append_subkey_array(msgpack_object *obj, struct mk_list *subkeys,
+ int levels, int *matched,
+ msgpack_object *in_val,
+ msgpack_packer *mp_pck)
+{
+ struct flb_ra_subentry *entry;
+ int i;
+ int ret;
+ int size;
+
+ /* check the current msgpack object is an array */
+ if (obj->type != MSGPACK_OBJECT_ARRAY) {
+ flb_trace("%s: object is not array", __FUNCTION__);
+ return -1;
+ }
+ size = obj->via.array.size;
+ entry = mk_list_entry_first(subkeys, struct flb_ra_subentry, _head);
+
+ if (levels == *matched) {
+ /* append val */
+ msgpack_pack_array(mp_pck, size+1);
+ for (i=0; i<size; i++) {
+ msgpack_pack_object(mp_pck, obj->via.array.ptr[i]);
+ }
+ msgpack_pack_object(mp_pck, *in_val);
+
+ *matched = -1;
+ return 0;
+ }
+
+ /* Index limit and ensure no overflow */
+ if (entry->array_id == INT_MAX ||
+ size < entry->array_id + 1) {
+ flb_trace("%s: out of index", __FUNCTION__);
+ return -1;
+ }
+
+ msgpack_pack_array(mp_pck, size);
+ for (i=0; i<size; i++) {
+ if (i != entry->array_id) {
+ msgpack_pack_object(mp_pck, obj->via.array.ptr[i]);
+ continue;
+ }
+ if (*matched >= 0) {
+ *matched += 1;
+ }
+ if (subkeys->next == NULL) {
+ flb_trace("%s: end of subkey", __FUNCTION__);
+ return -1;
+ }
+ ret = append_subkey(&obj->via.array.ptr[i], subkeys->next,
+ levels, matched,
+ in_val, mp_pck);
+ if (ret < 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int append_subkey_map(msgpack_object *obj, struct mk_list *subkeys,
+ int levels, int *matched,
+ msgpack_object *in_val,
+ msgpack_packer *mp_pck)
+{
+ struct flb_ra_subentry *entry;
+ int i;
+ int ret_id;
+ int size;
+ int ret;
+
+ /* check the current msgpack object is a map */
+ if (obj->type != MSGPACK_OBJECT_MAP) {
+ flb_trace("%s: object is not map", __FUNCTION__);
+ return -1;
+ }
+ size = obj->via.map.size;
+ entry = mk_list_entry_first(subkeys, struct flb_ra_subentry, _head);
+
+ if (levels == *matched) {
+ /* append val */
+ msgpack_pack_map(mp_pck, size+1);
+ for (i=0; i<size; i++) {
+ msgpack_pack_object(mp_pck, obj->via.map.ptr[i].key);
+ msgpack_pack_object(mp_pck, obj->via.map.ptr[i].val);
+ }
+ msgpack_pack_str(mp_pck, flb_sds_len(entry->str));
+ msgpack_pack_str_body(mp_pck, entry->str, flb_sds_len(entry->str));
+ msgpack_pack_object(mp_pck, *in_val);
+
+ *matched = -1;
+ return 0;
+ }
+
+
+ ret_id = ra_key_val_id(entry->str, *obj);
+ if (ret_id < 0) {
+ flb_trace("%s: not found", __FUNCTION__);
+ return -1;
+ }
+
+ msgpack_pack_map(mp_pck, size);
+ for (i=0; i<size; i++) {
+ if (i != ret_id) {
+ msgpack_pack_object(mp_pck, obj->via.map.ptr[i].key);
+ msgpack_pack_object(mp_pck, obj->via.map.ptr[i].val);
+ continue;
+ }
+
+ if (*matched >= 0) {
+ *matched += 1;
+ }
+ if (subkeys->next == NULL) {
+ flb_trace("%s: end of subkey", __FUNCTION__);
+ return -1;
+ }
+ msgpack_pack_object(mp_pck, obj->via.map.ptr[i].key);
+ ret = append_subkey(&(obj->via.map.ptr[i].val), subkeys->next,
+ levels, matched,
+ in_val, mp_pck);
+ if (ret < 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int append_subkey(msgpack_object *obj, struct mk_list *subkeys,
+ int levels, int *matched,
+ msgpack_object *in_val,
+ msgpack_packer *mp_pck)
+{
+ struct flb_ra_subentry *entry;
+
+ entry = mk_list_entry_first(subkeys, struct flb_ra_subentry, _head);
+
+ if (entry->type == FLB_RA_PARSER_ARRAY_ID) {
+ return append_subkey_array(obj, subkeys,
+ levels, matched,
+ in_val, mp_pck);
+ }
+ return append_subkey_map(obj, subkeys, levels, matched, in_val, mp_pck);
+}
+
+int flb_ra_key_value_append(struct flb_ra_parser *rp, msgpack_object map,
+ msgpack_object *in_val, msgpack_packer *mp_pck)
+{
+ int ref_level;
+ int map_size;
+ int i;
+ int kv_id;
+ int ret;
+ int matched = 0;
+
+ map_size = map.via.map.size;
+
+ /* Decrement since the last key doesn't exist */
+ ref_level = mk_list_size(rp->key->subkeys) - 1;
+ if (ref_level < 0) {
+ /* no subkeys */
+ msgpack_pack_map(mp_pck, map_size+1);
+ for (i=0; i<map_size; i++) {
+ msgpack_pack_object(mp_pck, map.via.map.ptr[i].key);
+ msgpack_pack_object(mp_pck, map.via.map.ptr[i].val);
+ }
+ msgpack_pack_str(mp_pck, flb_sds_len(rp->key->name));
+ msgpack_pack_str_body(mp_pck, rp->key->name, flb_sds_len(rp->key->name));
+ msgpack_pack_object(mp_pck, *in_val);
+ return 0;
+ }
+
+ /* Get the key position in the map */
+ kv_id = ra_key_val_id(rp->key->name, map);
+ if (kv_id == -1) {
+ return -1;
+ }
+
+ msgpack_pack_map(mp_pck, map_size);
+ for (i=0; i<map_size; i++) {
+ msgpack_pack_object(mp_pck, map.via.map.ptr[i].key);
+ if (i != kv_id) {
+ msgpack_pack_object(mp_pck, map.via.map.ptr[i].val);
+ continue;
+ }
+ ret = append_subkey(&(map.via.map.ptr[i].val), rp->key->subkeys,
+ ref_level, &matched,
+ in_val, mp_pck);
+ if (ret < 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void flb_ra_key_value_destroy(struct flb_ra_value *v)
+{
+ if (v->type == FLB_RA_STRING) {
+ flb_sds_destroy(v->val.string);
+ }
+ flb_free(v);
+}
diff --git a/fluent-bit/src/flb_random.c b/fluent-bit/src/flb_random.c
new file mode 100644
index 000000000..2425ec258
--- /dev/null
+++ b/fluent-bit/src/flb_random.c
@@ -0,0 +1,97 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_compat.h>
+#include <fcntl.h>
+
+#ifdef FLB_HAVE_GETENTROPY
+#include <unistd.h>
+#endif
+#ifdef FLB_HAVE_GETENTROPY_SYS_RANDOM
+#include <sys/random.h>
+#endif
+
+#define MAX_GETENTROPY_LEN 256
+
+/*
+ * This module provides a random number generator for common use cases.
+ *
+ * On Windows, we use BCryptGenRandom() from CNG API. This function
+ * is available since Windows Vista, and should be compliant to the
+ * official recommendation.
+ *
+ * On other platforms, we use getentropy(3) if available, otherwise
+ * /dev/urandom as a secure random source.
+ */
+
+int flb_random_bytes(unsigned char *buf, int len)
+{
+#ifdef FLB_SYSTEM_WINDOWS
+ NTSTATUS ret;
+ ret = BCryptGenRandom(NULL, buf, len, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
+ if (!BCRYPT_SUCCESS(ret)) {
+ return -1;
+ }
+ return 0;
+#else
+ int fd;
+ ssize_t bytes;
+
+#if defined(FLB_HAVE_GETENTROPY) || defined(FLB_HAVE_GETENTROPY_SYS_RANDOM)
+ while (len > 0) {
+ if (len > MAX_GETENTROPY_LEN) {
+ bytes = MAX_GETENTROPY_LEN;
+ }
+ else {
+ bytes = len;
+ }
+ if (getentropy(buf, bytes) < 0) {
+#ifdef ENOSYS
+ /* Fall back to urandom if the syscall is not available (Linux only) */
+ if (errno == ENOSYS) {
+ goto try_urandom;
+ }
+#endif
+ return -1;
+ }
+ len -= bytes;
+ buf += bytes;
+ }
+ return 0;
+
+try_urandom:
+#endif /* FLB_HAVE_GETENTROPY || FLB_HAVE_GETENTROPY_SYS_RANDOM */
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd == -1) {
+ return -1;
+ }
+
+ while (len > 0) {
+ bytes = read(fd, buf, len);
+ if (bytes <= 0) {
+ close(fd);
+ return -1;
+ }
+ len -= bytes;
+ buf += bytes;
+ }
+ close(fd);
+ return 0;
+#endif /* FLB_SYSTEM_WINDOWS */
+}
diff --git a/fluent-bit/src/flb_record_accessor.c b/fluent-bit/src/flb_record_accessor.c
new file mode 100644
index 000000000..d84f7ea64
--- /dev/null
+++ b/fluent-bit/src/flb_record_accessor.c
@@ -0,0 +1,906 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_env.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_sds_list.h>
+#include <fluent-bit/flb_record_accessor.h>
+#include <fluent-bit/flb_ra_key.h>
+#include <fluent-bit/record_accessor/flb_ra_parser.h>
+#include <monkey/mk_core.h>
+#include <msgpack.h>
+
+#include <ctype.h>
+
+static struct flb_ra_parser *ra_parse_string(struct flb_record_accessor *ra,
+ flb_sds_t buf, int start, int end)
+{
+ int len;
+ struct flb_ra_parser *rp;
+
+ len = end - start;
+ rp = flb_ra_parser_string_create(buf + start, len);
+ if (!rp) {
+ return NULL;
+ }
+
+ return rp;
+}
+
+/* Create a parser context for a key map or function definition */
+static struct flb_ra_parser *ra_parse_meta(struct flb_record_accessor *ra,
+ flb_sds_t buf, int start, int end)
+{
+ int len;
+ struct flb_ra_parser *rp;
+
+ len = end - start;
+ rp = flb_ra_parser_meta_create(buf + start, len);
+ if (!rp) {
+ return NULL;
+ }
+
+ return rp;
+}
+
+/*
+ * Supported data
+ *
+ * ${X} => environment variable
+ * $key, $key['x'], $key['x'][N]['z'] => record key value or array index
+ * $0, $1,..$9 => regex id
+ * $X() => built-in function
+ */
+static int ra_parse_buffer(struct flb_record_accessor *ra, flb_sds_t buf)
+{
+ int i;
+ int n;
+ int c;
+ int t;
+ int len;
+ int pre = 0;
+ int end = 0;
+ int quote_cnt;
+ struct flb_ra_parser *rp;
+ struct flb_ra_parser *rp_str = NULL;
+
+ len = flb_sds_len(buf);
+
+ for (i = 0; i < len; i++) {
+ if (buf[i] != '$') {
+ continue;
+ }
+
+ /*
+ * Before to add the number entry, add the previous text
+ * before hitting this.
+ */
+ if (i > pre) {
+ rp = ra_parse_string(ra, buf, pre, i);
+ if (!rp) {
+ return -1;
+ }
+ mk_list_add(&rp->_head, &ra->list);
+ }
+ pre = i;
+
+
+ n = i + 1;
+ if (n >= len) {
+ /* Finalize, nothing to do */
+ break;
+ }
+
+ /*
+ * If the next character is a digit like $0,$1,$2..$9, means the user wants to use
+ * the result of a regex capture.
+ *
+ * We support up to 10 regex ids [0-9]
+ */
+ if (isdigit(buf[n])) {
+ /* Add REGEX_ID entry */
+ c = atoi(buf + n);
+ rp = flb_ra_parser_regex_id_create(c);
+ if (!rp) {
+ return -1;
+ }
+
+ mk_list_add(&rp->_head, &ra->list);
+ i++;
+ pre = i + 1;
+ continue;
+ }
+
+ /*
+ * If the next 3 character are 'TAG', the user might want to include the tag or
+ * part of it (e.g: TAG[n]).
+ */
+ if (n + 2 < len && strncmp(buf + n, "TAG", 3) == 0) {
+ /* Check if some [] was added */
+ if (n + 4 < len) {
+ end = -1;
+ if (buf[n + 3] == '[') {
+ t = n + 3;
+
+ /* Look for the ending ']' */
+ end = mk_string_char_search(buf + t, ']', len - t);
+ if (end == 0) {
+ end = -1;
+ }
+
+ /* continue processsing */
+ c = atoi(buf + t + 1);
+
+ rp = flb_ra_parser_tag_part_create(c);
+ if (!rp) {
+ return -1;
+ }
+ mk_list_add(&rp->_head, &ra->list);
+
+ i = t + end + 1;
+ pre = i;
+ continue;
+ }
+ }
+
+ /* Append full tag */
+ rp = flb_ra_parser_tag_create();
+ if (!rp) {
+ return -1;
+ }
+ mk_list_add(&rp->_head, &ra->list);
+ i = n + 3;
+ pre = n + 3;
+ continue;
+ }
+
+ quote_cnt = 0;
+ for (end = i + 1; end < len; end++) {
+ if (buf[end] == '\'') {
+ ++quote_cnt;
+ }
+ else if (buf[end] == '.' && (quote_cnt & 0x01)) {
+ /* ignore '.' if it is inside a string/subkey */
+ continue;
+ }
+ else if (buf[end] == '.' || buf[end] == ' ' || buf[end] == ',' || buf[end] == '"') {
+ break;
+ }
+ }
+ if (end > len) {
+ end = len;
+ }
+
+ /* Parse the content, we use 'end' as the separator position */
+ rp = ra_parse_meta(ra, buf, i, end);
+ if (!rp) {
+ return -1;
+ }
+
+ /* Generate fixed length string */
+ if (pre < i) {
+ rp_str = ra_parse_string(ra, buf, pre, i);
+ if (!rp_str) {
+ flb_ra_parser_destroy(rp);
+ return -1;
+ }
+ }
+ else {
+ rp_str = NULL;
+ }
+
+ if (rp_str) {
+ mk_list_add(&rp_str->_head, &ra->list);
+ }
+ mk_list_add(&rp->_head, &ra->list);
+ pre = end;
+ i = end;
+ }
+
+ /* Append remaining string */
+ if ((i - 1 > end && pre < i) || i == 1 /*allow single character*/) {
+ end = flb_sds_len(buf);
+ rp_str = ra_parse_string(ra, buf, pre, end);
+ if (rp_str) {
+ mk_list_add(&rp_str->_head, &ra->list);
+ }
+ }
+
+ return 0;
+}
+
+void flb_ra_destroy(struct flb_record_accessor *ra)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_ra_parser *rp;
+
+ mk_list_foreach_safe(head, tmp, &ra->list) {
+ rp = mk_list_entry(head, struct flb_ra_parser, _head);
+ mk_list_del(&rp->_head);
+ flb_ra_parser_destroy(rp);
+ }
+
+ if (ra->pattern) {
+ flb_sds_destroy(ra->pattern);
+ }
+ flb_free(ra);
+}
+
+int flb_ra_subkey_count(struct flb_record_accessor *ra)
+{
+ struct mk_list *head;
+ struct flb_ra_parser *rp;
+ int ret = -1;
+ int tmp;
+
+ if (ra == NULL) {
+ return -1;
+ }
+ mk_list_foreach(head, &ra->list) {
+ rp = mk_list_entry(head, struct flb_ra_parser, _head);
+ tmp = flb_ra_parser_subkey_count(rp);
+ if (tmp > ret) {
+ ret = tmp;
+ }
+ }
+
+ return ret;
+}
+
+struct flb_record_accessor *flb_ra_create(char *str, int translate_env)
+{
+ int ret;
+ size_t hint = 0;
+ char *p;
+ flb_sds_t buf = NULL;
+ struct flb_env *env;
+ struct mk_list *head;
+ struct flb_ra_parser *rp;
+ struct flb_record_accessor *ra;
+
+ p = str;
+ if (translate_env == FLB_TRUE) {
+ /*
+ * Check if some environment variable has been created as part of the
+ * string. Upon running the environment variable will be pre-set in the
+ * string.
+ */
+ env = flb_env_create();
+ if (!env) {
+ flb_error("[record accessor] cannot create environment context");
+ return NULL;
+ }
+
+ /* Translate string */
+ buf = flb_env_var_translate(env, str);
+ if (!buf) {
+ flb_error("[record accessor] cannot translate string");
+ flb_env_destroy(env);
+ return NULL;
+ }
+ flb_env_destroy(env);
+ p = buf;
+ }
+
+ /* Allocate context */
+ ra = flb_calloc(1, sizeof(struct flb_record_accessor));
+ if (!ra) {
+ flb_errno();
+ flb_error("[record accessor] cannot create context");
+ if (buf) {
+ flb_sds_destroy(buf);
+ }
+ return NULL;
+ }
+ ra->pattern = flb_sds_create(str);
+ if (!ra->pattern) {
+ flb_error("[record accessor] could not allocate pattern");
+ flb_free(ra);
+ if (buf) {
+ flb_sds_destroy(buf);
+ }
+ return NULL;
+ }
+
+ mk_list_init(&ra->list);
+
+ /*
+ * The buffer needs to processed where we create a list of parts, basically
+ * a linked list of sds using 'slist' api.
+ */
+ ret = ra_parse_buffer(ra, p);
+ if (buf) {
+ flb_sds_destroy(buf);
+ }
+ if (ret == -1) {
+ flb_ra_destroy(ra);
+ return NULL;
+ }
+
+ /* Calculate a hint of an outgoing size buffer */
+ mk_list_foreach(head, &ra->list) {
+ rp = mk_list_entry(head, struct flb_ra_parser, _head);
+ if (rp->key) {
+ if (rp->type == FLB_RA_PARSER_REGEX_ID) {
+ hint += 32;
+ }
+ else {
+ hint += flb_sds_len(rp->key->name);
+ }
+ }
+ }
+ ra->size_hint = hint + 128;
+ return ra;
+}
+
+/*
+ flb_ra_create_str_from_list returns record accessor string from string list.
+ e.g. {"aa", "bb", "cc", NULL} -> "$aa['bb']['cc']"
+ Return value should be freed using flb_sds_destroy after using.
+ */
+flb_sds_t flb_ra_create_str_from_list(struct flb_sds_list *str_list)
+{
+ int i = 0;
+ int ret_i = 0;
+ int offset = 0;
+
+ char *fmt = NULL;
+ char **strs = NULL;
+ flb_sds_t str;
+ flb_sds_t tmp_sds;
+
+ if (str_list == NULL || flb_sds_list_size(str_list) == 0) {
+ return NULL;
+ }
+
+ str = flb_sds_create_size(256);
+ if (str == NULL) {
+ flb_errno();
+ return NULL;
+ }
+
+ strs = flb_sds_list_create_str_array(str_list);
+ if (strs == NULL) {
+ flb_error("%s flb_sds_list_create_str_array failed", __FUNCTION__);
+ return NULL;
+ }
+
+ while(strs[i] != NULL) {
+ if (i == 0) {
+ fmt = "$%s";
+ }
+ else {
+ fmt = "['%s']";
+ }
+
+ ret_i = snprintf(str+offset, flb_sds_alloc(str)-offset-1, fmt, strs[i]);
+ if (ret_i > flb_sds_alloc(str)-offset-1) {
+ tmp_sds = flb_sds_increase(str, ret_i);
+ if (tmp_sds == NULL) {
+ flb_errno();
+ flb_sds_list_destroy_str_array(strs);
+ flb_sds_destroy(str);
+ return NULL;
+ }
+ str = tmp_sds;
+ ret_i = snprintf(str+offset, flb_sds_alloc(str)-offset-1, fmt, strs[i]);
+ if (ret_i > flb_sds_alloc(str)-offset-1) {
+ flb_errno();
+ flb_sds_list_destroy_str_array(strs);
+ flb_sds_destroy(str);
+ return NULL;
+ }
+ }
+ offset += ret_i;
+ i++;
+ }
+ flb_sds_list_destroy_str_array(strs);
+
+ return str;
+}
+
+struct flb_record_accessor *flb_ra_create_from_list(struct flb_sds_list *str_list, int translate_env)
+{
+ flb_sds_t tmp = NULL;
+ struct flb_record_accessor *ret = NULL;
+
+ tmp = flb_ra_create_str_from_list(str_list);
+ if (tmp == NULL) {
+ flb_errno();
+ return NULL;
+ }
+
+ ret = flb_ra_create(tmp, translate_env);
+ flb_sds_destroy(tmp);
+
+ return ret;
+}
+
+void flb_ra_dump(struct flb_record_accessor *ra)
+{
+ struct mk_list *head;
+ struct flb_ra_parser *rp;
+
+ mk_list_foreach(head, &ra->list) {
+ rp = mk_list_entry(head, struct flb_ra_parser, _head);
+ printf("\n");
+ flb_ra_parser_dump(rp);
+ }
+}
+
+static flb_sds_t ra_translate_regex_id(struct flb_ra_parser *rp,
+ struct flb_regex_search *result,
+ flb_sds_t buf)
+{
+ int ret;
+ ptrdiff_t start;
+ ptrdiff_t end;
+ flb_sds_t tmp;
+
+ ret = flb_regex_results_get(result, rp->id, &start, &end);
+ if (ret == -1) {
+ return buf;
+ }
+
+ tmp = flb_sds_cat(buf, result->str + start, end - start);
+ return tmp;
+}
+
+static flb_sds_t ra_translate_tag(struct flb_ra_parser *rp, flb_sds_t buf,
+ char *tag, int tag_len)
+{
+ flb_sds_t tmp;
+
+ tmp = flb_sds_cat(buf, tag, tag_len);
+ return tmp;
+}
+
+static flb_sds_t ra_translate_tag_part(struct flb_ra_parser *rp, flb_sds_t buf,
+ char *tag, int tag_len)
+{
+ int i = 0;
+ int id = -1;
+ int end;
+ flb_sds_t tmp = buf;
+
+ while (i < tag_len) {
+ end = mk_string_char_search(tag + i, '.', tag_len - i);
+ if (end == -1) {
+ if (i == 0) {
+ break;
+ }
+ end = tag_len - i;
+ }
+ id++;
+ if (rp->id == id) {
+ tmp = flb_sds_cat(buf, tag + i, end);
+ break;
+ }
+
+ i += end + 1;
+ }
+
+ /* No dots in the tag */
+ if (rp->id == 0 && id == -1 && i < tag_len) {
+ tmp = flb_sds_cat(buf, tag, tag_len);
+ return tmp;
+ }
+
+ return tmp;
+}
+
+static flb_sds_t ra_translate_string(struct flb_ra_parser *rp, flb_sds_t buf)
+{
+ flb_sds_t tmp;
+
+ tmp = flb_sds_cat(buf, rp->key->name, flb_sds_len(rp->key->name));
+ return tmp;
+}
+
+static flb_sds_t ra_translate_keymap(struct flb_ra_parser *rp, flb_sds_t buf,
+ msgpack_object map, int *found)
+{
+ int len;
+ char *js;
+ char str[32];
+ flb_sds_t tmp = NULL;
+ struct flb_ra_value *v;
+
+ /* Lookup key or subkey value */
+ if (rp->key == NULL) {
+ *found = FLB_FALSE;
+ return buf;
+ }
+
+ v = flb_ra_key_to_value(rp->key->name, map, rp->key->subkeys);
+ if (!v) {
+ *found = FLB_FALSE;
+ return buf;
+ }
+ else {
+ *found = FLB_TRUE;
+ }
+
+ /* Based on data type, convert to it string representation */
+ if (v->type == FLB_RA_BOOL) {
+ /* Check if is a map or a real bool */
+ if (v->o.type == MSGPACK_OBJECT_MAP) {
+ /* Convert msgpack map to JSON string */
+ js = flb_msgpack_to_json_str(1024, &v->o);
+ if (js) {
+ len = strlen(js);
+ tmp = flb_sds_cat(buf, js, len);
+ flb_free(js);
+ }
+ }
+ else if (v->o.type == MSGPACK_OBJECT_BOOLEAN) {
+ if (v->val.boolean) {
+ tmp = flb_sds_cat(buf, "true", 4);
+ }
+ else {
+ tmp = flb_sds_cat(buf, "false", 5);
+ }
+ }
+ }
+ else if (v->type == FLB_RA_INT) {
+ len = snprintf(str, sizeof(str) - 1, "%" PRId64, v->val.i64);
+ tmp = flb_sds_cat(buf, str, len);
+ }
+ else if (v->type == FLB_RA_FLOAT) {
+ len = snprintf(str, sizeof(str) - 1, "%f", v->val.f64);
+ if (len >= sizeof(str)) {
+ tmp = flb_sds_cat(buf, str, sizeof(str)-1);
+ }
+ else {
+ tmp = flb_sds_cat(buf, str, len);
+ }
+ }
+ else if (v->type == FLB_RA_STRING) {
+ tmp = flb_sds_cat(buf, v->val.string, flb_sds_len(v->val.string));
+ }
+ else if (v->type == FLB_RA_NULL) {
+ tmp = flb_sds_cat(buf, "null", 4);
+ }
+
+ flb_ra_key_value_destroy(v);
+ return tmp;
+}
+
+/*
+ * Translate a record accessor buffer, tag and records are optional
+ * parameters.
+ *
+ * For safety, the function returns a newly created string that needs
+ * to be destroyed by the caller.
+ */
+flb_sds_t flb_ra_translate(struct flb_record_accessor *ra,
+ char *tag, int tag_len,
+ msgpack_object map, struct flb_regex_search *result)
+{
+ return flb_ra_translate_check(ra, tag, tag_len, map, result, FLB_FALSE);
+}
+
+/*
+ * Translate a record accessor buffer, tag and records are optional
+ * parameters.
+ *
+ * For safety, the function returns a newly created string that needs
+ * to be destroyed by the caller.
+ *
+ * Returns NULL if `check` is FLB_TRUE and any key lookup in the record failed
+ */
+flb_sds_t flb_ra_translate_check(struct flb_record_accessor *ra,
+ char *tag, int tag_len,
+ msgpack_object map, struct flb_regex_search *result,
+ int check)
+{
+ flb_sds_t tmp = NULL;
+ flb_sds_t buf;
+ struct mk_list *head;
+ struct flb_ra_parser *rp;
+ int found = FLB_FALSE;
+
+ buf = flb_sds_create_size(ra->size_hint);
+ if (!buf) {
+ flb_error("[record accessor] cannot create outgoing buffer");
+ return NULL;
+ }
+
+ mk_list_foreach(head, &ra->list) {
+ rp = mk_list_entry(head, struct flb_ra_parser, _head);
+ if (rp->type == FLB_RA_PARSER_STRING) {
+ tmp = ra_translate_string(rp, buf);
+ }
+ else if (rp->type == FLB_RA_PARSER_KEYMAP) {
+ tmp = ra_translate_keymap(rp, buf, map, &found);
+ if (check == FLB_TRUE && found == FLB_FALSE) {
+ flb_warn("[record accessor] translation failed, root key=%s", rp->key->name);
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ }
+ else if (rp->type == FLB_RA_PARSER_REGEX_ID && result) {
+ tmp = ra_translate_regex_id(rp, result, buf);
+ }
+ else if (rp->type == FLB_RA_PARSER_TAG && tag) {
+ tmp = ra_translate_tag(rp, buf, tag, tag_len);
+ }
+ else if (rp->type == FLB_RA_PARSER_TAG_PART && tag) {
+ tmp = ra_translate_tag_part(rp, buf, tag, tag_len);
+ }
+
+ //else if (rp->type == FLB_RA_PARSER_FUNC) {
+ //tmp = ra_translate_func(rp, buf, tag, tag_len);
+ //}
+
+ if (!tmp) {
+ flb_error("[record accessor] translation failed");
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ if (tmp != buf) {
+ buf = tmp;
+ }
+ }
+
+ return buf;
+}
+
+/*
+ * If the record accessor rules do not generate content based on a keymap or
+ * regex, it's considered to be 'static', so the value returned will always be
+ * the same.
+ *
+ * If the 'ra' is static, return FLB_TRUE, otherwise FLB_FALSE.
+ */
+int flb_ra_is_static(struct flb_record_accessor *ra)
+{
+ struct mk_list *head;
+ struct flb_ra_parser *rp;
+
+ mk_list_foreach(head, &ra->list) {
+ rp = mk_list_entry(head, struct flb_ra_parser, _head);
+ if (rp->type == FLB_RA_PARSER_STRING) {
+ continue;
+ }
+ else if (rp->type == FLB_RA_PARSER_KEYMAP) {
+ return FLB_FALSE;
+ }
+ else if (rp->type == FLB_RA_PARSER_REGEX_ID) {
+ return FLB_FALSE;
+ }
+ else if (rp->type == FLB_RA_PARSER_TAG) {
+ continue;
+ }
+ else if (rp->type == FLB_RA_PARSER_TAG_PART) {
+ continue;
+ }
+ }
+
+ return FLB_TRUE;
+}
+
+/*
+ * Compare a string value against the first entry of a record accessor component, used
+ * specifically when the record accessor refers to a single key name.
+ */
+int flb_ra_strcmp(struct flb_record_accessor *ra, msgpack_object map,
+ char *str, int len)
+{
+ struct flb_ra_parser *rp;
+
+ rp = mk_list_entry_first(&ra->list, struct flb_ra_parser, _head);
+ return flb_ra_key_strcmp(rp->key->name, map, rp->key->subkeys,
+ rp->key->name, flb_sds_len(rp->key->name));
+}
+
+/*
+ * Check if a regular expression matches a record accessor key in the
+ * given map
+ */
+int flb_ra_regex_match(struct flb_record_accessor *ra, msgpack_object map,
+ struct flb_regex *regex, struct flb_regex_search *result)
+{
+ struct flb_ra_parser *rp;
+
+ rp = mk_list_entry_first(&ra->list, struct flb_ra_parser, _head);
+ if (rp == NULL || rp->key == NULL) {
+ return -1;
+ }
+ return flb_ra_key_regex_match(rp->key->name, map, rp->key->subkeys,
+ regex, result);
+}
+
+
+static struct flb_ra_parser* get_ra_parser(struct flb_record_accessor *ra)
+{
+ struct flb_ra_parser *rp = NULL;
+
+ if (mk_list_size(&ra->list) == 0) {
+ return NULL;
+ }
+ rp = mk_list_entry_first(&ra->list, struct flb_ra_parser, _head);
+ if (!rp->key) {
+ return NULL;
+ }
+ return rp;
+}
+
+/*
+ * If 'record accessor' pattern matches an entry in the 'map', set the
+ * reference in 'out_key' and 'out_val' for the entries in question.
+ *
+ * Returns FLB_TRUE if the pattern matched a kv pair, otherwise it returns
+ * FLB_FALSE.
+ */
+int flb_ra_get_kv_pair(struct flb_record_accessor *ra, msgpack_object map,
+ msgpack_object **start_key,
+ msgpack_object **out_key, msgpack_object **out_val)
+{
+ struct flb_ra_parser *rp;
+
+ rp = get_ra_parser(ra);
+ if (rp == NULL) {
+ return FLB_FALSE;
+ }
+
+ return flb_ra_key_value_get(rp->key->name, map, rp->key->subkeys,
+ start_key, out_key, out_val);
+}
+
+struct flb_ra_value *flb_ra_get_value_object(struct flb_record_accessor *ra,
+ msgpack_object map)
+{
+ struct flb_ra_parser *rp;
+
+ rp = get_ra_parser(ra);
+ if (rp == NULL) {
+ return NULL;
+ }
+
+ return flb_ra_key_to_value(rp->key->name, map, rp->key->subkeys);
+}
+
+/**
+ * Update key and/or value of the map using record accessor.
+ *
+ * @param ra the record accessor to specify key/value pair
+ * @param map the original map.
+ * @param in_key the pointer to overwrite key. If NULL, key will not be updated.
+ * @param in_val the pointer to overwrite val. If NULL, val will not be updated.
+ * @param out_map the updated map. If the API fails, out_map will be NULL.
+ *
+ * @return result of the API. 0:success, -1:fail
+ */
+
+int flb_ra_update_kv_pair(struct flb_record_accessor *ra, msgpack_object map,
+ void **out_map, size_t *out_size,
+ msgpack_object *in_key, msgpack_object *in_val)
+{
+ struct flb_ra_parser *rp;
+ msgpack_packer mp_pck;
+ msgpack_sbuffer mp_sbuf;
+ int ret;
+
+ msgpack_object *s_key;
+ msgpack_object *o_key;
+ msgpack_object *o_val;
+
+ if (in_key == NULL && in_val == NULL) {
+ /* no key and value. nothing to do */
+ flb_error("%s: no inputs", __FUNCTION__);
+ return -1;
+ }
+ else if (ra == NULL || out_map == NULL || out_size == NULL) {
+ /* invalid input */
+ flb_error("%s: invalid input", __FUNCTION__);
+ return -1;
+ }
+ else if ( flb_ra_get_kv_pair(ra, map, &s_key, &o_key, &o_val) != 0) {
+ /* key and value are not found */
+ flb_error("%s: no value", __FUNCTION__);
+ return -1;
+ }
+
+ rp = get_ra_parser(ra);
+ if (rp == NULL) {
+ return -1;
+ }
+
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ ret = flb_ra_key_value_update(rp, map, in_key, in_val, &mp_pck);
+ if (ret < 0) {
+ msgpack_sbuffer_destroy(&mp_sbuf);
+ return -1;
+ }
+ *out_map = mp_sbuf.data;
+ *out_size = mp_sbuf.size;
+
+ return 0;
+}
+
+/**
+ * Add key and/or value of the map using record accessor.
+ * If key already exists, the API fails.
+ *
+ * @param ra the record accessor to specify key.
+ * @param map the original map.
+ * @param in_val the pointer to add val.
+ * @param out_map the updated map. If the API fails, out_map will be NULL.
+ *
+ * @return result of the API. 0:success, -1:fail
+ */
+
+int flb_ra_append_kv_pair(struct flb_record_accessor *ra, msgpack_object map,
+ void **out_map, size_t *out_size,
+ msgpack_object *in_val)
+{
+ struct flb_ra_parser *rp;
+ msgpack_packer mp_pck;
+ msgpack_sbuffer mp_sbuf;
+ int ret;
+
+ msgpack_object *s_key = NULL;
+ msgpack_object *o_key = NULL;
+ msgpack_object *o_val = NULL;
+
+ if (in_val == NULL) {
+ /* no key and value. nothing to do */
+ flb_error("%s: no value", __FUNCTION__);
+ return -1;
+ }
+ else if (ra == NULL || out_map == NULL|| out_size == NULL) {
+ /* invalid input */
+ flb_error("%s: invalid input", __FUNCTION__);
+ return -1;
+ }
+
+ flb_ra_get_kv_pair(ra, map, &s_key, &o_key, &o_val);
+ if (o_key != NULL && o_val != NULL) {
+ /* key and value already exist */
+ flb_error("%s: already exist", __FUNCTION__);
+ return -1;
+ }
+
+ rp = get_ra_parser(ra);
+ if (rp == NULL || rp->key == NULL) {
+ return -1;
+ }
+
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ ret = flb_ra_key_value_append(rp, map, in_val, &mp_pck);
+ if (ret < 0) {
+ msgpack_sbuffer_destroy(&mp_sbuf);
+ return -1;
+ }
+
+ *out_map = mp_sbuf.data;
+ *out_size = mp_sbuf.size;
+
+ return 0;
+}
diff --git a/fluent-bit/src/flb_regex.c b/fluent-bit/src/flb_regex.c
new file mode 100644
index 000000000..b2a1a5d57
--- /dev/null
+++ b/fluent-bit/src/flb_regex.c
@@ -0,0 +1,319 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_regex.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+
+#include <string.h>
+#include <onigmo.h>
+
+static int
+cb_onig_named(const UChar *name, const UChar *name_end,
+ int ngroup_num, int *group_nums,
+ regex_t *reg, void *data)
+{
+ int i;
+ int gn;
+ struct flb_regex_search *s;
+ OnigRegion *region;
+
+ s = (struct flb_regex_search *) data;
+ region = s->region;
+
+ for (i = 0; i < ngroup_num; i++) {
+ gn = group_nums[i];
+ onig_name_to_backref_number(reg, name, name_end, region);
+
+ if (s->cb_match) {
+ s->cb_match((const char *)name,
+ s->str + region->beg[gn],
+ region->end[gn] - region->beg[gn],
+ s->data);
+ }
+
+ if (region->end[gn] >= 0) {
+ s->last_pos = region->end[gn];
+ }
+ }
+
+ return 0;
+}
+
+static OnigOptionType check_option(const char *start, const char *end, char **new_end)
+{
+ char *chr = NULL;
+ OnigOptionType option = ONIG_OPTION_NONE;
+
+ if (start == NULL || end == NULL || new_end == NULL) {
+ return ONIG_OPTION_DEFAULT;
+ } else if (start[0] != '/') {
+ *new_end = NULL;
+ return ONIG_OPTION_DEFAULT;
+ }
+
+ chr = strrchr(start, '/');
+ if (chr == start || chr == end) {
+ *new_end = NULL;
+ return ONIG_OPTION_DEFAULT;
+ }
+ *new_end = chr;
+
+ chr++;
+ while(chr != end && *chr != '\0') {
+ switch (*chr) {
+ case 'm':
+ option |= ONIG_OPTION_MULTILINE;
+ break;
+ case 'i':
+ option |= ONIG_OPTION_IGNORECASE;
+ break;
+ case 'o':
+ flb_debug("[regex:%s]: 'o' option is not supported.", __FUNCTION__);
+ break;
+ case 'x':
+ option |= ONIG_OPTION_EXTEND;
+ break;
+ default:
+ flb_debug("[regex:%s]: unknown option. use default.", __FUNCTION__);
+ *new_end = NULL;
+ return ONIG_OPTION_DEFAULT;
+ }
+ chr++;
+ }
+
+ if (option == ONIG_OPTION_NONE) {
+ *new_end = NULL;
+ option = ONIG_OPTION_DEFAULT;
+ }
+
+ return option;
+}
+
+static int str_to_regex(const char *pattern, OnigRegex *reg)
+{
+ int ret;
+ size_t len;
+ const char *start;
+ const char *end;
+ char *new_end = NULL;
+ OnigErrorInfo einfo;
+ OnigOptionType option;
+
+ len = strlen(pattern);
+ start = pattern;
+ end = pattern + len;
+
+ option = check_option(start, end, &new_end);
+
+ if (pattern[0] == '/' && pattern[len - 1] == '/') {
+ start++;
+ end--;
+ }
+
+ if (new_end != NULL) {
+ /* pattern is /pat/option. new_end indicates a last '/'. */
+ start++;
+ end = new_end;
+ }
+
+ ret = onig_new(reg,
+ (const unsigned char *)start, (const unsigned char *)end,
+ option,
+ ONIG_ENCODING_UTF8, ONIG_SYNTAX_RUBY, &einfo);
+
+ if (ret != ONIG_NORMAL) {
+ return -1;
+ }
+ return 0;
+}
+
+/* Initialize backend library */
+int flb_regex_init()
+{
+ return onig_init();
+}
+
+struct flb_regex *flb_regex_create(const char *pattern)
+{
+ int ret;
+ struct flb_regex *r;
+
+ /* Create context */
+ r = flb_malloc(sizeof(struct flb_regex));
+ if (!r) {
+ flb_errno();
+ return NULL;
+ }
+
+ /* Compile pattern */
+ ret = str_to_regex(pattern, (OnigRegex*)&r->regex);
+ if (ret == -1) {
+ flb_free(r);
+ return NULL;
+ }
+
+ return r;
+}
+
+ssize_t flb_regex_do(struct flb_regex *r, const char *str, size_t slen,
+ struct flb_regex_search *result)
+{
+ int ret;
+ const char *start;
+ const char *end;
+ const char *range;
+ OnigRegion *region;
+
+ region = onig_region_new();
+ if (!region) {
+ flb_errno();
+ result->region = NULL;
+ return -1;
+ }
+
+ /* Search scope */
+ start = str;
+ end = start + slen;
+ range = end;
+
+ ret = onig_search(r->regex,
+ (const unsigned char *)str,
+ (const unsigned char *)end,
+ (const unsigned char *)start,
+ (const unsigned char *)range,
+ region, ONIG_OPTION_NONE);
+ if (ret == ONIG_MISMATCH) {
+ result->region = NULL;
+ onig_region_free(region, 1);
+ return -1;
+ }
+ else if (ret < 0) {
+ result->region = NULL;
+ onig_region_free(region, 1);
+ return -1;
+ }
+
+ result->region = region;
+ result->str = str;
+
+ ret = region->num_regs - 1;
+
+ if (ret == 0) {
+ result->region = NULL;
+ onig_region_free(region, 1);
+ }
+
+ return ret;
+}
+
+int flb_regex_results_get(struct flb_regex_search *result, int i,
+ ptrdiff_t *start, ptrdiff_t *end)
+{
+ OnigRegion *region;
+
+ region = (OnigRegion *) result->region;
+ if (!region) {
+ return -1;
+ }
+
+ if (i >= region->num_regs) {
+ return -1;
+ }
+
+ *start = region->beg[i];
+ *end = region->end[i];
+
+ return 0;
+}
+
+void flb_regex_results_release(struct flb_regex_search *result)
+{
+ onig_region_free(result->region, 1);
+}
+
+int flb_regex_results_size(struct flb_regex_search *result)
+{
+ OnigRegion *region;
+
+ region = (OnigRegion *) result->region;
+ if (!region) {
+ return -1;
+ }
+
+ return region->num_regs;
+}
+
+int flb_regex_match(struct flb_regex *r, unsigned char *str, size_t slen)
+{
+ int ret;
+ unsigned char *start;
+ unsigned char *end;
+ unsigned char *range;
+
+ /* Search scope */
+ start = (unsigned char *) str;
+ end = start + slen;
+ range = end;
+
+ ret = onig_search(r->regex, str, end, start, range, NULL, ONIG_OPTION_NONE);
+
+ if (ret == ONIG_MISMATCH) {
+ return 0;
+ }
+ else if (ret < 0) {
+ return ret;
+ }
+ return 1;
+}
+
+
+int flb_regex_parse(struct flb_regex *r, struct flb_regex_search *result,
+ void (*cb_match) (const char *, /* name */
+ const char *, size_t, /* value */
+ void *), /* caller data */
+ void *data)
+{
+ int ret;
+
+ result->data = data;
+ result->cb_match = cb_match;
+ result->last_pos = -1;
+
+ ret = onig_foreach_name(r->regex, cb_onig_named, result);
+ onig_region_free(result->region, 1);
+
+ if (ret == 0) {
+ return result->last_pos;
+ }
+ return -1;
+}
+
+int flb_regex_destroy(struct flb_regex *r)
+{
+ onig_free(r->regex);
+ flb_free(r);
+ return 0;
+}
+
+void flb_regex_exit()
+{
+ onig_end();
+}
diff --git a/fluent-bit/src/flb_reload.c b/fluent-bit/src/flb_reload.c
new file mode 100644
index 000000000..641fa4a66
--- /dev/null
+++ b/fluent-bit/src/flb_reload.c
@@ -0,0 +1,517 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2023 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_error.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_lib.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_filter.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_custom.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_config_format.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_plugin.h>
+
+#include <cfl/cfl.h>
+#include <cfl/cfl_sds.h>
+#include <cfl/cfl_variant.h>
+#include <cfl/cfl_kvlist.h>
+
+static int flb_input_propery_check_all(struct flb_config *config)
+{
+ int ret;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_input_instance *ins;
+ struct flb_input_plugin *p;
+
+ /* Iterate all active input instance plugins */
+ mk_list_foreach_safe(head, tmp, &config->inputs) {
+ ins = mk_list_entry(head, struct flb_input_instance, _head);
+ p = ins->p;
+
+ /* Skip pseudo input plugins */
+ if (!p) {
+ continue;
+ }
+
+ /* Check net property */
+ ret = flb_input_net_property_check(ins, config);
+ if (ret == -1) {
+ return -1;
+ }
+
+ /* Check plugin property */
+ ret = flb_input_plugin_property_check(ins, config);
+ if (ret == -1) {
+ return -1;
+ }
+
+ /* destroy net config map (will be recreated at flb_start) */
+ if (ins->net_config_map) {
+ flb_config_map_destroy(ins->net_config_map);
+ ins->net_config_map = NULL;
+ }
+
+ /* destroy config map (will be recreated at flb_start) */
+ if (ins->config_map) {
+ flb_config_map_destroy(ins->config_map);
+ ins->config_map = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static int flb_output_propery_check_all(struct flb_config *config)
+{
+ int ret;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_output_instance *ins;
+
+ /* Iterate all active input instance plugins */
+ mk_list_foreach_safe(head, tmp, &config->outputs) {
+ ins = mk_list_entry(head, struct flb_output_instance, _head);
+
+ /* Check net property */
+ ret = flb_output_net_property_check(ins, config);
+ if (ret == -1) {
+ return -1;
+ }
+
+ /* Check plugin property */
+ ret = flb_output_plugin_property_check(ins, config);
+ if (ret == -1) {
+ return -1;
+ }
+
+ /* destroy net config map (will be recreated at flb_start) */
+ if (ins->net_config_map) {
+ flb_config_map_destroy(ins->net_config_map);
+ ins->net_config_map = NULL;
+ }
+
+ /* destroy config map (will be recreated at flb_start) */
+ if (ins->config_map) {
+ flb_config_map_destroy(ins->config_map);
+ ins->config_map = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static int flb_filter_propery_check_all(struct flb_config *config)
+{
+ int ret;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_filter_instance *ins;
+
+ /* Iterate all active input instance plugins */
+ mk_list_foreach_safe(head, tmp, &config->filters) {
+ ins = mk_list_entry(head, struct flb_filter_instance, _head);
+
+ if (flb_filter_match_property_existence(ins) == FLB_FALSE) {
+ flb_error("[filter] NO match rule for %s filter instance, halting to reload.",
+ ins->name);
+ return -1;
+ }
+
+ /* Check plugin property */
+ ret = flb_filter_plugin_property_check(ins, config);
+ if (ret == -1) {
+ return -1;
+ }
+
+ /* destroy config map (will be recreated at flb_start) */
+ if (ins->config_map) {
+ flb_config_map_destroy(ins->config_map);
+ ins->config_map = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static int flb_custom_propery_check_all(struct flb_config *config)
+{
+ int ret;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_custom_instance *ins;
+
+ /* Iterate all active input instance plugins */
+ mk_list_foreach_safe(head, tmp, &config->customs) {
+ ins = mk_list_entry(head, struct flb_custom_instance, _head);
+
+ /* Check plugin property */
+ ret = flb_custom_plugin_property_check(ins, config);
+ if (ret == -1) {
+ return -1;
+ }
+
+ /* destroy config map (will be recreated at flb_start) */
+ if (ins->config_map) {
+ flb_config_map_destroy(ins->config_map);
+ ins->config_map = NULL;
+ }
+ }
+
+ return 0;
+}
+
+int flb_reload_property_check_all(struct flb_config *config)
+{
+ int ret = 0;
+
+ /* Check properties of custom plugins */
+ ret = flb_custom_propery_check_all(config);
+ if (ret == -1) {
+ flb_error("[reload] check properties for custom plugins is failed");
+
+ return -1;
+ }
+
+ /* Check properties of input plugins */
+ ret = flb_input_propery_check_all(config);
+ if (ret == -1) {
+ flb_error("[reload] check properties for input plugins is failed");
+
+ return -1;
+ }
+
+ /* Check properties of filter plugins */
+ ret = flb_filter_propery_check_all(config);
+ if (ret == -1) {
+ flb_error("[reload] check properties for filter plugins is failed");
+
+ return -1;
+ }
+
+ /* Check properties of output plugins */
+ ret = flb_output_propery_check_all(config);
+ if (ret == -1) {
+ flb_error("[reload] check properties for output plugins is failed");
+
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Hot reload
+ * ----------
+ * Reload a Fluent Bit instance by using a new 'config_format' context.
+ *
+ * 1. As a first step, the config format is validated against the 'config maps',
+ * this will check that all configuration properties are valid.
+ */
+
+static int recreate_cf_section(struct flb_cf_section *s, struct flb_cf *cf)
+{
+ struct mk_list *head;
+ struct cfl_list *p_head;
+ struct cfl_kvpair *kv;
+ struct flb_cf_group *g;
+ struct flb_cf_section *new_s;
+ struct flb_cf_group *new_g;
+ struct cfl_variant *var = NULL;
+
+ new_s = flb_cf_section_create(cf, s->name, flb_sds_len(s->name));
+ if (cfl_list_size(&s->properties->list) > 0) {
+ cfl_list_foreach(p_head, &s->properties->list) {
+ var = NULL;
+ kv = cfl_list_entry(p_head, struct cfl_kvpair, _head);
+ var = flb_cf_section_property_add(cf, new_s->properties,
+ kv->key, cfl_sds_len(kv->key),
+ kv->val->data.as_string, cfl_sds_len(kv->val->data.as_string));
+
+ if (var == NULL) {
+ flb_error("[reload] recreating section '%s' property '%s' is failed", s->name, kv->key);
+ return -1;
+ }
+ }
+ }
+
+ if (mk_list_size(&s->groups) <= 0) {
+ return 0;
+ }
+
+ mk_list_foreach(head, &s->groups) {
+ g = mk_list_entry(head, struct flb_cf_group, _head);
+ new_g = flb_cf_group_create(cf, new_s, g->name, flb_sds_len(g->name));
+
+ if (cfl_list_size(&g->properties->list) > 0) {
+ cfl_list_foreach(p_head, &g->properties->list) {
+ var = NULL;
+ kv = cfl_list_entry(p_head, struct cfl_kvpair, _head);
+ var = flb_cf_section_property_add(cf, new_g->properties,
+ kv->key, cfl_sds_len(kv->key),
+ kv->val->data.as_string, cfl_sds_len(kv->val->data.as_string));
+ if (var == NULL) {
+ flb_error("[reload] recreating group '%s' property '%s' is failed", g->name, kv->key);
+ return -1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+int flb_reload_reconstruct_cf(struct flb_cf *src_cf, struct flb_cf *dest_cf)
+{
+ struct mk_list *head;
+ struct flb_cf_section *s;
+ struct flb_kv *kv;
+
+ mk_list_foreach(head, &src_cf->sections) {
+ s = mk_list_entry(head, struct flb_cf_section, _head);
+ if (recreate_cf_section(s, dest_cf) != 0) {
+ return -1;
+ }
+ }
+
+ /* Copy and store env. (For yaml cf.) */
+ mk_list_foreach(head, &src_cf->env) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ if (!flb_cf_env_property_add(dest_cf,
+ kv->key, cfl_sds_len(kv->key),
+ kv->val, cfl_sds_len(kv->val))) {
+ return -1;
+ }
+
+ }
+
+ /* Copy and store metas. (For old fluent-bit cf.) */
+ mk_list_foreach(head, &src_cf->metas) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ if (!flb_kv_item_create_len(&dest_cf->metas,
+ kv->key, cfl_sds_len(kv->key),
+ kv->val, cfl_sds_len(kv->val))) {
+ return -1;
+ }
+
+ }
+
+ return 0;
+}
+
+#ifdef FLB_HAVE_STREAM_PROCESSOR
+static int flb_reload_reconstruct_sp(struct flb_config *src, struct flb_config *dest)
+{
+ struct mk_list *head;
+ struct flb_slist_entry *e;
+
+ /* Check for pre-configured Tasks (command line) */
+ mk_list_foreach(head, &src->stream_processor_tasks) {
+ e = mk_list_entry(head, struct flb_slist_entry, _head);
+ flb_slist_add(&dest->stream_processor_tasks, e->str);
+ }
+
+ return 0;
+}
+#endif
+
+static int flb_reload_reinstantiate_external_plugins(struct flb_config *src, struct flb_config *dest)
+{
+ int ret;
+ struct mk_list *head;
+ struct flb_slist_entry *e;
+
+ /* Check for pre-configured Tasks (command line) */
+ mk_list_foreach(head, &src->external_plugins) {
+ e = mk_list_entry(head, struct flb_slist_entry, _head);
+ flb_info("[reload] slist externals %s", e->str);
+ /* Load the new config format context to config context. */
+ ret = flb_plugin_load_router(e->str, dest);
+ if (ret != 0) {
+ return -1;
+ }
+ flb_slist_add(&dest->external_plugins, e->str);
+ }
+
+ return 0;
+}
+
+int flb_reload(flb_ctx_t *ctx, struct flb_cf *cf_opts)
+{
+ int ret;
+ flb_sds_t file = NULL;
+ struct flb_config *old_config;
+ struct flb_config *new_config;
+ flb_ctx_t *new_ctx = NULL;
+ struct flb_cf *new_cf;
+ struct flb_cf *original_cf;
+ int verbose;
+ int reloaded_count = 0;
+
+ if (ctx == NULL) {
+ flb_error("[reload] given flb context is NULL");
+ return -2;
+ }
+
+ old_config = ctx->config;
+ if (old_config->enable_hot_reload != FLB_TRUE) {
+ flb_warn("[reload] hot reloading is not enabled");
+ return -3;
+ }
+
+ if (old_config->ensure_thread_safety_on_hot_reloading) {
+ old_config->grace = -1;
+ }
+
+ /* Normally, we should create a service section before using this cf
+ * context. However, this context of config format will be used
+ * for copying contents from other one. So, we just need to create
+ * a new cf instance here.
+ */
+ new_cf = flb_cf_create();
+ if (!new_cf) {
+ return -1;
+ }
+
+ flb_info("reloading instance pid=%lu tid=%p", (long unsigned) getpid(), pthread_self());
+
+ if (old_config->conf_path_file) {
+ file = flb_sds_create(old_config->conf_path_file);
+ }
+ if (cf_opts != NULL) {
+ if (flb_reload_reconstruct_cf(cf_opts, new_cf) != 0) {
+ if (file != NULL) {
+ flb_sds_destroy(file);
+ }
+ flb_error("[reload] reconstruct cf failed");
+ return -1;
+ }
+ }
+
+ /* Create another instance */
+ new_ctx = flb_create();
+ if (new_ctx == NULL) {
+ if (file != NULL) {
+ flb_sds_destroy(file);
+ }
+ flb_cf_destroy(new_cf);
+ flb_error("[reload] creating flb context is failed. Reloading is halted");
+
+ return -1;
+ }
+
+ new_config = new_ctx->config;
+
+ /* Inherit verbose from the old ctx instance */
+ verbose = ctx->config->verbose;
+ new_config->verbose = verbose;
+ /* Increment and store the number of hot reloaded times */
+ reloaded_count = ctx->config->hot_reloaded_count + 1;
+
+#ifdef FLB_HAVE_STREAM_PROCESSOR
+ /* Inherit stream processor definitions from command line */
+ flb_reload_reconstruct_sp(old_config, new_config);
+#endif
+
+ /* Create another config format context */
+ if (file != NULL) {
+ new_cf = flb_cf_create_from_file(new_cf, file);
+
+ if (!new_cf) {
+ flb_sds_destroy(file);
+
+ return -1;
+ }
+ }
+
+ /* Load external plugins via command line */
+ if (mk_list_size(&old_config->external_plugins) > 0) {
+ ret = flb_reload_reinstantiate_external_plugins(old_config, new_config);
+ if (ret == -1) {
+ if (file != NULL) {
+ flb_sds_destroy(file);
+ }
+ flb_cf_destroy(new_cf);
+ flb_stop(new_ctx);
+ flb_destroy(new_ctx);
+ flb_error("[reload] reloaded config is invalid. Reloading is halted");
+
+ return -1;
+ }
+ }
+
+ /* Load the new config format context to config context. */
+ ret = flb_config_load_config_format(new_config, new_cf);
+ if (ret != 0) {
+ flb_sds_destroy(file);
+ flb_cf_destroy(new_cf);
+ flb_stop(new_ctx);
+ flb_destroy(new_ctx);
+
+ flb_error("[reload] reloaded config format is invalid. Reloading is halted");
+
+ return -1;
+ }
+
+ /* Validate plugin properites before fluent-bit stops the old context. */
+ ret = flb_reload_property_check_all(new_config);
+ if (ret != 0) {
+ flb_sds_destroy(file);
+ flb_cf_destroy(new_cf);
+ flb_stop(new_ctx);
+ flb_destroy(new_ctx);
+
+ flb_error("[reload] reloaded config is invalid. Reloading is halted");
+
+ return -1;
+ }
+
+ /* Delete the original context of config format before replacing
+ * with the new one. */
+ original_cf = new_config->cf_main;
+ flb_cf_destroy(original_cf);
+
+ new_config->cf_main = new_cf;
+ new_config->cf_opts = cf_opts;
+
+ if (file != NULL) {
+ new_config->conf_path_file = file;
+ }
+
+ flb_info("[reload] stop everything of the old context");
+ flb_stop(ctx);
+ flb_destroy(ctx);
+
+ flb_info("[reload] start everything");
+
+ ret = flb_start(new_ctx);
+
+ /* Store the new value of hot reloading times into the new context */
+ if (ret == 0) {
+ new_config->hot_reloaded_count = reloaded_count;
+ flb_debug("[reload] hot reloaded %d time(s)", reloaded_count);
+ }
+
+ return 0;
+}
diff --git a/fluent-bit/src/flb_ring_buffer.c b/fluent-bit/src/flb_ring_buffer.c
new file mode 100644
index 000000000..77b6e86b1
--- /dev/null
+++ b/fluent-bit/src/flb_ring_buffer.c
@@ -0,0 +1,205 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+/*
+ * This interface is a wrapper of the 'lwrb' ring buffer implementation:
+ *
+ * - https://github.com/MaJerle/lwrb
+ */
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_pipe.h>
+#include <fluent-bit/flb_ring_buffer.h>
+#include <fluent-bit/flb_engine_macros.h>
+
+#include <monkey/mk_core.h>
+
+#include <math.h>
+
+/* lwrb header */
+#include <lwrb/lwrb.h>
+
+static void flb_ring_buffer_remove_event_loop(struct flb_ring_buffer *rb);
+
+struct flb_ring_buffer *flb_ring_buffer_create(uint64_t size)
+{
+ lwrb_t *lwrb;
+ void * data_buf;
+ size_t data_size;
+ struct flb_ring_buffer *rb;
+
+ rb = flb_calloc(1, sizeof(struct flb_ring_buffer));
+ if (!rb) {
+ flb_errno();
+ return NULL;
+ }
+ rb->data_size = size;
+
+ /* lwrb context */
+ lwrb = flb_malloc(sizeof(lwrb_t));
+ if (!lwrb) {
+ flb_errno();
+ flb_free(rb);
+ return NULL;
+ }
+ rb->ctx = lwrb;
+
+ /* data buffer for backend library */
+ data_size = 1 + (sizeof(uint8_t) * size);
+ data_buf = flb_calloc(1, data_size);
+ if (!data_buf) {
+ flb_errno();
+ flb_free(rb);
+ flb_free(lwrb);
+ return NULL;
+ }
+ rb->data_buf = data_buf;
+
+ /* initialize lwrb */
+ lwrb_init(rb->ctx, data_buf, data_size);
+
+ return rb;
+}
+
+void flb_ring_buffer_destroy(struct flb_ring_buffer *rb)
+{
+ flb_ring_buffer_remove_event_loop(rb);
+
+ if (rb->data_buf) {
+ flb_free(rb->data_buf);
+ }
+
+ if (rb->ctx) {
+ flb_free(rb->ctx);
+ }
+
+ flb_free(rb);
+}
+
+int flb_ring_buffer_add_event_loop(struct flb_ring_buffer *rb, void *evl, uint8_t window_size)
+{
+ int result;
+
+ if (window_size == 0) {
+ return -1;
+ }
+ else if (window_size > 100) {
+ window_size = 100;
+ }
+
+ rb->data_window = (uint64_t) floor((rb->data_size * window_size) / 100);
+
+ result = flb_pipe_create(rb->signal_channels);
+
+ if (result) {
+ return -2;
+ }
+
+ flb_pipe_set_nonblocking(rb->signal_channels[0]);
+ flb_pipe_set_nonblocking(rb->signal_channels[1]);
+
+ rb->signal_event = (void *) flb_calloc(1, sizeof(struct mk_event));
+
+ if (rb->signal_event == NULL) {
+ flb_pipe_destroy(rb->signal_channels);
+
+ return -2;
+ }
+
+ MK_EVENT_ZERO(rb->signal_event);
+
+ result = mk_event_add(evl,
+ rb->signal_channels[0],
+ FLB_ENGINE_EV_THREAD_INPUT,
+ MK_EVENT_READ,
+ rb->signal_event);
+
+ if (result) {
+ flb_pipe_destroy(rb->signal_channels);
+ flb_free(rb->signal_event);
+
+ rb->signal_event = NULL;
+
+ return -3;
+ }
+
+ rb->event_loop = evl;
+
+ return 0;
+}
+
+static void flb_ring_buffer_remove_event_loop(struct flb_ring_buffer *rb)
+{
+ if (rb->event_loop != NULL) {
+ mk_event_del(rb->event_loop, rb->signal_event);
+ flb_pipe_destroy(rb->signal_channels);
+ flb_free(rb->signal_event);
+
+ rb->signal_event = NULL;
+ rb->data_window = 0;
+ rb->event_loop = NULL;
+ }
+}
+
+int flb_ring_buffer_write(struct flb_ring_buffer *rb, void *ptr, size_t size)
+{
+ size_t used_size;
+ size_t ret;
+ size_t av;
+
+ /* make sure there is enough space available */
+ av = lwrb_get_free(rb->ctx);
+ if (av < size) {
+ return -1;
+ }
+
+ /* write the content */
+ ret = lwrb_write(rb->ctx, ptr, size);
+ if (ret == 0) {
+ return -1;
+ }
+
+ if (!rb->flush_pending) {
+ used_size = rb->data_size - (av - size);
+
+ if (used_size >= rb->data_window) {
+ rb->flush_pending = FLB_TRUE;
+
+ flb_pipe_write_all(rb->signal_channels[1], ".", 1);
+ }
+ }
+
+ return 0;
+}
+
+int flb_ring_buffer_read(struct flb_ring_buffer *rb, void *ptr, size_t size)
+{
+ size_t ret;
+
+ ret = lwrb_read(rb->ctx, ptr, size);
+ if (ret == 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+
diff --git a/fluent-bit/src/flb_router.c b/fluent-bit/src/flb_router.c
new file mode 100644
index 000000000..551e9c335
--- /dev/null
+++ b/fluent-bit/src/flb_router.c
@@ -0,0 +1,271 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_input_chunk.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_router.h>
+
+#ifdef FLB_HAVE_REGEX
+#include <onigmo.h>
+#endif
+
+#include <string.h>
+
+/* wildcard support */
+/* tag and match should be null terminated. */
+static inline int router_match(const char *tag, int tag_len,
+ const char *match,
+ void *match_r)
+{
+ int ret = FLB_FALSE;
+ char *pos = NULL;
+
+#ifdef FLB_HAVE_REGEX
+ struct flb_regex *match_regex = match_r;
+ int n;
+ if (match_regex) {
+ n = onig_match(match_regex->regex,
+ (const unsigned char *) tag,
+ (const unsigned char *) tag + tag_len,
+ (const unsigned char *) tag, 0,
+ ONIG_OPTION_NONE);
+ if (n > 0) {
+ return 1;
+ }
+ }
+#else
+ (void) match_r;
+#endif
+
+ while (match) {
+ if (*match == '*') {
+ while (*++match == '*'){
+ /* skip successive '*' */
+ }
+ if (*match == '\0') {
+ /* '*' is last of string */
+ ret = 1;
+ break;
+ }
+
+ while ((pos = strchr(tag, (int) *match))) {
+#ifndef FLB_HAVE_REGEX
+ if (router_match(pos, tag_len, match, NULL)) {
+#else
+ /* We don't need to pass the regex recursively,
+ * we matched in order above
+ */
+ if (router_match(pos, tag_len, match, NULL)) {
+#endif
+ ret = 1;
+ break;
+ }
+ tag = pos+1;
+ }
+ break;
+ }
+ else if (*tag != *match) {
+ /* mismatch! */
+ break;
+ }
+ else if (*tag == '\0') {
+ /* end of tag. so matched! */
+ ret = 1;
+ break;
+ }
+ tag++;
+ match++;
+ }
+
+ return ret;
+}
+
+int flb_router_match(const char *tag, int tag_len, const char *match,
+ void *match_regex)
+{
+ int ret;
+ flb_sds_t t;
+
+ if (tag[tag_len] != '\0') {
+ t = flb_sds_create_len(tag, tag_len);
+ if (!t) {
+ return FLB_FALSE;
+ }
+
+ ret = router_match(t, tag_len, match, match_regex);
+ flb_sds_destroy(t);
+ }
+ else {
+ ret = router_match(tag, tag_len, match, match_regex);
+ }
+
+ return ret;
+}
+
+/* Associate and input and output instances due to a previous match */
+int flb_router_connect(struct flb_input_instance *in,
+ struct flb_output_instance *out)
+{
+ struct flb_router_path *p;
+
+ p = flb_malloc(sizeof(struct flb_router_path));
+ if (!p) {
+ flb_errno();
+ return -1;
+ }
+
+ p->ins = out;
+ mk_list_add(&p->_head, &in->routes);
+
+ return 0;
+}
+
+int flb_router_connect_direct(struct flb_input_instance *in,
+ struct flb_output_instance *out)
+{
+ struct flb_router_path *p;
+
+ p = flb_malloc(sizeof(struct flb_router_path));
+ if (!p) {
+ flb_errno();
+ return -1;
+ }
+
+ p->ins = out;
+ mk_list_add(&p->_head, &in->routes_direct);
+
+ return 0;
+}
+
+/*
+ * This routine defines static routes for the plugins that have registered
+ * tags. It check where data should go before the service start running, each
+ * input 'instance' plugin will contain a list of destinations.
+ */
+int flb_router_io_set(struct flb_config *config)
+{
+ int in_count = 0;
+ int out_count = 0;
+ struct mk_list *i_head;
+ struct mk_list *o_head;
+ struct flb_input_instance *i_ins;
+ struct flb_output_instance *o_ins;
+
+ /* Quick setup for 1:1 */
+ in_count = mk_list_size(&config->inputs);
+ out_count = mk_list_size(&config->outputs);
+
+ /* Mostly used for command line tests */
+ if (in_count == 1 && out_count == 1) {
+ i_ins = mk_list_entry_first(&config->inputs, struct flb_input_instance, _head);
+ o_ins = mk_list_entry_first(&config->outputs, struct flb_output_instance, _head);
+
+ if (!o_ins->match
+#ifdef FLB_HAVE_REGEX
+ && !o_ins->match_regex
+#endif
+ ) {
+
+ o_ins->match = flb_sds_create_len("*", 1);
+ }
+ flb_router_connect(i_ins, o_ins);
+ return 0;
+ }
+
+ /* N:M case, iterate all input instances */
+ mk_list_foreach(i_head, &config->inputs) {
+ i_ins = mk_list_entry(i_head, struct flb_input_instance, _head);
+ if (!i_ins->p) {
+ continue;
+ }
+
+ if (!i_ins->tag) {
+ flb_warn("[router] NO tag for %s input instance",
+ i_ins->name);
+ continue;
+ }
+
+
+ flb_trace("[router] input=%s tag=%s", i_ins->name, i_ins->tag);
+
+ /* Try to find a match with output instances */
+ mk_list_foreach(o_head, &config->outputs) {
+ o_ins = mk_list_entry(o_head, struct flb_output_instance, _head);
+ if (!o_ins->match
+#ifdef FLB_HAVE_REGEX
+ && !o_ins->match_regex
+#endif
+ ) {
+ flb_warn("[router] NO match for %s output instance",
+ o_ins->name);
+ continue;
+ }
+
+ if (flb_router_match(i_ins->tag, i_ins->tag_len, o_ins->match
+#ifdef FLB_HAVE_REGEX
+ , o_ins->match_regex
+#else
+ , NULL
+#endif
+ )) {
+
+ flb_debug("[router] match rule %s:%s",
+ i_ins->name, o_ins->name);
+ flb_router_connect(i_ins, o_ins);
+ }
+ }
+ }
+
+ return 0;
+}
+
+void flb_router_exit(struct flb_config *config)
+{
+ struct mk_list *tmp;
+ struct mk_list *r_tmp;
+ struct mk_list *head;
+ struct mk_list *r_head;
+ struct flb_input_instance *in;
+ struct flb_router_path *r;
+
+ /* Iterate input plugins */
+ mk_list_foreach_safe(head, tmp, &config->inputs) {
+ in = mk_list_entry(head, struct flb_input_instance, _head);
+
+ /* Iterate instance routes */
+ mk_list_foreach_safe(r_head, r_tmp, &in->routes) {
+ r = mk_list_entry(r_head, struct flb_router_path, _head);
+ mk_list_del(&r->_head);
+ flb_free(r);
+ }
+
+ /* Iterate instance routes direct */
+ mk_list_foreach_safe(r_head, r_tmp, &in->routes_direct) {
+ r = mk_list_entry(r_head, struct flb_router_path, _head);
+ mk_list_del(&r->_head);
+ flb_free(r);
+ }
+ }
+}
diff --git a/fluent-bit/src/flb_routes_mask.c b/fluent-bit/src/flb_routes_mask.c
new file mode 100644
index 000000000..e9a21f5a7
--- /dev/null
+++ b/fluent-bit/src/flb_routes_mask.c
@@ -0,0 +1,143 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_router.h>
+#include <fluent-bit/flb_routes_mask.h>
+
+
+/*
+ * Set the routes_mask for input chunk with a router_match on tag, return a
+ * non-zero value if any routes matched
+ */
+int flb_routes_mask_set_by_tag(uint64_t *routes_mask,
+ const char *tag,
+ int tag_len,
+ struct flb_input_instance *in)
+{
+ int has_routes = 0;
+ struct mk_list *o_head;
+ struct flb_output_instance *o_ins;
+ if (!in) {
+ return 0;
+ }
+
+ /* Clear the bit field */
+ memset(routes_mask, 0, sizeof(uint64_t) * FLB_ROUTES_MASK_ELEMENTS);
+
+ /* Find all matching routes for the given tag */
+ mk_list_foreach(o_head, &in->config->outputs) {
+ o_ins = mk_list_entry(o_head,
+ struct flb_output_instance, _head);
+
+ if (flb_router_match(tag, tag_len, o_ins->match
+#ifdef FLB_HAVE_REGEX
+ , o_ins->match_regex
+#else
+ , NULL
+#endif
+ )) {
+ flb_routes_mask_set_bit(routes_mask, o_ins->id);
+ has_routes = 1;
+ }
+ }
+
+ return has_routes;
+}
+
+/*
+ * Sets a single bit in an array of bitfields
+ *
+ * For example: Given a value of 35 this routine will set the
+ * 4th bit in the 2nd value of the bitfield array.
+ *
+ */
+void flb_routes_mask_set_bit(uint64_t *routes_mask, int value)
+{
+ int index;
+ uint64_t bit;
+
+ if (value < 0 || value > FLB_ROUTES_MASK_MAX_VALUE) {
+ flb_warn("[routes_mask] Can't set bit (%d) past limits of bitfield",
+ value);
+ return;
+ }
+
+ index = value / FLB_ROUTES_MASK_ELEMENT_BITS;
+ bit = 1ULL << (value % FLB_ROUTES_MASK_ELEMENT_BITS);
+ routes_mask[index] |= bit;
+}
+
+/*
+ * Clears a single bit in an array of bitfields
+ *
+ * For example: Given a value of 68 this routine will clear the
+ * 4th bit in the 2nd value of the bitfield array.
+ *
+ */
+void flb_routes_mask_clear_bit(uint64_t *routes_mask, int value)
+{
+ int index;
+ uint64_t bit;
+
+ if (value < 0 || value > FLB_ROUTES_MASK_MAX_VALUE) {
+ flb_warn("[routes_mask] Can't set bit (%d) past limits of bitfield",
+ value);
+ return;
+ }
+
+ index = value / FLB_ROUTES_MASK_ELEMENT_BITS;
+ bit = 1ULL << (value % FLB_ROUTES_MASK_ELEMENT_BITS);
+ routes_mask[index] &= ~(bit);
+}
+
+/*
+ * Checks the value of a single bit in an array of bitfields and returns a
+ * non-zero value if that bit is set.
+ *
+ * For example: Given a value of 68 this routine will return a non-zero value
+ * if the 4th bit in the 2nd value of the bitfield array is set.
+ *
+ */
+int flb_routes_mask_get_bit(uint64_t *routes_mask, int value)
+{
+ int index;
+ uint64_t bit;
+
+ if (value < 0 || value > FLB_ROUTES_MASK_MAX_VALUE) {
+ flb_warn("[routes_mask] Can't get bit (%d) past limits of bitfield",
+ value);
+ return 0;
+ }
+
+ index = value / FLB_ROUTES_MASK_ELEMENT_BITS;
+ bit = 1ULL << (value % FLB_ROUTES_MASK_ELEMENT_BITS);
+ return (routes_mask[index] & bit) != 0ULL;
+}
+
+int flb_routes_mask_is_empty(uint64_t *routes_mask)
+{
+ uint64_t empty[FLB_ROUTES_MASK_ELEMENTS];
+
+ /* Clear the tmp bitfield */
+ memset(empty, 0, sizeof(empty));
+ return memcmp(routes_mask, empty, sizeof(empty)) == 0;
+}
diff --git a/fluent-bit/src/flb_scheduler.c b/fluent-bit/src/flb_scheduler.c
new file mode 100644
index 000000000..91f706828
--- /dev/null
+++ b/fluent-bit/src/flb_scheduler.c
@@ -0,0 +1,727 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_coro.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_scheduler.h>
+#include <fluent-bit/flb_pipe.h>
+#include <fluent-bit/flb_engine.h>
+#include <fluent-bit/flb_engine_dispatch.h>
+#include <fluent-bit/flb_random.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+FLB_TLS_DEFINE(struct flb_sched, flb_sched_ctx);
+
+void flb_sched_ctx_init()
+{
+ FLB_TLS_INIT(flb_sched_ctx);
+}
+
+struct flb_sched *flb_sched_ctx_get()
+{
+ struct flb_sched *sched;
+
+ sched = FLB_TLS_GET(flb_sched_ctx);
+ return sched;
+}
+
+void flb_sched_ctx_set(struct flb_sched *sched)
+{
+ FLB_TLS_SET(flb_sched_ctx, sched);
+}
+
+static inline double xmin(double a, double b)
+{
+ return a < b ? a : b;
+}
+
+/* Consume an unsigned 64 bit number from fd */
+static inline int consume_byte(flb_pipefd_t fd)
+{
+ int ret;
+ uint64_t val;
+
+ /* We need to consume the byte */
+ ret = flb_pipe_r(fd, &val, sizeof(val));
+#if defined(__APPLE__) || __FreeBSD__ >= 12
+ if (ret < 0) {
+#else
+ if (ret <= 0) {
+#endif
+ flb_errno();
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Generate an uniform random value between min and max. Original version
+ * taken from internet and modified to use /dev/urandom to set a seed on
+ * each call. Despites using the urandom device may add some overhead,
+ * this function is not called too often so it should not be an issue.
+ */
+static int random_uniform(int min, int max)
+{
+ int val;
+ int range;
+ int copies;
+ int limit;
+ int ra;
+
+ if (flb_random_bytes((unsigned char *) &val, sizeof(int))) {
+ val = time(NULL);
+ }
+ srand(val);
+
+ range = max - min + 1;
+ copies = (RAND_MAX / range);
+ limit = range * copies;
+ ra = -1;
+
+ while (ra < 0 || ra >= limit) {
+ ra = rand();
+ }
+
+ return ra / copies + min;
+}
+
+
+/*
+ * Schedule a request that will be processed within the next
+ * FLB_SCHED_REQUEST_FRAME seconds.
+ */
+static int schedule_request_now(int seconds,
+ struct flb_sched_timer *timer,
+ struct flb_sched_request *request,
+ struct flb_config *config)
+{
+ flb_pipefd_t fd;
+ struct mk_event *event;
+ struct flb_sched *sched = config->sched;
+
+ /* Initialize event */
+ event = &timer->event;
+ event->mask = MK_EVENT_EMPTY;
+ event->status = MK_EVENT_NONE;
+
+ /* Create a timeout into the main event loop */
+ fd = mk_event_timeout_create(config->evl, seconds, 0, event);
+ event->priority = FLB_ENGINE_PRIORITY_CB_SCHED;
+ if (fd == -1) {
+ return -1;
+ }
+ request->fd = fd;
+ timer->timer_fd = fd;
+
+ /*
+ * Note: mk_event_timeout_create() sets a type = MK_EVENT_NOTIFICATION by
+ * default, we need to overwrite this value so we can do a clean check
+ * into the Engine when the event is triggered.
+ */
+ event->type = FLB_ENGINE_EV_SCHED;
+ mk_list_add(&request->_head, &sched->requests);
+
+ return 0;
+}
+
+/*
+ * Enqueue a request that will wait until it expected timeout reach the
+ * FLB_SCHED_REQUEST_FRAME interval.
+ */
+static int schedule_request_wait(struct flb_sched_request *request,
+ struct flb_config *config)
+{
+ struct flb_sched *sched = config->sched;
+
+ mk_list_add(&request->_head, &sched->requests_wait);
+ return 0;
+}
+
+/*
+ * Iterate requests_wait list looking for candidates to be promoted
+ * to the 'requests' list.
+ */
+static int schedule_request_promote(struct flb_sched *sched)
+{
+ int ret;
+ int next;
+ int passed;
+ time_t now;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct mk_list failed_requests;
+ struct flb_sched_request *request;
+
+ now = time(NULL);
+ mk_list_init(&failed_requests);
+
+ mk_list_foreach_safe(head, tmp, &sched->requests_wait) {
+ request = mk_list_entry(head, struct flb_sched_request, _head);
+
+ /* First check how many seconds have passed since the request creation */
+ passed = (now - request->created);
+ ret = 0;
+
+ /* If we passed the original time, schedule now for the next second */
+ if (passed > request->timeout) {
+ mk_list_del(&request->_head);
+ ret = schedule_request_now(1, request->timer, request, sched->config);
+ if (ret != 0) {
+ mk_list_add(&request->_head, &failed_requests);
+ }
+ }
+ else if (passed + FLB_SCHED_REQUEST_FRAME >= request->timeout) {
+ /* Check if we should schedule within this frame */
+ mk_list_del(&request->_head);
+ next = labs(passed - request->timeout);
+ ret = schedule_request_now(next, request->timer, request, sched->config);
+ if (ret != 0) {
+ mk_list_add(&request->_head, &failed_requests);
+ }
+ }
+ else {
+ continue;
+ }
+
+ /*
+ * If the 'request' could not be scheduled, this could only happen due to memory
+ * exhaustion or running out of file descriptors. There is no much we can do
+ * at this time.
+ */
+ if (ret == -1) {
+ flb_error("[sched] a 'retry request' could not be scheduled. the "
+ "system might be running out of memory or file "
+ "descriptors. The scheduler will do a retry later.");
+ }
+ }
+
+ /* For each failed request, re-add them to the wait list */
+ mk_list_foreach_safe(head, tmp, &failed_requests) {
+ request = mk_list_entry(head, struct flb_sched_request, _head);
+ mk_list_del(&request->_head);
+ mk_list_add(&request->_head, &sched->requests_wait);
+ }
+
+ return 0;
+}
+
+static double ipow(double base, int exp)
+{
+ double result = 1;
+
+ for (;;) {
+ if (exp & 1) {
+ result *= base;
+ }
+
+ exp >>= 1;
+ if (!exp) {
+ break;
+ }
+ base *= base;
+ }
+
+ return result;
+}
+
+/*
+ * The 'backoff full jitter' algorithm implements a capped backoff with a jitter
+ * to generate numbers to be used as 'wait times', this implementation is fully
+ * based on the following article:
+ *
+ * https://www.awsarchitectureblog.com/2015/03/backoff.html
+ */
+static int backoff_full_jitter(int base, int cap, int n)
+{
+ int temp;
+
+ temp = xmin(cap, base * ipow(2, n));
+ return random_uniform(base, temp);
+}
+
+/* Schedule the 'retry' for a thread buffer flush */
+int flb_sched_request_create(struct flb_config *config, void *data, int tries)
+{
+ int ret;
+ int seconds;
+ struct flb_sched_timer *timer;
+ struct flb_sched_request *request;
+
+ /* Allocate timer context */
+ timer = flb_sched_timer_create(config->sched);
+ if (!timer) {
+ return -1;
+ }
+
+ /* Allocate request node */
+ request = flb_malloc(sizeof(struct flb_sched_request));
+ if (!request) {
+ flb_errno();
+ return -1;
+ }
+
+ /* Link timer references */
+ timer->type = FLB_SCHED_TIMER_REQUEST;
+ timer->data = request;
+ timer->event.mask = MK_EVENT_EMPTY;
+
+ /* Get suggested wait_time for this request. If shutting down, set to 0. */
+ if (config->is_shutting_down) {
+ seconds = 0;
+ } else {
+ seconds = backoff_full_jitter((int)config->sched_base, (int)config->sched_cap,
+ tries);
+ }
+ seconds += 1;
+
+ /* Populare request */
+ request->fd = -1;
+ request->created = time(NULL);
+ request->timeout = seconds;
+ request->data = data;
+ request->timer = timer;
+
+ /* Request to be placed into the sched_requests_wait list */
+ if (seconds > FLB_SCHED_REQUEST_FRAME) {
+ schedule_request_wait(request, config);
+ }
+ else {
+ ret = schedule_request_now(seconds, timer, request, config);
+ if (ret == -1) {
+ flb_error("[sched] 'retry request' could not be created. the "
+ "system might be running out of memory or file "
+ "descriptors.");
+ flb_sched_timer_destroy(timer);
+ flb_free(request);
+ return -1;
+ }
+ }
+
+ return seconds;
+}
+
+int flb_sched_request_destroy(struct flb_sched_request *req)
+{
+ struct flb_sched_timer *timer;
+
+ if (!req) {
+ return 0;
+ }
+
+ mk_list_del(&req->_head);
+
+ timer = req->timer;
+
+ /*
+ * We invalidate the timer since in the same event loop round
+ * an event associated to this timer can be present. Invalidation
+ * means the timer will do nothing and will be removed after
+ * the event loop round finish.
+ */
+ flb_sched_timer_invalidate(timer);
+
+ /* Remove request */
+ flb_free(req);
+
+ return 0;
+}
+
+int flb_sched_request_invalidate(struct flb_config *config, void *data)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_sched_request *request;
+ struct flb_sched *sched;
+
+ sched = config->sched;
+ mk_list_foreach_safe(head, tmp, &sched->requests) {
+ request = mk_list_entry(head, struct flb_sched_request, _head);
+ if (request->data == data) {
+ flb_sched_request_destroy(request);
+ return 0;
+ }
+ }
+
+ /*
+ * Clean up retry tasks that are scheduled more than 60s.
+ * Task might be destroyed when there are still retry
+ * scheduled but no thread is running for the task.
+ *
+ * We need to drop buffered chunks when the filesystem buffer
+ * limit is reached. We need to make sure that all requests
+ * should be destroyed to avoid invoke an invlidated request.
+ */
+ mk_list_foreach_safe(head, tmp, &sched->requests_wait) {
+ request = mk_list_entry(head, struct flb_sched_request, _head);
+ if (request->data == data) {
+ flb_sched_request_destroy(request);
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+/* Handle a timeout event set by a previous flb_sched_request_create(...) */
+int flb_sched_event_handler(struct flb_config *config, struct mk_event *event)
+{
+ int ret;
+ struct flb_sched *sched;
+ struct flb_sched_timer *timer;
+ struct flb_sched_request *req;
+
+ timer = (struct flb_sched_timer *) event;
+ if (timer->active == FLB_FALSE) {
+ return 0;
+ }
+
+ if (timer->type == FLB_SCHED_TIMER_REQUEST) {
+ /* Map request struct */
+ req = timer->data;
+ consume_byte(req->fd);
+
+ /* Dispatch 'retry' */
+ ret = flb_engine_dispatch_retry(req->data, config);
+
+ /* Destroy this scheduled request, it's not longer required */
+ if (ret == 0) {
+ flb_sched_request_destroy(req);
+ }
+ }
+ else if (timer->type == FLB_SCHED_TIMER_FRAME) {
+ sched = timer->data;
+#ifndef __APPLE__
+ consume_byte(sched->frame_fd);
+#endif
+ schedule_request_promote(sched);
+ }
+ else if (timer->type == FLB_SCHED_TIMER_CB_ONESHOT) {
+ consume_byte(timer->timer_fd);
+ flb_sched_timer_cb_disable(timer);
+ timer->cb(config, timer->data);
+ flb_sched_timer_cb_destroy(timer);
+ }
+ else if (timer->type == FLB_SCHED_TIMER_CB_PERM) {
+ consume_byte(timer->timer_fd);
+ timer->cb(config, timer->data);
+ }
+
+ return 0;
+}
+
+/*
+ * Create a timer that once it expire, it triggers the defined callback
+ * upon creation. This interface is for generic purposes and not specific
+ * for re-tries.
+ *
+ * use-case: invoke function A() after M milliseconds.
+ */
+int flb_sched_timer_cb_create(struct flb_sched *sched, int type, int ms,
+ void (*cb)(struct flb_config *, void *),
+ void *data, struct flb_sched_timer **out_timer)
+{
+ int fd;
+ time_t sec;
+ long nsec;
+ struct mk_event *event;
+ struct flb_sched_timer *timer;
+
+ if (type != FLB_SCHED_TIMER_CB_ONESHOT && type != FLB_SCHED_TIMER_CB_PERM) {
+ flb_error("[sched] invalid callback timer type %i", type);
+ return -1;
+ }
+
+ timer = flb_sched_timer_create(sched);
+ if (!timer) {
+ return -1;
+ }
+
+ timer->type = type;
+ timer->data = data;
+ timer->cb = cb;
+
+ /* Initialize event */
+ event = &timer->event;
+ event->mask = MK_EVENT_EMPTY;
+ event->status = MK_EVENT_NONE;
+
+ /* Convert from milliseconds to seconds and nanoseconds */
+ sec = (ms / 1000);
+ nsec = ((ms % 1000) * 1000000);
+
+ /* Create the frame timer */
+ fd = mk_event_timeout_create(sched->evl, sec, nsec, event);
+ event->priority = FLB_ENGINE_PRIORITY_CB_TIMER;
+ if (fd == -1) {
+ flb_error("[sched] cannot do timeout_create()");
+ flb_sched_timer_destroy(timer);
+ return -1;
+ }
+
+ /*
+ * Note: mk_event_timeout_create() sets a type = MK_EVENT_NOTIFICATION by
+ * default, we need to overwrite this value so we can do a clean check
+ * into the Engine when the event is triggered.
+ */
+ event->type = FLB_ENGINE_EV_SCHED;
+ timer->timer_fd = fd;
+
+ if (out_timer != NULL) {
+ *out_timer = timer;
+ }
+
+ return 0;
+}
+
+/* Disable notifications, used before to destroy the context */
+int flb_sched_timer_cb_disable(struct flb_sched_timer *timer)
+{
+ if (timer->timer_fd != -1) {
+ mk_event_timeout_destroy(timer->sched->evl, &timer->event);
+
+ timer->timer_fd = -1;
+ }
+
+ return 0;
+}
+
+int flb_sched_timer_cb_destroy(struct flb_sched_timer *timer)
+{
+ flb_sched_timer_destroy(timer);
+
+ return 0;
+}
+
+/* Initialize the Scheduler */
+struct flb_sched *flb_sched_create(struct flb_config *config,
+ struct mk_event_loop *evl)
+{
+ flb_pipefd_t fd;
+ struct mk_event *event;
+ struct flb_sched *sched;
+ struct flb_sched_timer *timer;
+
+ sched = flb_calloc(1, sizeof(struct flb_sched));
+ if (!sched) {
+ flb_errno();
+ return NULL;
+ }
+
+ sched->config = config;
+ sched->evl = evl;
+
+ /* Initialize lists */
+ mk_list_init(&sched->requests);
+ mk_list_init(&sched->requests_wait);
+ mk_list_init(&sched->timers);
+ mk_list_init(&sched->timers_drop);
+
+ /* Create the frame timer who enqueue 'requests' for future time */
+ timer = flb_sched_timer_create(sched);
+ if (!timer) {
+ flb_free(sched);
+ return NULL;
+ }
+
+ timer->type = FLB_SCHED_TIMER_FRAME;
+ timer->data = sched;
+
+ /* Initialize event */
+ event = &timer->event;
+ event->mask = MK_EVENT_EMPTY;
+ event->status = MK_EVENT_NONE;
+
+ /* Create the frame timer */
+ fd = mk_event_timeout_create(evl, FLB_SCHED_REQUEST_FRAME, 0,
+ event);
+ event->priority = FLB_ENGINE_PRIORITY_CB_SCHED;
+ if (fd == -1) {
+ flb_sched_timer_destroy(timer);
+ flb_free(sched);
+ return NULL;
+ }
+ sched->frame_fd = fd;
+
+ /*
+ * Note: mk_event_timeout_create() sets a type = MK_EVENT_NOTIFICATION by
+ * default, we need to overwrite this value so we can do a clean check
+ * into the Engine when the event is triggered.
+ */
+ event->type = FLB_ENGINE_EV_SCHED_FRAME;
+
+ return sched;
+}
+
+/* Release all resources used by the Scheduler */
+int flb_sched_destroy(struct flb_sched *sched)
+{
+ int c = 0;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_sched_timer *timer;
+ struct flb_sched_request *request;
+
+ if (!sched) {
+ return 0;
+ }
+
+ mk_list_foreach_safe(head, tmp, &sched->requests) {
+ request = mk_list_entry(head, struct flb_sched_request, _head);
+ flb_sched_request_destroy(request);
+ c++; /* evil counter */
+ }
+
+ /* Delete requests on wait list */
+ mk_list_foreach_safe(head, tmp, &sched->requests_wait) {
+ request = mk_list_entry(head, struct flb_sched_request, _head);
+ flb_sched_request_destroy(request);
+ c++; /* evil counter */
+ }
+
+ /* Delete timers */
+ mk_list_foreach_safe(head, tmp, &sched->timers) {
+ timer = mk_list_entry(head, struct flb_sched_timer, _head);
+ flb_sched_timer_destroy(timer);
+ c++;
+ }
+
+ /* Delete timers drop list */
+ mk_list_foreach_safe(head, tmp, &sched->timers_drop) {
+ timer = mk_list_entry(head, struct flb_sched_timer, _head);
+ flb_sched_timer_destroy(timer);
+ c++;
+ }
+
+ flb_free(sched);
+ return c;
+}
+
+/* Create a timer context */
+struct flb_sched_timer *flb_sched_timer_create(struct flb_sched *sched)
+{
+ struct flb_sched_timer *timer;
+
+ /* Create timer context */
+ timer = flb_calloc(1, sizeof(struct flb_sched_timer));
+ if (!timer) {
+ flb_errno();
+ return NULL;
+ }
+ MK_EVENT_ZERO(&timer->event);
+
+ timer->timer_fd = -1;
+ timer->config = sched->config;
+ timer->sched = sched;
+ timer->data = NULL;
+
+ /* Active timer (not invalidated) */
+ timer->active = FLB_TRUE;
+ mk_list_add(&timer->_head, &sched->timers);
+
+ return timer;
+}
+
+void flb_sched_timer_invalidate(struct flb_sched_timer *timer)
+{
+ flb_sched_timer_cb_disable(timer);
+
+ timer->active = FLB_FALSE;
+
+ mk_list_del(&timer->_head);
+ mk_list_add(&timer->_head, &timer->sched->timers_drop);
+}
+
+/* Destroy a timer context */
+int flb_sched_timer_destroy(struct flb_sched_timer *timer)
+{
+ flb_sched_timer_cb_disable(timer);
+
+ mk_list_del(&timer->_head);
+ flb_free(timer);
+
+ return 0;
+}
+
+/* Used by the engine to cleanup pending timers waiting to be destroyed */
+int flb_sched_timer_cleanup(struct flb_sched *sched)
+{
+ int c = 0;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_sched_timer *timer;
+
+ mk_list_foreach_safe(head, tmp, &sched->timers_drop) {
+ timer = mk_list_entry(head, struct flb_sched_timer, _head);
+ flb_sched_timer_destroy(timer);
+ c++;
+ }
+
+ return c;
+}
+
+int flb_sched_retry_now(struct flb_config *config,
+ struct flb_task_retry *retry)
+{
+ int ret;
+ struct flb_sched_timer *timer;
+ struct flb_sched_request *request;
+
+ /* Allocate timer context */
+ timer = flb_sched_timer_create(config->sched);
+ if (!timer) {
+ return -1;
+ }
+
+ /* Allocate request node */
+ request = flb_malloc(sizeof(struct flb_sched_request));
+ if (!request) {
+ flb_errno();
+ flb_sched_timer_destroy(timer);
+ return -1;
+ }
+
+ /* Link timer references */
+ timer->type = FLB_SCHED_TIMER_REQUEST;
+ timer->data = request;
+ timer->event.mask = MK_EVENT_EMPTY;
+
+ /* Populate request */
+ request->fd = -1;
+ request->created = time(NULL);
+ request->timeout = 0;
+ request->data = retry;
+ request->timer = timer;
+
+ ret = schedule_request_now(0 /* seconds */, timer, request, config);
+ if (ret == -1) {
+ flb_error("[sched] 'retry-now request' could not be created. the "
+ "system might be running out of memory or file "
+ "descirptors.");
+ flb_sched_timer_destroy(timer);
+ flb_free(request);
+ return -1;
+ }
+ return 0;
+}
diff --git a/fluent-bit/src/flb_sds.c b/fluent-bit/src/flb_sds.c
new file mode 100644
index 000000000..2cb562c86
--- /dev/null
+++ b/fluent-bit/src/flb_sds.c
@@ -0,0 +1,500 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+/*
+ * The following SDS interface is a clone/strip-down version of the original
+ * SDS library created by Antirez at https://github.com/antirez/sds.
+ */
+
+#include <fluent-bit/flb_compat.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_utf8.h>
+
+#include <stdarg.h>
+#include <ctype.h>
+
+static flb_sds_t sds_alloc(size_t size)
+{
+ void *buf;
+ flb_sds_t s;
+ struct flb_sds *head;
+
+ buf = flb_malloc(FLB_SDS_HEADER_SIZE + size + 1);
+ if (!buf) {
+ flb_errno();
+ return NULL;
+ }
+
+ head = buf;
+ head->len = 0;
+ head->alloc = size;
+
+ s = head->buf;
+ *s = '\0';
+
+ return s;
+}
+
+flb_sds_t flb_sds_create_len(const char *str, int len)
+{
+ flb_sds_t s;
+ struct flb_sds *head;
+
+ s = sds_alloc(len);
+ if (!s) {
+ return NULL;
+ }
+
+ if (str) {
+ memcpy(s, str, len);
+ s[len] = '\0';
+
+ head = FLB_SDS_HEADER(s);
+ head->len = len;
+ }
+ return s;
+}
+
+flb_sds_t flb_sds_create(const char *str)
+{
+ size_t len;
+
+ if (!str) {
+ len = 0;
+ }
+ else {
+ len = strlen(str);
+ }
+
+ return flb_sds_create_len(str, len);
+}
+
+flb_sds_t flb_sds_create_size(size_t size)
+{
+ return sds_alloc(size);
+}
+
+/* Increase SDS buffer size 'len' bytes */
+flb_sds_t flb_sds_increase(flb_sds_t s, size_t len)
+{
+ size_t new_size;
+ struct flb_sds *head;
+ flb_sds_t out;
+ void *tmp;
+
+ out = s;
+ new_size = (FLB_SDS_HEADER_SIZE + flb_sds_alloc(s) + len + 1);
+ head = FLB_SDS_HEADER(s);
+ tmp = flb_realloc(head, new_size);
+ if (!tmp) {
+ flb_errno();
+ return NULL;
+ }
+ head = (struct flb_sds *) tmp;
+ head->alloc += len;
+ out = head->buf;
+
+ return out;
+}
+
+flb_sds_t flb_sds_cat(flb_sds_t s, const char *str, int len)
+{
+ size_t avail;
+ struct flb_sds *head;
+ flb_sds_t tmp = NULL;
+
+ avail = flb_sds_avail(s);
+ if (avail < len) {
+ tmp = flb_sds_increase(s, len);
+ if (!tmp) {
+ return NULL;
+ }
+ s = tmp;
+ }
+ memcpy((char *) (s + flb_sds_len(s)), str, len);
+
+ head = FLB_SDS_HEADER(s);
+ head->len += len;
+ s[head->len] = '\0';
+
+ return s;
+}
+
+
+/*
+ * remove empty spaces on left/right from sds buffer 's' and return the new length
+ * of the content.
+ */
+int flb_sds_trim(flb_sds_t s)
+{
+ unsigned int i;
+ unsigned int len;
+ char *left = 0, *right = 0;
+ char *buf;
+
+ if (!s) {
+ return -1;
+ }
+
+ len = flb_sds_len(s);
+ if (len == 0) {
+ return 0;
+ }
+
+ buf = s;
+ left = buf;
+
+ /* left spaces */
+ while (left) {
+ if (isspace(*left)) {
+ left++;
+ }
+ else {
+ break;
+ }
+ }
+
+ right = buf + (len - 1);
+ /* Validate right v/s left */
+ if (right < left) {
+ buf[0] = '\0';
+ return -1;
+ }
+
+ /* Move back */
+ while (right != buf){
+ if (isspace(*right)) {
+ right--;
+ }
+ else {
+ break;
+ }
+ }
+
+ len = (right - left) + 1;
+ for (i=0; i<len; i++) {
+ buf[i] = (char) left[i];
+ }
+ buf[i] = '\0';
+ flb_sds_len_set(buf, i);
+
+ return i;
+}
+
+int flb_sds_cat_safe(flb_sds_t *buf, const char *str, int len)
+{
+ flb_sds_t tmp;
+
+ tmp = flb_sds_cat(*buf, str, len);
+ if (!tmp) {
+ return -1;
+ }
+ *buf = tmp;
+ return 0;
+}
+
+flb_sds_t flb_sds_cat_esc(flb_sds_t s, const char *str, int len,
+ char *esc, size_t esc_size)
+{
+ size_t avail;
+ struct flb_sds *head;
+ flb_sds_t tmp = NULL;
+ uint32_t c;
+ int i;
+
+ avail = flb_sds_avail(s);
+ if (avail < len) {
+ tmp = flb_sds_increase(s, len);
+ if (!tmp) {
+ return NULL;
+ }
+ s = tmp;
+ }
+ head = FLB_SDS_HEADER(s);
+
+ for (i = 0; i < len; i++) {
+ if (flb_sds_avail(s) < 8) {
+ tmp = flb_sds_increase(s, 8);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ s = tmp;
+ head = FLB_SDS_HEADER(s);
+ }
+ c = (unsigned char) str[i];
+ if (esc != NULL && c < esc_size && esc[c] != 0) {
+ s[head->len++] = '\\';
+ s[head->len++] = esc[c];
+ }
+ else {
+ s[head->len++] = c;
+ }
+ }
+
+ s[head->len] = '\0';
+
+ return s;
+}
+
+
+flb_sds_t flb_sds_copy(flb_sds_t s, const char *str, int len)
+{
+ size_t avail;
+ struct flb_sds *head;
+ flb_sds_t tmp = NULL;
+
+ avail = flb_sds_alloc(s);
+ if (avail < len) {
+ tmp = flb_sds_increase(s, len);
+ if (!tmp) {
+ return NULL;
+ }
+ s = tmp;
+ }
+ memcpy((char *) s, str, len);
+
+ head = FLB_SDS_HEADER(s);
+ head->len = len;
+ s[head->len] = '\0';
+
+ return s;
+}
+
+flb_sds_t flb_sds_cat_utf8 (flb_sds_t *sds, const char *str, int str_len)
+{
+ static const char int2hex[] = "0123456789abcdef";
+ int i;
+ int b;
+ int ret;
+ int hex_bytes;
+ uint32_t cp;
+ uint32_t state = 0;
+ unsigned char c;
+ const uint8_t *p;
+ struct flb_sds *head;
+ flb_sds_t tmp;
+ flb_sds_t s;
+
+ s = *sds;
+ head = FLB_SDS_HEADER(s);
+
+ if (flb_sds_avail(s) <= str_len) {
+ tmp = flb_sds_increase(s, str_len);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *sds = s = tmp;
+ head = FLB_SDS_HEADER(s);
+ }
+
+ for (i = 0; i < str_len; i++) {
+ if (flb_sds_avail(s) < 8) {
+ tmp = flb_sds_increase(s, 8);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ *sds = s = tmp;
+ head = FLB_SDS_HEADER(s);
+ }
+
+ c = (unsigned char)str[i];
+ if (c == '\\' || c == '"') {
+ s[head->len++] = '\\';
+ s[head->len++] = c;
+ }
+ else if (c >= '\b' && c <= '\r') {
+ s[head->len++] = '\\';
+ switch (c) {
+ case '\n':
+ s[head->len++] = 'n';
+ break;
+ case '\t':
+ s[head->len++] = 't';
+ break;
+ case '\b':
+ s[head->len++] = 'b';
+ break;
+ case '\f':
+ s[head->len++] = 'f';
+ break;
+ case '\r':
+ s[head->len++] = 'r';
+ break;
+ case '\v':
+ s[head->len++] = 'u';
+ s[head->len++] = '0';
+ s[head->len++] = '0';
+ s[head->len++] = '0';
+ s[head->len++] = 'b';
+ break;
+ }
+ }
+ else if (c < 32 || c == 0x7f) {
+ s[head->len++] = '\\';
+ s[head->len++] = 'u';
+ s[head->len++] = '0';
+ s[head->len++] = '0';
+ s[head->len++] = int2hex[ (unsigned char) ((c & 0xf0) >> 4)];
+ s[head->len++] = int2hex[ (unsigned char) (c & 0x0f)];
+ }
+ else if (c >= 0x80) {
+ hex_bytes = flb_utf8_len(str + i);
+ state = FLB_UTF8_ACCEPT;
+ cp = 0;
+ for (b = 0; b < hex_bytes; b++) {
+ p = (const unsigned char *) str + i + b;
+ if (p >= (unsigned char *) (str + str_len)) {
+ break;
+ }
+ ret = flb_utf8_decode(&state, &cp, *p);
+ if (ret == 0) {
+ break;
+ }
+ }
+
+ if (state != FLB_UTF8_ACCEPT) {
+ /* Invalid UTF-8 hex, just skip utf-8 bytes */
+ flb_warn("[pack] invalid UTF-8 bytes, skipping");
+ break;
+ }
+
+ s[head->len++] = '\\';
+ s[head->len++] = 'u';
+ if (cp > 0xFFFF) {
+ c = (unsigned char) ((cp & 0xf00000) >> 20);
+ if (c > 0) {
+ s[head->len++] = int2hex[c];
+ }
+ c = (unsigned char) ((cp & 0x0f0000) >> 16);
+ if (c > 0) {
+ s[head->len++] = int2hex[c];
+ }
+ }
+ s[head->len++] = int2hex[ (unsigned char) ((cp & 0xf000) >> 12)];
+ s[head->len++] = int2hex[ (unsigned char) ((cp & 0x0f00) >> 8)];
+ s[head->len++] = int2hex[ (unsigned char) ((cp & 0xf0) >> 4)];
+ s[head->len++] = int2hex[ (unsigned char) (cp & 0x0f)];
+ i += (hex_bytes - 1);
+ }
+ else {
+ s[head->len++] = c;
+ }
+ }
+
+ s[head->len] = '\0';
+
+ return s;
+}
+
+flb_sds_t flb_sds_printf(flb_sds_t *sds, const char *fmt, ...)
+{
+ va_list ap;
+ int len = strlen(fmt)*2;
+ int size;
+ flb_sds_t tmp = NULL;
+ flb_sds_t s;
+ struct flb_sds *head;
+
+ if (len < 64) len = 64;
+
+ s = *sds;
+ if (flb_sds_avail(s) < len) {
+ tmp = flb_sds_increase(s, len - flb_sds_avail(s));
+ if (!tmp) {
+ return NULL;
+ }
+ *sds = s = tmp;
+ }
+
+ va_start(ap, fmt);
+ size = vsnprintf((char *) (s + flb_sds_len(s)), flb_sds_avail(s), fmt, ap);
+ if (size < 0) {
+ flb_warn("[%s] buggy vsnprintf return %d", __FUNCTION__, size);
+ va_end(ap);
+ return NULL;
+ }
+ va_end(ap);
+
+ if (size >= flb_sds_avail(s)) {
+ tmp = flb_sds_increase(s, size - flb_sds_avail(s) + 1);
+ if (!tmp) {
+ return NULL;
+ }
+ *sds = s = tmp;
+
+ va_start(ap, fmt);
+ size = vsnprintf((char *) (s + flb_sds_len(s)), flb_sds_avail(s), fmt, ap);
+ if (size > flb_sds_avail(s)) {
+ flb_warn("[%s] vsnprintf is insatiable ", __FUNCTION__);
+ va_end(ap);
+ return NULL;
+ }
+ va_end(ap);
+ }
+
+ head = FLB_SDS_HEADER(s);
+ head->len += size;
+ s[head->len] = '\0';
+
+ return s;
+}
+
+void flb_sds_destroy(flb_sds_t s)
+{
+ struct flb_sds *head;
+
+ if (!s) {
+ return;
+ }
+
+ head = FLB_SDS_HEADER(s);
+ flb_free(head);
+}
+
+/*
+ * flb_sds_snprintf is a wrapper of snprintf.
+ * The difference is that this function can increase the buffer of flb_sds_t.
+ */
+int flb_sds_snprintf(flb_sds_t *str, size_t size, const char *fmt, ...)
+{
+ va_list va;
+ flb_sds_t tmp;
+ int ret;
+
+ retry_snprintf:
+ va_start(va, fmt);
+ ret = vsnprintf(*str, size, fmt, va);
+ if (ret > size) {
+ tmp = flb_sds_increase(*str, ret-size);
+ if (tmp == NULL) {
+ return -1;
+ }
+ *str = tmp;
+ size = ret;
+ va_end(va);
+ goto retry_snprintf;
+ }
+ va_end(va);
+
+ flb_sds_len_set(*str, ret);
+ return ret;
+}
diff --git a/fluent-bit/src/flb_sds_list.c b/fluent-bit/src/flb_sds_list.c
new file mode 100644
index 000000000..2757ceaac
--- /dev/null
+++ b/fluent-bit/src/flb_sds_list.c
@@ -0,0 +1,185 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit.h>
+#include <fluent-bit/flb_sds_list.h>
+
+size_t flb_sds_list_size(struct flb_sds_list *list)
+{
+ if (list == NULL) {
+ return 0;
+ }
+ return mk_list_size(&list->strs);
+}
+
+struct flb_sds_list *flb_sds_list_create()
+{
+ struct flb_sds_list *ret = NULL;
+
+ ret = flb_calloc(1, sizeof(struct flb_sds_list));
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ mk_list_init(&ret->strs);
+
+ return ret;
+}
+
+int flb_sds_list_del(struct flb_sds_list_entry* entry)
+{
+ if (entry == NULL) {
+ return -1;
+ }
+ if (entry->str != NULL) {
+ flb_sds_destroy(entry->str);
+ }
+ mk_list_del(&entry->_head);
+ flb_free(entry);
+
+ return 0;
+}
+
+
+int flb_sds_list_destroy(struct flb_sds_list *list)
+{
+ struct mk_list *tmp = NULL;
+ struct mk_list *head = NULL;
+ struct flb_sds_list_entry *entry = NULL;
+
+ if (list == NULL) {
+ return -1;
+ }
+
+ mk_list_foreach_safe(head, tmp, &list->strs) {
+ entry = mk_list_entry(head, struct flb_sds_list_entry, _head);
+ flb_sds_list_del(entry);
+ }
+ flb_free(list);
+
+ return 0;
+}
+
+int flb_sds_list_add(struct flb_sds_list* list, char *in_str, size_t in_size)
+{
+ flb_sds_t str;
+ struct flb_sds_list_entry *entry = NULL;
+
+ if (list == NULL || in_str == NULL || in_size == 0) {
+ return -1;
+ }
+
+ str = flb_sds_create_len(in_str, in_size);
+ if (str == NULL) {
+ return -1;
+ }
+
+ entry = flb_malloc(sizeof(struct flb_sds_list_entry));
+ if (entry == NULL) {
+ flb_errno();
+ flb_sds_destroy(str);
+ return -1;
+ }
+ entry->str = str;
+
+ mk_list_add(&entry->_head, &list->strs);
+
+ return 0;
+}
+
+int flb_sds_list_destroy_str_array(char **array)
+{
+ char **str = array;
+ int i = 0;
+
+ if (array == NULL) {
+ return -1;
+ }
+ while(str[i] != NULL) {
+ flb_free(str[i]);
+ i++;
+ }
+ flb_free(array);
+
+ return 0;
+}
+
+
+/*
+ This function allocates NULL terminated string array from list.
+ The array should be destroyed by flb_sds_list_destroy_str_array.
+*/
+char **flb_sds_list_create_str_array(struct flb_sds_list *list)
+{
+ int i = 0;
+ size_t size;
+ char **ret = NULL;
+ struct mk_list *tmp = NULL;
+ struct mk_list *head = NULL;
+ struct flb_sds_list_entry *entry = NULL;
+
+ if (list == NULL) {
+ return NULL;
+ }
+
+ size = flb_sds_list_size(list);
+ if (size == 0) {
+ return NULL;
+ }
+
+ ret = flb_malloc(sizeof(char*) * (size + 1));
+ if (ret == NULL) {
+ flb_errno();
+ return NULL;
+ }
+
+ mk_list_foreach_safe(head, tmp, &list->strs) {
+ entry = mk_list_entry(head, struct flb_sds_list_entry, _head);
+ if (entry == NULL) {
+ flb_free(ret);
+ return NULL;
+ }
+ ret[i] = flb_malloc(flb_sds_len(entry->str)+1);
+ if (ret[i] == NULL) {
+ flb_free(ret);
+ return NULL;
+ }
+ strncpy(ret[i], entry->str, flb_sds_len(entry->str));
+ ret[i][flb_sds_len(entry->str)] = '\0';
+ i++;
+ }
+ ret[i] = NULL;
+
+ return ret;
+}
+
+int flb_sds_list_del_last_entry(struct flb_sds_list* list)
+{
+ struct flb_sds_list_entry *entry = NULL;
+
+ if (list == NULL || flb_sds_list_size(list) == 0) {
+ return -1;
+ }
+
+ entry = mk_list_entry_last(&list->strs, struct flb_sds_list_entry, _head);
+ if (entry == NULL) {
+ return -1;
+ }
+ return flb_sds_list_del(entry);
+}
diff --git a/fluent-bit/src/flb_signv4.c b/fluent-bit/src/flb_signv4.c
new file mode 100644
index 000000000..a4283dc05
--- /dev/null
+++ b/fluent-bit/src/flb_signv4.c
@@ -0,0 +1,1245 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+/*
+ *
+ * AWS Signv4 documentation
+ *
+ * https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
+ */
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_hmac.h>
+#include <fluent-bit/flb_hash.h>
+#include <fluent-bit/flb_http_client.h>
+#include <fluent-bit/flb_signv4.h>
+#include <fluent-bit/flb_aws_credentials.h>
+
+#include <stdlib.h>
+#include <ctype.h>
+
+static flb_sds_t sha256_to_hex(unsigned char *sha256)
+{
+ int i;
+ flb_sds_t hex;
+ flb_sds_t tmp;
+
+ hex = flb_sds_create_size(64);
+ if (!hex) {
+ flb_error("[signv4] cannot allocate buffer to convert sha256 to hex");
+ return NULL;
+ }
+
+ for (i = 0; i < 32; i++) {
+ tmp = flb_sds_printf(&hex, "%02x", sha256[i]);
+ if (!tmp) {
+ flb_error("[signedv4] error formatting sha256 -> hex");
+ flb_sds_destroy(hex);
+ return NULL;
+ }
+ hex = tmp;
+ }
+
+ return hex;
+}
+
+static int hmac_sha256_sign(unsigned char out[32],
+ unsigned char *key, size_t key_len,
+ unsigned char *msg, size_t msg_len)
+{
+ int result;
+
+ result = flb_hmac_simple(FLB_HASH_SHA256,
+ key, key_len,
+ msg, msg_len,
+ out, 32);
+
+ if (result != FLB_CRYPTO_SUCCESS) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int kv_key_cmp(const void *a_arg, const void *b_arg)
+{
+ int ret;
+ struct flb_kv *kv_a = *(struct flb_kv **) a_arg;
+ struct flb_kv *kv_b = *(struct flb_kv **) b_arg;
+
+ ret = strcmp(kv_a->key, kv_b->key);
+ if (ret == 0) {
+ /*
+ * NULL pointer is allowed in kv_a->val and kv_b->val.
+ * Handle NULL pointer cases before passing to strcmp.
+ */
+ if (kv_a->val == NULL && kv_b->val == NULL) {
+ ret = 0;
+ }
+ else if (kv_a->val == NULL) {
+ ret = -1;
+ }
+ else if (kv_b->val == NULL) {
+ ret = 1;
+ }
+ else {
+ ret = strcmp(kv_a->val, kv_b->val);
+ }
+ }
+
+ return ret;
+}
+
+static inline int to_encode(char c)
+{
+ if ((c >= 48 && c <= 57) || /* 0-9 */
+ (c >= 65 && c <= 90) || /* A-Z */
+ (c >= 97 && c <= 122) || /* a-z */
+ (c == '-' || c == '_' || c == '.' || c == '~' || c == '/' ||
+ c == '=')) {
+ return FLB_FALSE;
+ }
+
+ return FLB_TRUE;
+}
+
+static inline int to_encode_path(char c)
+{
+ if ((c >= 48 && c <= 57) || /* 0-9 */
+ (c >= 65 && c <= 90) || /* A-Z */
+ (c >= 97 && c <= 122) || /* a-z */
+ (c == '-' || c == '_' || c == '.' || c == '~' || c == '/')) {
+ return FLB_FALSE;
+ }
+
+ return FLB_TRUE;
+}
+
+flb_sds_t flb_signv4_uri_normalize_path(char *uri, size_t len)
+{
+ char *p;
+ int end_slash = FLB_FALSE;
+ struct mk_list *tmp;
+ struct mk_list *prev;
+ struct mk_list *head;
+ struct mk_list *split;
+ struct flb_split_entry *entry;
+ flb_sds_t out;
+
+ if (len == 0) {
+ return NULL;
+ }
+
+ out = flb_sds_create_len(uri, len+1);
+ if (!out) {
+ return NULL;
+ }
+ out[len] = '\0';
+
+ if (uri[len - 1] == '/') {
+ end_slash = FLB_TRUE;
+ }
+
+ split = flb_utils_split(out, '/', -1);
+ if (!split) {
+ flb_sds_destroy(out);
+ return NULL;
+ }
+
+ p = out;
+ *p++ = '/';
+
+ mk_list_foreach_safe(head, tmp, split) {
+ entry = mk_list_entry(head, struct flb_split_entry, _head);
+ if (entry->len == 1 && *entry->value == '.') {
+ flb_utils_split_free_entry(entry);
+ }
+ else if (entry->len == 2 && memcmp(entry->value, "..", 2) == 0) {
+ prev = head->prev;
+ if (prev != split) {
+ entry = mk_list_entry(prev, struct flb_split_entry, _head);
+ flb_utils_split_free_entry(entry);
+ }
+ entry = mk_list_entry(head, struct flb_split_entry, _head);
+ flb_utils_split_free_entry(entry);
+ }
+ }
+
+ mk_list_foreach(head, split) {
+ entry = mk_list_entry(head, struct flb_split_entry, _head);
+ memcpy(p, entry->value, entry->len);
+ p += entry->len;
+
+ if (head->next != split) {
+ *p++ = '/';
+ }
+ }
+
+ len = (p - out);
+ if (end_slash == FLB_TRUE && out[len - 1] != '/') {
+ *p++ = '/';
+ }
+
+ flb_utils_split_free(split);
+
+ flb_sds_len_set(out, p - out);
+ out[p - out] = '\0';
+
+ return out;
+}
+
+static flb_sds_t uri_encode(const char *uri, size_t len)
+{
+ int i;
+ flb_sds_t buf = NULL;
+ flb_sds_t tmp = NULL;
+ int is_query_string = FLB_FALSE;
+ int do_encode = FLB_FALSE;
+
+ buf = flb_sds_create_size(len * 2);
+ if (!buf) {
+ flb_error("[signv4] cannot allocate buffer for URI encoding");
+ return NULL;
+ }
+
+ for (i = 0; i < len; i++) {
+ if (uri[i] == '?') {
+ is_query_string = FLB_TRUE;
+ }
+ do_encode = FLB_FALSE;
+
+ if (is_query_string == FLB_FALSE && to_encode_path(uri[i]) == FLB_TRUE) {
+ do_encode = FLB_TRUE;
+ }
+ if (is_query_string == FLB_TRUE && to_encode(uri[i]) == FLB_TRUE) {
+ do_encode = FLB_TRUE;
+ }
+ if (do_encode == FLB_TRUE) {
+ tmp = flb_sds_printf(&buf, "%%%02X", (unsigned char) *(uri + i));
+ if (!tmp) {
+ flb_error("[signv4] error formatting special character");
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ buf = tmp;
+ continue;
+ }
+
+ /* Direct assignment, just copy the character */
+ if (buf) {
+ tmp = flb_sds_cat(buf, uri + i, 1);
+ if (!tmp) {
+ flb_error("[signv4] error composing outgoing buffer");
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ buf = tmp;
+ }
+ }
+
+ return buf;
+}
+
+/*
+ * Encodes URI parameters, which can not have "/" characters in them
+ * (This happens in an STS request, the role ARN has a slash and is
+ * given as a query parameter).
+ */
+static flb_sds_t uri_encode_params(const char *uri, size_t len)
+{
+ int i;
+ flb_sds_t buf = NULL;
+ flb_sds_t tmp = NULL;
+
+ buf = flb_sds_create_size(len * 2);
+ if (!buf) {
+ flb_error("[signv4] cannot allocate buffer for URI encoding");
+ return NULL;
+ }
+
+ for (i = 0; i < len; i++) {
+ if (to_encode(uri[i]) == FLB_TRUE || uri[i] == '/') {
+ tmp = flb_sds_printf(&buf, "%%%02X", (unsigned char) *(uri + i));
+ if (!tmp) {
+ flb_error("[signv4] error formatting special character");
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ continue;
+ }
+
+ /* Direct assignment, just copy the character */
+ if (buf) {
+ tmp = flb_sds_cat(buf, uri + i, 1);
+ if (!tmp) {
+ flb_error("[signv4] error composing outgoing buffer");
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ buf = tmp;
+ }
+ }
+
+ return buf;
+}
+
+/*
+ * Convert URL encoded params (query string or POST payload) to a sorted
+ * key/value linked list
+ */
+static flb_sds_t url_params_format(char *params)
+{
+ int i;
+ int ret;
+ int len;
+ int items;
+ char *p;
+ struct mk_list list;
+ struct mk_list split;
+ struct mk_list *h_tmp;
+ struct mk_list *head;
+ struct flb_slist_entry *e;
+ flb_sds_t key;
+ flb_sds_t val;
+ flb_sds_t tmp;
+ flb_sds_t buf = NULL;
+ struct flb_kv *kv;
+ struct flb_kv **arr;
+
+ mk_list_init(&list);
+ mk_list_init(&split);
+
+ ret = flb_slist_split_string(&split, params, '&', -1);
+ if (ret == -1) {
+ flb_error("[signv4] error processing given query string");
+ flb_slist_destroy(&split);
+ flb_kv_release(&list);
+ return NULL;
+ }
+
+ mk_list_foreach_safe(head, h_tmp, &split) {
+ e = mk_list_entry(head, struct flb_slist_entry, _head);
+ p = strchr(e->str, '=');
+ if (!p) {
+ continue;
+ }
+
+ len = (p - e->str);
+ p++;
+
+ /* URI encode every key and value */
+ key = uri_encode_params(e->str, len);
+ len++;
+ val = uri_encode_params(p, flb_sds_len(e->str) - len);
+ if (!key || !val) {
+ flb_error("[signv4] error encoding uri for query string");
+ if (key) {
+ flb_sds_destroy(key);
+ }
+ if (val) {
+ flb_sds_destroy(val);
+ }
+ flb_slist_destroy(&split);
+ flb_kv_release(&list);
+ return NULL;
+ }
+
+ /*
+ * If key length is 0 then a problem occurs because val
+ * will not be set flb_kv_item_create_len, which eventually
+ * results in issues since kv->val will be equal to NULL.
+ * Thus, check here whether key length is satisfied
+ */
+ if (flb_sds_len(key) == 0) {
+ flb_sds_destroy(key);
+ flb_sds_destroy(val);
+ flb_slist_destroy(&split);
+ flb_kv_release(&list);
+ return NULL;
+ }
+
+ kv = flb_kv_item_create_len(&list,
+ key, flb_sds_len(key),
+ val, flb_sds_len(val));
+ flb_sds_destroy(key);
+ flb_sds_destroy(val);
+
+ if (!kv) {
+ flb_error("[signv4] error processing key/value from query string");
+ flb_slist_destroy(&split);
+ flb_kv_release(&list);
+ return NULL;
+ }
+ }
+ flb_slist_destroy(&split);
+
+ /* Sort the kv list of parameters */
+ items = mk_list_size(&list);
+ if (items == 0) {
+ flb_kv_release(&list);
+ return flb_sds_create("");
+ }
+
+ arr = flb_calloc(1, sizeof(struct flb_kv *) * items);
+ if (!arr) {
+ flb_errno();
+ flb_kv_release(&list);
+ return NULL;
+ }
+
+ i = 0;
+ mk_list_foreach(head, &list) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ arr[i] = kv;
+ i++;
+ }
+ /* sort headers by key */
+ qsort(arr, items, sizeof(struct flb_kv *), kv_key_cmp);
+
+ /* Format query string parameters */
+ buf = flb_sds_create_size(items * 64);
+ if (!buf) {
+ flb_kv_release(&list);
+ flb_free(arr);
+ return NULL;
+ }
+
+ for (i = 0; i < items; i++) {
+ kv = (struct flb_kv *) arr[i];
+ if (i + 1 < items) {
+ if (kv->val == NULL) {
+ tmp = flb_sds_printf(&buf, "%s=&",
+ kv->key);
+ }
+ else {
+ tmp = flb_sds_printf(&buf, "%s=%s&",
+ kv->key, kv->val);
+ }
+ }
+ else {
+ if (kv->val == NULL) {
+ tmp = flb_sds_printf(&buf, "%s=",
+ kv->key);
+ }
+ else {
+ tmp = flb_sds_printf(&buf, "%s=%s",
+ kv->key, kv->val);
+ }
+ }
+ if (!tmp) {
+ flb_error("[signv4] error allocating value");
+ }
+ buf = tmp;
+ }
+
+ flb_kv_release(&list);
+ flb_free(arr);
+
+ return buf;
+}
+
+/*
+ * Given an original list of kv headers with 'in_list' as the list headed,
+ * generate new entries on 'out_list' considering lower case headers key,
+ * sorted by keys and values and merged duplicates.
+ */
+void headers_sanitize(struct mk_list *in_list, struct mk_list *out_list)
+{
+ int x;
+ char *v_start;
+ char *v_end;
+ char *val;
+ struct mk_list *head;
+ struct mk_list *c_head;
+ struct mk_list *tmp;
+ struct mk_list out_tmp;
+ struct flb_kv *kv;
+ struct flb_kv *c_kv;
+ flb_sds_t t;
+
+ mk_list_init(&out_tmp);
+
+ /* Create lowercase key headers in the temporal list */
+ mk_list_foreach(head, in_list) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+
+ /* Sanitize value */
+ v_start = kv->val;
+ v_end = kv->val + flb_sds_len(kv->val);
+ while (*v_start == ' ' || *v_start == '\t') {
+ v_start++;
+ }
+ while (*v_end == ' ' || *v_end == '\t') {
+ v_end--;
+ }
+
+ /*
+ * The original headers might have upper case characters, for safety just
+ * make a copy of them so we can lowercase them if required.
+ */
+ kv = flb_kv_item_create_len(&out_tmp,
+ kv->key, flb_sds_len(kv->key),
+ v_start, v_end - v_start);
+ if (kv == NULL) {
+ continue;
+ }
+ for (x = 0; x < flb_sds_len(kv->key); x++) {
+ kv->key[x] = tolower(kv->key[x]);
+ }
+
+ /*
+ * trim: kv->val alreay have a copy of the original value, now we need
+ * to look for double empty spaces in the middle of the value and do
+ * proper adjustments.
+ */
+ val = kv->val;
+ while (v_start < v_end) {
+ if (*v_start == ' ') {
+ if (v_start < v_end && *(v_start + 1) == ' ') {
+ v_start++;
+ continue;
+ }
+ }
+ *val = *v_start;
+ v_start++;
+ val++;
+ }
+ *val = '\0';
+ flb_sds_len_set(kv->val, val - kv->val);
+ }
+
+ /* Find and merge duplicates */
+ mk_list_foreach_safe(head, tmp, &out_tmp) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+
+ /* Check if this kv exists in out_list */
+ c_kv = NULL;
+ mk_list_foreach(c_head, out_list) {
+ c_kv = mk_list_entry(c_head, struct flb_kv, _head);
+ if (strcmp(kv->key, c_kv->key) == 0) {
+ break;
+ }
+ c_kv = NULL;
+ }
+
+ /* if c_kv is set, means the key already exists in the outgoing list */
+ if (c_kv) {
+ t = flb_sds_printf(&c_kv->val, ",%s", kv->val);
+ c_kv->val = t;
+ flb_kv_item_destroy(kv);
+ }
+ else {
+ mk_list_del(&kv->_head);
+ mk_list_add(&kv->_head, out_list);
+ }
+ }
+}
+
+/*
+ * Task 1: Create a canonical request
+ * ==================================
+ *
+ * https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
+ *
+ * CanonicalRequest =
+ * HTTPRequestMethod + '\n' +
+ * CanonicalURI + '\n' +
+ * CanonicalQueryString + '\n' +
+ * CanonicalHeaders + '\n' +
+ * SignedHeaders + '\n' +
+ * HexEncode(Hash(RequestPayload))
+ */
+static flb_sds_t flb_signv4_canonical_request(struct flb_http_client *c,
+ int normalize_uri,
+ int amz_date_header,
+ char *amzdate,
+ char *security_token,
+ int s3_mode,
+ struct mk_list *excluded_headers,
+ flb_sds_t *signed_headers)
+{
+ int i;
+ int len;
+ int items;
+ int all_items;
+ int excluded_items;
+ int post_params = FLB_FALSE;
+ int result;
+ size_t size;
+ int skip_header;
+ char *val;
+ struct flb_kv **arr;
+ flb_sds_t cr;
+ flb_sds_t uri;
+ flb_sds_t tmp = NULL;
+ flb_sds_t params = NULL;
+ flb_sds_t payload_hash = NULL;
+ struct flb_kv *kv;
+ struct mk_list list_tmp;
+ struct mk_list *head;
+ struct mk_list *head_2;
+ struct flb_slist_entry *sle;
+ unsigned char sha256_buf[64] = {0};
+
+ /* Size hint */
+ size = strlen(c->uri) + (mk_list_size(&c->headers) * 64) + 256;
+
+ cr = flb_sds_create_size(size);
+ if (!cr) {
+ flb_error("[signv4] cannot allocate buffer");
+ return NULL;
+ }
+
+ switch (c->method) {
+ case FLB_HTTP_GET:
+ tmp = flb_sds_cat(cr, "GET\n", 4);
+ break;
+ case FLB_HTTP_POST:
+ tmp = flb_sds_cat(cr, "POST\n", 5);
+ break;
+ case FLB_HTTP_PUT:
+ tmp = flb_sds_cat(cr, "PUT\n", 4);
+ break;
+ case FLB_HTTP_HEAD:
+ tmp = flb_sds_cat(cr, "HEAD\n", 5);
+ break;
+ };
+
+ if (!tmp) {
+ flb_error("[signv4] invalid processing of HTTP method");
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+
+ cr = tmp;
+
+ /* Our URI already contains the query string, so do the proper adjustments */
+ if (c->query_string) {
+ len = (c->query_string - c->uri) - 1;
+ }
+ else {
+ len = strlen(c->uri);
+ }
+
+ /*
+ * URI normalization is required by certain AWS service, for hence the caller
+ * plugin is responsible to enable/disable this flag. If set the URI in the
+ * canonical request will be normalized.
+ */
+ if (normalize_uri == FLB_TRUE) {
+ tmp = flb_signv4_uri_normalize_path((char *) c->uri, len);
+ if (!tmp) {
+ flb_error("[signv4] error normalizing path");
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ len = flb_sds_len(tmp);
+ }
+ else {
+ tmp = (char *) c->uri;
+ }
+
+ /* Do URI encoding (rfc3986) */
+ uri = uri_encode(tmp, len);
+ if (tmp != c->uri) {
+ flb_sds_destroy(tmp);
+ }
+ if (!uri) {
+ /* error composing outgoing buffer */
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+
+ tmp = flb_sds_cat(cr, uri, flb_sds_len(uri));
+ if (!tmp) {
+ flb_error("[signv4] error concatenating encoded URI");
+ flb_sds_destroy(uri);
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ cr = tmp;
+ flb_sds_destroy(uri);
+
+ tmp = flb_sds_cat(cr, "\n", 1);
+ if (!tmp) {
+ flb_error("[signv4] error concatenating encoded URI break line");
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ cr = tmp;
+
+ /* Canonical Query String */
+ tmp = NULL;
+ if (c->query_string) {
+ params = url_params_format((char *) c->query_string);
+ if (!params) {
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ tmp = flb_sds_cat(cr, params, flb_sds_len(params));
+ if (!tmp) {
+ flb_error("[signv4] error concatenating query string");
+ flb_sds_destroy(params);
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ flb_sds_destroy(params);
+ cr = tmp;
+ }
+
+ /*
+ * If the original HTTP method is POST and we have some urlencoded parameters
+ * as payload, we must handle them as we did for the query string.
+ */
+ if (c->method == FLB_HTTP_POST && c->body_len > 0) {
+ val = (char *) flb_kv_get_key_value("Content-Type", &c->headers);
+ if (val) {
+ if (strstr(val, "application/x-www-form-urlencoded")) {
+ params = url_params_format((char *) c->body_buf);
+ if (!params) {
+ flb_error("[signv4] error processing POST payload params");
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ tmp = flb_sds_cat(cr, params, flb_sds_len(params));
+ if (!tmp) {
+ flb_error("[signv4] error concatenating POST payload params");
+ flb_sds_destroy(params);
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ cr = tmp;
+ flb_sds_destroy(params);
+ post_params = FLB_TRUE;
+ }
+ }
+ }
+
+ /* query string / POST separator */
+ tmp = flb_sds_cat(cr, "\n", 1);
+ if (!tmp) {
+ flb_error("[signv4] error adding params breakline separator");
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ cr = tmp;
+
+ /*
+ * Calculate payload hash.
+ * This is added at the end of all canonical requests, unless
+ * S3_MODE_UNSIGNED_PAYLOAD is set.
+ * If we're using S3_MODE_SIGNED_PAYLOAD, then the hash is added to the
+ * canonical headers.
+ */
+ if (s3_mode == S3_MODE_UNSIGNED_PAYLOAD) {
+ payload_hash = flb_sds_create("UNSIGNED-PAYLOAD");
+ } else {
+ if (c->body_len > 0 && post_params == FLB_FALSE) {
+ result = flb_hash_simple(FLB_HASH_SHA256,
+ (unsigned char *) c->body_buf, c->body_len,
+ sha256_buf, sizeof(sha256_buf));
+ }
+ else {
+ result = flb_hash_simple(FLB_HASH_SHA256,
+ (unsigned char *) NULL, 0,
+ sha256_buf, sizeof(sha256_buf));
+ }
+
+ if (result != FLB_CRYPTO_SUCCESS) {
+ flb_error("[signv4] error hashing payload");
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+
+ payload_hash = flb_sds_create_size(64);
+ if (!payload_hash) {
+ flb_error("[signv4] error formatting hashed payload");
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ for (i = 0; i < 32; i++) {
+ tmp = flb_sds_printf(&payload_hash, "%02x",
+ (unsigned char) sha256_buf[i]);
+ if (!tmp) {
+ flb_error("[signv4] error formatting hashed payload");
+ flb_sds_destroy(cr);
+ flb_sds_destroy(payload_hash);
+ return NULL;
+ }
+ payload_hash = tmp;
+ }
+ }
+
+ /*
+ * Canonical Headers
+ *
+ * Add the required custom headers:
+ *
+ * - x-amz-date
+ * - x-amz-security-token (if set)
+ * - x-amz-content-sha256 (if S3_MODE_SIGNED_PAYLOAD)
+ */
+ mk_list_init(&list_tmp);
+
+ /* include x-amz-date header ? */
+ if (amz_date_header == FLB_TRUE) {
+ len = strlen(amzdate);
+ flb_http_add_header(c, "x-amz-date", 10, amzdate, len);
+ }
+
+ /* x-amz-security_token */
+ if (security_token) {
+ len = strlen(security_token);
+ flb_http_add_header(c, "x-amz-security-token", 20, security_token, len);
+ }
+
+ if (s3_mode == S3_MODE_SIGNED_PAYLOAD) {
+ flb_http_add_header(c, "x-amz-content-sha256", 20, payload_hash, 64);
+ }
+
+ headers_sanitize(&c->headers, &list_tmp);
+
+ /*
+ * For every header registered, append it to the temporal array so we can sort them
+ * later.
+ */
+ all_items = mk_list_size(&list_tmp);
+ excluded_items = 0;
+ size = (sizeof(struct flb_kv *) * (all_items));
+ arr = flb_calloc(1, size);
+ if (!arr) {
+ flb_errno();
+ flb_kv_release(&list_tmp);
+ flb_sds_destroy(cr);
+ flb_sds_destroy(payload_hash);
+ return NULL;
+ }
+
+ /* Compose temporal array to sort headers */
+ i = 0;
+ mk_list_foreach(head, &list_tmp) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+
+ /* Skip excluded headers */
+ if (excluded_headers) {
+ skip_header = FLB_FALSE;
+ mk_list_foreach(head_2, excluded_headers) {
+ sle = mk_list_entry(head_2, struct flb_slist_entry, _head);
+ if (flb_sds_casecmp(kv->key, sle->str, flb_sds_len(sle->str)) == 0) {
+
+ /* Skip header */
+ excluded_items++;
+ skip_header = FLB_TRUE;
+ break;
+ }
+ }
+ if (skip_header) {
+ continue;
+ }
+ }
+
+ arr[i] = kv;
+ i++;
+ }
+
+ /* Count items */
+ items = all_items - excluded_items;
+
+ /* Sort the headers from the temporal array */
+ qsort(arr, items, sizeof(struct flb_kv *), kv_key_cmp);
+
+ /* Iterate sorted headers and append them to the outgoing buffer */
+ for (i = 0; i < items; i++) {
+ kv = (struct flb_kv *) arr[i];
+ tmp = flb_sds_printf(&cr, "%s:%s\n", kv->key, kv->val);
+ if (!tmp) {
+ flb_error("[signv4] error composing canonical headers");
+ flb_free(arr);
+ flb_kv_release(&list_tmp);
+ flb_sds_destroy(cr);
+ flb_sds_destroy(payload_hash);
+ return NULL;
+ }
+ cr = tmp;
+ }
+
+ /* Add required breakline */
+ tmp = flb_sds_printf(&cr, "\n");
+ if (!tmp) {
+ flb_error("[signv4] error adding extra breakline separator");
+ flb_free(arr);
+ flb_kv_release(&list_tmp);
+ flb_sds_destroy(cr);
+ flb_sds_destroy(payload_hash);
+ return NULL;
+ }
+ cr = tmp;
+
+ /* Signed Headers for canonical request context */
+ for (i = 0; i < items; i++) {
+ kv = (struct flb_kv *) arr[i];
+
+ /* Check if this is the last header, if so add breakline separator */
+ if (i + 1 == items) {
+ tmp = flb_sds_printf(&cr, "%s\n", kv->key);
+ }
+ else {
+ tmp = flb_sds_printf(&cr, "%s;", kv->key);
+ }
+ if (!tmp) {
+ flb_error("[signv4] error composing canonical signed headers");
+ flb_free(arr);
+ flb_kv_release(&list_tmp);
+ flb_sds_destroy(cr);
+ flb_sds_destroy(payload_hash);
+ return NULL;
+ }
+ cr = tmp;
+ }
+
+ /* Signed Headers for authorization header (Task 4) */
+ for (i = 0; i < items; i++) {
+ kv = (struct flb_kv *) arr[i];
+
+ /* Check if this is the last header, if so add breakline separator */
+ if (i + 1 == items) {
+ tmp = flb_sds_printf(signed_headers, "%s", kv->key);
+ }
+ else {
+ tmp = flb_sds_printf(signed_headers, "%s;", kv->key);
+ }
+ if (!tmp) {
+ flb_error("[signv4] error composing auth signed headers");
+ flb_free(arr);
+ flb_kv_release(&list_tmp);
+ flb_sds_destroy(cr);
+ flb_sds_destroy(payload_hash);
+ return NULL;
+ }
+ *signed_headers = tmp;
+ }
+
+ flb_free(arr);
+ flb_kv_release(&list_tmp);
+
+ /* Add Payload Hash */
+ tmp = flb_sds_printf(&cr, "%s", payload_hash);
+ if (!tmp) {
+ flb_error("[signv4] error adding payload hash");
+ flb_sds_destroy(cr);
+ flb_sds_destroy(payload_hash);
+ return NULL;
+ }
+ cr = tmp;
+ flb_sds_destroy(payload_hash);
+
+ return cr;
+}
+
+/*
+ * Task 2
+ * ======
+ * https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
+ */
+static flb_sds_t flb_signv4_string_to_sign(struct flb_http_client *c,
+ flb_sds_t cr, char *amzdate,
+ char *datestamp, char *service,
+ char *region)
+{
+ int i;
+ int result;
+ flb_sds_t tmp;
+ flb_sds_t sign;
+ unsigned char sha256_buf[64] = {0};
+
+ sign = flb_sds_create_size(256);
+ if (!sign) {
+ flb_error("[signv4] cannot create buffer for signature");
+ return NULL;
+ }
+
+ /* Hashing Algorithm */
+ tmp = flb_sds_cat(sign, "AWS4-HMAC-SHA256\n", 17);
+ if (!tmp) {
+ flb_error("[signv4] cannot add algorithm to signature");
+ flb_sds_destroy(sign);
+ return NULL;
+ }
+ sign = tmp;
+
+ /* Amazon date */
+ tmp = flb_sds_printf(&sign, "%s\n", amzdate);
+ if (!tmp) {
+ flb_error("[signv4] cannot add amz-date to signature");
+ flb_sds_destroy(sign);
+ return NULL;
+ }
+ sign = tmp;
+
+ /* Credentials Scope */
+ tmp = flb_sds_printf(&sign, "%s/%s/%s/aws4_request\n",
+ datestamp, region, service);
+ if (!tmp) {
+ flb_error("[signv4] cannot add credentials scope to signature");
+ flb_sds_destroy(sign);
+ return NULL;
+ }
+
+ /* Hash of Canonical Request */
+ result = flb_hash_simple(FLB_HASH_SHA256,
+ (unsigned char *) cr, flb_sds_len(cr),
+ sha256_buf, sizeof(sha256_buf));
+
+ if (result != FLB_CRYPTO_SUCCESS) {
+ flb_error("[signv4] error hashing canonical request");
+ flb_sds_destroy(sign);
+ return NULL;
+ }
+
+ for (i = 0; i < 32; i++) {
+ tmp = flb_sds_printf(&sign, "%02x", (unsigned char) sha256_buf[i]);
+ if (!tmp) {
+ flb_error("[signv4] error formatting hashed canonical request");
+ flb_sds_destroy(sign);
+ return NULL;
+ }
+ sign = tmp;
+ }
+
+ return sign;
+}
+
+/*
+ * Task 3
+ * ======
+ *
+ * https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
+ *
+ * TODO: The signing key could be cached to improve performance
+ */
+static flb_sds_t flb_signv4_calculate_signature(flb_sds_t string_to_sign,
+ char *datestamp, char *service,
+ char *region, char *secret_key)
+{
+ int len;
+ int klen = 32;
+ flb_sds_t tmp;
+ flb_sds_t key;
+ unsigned char key_date[32];
+ unsigned char key_region[32];
+ unsigned char key_service[32];
+ unsigned char key_signing[32];
+ unsigned char signature[32];
+
+ /* Compose initial key */
+ key = flb_sds_create_size(256);
+ if (!key) {
+ flb_error("[signv4] cannot create buffer for signature calculation");
+ return NULL;
+ }
+
+ tmp = flb_sds_printf(&key, "AWS4%s", secret_key);
+ if (!tmp) {
+ flb_error("[signv4] error formatting initial key");
+ flb_sds_destroy(key);
+ return NULL;
+ }
+ key = tmp;
+
+ /* key_date */
+ len = strlen(datestamp);
+ hmac_sha256_sign(key_date, (unsigned char *) key, flb_sds_len(key),
+ (unsigned char *) datestamp, len);
+ flb_sds_destroy(key);
+
+ /* key_region */
+ len = strlen(region);
+ hmac_sha256_sign(key_region, key_date, klen, (unsigned char *) region, len);
+
+ /* key_service */
+ len = strlen(service);
+ hmac_sha256_sign(key_service, key_region, klen, (unsigned char *) service, len);
+
+ /* key_signing */
+ hmac_sha256_sign(key_signing, key_service, klen,
+ (unsigned char *) "aws4_request", 12);
+
+ /* Signature */
+ hmac_sha256_sign(signature, key_signing, klen,
+ (unsigned char *) string_to_sign, flb_sds_len(string_to_sign));
+
+ return sha256_to_hex(signature);
+}
+
+/*
+ * Task 4
+ * ======
+ *
+ * https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
+ */
+static flb_sds_t flb_signv4_add_authorization(struct flb_http_client *c,
+ char *access_key,
+ char *datestamp,
+ char *region, char *service,
+ flb_sds_t signed_headers,
+ flb_sds_t signature)
+{
+ int ret;
+ int len;
+ flb_sds_t tmp;
+ flb_sds_t header_value;
+
+ header_value = flb_sds_create_size(512);
+ if (!header_value) {
+ flb_error("[signv4] cannot allocate buffer for authorization header");
+ return NULL;
+ }
+
+ tmp = flb_sds_printf(&header_value,
+ "AWS4-HMAC-SHA256 Credential=%s/%s/%s/%s/aws4_request, "
+ "SignedHeaders=%s, Signature=%s",
+ access_key, datestamp, region, service,
+ signed_headers, signature);
+ if (!tmp) {
+ flb_error("[signv4] error composing authorization header");
+ flb_sds_destroy(header_value);
+ return NULL;
+ }
+ header_value = tmp;
+
+ len = flb_sds_len(header_value);
+ ret = flb_http_add_header(c, "Authorization", 13, header_value, len);
+ if (ret == -1) {
+ flb_error("[signv4] could not add authorization header");
+ flb_sds_destroy(header_value);
+ return NULL;
+
+ }
+
+ /* Return the composed final header for testing if required */
+ return header_value;
+}
+
+flb_sds_t flb_signv4_do(struct flb_http_client *c, int normalize_uri,
+ int amz_date_header,
+ time_t t_now,
+ char *region, char *service,
+ int s3_mode,
+ struct mk_list *unsigned_headers,
+ struct flb_aws_provider *provider)
+{
+ char amzdate[32];
+ char datestamp[32];
+ struct tm *gmt;
+ flb_sds_t cr;
+ flb_sds_t string_to_sign;
+ flb_sds_t signature;
+ flb_sds_t signed_headers;
+ flb_sds_t auth_header;
+ struct flb_aws_credentials *creds;
+
+ creds = provider->provider_vtable->get_credentials(provider);
+ if (!creds) {
+ flb_error("[signv4] Provider returned no credentials, service=%s",
+ service);
+ return NULL;
+ }
+
+ gmt = flb_calloc(1, sizeof(struct tm));
+ if (!gmt) {
+ flb_errno();
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+ }
+
+ if (!gmtime_r(&t_now, gmt)) {
+ flb_error("[signv4] error converting given unix timestamp");
+ flb_free(gmt);
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+ }
+
+ strftime(amzdate, sizeof(amzdate) - 1, "%Y%m%dT%H%M%SZ", gmt);
+ strftime(datestamp, sizeof(datestamp) - 1, "%Y%m%d", gmt);
+ flb_free(gmt);
+
+ /* Task 1: canonical request */
+ signed_headers = flb_sds_create_size(256);
+ if (!signed_headers) {
+ flb_error("[signedv4] cannot allocate buffer for auth signed headers");
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+ }
+
+ cr = flb_signv4_canonical_request(c, normalize_uri,
+ amz_date_header, amzdate,
+ creds->session_token, s3_mode,
+ unsigned_headers,
+ &signed_headers);
+ if (!cr) {
+ flb_error("[signv4] failed canonical request");
+ flb_sds_destroy(signed_headers);
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+ }
+
+ /* Task 2: string to sign */
+ string_to_sign = flb_signv4_string_to_sign(c, cr, amzdate,
+ datestamp, service, region);
+ if (!string_to_sign) {
+ flb_error("[signv4] failed string to sign");
+ flb_sds_destroy(cr);
+ flb_sds_destroy(signed_headers);
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+ }
+ flb_sds_destroy(cr);
+
+ /* Task 3: calculate the signature */
+ signature = flb_signv4_calculate_signature(string_to_sign, datestamp,
+ service, region,
+ creds->secret_access_key);
+ if (!signature) {
+ flb_error("[signv4] failed calculate_string");
+ flb_sds_destroy(signed_headers);
+ flb_sds_destroy(string_to_sign);
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+ }
+ flb_sds_destroy(string_to_sign);
+
+ /* Task 4: add signature to HTTP request */
+ auth_header = flb_signv4_add_authorization(c,
+ creds->access_key_id,
+ datestamp, region, service,
+ signed_headers, signature);
+ flb_sds_destroy(signed_headers);
+ flb_sds_destroy(signature);
+ flb_aws_credentials_destroy(creds);
+
+ if (!auth_header) {
+ flb_error("[signv4] error creating authorization header");
+ return NULL;
+ }
+
+ return auth_header;
+}
diff --git a/fluent-bit/src/flb_slist.c b/fluent-bit/src/flb_slist.c
new file mode 100644
index 000000000..512fe4522
--- /dev/null
+++ b/fluent-bit/src/flb_slist.c
@@ -0,0 +1,356 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_slist.h>
+
+/* Initialize slist */
+int flb_slist_create(struct mk_list *list)
+{
+ mk_list_init(list);
+ return 0;
+}
+
+/* Append 'len' bytes of 'str' as a new string node into the list */
+int flb_slist_add_n(struct mk_list *head, const char *str, int len)
+{
+ struct flb_slist_entry *e;
+
+ e = flb_malloc(sizeof(struct flb_slist_entry));
+ if (!e) {
+ flb_errno();
+ return -1;
+ }
+
+ e->str = flb_sds_create_len(str, len);
+ if (!e->str) {
+ flb_free(e);
+ return -1;
+ }
+
+ mk_list_add(&e->_head, head);
+ return 0;
+}
+
+int flb_slist_add_sds(struct mk_list *head, flb_sds_t str)
+{
+ struct flb_slist_entry *e;
+
+ e = flb_malloc(sizeof(struct flb_slist_entry));
+ if (!e) {
+ flb_errno();
+ return -1;
+ }
+
+ e->str = str;
+ mk_list_add(&e->_head, head);
+ return 0;
+}
+
+/* Append NULL terminated string as a new node into the list */
+int flb_slist_add(struct mk_list *head, const char *str)
+{
+ int len;
+
+ if (!str) {
+ return -1;
+ }
+
+ len = strlen(str);
+ if (len <= 0) {
+ return -1;
+ }
+
+ return flb_slist_add_n(head, str, len);
+}
+
+static inline int token_unescape(char *token)
+{
+ char *in = token;
+ char *out = token;
+
+ while (*in) {
+ if ((in[0] == '\\') && (in[1] == '"')) {
+ *out = in[1];
+ out++;
+ in += 2;
+ }
+ else {
+ *out = *in;
+ out++;
+ in++;
+ }
+ }
+ *out = 0;
+ return out - token;
+}
+
+static flb_sds_t token_retrieve(char **str)
+{
+ int len;
+ int quoted = FLB_FALSE;
+ char *p;
+ char *start;
+ char *prev;
+ flb_sds_t out = NULL;
+
+ if (!*str) {
+ return NULL;
+ }
+
+ p = *str;
+
+ /* Skip empty spaces */
+ while (*p == ' ') {
+ p++;
+ }
+ start = p;
+
+ if (*p == '"') {
+ quoted = FLB_TRUE;
+ p++;
+ start = p;
+ while (1) {
+ while (*p && *p != '"') {
+ p++;
+ }
+
+ if (!*p) {
+ goto exit;
+ }
+
+ prev = p - 1;
+ if (*prev == '\\') {
+ p++;
+ continue;
+ }
+ goto exit;
+ }
+ }
+
+ while (*p && *p != ' ') {
+ p++;
+ }
+
+ exit:
+ if (*p) {
+ out = flb_sds_create_len(start, p - start);
+ if (!out) {
+ *str = NULL;
+ return NULL;
+ }
+ if (quoted == FLB_TRUE) {
+ len = token_unescape(out);
+ flb_sds_len_set(out, len);
+ }
+ p++;
+
+ while (*p && *p == ' ') {
+ p++;
+ }
+ *str = p;
+ }
+ else {
+ if (p > start) {
+ out = flb_sds_create(start);
+ }
+ *str = NULL;
+ }
+
+ return out;
+}
+
+int flb_slist_split_tokens(struct mk_list *list, const char *str, int max_split)
+{
+ int count = 0;
+ char *p;
+ char *buf;
+ flb_sds_t tmp = NULL;
+
+ buf = (char *) str;
+ while ((tmp = token_retrieve(&buf))) {
+ flb_slist_add_sds(list, tmp);
+ if (!buf) {
+ break;
+ }
+ count++;
+
+ /* Append remaining string if we use a maximum number of tokens */
+ if (count >= max_split && max_split > 0) {
+ p = buf;
+ while (*p == ' ') {
+ p++;
+ }
+
+ if (*p) {
+ flb_slist_add(list, p);
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Split a string using a separator, every splitted content is appended to the end of
+ * the slist list head.
+ */
+int flb_slist_split_string(struct mk_list *list, const char *str,
+ int separator, int max_split)
+{
+ int i = 0;
+ int ret;
+ int count = 0;
+ int val_len;
+ int len;
+ int end;
+ char *p_init;
+ char *p_end;
+
+ if (!str) {
+ return -1;
+ }
+
+ len = strlen(str);
+ while (i < len) {
+ end = mk_string_char_search(str + i, separator, len - i);
+ if (end < 0) {
+ end = len - i;
+ }
+ else if ((end + i) == i) {
+ i++;
+ continue;
+ }
+
+ p_init = (char *) str + i;
+ p_end = p_init + end - 1;
+
+ /* Skip empty spaces */
+ while (*p_init == ' ') {
+ p_init++;
+ }
+
+ while (*p_end == ' ' && p_end >= p_init) {
+ p_end--;
+ }
+
+ if (p_init > p_end) {
+ goto next;
+ }
+
+ if (p_init == p_end) {
+ if (*p_init == ' ') {
+ goto next;
+ }
+ val_len = 1;
+ }
+ else {
+ val_len = (p_end - p_init) + 1;
+ }
+
+ if (val_len == 0) {
+ goto next;
+ }
+
+ ret = flb_slist_add_n(list, p_init, val_len);
+ if (ret == -1) {
+ return -1;
+ }
+ count++;
+
+ /* Append remaining string as a new node ? */
+ if (count >= max_split && max_split > 0) {
+ p_end = p_init + end;
+ if (*p_end == separator) {
+ p_end++;
+ }
+ while (*p_end == ' ') {
+ p_end++;
+ }
+
+ if ((p_end - str) >= len) {
+ break;
+ }
+
+ ret = flb_slist_add(list, p_end);
+ if (ret == -1) {
+ return -1;
+ }
+ count++;
+ break;
+ }
+
+ next:
+ i += end + 1;
+ }
+
+ return count;
+}
+
+void flb_slist_dump(struct mk_list *list)
+{
+ struct mk_list *head;
+ struct flb_slist_entry *e;
+
+ printf("[slist %p]\n", list);
+ mk_list_foreach(head, list) {
+ e = mk_list_entry(head, struct flb_slist_entry, _head);
+ printf(" - '%s'\n", e->str);
+ }
+}
+
+void flb_slist_destroy(struct mk_list *list)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_slist_entry *e;
+
+ mk_list_foreach_safe(head, tmp, list) {
+ e = mk_list_entry(head, struct flb_slist_entry, _head);
+ flb_sds_destroy(e->str);
+ mk_list_del(&e->_head);
+ flb_free(e);
+ }
+}
+
+/* Return the entry in position number 'n' */
+struct flb_slist_entry *flb_slist_entry_get(struct mk_list *list, int n)
+{
+ int i = 0;
+ struct mk_list *head;
+ struct flb_slist_entry *e;
+
+ if (!list || mk_list_size(list) == 0) {
+ return NULL;
+ }
+
+ mk_list_foreach(head, list) {
+ if (i == n) {
+ e = mk_list_entry(head, struct flb_slist_entry, _head);
+ return e;
+ }
+ i++;
+ }
+
+ return NULL;
+}
diff --git a/fluent-bit/src/flb_snappy.c b/fluent-bit/src/flb_snappy.c
new file mode 100644
index 000000000..865e18399
--- /dev/null
+++ b/fluent-bit/src/flb_snappy.c
@@ -0,0 +1,344 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+
+#include <cfl/cfl.h>
+#include <cfl/cfl_list.h>
+#include <cfl/cfl_checksum.h>
+
+#include <fluent-bit/flb_snappy.h>
+
+#include <snappy.h>
+
+int flb_snappy_compress(char *in_data, size_t in_len,
+ char **out_data, size_t *out_len)
+{
+ struct snappy_env snappy_env;
+ char *tmp_data;
+ size_t tmp_len;
+ int result;
+
+ tmp_len = snappy_max_compressed_length(in_len);
+
+ tmp_data = flb_malloc(tmp_len);
+
+ if (tmp_data == NULL) {
+ flb_errno();
+
+ return -1;
+ }
+
+ result = snappy_init_env(&snappy_env);
+
+ if (result != 0) {
+ flb_free(tmp_data);
+
+ return -2;
+ }
+
+ result = snappy_compress(&snappy_env, in_data, in_len, tmp_data, &tmp_len);
+
+ if (result != 0) {
+ flb_free(tmp_data);
+
+ return -3;
+ }
+
+ snappy_free_env(&snappy_env);
+
+ *out_data = tmp_data;
+ *out_len = tmp_len;
+
+ return 0;
+}
+
+int flb_snappy_uncompress(char *in_data, size_t in_len,
+ char **out_data, size_t *out_len)
+{
+ char *tmp_data;
+ size_t tmp_len;
+ int result;
+
+ result = snappy_uncompressed_length(in_data, in_len, &tmp_len);
+
+ if (result == 0) {
+ return -1;
+ }
+
+ tmp_data = flb_malloc(tmp_len);
+
+ if (tmp_data == NULL) {
+ flb_errno();
+
+ return -2;
+ }
+
+ result = snappy_uncompress(in_data, in_len, tmp_data);
+
+ if (result != 0) {
+ flb_free(tmp_data);
+
+ return -3;
+ }
+
+ *out_data = tmp_data;
+ *out_len = tmp_len;
+
+ return 0;
+}
+
+static uint32_t calculate_checksum(char *buffer, size_t length)
+{
+ uint32_t checksum;
+
+ checksum = cfl_checksum_crc32c((unsigned char *) buffer, length);
+
+ return ((checksum >> 15) |
+ (checksum << 17)) + 0xa282ead8;
+}
+
+int flb_snappy_uncompress_framed_data(char *in_data, size_t in_len,
+ char **out_data, size_t *out_len)
+{
+ uint32_t decompressed_data_checksum;
+ size_t stream_identifier_length;
+ size_t uncompressed_chunk_count;
+ int stream_identifier_found;
+ char *aggregated_data_buffer;
+ size_t aggregated_data_length = 0;
+ size_t aggregated_data_offset;
+ size_t compressed_chunk_count;
+ struct cfl_list *iterator_backup;
+ uint32_t frame_checksum;
+ char *frame_buffer;
+ size_t frame_length;
+ char *frame_body;
+ unsigned char frame_type;
+ struct cfl_list *iterator;
+ int result;
+ size_t offset;
+ struct cfl_list chunks;
+ struct flb_snappy_data_chunk *chunk;
+
+ if (*((uint8_t *) in_data) != FLB_SNAPPY_FRAME_TYPE_STREAM_IDENTIFIER) {
+ return flb_snappy_uncompress(in_data, in_len, out_data, out_len);
+ }
+
+ if (out_data == NULL) {
+ return -1;
+ }
+
+ if (out_len == NULL) {
+ return -1;
+ }
+
+ *out_data = NULL;
+ *out_len = 0;
+
+ cfl_list_init(&chunks);
+
+ compressed_chunk_count = 0;
+ uncompressed_chunk_count = 0;
+
+ stream_identifier_found = FLB_FALSE;
+ stream_identifier_length = strlen(FLB_SNAPPY_STREAM_IDENTIFIER_STRING);
+
+ result = 0;
+ offset = 0;
+
+ while (offset < in_len && result == 0) {
+ frame_buffer = &in_data[offset];
+
+ frame_type = *((uint8_t *) &frame_buffer[0]);
+
+ frame_length = *((uint32_t *) &frame_buffer[1]);
+ frame_length &= 0x00FFFFFF;
+
+ frame_body = &frame_buffer[4];
+
+ if (frame_length > FLB_SNAPPY_FRAME_SIZE_LIMIT) {
+ result = -2;
+ }
+ else if (frame_type == FLB_SNAPPY_FRAME_TYPE_STREAM_IDENTIFIER) {
+ if (!stream_identifier_found) {
+ if (frame_length == stream_identifier_length) {
+ result = strncmp(frame_body,
+ FLB_SNAPPY_STREAM_IDENTIFIER_STRING,
+ stream_identifier_length);
+
+ if (result == 0) {
+ stream_identifier_found = FLB_TRUE;
+ }
+ }
+ }
+ }
+ else if (frame_type == FLB_SNAPPY_FRAME_TYPE_COMPRESSED_DATA) {
+ chunk = (struct flb_snappy_data_chunk * ) \
+ flb_calloc(1, sizeof(struct flb_snappy_data_chunk));
+
+ if (chunk != NULL) {
+ /* We add the chunk to the list now because that way
+ * even if the process fails we can clean up in a single
+ * place.
+ */
+ compressed_chunk_count++;
+
+ chunk->dynamically_allocated_buffer = FLB_TRUE;
+
+ cfl_list_add(&chunk->_head, &chunks);
+
+ frame_checksum = *((uint32_t *) &frame_body[0]);
+ frame_body = &frame_body[4];
+
+ result = flb_snappy_uncompress(
+ frame_body,
+ frame_length - sizeof(uint32_t),
+ &chunk->buffer,
+ &chunk->length);
+
+ /* decompressed data */
+ if (result == 0) {
+ decompressed_data_checksum = calculate_checksum(
+ chunk->buffer,
+ chunk->length);
+
+ if (decompressed_data_checksum != frame_checksum) {
+ result = -3;
+ }
+ else {
+ aggregated_data_length += chunk->length;
+ }
+ }
+ else {
+ result = -4;
+ }
+ }
+ }
+ else if (frame_type == FLB_SNAPPY_FRAME_TYPE_UNCOMPRESSED_DATA) {
+ chunk = (struct flb_snappy_data_chunk *) \
+ flb_calloc(1, sizeof(struct flb_snappy_data_chunk));
+
+ if (chunk != NULL) {
+ /* We add the chunk to the list now because that way
+ * even if the process fails we can clean up in a single
+ * place.
+ */
+ uncompressed_chunk_count++;
+
+ chunk->dynamically_allocated_buffer = FLB_FALSE;
+
+ cfl_list_add(&chunk->_head, &chunks);
+
+ frame_checksum = *((uint32_t *) &frame_body[0]);
+ frame_body = &frame_body[4];
+
+ chunk->buffer = frame_body;
+ chunk->length = frame_length - sizeof(uint32_t);
+
+ decompressed_data_checksum = calculate_checksum(
+ chunk->buffer,
+ chunk->length);
+
+ if (decompressed_data_checksum != frame_checksum) {
+ result = -3;
+ }
+ else {
+ aggregated_data_length += chunk->length;
+ }
+ }
+ }
+ else if (frame_type == FLB_SNAPPY_FRAME_TYPE_PADDING) {
+ /* We just need to skip these frames */
+ }
+ else if (frame_type >= FLB_SNAPPY_FRAME_TYPE_RESERVED_UNSKIPPABLE_BASE &&
+ frame_type <= FLB_SNAPPY_FRAME_TYPE_RESERVED_UNSKIPPABLE_TOP) {
+ result = -5;
+ }
+ else if (frame_type >= FLB_SNAPPY_FRAME_TYPE_RESERVED_SKIPPABLE_BASE &&
+ frame_type <= FLB_SNAPPY_FRAME_TYPE_RESERVED_SKIPPABLE_TOP) {
+ /* We just need to skip these frames */
+ }
+
+ offset += frame_length + 4;
+ }
+
+ aggregated_data_buffer = NULL;
+ aggregated_data_length = 0;
+
+ if (compressed_chunk_count == 1 &&
+ uncompressed_chunk_count == 0 &&
+ result == 0) {
+ /* This is a "past path" to avoid unnecessarily copying
+ * data whene the input is only comprised of a single
+ * compressed chunk.
+ */
+
+ chunk = cfl_list_entry_first(&chunks,
+ struct flb_snappy_data_chunk, _head);
+
+ aggregated_data_buffer = chunk->buffer;
+ aggregated_data_length = chunk->length;
+ aggregated_data_offset = aggregated_data_length;
+
+ flb_free(chunk);
+ }
+ else {
+ if (aggregated_data_length > 0) {
+ aggregated_data_buffer = flb_calloc(aggregated_data_length,
+ sizeof(char));
+
+ if (aggregated_data_buffer == NULL) {
+ result = -6;
+ }
+ }
+
+ aggregated_data_offset = 0;
+ cfl_list_foreach_safe(iterator, iterator_backup, &chunks) {
+ chunk = cfl_list_entry(iterator,
+ struct flb_snappy_data_chunk, _head);
+
+ if (chunk->buffer != NULL) {
+ if (aggregated_data_buffer != NULL &&
+ result == 0) {
+ memcpy(&aggregated_data_buffer[aggregated_data_offset],
+ chunk->buffer,
+ chunk->length);
+
+ aggregated_data_offset += chunk->length;
+ }
+
+ if (chunk->dynamically_allocated_buffer) {
+ flb_free(chunk->buffer);
+ }
+ }
+
+ cfl_list_del(&chunk->_head);
+
+ flb_free(chunk);
+ }
+ }
+
+ *out_data = (char *) aggregated_data_buffer;
+ *out_len = aggregated_data_offset;
+
+ return result;
+}
diff --git a/fluent-bit/src/flb_socket.c b/fluent-bit/src/flb_socket.c
new file mode 100644
index 000000000..daf995e37
--- /dev/null
+++ b/fluent-bit/src/flb_socket.c
@@ -0,0 +1,46 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_socket.h>
+
+#ifndef _WIN32
+
+int flb_socket_error(int fd)
+{
+ int ret;
+ int error = 0;
+ socklen_t slen = sizeof(error);
+
+ ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &slen);
+ if (ret == -1) {
+ flb_debug("[socket] could not validate socket status for #%i (don't worry)",
+ fd);
+ return -1;
+ }
+
+ if (error != 0) {
+ return error;
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/fluent-bit/src/flb_sosreport.c b/fluent-bit/src/flb_sosreport.c
new file mode 100644
index 000000000..7bbd79aef
--- /dev/null
+++ b/fluent-bit/src/flb_sosreport.c
@@ -0,0 +1,322 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_compat.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_filter.h>
+#include <fluent-bit/flb_router.h>
+#include <fluent-bit/flb_version.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_kv.h>
+
+#ifndef _MSC_VER
+#include <sys/utsname.h>
+#endif
+
+static void print_key(char *key)
+{
+ printf(" %-20s", key);
+}
+
+static void print_kv(char *key, char *val)
+{
+ print_key(key);
+ printf("%s\n", val);
+}
+
+static char *get_str(char *p)
+{
+ if (p) {
+ return p;
+ }
+
+ return "(not set)";
+}
+
+
+
+static char *log_level(int x)
+{
+ switch (x) {
+ case 0: return "Off";
+ case 1: return "Error";
+ case 2: return "Warn";
+ case 3: return "Info";
+ case 4: return "Debug";
+ case 5: return "Trace";
+ default: return "Unknown";
+ }
+}
+
+static void input_flags(int flags)
+{
+ if (flags & FLB_INPUT_NET) {
+ printf("NET ");
+ }
+
+ if (flags & FLB_INPUT_CORO) {
+ printf("CORO ");
+ }
+
+ printf("\n");
+}
+
+static void print_host(struct flb_net_host *host)
+{
+ if (host->address) {
+ printf(" Host.Address\t%s\n", host->address);
+ }
+ if (host->port > 0) {
+ printf(" Host.TCP_Port\t%i\n", host->port);
+ }
+ if (host->name) {
+ printf(" Host.Name\t\t%s\n", host->name);
+ }
+ if (host->listen) {
+ printf(" Host.Listen\t\t%s\n", host->listen);
+ }
+}
+
+static void print_properties(struct mk_list *props)
+{
+ struct mk_list *head;
+ struct flb_kv *kv;
+
+ mk_list_foreach(head, props) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ print_kv(kv->key, kv->val);
+ }
+}
+
+#ifdef _MSC_VER
+/* A definition table of SYSTEM_INFO.wProcessorArchitecture.
+ *
+ * This is a streight-forward translation of the official manual.
+ * https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/
+ */
+static const char* win32_arch(int archid)
+{
+ switch(archid)
+ {
+ case PROCESSOR_ARCHITECTURE_AMD64:
+ return "x64 (AMD or Intel)";
+ case PROCESSOR_ARCHITECTURE_ARM:
+ return "ARM";
+ case PROCESSOR_ARCHITECTURE_ARM64:
+ return "ARM64";
+ case PROCESSOR_ARCHITECTURE_IA64:
+ return "Intel Itanium-based";
+ case PROCESSOR_ARCHITECTURE_INTEL:
+ return "x86";
+ case PROCESSOR_ARCHITECTURE_UNKNOWN:
+ default:
+ return "unknown";
+ }
+}
+
+static void win32_operating_system_info()
+{
+ OSVERSIONINFOA win32os;
+
+ /* TODO Support "Application Manifest". Windows 10 reports a wrong
+ * version info if we do not manifest the supported OS.
+ * https://blogs.msdn.microsoft.com/chuckw/2013/09/10/manifest-madness/
+ */
+ win32os.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
+ GetVersionExA(&win32os);
+
+ printf("[Operating System]\n");
+ printf(" Name\t\tWindows\n");
+ printf(" Version\t\t%i.%i\n", win32os.dwMajorVersion, win32os.dwMinorVersion);
+ printf(" Build\t\t%i\n", win32os.dwBuildNumber);
+ printf("\n");
+}
+
+static void win32_hardware_info()
+{
+ SYSTEM_INFO win32info;
+
+ GetNativeSystemInfo(&win32info);
+ printf("[Hardware]\n");
+ printf(" Architecture\t%s\n", win32_arch(win32info.wProcessorArchitecture));
+ printf(" Processors\t\t%i\n", win32info.dwNumberOfProcessors);
+ printf("\n");
+}
+#endif
+
+int flb_sosreport(struct flb_config *config)
+{
+ char tmp[32];
+ struct mk_list *head;
+ struct mk_list *head_r;
+ struct flb_input_plugin *in;
+ struct flb_filter_plugin *filter;
+ struct flb_output_plugin *out;
+ struct flb_input_instance *ins_in;
+ struct flb_filter_instance *ins_filter;
+ struct flb_output_instance *ins_out;
+ struct flb_router_path *route;
+
+ printf("\n");
+ printf("Fluent Bit Enterprise - SOS Report\n");
+ printf("==================================\n");
+ printf("The following report aims to be used by Fluent Bit and Fluentd "
+ "community users.\n\n");
+
+ /* Fluent Bit */
+ printf("\n[Fluent Bit]\n");
+ printf(" Version\t\t%s\n", FLB_VERSION_STR);
+ printf(" Built Flags\t\t%s\n", FLB_INFO_FLAGS);
+ printf("\n");
+
+#ifndef _MSC_VER
+ struct utsname uts;
+ uname(&uts);
+
+ /* Operating System */
+ printf("[Operating System]\n");
+ printf(" Name\t\t%s\n", uts.sysname);
+ printf(" Release\t\t%s\n", uts.release);
+ printf(" Version\t\t%s\n", uts.version);
+ printf("\n");
+
+ /* Basic hardware info */
+ printf("[Hardware]\n");
+ printf(" Architecture\t%s\n", uts.machine);
+ printf(" Processors\t\t%i\n", (int) sysconf(_SC_NPROCESSORS_ONLN));
+ printf("\n");
+#else
+ win32_operating_system_info();
+ win32_hardware_info();
+#endif
+
+ /* Fluent Bit */
+ printf("[Built Plugins]\n");
+ print_key("Inputs");
+ mk_list_foreach(head, &config->in_plugins) {
+ in = mk_list_entry(head, struct flb_input_plugin, _head);
+ printf("%s ", in->name);
+ }
+ printf("\n");
+
+ print_key("Filters");
+ mk_list_foreach(head, &config->filter_plugins) {
+ filter = mk_list_entry(head, struct flb_filter_plugin, _head);
+ printf("%s ", filter->name);
+ }
+ printf("\n");
+
+ print_key("Outputs");
+ mk_list_foreach(head, &config->out_plugins) {
+ out = mk_list_entry(head, struct flb_output_plugin, _head);
+ printf("%s ", out->name);
+ }
+ printf("\n");
+
+ /* Runtime configuration, what do the Engine have before to start */
+ printf("\n");
+
+ /* Config: [SERVER] */
+ printf("[SERVER] Runtime configuration\n");
+ printf(" Flush\t\t%f\n", config->flush);
+ printf(" Daemon\t\t%s\n", config->daemon ? "On": "Off");
+ printf(" Log_Level\t\t%s\n", log_level(config->verbose));
+ printf("\n");
+
+ /* Config: [INPUT] */
+ mk_list_foreach(head, &config->inputs) {
+ ins_in = mk_list_entry(head, struct flb_input_instance, _head);
+ printf("[INPUT] Instance\n");
+ printf(" Name\t\t%s (%s, id=%i)\n", ins_in->name, ins_in->p->name,
+ ins_in->id);
+ printf(" Flags\t\t"); input_flags(ins_in->flags);
+ printf(" Coroutines\t\t%s\n", ins_in->runs_in_coroutine ? "Yes": "No");
+ if (ins_in->tag) {
+ printf(" Tag\t\t\t%s\n", ins_in->tag);
+ }
+ if (ins_in->flags & FLB_INPUT_NET) {
+ print_host(&ins_in->host);
+ }
+
+ if (ins_in->mem_buf_limit > 0) {
+ flb_utils_bytes_to_human_readable_size(ins_in->mem_buf_limit,
+ tmp, sizeof(tmp) - 1);
+ printf(" Mem_Buf_Limit\t%s\n", tmp);
+ }
+
+ print_properties(&ins_in->properties);
+
+ /* Fixed Routes */
+ if (mk_list_is_empty(&ins_in->routes) != 0) {
+ printf(" Routes\t\t");
+ mk_list_foreach(head_r, &ins_in->routes) {
+ route = mk_list_entry(head_r, struct flb_router_path, _head);
+ printf("%s ", route->ins->name);
+ }
+ printf("\n");
+ }
+ printf("\n");
+ }
+
+ /* Config: [FILTER] */
+ mk_list_foreach(head, &config->filters) {
+ ins_filter = mk_list_entry(head, struct flb_filter_instance, _head);
+ printf("[FILTER] Instance\n");
+ printf(" Name\t\t%s (%s, id=%i)\n", ins_filter->name, ins_filter->p->name,
+ ins_filter->id);
+ printf(" Match\t\t%s\n", ins_filter->match);
+ print_properties(&ins_filter->properties);
+ }
+ printf("\n");
+
+ /* Config: [OUTPUT] */
+ mk_list_foreach(head, &config->outputs) {
+ ins_out = mk_list_entry(head, struct flb_output_instance, _head);
+ printf("[OUTPUT] Instance\n");
+ printf(" Name\t\t%s (%s, id=%" PRIu64 ")\n", ins_out->name, ins_out->p->name,
+ (uint64_t) ins_out->id);
+ printf(" Match\t\t%s\n", ins_out->match);
+
+#ifdef FLB_HAVE_TLS
+ printf(" TLS Active\t\t%s\n", ins_out->use_tls ? "Yes" : "No");
+ if (ins_out->use_tls == FLB_TRUE) {
+ printf(" TLS.Verify\t\t%s\n", ins_out->tls_verify ? "On": "Off");
+ printf(" TLS.Ca_File\t\t%s\n", get_str(ins_out->tls_ca_file));
+ printf(" TLS.Crt_File\t%s\n", get_str(ins_out->tls_crt_file));
+ printf(" TLS.Key_File\t%s\n", get_str(ins_out->tls_key_file));
+ printf(" TLS.Key_Passwd\t%s\n",
+ ins_out->tls_key_passwd ? "*****" : "(not set)");
+ }
+#endif
+ if (ins_out->retry_limit == FLB_OUT_RETRY_UNLIMITED) {
+ printf(" Retry Limit\t\tno limit\n");
+ }
+ else {
+ printf(" Retry Limit\t\t%i\n", ins_out->retry_limit);
+ }
+ print_host(&ins_out->host);
+ print_properties(&ins_out->properties);
+ printf("\n");
+ }
+
+ return 0;
+}
diff --git a/fluent-bit/src/flb_sqldb.c b/fluent-bit/src/flb_sqldb.c
new file mode 100644
index 000000000..6633501e0
--- /dev/null
+++ b/fluent-bit/src/flb_sqldb.c
@@ -0,0 +1,132 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_sqldb.h>
+
+/*
+ * Open or create a new database. Note that this function will always try to
+ * use an open database and share it handler in as a new context.
+ */
+struct flb_sqldb *flb_sqldb_open(const char *path, const char *desc,
+ struct flb_config *config)
+{
+ int ret;
+ struct mk_list *head;
+ struct flb_sqldb *db_temp = NULL;
+ struct flb_sqldb *db;
+ sqlite3 *sdb = NULL;
+
+ db = flb_malloc(sizeof(struct flb_sqldb));
+ if (!db) {
+ flb_errno();
+ return NULL;
+ }
+ db->parent = NULL;
+ db->shared = FLB_FALSE;
+ db->users = 0;
+
+ /*
+ * The database handler can be shared across different instances of
+ * Fluent Bit. Before to open a new one, try to find a database that
+ * is already open.
+ */
+ mk_list_foreach(head, &config->sqldb_list) {
+ db_temp = mk_list_entry(head, struct flb_sqldb, _head);
+
+ /* Only lookup for original database, not contexts already shared */
+ if (db_temp->shared == FLB_TRUE) {
+ continue;
+ }
+
+ if (strcmp(db_temp->path, path) == 0) {
+ break;
+ }
+ db_temp = NULL;
+ }
+
+ /* Found a database that can be shared */
+ if (db_temp) {
+ /* Increase users counter */
+ db_temp->users++;
+
+ /* Setup the new context */
+ db->handler = db_temp->handler;
+ db->shared = FLB_TRUE;
+ db->parent = db_temp;
+ }
+ else {
+ ret = sqlite3_open(path, &sdb);
+ if (ret) {
+ flb_error("[sqldb] cannot open database %s", path);
+ flb_free(db);
+ return NULL;
+ }
+ db->handler = sdb;
+ }
+
+ db->path = flb_strdup(path);
+ db->desc = flb_strdup(desc);
+ mk_list_add(&db->_head, &config->sqldb_list);
+
+ return db;
+}
+
+int flb_sqldb_close(struct flb_sqldb *db)
+{
+ struct flb_sqldb *parent;
+
+ if (db->shared == FLB_TRUE) {
+ parent = db->parent;
+ parent->users--;
+ }
+ else {
+ sqlite3_close(db->handler);
+ }
+ mk_list_del(&db->_head);
+ flb_free(db->path);
+ flb_free(db->desc);
+ flb_free(db);
+
+ return 0;
+}
+
+int flb_sqldb_query(struct flb_sqldb *db, const char *sql,
+ int (*callback) (void *, int, char **, char **),
+ void *data)
+{
+ int ret;
+ char *err_msg = NULL;
+
+ ret = sqlite3_exec(db->handler, sql, callback, data, &err_msg);
+ if (ret != SQLITE_OK) {
+ flb_error("[sqldb] error=%s", err_msg);
+ sqlite3_free(err_msg);
+ return FLB_ERROR;
+ }
+
+ return FLB_OK;
+}
+
+int64_t flb_sqldb_last_id(struct flb_sqldb *db)
+{
+ return sqlite3_last_insert_rowid(db->handler);
+}
diff --git a/fluent-bit/src/flb_storage.c b/fluent-bit/src/flb_storage.c
new file mode 100644
index 000000000..a89a4c4b5
--- /dev/null
+++ b/fluent-bit/src/flb_storage.c
@@ -0,0 +1,718 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_storage.h>
+#include <fluent-bit/flb_scheduler.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_http_server.h>
+
+static struct cmt *metrics_context_create(struct flb_storage_metrics *sm)
+{
+ struct cmt *cmt;
+
+ cmt = cmt_create();
+ if (!cmt) {
+ return NULL;
+ }
+
+ sm->cmt_chunks = cmt_gauge_create(cmt,
+ "fluentbit", "storage", "chunks",
+ "Total number of chunks in the storage layer.",
+ 0, (char *[]) { NULL });
+
+ sm->cmt_mem_chunks = cmt_gauge_create(cmt,
+ "fluentbit", "storage", "mem_chunks",
+ "Total number of memory chunks.",
+ 0, (char *[]) { NULL });
+
+ sm->cmt_fs_chunks = cmt_gauge_create(cmt,
+ "fluentbit", "storage", "fs_chunks",
+ "Total number of filesystem chunks.",
+ 0, (char *[]) { NULL });
+
+ sm->cmt_fs_chunks_up = cmt_gauge_create(cmt,
+ "fluentbit", "storage", "fs_chunks_up",
+ "Total number of filesystem chunks up in memory.",
+ 0, (char *[]) { NULL });
+
+ sm->cmt_fs_chunks_down = cmt_gauge_create(cmt,
+ "fluentbit", "storage", "fs_chunks_down",
+ "Total number of filesystem chunks down.",
+ 0, (char *[]) { NULL });
+
+ return cmt;
+}
+
+
+/* This function collect the 'global' metrics of the storage layer (cmetrics) */
+int flb_storage_metrics_update(struct flb_config *ctx, struct flb_storage_metrics *sm)
+{
+ uint64_t ts;
+ struct cio_stats st;
+
+ /* Retrieve general stats from the storage layer */
+ cio_stats_get(ctx->cio, &st);
+
+ ts = cfl_time_now();
+
+ cmt_gauge_set(sm->cmt_chunks, ts, st.chunks_total, 0, NULL);
+ cmt_gauge_set(sm->cmt_mem_chunks, ts, st.chunks_mem, 0, NULL);
+ cmt_gauge_set(sm->cmt_fs_chunks, ts, st.chunks_fs, 0, NULL);
+ cmt_gauge_set(sm->cmt_fs_chunks_up, ts, st.chunks_fs_up, 0, NULL);
+ cmt_gauge_set(sm->cmt_fs_chunks_down, ts, st.chunks_fs_down, 0, NULL);
+
+ return 0;
+}
+
+static void metrics_append_general(msgpack_packer *mp_pck,
+ struct flb_config *ctx,
+ struct flb_storage_metrics *sm)
+{
+ struct cio_stats storage_st;
+
+ /* Retrieve general stats from the storage layer */
+ cio_stats_get(ctx->cio, &storage_st);
+
+ msgpack_pack_str(mp_pck, 13);
+ msgpack_pack_str_body(mp_pck, "storage_layer", 13);
+ msgpack_pack_map(mp_pck, 1);
+
+ /* Chunks */
+ msgpack_pack_str(mp_pck, 6);
+ msgpack_pack_str_body(mp_pck, "chunks", 6);
+ msgpack_pack_map(mp_pck, 5);
+
+ /* chunks['total_chunks'] */
+ msgpack_pack_str(mp_pck, 12);
+ msgpack_pack_str_body(mp_pck, "total_chunks", 12);
+ msgpack_pack_uint64(mp_pck, storage_st.chunks_total);
+
+ /* chunks['mem_chunks'] */
+ msgpack_pack_str(mp_pck, 10);
+ msgpack_pack_str_body(mp_pck, "mem_chunks", 10);
+ msgpack_pack_uint64(mp_pck, storage_st.chunks_mem);
+
+ /* chunks['fs_chunks'] */
+ msgpack_pack_str(mp_pck, 9);
+ msgpack_pack_str_body(mp_pck, "fs_chunks", 9);
+ msgpack_pack_uint64(mp_pck, storage_st.chunks_fs);
+
+ /* chunks['fs_up_chunks'] */
+ msgpack_pack_str(mp_pck, 12);
+ msgpack_pack_str_body(mp_pck, "fs_chunks_up", 12);
+ msgpack_pack_uint64(mp_pck, storage_st.chunks_fs_up);
+
+ /* chunks['fs_down_chunks'] */
+ msgpack_pack_str(mp_pck, 14);
+ msgpack_pack_str_body(mp_pck, "fs_chunks_down", 14);
+ msgpack_pack_uint64(mp_pck, storage_st.chunks_fs_down);
+}
+
+static void metrics_append_input(msgpack_packer *mp_pck,
+ struct flb_config *ctx,
+ struct flb_storage_metrics *sm)
+{
+ int len;
+ int ret;
+ uint64_t ts;
+ const char *tmp;
+ char buf[32];
+ ssize_t size;
+ size_t total_chunks;
+
+ /* chunks */
+ int up;
+ int down;
+ int busy;
+ char *name;
+ ssize_t busy_size;
+ struct mk_list *head;
+ struct mk_list *h_chunks;
+ struct flb_input_instance *i;
+ struct flb_input_chunk *ic;
+
+ /*
+ * DISCLAIMER: This interface will be deprecated once we extend Chunk I/O
+ * stats per stream.
+ *
+ * For now and to avoid duplication of iterating chunks we are adding the
+ * metrics counting for CMetrics inside the same logic for the old code.
+ */
+
+ msgpack_pack_str(mp_pck, 12);
+ msgpack_pack_str_body(mp_pck, "input_chunks", 12);
+ msgpack_pack_map(mp_pck, mk_list_size(&ctx->inputs));
+
+ /* current time */
+ ts = cfl_time_now();
+
+ /* Input Plugins Ingestion */
+ mk_list_foreach(head, &ctx->inputs) {
+ i = mk_list_entry(head, struct flb_input_instance, _head);
+
+ name = (char *) flb_input_name(i);
+ total_chunks = mk_list_size(&i->chunks);
+
+ tmp = flb_input_name(i);
+ len = strlen(tmp);
+
+ msgpack_pack_str(mp_pck, len);
+ msgpack_pack_str_body(mp_pck, tmp, len);
+
+ /* Map for 'status' and 'chunks' */
+ msgpack_pack_map(mp_pck, 2);
+
+ /*
+ * Status
+ * ======
+ */
+ msgpack_pack_str(mp_pck, 6);
+ msgpack_pack_str_body(mp_pck, "status", 6);
+
+ /* 'status' map has 2 keys: overlimit and chunks */
+ msgpack_pack_map(mp_pck, 3);
+
+ /* status['overlimit'] */
+ msgpack_pack_str(mp_pck, 9);
+ msgpack_pack_str_body(mp_pck, "overlimit", 9);
+
+
+ /* CMetrics */
+ ret = FLB_FALSE;
+ if (i->mem_buf_limit > 0) {
+ if (i->mem_chunks_size >= i->mem_buf_limit) {
+ ret = FLB_TRUE;
+ }
+ }
+ if (ret == FLB_TRUE) {
+ /* cmetrics */
+ cmt_gauge_set(i->cmt_storage_overlimit, ts, 1,
+ 1, (char *[]) {name});
+
+ /* old code */
+ msgpack_pack_true(mp_pck);
+ }
+ else {
+ /* cmetrics */
+ cmt_gauge_set(i->cmt_storage_overlimit, ts, 0,
+ 1, (char *[]) {name});
+
+ /* old code */
+ msgpack_pack_false(mp_pck);
+ }
+
+ /* fluentbit_storage_memory_bytes */
+ cmt_gauge_set(i->cmt_storage_memory_bytes, ts, i->mem_chunks_size,
+ 1, (char *[]) {name});
+
+ /* status['mem_size'] */
+ msgpack_pack_str(mp_pck, 8);
+ msgpack_pack_str_body(mp_pck, "mem_size", 8);
+
+ /* Current memory size used based on last ingestion */
+ flb_utils_bytes_to_human_readable_size(i->mem_chunks_size,
+ buf, sizeof(buf) - 1);
+ len = strlen(buf);
+ msgpack_pack_str(mp_pck, len);
+ msgpack_pack_str_body(mp_pck, buf, len);
+
+ /* status['mem_limit'] */
+ msgpack_pack_str(mp_pck, 9);
+ msgpack_pack_str_body(mp_pck, "mem_limit", 9);
+
+ flb_utils_bytes_to_human_readable_size(i->mem_buf_limit,
+ buf, sizeof(buf) - 1);
+ len = strlen(buf);
+ msgpack_pack_str(mp_pck, len);
+ msgpack_pack_str_body(mp_pck, buf, len);
+
+ /*
+ * Chunks
+ * ======
+ */
+
+ /* cmetrics */
+ cmt_gauge_set(i->cmt_storage_chunks, ts, total_chunks,
+ 1, (char *[]) {name});
+
+
+ /* old code */
+ msgpack_pack_str(mp_pck, 6);
+ msgpack_pack_str_body(mp_pck, "chunks", 6);
+
+ /* 'chunks' has 3 keys: total, up, down, busy and busy_size */
+ msgpack_pack_map(mp_pck, 5);
+
+ /* chunks['total_chunks'] */
+ msgpack_pack_str(mp_pck, 5);
+ msgpack_pack_str_body(mp_pck, "total", 5);
+ msgpack_pack_uint64(mp_pck, total_chunks);
+
+ /*
+ * chunks Details: chunks marked as 'busy' are 'locked' since they are in
+ * a 'flush' state. No more data can be appended to a busy chunk.
+ */
+ busy = 0;
+ busy_size = 0;
+
+ /* up/down */
+ up = 0;
+ down = 0;
+
+ /* Iterate chunks for the input instance in question */
+ mk_list_foreach(h_chunks, &i->chunks) {
+ ic = mk_list_entry(h_chunks, struct flb_input_chunk, _head);
+ if (ic->busy == FLB_TRUE) {
+ busy++;
+ size = cio_chunk_get_content_size(ic->chunk);
+ if (size >= 0) {
+ busy_size += size;
+ }
+ }
+
+ if (cio_chunk_is_up(ic->chunk) == CIO_TRUE) {
+ up++;
+ }
+ else {
+ down++;
+ }
+
+ }
+
+ /* fluentbit_storage_chunks_up */
+ cmt_gauge_set(i->cmt_storage_chunks_up, ts, up,
+ 1, (char *[]) {name});
+
+ /* chunks['up'] */
+ msgpack_pack_str(mp_pck, 2);
+ msgpack_pack_str_body(mp_pck, "up", 2);
+ msgpack_pack_uint64(mp_pck, up);
+
+ /* fluentbit_storage_chunks_down */
+ cmt_gauge_set(i->cmt_storage_chunks_down, ts, down,
+ 1, (char *[]) {name});
+
+ /* chunks['down'] */
+ msgpack_pack_str(mp_pck, 4);
+ msgpack_pack_str_body(mp_pck, "down", 4);
+ msgpack_pack_uint64(mp_pck, down);
+
+ /* fluentbit_storage_chunks_busy */
+ cmt_gauge_set(i->cmt_storage_chunks_busy, ts, busy,
+ 1, (char *[]) {name});
+
+ /* chunks['busy'] */
+ msgpack_pack_str(mp_pck, 4);
+ msgpack_pack_str_body(mp_pck, "busy", 4);
+ msgpack_pack_uint64(mp_pck, busy);
+
+ /* fluentbit_storage_chunks_busy_size */
+ cmt_gauge_set(i->cmt_storage_chunks_busy_bytes, ts, busy_size,
+ 1, (char *[]) {name});
+
+ /* chunks['busy_size'] */
+ msgpack_pack_str(mp_pck, 9);
+ msgpack_pack_str_body(mp_pck, "busy_size", 9);
+
+ flb_utils_bytes_to_human_readable_size(busy_size, buf, sizeof(buf) - 1);
+ len = strlen(buf);
+ msgpack_pack_str(mp_pck, len);
+ msgpack_pack_str_body(mp_pck, buf, len);
+ }
+}
+
+static void cb_storage_metrics_collect(struct flb_config *ctx, void *data)
+{
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+
+ /* Prepare new outgoing buffer */
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ /* Pack main map and append relevant data */
+ msgpack_pack_map(&mp_pck, 2);
+ metrics_append_general(&mp_pck, ctx, data);
+ metrics_append_input(&mp_pck, ctx, data);
+
+#ifdef FLB_HAVE_HTTP_SERVER
+ if (ctx->http_server == FLB_TRUE && ctx->storage_metrics == FLB_TRUE) {
+ flb_hs_push_storage_metrics(ctx->http_ctx, mp_sbuf.data, mp_sbuf.size);
+ }
+#endif
+ msgpack_sbuffer_destroy(&mp_sbuf);
+}
+
+struct flb_storage_metrics *flb_storage_metrics_create(struct flb_config *ctx)
+{
+ int ret;
+ struct flb_storage_metrics *sm;
+
+ sm = flb_calloc(1, sizeof(struct flb_storage_metrics));
+ if (!sm) {
+ flb_errno();
+ return NULL;
+ }
+ sm->cmt = metrics_context_create(sm);
+ if(!sm->cmt) {
+ flb_free(sm);
+ return NULL;
+ }
+
+ ret = flb_sched_timer_cb_create(ctx->sched, FLB_SCHED_TIMER_CB_PERM, 5000,
+ cb_storage_metrics_collect,
+ ctx->storage_metrics_ctx, NULL);
+ if (ret == -1) {
+ flb_error("[storage metrics] cannot create timer to collect metrics");
+ flb_free(sm);
+ return NULL;
+ }
+
+ return sm;
+}
+
+static int sort_chunk_cmp(const void *a_arg, const void *b_arg)
+{
+ char *p;
+ struct cio_chunk *chunk_a = *(struct cio_chunk **) a_arg;
+ struct cio_chunk *chunk_b = *(struct cio_chunk **) b_arg;
+ struct timespec tm_a;
+ struct timespec tm_b;
+
+ /* Scan Chunk A */
+ p = strchr(chunk_a->name, '-');
+ if (!p) {
+ return -1;
+ }
+ p++;
+
+ sscanf(p, "%lu.%lu.flb", &tm_a.tv_sec, &tm_a.tv_nsec);
+
+ /* Scan Chunk B */
+ p = strchr(chunk_b->name, '-');
+ if (!p) {
+ return -1;
+ }
+ p++;
+ sscanf(p, "%lu.%lu.flb", &tm_b.tv_sec, &tm_b.tv_nsec);
+
+ /* Compare */
+ if (tm_a.tv_sec != tm_b.tv_sec) {
+ if (tm_a.tv_sec > tm_b.tv_sec) {
+ return 1;
+ }
+ else {
+ return -1;
+ }
+ }
+ else {
+ if (tm_a.tv_nsec > tm_b.tv_nsec) {
+ return 1;
+ }
+ else if (tm_a.tv_nsec < tm_b.tv_nsec) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void print_storage_info(struct flb_config *ctx, struct cio_ctx *cio)
+{
+ char *type;
+ char *sync;
+ char *checksum;
+ struct flb_input_instance *in;
+
+ if (cio->options.root_path) {
+ type = "memory+filesystem";
+ }
+ else {
+ type = "memory";
+ }
+
+ if (cio->options.flags & CIO_FULL_SYNC) {
+ sync = "full";
+ }
+ else {
+ sync = "normal";
+ }
+
+ if (cio->options.flags & CIO_CHECKSUM) {
+ checksum = "on";
+ }
+ else {
+ checksum = "off";
+ }
+
+ flb_info("[storage] ver=%s, type=%s, sync=%s, checksum=%s, max_chunks_up=%i",
+ cio_version(), type, sync, checksum, ctx->storage_max_chunks_up);
+
+ /* Storage input plugin */
+ if (ctx->storage_input_plugin) {
+ in = (struct flb_input_instance *) ctx->storage_input_plugin;
+ flb_info("[storage] backlog input plugin: %s", in->name);
+ }
+}
+
+static int log_cb(struct cio_ctx *ctx, int level, const char *file, int line,
+ char *str)
+{
+ if (level == CIO_LOG_ERROR) {
+ flb_error("[storage] %s", str);
+ }
+ else if (level == CIO_LOG_WARN) {
+ flb_warn("[storage] %s", str);
+ }
+ else if (level == CIO_LOG_INFO) {
+ flb_info("[storage] %s", str);
+ }
+ else if (level == CIO_LOG_DEBUG) {
+ flb_debug("[storage] %s", str);
+ }
+
+ return 0;
+}
+
+int flb_storage_input_create(struct cio_ctx *cio,
+ struct flb_input_instance *in)
+{
+ int cio_storage_type;
+ struct flb_storage_input *si;
+ struct cio_stream *stream;
+
+ /* storage config: get stream type */
+ if (in->storage_type == -1) {
+ in->storage_type = FLB_STORAGE_MEM;
+ }
+
+ if (in->storage_type == FLB_STORAGE_FS && cio->options.root_path == NULL) {
+ flb_error("[storage] instance '%s' requested filesystem storage "
+ "but no filesystem path was defined.",
+ flb_input_name(in));
+ return -1;
+ }
+
+ /*
+ * The input instance can define it owns storage type which is based on some
+ * specific Chunk I/O storage type. We handle the proper initialization here.
+ */
+ cio_storage_type = in->storage_type;
+ if (in->storage_type == FLB_STORAGE_MEMRB) {
+ cio_storage_type = FLB_STORAGE_MEM;
+ }
+
+ /* Check for duplicates */
+ stream = cio_stream_get(cio, in->name);
+ if (!stream) {
+ /* create stream for input instance */
+ stream = cio_stream_create(cio, in->name, cio_storage_type);
+ if (!stream) {
+ flb_error("[storage] cannot create stream for instance %s",
+ in->name);
+ return -1;
+ }
+ }
+
+ /* allocate storage context for the input instance */
+ si = flb_malloc(sizeof(struct flb_storage_input));
+ if (!si) {
+ flb_errno();
+ return -1;
+ }
+
+ si->stream = stream;
+ si->cio = cio;
+ si->type = in->storage_type;
+ in->storage = si;
+
+ return 0;
+}
+
+void flb_storage_input_destroy(struct flb_input_instance *in)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_input_chunk *ic;
+
+ /* Save current temporary data and destroy chunk references */
+ mk_list_foreach_safe(head, tmp, &in->chunks) {
+ ic = mk_list_entry(head, struct flb_input_chunk, _head);
+ flb_input_chunk_destroy(ic, FLB_FALSE);
+ }
+
+ flb_free(in->storage);
+ in->storage = NULL;
+}
+
+static int storage_contexts_create(struct flb_config *config)
+{
+ int c = 0;
+ int ret;
+ struct mk_list *head;
+ struct flb_input_instance *in;
+
+ /* Iterate each input instance and create a stream for it */
+ mk_list_foreach(head, &config->inputs) {
+ in = mk_list_entry(head, struct flb_input_instance, _head);
+ ret = flb_storage_input_create(config->cio, in);
+ if (ret == -1) {
+ flb_error("[storage] could not create storage for instance: %s",
+ in->name);
+ return -1;
+ }
+ c++;
+ }
+
+ return c;
+}
+
+int flb_storage_create(struct flb_config *ctx)
+{
+ int ret;
+ int flags;
+ struct flb_input_instance *in = NULL;
+ struct cio_ctx *cio;
+ struct cio_options opts = {0};
+
+ /* always use read/write mode */
+ flags = CIO_OPEN;
+
+ /* if explicitly stated any irrecoverably corrupted
+ * chunks will be deleted */
+ if (ctx->storage_del_bad_chunks) {
+ flags |= CIO_DELETE_IRRECOVERABLE;
+ }
+
+ /* synchronization mode */
+ if (ctx->storage_sync) {
+ if (strcasecmp(ctx->storage_sync, "normal") == 0) {
+ /* do nothing, keep the default */
+ }
+ else if (strcasecmp(ctx->storage_sync, "full") == 0) {
+ flags |= CIO_FULL_SYNC;
+ }
+ else {
+ flb_error("[storage] invalid synchronization mode");
+ return -1;
+ }
+ }
+
+ /* checksum */
+ if (ctx->storage_checksum == FLB_TRUE) {
+ flags |= CIO_CHECKSUM;
+ }
+
+ /* file trimming */
+ if (ctx->storage_trim_files == FLB_TRUE) {
+ flags |= CIO_TRIM_FILES;
+ }
+
+ /* chunkio options */
+ cio_options_init(&opts);
+
+ opts.root_path = ctx->storage_path;
+ opts.flags = flags;
+ opts.log_cb = log_cb;
+ opts.log_level = CIO_LOG_INFO;
+
+ /* Create chunkio context */
+ cio = cio_create(&opts);
+ if (!cio) {
+ flb_error("[storage] error initializing storage engine");
+ return -1;
+ }
+ ctx->cio = cio;
+
+ /* Set Chunk I/O maximum number of chunks up */
+ if (ctx->storage_max_chunks_up == 0) {
+ ctx->storage_max_chunks_up = FLB_STORAGE_MAX_CHUNKS_UP;
+ }
+ cio_set_max_chunks_up(ctx->cio, ctx->storage_max_chunks_up);
+
+ /* Load content from the file system if any */
+ ret = cio_load(ctx->cio, NULL);
+ if (ret == -1) {
+ flb_error("[storage] error scanning root path content: %s",
+ ctx->storage_path);
+ cio_destroy(ctx->cio);
+ return -1;
+ }
+
+ /* Sort chunks */
+ cio_qsort(ctx->cio, sort_chunk_cmp);
+
+ /*
+ * If we have a filesystem storage path, create an instance of the
+ * storage_backlog input plugin to consume any possible pending
+ * chunks.
+ */
+ if (ctx->storage_path) {
+ in = flb_input_new(ctx, "storage_backlog", cio, FLB_FALSE);
+ if (!in) {
+ flb_error("[storage] cannot init storage backlog input plugin");
+ cio_destroy(cio);
+ ctx->cio = NULL;
+ return -1;
+ }
+ ctx->storage_input_plugin = in;
+
+ /* Set a queue memory limit */
+ if (!ctx->storage_bl_mem_limit) {
+ ctx->storage_bl_mem_limit = flb_strdup(FLB_STORAGE_BL_MEM_LIMIT);
+ }
+ }
+
+ /* Create streams for input instances */
+ ret = storage_contexts_create(ctx);
+ if (ret == -1) {
+ return -1;
+ }
+
+ /* print storage info */
+ print_storage_info(ctx, cio);
+
+ return 0;
+}
+
+void flb_storage_destroy(struct flb_config *ctx)
+{
+ struct cio_ctx *cio;
+ struct flb_storage_metrics *sm;
+
+ /* Destroy Chunk I/O context */
+ cio = (struct cio_ctx *) ctx->cio;
+
+ if (!cio) {
+ return;
+ }
+
+ sm = ctx->storage_metrics_ctx;
+ if (ctx->storage_metrics == FLB_TRUE && sm != NULL) {
+ cmt_destroy(sm->cmt);
+ flb_free(sm);
+ ctx->storage_metrics_ctx = NULL;
+ }
+
+ cio_destroy(cio);
+ ctx->cio = NULL;
+}
diff --git a/fluent-bit/src/flb_strptime.c b/fluent-bit/src/flb_strptime.c
new file mode 100644
index 000000000..492a6ee74
--- /dev/null
+++ b/fluent-bit/src/flb_strptime.c
@@ -0,0 +1,750 @@
+/* $OpenBSD: strptime.c,v 1.30 2019/05/12 12:49:52 schwarze Exp $ */
+/* $NetBSD: strptime.c,v 1.12 1998/01/20 21:39:40 mycroft Exp $ */
+/*-
+ * Copyright (c) 1997, 1998, 2005, 2008 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code was contributed to The NetBSD Foundation by Klaus Klein.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This file provides a portable implementation of strptime(2), based
+ * on the work of OpenSBD project. Since various platforms implement
+ * strptime differently, this one should work as a fallback.
+ */
+
+#include <ctype.h>
+#include <locale.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <fluent-bit/flb_compat.h>
+#include <fluent-bit/flb_langinfo.h>
+#include <fluent-bit/flb_time.h>
+
+#define _ctloc(x) (nl_langinfo(x))
+
+/*
+ * We do not implement alternate representations. However, we always
+ * check whether a given modifier is allowed for a certain conversion.
+ */
+#define _ALT_E 0x01
+#define _ALT_O 0x02
+#define _LEGAL_ALT(x) { if (alt_format & ~(x)) return (0); }
+
+/*
+ * Copied from libc/time/private.h and libc/time/tzfile.h
+ */
+#define TM_YEAR_BASE 1900
+#define DAYSPERNYEAR 365
+#define DAYSPERLYEAR 366
+#define DAYSPERWEEK 7
+#define MONSPERYEAR 12
+#define EPOCH_YEAR 1970
+#define EPOCH_WDAY 4 /* Thursday */
+#define SECSPERHOUR 3600
+#define SECSPERMIN 60
+
+#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
+
+/*
+ * We keep track of some of the fields we set in order to compute missing ones.
+ */
+#define FIELD_TM_MON (1 << 0)
+#define FIELD_TM_MDAY (1 << 1)
+#define FIELD_TM_WDAY (1 << 2)
+#define FIELD_TM_YDAY (1 << 3)
+#define FIELD_TM_YEAR (1 << 4)
+
+static char gmt[] = { "GMT" };
+static char utc[] = { "UTC" };
+/* RFC-822/RFC-2822 */
+static const char * const nast[5] = {
+ "EST", "CST", "MST", "PST", "\0\0\0"
+};
+static const char * const nadt[5] = {
+ "EDT", "CDT", "MDT", "PDT", "\0\0\0"
+};
+
+static const int mon_lengths[2][MONSPERYEAR] = {
+ { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+ { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
+};
+
+static nl_item day[] = {
+ DAY_1, DAY_2, DAY_3, DAY_4, DAY_5, DAY_6, DAY_7
+};
+
+static nl_item mon[] = {
+ MON_1, MON_2, MON_3, MON_4, MON_5, MON_6, MON_7, MON_8, MON_9,
+ MON_10, MON_11, MON_12
+};
+
+static nl_item abday[] = {
+ ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7
+};
+
+static nl_item abmon[] = {
+ ABMON_1, ABMON_2, ABMON_3, ABMON_4, ABMON_5, ABMON_6, ABMON_7,
+ ABMON_8, ABMON_9, ABMON_10, ABMON_11, ABMON_12
+};
+
+static int _conv_num64(const unsigned char **, int64_t *, int64_t, int64_t);
+static int _conv_num(const unsigned char **, int *, int, int);
+static int leaps_thru_end_of(const int y);
+static char *_flb_strptime(const char *, const char *, struct flb_tm *, int);
+static const u_char *_find_string(const u_char *, int *, const char * const *,
+ const char * const *, int);
+
+/*
+ * FreeBSD does not support `timezone` in time.h.
+ * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=24590
+ */
+#ifdef __FreeBSD__
+int flb_timezone(void)
+{
+ struct tm tm;
+ time_t t = 0;
+ tzset();
+ localtime_r(&t, &tm);
+ return -(tm.tm_gmtoff);
+}
+#define timezone (flb_timezone())
+#endif
+
+char *
+flb_strptime(const char *buf, const char *fmt, struct flb_tm *tm)
+{
+ return(_flb_strptime(buf, fmt, tm, 1));
+}
+
+static char *
+_flb_strptime(const char *buf, const char *fmt, struct flb_tm *tm, int initialize)
+{
+ unsigned char c;
+ const unsigned char *bp, *ep;
+ size_t len = 0;
+ int alt_format, i, offs;
+ int neg = 0;
+ static int century, relyear, fields;
+
+ if (initialize) {
+ century = TM_YEAR_BASE;
+ relyear = -1;
+ fields = 0;
+ }
+
+ bp = (const unsigned char *)buf;
+ while ((c = *fmt) != '\0') {
+ /* Clear `alternate' modifier prior to new conversion. */
+ alt_format = 0;
+
+ /* Eat up white-space. */
+ if (isspace(c)) {
+ while (isspace(*bp))
+ bp++;
+
+ fmt++;
+ continue;
+ }
+
+ /*
+ * Having increased bp we need to ensure we are not
+ * moving beyond bounds.
+ */
+ if (*bp == '\0')
+ return (NULL);
+
+ if ((c = *fmt++) != '%')
+ goto literal;
+
+
+again: switch (c = *fmt++) {
+ case '%': /* "%%" is converted to "%". */
+literal:
+ if (c != *bp++)
+ return (NULL);
+
+ break;
+
+ /*
+ * "Alternative" modifiers. Just set the appropriate flag
+ * and start over again.
+ */
+ case 'E': /* "%E?" alternative conversion modifier. */
+ _LEGAL_ALT(0);
+ alt_format |= _ALT_E;
+ goto again;
+
+ case 'O': /* "%O?" alternative conversion modifier. */
+ _LEGAL_ALT(0);
+ alt_format |= _ALT_O;
+ goto again;
+
+ /*
+ * "Complex" conversion rules, implemented through recursion.
+ */
+ case 'c': /* Date and time, using the locale's format. */
+ _LEGAL_ALT(_ALT_E);
+ if (!(bp = (const unsigned char *)_flb_strptime((const char *)bp, _ctloc(D_T_FMT), tm, 0)))
+ return (NULL);
+ break;
+
+ case 'D': /* The date as "%m/%d/%y". */
+ _LEGAL_ALT(0);
+ if (!(bp = (const unsigned char *)_flb_strptime((const char *)bp, "%m/%d/%y", tm, 0)))
+ return (NULL);
+ break;
+
+ case 'F': /* The date as "%Y-%m-%d". */
+ _LEGAL_ALT(0);
+ if (!(bp = (const unsigned char *)_flb_strptime((const char *)bp, "%Y-%m-%d", tm, 0)))
+ return (NULL);
+ continue;
+
+ case 'R': /* The time as "%H:%M". */
+ _LEGAL_ALT(0);
+ if (!(bp = (const unsigned char *)_flb_strptime((const char *)bp, "%H:%M", tm, 0)))
+ return (NULL);
+ break;
+
+ case 'r': /* The time as "%I:%M:%S %p". */
+ _LEGAL_ALT(0);
+ if (!(bp = (const unsigned char *)_flb_strptime((const char *)bp, "%I:%M:%S %p", tm, 0)))
+ return (NULL);
+ break;
+
+ case 'T': /* The time as "%H:%M:%S". */
+ _LEGAL_ALT(0);
+ if (!(bp = (const unsigned char *)_flb_strptime((const char *)bp, "%H:%M:%S", tm, 0)))
+ return (NULL);
+ break;
+
+ case 'X': /* The time, using the locale's format. */
+ _LEGAL_ALT(_ALT_E);
+ if (!(bp = (const unsigned char *)_flb_strptime((const char *)bp, _ctloc(T_FMT), tm, 0)))
+ return (NULL);
+ break;
+
+ case 'x': /* The date, using the locale's format. */
+ _LEGAL_ALT(_ALT_E);
+ if (!(bp = (const unsigned char *)_flb_strptime((const char *)bp, _ctloc(D_FMT), tm, 0)))
+ return (NULL);
+ break;
+
+ /*
+ * "Elementary" conversion rules.
+ */
+ case 'A': /* The day of week, using the locale's form. */
+ case 'a':
+ _LEGAL_ALT(0);
+ for (i = 0; i < 7; i++) {
+ /* Full name. */
+ len = strlen(_ctloc(day[i]));
+ if (strncasecmp(_ctloc(day[i]), (const char *)bp, len) == 0)
+ break;
+
+ /* Abbreviated name. */
+ len = strlen(_ctloc(abday[i]));
+ if (strncasecmp(_ctloc(abday[i]), (const char *)bp, len) == 0)
+ break;
+ }
+
+ /* Nothing matched. */
+ if (i == 7)
+ return (NULL);
+
+ tm->tm.tm_wday = i;
+ bp += len;
+ fields |= FIELD_TM_WDAY;
+ break;
+
+ case 'B': /* The month, using the locale's form. */
+ case 'b':
+ case 'h':
+ _LEGAL_ALT(0);
+ for (i = 0; i < 12; i++) {
+ /* Full name. */
+ len = strlen(_ctloc(mon[i]));
+ if (strncasecmp(_ctloc(mon[i]), (const char *)bp, len) == 0)
+ break;
+
+ /* Abbreviated name. */
+ len = strlen(_ctloc(abmon[i]));
+ if (strncasecmp(_ctloc(abmon[i]), (const char *)bp, len) == 0)
+ break;
+ }
+
+ /* Nothing matched. */
+ if (i == 12)
+ return (NULL);
+
+ tm->tm.tm_mon = i;
+ bp += len;
+ fields |= FIELD_TM_MON;
+ break;
+
+ case 'C': /* The century number. */
+ _LEGAL_ALT(_ALT_E);
+ if (!(_conv_num(&bp, &i, 0, 99)))
+ return (NULL);
+
+ century = i * 100;
+ break;
+
+ case 'e': /* The day of month. */
+ if (isspace(*bp))
+ bp++;
+ /* FALLTHROUGH */
+ case 'd':
+ _LEGAL_ALT(_ALT_O);
+ if (!(_conv_num(&bp, &tm->tm.tm_mday, 1, 31)))
+ return (NULL);
+ fields |= FIELD_TM_MDAY;
+ break;
+
+ case 'k': /* The hour (24-hour clock representation). */
+ _LEGAL_ALT(0);
+ /* FALLTHROUGH */
+ case 'H':
+ _LEGAL_ALT(_ALT_O);
+ if (!(_conv_num(&bp, &tm->tm.tm_hour, 0, 23)))
+ return (NULL);
+ break;
+
+ case 'l': /* The hour (12-hour clock representation). */
+ _LEGAL_ALT(0);
+ /* FALLTHROUGH */
+ case 'I':
+ _LEGAL_ALT(_ALT_O);
+ if (!(_conv_num(&bp, &tm->tm.tm_hour, 1, 12)))
+ return (NULL);
+ break;
+
+ case 'j': /* The day of year. */
+ _LEGAL_ALT(0);
+ if (!(_conv_num(&bp, &tm->tm.tm_yday, 1, 366)))
+ return (NULL);
+ tm->tm.tm_yday--;
+ fields |= FIELD_TM_YDAY;
+ break;
+
+ case 'M': /* The minute. */
+ _LEGAL_ALT(_ALT_O);
+ if (!(_conv_num(&bp, &tm->tm.tm_min, 0, 59)))
+ return (NULL);
+ break;
+
+ case 'm': /* The month. */
+ _LEGAL_ALT(_ALT_O);
+ if (!(_conv_num(&bp, &tm->tm.tm_mon, 1, 12)))
+ return (NULL);
+ tm->tm.tm_mon--;
+ fields |= FIELD_TM_MON;
+ break;
+
+ case 'p': /* The locale's equivalent of AM/PM. */
+ _LEGAL_ALT(0);
+ /* AM? */
+ len = strlen(_ctloc(AM_STR));
+ if (strncasecmp(_ctloc(AM_STR), (const char *)bp, len) == 0) {
+ if (tm->tm.tm_hour > 12) /* i.e., 13:00 AM ?! */
+ return (NULL);
+ else if (tm->tm.tm_hour == 12)
+ tm->tm.tm_hour = 0;
+
+ bp += len;
+ break;
+ }
+ /* PM? */
+ len = strlen(_ctloc(PM_STR));
+ if (strncasecmp(_ctloc(PM_STR), (const char *)bp, len) == 0) {
+ if (tm->tm.tm_hour > 12) /* i.e., 13:00 PM ?! */
+ return (NULL);
+ else if (tm->tm.tm_hour < 12)
+ tm->tm.tm_hour += 12;
+
+ bp += len;
+ break;
+ }
+
+ /* Nothing matched. */
+ return (NULL);
+
+ case 'S': /* The seconds. */
+ _LEGAL_ALT(_ALT_O);
+ if (!(_conv_num(&bp, &tm->tm.tm_sec, 0, 60)))
+ return (NULL);
+ break;
+ case 's': /* Seconds since epoch */
+ {
+ int64_t i64;
+ if (!(_conv_num64(&bp, &i64, 0, INT64_MAX)))
+ return (NULL);
+ if (!gmtime_r((const time_t *) &i64, &tm->tm))
+ return (NULL);
+ fields = 0xffff; /* everything */
+ }
+ break;
+ case 'U': /* The week of year, beginning on sunday. */
+ case 'W': /* The week of year, beginning on monday. */
+ _LEGAL_ALT(_ALT_O);
+ /*
+ * XXX This is bogus, as we can not assume any valid
+ * information present in the tm structure at this
+ * point to calculate a real value, so just check the
+ * range for now.
+ */
+ if (!(_conv_num(&bp, &i, 0, 53)))
+ return (NULL);
+ break;
+
+ case 'w': /* The day of week, beginning on sunday. */
+ _LEGAL_ALT(_ALT_O);
+ if (!(_conv_num(&bp, &tm->tm.tm_wday, 0, 6)))
+ return (NULL);
+ fields |= FIELD_TM_WDAY;
+ break;
+
+ case 'u': /* The day of week, monday = 1. */
+ _LEGAL_ALT(_ALT_O);
+ if (!(_conv_num(&bp, &i, 1, 7)))
+ return (NULL);
+ tm->tm.tm_wday = i % 7;
+ fields |= FIELD_TM_WDAY;
+ continue;
+
+ case 'g': /* The year corresponding to the ISO week
+ * number but without the century.
+ */
+ if (!(_conv_num(&bp, &i, 0, 99)))
+ return (NULL);
+ continue;
+
+ case 'G': /* The year corresponding to the ISO week
+ * number with century.
+ */
+ do
+ bp++;
+ while (isdigit(*bp));
+ continue;
+
+ case 'V': /* The ISO 8601:1988 week number as decimal */
+ if (!(_conv_num(&bp, &i, 0, 53)))
+ return (NULL);
+ continue;
+
+ case 'Y': /* The year. */
+ _LEGAL_ALT(_ALT_E);
+ if (!(_conv_num(&bp, &i, 0, 9999)))
+ return (NULL);
+
+ relyear = -1;
+ tm->tm.tm_year = i - TM_YEAR_BASE;
+ fields |= FIELD_TM_YEAR;
+ break;
+
+ case 'y': /* The year within the century (2 digits). */
+ _LEGAL_ALT(_ALT_E | _ALT_O);
+ if (!(_conv_num(&bp, &relyear, 0, 99)))
+ return (NULL);
+ break;
+
+ case 'Z':
+ tzset();
+ if (strncmp((const char *)bp, gmt, 3) == 0) {
+ tm->tm.tm_isdst = 0;
+ flb_tm_gmtoff(tm) = 0;
+#ifdef FLB_HAVE_ZONE
+ tm->tm.tm_zone = gmt;
+#endif
+ bp += 3;
+ } else if (strncmp((const char *)bp, utc, 3) == 0) {
+ tm->tm.tm_isdst = 0;
+ flb_tm_gmtoff(tm) = 0;
+#ifdef FLB_HAVE_ZONE
+ tm->tm.tm_zone = utc;
+#endif
+ bp += 3;
+ } else {
+ ep = _find_string(bp, &i,
+ (const char * const *)tzname,
+ NULL, 2);
+ if (ep == NULL)
+ return (NULL);
+
+ tm->tm.tm_isdst = i;
+ flb_tm_gmtoff(tm) = -(timezone);
+#ifdef FLB_HAVE_ZONE
+ tm->tm.tm_zone = tzname[i];
+#endif
+ bp = ep;
+ }
+ continue;
+
+ case 'z':
+ /*
+ * We recognize all ISO 8601 formats:
+ * Z = Zulu time/UTC
+ * [+-]hhmm
+ * [+-]hh:mm
+ * [+-]hh
+ * We recognize all RFC-822/RFC-2822 formats:
+ * UT|GMT
+ * North American : UTC offsets
+ * E[DS]T = Eastern : -4 | -5
+ * C[DS]T = Central : -5 | -6
+ * M[DS]T = Mountain: -6 | -7
+ * P[DS]T = Pacific : -7 | -8
+ */
+ while (isspace(*bp))
+ bp++;
+
+ switch (*bp++) {
+ case 'G':
+ if (*bp++ != 'M')
+ return NULL;
+ /*FALLTHROUGH*/
+ case 'U':
+ if (*bp++ != 'T')
+ return NULL;
+ /*FALLTHROUGH*/
+ case 'Z':
+ tm->tm.tm_isdst = 0;
+ flb_tm_gmtoff(tm) = 0;
+#ifdef FLB_HAVE_ZONE
+ tm->tm.tm_zone = utc;
+#endif
+ continue;
+ case '+':
+ neg = 0;
+ break;
+ case '-':
+ neg = 1;
+ break;
+ default:
+ --bp;
+ ep = _find_string(bp, &i, nast, NULL, 4);
+ if (ep != NULL) {
+ flb_tm_gmtoff(tm) = (-5 - i) * SECSPERHOUR;
+#ifdef FLB_HAVE_ZONE
+ tm->tm.tm_zone = (char *)nast[i];
+#endif
+ bp = ep;
+ continue;
+ }
+ ep = _find_string(bp, &i, nadt, NULL, 4);
+ if (ep != NULL) {
+ tm->tm.tm_isdst = 1;
+ flb_tm_gmtoff(tm) = (-4 - i) * SECSPERHOUR;
+#ifdef FLB_HAVE_ZONE
+ tm->tm.tm_zone = (char *)nadt[i];
+#endif
+ bp = ep;
+ continue;
+ }
+ return NULL;
+ }
+ if (!isdigit(bp[0]) || !isdigit(bp[1]))
+ return NULL;
+ offs = ((bp[0]-'0') * 10 + (bp[1]-'0')) * SECSPERHOUR;
+ bp += 2;
+ if (*bp == ':')
+ bp++;
+ if (isdigit(*bp)) {
+ offs += (*bp++ - '0') * 10 * SECSPERMIN;
+ if (!isdigit(*bp))
+ return NULL;
+ offs += (*bp++ - '0') * SECSPERMIN;
+ }
+ if (neg)
+ offs = -offs;
+ tm->tm.tm_isdst = 0; /* XXX */
+ flb_tm_gmtoff(tm) = offs;
+#ifdef FLB_HAVE_ZONE
+ tm->tm.tm_zone = NULL; /* XXX */
+#endif
+ continue;
+
+ /*
+ * Miscellaneous conversions.
+ */
+ case 'n': /* Any kind of white-space. */
+ case 't':
+ _LEGAL_ALT(0);
+ while (isspace(*bp))
+ bp++;
+ break;
+
+
+ default: /* Unknown/unsupported conversion. */
+ return (NULL);
+ }
+
+
+ }
+
+ /*
+ * We need to evaluate the two digit year spec (%y)
+ * last as we can get a century spec (%C) at any time.
+ */
+ if (relyear != -1) {
+ if (century == TM_YEAR_BASE) {
+ if (relyear <= 68)
+ tm->tm.tm_year = relyear + 2000 - TM_YEAR_BASE;
+ else
+ tm->tm.tm_year = relyear + 1900 - TM_YEAR_BASE;
+ } else {
+ tm->tm.tm_year = relyear + century - TM_YEAR_BASE;
+ }
+ fields |= FIELD_TM_YEAR;
+ }
+
+ /* Compute some missing values when possible. */
+ if (fields & FIELD_TM_YEAR) {
+ const int year = (unsigned int)tm->tm.tm_year + (unsigned int)TM_YEAR_BASE;
+ const int *mon_lens = mon_lengths[isleap(year)];
+ if (!(fields & FIELD_TM_YDAY) &&
+ (fields & FIELD_TM_MON) && (fields & FIELD_TM_MDAY)) {
+ tm->tm.tm_yday = tm->tm.tm_mday - 1;
+ for (i = 0; i < tm->tm.tm_mon; i++)
+ tm->tm.tm_yday += mon_lens[i];
+ fields |= FIELD_TM_YDAY;
+ }
+ if (fields & FIELD_TM_YDAY) {
+ int days = tm->tm.tm_yday;
+ if (!(fields & FIELD_TM_WDAY)) {
+ tm->tm.tm_wday = EPOCH_WDAY +
+ ((year - EPOCH_YEAR) % DAYSPERWEEK) *
+ (DAYSPERNYEAR % DAYSPERWEEK) +
+ leaps_thru_end_of(year - 1) -
+ leaps_thru_end_of(EPOCH_YEAR - 1) +
+ tm->tm.tm_yday;
+ tm->tm.tm_wday %= DAYSPERWEEK;
+ if (tm->tm.tm_wday < 0)
+ tm->tm.tm_wday += DAYSPERWEEK;
+ }
+ if (!(fields & FIELD_TM_MON)) {
+ tm->tm.tm_mon = 0;
+ while (tm->tm.tm_mon < MONSPERYEAR && days >= mon_lens[tm->tm.tm_mon])
+ days -= mon_lens[tm->tm.tm_mon++];
+ }
+ if (!(fields & FIELD_TM_MDAY))
+ tm->tm.tm_mday = days + 1;
+ }
+ }
+
+ return ((char *)bp);
+}
+
+
+static int
+_conv_num(const unsigned char **buf, int *dest, int llim, int ulim)
+{
+ int result = 0;
+ int rulim = ulim;
+
+ if (**buf < '0' || **buf > '9')
+ return (0);
+
+ /* we use rulim to break out of the loop when we run out of digits */
+ do {
+ result *= 10;
+ result += *(*buf)++ - '0';
+ rulim /= 10;
+ } while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9');
+
+ if (result < llim || result > ulim)
+ return (0);
+
+ *dest = result;
+ return (1);
+}
+
+static int
+_conv_num64(const unsigned char **buf, int64_t *dest, int64_t llim, int64_t ulim)
+{
+ int64_t result = 0;
+ int64_t rulim = ulim;
+
+ if (**buf < '0' || **buf > '9')
+ return (0);
+
+ /* we use rulim to break out of the loop when we run out of digits */
+ do {
+ /* Avoid overflow: result > ((2**64)/2.0) / 10.0 */
+ if (result > 922337203685477580) {
+ return (0);
+ }
+ result *= 10;
+
+ /* Avoid overflow: result > ((2**64)/2.0) - 48 */
+ if (result > 9223372036854775760) {
+ return (0);
+ }
+ result += *(*buf)++ - '0';
+ rulim /= 10;
+ /* watch out for overflows. If value gets above
+ * ((2**64)/2.0)/10.0 then we will overflow. So instead
+ * we return 0 */
+ if (result >= 922337203685477580) {
+ return (0);
+ }
+ } while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9');
+
+ if (result < llim || result > ulim)
+ return (0);
+
+ *dest = result;
+ return (1);
+}
+
+static const u_char *
+_find_string(const u_char *bp, int *tgt, const char * const *n1,
+ const char * const *n2, int c)
+{
+ int i;
+ unsigned int len;
+
+ /* check full name - then abbreviated ones */
+ for (; n1 != NULL; n1 = n2, n2 = NULL) {
+ for (i = 0; i < c; i++, n1++) {
+ len = strlen(*n1);
+ if (strncasecmp(*n1, (const char *)bp, len) == 0) {
+ *tgt = i;
+ return bp + len;
+ }
+ }
+ }
+
+ /* Nothing matched */
+ return NULL;
+}
+
+static int
+leaps_thru_end_of(const int y)
+{
+ return (y >= 0) ? (y / 4 - y / 100 + y / 400) :
+ -(leaps_thru_end_of(-(y + 1)) + 1);
+}
diff --git a/fluent-bit/src/flb_task.c b/fluent-bit/src/flb_task.c
new file mode 100644
index 000000000..bc9ddc634
--- /dev/null
+++ b/fluent-bit/src/flb_task.c
@@ -0,0 +1,520 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_input_chunk.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_router.h>
+#include <fluent-bit/flb_task.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_scheduler.h>
+
+/*
+ * Every task created must have an unique ID, this function lookup the
+ * lowest number available in the tasks_map.
+ *
+ * This 'id' is used by the task interface to communicate with the engine event
+ * loop about some action.
+ */
+
+static inline int map_get_task_id(struct flb_config *config)
+{
+ int i;
+ int map_size = (sizeof(config->tasks_map) / sizeof(struct flb_task_map));
+
+ for (i = 0; i < map_size; i++) {
+ if (config->tasks_map[i].task == NULL) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static inline void map_set_task_id(int id, struct flb_task *task,
+ struct flb_config *config)
+{
+ config->tasks_map[id].task = task;
+
+}
+
+static inline void map_free_task_id(int id, struct flb_config *config)
+{
+ config->tasks_map[id].task = NULL;
+}
+
+void flb_task_retry_destroy(struct flb_task_retry *retry)
+{
+ int ret;
+
+ /* Make sure to invalidate any request from the scheduler */
+ ret = flb_sched_request_invalidate(retry->parent->config, retry);
+ if (ret == 0) {
+ flb_debug("[retry] task retry=%p, invalidated from the scheduler",
+ retry);
+ }
+
+ mk_list_del(&retry->_head);
+ flb_free(retry);
+}
+
+/*
+ * For an existing task 'retry', re-schedule it. One of the use case of this function
+ * is when the engine dispatcher fails to bring the chunk up due to Chunk I/O
+ * configuration restrictions, the task needs to be re-scheduled.
+ */
+int flb_task_retry_reschedule(struct flb_task_retry *retry, struct flb_config *config)
+{
+ int seconds;
+ struct flb_task *task;
+
+ task = retry->parent;
+ seconds = flb_sched_request_create(config, retry, retry->attempts);
+ if (seconds == -1) {
+ /*
+ * This is the worse case scenario: 'cannot re-schedule a retry'. If the Chunk
+ * resides only in memory, it will be lost. */
+ flb_warn("[task] retry for task %i could not be re-scheduled", task->id);
+ flb_task_retry_destroy(retry);
+ if (task->users == 0 && mk_list_size(&task->retries) == 0) {
+ flb_task_destroy(task, FLB_TRUE);
+ }
+ return -1;
+ }
+ else {
+ flb_info("[task] re-schedule retry=%p %i in the next %i seconds",
+ retry, task->id, seconds);
+ }
+
+ return 0;
+}
+
+struct flb_task_retry *flb_task_retry_create(struct flb_task *task,
+ struct flb_output_instance *ins)
+{
+ struct mk_list *head;
+ struct mk_list *tmp;
+ struct flb_task_retry *retry = NULL;
+
+ /* First discover if is there any previous retry context in the task */
+ mk_list_foreach_safe(head, tmp, &task->retries) {
+ retry = mk_list_entry(head, struct flb_task_retry, _head);
+ if (retry->o_ins == ins) {
+ if (retry->attempts >= ins->retry_limit && ins->retry_limit >= 0) {
+ flb_debug("[task] task_id=%i reached retry-attempts limit %i/%i",
+ task->id, retry->attempts, ins->retry_limit);
+ flb_task_retry_destroy(retry);
+ return NULL;
+ }
+ break;
+ }
+ retry = NULL;
+ }
+
+ if (!retry) {
+ /* Create a new re-try instance */
+ retry = flb_malloc(sizeof(struct flb_task_retry));
+ if (!retry) {
+ flb_errno();
+ return NULL;
+ }
+
+ retry->attempts = 1;
+ retry->o_ins = ins;
+ retry->parent = task;
+ mk_list_add(&retry->_head, &task->retries);
+
+ flb_debug("[retry] new retry created for task_id=%i attempts=%i",
+ task->id, retry->attempts);
+ }
+ else {
+ retry->attempts++;
+ flb_debug("[retry] re-using retry for task_id=%i attempts=%i",
+ task->id, retry->attempts);
+ }
+
+ /*
+ * This 'retry' was issued by an output plugin, from an Engine perspective
+ * we need to determinate if the source input plugin have some memory
+ * restrictions and if the Storage type is 'filesystem' we need to put
+ * the file content down.
+ */
+ flb_input_chunk_set_up_down(task->ic);
+
+ /*
+ * Besides limits adjusted above, a retry that's going to only one place
+ * must be down.
+ */
+ if (mk_list_size(&task->routes) == 1) {
+ flb_input_chunk_down(task->ic);
+ }
+
+ return retry;
+}
+
+/*
+ * Return FLB_TRUE or FLB_FALSE if the chunk pointed by the task was
+ * created on this running instance or it comes from a chunk in the
+ * filesystem from a previous run.
+ */
+int flb_task_from_fs_storage(struct flb_task *task)
+{
+ struct flb_input_chunk *ic;
+
+ ic = (struct flb_input_chunk *) task->ic;
+ return ic->fs_backlog;
+}
+
+int flb_task_retry_count(struct flb_task *task, void *data)
+{
+ struct mk_list *head;
+ struct flb_task_retry *retry;
+ struct flb_output_instance *o_ins;
+
+ o_ins = (struct flb_output_instance *) data;
+
+ mk_list_foreach(head, &task->retries) {
+ retry = mk_list_entry(head, struct flb_task_retry, _head);
+
+ if (retry->o_ins == o_ins) {
+ return retry->attempts;
+ }
+ }
+
+ return -1;
+}
+
+/* Check if a 'retry' context exists for a specific task, if so, cleanup */
+int flb_task_retry_clean(struct flb_task *task, struct flb_output_instance *ins)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_task_retry *retry;
+
+ /* Delete 'retries' only associated with the output instance */
+ mk_list_foreach_safe(head, tmp, &task->retries) {
+ retry = mk_list_entry(head, struct flb_task_retry, _head);
+ if (retry->o_ins == ins) {
+ flb_task_retry_destroy(retry);
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+/* Allocate an initialize a basic Task structure */
+static struct flb_task *task_alloc(struct flb_config *config)
+{
+ int task_id;
+ struct flb_task *task;
+
+ /* Allocate the new task */
+ task = (struct flb_task *) flb_calloc(1, sizeof(struct flb_task));
+ if (!task) {
+ flb_errno();
+ return NULL;
+ }
+
+ /* Get ID and set back 'task' reference */
+ task_id = map_get_task_id(config);
+ if (task_id == -1) {
+ flb_free(task);
+ return NULL;
+ }
+ map_set_task_id(task_id, task, config);
+
+ flb_trace("[task %p] created (id=%i)", task, task_id);
+
+ /* Initialize minimum variables */
+ task->id = task_id;
+ task->config = config;
+ task->status = FLB_TASK_NEW;
+ task->users = 0;
+ mk_list_init(&task->routes);
+ mk_list_init(&task->retries);
+
+ pthread_mutex_init(&task->lock, NULL);
+
+ return task;
+}
+
+/* Return the number of tasks with 'running status' or tasks with retries */
+int flb_task_running_count(struct flb_config *config)
+{
+ int count = 0;
+ struct mk_list *head;
+ struct mk_list *t_head;
+ struct flb_task *task;
+ struct flb_input_instance *ins;
+
+ mk_list_foreach(head, &config->inputs) {
+ ins = mk_list_entry(head, struct flb_input_instance, _head);
+ mk_list_foreach(t_head, &ins->tasks) {
+ task = mk_list_entry(t_head, struct flb_task, _head);
+ if (task->users > 0 || mk_list_size(&task->retries) > 0) {
+ count++;
+ }
+ }
+ }
+
+ return count;
+}
+
+int flb_task_running_print(struct flb_config *config)
+{
+ int count = 0;
+ flb_sds_t tmp;
+ flb_sds_t routes;
+ struct mk_list *head;
+ struct mk_list *t_head;
+ struct mk_list *r_head;
+ struct flb_task *task;
+ struct flb_task_route *route;
+ struct flb_input_instance *ins;
+
+ routes = flb_sds_create_size(256);
+ if (!routes) {
+ flb_error("[task] cannot allocate space to report pending tasks");
+ return -1;
+ }
+
+ mk_list_foreach(head, &config->inputs) {
+ ins = mk_list_entry(head, struct flb_input_instance, _head);
+ count = mk_list_size(&ins->tasks);
+ flb_info("[task] %s/%s has %i pending task(s):",
+ ins->p->name, flb_input_name(ins), count);
+ mk_list_foreach(t_head, &ins->tasks) {
+ task = mk_list_entry(t_head, struct flb_task, _head);
+
+ mk_list_foreach(r_head, &task->routes) {
+ route = mk_list_entry(r_head, struct flb_task_route, _head);
+ tmp = flb_sds_printf(&routes, "%s/%s ",
+ route->out->p->name,
+ flb_output_name(route->out));
+ if (!tmp) {
+ flb_sds_destroy(routes);
+ flb_error("[task] cannot print report for pending tasks");
+ return -1;
+ }
+ routes = tmp;
+ }
+
+ flb_info("[task] task_id=%i still running on route(s): %s",
+ task->id, routes);
+ flb_sds_len_set(routes, 0);
+ }
+ }
+ flb_sds_destroy(routes);
+ return 0;
+}
+
+/* Create an engine task to handle the output plugin flushing work */
+struct flb_task *flb_task_create(uint64_t ref_id,
+ const char *buf,
+ size_t size,
+ struct flb_input_instance *i_ins,
+ struct flb_input_chunk *ic,
+ const char *tag_buf, int tag_len,
+ struct flb_config *config,
+ int *err)
+{
+ int count = 0;
+ int total_events = 0;
+ struct flb_task *task;
+ struct flb_event_chunk *evc;
+ struct flb_task_route *route;
+ struct flb_router_path *route_path;
+ struct flb_output_instance *o_ins;
+ struct flb_input_chunk *task_ic;
+ struct mk_list *i_head;
+ struct mk_list *o_head;
+
+ /* No error status */
+ *err = FLB_FALSE;
+
+ /* allocate task */
+ task = task_alloc(config);
+ if (!task) {
+ *err = FLB_TRUE;
+ return NULL;
+ }
+
+#ifdef FLB_HAVE_METRICS
+ total_events = ((struct flb_input_chunk *) ic)->total_records;
+#endif
+
+ /* event chunk */
+ evc = flb_event_chunk_create(ic->event_type,
+ total_events,
+ (char *) tag_buf, tag_len,
+ (char *) buf, size);
+ if (!evc) {
+ flb_free(task);
+ *err = FLB_TRUE;
+ return NULL;
+ }
+ task->event_chunk = evc;
+ task_ic = (struct flb_input_chunk *) ic;
+ task_ic->task = task;
+
+ /* Keep track of origins */
+ task->ref_id = ref_id;
+ task->i_ins = i_ins;
+ task->ic = ic;
+ mk_list_add(&task->_head, &i_ins->tasks);
+
+#ifdef FLB_HAVE_METRICS
+ task->records = ((struct flb_input_chunk *) ic)->total_records;
+#endif
+
+ /* Direct connects betweek input <> outputs (API based) */
+ if (mk_list_size(&i_ins->routes_direct) > 0) {
+ mk_list_foreach(i_head, &i_ins->routes_direct) {
+ route_path = mk_list_entry(i_head, struct flb_router_path, _head);
+ o_ins = route_path->ins;
+
+ route = flb_malloc(sizeof(struct flb_task_route));
+ if (!route) {
+ flb_errno();
+ task->event_chunk->data = NULL;
+ flb_task_destroy(task, FLB_TRUE);
+ return NULL;
+ }
+
+ route->out = o_ins;
+ mk_list_add(&route->_head, &task->routes);
+ }
+ flb_debug("[task] created direct task=%p id=%i OK", task, task->id);
+ return task;
+ }
+
+ /* Find matching routes for the incoming task */
+ mk_list_foreach(o_head, &config->outputs) {
+ o_ins = mk_list_entry(o_head,
+ struct flb_output_instance, _head);
+
+ /* skip output plugins that don't handle proper event types */
+ if (!flb_router_match_type(ic->event_type, o_ins)) {
+ continue;
+ }
+
+ if (flb_routes_mask_get_bit(task_ic->routes_mask, o_ins->id) != 0) {
+ route = flb_calloc(1, sizeof(struct flb_task_route));
+ if (!route) {
+ flb_errno();
+ continue;
+ }
+
+ route->status = FLB_TASK_ROUTE_INACTIVE;
+ route->out = o_ins;
+ mk_list_add(&route->_head, &task->routes);
+ count++;
+ }
+ }
+
+ /* no destinations ?, useless task. */
+ if (count == 0) {
+ flb_debug("[task] created task=%p id=%i without routes, dropping.",
+ task, task->id);
+ task->event_chunk->data = NULL;
+ flb_task_destroy(task, FLB_TRUE);
+ return NULL;
+ }
+
+ flb_debug("[task] created task=%p id=%i OK", task, task->id);
+ return task;
+}
+
+void flb_task_destroy(struct flb_task *task, int del)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_task_route *route;
+ struct flb_task_retry *retry;
+
+ flb_debug("[task] destroy task=%p (task_id=%i)", task, task->id);
+
+ /* Release task_id */
+ map_free_task_id(task->id, task->config);
+
+ /* Remove routes */
+ mk_list_foreach_safe(head, tmp, &task->routes) {
+ route = mk_list_entry(head, struct flb_task_route, _head);
+ mk_list_del(&route->_head);
+ flb_free(route);
+ }
+
+ /* Unlink and release task */
+ mk_list_del(&task->_head);
+
+ /* destroy chunk */
+ flb_input_chunk_destroy(task->ic, del);
+
+ /* Remove 'retries' */
+ mk_list_foreach_safe(head, tmp, &task->retries) {
+ retry = mk_list_entry(head, struct flb_task_retry, _head);
+ flb_task_retry_destroy(retry);
+ }
+
+ flb_input_chunk_set_limits(task->i_ins);
+
+ if (task->event_chunk) {
+ flb_event_chunk_destroy(task->event_chunk);
+ }
+ flb_free(task);
+}
+
+struct flb_task_queue* flb_task_queue_create() {
+ struct flb_task_queue *tq;
+ tq = flb_malloc(sizeof(struct flb_task_queue));
+ if (!tq) {
+ flb_errno();
+ return NULL;
+ }
+ mk_list_init(&tq->pending);
+ mk_list_init(&tq->in_progress);
+ return tq;
+}
+
+void flb_task_queue_destroy(struct flb_task_queue *queue) {
+ struct flb_task_enqueued *queued_task;
+ struct mk_list *tmp;
+ struct mk_list *head;
+
+ mk_list_foreach_safe(head, tmp, &queue->pending) {
+ queued_task = mk_list_entry(head, struct flb_task_enqueued, _head);
+ mk_list_del(&queued_task->_head);
+ flb_free(queued_task);
+ }
+
+ mk_list_foreach_safe(head, tmp, &queue->in_progress) {
+ queued_task = mk_list_entry(head, struct flb_task_enqueued, _head);
+ mk_list_del(&queued_task->_head);
+ flb_free(queued_task);
+ }
+
+ flb_free(queue);
+}
diff --git a/fluent-bit/src/flb_thread_pool.c b/fluent-bit/src/flb_thread_pool.c
new file mode 100644
index 000000000..e3b854717
--- /dev/null
+++ b/fluent-bit/src/flb_thread_pool.c
@@ -0,0 +1,209 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_worker.h>
+#include <fluent-bit/flb_thread_pool.h>
+
+/* Return the next thread id. We use the list size to set an id */
+static int flb_tp_thread_get_id(struct flb_tp *tp)
+{
+ return mk_list_size(&tp->list_threads);
+}
+
+/* Create a thread manager context */
+struct flb_tp *flb_tp_create(struct flb_config *config)
+{
+ struct flb_tp *tp;
+
+ tp = flb_calloc(1, sizeof(struct flb_tp));
+ if (!tp) {
+ flb_errno();
+ return NULL;
+ }
+ tp->config = config;
+ mk_list_init(&tp->list_threads);
+
+ return tp;
+}
+
+void flb_tp_destroy(struct flb_tp *tp)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_tp_thread *th;
+
+ mk_list_foreach_safe(head, tmp, &tp->list_threads) {
+ th = mk_list_entry(head, struct flb_tp_thread, _head);
+ mk_list_del(&th->_head);
+ flb_free(th);
+ }
+
+ flb_free(tp);
+}
+
+struct flb_tp_thread *flb_tp_thread_create(struct flb_tp *tp,
+ void (*func)(void *), void *arg,
+ struct flb_config *config)
+
+{
+ struct flb_tp_thread *th;
+
+ /* Create thread context */
+ th = flb_calloc(1, sizeof(struct flb_tp_thread));
+ if (!th) {
+ flb_errno();
+ return NULL;
+ }
+ th->config = config;
+
+ /*
+ * To spawn a thread, we use the 'worker' interface. Since the worker will
+ * start the thread as soon as is invoked, we keep a reference to the worker
+ * parameters in our context and we only use them when the thread is really
+ * started through the call flb_tp_thread_start().
+ */
+ th->params.func = func;
+ th->params.data = arg;
+
+ /* Status */
+ th->status = FLB_THREAD_POOL_NONE;
+
+ /* Set the thread id */
+ th->id = flb_tp_thread_get_id(tp);
+
+ /* Link this thread context to the parent context list */
+ mk_list_add(&th->_head, &tp->list_threads);
+
+ return th;
+}
+
+
+/* Get a candidate thread using round-robin */
+struct flb_tp_thread *flb_tp_thread_get_rr(struct flb_tp *tp)
+{
+ struct flb_tp_thread *th;
+
+ if (!tp->thread_cur) {
+ th = mk_list_entry_first(&tp->list_threads,
+ struct flb_tp_thread, _head);
+ }
+ else {
+ th = mk_list_entry_next(tp->thread_cur,
+ struct flb_tp_thread, _head,
+ &tp->list_threads);
+ }
+ tp->thread_cur = &th->_head;
+
+ return th;
+}
+
+int flb_tp_thread_start(struct flb_tp *tp, struct flb_tp_thread *th)
+{
+ int ret;
+
+ ret = flb_worker_create(th->params.func, th->params.data, &th->tid,
+ th->config);
+ if (ret == -1) {
+ th->status = FLB_THREAD_POOL_ERROR;
+ return -1;
+ }
+
+ /*
+ * Retrieve the Worker context. The worker API don't return the
+ * id or the context, so we use the created pthread_t (task id)
+ * to obtain the reference.
+ */
+ th->worker = flb_worker_lookup(th->tid, tp->config);
+ th->status = FLB_THREAD_POOL_RUNNING;
+
+ return 0;
+}
+
+int flb_tp_thread_start_id(struct flb_tp *tp, int id)
+{
+ int i = 0;
+ struct mk_list *head;
+ struct flb_tp_thread *th = NULL;
+
+ mk_list_foreach(head, &tp->list_threads) {
+ if (i == id) {
+ th = mk_list_entry(head, struct flb_tp_thread, _head);
+ break;
+ }
+ th = NULL;
+ i++;
+ }
+
+ if (!th) {
+ return -1;
+ }
+
+ return flb_tp_thread_start(tp, th);
+}
+
+int flb_tp_thread_start_all(struct flb_tp *tp)
+{
+ struct mk_list *head;
+ struct flb_tp_thread *th;
+
+ mk_list_foreach(head, &tp->list_threads) {
+ th = mk_list_entry(head, struct flb_tp_thread, _head);
+ flb_tp_thread_start(tp, th);
+ }
+
+ return 0;
+}
+
+int flb_tp_thread_stop(struct flb_tp *tp, struct flb_tp_thread *th)
+{
+ return 0;
+}
+
+int flb_tp_thread_stop_all(struct flb_tp *tp)
+{
+ int ret;
+ struct mk_list *head;
+ struct flb_tp_thread *th;
+
+ /*
+ * Iterate each worker thread, signal them to stop working
+ * and wait a proper exit.
+ */
+ mk_list_foreach(head, &tp->list_threads) {
+ th = mk_list_entry(head, struct flb_tp_thread, _head);
+ if (th->status != FLB_THREAD_POOL_RUNNING) {
+ continue;
+ }
+
+ ret = flb_tp_thread_stop(tp, th);
+ if (ret == -1) {
+
+ }
+ }
+
+ return 0;
+}
+
+int flb_tp_thread_destroy()
+{
+ return 0;
+}
diff --git a/fluent-bit/src/flb_time.c b/fluent-bit/src/flb_time.c
new file mode 100644
index 000000000..624b70d02
--- /dev/null
+++ b/fluent-bit/src/flb_time.c
@@ -0,0 +1,444 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 "cmetrics/lib/mpack/src/mpack/mpack.h"
+#include <msgpack.h>
+#include <mpack/mpack.h>
+#include <fluent-bit/flb_compat.h>
+#include <fluent-bit/flb_macros.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_time.h>
+#include <stdint.h>
+#ifdef FLB_HAVE_CLOCK_GET_TIME
+# include <mach/clock.h>
+# include <mach/mach.h>
+#endif
+
+#include <string.h>
+#include <inttypes.h>
+#include <time.h>
+
+#define ONESEC_IN_NSEC 1000000000
+
+static int is_valid_format(int fmt)
+{
+ return (FLB_TIME_ETFMT_INT <= fmt) && (fmt < FLB_TIME_ETFMT_OTHER) ?
+ FLB_TRUE : FLB_FALSE;
+}
+
+static int _flb_time_get(struct flb_time *tm)
+{
+ if (tm == NULL) {
+ return -1;
+ }
+#if defined FLB_TIME_FORCE_FMT_INT
+ tm->tm.tv_sec = time(NULL);
+ tm->tm.tv_nsec = 0;
+ return 0;
+#elif defined FLB_HAVE_TIMESPEC_GET
+ /* C11 supported! */
+ return timespec_get(&tm->tm, TIME_UTC);
+#elif defined FLB_CLOCK_GET_TIME
+ clock_serv_t cclock;
+ mach_timespec_t mts;
+ host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
+ clock_get_time(cclock, &mts);
+ tm->tv_sec = mts.tv_sec;
+ tm->tv_nsec = mts.tv_nsec;
+ return mach_port_deallocate(mach_task_self(), cclock);
+#else /* __STDC_VERSION__ */
+ return clock_gettime(CLOCK_REALTIME, &tm->tm);
+#endif
+}
+
+int flb_time_get(struct flb_time *tm)
+{
+ return _flb_time_get(tm);
+}
+
+/* A portable function to sleep N msec */
+int flb_time_msleep(uint32_t ms)
+{
+#ifdef _MSC_VER
+ Sleep((DWORD) ms);
+ return 0;
+#else
+ struct timespec ts;
+ ts.tv_sec = ms / 1000;
+ ts.tv_nsec = (ms % 1000) * 1000000;
+ return nanosleep(&ts, NULL);
+#endif
+}
+
+double flb_time_to_double(struct flb_time *tm)
+{
+ return (double)(tm->tm.tv_sec) + ((double)tm->tm.tv_nsec/(double)ONESEC_IN_NSEC);
+}
+
+uint64_t flb_time_to_nanosec(struct flb_time *tm)
+{
+ return (((uint64_t)tm->tm.tv_sec * 1000000000L) + tm->tm.tv_nsec);
+}
+
+uint64_t flb_time_to_millisec(struct flb_time *tm)
+{
+ return (((uint64_t)tm->tm.tv_sec * 1000L) + tm->tm.tv_nsec / 1000000L);
+}
+
+int flb_time_add(struct flb_time *base, struct flb_time *duration, struct flb_time *result)
+{
+ if (base == NULL || duration == NULL|| result == NULL) {
+ return -1;
+ }
+ result->tm.tv_sec = base->tm.tv_sec + duration->tm.tv_sec;
+ result->tm.tv_nsec = base->tm.tv_nsec + duration->tm.tv_nsec;
+
+ if (result->tm.tv_nsec > ONESEC_IN_NSEC) {
+ result->tm.tv_nsec -= ONESEC_IN_NSEC;
+ result->tm.tv_sec++;
+ } else if (result->tm.tv_nsec < 0) {
+ result->tm.tv_nsec += ONESEC_IN_NSEC;
+ result->tm.tv_sec--;
+ }
+
+ return 0;
+}
+
+int flb_time_diff(struct flb_time *time1,
+ struct flb_time *time0,struct flb_time *result)
+{
+ if (time1 == NULL || time0 == NULL || result == NULL) {
+ return -1;
+ }
+
+ if (time1->tm.tv_sec >= time0->tm.tv_sec) {
+ result->tm.tv_sec = time1->tm.tv_sec - time0->tm.tv_sec;
+ if (time1->tm.tv_nsec >= time0->tm.tv_nsec) {
+ result->tm.tv_nsec = time1->tm.tv_nsec - time0->tm.tv_nsec;
+ }
+ else if(result->tm.tv_sec == 0){
+ /* underflow */
+ return -2;
+ }
+ else{
+ result->tm.tv_nsec = ONESEC_IN_NSEC
+ + time1->tm.tv_nsec - time0->tm.tv_nsec;
+ result->tm.tv_sec--;
+ }
+ }
+ else {
+ /* underflow */
+ return -3;
+ }
+ return 0;
+}
+
+int flb_time_append_to_mpack(mpack_writer_t *writer, struct flb_time *tm, int fmt)
+{
+ int ret = 0;
+ struct flb_time l_time;
+ char ext_data[8];
+ uint32_t tmp;
+
+ if (!is_valid_format(fmt)) {
+#ifdef FLB_TIME_FORCE_FMT_INT
+ fmt = FLB_TIME_ETFMT_INT;
+#else
+ fmt = FLB_TIME_ETFMT_V1_FIXEXT;
+#endif
+ }
+
+ if (tm == NULL) {
+ if (fmt == FLB_TIME_ETFMT_INT) {
+ l_time.tm.tv_sec = time(NULL);
+ }
+ else {
+ _flb_time_get(&l_time);
+ }
+ tm = &l_time;
+ }
+
+ switch(fmt) {
+ case FLB_TIME_ETFMT_INT:
+ mpack_write_uint(writer, tm->tm.tv_sec);
+ break;
+
+ case FLB_TIME_ETFMT_V0:
+ case FLB_TIME_ETFMT_V1_EXT:
+ /* We can't set with msgpack-c !! */
+ /* see pack_template.h and msgpack_pack_inline_func(_ext) */
+ case FLB_TIME_ETFMT_V1_FIXEXT:
+ tmp = htonl((uint32_t)tm->tm.tv_sec); /* second from epoch */
+ memcpy(&ext_data, &tmp, 4);
+ tmp = htonl((uint32_t)tm->tm.tv_nsec);/* nanosecond */
+ memcpy(&ext_data[4], &tmp, 4);
+
+ /* https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1#eventtime-ext-format */
+ mpack_write_ext(writer, 0 /*ext type=0 */, ext_data, sizeof(ext_data));
+ break;
+
+ default:
+ ret = -1;
+ }
+
+ return ret;
+}
+
+int flb_time_append_to_msgpack(struct flb_time *tm, msgpack_packer *pk, int fmt)
+{
+ int ret = 0;
+ struct flb_time l_time;
+ char ext_data[8];
+ uint32_t tmp;
+
+ if (!is_valid_format(fmt)) {
+#ifdef FLB_TIME_FORCE_FMT_INT
+ fmt = FLB_TIME_ETFMT_INT;
+#else
+ fmt = FLB_TIME_ETFMT_V1_FIXEXT;
+#endif
+ }
+
+ if (tm == NULL) {
+ if (fmt == FLB_TIME_ETFMT_INT) {
+ l_time.tm.tv_sec = time(NULL);
+ }
+ else {
+ _flb_time_get(&l_time);
+ }
+ tm = &l_time;
+ }
+
+ switch(fmt) {
+ case FLB_TIME_ETFMT_INT:
+ msgpack_pack_uint64(pk, tm->tm.tv_sec);
+ break;
+
+ case FLB_TIME_ETFMT_V0:
+ case FLB_TIME_ETFMT_V1_EXT:
+ /* We can't set with msgpack-c !! */
+ /* see pack_template.h and msgpack_pack_inline_func(_ext) */
+ case FLB_TIME_ETFMT_V1_FIXEXT:
+ tmp = htonl((uint32_t)tm->tm.tv_sec); /* second from epoch */
+ memcpy(&ext_data, &tmp, 4);
+ tmp = htonl((uint32_t)tm->tm.tv_nsec);/* nanosecond */
+ memcpy(&ext_data[4], &tmp, 4);
+
+ msgpack_pack_ext(pk, 8/*fixext8*/, 0);
+ msgpack_pack_ext_body(pk, ext_data, sizeof(ext_data));
+
+ break;
+
+ default:
+ ret = -1;
+ }
+
+ return ret;
+}
+
+static inline int is_eventtime(msgpack_object *obj)
+{
+ if (obj->via.ext.type != 0 || obj->via.ext.size != 8) {
+ return FLB_FALSE;
+ }
+ return FLB_TRUE;
+}
+
+int flb_time_msgpack_to_time(struct flb_time *time, msgpack_object *obj)
+{
+ uint32_t tmp;
+
+ switch(obj->type) {
+ case MSGPACK_OBJECT_POSITIVE_INTEGER:
+ time->tm.tv_sec = obj->via.u64;
+ time->tm.tv_nsec = 0;
+ break;
+ case MSGPACK_OBJECT_FLOAT:
+ time->tm.tv_sec = obj->via.f64;
+ time->tm.tv_nsec = ((obj->via.f64 - time->tm.tv_sec) * ONESEC_IN_NSEC);
+ break;
+ case MSGPACK_OBJECT_EXT:
+ if (is_eventtime(obj) != FLB_TRUE) {
+ flb_warn("[time] unknown ext type. type=%d size=%d",
+ obj->via.ext.type, obj->via.ext.size);
+ return -1;
+ }
+ memcpy(&tmp, &obj->via.ext.ptr[0], 4);
+ time->tm.tv_sec = (uint32_t) ntohl(tmp);
+ memcpy(&tmp, &obj->via.ext.ptr[4], 4);
+ time->tm.tv_nsec = (uint32_t) ntohl(tmp);
+ break;
+ default:
+ flb_warn("unknown time format %x", obj->type);
+ return -1;
+ }
+
+ return 0;
+}
+
+int flb_time_pop_from_mpack(struct flb_time *time, mpack_reader_t *reader)
+{
+ mpack_tag_t tag;
+ double d;
+ float f;
+ int64_t i;
+ uint32_t tmp;
+ char extbuf[8];
+ size_t ext_len;
+ int header_detected;
+
+ if (time == NULL) {
+ return -1;
+ }
+
+ header_detected = FLB_FALSE;
+
+ /* consume the record array */
+ tag = mpack_read_tag(reader);
+
+ if (mpack_reader_error(reader) != mpack_ok ||
+ mpack_tag_type(&tag) != mpack_type_array ||
+ mpack_tag_array_count(&tag) == 0) {
+ return -1;
+ }
+
+ /* consume the header array or the timestamp
+ * depending on the chunk encoding
+ */
+ tag = mpack_read_tag(reader);
+
+ if (mpack_reader_error(reader) != mpack_ok) {
+ return -1;
+ }
+
+ if (mpack_tag_type(&tag) == mpack_type_array) {
+ if(mpack_tag_array_count(&tag) != 2) {
+ return -1;
+ }
+
+ /* consume the timestamp element */
+ tag = mpack_read_tag(reader);
+
+ if (mpack_reader_error(reader) != mpack_ok) {
+ return -1;
+ }
+
+ header_detected = FLB_TRUE;
+ }
+
+ switch (mpack_tag_type(&tag)) {
+ case mpack_type_int:
+ i = mpack_tag_int_value(&tag);
+ if (i < 0) {
+ flb_warn("expecting positive integer, got %" PRId64, i);
+ return -1;
+ }
+ time->tm.tv_sec = i;
+ time->tm.tv_nsec = 0;
+ break;
+ case mpack_type_uint:
+ time->tm.tv_sec = mpack_tag_uint_value(&tag);
+ time->tm.tv_nsec = 0;
+ break;
+ case mpack_type_float:
+ f = mpack_tag_float_value(&tag);
+ time->tm.tv_sec = f;
+ time->tm.tv_nsec = ((f - time->tm.tv_sec) * ONESEC_IN_NSEC);
+ case mpack_type_double:
+ d = mpack_tag_double_value(&tag);
+ time->tm.tv_sec = d;
+ time->tm.tv_nsec = ((d - time->tm.tv_sec) * ONESEC_IN_NSEC);
+ break;
+ case mpack_type_ext:
+ ext_len = mpack_tag_ext_length(&tag);
+ if (ext_len != 8) {
+ flb_warn("expecting ext_len is 8, got %ld", ext_len);
+ return -1;
+ }
+ mpack_read_bytes(reader, extbuf, ext_len);
+ memcpy(&tmp, extbuf, 4);
+ time->tm.tv_sec = (uint32_t) ntohl(tmp);
+ memcpy(&tmp, extbuf + 4, 4);
+ time->tm.tv_nsec = (uint32_t) ntohl(tmp);
+ break;
+ default:
+ flb_warn("unknown time format %d", tag.type);
+ return -1;
+ }
+
+ /* discard the metadata map if present */
+
+ if (header_detected) {
+ mpack_discard(reader);
+ }
+
+ return 0;
+}
+
+int flb_time_pop_from_msgpack(struct flb_time *time, msgpack_unpacked *upk,
+ msgpack_object **map)
+{
+ int ret;
+ msgpack_object obj;
+
+ if (time == NULL || upk == NULL) {
+ return -1;
+ }
+
+ if (upk->data.type != MSGPACK_OBJECT_ARRAY) {
+ return -1;
+ }
+
+ obj = upk->data.via.array.ptr[0];
+
+ if (obj.type == MSGPACK_OBJECT_ARRAY) {
+ if (obj.via.array.size != 2) {
+ return -1;
+ }
+
+ obj = obj.via.array.ptr[0];
+ }
+
+ *map = &upk->data.via.array.ptr[1];
+
+ ret = flb_time_msgpack_to_time(time, &obj);
+ return ret;
+}
+
+long flb_time_tz_offset_to_second()
+{
+ time_t t = time(NULL);
+ struct tm local = *localtime(&t);
+ struct tm utc = *gmtime(&t);
+
+ long diff = ((local.tm_hour - utc.tm_hour) \
+ * 60 + (local.tm_min - utc.tm_min)) \
+ * 60L + (local.tm_sec - utc.tm_sec);
+
+ int delta_day = local.tm_mday - utc.tm_mday;
+
+ if ((delta_day == 1) || (delta_day < -1)) {
+ diff += 24L * 60 * 60;
+ }
+ else if ((delta_day == -1) || (delta_day > 1)) {
+ diff -= 24L * 60 * 60;
+ }
+
+ return diff;
+}
diff --git a/fluent-bit/src/flb_typecast.c b/fluent-bit/src/flb_typecast.c
new file mode 100644
index 000000000..4c2d10536
--- /dev/null
+++ b/fluent-bit/src/flb_typecast.c
@@ -0,0 +1,525 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_typecast.h>
+#include <string.h>
+#include <inttypes.h>
+#include <msgpack.h>
+
+flb_typecast_type_t flb_typecast_str_to_type_t(char *type_str, int type_len)
+{
+ if (!strncasecmp(type_str, "int", type_len)) {
+ return FLB_TYPECAST_TYPE_INT;
+ }
+ else if (!strncasecmp(type_str, "uint", type_len)) {
+ return FLB_TYPECAST_TYPE_UINT;
+ }
+ else if (!strncasecmp(type_str, "float", type_len)) {
+ return FLB_TYPECAST_TYPE_FLOAT;
+ }
+ else if (!strncasecmp(type_str, "hex", type_len)) {
+ return FLB_TYPECAST_TYPE_HEX;
+ }
+ else if (!strncasecmp(type_str, "string", type_len)) {
+ return FLB_TYPECAST_TYPE_STR;
+ }
+ else if(!strncasecmp(type_str, "bool", type_len)) {
+ return FLB_TYPECAST_TYPE_BOOL;
+ }
+
+ return FLB_TYPECAST_TYPE_ERROR;
+}
+
+const char * flb_typecast_type_t_to_str(flb_typecast_type_t type)
+{
+ switch(type) {
+ case FLB_TYPECAST_TYPE_INT:
+ return "int";
+ case FLB_TYPECAST_TYPE_UINT:
+ return "uint";
+ case FLB_TYPECAST_TYPE_FLOAT:
+ return "float";
+ case FLB_TYPECAST_TYPE_HEX:
+ return "hex";
+ case FLB_TYPECAST_TYPE_STR:
+ return "string";
+ case FLB_TYPECAST_TYPE_BOOL:
+ return "bool";
+ default:
+ return "unknown type";
+ }
+
+}
+
+static int flb_typecast_conv_str(const char *input, int input_len,
+ struct flb_typecast_rule *rule,
+ msgpack_packer *pck,
+ struct flb_typecast_value *output)
+{
+ flb_sds_t tmp_str;
+ int ret = 0;
+
+ if(input == NULL || rule == NULL || output == NULL) {
+ return -1;
+ }
+ else if (rule->from_type != FLB_TYPECAST_TYPE_STR) {
+ flb_error("%s: Type is not string.",__FUNCTION__);
+ return -1;
+ }
+
+ /*
+ * msgpack char is not null terminated.
+ * So make a temporary copy.
+ */
+ tmp_str = flb_sds_create_len(input, input_len);
+ if (tmp_str == NULL) {
+ flb_errno();
+ return -1;
+ }
+
+ switch(rule->to_type) {
+ case FLB_TYPECAST_TYPE_INT:
+ output->val.i_num = strtoimax(tmp_str, NULL, 10);
+ if (output->val.i_num == 0) {
+ flb_error("%s: convert error. input=%s", __FUNCTION__, tmp_str);
+ ret = -1;
+ goto typecast_conv_str_end;
+ }
+ if (pck != NULL) {
+ msgpack_pack_int64(pck, output->val.i_num);
+ }
+ break;
+ case FLB_TYPECAST_TYPE_UINT:
+ output->val.ui_num = strtoumax(tmp_str, NULL, 10);
+ if (output->val.ui_num == 0) {
+ flb_error("%s: convert error. input=%s", __FUNCTION__, tmp_str);
+ ret = -1;
+ goto typecast_conv_str_end;
+ }
+ if (pck != NULL) {
+ msgpack_pack_uint64(pck, output->val.ui_num);
+ }
+ break;
+ case FLB_TYPECAST_TYPE_HEX:
+ output->val.ui_num = strtoumax(tmp_str, NULL, 16);
+ if (output->val.ui_num == 0) {
+ flb_error("%s: convert error. input=%s", __FUNCTION__, tmp_str);
+ ret = -1;
+ goto typecast_conv_str_end;
+ }
+ if (pck != NULL) {
+ msgpack_pack_uint64(pck, output->val.ui_num);
+ }
+ break;
+ case FLB_TYPECAST_TYPE_FLOAT:
+ output->val.d_num = atof(tmp_str);
+ if (pck != NULL) {
+ msgpack_pack_double(pck, output->val.d_num);
+ }
+ break;
+ case FLB_TYPECAST_TYPE_BOOL:
+ if (input_len >= 4 && !strncasecmp(tmp_str, "true", 4)) {
+ output->val.boolean = FLB_TRUE;
+ }
+ else if (input_len >= 5 && !strncasecmp(tmp_str, "false", 5)) {
+ output->val.boolean = FLB_FALSE;
+ }
+ else {
+ flb_error("%s: convert error. input=%s", __FUNCTION__, tmp_str);
+ ret = -1;
+ goto typecast_conv_str_end;
+ }
+
+ if (pck != NULL) {
+ if (output->val.boolean) {
+ msgpack_pack_true(pck);
+ }
+ else {
+ msgpack_pack_false(pck);
+ }
+ }
+
+ break;
+ case FLB_TYPECAST_TYPE_STR:
+ flb_error("%s: str to str. nothing to do.", __FUNCTION__);
+ return -1;
+ break;
+ default:
+ flb_error("%s: unknown type %d", __FUNCTION__, rule->to_type);
+ ret = -1;
+ }
+ typecast_conv_str_end:
+ flb_sds_destroy(tmp_str);
+ return ret;
+}
+
+static int flb_typecast_conv_bool(int input_bool,
+ struct flb_typecast_rule *rule,
+ msgpack_packer *pck,
+ struct flb_typecast_value *output)
+{
+ if(rule == NULL || output == NULL) {
+ return -1;
+ }
+
+ if (rule->to_type != FLB_TYPECAST_TYPE_STR) {
+ flb_error("%s: type %s is not supported",__FUNCTION__,
+ flb_typecast_type_t_to_str(rule->to_type));
+ return -1;
+ }
+
+ if (input_bool == FLB_TRUE) {
+ output->val.str = flb_sds_create_len("true", 4);
+ if (pck != NULL) {
+ msgpack_pack_str(pck, 4);
+ msgpack_pack_str_body(pck, "true", 4);
+ }
+ return 0;
+ }
+ else if (input_bool == FLB_FALSE) {
+ output->val.str = flb_sds_create_len("false", 5);
+ if (pck != NULL) {
+ msgpack_pack_str(pck, 5);
+ msgpack_pack_str_body(pck, "false", 5);
+ }
+ return 0;
+ }
+ flb_error("%s: unsupported input %d",__FUNCTION__,
+ input_bool);
+ return -1;
+}
+
+static int flb_typecast_conv_int(int64_t input,
+ struct flb_typecast_rule *rule,
+ msgpack_packer *pck,
+ struct flb_typecast_value *output)
+{
+ char temp[32] = {0};
+ int i;
+
+ if(rule == NULL || output == NULL) {
+ return -1;
+ }
+
+ switch(rule->to_type) {
+ case FLB_TYPECAST_TYPE_STR:
+ i = snprintf(temp, sizeof(temp) -1, "%"PRId64, input);
+ output->val.str = flb_sds_create_len(temp, i);
+ if(pck != NULL) {
+ msgpack_pack_str(pck, i);
+ msgpack_pack_str_body(pck, output->val.str, i);
+ }
+ break;
+
+ case FLB_TYPECAST_TYPE_FLOAT:
+ output->val.d_num = (double)input;
+ if (pck != NULL) {
+ msgpack_pack_double(pck, output->val.d_num);
+ }
+ break;
+ case FLB_TYPECAST_TYPE_UINT:
+ output->val.ui_num = (uint64_t)input;
+ if (pck != NULL) {
+ msgpack_pack_uint64(pck, output->val.ui_num);
+ }
+ break;
+
+ default:
+ flb_error("%s: type %s is not supported",__FUNCTION__,
+ flb_typecast_type_t_to_str(rule->to_type));
+ return -1;
+ }
+ return 0;
+}
+
+static int flb_typecast_conv_uint(uint64_t input,
+ struct flb_typecast_rule *rule,
+ msgpack_packer *pck,
+ struct flb_typecast_value *output)
+{
+ char temp[32] = {0};
+ int i;
+
+ if(rule == NULL || output == NULL) {
+ return -1;
+ }
+
+ switch(rule->to_type) {
+ case FLB_TYPECAST_TYPE_STR:
+ i = snprintf(temp, sizeof(temp) -1, "%"PRIu64, input);
+ output->val.str = flb_sds_create_len(temp, i);
+ if(pck != NULL) {
+ msgpack_pack_str(pck, i);
+ msgpack_pack_str_body(pck, output->val.str, i);
+ }
+ break;
+
+ case FLB_TYPECAST_TYPE_FLOAT:
+ output->val.d_num = (double)input;
+ if (pck != NULL) {
+ msgpack_pack_double(pck, output->val.d_num);
+ }
+ break;
+ case FLB_TYPECAST_TYPE_INT:
+ output->val.i_num = (int64_t)input;
+ if (pck != NULL) {
+ msgpack_pack_int64(pck, output->val.ui_num);
+ }
+ break;
+
+ default:
+ flb_error("%s: type %s is not supported",__FUNCTION__,
+ flb_typecast_type_t_to_str(rule->to_type));
+ return -1;
+ }
+ return 0;
+}
+
+static int flb_typecast_conv_float(double input,
+ struct flb_typecast_rule *rule,
+ msgpack_packer *pck,
+ struct flb_typecast_value *output)
+{
+ char temp[512] = {0};
+ int i;
+
+ if(rule == NULL || output == NULL) {
+ return -1;
+ }
+
+ switch(rule->to_type) {
+ case FLB_TYPECAST_TYPE_STR:
+ if (input == (double)(long long int)input) {
+ i = snprintf(temp, sizeof(temp)-1, "%.1f", input);
+ }
+ else {
+ i = snprintf(temp, sizeof(temp)-1, "%.16g", input);
+ }
+ output->val.str = flb_sds_create_len(temp, i);
+ if(pck != NULL) {
+ msgpack_pack_str(pck, i);
+ msgpack_pack_str_body(pck, output->val.str, i);
+ }
+ break;
+ case FLB_TYPECAST_TYPE_INT:
+ output->val.i_num = (int64_t)input;
+ if (pck != NULL) {
+ msgpack_pack_int64(pck, output->val.ui_num);
+ }
+ break;
+ case FLB_TYPECAST_TYPE_UINT:
+ output->val.ui_num = (uint64_t)input;
+ if (pck != NULL) {
+ msgpack_pack_uint64(pck, output->val.ui_num);
+ }
+ break;
+
+ default:
+ flb_error("%s: type %s is not supported",__FUNCTION__,
+ flb_typecast_type_t_to_str(rule->to_type));
+ return -1;
+ }
+ return 0;
+}
+
+int flb_typecast_rule_destroy(struct flb_typecast_rule *rule)
+{
+ if(rule == NULL) {
+ return 0;
+ }
+ flb_free(rule);
+
+ return 0;
+}
+
+struct flb_typecast_rule *flb_typecast_rule_create(char *from_type, int from_len,
+ char *to_type, int to_len)
+{
+ struct flb_typecast_rule *rule = NULL;
+
+ if (from_type == NULL || to_type == NULL) {
+ return NULL;
+ }
+ rule = flb_malloc(sizeof(struct flb_typecast_rule));
+ if (rule == NULL) {
+ flb_errno();
+ return NULL;
+ }
+
+ rule->from_type = flb_typecast_str_to_type_t(from_type, from_len);
+ if (rule->from_type == FLB_TYPECAST_TYPE_ERROR) {
+ flb_error("%s: unknown from str %s", __FUNCTION__, from_type);
+ flb_typecast_rule_destroy(rule);
+ return NULL;
+ }
+
+ rule->to_type = flb_typecast_str_to_type_t(to_type, to_len);
+ if (rule->to_type == FLB_TYPECAST_TYPE_ERROR) {
+ flb_error("%s: unknown to str %s", __FUNCTION__, to_type);
+ flb_typecast_rule_destroy(rule);
+ return NULL;
+ }
+
+ return rule;
+}
+
+
+/**
+ * Convert msgpack object according to a rule.
+ *
+ * @param input msgpack object to be converted
+ * @param rule conversion rule
+ * @param pck msgpack packer to write converted object. If NULL, not to write.
+ * @param output converted value. User must call flb_typecast_value_destroy after using.
+ * If NULL, not to be filled.
+ *
+ * @return 0 : success, !0: fail
+ */
+static int flb_typecast_value_conv(msgpack_object input,
+ struct flb_typecast_rule *rule,
+ msgpack_packer *pck,
+ struct flb_typecast_value *output)
+{
+ int ret = -1;
+
+ if (rule == NULL || output == NULL) {
+ return -1;
+ }
+
+ switch(rule->from_type) {
+ case FLB_TYPECAST_TYPE_STR:
+ if (input.type != MSGPACK_OBJECT_STR) {
+ flb_error("%s: src type is not str", __FUNCTION__);
+ return -1;
+ }
+ ret = flb_typecast_conv_str(input.via.str.ptr,
+ input.via.str.size,
+ rule , pck, output);
+ break;
+ case FLB_TYPECAST_TYPE_BOOL:
+ if (input.type != MSGPACK_OBJECT_BOOLEAN) {
+ flb_error("%s: src type is not boolean", __FUNCTION__);
+ return -1;
+ }
+ ret = flb_typecast_conv_bool(input.via.boolean ? FLB_TRUE:FLB_FALSE,
+ rule, pck, output);
+ break;
+ case FLB_TYPECAST_TYPE_INT:
+ if (input.type != MSGPACK_OBJECT_POSITIVE_INTEGER &&
+ input.type != MSGPACK_OBJECT_NEGATIVE_INTEGER) {
+ flb_error("%s: src type is not int", __FUNCTION__);
+ return -1;
+ }
+ ret = flb_typecast_conv_int(input.via.i64, rule, pck, output);
+
+ break;
+ case FLB_TYPECAST_TYPE_UINT:
+ if (input.type != MSGPACK_OBJECT_POSITIVE_INTEGER &&
+ input.type != MSGPACK_OBJECT_NEGATIVE_INTEGER) {
+ flb_error("%s: src type is not uint", __FUNCTION__);
+ return -1;
+ }
+ ret = flb_typecast_conv_uint(input.via.u64, rule, pck, output);
+
+ break;
+ case FLB_TYPECAST_TYPE_FLOAT:
+ if (input.type != MSGPACK_OBJECT_FLOAT32 &&
+ input.type != MSGPACK_OBJECT_FLOAT64) {
+ flb_error("%s: src type is not float", __FUNCTION__);
+ return -1;
+ }
+ ret = flb_typecast_conv_float(input.via.f64, rule, pck, output);
+
+ break;
+
+ default:
+ flb_error("%s: unknown type %d", __FUNCTION__, rule->from_type);
+ }
+ return ret;
+}
+
+int flb_typecast_value_destroy(struct flb_typecast_value* val)
+{
+ if (val == NULL) {
+ return 0;
+ }
+ if (val->type == FLB_TYPECAST_TYPE_STR) {
+ flb_sds_destroy(val->val.str);
+ }
+ flb_free(val);
+ return 0;
+}
+
+
+struct flb_typecast_value *flb_typecast_value_create(msgpack_object input,
+ struct flb_typecast_rule *rule)
+{
+ int ret = -1;
+ struct flb_typecast_value *val;
+
+ if (rule == NULL) {
+ return NULL;
+ }
+ val = flb_malloc(sizeof(struct flb_typecast_value));
+ if (val == NULL) {
+ flb_errno();
+ return NULL;
+ }
+ val->type = FLB_TYPECAST_TYPE_ERROR;
+ ret = flb_typecast_value_conv(input, rule, NULL, val);
+ if (ret < 0) {
+ flb_free(val);
+ return NULL;
+ }
+ val->type = rule->to_type;
+
+ return val;
+}
+
+/**
+ * Convert msgpack object according to a rule.
+ *
+ * @param input msgpack object to be converted
+ * @param rule conversion rule
+ * @param pck msgpack packer to write converted object
+ *
+ * @return 0 : success, !0: fail
+ */
+int flb_typecast_pack(msgpack_object input,
+ struct flb_typecast_rule *rule,
+ msgpack_packer *pck)
+{
+ int ret = -1;
+ struct flb_typecast_value val;
+
+ if (rule == NULL || pck == NULL) {
+ flb_error("%s: input is null", __FUNCTION__);
+ return -1;
+ }
+
+ ret = flb_typecast_value_conv(input, rule, pck, &val);
+
+ if (ret == 0 && rule->to_type == FLB_TYPECAST_TYPE_STR) {
+ flb_sds_destroy(val.val.str);
+ }
+
+ return ret;
+}
diff --git a/fluent-bit/src/flb_unescape.c b/fluent-bit/src/flb_unescape.c
new file mode 100644
index 000000000..44f575b41
--- /dev/null
+++ b/fluent-bit/src/flb_unescape.c
@@ -0,0 +1,328 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_compat.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+
+static int octal_digit(char c)
+{
+ return (c >= '0' && c <= '7');
+}
+
+static int hex_digit(char c)
+{
+ return ((c >= '0' && c <= '9') ||
+ (c >= 'A' && c <= 'F') ||
+ (c >= 'a' && c <= 'f'));
+}
+
+static int u8_wc_toutf8(char *dest, uint32_t ch)
+{
+ if (ch < 0x80) {
+ dest[0] = (char)ch;
+ return 1;
+ }
+ if (ch < 0x800) {
+ dest[0] = (ch>>6) | 0xC0;
+ dest[1] = (ch & 0x3F) | 0x80;
+ return 2;
+ }
+ if (ch < 0x10000) {
+ dest[0] = (ch>>12) | 0xE0;
+ dest[1] = ((ch>>6) & 0x3F) | 0x80;
+ dest[2] = (ch & 0x3F) | 0x80;
+ return 3;
+ }
+ if (ch < 0x110000) {
+ dest[0] = (ch>>18) | 0xF0;
+ dest[1] = ((ch>>12) & 0x3F) | 0x80;
+ dest[2] = ((ch>>6) & 0x3F) | 0x80;
+ dest[3] = (ch & 0x3F) | 0x80;
+ return 4;
+ }
+ return 0;
+}
+
+/* assumes that src points to the character after a backslash
+ returns number of input characters processed */
+static int u8_read_escape_sequence(const char *str, int size, uint32_t *dest)
+{
+ uint32_t ch;
+ char digs[9]="\0\0\0\0\0\0\0\0";
+ int dno=0, i=1;
+
+ ch = (uint32_t)str[0]; /* take literal character */
+
+ if (str[0] == 'n')
+ ch = L'\n';
+ else if (str[0] == 't')
+ ch = L'\t';
+ else if (str[0] == 'r')
+ ch = L'\r';
+ else if (str[0] == 'b')
+ ch = L'\b';
+ else if (str[0] == 'f')
+ ch = L'\f';
+ else if (str[0] == 'v')
+ ch = L'\v';
+ else if (str[0] == 'a')
+ ch = L'\a';
+ else if (octal_digit(str[0])) {
+ i = 0;
+ do {
+ digs[dno++] = str[i++];
+ } while (i < size && octal_digit(str[i]) && dno < 3);
+ ch = strtol(digs, NULL, 8);
+ }
+ else if (str[0] == 'x') {
+ while (i < size && hex_digit(str[i]) && dno < 2) {
+ digs[dno++] = str[i++];
+ }
+ if (dno > 0) {
+ ch = strtol(digs, NULL, 16);
+ }
+ }
+ else if (str[0] == 'u') {
+ while (i < size && hex_digit(str[i]) && dno < 4) {
+ digs[dno++] = str[i++];
+ }
+ if (dno > 0) {
+ ch = strtol(digs, NULL, 16);
+ }
+ }
+ else if (str[0] == 'U') {
+ while (i < size && hex_digit(str[i]) && dno < 8) {
+ digs[dno++] = str[i++];
+ }
+ if (dno > 0) {
+ ch = strtol(digs, NULL, 16);
+ }
+ }
+ *dest = ch;
+
+ return i;
+}
+
+int flb_unescape_string_utf8(const char *in_buf, int sz, char *out_buf)
+{
+ uint32_t ch;
+ char temp[4];
+ const char *end;
+ const char *next;
+ int size;
+
+
+ int count_out = 0;
+ int count_in = 0;
+ int esc_in = 0;
+ int esc_out = 0;
+
+ end = in_buf + sz;
+ while (in_buf < end && *in_buf && count_in < sz) {
+ next = in_buf + 1;
+ if (next < end && *in_buf == '\\') {
+ esc_in = 2;
+ switch (*next) {
+ case '"':
+ ch = '"';
+ break;
+ case '\'':
+ ch = '\'';
+ break;
+ case '\\':
+ ch = '\\';
+ break;
+ case '/':
+ ch = '/';
+ break;
+ case 'n':
+ ch = '\n';
+ break;
+ case 'b':
+ ch = '\b';
+ break;
+ case 't':
+ ch = '\t';
+ break;
+ case 'f':
+ ch = '\f';
+ break;
+ case 'r':
+ ch = '\r';
+ break;
+ default:
+ size = end - next;
+ if (size > 0) {
+ esc_in = u8_read_escape_sequence(next, size, &ch) + 1;
+ }
+ else {
+ /* because char is unsigned char by default on arm, so we need to do a explicit conversion */
+ ch = (uint32_t) (signed char) *in_buf;
+ esc_in = 1;
+ }
+ }
+ }
+ else {
+ /* explicit convert char to signed char */
+ ch = (uint32_t) (signed char) *in_buf;
+ esc_in = 1;
+ }
+
+ in_buf += esc_in;
+ count_in += esc_in;
+
+ esc_out = u8_wc_toutf8(temp, ch);
+ if (esc_out > sz-count_out) {
+ flb_error("Crossing over string boundary");
+ break;
+ }
+
+ if (esc_out == 0) {
+ out_buf[count_out] = ch;
+ esc_out = 1;
+ }
+ else if (esc_out == 1) {
+ out_buf[count_out] = (char) temp[0];
+ }
+ else {
+ memcpy(&out_buf[count_out], temp, esc_out);
+ }
+ count_out += esc_out;
+ }
+ if (count_in < sz) {
+ flb_error("Not at boundary but still NULL terminating : %d - '%s'", sz, in_buf);
+ }
+ out_buf[count_out] = '\0';
+ return count_out;
+}
+
+int flb_unescape_string(const char *buf, int buf_len, char **unesc_buf)
+{
+ int i = 0;
+ int j = 0;
+ char *p;
+ char n;
+
+ p = *unesc_buf;
+ while (i < buf_len) {
+ if (buf[i] == '\\') {
+ if (i + 1 < buf_len) {
+ n = buf[i + 1];
+ if (n == 'n') {
+ p[j++] = '\n';
+ i++;
+ }
+ else if (n == 'a') {
+ p[j++] = '\a';
+ i++;
+ }
+ else if (n == 'b') {
+ p[j++] = '\b';
+ i++;
+ }
+ else if (n == 't') {
+ p[j++] = '\t';
+ i++;
+ }
+ else if (n == 'v') {
+ p[j++] = '\v';
+ i++;
+ }
+ else if (n == 'f') {
+ p[j++] = '\f';
+ i++;
+ }
+ else if (n == 'r') {
+ p[j++] = '\r';
+ i++;
+ }
+ else if (n == '\\') {
+ p[j++] = '\\';
+ i++;
+ }
+ i++;
+ continue;
+ }
+ else {
+ i++;
+ }
+ }
+ p[j++] = buf[i++];
+ }
+ p[j] = '\0';
+ return j;
+}
+
+
+/* mysql unquote */
+int flb_mysql_unquote_string(char *buf, int buf_len, char **unesc_buf)
+{
+ int i = 0;
+ int j = 0;
+ char *p;
+ char n;
+
+ p = *unesc_buf;
+ while (i < buf_len) {
+ if ((n = buf[i++]) != '\\') {
+ p[j++] = n;
+ } else if(i >= buf_len) {
+ p[j++] = n;
+ } else {
+ n = buf[i++];
+ switch(n) {
+ case 'n':
+ p[j++] = '\n';
+ break;
+ case 'r':
+ p[j++] = '\r';
+ break;
+ case 't':
+ p[j++] = '\t';
+ break;
+ case '\\':
+ p[j++] = '\\';
+ break;
+ case '\'':
+ p[j++] = '\'';
+ break;
+ case '\"':
+ p[j++] = '\"';
+ break;
+ case '0':
+ p[j++] = 0;
+ break;
+ case 'Z':
+ p[j++] = 0x1a;
+ break;
+ default:
+ p[j++] = '\\';
+ p[j++] = n;
+ break;
+ }
+ }
+ }
+ p[j] = '\0';
+ return j;
+}
diff --git a/fluent-bit/src/flb_upstream.c b/fluent-bit/src/flb_upstream.c
new file mode 100644
index 000000000..9ec39b84c
--- /dev/null
+++ b/fluent-bit/src/flb_upstream.c
@@ -0,0 +1,1202 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <monkey/mk_core.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_slist.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_upstream.h>
+#include <fluent-bit/flb_io.h>
+#include <fluent-bit/tls/flb_tls.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_engine.h>
+#include <fluent-bit/flb_config_map.h>
+#include <fluent-bit/flb_thread_storage.h>
+
+FLB_TLS_DEFINE(struct mk_list, flb_upstream_list_key);
+
+/* Config map for Upstream networking setup */
+struct flb_config_map upstream_net[] = {
+ {
+ FLB_CONFIG_MAP_STR, "net.dns.mode", NULL,
+ 0, FLB_TRUE, offsetof(struct flb_net_setup, dns_mode),
+ "Select the primary DNS connection type (TCP or UDP)"
+ },
+
+ {
+ FLB_CONFIG_MAP_STR, "net.dns.resolver", NULL,
+ 0, FLB_TRUE, offsetof(struct flb_net_setup, dns_resolver),
+ "Select the primary DNS resolver type (LEGACY or ASYNC)"
+ },
+
+ {
+ FLB_CONFIG_MAP_BOOL, "net.dns.prefer_ipv4", "false",
+ 0, FLB_TRUE, offsetof(struct flb_net_setup, dns_prefer_ipv4),
+ "Prioritize IPv4 DNS results when trying to establish a connection"
+ },
+
+ {
+ FLB_CONFIG_MAP_BOOL, "net.keepalive", "true",
+ 0, FLB_TRUE, offsetof(struct flb_net_setup, keepalive),
+ "Enable or disable Keepalive support"
+ },
+
+ {
+ FLB_CONFIG_MAP_TIME, "net.keepalive_idle_timeout", "30s",
+ 0, FLB_TRUE, offsetof(struct flb_net_setup, keepalive_idle_timeout),
+ "Set maximum time allowed for an idle Keepalive connection"
+ },
+
+ {
+ FLB_CONFIG_MAP_TIME, "net.io_timeout", "0s",
+ 0, FLB_TRUE, offsetof(struct flb_net_setup, io_timeout),
+ "Set maximum time a connection can stay idle while assigned"
+ },
+
+ {
+ FLB_CONFIG_MAP_TIME, "net.connect_timeout", "10s",
+ 0, FLB_TRUE, offsetof(struct flb_net_setup, connect_timeout),
+ "Set maximum time allowed to establish a connection, this time "
+ "includes the TLS handshake"
+ },
+
+ {
+ FLB_CONFIG_MAP_BOOL, "net.connect_timeout_log_error", "true",
+ 0, FLB_TRUE, offsetof(struct flb_net_setup, connect_timeout_log_error),
+ "On connection timeout, specify if it should log an error. When disabled, "
+ "the timeout is logged as a debug message"
+ },
+
+ {
+ FLB_CONFIG_MAP_STR, "net.source_address", NULL,
+ 0, FLB_TRUE, offsetof(struct flb_net_setup, source_address),
+ "Specify network address to bind for data traffic"
+ },
+
+ {
+ FLB_CONFIG_MAP_INT, "net.keepalive_max_recycle", "2000",
+ 0, FLB_TRUE, offsetof(struct flb_net_setup, keepalive_max_recycle),
+ "Set maximum number of times a keepalive connection can be used "
+ "before it is retried."
+ },
+
+ {
+ FLB_CONFIG_MAP_INT, "net.max_worker_connections", "0",
+ 0, FLB_TRUE, offsetof(struct flb_net_setup, max_worker_connections),
+ "Set the maximum number of active TCP connections that can be used per worker thread."
+ },
+
+ /* EOF */
+ {0}
+};
+
+int flb_upstream_needs_proxy(const char *host, const char *proxy,
+ const char *no_proxy);
+
+static void flb_upstream_increment_busy_connections_count(
+ struct flb_upstream *stream);
+
+static void flb_upstream_decrement_busy_connections_count(
+ struct flb_upstream *stream);
+
+static void flb_upstream_increment_total_connections_count(
+ struct flb_upstream *stream);
+
+static void flb_upstream_decrement_total_connections_count(
+ struct flb_upstream *stream);
+
+/* Enable thread-safe mode for upstream connection */
+void flb_upstream_thread_safe(struct flb_upstream *u)
+{
+ /*
+ * Upon upstream creation, automatically the upstream is linked into
+ * the main Fluent Bit context (struct flb_config *)->upstreams. We
+ * have to avoid any access to this context outside of the worker
+ * thread.
+ */
+
+ flb_stream_enable_thread_safety(&u->base);
+}
+
+struct mk_list *flb_upstream_get_config_map(struct flb_config *config)
+{
+ size_t config_index;
+ struct mk_list *config_map;
+
+ /* If a global dns mode was provided in the SERVICE category then we set it as
+ * the default value for net.dns_mode, that way the user can set a global value and
+ * override it on a per plugin basis, however, it's not because of this flexibility
+ * that it was done but because in order to be able to save the value in the
+ * flb_net_setup structure (and not lose it when flb_output_upstream_set overwrites
+ * the structure) we need to do it this way (or at least that's what I think)
+ */
+ for (config_index = 0 ; upstream_net[config_index].name != NULL ; config_index++) {
+ if (config->dns_mode != NULL) {
+ if (strcmp(upstream_net[config_index].name, "net.dns.mode") == 0) {
+ upstream_net[config_index].def_value = config->dns_mode;
+ }
+ }
+ if (config->dns_resolver != NULL) {
+ if (strcmp(upstream_net[config_index].name, "net.dns.resolver") == 0) {
+ upstream_net[config_index].def_value = config->dns_resolver;
+ }
+ }
+ if (config->dns_prefer_ipv4) {
+ if (strcmp(upstream_net[config_index].name,
+ "net.dns.prefer_ipv4") == 0) {
+ upstream_net[config_index].def_value = "true";
+ }
+ }
+ }
+
+ config_map = flb_config_map_create(config, upstream_net);
+
+ return config_map;
+}
+
+void flb_upstream_queue_init(struct flb_upstream_queue *uq)
+{
+ mk_list_init(&uq->av_queue);
+ mk_list_init(&uq->busy_queue);
+ mk_list_init(&uq->destroy_queue);
+}
+
+struct flb_upstream_queue *flb_upstream_queue_get(struct flb_upstream *u)
+{
+ struct mk_list *head;
+ struct mk_list *list;
+ struct flb_upstream *th_u;
+ struct flb_upstream_queue *uq;
+
+ /*
+ * Get the upstream queue, this might be different if the upstream is running
+ * in single-thread or multi-thread mode.
+ */
+ if (flb_stream_is_thread_safe(&u->base) == FLB_TRUE) {
+ list = flb_upstream_list_get();
+ if (!list) {
+ /*
+ * Here is the issue: a plugin enabled in multiworker mode in the
+ * initialization callback might wanted to use an upstream
+ * connection, but the init callback does not run in threaded mode
+ * so we hit this problem.
+ *
+ * As a fallback mechanism: just cross our fingers and return the
+ * principal upstream queue.
+ */
+ return (struct flb_upstream_queue *) &u->queue;
+ }
+
+ mk_list_foreach(head, list) {
+ th_u = mk_list_entry(head, struct flb_upstream, base._head);
+ if (th_u->parent_upstream == u) {
+ break;
+ }
+ th_u = NULL;
+ }
+
+ if (!th_u) {
+ return NULL;
+ }
+ uq = &th_u->queue;
+ }
+ else {
+ uq = &u->queue;
+ }
+
+ return uq;
+}
+
+void flb_upstream_list_set(struct mk_list *list)
+{
+ FLB_TLS_SET(flb_upstream_list_key, list);
+}
+
+struct mk_list *flb_upstream_list_get()
+{
+ return FLB_TLS_GET(flb_upstream_list_key);
+}
+
+/* Initialize any upstream environment context */
+void flb_upstream_init()
+{
+ /* Initialize the upstream queue thread local storage */
+ FLB_TLS_INIT(flb_upstream_list_key);
+}
+
+/* Creates a new upstream context */
+struct flb_upstream *flb_upstream_create(struct flb_config *config,
+ const char *host, int port, int flags,
+ struct flb_tls *tls)
+{
+ int ret;
+ char *proxy_protocol = NULL;
+ char *proxy_host = NULL;
+ char *proxy_port = NULL;
+ char *proxy_username = NULL;
+ char *proxy_password = NULL;
+ struct flb_upstream *u;
+
+ u = flb_calloc(1, sizeof(struct flb_upstream));
+ if (!u) {
+ flb_errno();
+ return NULL;
+ }
+
+ u->base.dynamically_allocated = FLB_TRUE;
+
+ flb_stream_setup(&u->base,
+ FLB_UPSTREAM,
+ FLB_TRANSPORT_TCP,
+ flags,
+ tls,
+ config,
+ NULL);
+
+ /* Set upstream to the http_proxy if it is specified. */
+ if (flb_upstream_needs_proxy(host, config->http_proxy, config->no_proxy) == FLB_TRUE) {
+ flb_debug("[upstream] config->http_proxy: %s", config->http_proxy);
+ ret = flb_utils_proxy_url_split(config->http_proxy, &proxy_protocol,
+ &proxy_username, &proxy_password,
+ &proxy_host, &proxy_port);
+ if (ret == -1) {
+ flb_errno();
+ flb_free(u);
+ return NULL;
+ }
+
+ u->tcp_host = flb_strdup(proxy_host);
+ u->tcp_port = atoi(proxy_port);
+ u->proxied_host = flb_strdup(host);
+ u->proxied_port = port;
+
+ if (proxy_username && proxy_password) {
+ u->proxy_username = flb_strdup(proxy_username);
+ u->proxy_password = flb_strdup(proxy_password);
+ }
+
+ flb_free(proxy_protocol);
+ flb_free(proxy_host);
+ flb_free(proxy_port);
+ flb_free(proxy_username);
+ flb_free(proxy_password);
+ }
+ else {
+ u->tcp_host = flb_strdup(host);
+ u->tcp_port = port;
+ }
+
+ if (!u->tcp_host) {
+ flb_free(u);
+ return NULL;
+ }
+
+ flb_stream_enable_flags(&u->base, FLB_IO_ASYNC);
+
+ /* Initialize queues */
+ flb_upstream_queue_init(&u->queue);
+
+ mk_list_add(&u->base._head, &config->upstreams);
+
+ return u;
+}
+
+/*
+ * Checks whehter a destinate URL should be proxied.
+ */
+int flb_upstream_needs_proxy(const char *host, const char *proxy,
+ const char *no_proxy)
+{
+ int ret;
+ struct mk_list no_proxy_list;
+ struct mk_list *head;
+ struct flb_slist_entry *e = NULL;
+
+ /* No HTTP_PROXY, should not set up proxy for the upstream `host`. */
+ if (proxy == NULL) {
+ return FLB_FALSE;
+ }
+
+ /* No NO_PROXY with HTTP_PROXY set, should set up proxy for the upstream `host`. */
+ if (no_proxy == NULL) {
+ return FLB_TRUE;
+ }
+
+ /* NO_PROXY=`*`, it matches all hosts. */
+ if (strcmp(no_proxy, "*") == 0) {
+ return FLB_FALSE;
+ }
+
+ /* check the URL list in the NO_PROXY */
+ ret = flb_slist_create(&no_proxy_list);
+ if (ret != 0) {
+ return FLB_TRUE;
+ }
+ ret = flb_slist_split_string(&no_proxy_list, no_proxy, ',', -1);
+ if (ret <= 0) {
+ return FLB_TRUE;
+ }
+ ret = FLB_TRUE;
+ mk_list_foreach(head, &no_proxy_list) {
+ e = mk_list_entry(head, struct flb_slist_entry, _head);
+ if (strcmp(host, e->str) == 0) {
+ ret = FLB_FALSE;
+ break;
+ }
+ }
+
+ /* clean up the resources. */
+ flb_slist_destroy(&no_proxy_list);
+
+ return ret;
+}
+
+/* Create an upstream context using a valid URL (protocol, host and port) */
+struct flb_upstream *flb_upstream_create_url(struct flb_config *config,
+ const char *url, int flags,
+ struct flb_tls *tls)
+{
+ int ret;
+ int tmp_port = 0;
+ char *prot = NULL;
+ char *host = NULL;
+ char *port = NULL;
+ char *uri = NULL;
+ struct flb_upstream *u = NULL;
+
+ /* Parse and split URL */
+ ret = flb_utils_url_split(url, &prot, &host, &port, &uri);
+ if (ret == -1) {
+ flb_error("[upstream] invalid URL: %s", url);
+ return NULL;
+ }
+
+ if (!prot) {
+ flb_error("[upstream] unknown protocol type from URL: %s", url);
+ goto out;
+ }
+
+ /* Manage some default ports */
+ if (!port) {
+ if (strcasecmp(prot, "http") == 0) {
+ tmp_port = 80;
+ }
+ else if (strcasecmp(prot, "https") == 0) {
+ tmp_port = 443;
+ if ((flags & FLB_IO_TLS) == 0) {
+ flags |= FLB_IO_TLS;
+ }
+ }
+ }
+ else {
+ tmp_port = atoi(port);
+ }
+
+ if (tmp_port <= 0) {
+ flb_error("[upstream] unknown TCP port in URL: %s", url);
+ goto out;
+ }
+
+ u = flb_upstream_create(config, host, tmp_port, flags, tls);
+ if (!u) {
+ flb_error("[upstream] error creating context from URL: %s", url);
+ }
+
+ out:
+ if (prot) {
+ flb_free(prot);
+ }
+ if (host) {
+ flb_free(host);
+ }
+ if (port) {
+ flb_free(port);
+ }
+ if (uri) {
+ flb_free(uri);
+ }
+
+ return u;
+}
+
+/* This function shuts the connection down in order to cause
+ * any client code trying to read or write from it to fail.
+ */
+static void shutdown_connection(struct flb_connection *u_conn)
+{
+ if (u_conn->fd > 0 &&
+ !u_conn->shutdown_flag) {
+ shutdown(u_conn->fd, SHUT_RDWR);
+
+ u_conn->shutdown_flag = FLB_TRUE;
+ }
+}
+
+/*
+ * This function moves the 'upstream connection' into the queue to be
+ * destroyed. Note that the caller is responsible to validate and check
+ * required mutex if this is being used in multi-worker mode.
+ */
+static int prepare_destroy_conn(struct flb_connection *u_conn)
+{
+ struct flb_upstream *u;
+ struct flb_upstream_queue *uq;
+
+ u = u_conn->upstream;
+
+ uq = flb_upstream_queue_get(u);
+
+ flb_trace("[upstream] destroy connection #%i to %s:%i",
+ u_conn->fd, u->tcp_host, u->tcp_port);
+
+ if (MK_EVENT_IS_REGISTERED((&u_conn->event))) {
+ mk_event_del(u_conn->evl, &u_conn->event);
+ }
+
+ if (u_conn->fd > 0) {
+#ifdef FLB_HAVE_TLS
+ if (u_conn->tls_session != NULL) {
+ flb_tls_session_destroy(u_conn->tls_session);
+
+ u_conn->tls_session = NULL;
+ }
+#endif
+ shutdown_connection(u_conn);
+
+ flb_socket_close(u_conn->fd);
+
+ u_conn->fd = -1;
+ u_conn->event.fd = -1;
+ }
+
+ /* remove connection from the queue */
+ mk_list_del(&u_conn->_head);
+
+ /* Add node to destroy queue */
+ mk_list_add(&u_conn->_head, &uq->destroy_queue);
+
+ flb_upstream_decrement_total_connections_count(u);
+
+ /*
+ * note: the connection context is destroyed by the engine once all events
+ * have been processed.
+ */
+ return 0;
+}
+
+/* 'safe' version of prepare_destroy_conn. It set locks if necessary */
+static inline int prepare_destroy_conn_safe(struct flb_connection *u_conn)
+{
+ int ret;
+
+ flb_stream_acquire_lock(u_conn->stream, FLB_TRUE);
+
+ ret = prepare_destroy_conn(u_conn);
+
+ flb_stream_release_lock(u_conn->stream);
+
+ return ret;
+}
+
+static int destroy_conn(struct flb_connection *u_conn)
+{
+ /* Delay the destruction of busy connections */
+ if (u_conn->busy_flag) {
+ return 0;
+ }
+
+ mk_list_del(&u_conn->_head);
+
+ flb_connection_destroy(u_conn);
+
+ return 0;
+}
+
+static struct flb_connection *create_conn(struct flb_upstream *u)
+{
+ struct flb_coro *coro;
+ struct flb_connection *conn;
+ int ret;
+ struct flb_upstream_queue *uq;
+
+ coro = flb_coro_get();
+
+ conn = flb_connection_create(FLB_INVALID_SOCKET,
+ FLB_UPSTREAM_CONNECTION,
+ (void *) u,
+ flb_engine_evl_get(),
+ flb_coro_get());
+ if (conn == NULL) {
+ return NULL;
+ }
+
+ conn->busy_flag = FLB_TRUE;
+
+ if (flb_stream_is_keepalive(&u->base)) {
+ flb_upstream_conn_recycle(conn, FLB_TRUE);
+ }
+ else {
+ flb_upstream_conn_recycle(conn, FLB_FALSE);
+ }
+
+ flb_stream_acquire_lock(&u->base, FLB_TRUE);
+
+ /* Link new connection to the busy queue */
+ uq = flb_upstream_queue_get(u);
+ mk_list_add(&conn->_head, &uq->busy_queue);
+
+ flb_upstream_increment_total_connections_count(u);
+
+ flb_stream_release_lock(&u->base);
+
+ flb_connection_reset_connection_timeout(conn);
+
+ /* Start connection */
+ ret = flb_io_net_connect(conn, coro);
+ if (ret == -1) {
+ flb_connection_unset_connection_timeout(conn);
+
+ flb_debug("[upstream] connection #%i failed to %s:%i",
+ conn->fd, u->tcp_host, u->tcp_port);
+
+ prepare_destroy_conn_safe(conn);
+ conn->busy_flag = FLB_FALSE;
+
+ return NULL;
+ }
+
+ flb_connection_unset_connection_timeout(conn);
+
+ if (flb_stream_is_keepalive(&u->base)) {
+ flb_debug("[upstream] KA connection #%i to %s:%i is connected",
+ conn->fd, u->tcp_host, u->tcp_port);
+ }
+
+ /* Invalidate timeout for connection */
+ conn->busy_flag = FLB_FALSE;
+
+ return conn;
+}
+
+int flb_upstream_destroy(struct flb_upstream *u)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_connection *u_conn;
+ struct flb_upstream_queue *uq;
+
+ uq = flb_upstream_queue_get(u);
+ if (!uq) {
+ uq = &u->queue;
+ }
+
+ mk_list_foreach_safe(head, tmp, &uq->av_queue) {
+ u_conn = mk_list_entry(head, struct flb_connection, _head);
+ prepare_destroy_conn(u_conn);
+ }
+
+ mk_list_foreach_safe(head, tmp, &uq->busy_queue) {
+ u_conn = mk_list_entry(head, struct flb_connection, _head);
+ prepare_destroy_conn(u_conn);
+ }
+
+ mk_list_foreach_safe(head, tmp, &uq->destroy_queue) {
+ u_conn = mk_list_entry(head, struct flb_connection, _head);
+ destroy_conn(u_conn);
+ }
+
+ flb_free(u->tcp_host);
+ flb_free(u->proxied_host);
+ flb_free(u->proxy_username);
+ flb_free(u->proxy_password);
+ mk_list_del(&u->base._head);
+ flb_free(u);
+
+ return 0;
+}
+
+/* Enable or disable 'recycle' flag for the connection */
+int flb_upstream_conn_recycle(struct flb_connection *conn, int val)
+{
+ if (val == FLB_TRUE || val == FLB_FALSE) {
+ conn->recycle = val;
+ }
+
+ return -1;
+}
+
+struct flb_connection *flb_upstream_conn_get(struct flb_upstream *u)
+{
+ int err;
+ int total_connections = 0;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_connection *conn;
+ struct flb_upstream_queue *uq;
+
+ uq = flb_upstream_queue_get(u);
+
+ flb_trace("[upstream] get new connection for %s:%i, net setup:\n"
+ "net.connect_timeout = %i seconds\n"
+ "net.source_address = %s\n"
+ "net.keepalive = %s\n"
+ "net.keepalive_idle_timeout = %i seconds\n"
+ "net.max_worker_connections = %i",
+ u->tcp_host, u->tcp_port,
+ u->base.net.connect_timeout,
+ u->base.net.source_address ? u->base.net.source_address: "any",
+ u->base.net.keepalive ? "enabled": "disabled",
+ u->base.net.keepalive_idle_timeout,
+ u->base.net.max_worker_connections);
+
+
+ /* If the upstream is limited by max connections, check current state */
+ if (u->base.net.max_worker_connections > 0) {
+ /*
+ * Connections are linked to one of these lists:
+ *
+ * - av_queue : connections ready to be used (available)
+ * - busy_queue: connections that are busy (someone is using them)
+ * - drop_queue: connections in the cleanup phase (to be drop)
+ *
+ * Fluent Bit don't create connections ahead of time, just on demand. When
+ * a connection is created is placed into the busy_queue, when is not longer
+ * needed one of these things happen:
+ *
+ * - if keepalive is enabled (default), the connection is moved to the 'av_queue'.
+ * - if keepalive is disabled, the connection is moved to 'drop_queue' then is
+ * closed and destroyed.
+ *
+ * Based on the logic described above, to limit the number of total connections
+ * in the worker, we only need to count the number of connections linked into
+ * the 'busy_queue' list because if there are connections available 'av_queue' it
+ * won't create a one.
+ */
+
+ /* Count the number of relevant connections */
+ flb_stream_acquire_lock(&u->base, FLB_TRUE);
+ total_connections = mk_list_size(&uq->busy_queue);
+ flb_stream_release_lock(&u->base);
+
+ if (total_connections >= u->base.net.max_worker_connections) {
+ flb_debug("[upstream] max worker connections=%i reached to: %s:%i, cannot connect",
+ u->base.net.max_worker_connections, u->tcp_host, u->tcp_port);
+ return NULL;
+ }
+ }
+
+ conn = NULL;
+
+ /*
+ * If we are in keepalive mode, iterate list of available connections,
+ * take a little of time to do some cleanup and assign a connection. If no
+ * entries exists, just create a new one.
+ */
+ if (u->base.net.keepalive) {
+ mk_list_foreach_safe(head, tmp, &uq->av_queue) {
+ conn = mk_list_entry(head, struct flb_connection, _head);
+
+ flb_stream_acquire_lock(&u->base, FLB_TRUE);
+
+ /* This connection works, let's move it to the busy queue */
+ mk_list_del(&conn->_head);
+ mk_list_add(&conn->_head, &uq->busy_queue);
+
+ flb_stream_release_lock(&u->base);
+
+ err = flb_socket_error(conn->fd);
+
+ if (!FLB_EINPROGRESS(err) && err != 0) {
+ flb_debug("[upstream] KA connection #%i is in a failed state "
+ "to: %s:%i, cleaning up",
+ conn->fd, u->tcp_host, u->tcp_port);
+ prepare_destroy_conn_safe(conn);
+ conn = NULL;
+ continue;
+ }
+
+ /* Reset errno */
+ conn->net_error = -1;
+
+ /* Connect timeout */
+ conn->ts_assigned = time(NULL);
+ flb_debug("[upstream] KA connection #%i to %s:%i has been assigned (recycled)",
+ conn->fd, u->tcp_host, u->tcp_port);
+ /*
+ * Note: since we are in a keepalive connection, the socket is already being
+ * monitored for possible disconnections while idle. Upon re-use by the caller
+ * when it try to send some data, the I/O interface (flb_io.c) will put the
+ * proper event mask and reuse, there is no need to remove the socket from
+ * the event loop and re-add it again.
+ *
+ * So just return the connection context.
+ */
+
+ break;
+ }
+ }
+
+ /*
+ * There are no keepalive connections available or keepalive is disabled
+ * so we need to create a new one.
+ */
+ if (conn == NULL) {
+ conn = create_conn(u);
+ }
+
+ if (conn != NULL) {
+ flb_connection_reset_io_timeout(conn);
+ flb_upstream_increment_busy_connections_count(u);
+ }
+
+ return conn;
+}
+
+/*
+ * An 'idle' and keepalive might be disconnected, if so, this callback will perform
+ * the proper connection cleanup.
+ */
+static int cb_upstream_conn_ka_dropped(void *data)
+{
+ struct flb_connection *conn;
+
+ conn = (struct flb_connection *) data;
+
+ flb_debug("[upstream] KA connection #%i to %s:%i has been disconnected "
+ "by the remote service",
+ conn->fd, conn->upstream->tcp_host, conn->upstream->tcp_port);
+ return prepare_destroy_conn_safe(conn);
+}
+
+int flb_upstream_conn_release(struct flb_connection *conn)
+{
+ int ret;
+ struct flb_upstream *u = conn->upstream;
+ struct flb_upstream_queue *uq;
+
+ flb_upstream_decrement_busy_connections_count(u);
+
+ uq = flb_upstream_queue_get(u);
+
+ /* If this is a valid KA connection just recycle */
+ if (u->base.net.keepalive == FLB_TRUE &&
+ conn->recycle == FLB_TRUE &&
+ conn->fd > -1 &&
+ conn->net_error == -1) {
+ /*
+ * This connection is still useful, move it to the 'available' queue and
+ * initialize variables.
+ */
+ flb_stream_acquire_lock(&u->base, FLB_TRUE);
+
+ mk_list_del(&conn->_head);
+ mk_list_add(&conn->_head, &uq->av_queue);
+
+ flb_stream_release_lock(&u->base);
+
+ conn->ts_available = time(NULL);
+
+ /*
+ * The socket at this point is not longer monitored, so if we want to be
+ * notified if the 'available keepalive connection' gets disconnected by
+ * the remote endpoint we need to add it again.
+ */
+ conn->event.handler = cb_upstream_conn_ka_dropped;
+
+ ret = mk_event_add(conn->evl,
+ conn->fd,
+ FLB_ENGINE_EV_CUSTOM,
+ MK_EVENT_CLOSE,
+ &conn->event);
+
+ conn->event.priority = FLB_ENGINE_PRIORITY_CONNECT;
+ if (ret == -1) {
+ /* We failed the registration, for safety just destroy the connection */
+ flb_debug("[upstream] KA connection #%i to %s:%i could not be "
+ "registered, closing.",
+ conn->fd, u->tcp_host, u->tcp_port);
+ return prepare_destroy_conn_safe(conn);
+ }
+
+ flb_debug("[upstream] KA connection #%i to %s:%i is now available",
+ conn->fd, u->tcp_host, u->tcp_port);
+ conn->ka_count++;
+
+ /* if we have exceeded our max number of uses of this connection, destroy it */
+ if (conn->net->keepalive_max_recycle > 0 &&
+ conn->ka_count > conn->net->keepalive_max_recycle) {
+ flb_debug("[upstream] KA count %i exceeded configured limit "
+ "of %i: closing.",
+ conn->ka_count,
+ conn->net->keepalive_max_recycle);
+
+ return prepare_destroy_conn_safe(conn);
+ }
+
+ return 0;
+ }
+
+ /* No keepalive connections must be destroyed */
+ return prepare_destroy_conn_safe(conn);
+}
+
+int flb_upstream_conn_timeouts(struct mk_list *list)
+{
+ time_t now;
+ int drop;
+ const char *reason;
+ struct mk_list *head;
+ struct mk_list *u_head;
+ struct mk_list *tmp;
+ struct flb_upstream *u;
+ struct flb_connection *u_conn;
+ struct flb_upstream_queue *uq;
+ int elapsed_time;
+
+ now = time(NULL);
+
+ /* Iterate all upstream contexts */
+ mk_list_foreach(head, list) {
+ u = mk_list_entry(head, struct flb_upstream, base._head);
+ uq = flb_upstream_queue_get(u);
+
+ flb_stream_acquire_lock(&u->base, FLB_TRUE);
+
+ /* Iterate every busy connection */
+ mk_list_foreach_safe(u_head, tmp, &uq->busy_queue) {
+ u_conn = mk_list_entry(u_head, struct flb_connection, _head);
+
+ drop = FLB_FALSE;
+
+ /* Connect timeouts */
+ if (u_conn->net->connect_timeout > 0 &&
+ u_conn->ts_connect_timeout > 0 &&
+ u_conn->ts_connect_timeout <= now) {
+ drop = FLB_TRUE;
+ reason = "connection timeout";
+ elapsed_time = u_conn->net->connect_timeout;
+ }
+ else if (u_conn->net->io_timeout > 0 &&
+ u_conn->ts_io_timeout > 0 &&
+ u_conn->ts_io_timeout <= now) {
+ drop = FLB_TRUE;
+ reason = "IO timeout";
+ elapsed_time = u_conn->net->io_timeout;
+ }
+
+ if (drop) {
+ if (!flb_upstream_is_shutting_down(u)) {
+ if (u->base.net.connect_timeout_log_error) {
+ flb_error("[upstream] connection #%i to %s timed "
+ "out after %i seconds (%s)",
+ u_conn->fd,
+ flb_connection_get_remote_address(u_conn),
+ elapsed_time,
+ reason);
+ }
+ else {
+ flb_debug("[upstream] connection #%i to %s timed "
+ "out after %i seconds (%s)",
+ u_conn->fd,
+ flb_connection_get_remote_address(u_conn),
+ elapsed_time,
+ reason);
+ }
+ }
+
+ u_conn->net_error = ETIMEDOUT;
+
+ /* We need to shut the connection down
+ * in order to cause some functions that are not
+ * aware of the connection error signaling
+ * mechanism to fail and abort.
+ *
+ * These functions do not check the net_error field
+ * in the connection instance upon being awakened
+ * so we need to ensure that any read/write
+ * operations on the socket generate an error.
+ *
+ * net_io_write_async
+ * net_io_read_async
+ * flb_tls_net_write_async
+ * flb_tls_net_read_async
+ *
+ * This operation could be selectively performed for
+ * connections that have already been established
+ * with no side effects because the connection
+ * establishment code honors `net_error` but
+ * taking in account that the previous version of
+ * the code did it unconditionally with no noticeable
+ * side effects leaving it that way is the safest
+ * choice at the moment.
+ */
+
+ if (MK_EVENT_IS_REGISTERED((&u_conn->event))) {
+ shutdown_connection(u_conn);
+
+ mk_event_inject(u_conn->evl,
+ &u_conn->event,
+ u_conn->event.mask,
+ FLB_TRUE);
+ }
+ else {
+ /* I can't think of a valid reason for this code path
+ * to be taken but considering that it was previously
+ * possible for it to happen (maybe wesley can shed
+ * some light on it if he remembers) I'll leave this
+ * for the moment.
+ * In any case, it's proven not to interfere with the
+ * coroutine awakening issue this change addresses.
+ */
+
+ prepare_destroy_conn(u_conn);
+ }
+
+ flb_upstream_decrement_busy_connections_count(u);
+ }
+ }
+
+ /* Check every available Keepalive connection */
+ mk_list_foreach_safe(u_head, tmp, &uq->av_queue) {
+ u_conn = mk_list_entry(u_head, struct flb_connection, _head);
+
+ if ((now - u_conn->ts_available) >= u->base.net.keepalive_idle_timeout) {
+ prepare_destroy_conn(u_conn);
+ flb_debug("[upstream] drop keepalive connection #%i to %s:%i "
+ "(keepalive idle timeout)",
+ u_conn->fd, u->tcp_host, u->tcp_port);
+ }
+ }
+
+ flb_stream_release_lock(&u->base);
+ }
+
+ return 0;
+}
+
+int flb_upstream_conn_pending_destroy(struct flb_upstream *u)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_connection *u_conn;
+ struct flb_upstream_queue *uq;
+
+ uq = flb_upstream_queue_get(u);
+
+ flb_stream_acquire_lock(&u->base, FLB_TRUE);
+
+ /* Real destroy of connections context */
+ mk_list_foreach_safe(head, tmp, &uq->destroy_queue) {
+ u_conn = mk_list_entry(head, struct flb_connection, _head);
+
+ destroy_conn(u_conn);
+ }
+
+ flb_stream_release_lock(&u->base);
+
+ return 0;
+}
+
+int flb_upstream_conn_active_destroy(struct flb_upstream *u)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_connection *u_conn;
+ struct flb_upstream_queue *uq;
+
+ uq = flb_upstream_queue_get(u);
+
+ /* Real destroy of connections context */
+ mk_list_foreach_safe(head, tmp, &uq->av_queue) {
+ u_conn = mk_list_entry(head, struct flb_connection, _head);
+
+ destroy_conn(u_conn);
+ }
+
+ return 0;
+}
+
+int flb_upstream_conn_active_destroy_list(struct mk_list *list)
+{
+ struct mk_list *head;
+ struct flb_upstream *u;
+
+ /* Iterate all upstream contexts */
+ mk_list_foreach(head, list) {
+ u = mk_list_entry(head, struct flb_upstream, base._head);
+
+ flb_upstream_conn_active_destroy(u);
+ }
+
+ return 0;
+}
+
+int flb_upstream_conn_pending_destroy_list(struct mk_list *list)
+{
+ struct mk_list *head;
+ struct flb_upstream *u;
+
+ /* Iterate all upstream contexts */
+ mk_list_foreach(head, list) {
+ u = mk_list_entry(head, struct flb_upstream, base._head);
+
+ flb_upstream_conn_pending_destroy(u);
+ }
+
+ return 0;
+}
+
+int flb_upstream_is_async(struct flb_upstream *u)
+{
+ return flb_stream_is_async(&u->base);
+}
+
+void flb_upstream_set_total_connections_label(
+ struct flb_upstream *stream,
+ const char *label_value)
+{
+ stream->cmt_total_connections_label = label_value;
+}
+
+void flb_upstream_set_total_connections_gauge(
+ struct flb_upstream *stream,
+ struct cmt_gauge *gauge_instance)
+{
+ stream->cmt_total_connections = gauge_instance;
+}
+
+static void flb_upstream_increment_total_connections_count(
+ struct flb_upstream *stream)
+{
+ if (stream->parent_upstream != NULL) {
+ stream = (struct flb_upstream *) stream->parent_upstream;
+
+ flb_upstream_increment_total_connections_count(stream);
+ }
+ if (stream->cmt_total_connections != NULL) {
+ if (stream->cmt_total_connections_label != NULL) {
+ cmt_gauge_inc(
+ stream->cmt_total_connections,
+ cfl_time_now(),
+ 1,
+ (char *[]) {
+ (char *) stream->cmt_total_connections_label
+ });
+ }
+ else {
+ cmt_gauge_inc(stream->cmt_total_connections,
+ cfl_time_now(),
+ 0, NULL);
+ }
+ }
+}
+
+static void flb_upstream_decrement_total_connections_count(
+ struct flb_upstream *stream)
+{
+ if (stream->parent_upstream != NULL) {
+ stream = (struct flb_upstream *) stream->parent_upstream;
+
+ flb_upstream_decrement_total_connections_count(stream);
+ }
+ else if (stream->cmt_total_connections != NULL) {
+ if (stream->cmt_total_connections_label != NULL) {
+ cmt_gauge_dec(
+ stream->cmt_total_connections,
+ cfl_time_now(),
+ 1,
+ (char *[]) {
+ (char *) stream->cmt_total_connections_label
+ });
+ }
+ else {
+ cmt_gauge_dec(stream->cmt_total_connections,
+ cfl_time_now(),
+ 0, NULL);
+ }
+ }
+}
+
+void flb_upstream_set_busy_connections_label(
+ struct flb_upstream *stream,
+ const char *label_value)
+{
+ stream->cmt_busy_connections_label = label_value;
+}
+
+void flb_upstream_set_busy_connections_gauge(
+ struct flb_upstream *stream,
+ struct cmt_gauge *gauge_instance)
+{
+ stream->cmt_busy_connections = gauge_instance;
+}
+
+static void flb_upstream_increment_busy_connections_count(
+ struct flb_upstream *stream)
+{
+ if (stream->parent_upstream != NULL) {
+ stream = (struct flb_upstream *) stream->parent_upstream;
+
+ flb_upstream_increment_busy_connections_count(stream);
+ }
+ else if (stream->cmt_busy_connections != NULL) {
+ if (stream->cmt_busy_connections_label != NULL) {
+ cmt_gauge_inc(
+ stream->cmt_busy_connections,
+ cfl_time_now(),
+ 1,
+ (char *[]) {
+ (char *) stream->cmt_busy_connections_label
+ });
+ }
+ else {
+ cmt_gauge_inc(stream->cmt_busy_connections,
+ cfl_time_now(),
+ 0, NULL);
+ }
+ }
+}
+
+static void flb_upstream_decrement_busy_connections_count(
+ struct flb_upstream *stream)
+{
+ if (stream->parent_upstream != NULL) {
+ stream = (struct flb_upstream *) stream->parent_upstream;
+
+ flb_upstream_decrement_busy_connections_count(stream);
+ }
+ else if (stream->cmt_busy_connections != NULL) {
+ if (stream->cmt_busy_connections_label != NULL) {
+ cmt_gauge_dec(
+ stream->cmt_busy_connections,
+ cfl_time_now(),
+ 1,
+ (char *[]) {
+ (char *) stream->cmt_busy_connections_label
+ });
+ }
+ else {
+ cmt_gauge_dec(stream->cmt_busy_connections,
+ cfl_time_now(),
+ 0, NULL);
+ }
+ }
+}
diff --git a/fluent-bit/src/flb_upstream_ha.c b/fluent-bit/src/flb_upstream_ha.c
new file mode 100644
index 000000000..6b2b68039
--- /dev/null
+++ b/fluent-bit/src/flb_upstream_ha.c
@@ -0,0 +1,357 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_compat.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_hash_table.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_upstream_ha.h>
+#include <fluent-bit/flb_upstream_node.h>
+#include <fluent-bit/flb_config_format.h>
+#include <fluent-bit/flb_kv.h>
+
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+/* Creates an Upstream HA Context */
+struct flb_upstream_ha *flb_upstream_ha_create(const char *name)
+{
+ struct flb_upstream_ha *ctx;
+
+ if (!name) {
+ return NULL;
+ }
+
+ ctx = flb_calloc(1, sizeof(struct flb_upstream_ha));
+ if (!ctx) {
+ flb_errno();
+ return NULL;
+ }
+
+ ctx->name = flb_sds_create(name);
+ if (!ctx->name) {
+ flb_free(ctx);
+ return NULL;
+ }
+
+ mk_list_init(&ctx->nodes);
+ ctx->last_used_node = NULL;
+
+ return ctx;
+}
+
+void flb_upstream_ha_destroy(struct flb_upstream_ha *ctx)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_upstream_node *node;
+
+ /* destroy nodes */
+ mk_list_foreach_safe(head, tmp, &ctx->nodes) {
+ node = mk_list_entry(head, struct flb_upstream_node, _head);
+ mk_list_del(&node->_head);
+ flb_upstream_node_destroy(node);
+ }
+
+ flb_sds_destroy(ctx->name);
+ flb_free(ctx);
+}
+
+/* Link a new node to the list handled by HA context */
+void flb_upstream_ha_node_add(struct flb_upstream_ha *ctx,
+ struct flb_upstream_node *node)
+{
+ mk_list_add(&node->_head, &ctx->nodes);
+}
+
+/* Return a target node to be used for I/O */
+struct flb_upstream_node *flb_upstream_ha_node_get(struct flb_upstream_ha *ctx)
+{
+ struct flb_upstream_node *cur_node;
+ struct flb_upstream_node *node;
+
+ if (mk_list_is_empty(&ctx->nodes) == 0) {
+ return NULL;
+ }
+
+ if (!ctx->last_used_node) {
+ node = mk_list_entry_first(&ctx->nodes, struct flb_upstream_node,
+ _head);
+ ctx->last_used_node = node;
+ return node;
+ }
+
+ cur_node = (struct flb_upstream_node *) ctx->last_used_node;
+
+ node = mk_list_entry_next(&cur_node->_head, struct flb_upstream_node,
+ _head, &ctx->nodes);
+ ctx->last_used_node = node;
+ return node;
+}
+
+static struct flb_upstream_node *create_node(int id,
+ struct flb_cf *cf,
+ struct flb_cf_section *s,
+ struct flb_config *config)
+{
+ int i;
+ int ret;
+ int skip;
+ int klen;
+ int vlen;
+ int tls = FLB_FALSE;
+ int tls_verify = FLB_TRUE;
+ int tls_debug = 1;
+ char key[32];
+ char *tmp;
+ char *name = NULL;
+ char *host = NULL;
+ char *port = NULL;
+ char *tls_vhost = NULL;
+ char *tls_ca_path = NULL;
+ char *tls_ca_file = NULL;
+ char *tls_crt_file = NULL;
+ char *tls_key_file = NULL;
+ char *tls_key_passwd = NULL;
+ struct cfl_list *head;
+ struct cfl_kvpair *entry;
+ struct flb_hash_table *ht;
+ const char *known_keys[] = {"name", "host", "port",
+ "tls", "tls.vhost", "tls.verify", "tls.debug",
+ "tls.ca_path", "tls.ca_file", "tls.crt_file",
+ "tls.key_file", "tls.key_passwd", NULL};
+
+ struct flb_upstream_node *node;
+
+ /* name */
+ name = flb_cf_section_property_get_string(cf, s, "name");
+ if (!name) {
+ flb_error("[upstream_ha] no 'name' has been set on node #%i",
+ id + 1);
+ return NULL;
+ }
+
+ /* host */
+ host = flb_cf_section_property_get_string(cf, s, "host");
+ if (!host) {
+ flb_error("[upstream_ha] no 'host' has been set on node #%i",
+ id + 1);
+ return NULL;
+ }
+
+ /* port */
+ port = flb_cf_section_property_get_string(cf, s, "port");
+ if (!port) {
+ flb_error("[upstream_ha] no 'port' has been set on node #%i",
+ id + 1);
+ return NULL;
+ }
+
+ /* tls */
+ tmp = flb_cf_section_property_get_string(cf, s, "tls");
+ if (tmp) {
+ tls = flb_utils_bool(tmp);
+ flb_sds_destroy(tmp);
+ }
+
+ /* tls.verify */
+ tmp = flb_cf_section_property_get_string(cf, s, "tls.verify");
+ if (tmp) {
+ tls_verify = flb_utils_bool(tmp);
+ flb_sds_destroy(tmp);
+ }
+
+ /* tls.debug */
+ tmp = flb_cf_section_property_get_string(cf, s, "tls.debug");
+ if (tmp) {
+ tls_debug = atoi(tmp);
+ flb_sds_destroy(tmp);
+ }
+
+ /* tls.vhost */
+ tls_vhost = flb_cf_section_property_get_string(cf, s, "tls.vhost");
+
+ /* tls.ca_path */
+ tls_ca_path = flb_cf_section_property_get_string(cf, s, "tls.ca_path");
+
+ /* tls.ca_file */
+ tls_ca_file = flb_cf_section_property_get_string(cf, s, "tls.ca_file");
+
+ /* tls.crt_file */
+ tls_crt_file = flb_cf_section_property_get_string(cf, s, "tls.crt_file");
+
+ /* tls.key_file */
+ tls_key_file = flb_cf_section_property_get_string(cf, s, "tls.key_file");
+
+ /* tls.key_file */
+ tls_key_passwd = flb_cf_section_property_get_string(cf, s, "tls.key_passwd");
+
+ /*
+ * Create hash table to store unknown key/values that might be used
+ * by the caller plugin.
+ */
+ ht = flb_hash_table_create(FLB_HASH_TABLE_EVICT_NONE, 32, 256);
+ if (!ht) {
+ flb_error("[upstream_ha] error creating hash table");
+ return NULL;
+ }
+
+ /*
+ * Iterate mk_rconf section internals, find all unknown keys and add
+ * them to the hash table associated to the node.
+ */
+ cfl_list_foreach(head, &s->properties->list) {
+ entry = cfl_list_entry(head, struct cfl_kvpair, _head);
+
+ /* If this is a known entry, just skip it */
+ skip = FLB_FALSE;
+ for (i = 0; known_keys[i] != NULL; i++) {
+ if (strcasecmp(entry->key, known_keys[i]) == 0) {
+ skip = FLB_TRUE;
+ break;
+ }
+ }
+ if (skip == FLB_TRUE) {
+ continue;
+ }
+
+ klen = flb_sds_len(entry->key);
+ vlen = flb_sds_len(entry->val->data.as_string);
+
+ /* Always store keys in lowercase */
+ for (i = 0; i < klen; i++) {
+ key[i] = tolower(entry->key[i]);
+ }
+ key[klen] = '\0';
+
+ /* Add the key and value to the hash table */
+ ret = flb_hash_table_add(ht, key, klen, entry->val->data.as_string, vlen);
+ if (ret == -1) {
+ flb_error("[upstream_ha] cannot add key %s to hash table",
+ entry->key);
+ }
+ }
+
+ node = flb_upstream_node_create(name, host, port, tls, tls_verify,
+ tls_debug, tls_vhost, tls_ca_path, tls_ca_file,
+ tls_crt_file, tls_key_file,
+ tls_key_passwd, ht, config);
+ return node;
+}
+
+/* Read an upstream file and generate the context */
+struct flb_upstream_ha *flb_upstream_ha_from_file(const char *file,
+ struct flb_config *config)
+{
+ int c = 0;
+ int ret;
+ const char *cfg = NULL;
+ char *tmp;
+ char path[PATH_MAX + 1];
+ struct stat st;
+ struct mk_list *head;
+ struct flb_upstream_ha *ups;
+ struct flb_upstream_node *node;
+ struct flb_cf *cf = NULL;
+ struct flb_cf_section *section;
+
+#ifndef FLB_HAVE_STATIC_CONF
+ ret = stat(file, &st);
+ if (ret == -1 && errno == ENOENT) {
+ /* Try to resolve the real path (if exists) */
+ if (file[0] == '/') {
+ return NULL;
+ }
+
+ if (config->conf_path) {
+ snprintf(path, PATH_MAX, "%s%s", config->conf_path, file);
+ cfg = path;
+ }
+ }
+ else {
+ cfg = file;
+ }
+ flb_debug("[upstream_ha] opening file %s", cfg);
+ cf = flb_cf_create_from_file(NULL, (char *) cfg);
+#else
+ //DISABLED/FIXME fconf = flb_config_static_open(file);
+#endif
+
+ if (!cf) {
+ return NULL;
+ }
+
+ /* 'upstream' sections are under enum section_type FLB_CF_OTHER */
+ section = flb_cf_section_get_by_name(cf, "upstream");
+ if (!section) {
+ flb_error("[upstream_ha] section name 'upstream' could not be found");
+ flb_cf_destroy(cf);
+ return NULL;
+ }
+
+ /* upstream name */
+ tmp = flb_cf_section_property_get_string(cf, section, "name");
+ if (!tmp) {
+ flb_error("[upstream_ha] missing name for upstream at %s", cfg);
+ flb_cf_destroy(cf);
+ return NULL;
+ }
+
+ ups = flb_upstream_ha_create(tmp);
+ flb_sds_destroy(tmp);
+ if (!ups) {
+ flb_error("[upstream_ha] cannot create context");
+ flb_cf_destroy(cf);
+ return NULL;
+ }
+
+ /* 'node' sections */
+ mk_list_foreach(head, &cf->sections) {
+ section = mk_list_entry(head, struct flb_cf_section, _head);
+ if (strcasecmp(section->name, "node") != 0) {
+ continue;
+ }
+
+ /* Read section info and create a Node context */
+ node = create_node(c, cf, section, config);
+ if (!node) {
+ flb_error("[upstream_ha] cannot register node on upstream '%s'",
+ tmp);
+ flb_upstream_ha_destroy(ups);
+ flb_cf_destroy(cf);
+ return NULL;
+ }
+
+ flb_upstream_ha_node_add(ups, node);
+ c++;
+ }
+
+ if (c == 0) {
+ flb_error("[upstream_ha] no nodes defined");
+ flb_upstream_ha_destroy(ups);
+ flb_cf_destroy(cf);
+ return NULL;
+ }
+
+ flb_cf_destroy(cf);
+ return ups;
+}
diff --git a/fluent-bit/src/flb_upstream_node.c b/fluent-bit/src/flb_upstream_node.c
new file mode 100644
index 000000000..b88d1e06f
--- /dev/null
+++ b/fluent-bit/src/flb_upstream_node.c
@@ -0,0 +1,213 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_io.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/tls/flb_tls.h>
+#include <fluent-bit/flb_hash_table.h>
+#include <fluent-bit/flb_upstream_node.h>
+
+/* Create a new Upstream Node context */
+struct flb_upstream_node *flb_upstream_node_create(flb_sds_t name, flb_sds_t host,
+ flb_sds_t port,
+ int tls, int tls_verify,
+ int tls_debug,
+ const char *tls_vhost,
+ const char *tls_ca_path,
+ const char *tls_ca_file,
+ const char *tls_crt_file,
+ const char *tls_key_file,
+ const char *tls_key_passwd,
+ struct flb_hash_table *ht,
+ struct flb_config *config)
+{
+ int i_port;
+ int io_flags;
+ char tmp[255];
+ struct flb_upstream_node *node;
+
+ if (!host || !port) {
+ return NULL;
+ }
+
+ /* port */
+ i_port = atoi(port);
+
+ /* Allocate node context */
+ node = flb_calloc(1, sizeof(struct flb_upstream_node));
+ if (!node) {
+ flb_errno();
+ return NULL;
+ }
+
+ /* Set node name */
+ if (!name) {
+ /* compose a name using given host and port */
+ snprintf(tmp, sizeof(tmp) - 1, "%s:%s", host, port);
+ node->name = flb_sds_create(tmp);
+ }
+ else {
+ node->name = name;
+ }
+
+ /* host */
+ node->host = host;
+ if (!node->host) {
+ flb_upstream_node_destroy(node);
+ return NULL;
+ }
+
+ /* port */
+ node->port = port;
+ if (!node->port) {
+ flb_upstream_node_destroy(node);
+ return NULL;
+ }
+
+#ifdef FLB_HAVE_TLS
+
+ /* tls: ca path */
+ node->tls_ca_path = flb_sds_create(tls_ca_path);
+ if (!node->tls_ca_path) {
+ flb_upstream_node_destroy(node);
+ return NULL;
+ }
+
+ /* tls: ca file */
+ node->tls_ca_file = flb_sds_create(tls_ca_file);
+ if (!node->tls_ca_file) {
+ flb_upstream_node_destroy(node);
+ return NULL;
+ }
+
+ /* tls: crt file */
+ node->tls_crt_file = flb_sds_create(tls_crt_file);
+ if (!node->tls_crt_file) {
+ flb_upstream_node_destroy(node);
+ return NULL;
+ }
+
+ /* tls: key file */
+ node->tls_key_file = flb_sds_create(tls_key_file);
+ if (!node->tls_key_file) {
+ flb_upstream_node_destroy(node);
+ return NULL;
+ }
+
+ /* tls: key passwd */
+ node->tls_key_passwd = flb_sds_create(tls_key_passwd);
+ if (!node->tls_key_passwd) {
+ flb_upstream_node_destroy(node);
+ return NULL;
+ }
+#endif
+
+ /* hash table */
+ node->ht = ht;
+
+#ifdef FLB_HAVE_TLS
+ /* TLS setup */
+ if (tls == FLB_TRUE) {
+ node->tls = flb_tls_create(FLB_TLS_CLIENT_MODE,
+ tls_verify,
+ tls_debug,
+ tls_vhost,
+ tls_ca_path,
+ tls_ca_file,
+ tls_crt_file,
+ tls_key_file,
+ tls_key_passwd);
+ if (!node->tls) {
+ flb_error("[upstream_node] error initializing TLS context "
+ "on node '%s'", name);
+ flb_upstream_node_destroy(node);
+ return NULL;
+ }
+ node->tls_enabled = FLB_TRUE;
+ }
+#endif
+
+
+ /* Upstream flags */
+ if (tls == FLB_TRUE) {
+ io_flags = FLB_IO_TLS;
+ }
+ else {
+ io_flags = FLB_IO_TCP;
+ }
+
+ /* upstream context */
+ node->u = flb_upstream_create(config, node->host, i_port,
+ io_flags, node->tls);
+ if (!node->u) {
+ flb_error("[upstream_node] error creating upstream context "
+ "for node '%s'", name);
+ flb_upstream_node_destroy(node);
+ return NULL;
+ }
+
+ return node;
+}
+
+const char *flb_upstream_node_get_property(const char *prop,
+ struct flb_upstream_node *node)
+{
+ int ret;
+ int len;
+ void *value;
+ size_t size;
+
+ len = strlen(prop);
+
+ ret = flb_hash_table_get(node->ht, prop, len, &value, &size);
+ if (ret == -1) {
+ return NULL;
+ }
+
+ return (char *) value;
+}
+
+void flb_upstream_node_destroy(struct flb_upstream_node *node)
+{
+ flb_sds_destroy(node->name);
+ flb_sds_destroy(node->host);
+ flb_sds_destroy(node->port);
+
+ flb_hash_table_destroy(node->ht);
+ if (node->u) {
+ flb_upstream_destroy(node->u);
+ }
+
+#ifdef FLB_HAVE_TLS
+ flb_sds_destroy(node->tls_ca_path);
+ flb_sds_destroy(node->tls_ca_file);
+ flb_sds_destroy(node->tls_crt_file);
+ flb_sds_destroy(node->tls_key_file);
+ flb_sds_destroy(node->tls_key_passwd);
+ if (node->tls) {
+ flb_tls_destroy(node->tls);
+ }
+#endif
+
+ /* note: node link must be handled by the caller before this call */
+ flb_free(node);
+}
diff --git a/fluent-bit/src/flb_uri.c b/fluent-bit/src/flb_uri.c
new file mode 100644
index 000000000..48a8b0d88
--- /dev/null
+++ b/fluent-bit/src/flb_uri.c
@@ -0,0 +1,186 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <stdlib.h>
+#include <monkey/mk_core.h>
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_uri.h>
+#include <fluent-bit/flb_utils.h>
+
+/*
+ * Perform URI encoding for the given string. Always returns a new sds buffer,
+ * if it fails it returns NULL.
+ */
+flb_sds_t flb_uri_encode(const char *uri, size_t len)
+{
+ int i;
+ flb_sds_t buf = NULL;
+ flb_sds_t tmp = NULL;
+
+ buf = flb_sds_create_size(len * 2);
+ if (!buf) {
+ flb_error("[uri] cannot allocate buffer for URI encoding");
+ return NULL;
+ }
+
+ for (i = 0; i < len; i++) {
+ if (flb_uri_to_encode(uri[i]) == FLB_TRUE) {
+ tmp = flb_sds_printf(&buf, "%%%02X", (unsigned char) *(uri + i));
+ if (!tmp) {
+ flb_error("[uri] error formatting special character");
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ continue;
+ }
+
+ /* Direct assignment, just copy the character */
+ if (buf) {
+ tmp = flb_sds_cat(buf, uri + i, 1);
+ if (!tmp) {
+ flb_error("[uri] error composing outgoing buffer");
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ buf = tmp;
+ }
+ }
+
+ return buf;
+}
+
+/* Retrieve a given field based on it expected position in the URI */
+struct flb_uri_field *flb_uri_get(struct flb_uri *uri, int pos)
+{
+ if (pos < 0) {
+ flb_trace("[uri] negative pos");
+ return NULL;
+ }
+
+ if (pos >= FLB_URI_MAX || pos > uri->count) {
+ flb_trace("[uri] requested position > FLB_URI_MAX");
+ return NULL;
+ }
+
+ return &uri->map[pos];
+}
+
+/*
+ * Given a 'URI' string, split the strings separated by a slash and create a
+ * context.
+ */
+struct flb_uri *flb_uri_create(const char *full_uri)
+{
+ int end;
+ unsigned int len;
+ unsigned int val_len;
+ unsigned int i = 0;
+ char *val;
+ size_t uri_size;
+ void *p;
+ struct flb_uri_field *field;
+ struct flb_uri *uri;
+
+ /* Set the required memory space */
+ uri_size = sizeof(struct flb_uri);
+ uri_size += (sizeof(struct flb_uri_field) * FLB_URI_MAX);
+
+ p = flb_calloc(1, uri_size);
+ if (!p) {
+ perror("malloc");
+ return NULL;
+ }
+
+ /* Link the 'map' */
+ uri = p;
+ p = ((char *) p) + sizeof(struct flb_uri);
+ uri->map = p;
+
+ /* Initialize fields list */
+ mk_list_init(&uri->list);
+ uri->count = 0;
+
+ len = strlen(full_uri);
+ while (i < len && uri->count < FLB_URI_MAX) {
+ end = mk_string_char_search(full_uri + i, '/', len - i);
+
+ if (end >= 0 && end + i < len) {
+ end += i;
+
+ if (i == (unsigned int) end) {
+ i++;
+ continue;
+ }
+
+ val = mk_string_copy_substr(full_uri, i, end);
+ val_len = end - i;
+ }
+ else {
+ val = mk_string_copy_substr(full_uri, i, len);
+ val_len = len - i;
+ end = len;
+
+ }
+
+ /* Alloc node */
+ field = &uri->map[uri->count];
+ field->value = flb_strdup(val);
+ field->length = val_len;
+ mk_list_add(&field->_head, &uri->list);
+ i = end + 1;
+ uri->count++;
+
+ mk_mem_free(val);
+ }
+
+ uri->full = flb_strdup(full_uri);
+ return uri;
+}
+
+/* Destroy an URI context and it resources associated */
+void flb_uri_destroy(struct flb_uri *uri)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_uri_field *field;
+
+ mk_list_foreach_safe(head, tmp, &uri->list) {
+ field = mk_list_entry(head, struct flb_uri_field, _head);
+ mk_list_del(&field->_head);
+ flb_free(field->value);
+ }
+
+ flb_free(uri->full);
+ flb_free(uri);
+}
+
+void flb_uri_dump(struct flb_uri *uri)
+{
+ int i;
+ struct flb_uri_field *f;
+
+ for (i = 0; i < uri->count; i++) {
+ f = &uri->map[i];
+ printf("[%i] length=%lu value='%s'\n",
+ i, f->length, f->value);
+ }
+}
diff --git a/fluent-bit/src/flb_utils.c b/fluent-bit/src/flb_utils.c
new file mode 100644
index 000000000..c2b2f58a6
--- /dev/null
+++ b/fluent-bit/src/flb_utils.c
@@ -0,0 +1,1433 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <ctype.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <msgpack.h>
+
+#include <monkey/mk_core.h>
+#include <fluent-bit/flb_macros.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_error.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_utf8.h>
+
+#ifdef FLB_HAVE_AWS_ERROR_REPORTER
+#include <fluent-bit/aws/flb_aws_error_reporter.h>
+
+extern struct flb_aws_error_reporter *error_reporter;
+#endif
+
+#ifdef FLB_HAVE_OPENSSL
+#include <openssl/rand.h>
+#endif
+
+/*
+ * The following block descriptor describes the private use unicode character range
+ * used for denoting invalid utf-8 fragments. Invalid fragment 0xCE would become
+ * utf-8 codepoint U+E0CE if FLB_UTILS_FRAGMENT_PRIVATE_BLOCK_DESCRIPTOR is set to
+ * E0 since U+E0CE = U+<FLB_UTILS_FRAGMENT_PRIVATE_BLOCK_DESCRIPTOR><HEX_FRAGMENT>
+ */
+#define FLB_UTILS_FRAGMENT_PRIVATE_BLOCK_DESCRIPTOR 0xE0
+
+void flb_utils_error(int err)
+{
+ char *msg = NULL;
+
+ switch (err) {
+ case FLB_ERR_CFG_FILE:
+ msg = "could not open configuration file";
+ break;
+ case FLB_ERR_CFG_FILE_FORMAT:
+ msg = "configuration file contains format errors";
+ break;
+ case FLB_ERR_CFG_FILE_STOP:
+ msg = "configuration file contains errors";
+ break;
+ case FLB_ERR_CFG_FLUSH:
+ msg = "invalid flush value";
+ break;
+ case FLB_ERR_CFG_FLUSH_CREATE:
+ msg = "could not create timer for flushing";
+ break;
+ case FLB_ERR_CFG_FLUSH_REGISTER:
+ msg = "could not register timer for flushing";
+ break;
+ case FLB_ERR_INPUT_INVALID:
+ msg = "invalid input type";
+ break;
+ case FLB_ERR_INPUT_UNDEF:
+ msg = "no input(s) have been defined";
+ break;
+ case FLB_ERR_INPUT_UNSUP:
+ msg = "unsupported Input";
+ break;
+ case FLB_ERR_OUTPUT_UNDEF:
+ msg = "you must specify an output target";
+ break;
+ case FLB_ERR_OUTPUT_INVALID:
+ msg = "invalid output target";
+ break;
+ case FLB_ERR_OUTPUT_UNIQ:
+ msg = "just one output type is supported";
+ break;
+ case FLB_ERR_FILTER_INVALID:
+ msg = "invalid filter plugin";
+ break;
+ case FLB_ERR_CFG_PARSER_FILE:
+ msg = "could not open parser configuration file";
+ break;
+ case FLB_ERR_JSON_INVAL:
+ msg = "invalid JSON string";
+ break;
+ case FLB_ERR_JSON_PART:
+ msg = "truncated JSON string";
+ break;
+ case FLB_ERR_CORO_STACK_SIZE:
+ msg = "invalid coroutine stack size";
+ break;
+ case FLB_ERR_CFG_PLUGIN_FILE:
+ msg = "plugins_file not found";
+ break;
+ case FLB_ERR_RELOADING_IN_PROGRESS:
+ msg = "reloading in progress";
+ break;
+ default:
+ flb_error("(error message is not defined. err=%d)", err);
+ }
+
+ if (!msg) {
+ fprintf(stderr,
+ "%sError%s: undefined. Aborting",
+ ANSI_BOLD ANSI_RED, ANSI_RESET);
+ #ifdef FLB_HAVE_AWS_ERROR_REPORTER
+ if (is_error_reporting_enabled()) {
+ flb_aws_error_reporter_write(error_reporter, "Error: undefined. Aborting\n");
+ }
+ #endif
+
+ }
+ else {
+ flb_error("%s, aborting.", msg);
+ #ifdef FLB_HAVE_AWS_ERROR_REPORTER
+ if (is_error_reporting_enabled()) {
+ flb_aws_error_reporter_write(error_reporter, msg);
+ }
+ #endif
+ }
+
+ if (err <= FLB_ERR_FILTER_INVALID) {
+ exit(EXIT_FAILURE);
+ }
+}
+
+/* Custom error */
+void flb_utils_error_c(const char *msg)
+{
+ fprintf(stderr,
+ "%sError%s: %s. Aborting\n\n",
+ ANSI_BOLD ANSI_RED, ANSI_RESET, msg);
+ exit(EXIT_FAILURE);
+}
+
+void flb_utils_warn_c(const char *msg)
+{
+ fprintf(stderr,
+ "%sWarning%s: %s",
+ ANSI_BOLD ANSI_YELLOW, ANSI_RESET, msg);
+}
+
+#ifdef FLB_HAVE_FORK
+/* Run current process in background mode */
+int flb_utils_set_daemon(struct flb_config *config)
+{
+ pid_t pid;
+
+ if ((pid = fork()) < 0){
+ flb_error("Failed creating to switch to daemon mode (fork failed)");
+ exit(EXIT_FAILURE);
+ }
+
+ if (pid > 0) { /* parent */
+ exit(EXIT_SUCCESS);
+ }
+
+ /* set files mask */
+ umask(0);
+
+ /* Create new session */
+ setsid();
+
+ if (chdir("/") < 0) { /* make sure we can unmount the inherited filesystem */
+ flb_error("Unable to unmount the inherited filesystem");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Our last STDOUT messages */
+ flb_info("switching to background mode (PID=%ld)", (long) getpid());
+
+ fclose(stderr);
+ fclose(stdout);
+
+ return 0;
+}
+#endif
+
+void flb_utils_print_setup(struct flb_config *config)
+{
+ struct mk_list *head;
+ struct mk_list *head_tmp;
+ struct flb_input_plugin *plugin;
+ struct flb_input_collector *collector;
+ struct flb_input_instance *in;
+ struct flb_filter_instance *f;
+ struct flb_output_instance *out;
+
+ flb_info("Configuration:");
+
+ /* general */
+ flb_info(" flush time | %f seconds", config->flush);
+ flb_info(" grace | %i seconds", config->grace);
+ flb_info(" daemon | %i", config->daemon);
+
+ /* Inputs */
+ flb_info("___________");
+ flb_info(" inputs:");
+ mk_list_foreach(head, &config->inputs) {
+ in = mk_list_entry(head, struct flb_input_instance, _head);
+ flb_info(" %s", in->p->name);
+ }
+
+ /* Filters */
+ flb_info("___________");
+ flb_info(" filters:");
+ mk_list_foreach(head, &config->filters) {
+ f = mk_list_entry(head, struct flb_filter_instance, _head);
+ flb_info(" %s", f->name);
+ }
+
+ /* Outputs */
+ flb_info("___________");
+ flb_info(" outputs:");
+ mk_list_foreach(head, &config->outputs) {
+ out = mk_list_entry(head, struct flb_output_instance, _head);
+ flb_info(" %s", out->name);
+ }
+
+ /* Collectors */
+ flb_info("___________");
+ flb_info(" collectors:");
+ mk_list_foreach(head, &config->inputs) {
+ in = mk_list_entry(head, struct flb_input_instance, _head);
+ mk_list_foreach(head_tmp, &in->collectors) {
+ collector = mk_list_entry(head_tmp, struct flb_input_collector, _head);
+ plugin = collector->instance->p;
+
+ if (collector->seconds > 0) {
+ flb_info("[%s %lus,%luns] ",
+ plugin->name,
+ collector->seconds,
+ collector->nanoseconds);
+ }
+ else {
+ flb_info(" [%s] ", plugin->name);
+ }
+ }
+ }
+}
+
+/*
+ * quoted_string_len returns the length of a quoted string, not including the quotes.
+ */
+static int quoted_string_len(const char *str)
+{
+ int len = 0;
+ char quote = *str++; /* Consume the quote character. */
+
+ while (quote != 0) {
+ char c = *str++;
+ switch (c) {
+ case '\0':
+ /* Error: string ends before end-quote was seen. */
+ return -1;
+ case '\\':
+ /* Skip escaped quote or \\. */
+ if (*str == quote || *str == '\\') {
+ str++;
+ }
+ break;
+ case '\'':
+ case '"':
+ /* End-quote seen: stop iterating. */
+ if (c == quote) {
+ quote = 0;
+ }
+ break;
+ default:
+ break;
+ }
+ len++;
+ }
+
+ /* Go back one character to ignore end-quote */
+ len--;
+
+ return len;
+}
+
+/*
+ * next_token returns the next token in the string 'str' delimited by 'separator'.
+ * 'out' is set to the beginning of the token.
+ * 'out_len' is set to the length of the token.
+ * 'parse_quotes' is set to FLB_TRUE when quotes shall be considered when tokenizing the 'str'.
+ * The function returns offset to next token in the string.
+ */
+static int next_token(const char *str, int separator, char **out, int *out_len, int parse_quotes) {
+ const char *token_in = str;
+ char *token_out;
+ int next_separator = 0;
+ int quote = 0; /* Parser state: 0 not inside quoted string, or '"' or '\'' when inside quoted string. */
+ int len = 0;
+ int i;
+
+ /* Skip leading separators. */
+ while (*token_in == separator) {
+ token_in++;
+ }
+
+ /* Should quotes be parsed? Or is token quoted? If not, copy until separator or the end of string. */
+ if (parse_quotes == FLB_FALSE || (*token_in != '"' && *token_in != '\'')) {
+ len = (int)strlen(token_in);
+ next_separator = mk_string_char_search(token_in, separator, len);
+ if (next_separator > 0) {
+ len = next_separator;
+ }
+ *out_len = len;
+ *out = mk_string_copy_substr(token_in, 0, len);
+ if (*out == NULL) {
+ return -1;
+ }
+
+ return (int)(token_in - str) + len;
+ }
+
+ /* Token is quoted. */
+
+ len = quoted_string_len(token_in);
+ if (len < 0) {
+ return -1;
+ }
+
+ /* Consume the quote character. */
+ quote = *token_in++;
+
+ token_out = flb_malloc(len + 1);
+ if (!token_out) {
+ return -1;
+ }
+
+ /* Copy the token */
+ for (i = 0; i < len; i++) {
+ /* Handle escapes when inside quoted token:
+ * \" -> "
+ * \' -> '
+ * \\ -> \
+ */
+ if (*token_in == '\\' && (token_in[1] == quote || token_in[1] == '\\')) {
+ token_in++;
+ }
+ token_out[i] = *token_in++;
+ }
+ token_out[i] = '\0';
+
+ *out = token_out;
+ *out_len = len;
+
+ return (int)(token_in - str);
+}
+
+
+static struct mk_list *split(const char *line, int separator, int max_split, int quoted)
+{
+ int i = 0;
+ int count = 0;
+ int val_len;
+ int len;
+ int end;
+ char *val;
+ struct mk_list *list;
+ struct flb_split_entry *new;
+
+ if (!line) {
+ return NULL;
+ }
+
+ list = flb_malloc(sizeof(struct mk_list));
+ if (!list) {
+ flb_errno();
+ return NULL;
+ }
+ mk_list_init(list);
+
+ len = strlen(line);
+ while (i < len) {
+ end = next_token(line + i, separator, &val, &val_len, quoted);
+ if (end == -1) {
+ flb_error("Parsing failed: %s", line);
+ flb_utils_split_free(list);
+ return NULL;
+ }
+
+ /* Update last position */
+ i += end;
+
+ /* Create new entry */
+ new = flb_malloc(sizeof(struct flb_split_entry));
+ if (!new) {
+ flb_errno();
+ flb_free(val);
+ flb_utils_split_free(list);
+ return NULL;
+ }
+ new->value = val;
+ new->len = val_len;
+ new->last_pos = i;
+ mk_list_add(&new->_head, list);
+ count++;
+
+ /* Update index for next loop */
+ i++;
+
+ /*
+ * If the counter exceeded the maximum specified and there
+ * are still remaining bytes, append those bytes in a new
+ * and last entry.
+ */
+ if (count >= max_split && max_split > 0 && i < len) {
+ new = flb_malloc(sizeof(struct flb_split_entry));
+ if (!new) {
+ flb_errno();
+ flb_utils_split_free(list);
+ return NULL;
+ }
+ new->value = mk_string_copy_substr(line, i, len);
+ new->len = len - i;
+ mk_list_add(&new->_head, list);
+ break;
+ }
+ }
+
+ return list;
+}
+
+struct mk_list *flb_utils_split_quoted(const char *line, int separator, int max_split)
+{
+ return split(line, separator, max_split, FLB_TRUE);
+}
+
+struct mk_list *flb_utils_split(const char *line, int separator, int max_split)
+{
+ return split(line, separator, max_split, FLB_FALSE);
+}
+
+
+void flb_utils_split_free_entry(struct flb_split_entry *entry)
+{
+ mk_list_del(&entry->_head);
+ flb_free(entry->value);
+ flb_free(entry);
+}
+
+void flb_utils_split_free(struct mk_list *list)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_split_entry *entry;
+
+ mk_list_foreach_safe(head, tmp, list) {
+ entry = mk_list_entry(head, struct flb_split_entry, _head);
+ flb_utils_split_free_entry(entry);
+ }
+
+ flb_free(list);
+}
+
+/* When a timer expires, it needs some handling */
+int flb_utils_timer_consume(flb_pipefd_t fd)
+{
+ int ret;
+ uint64_t val;
+
+ ret = flb_pipe_r(fd, &val, sizeof(val));
+ if (ret == -1) {
+ flb_errno();
+ return -1;
+ }
+
+#ifdef __linux__
+ /* A timer on linux must return an unisgned 64 bit number */
+ if (ret == 0) {
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+int flb_utils_pipe_byte_consume(flb_pipefd_t fd)
+{
+ int ret;
+ uint64_t val;
+
+ ret = flb_pipe_r(fd, &val, sizeof(val));
+ if (ret == -1) {
+ flb_errno();
+ return -1;
+ }
+
+ return 0;
+}
+
+int64_t flb_utils_size_to_bytes(const char *size)
+{
+ int i;
+ int len;
+ int plen = 0;
+ int64_t val;
+ char c;
+ char tmp[3] = {0};
+ int64_t KB = 1000;
+ int64_t MB = 1000 * KB;
+ int64_t GB = 1000 * MB;
+
+ if (!size) {
+ return -1;
+ }
+
+ if (strcasecmp(size, "false") == 0) {
+ return 0;
+ }
+
+ len = strlen(size);
+ val = atoll(size);
+
+ if (len == 0) {
+ return -1;
+ }
+
+ for (i = len - 1; i > 0; i--) {
+ if (isdigit(size[i])) {
+ break;
+ }
+ else {
+ plen++;
+ }
+ }
+
+ if (plen == 0) {
+ return val;
+ }
+ else if (plen > 2) {
+ return -1;
+ }
+
+ for (i = 0; i < plen; i++) {
+ c = size[(len - plen) + i];
+ tmp[i] = toupper(c);
+ }
+
+ if (plen == 2) {
+ if (tmp[1] != 'B') {
+ return -1;
+ }
+ }
+
+ if (tmp[0] == 'K') {
+ /* set upper bound (2**64/KB)/2 to avoid overflows */
+ if (val >= 9223372036854775 || val <= -9223372036854774)
+ {
+ return -1;
+ }
+ return (val * KB);
+ }
+ else if (tmp[0] == 'M') {
+ /* set upper bound (2**64/MB)/2 to avoid overflows */
+ if (val >= 9223372036854 || val <= -9223372036853) {
+ return -1;
+ }
+ return (val * MB);
+ }
+ else if (tmp[0] == 'G') {
+ /* set upper bound (2**64/GB)/2 to avoid overflows */
+ if (val >= 9223372036 || val <= -9223372035) {
+ return -1;
+ }
+ return (val * GB);
+ }
+ else {
+ return -1;
+ }
+
+ return val;
+}
+
+int64_t flb_utils_hex2int(char *hex, int len)
+{
+ int i = 0;
+ int64_t res = 0;
+ char c;
+
+ while ((c = *hex++) && i < len) {
+ /* Ensure no overflow */
+ if (res >= (int64_t)((INT64_MAX/0x10) - 0xff)) {
+ return -1;
+ }
+
+ res *= 0x10;
+
+ if (c >= 'a' && c <= 'f') {
+ res += (c - 0x57);
+ }
+ else if (c >= 'A' && c <= 'F') {
+ res += (c - 0x37);
+ }
+ else if (c >= '0' && c <= '9') {
+ res += (c - 0x30);
+ }
+ else {
+ return -1;
+ }
+ i++;
+ }
+
+ if (res < 0) {
+ return -1;
+ }
+
+ return res;
+}
+
+int flb_utils_time_to_seconds(const char *time)
+{
+ int len;
+ size_t val;
+
+ len = strlen(time);
+ if (len == 0) {
+ return 0;
+ }
+ val = atoi(time);
+
+ /* String time to seconds */
+ if (time[len - 1] == 'D' || time[len - 1] == 'd') {
+ val *= 86400;
+ }
+ if (time[len - 1] == 'H' || time[len - 1] == 'h') {
+ val *= 3600;
+ }
+ else if (time[len - 1] == 'M' || time[len - 1] == 'm') {
+ val *= 60;
+ }
+
+ return val;
+}
+
+int flb_utils_bool(const char *val)
+{
+ if (strcasecmp(val, "true") == 0 ||
+ strcasecmp(val, "on") == 0 ||
+ strcasecmp(val, "yes") == 0) {
+ return FLB_TRUE;
+ }
+ else if (strcasecmp(val, "false") == 0 ||
+ strcasecmp(val, "off") == 0 ||
+ strcasecmp(val, "no") == 0) {
+ return FLB_FALSE;
+ }
+
+ return -1;
+}
+
+/* Convert a 'string' time seconds.nanoseconds to int and long values */
+int flb_utils_time_split(const char *time, int *sec, long *nsec)
+{
+ char *p;
+ char *end;
+ long val = 0;
+
+ errno = 0;
+ val = strtol(time, &end, 10);
+ if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
+ || (errno != 0 && val == 0)) {
+ flb_errno();
+ return -1;
+ }
+ if (end == time) {
+ return -1;
+ }
+ *sec = (int) val;
+
+ /* Try to find subseconds */
+ *nsec = 0;
+ p = strchr(time, '.');
+ if (p) {
+ p += 1;
+ val = strtol(p, &end, 10);
+ if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
+ || (errno != 0 && val == 0)) {
+ flb_errno();
+ return -1;
+ }
+ if (end == p) {
+ return -1;
+ }
+ *nsec = val;
+ }
+
+ return 0;
+}
+
+void flb_utils_bytes_to_human_readable_size(size_t bytes,
+ char *out_buf, size_t size)
+{
+ unsigned long i;
+ unsigned long u = 1024;
+ static const char *__units[] = {
+ "b", "K", "M", "G",
+ "T", "P", "E", "Z", "Y", NULL
+ };
+
+ for (i = 0; __units[i] != NULL; i++) {
+ if ((bytes / u) == 0) {
+ break;
+ }
+ u *= 1024;
+ }
+ if (!i) {
+ snprintf(out_buf, size, "%lu%s", (long unsigned int) bytes, __units[0]);
+ }
+ else {
+ float fsize = (float) ((double) bytes / (u / 1024));
+ snprintf(out_buf, size, "%.1f%s", fsize, __units[i]);
+ }
+}
+
+
+static inline void encoded_to_buf(char *out, const char *in, int len)
+{
+ int i;
+ char *p = out;
+
+ for (i = 0; i < len; i++) {
+ *p++ = in[i];
+ }
+}
+
+/*
+ * Write string pointed by 'str' to the destination buffer 'buf'. It's make sure
+ * to escape sepecial characters and convert utf-8 byte characters to string
+ * representation.
+ */
+int flb_utils_write_str(char *buf, int *off, size_t size,
+ const char *str, size_t str_len)
+{
+ int i;
+ int b;
+ int ret;
+ int written = 0;
+ int required;
+ int len;
+ int hex_bytes;
+ int is_valid;
+ int utf_sequence_number;
+ int utf_sequence_length;
+ uint32_t codepoint;
+ uint32_t state = 0;
+ char tmp[16];
+ size_t available;
+ uint32_t c;
+ char *p;
+ uint8_t *s;
+
+ available = (size - *off);
+ required = str_len;
+ if (available <= required) {
+ return FLB_FALSE;
+ }
+
+ p = buf + *off;
+ for (i = 0; i < str_len; i++) {
+ if ((available - written) < 2) {
+ return FLB_FALSE;
+ }
+
+ c = (uint32_t) str[i];
+ if (c == '\"') {
+ *p++ = '\\';
+ *p++ = '\"';
+ }
+ else if (c == '\\') {
+ *p++ = '\\';
+ *p++ = '\\';
+ }
+ else if (c == '\n') {
+ *p++ = '\\';
+ *p++ = 'n';
+ }
+ else if (c == '\r') {
+ *p++ = '\\';
+ *p++ = 'r';
+ }
+ else if (c == '\t') {
+ *p++ = '\\';
+ *p++ = 't';
+ }
+ else if (c == '\b') {
+ *p++ = '\\';
+ *p++ = 'b';
+ }
+ else if (c == '\f') {
+ *p++ = '\\';
+ *p++ = 'f';
+ }
+ else if (c < 32 || c == 0x7f) {
+ if ((available - written) < 6) {
+ return FLB_FALSE;
+ }
+ len = snprintf(tmp, sizeof(tmp) - 1, "\\u%.4hhx", (unsigned char) c);
+ if ((available - written) < len) {
+ return FLB_FALSE;
+ }
+ encoded_to_buf(p, tmp, len);
+ p += len;
+ }
+ else if (c >= 0x80 && c <= 0xFFFF) {
+ hex_bytes = flb_utf8_len(str + i);
+ if (available - written < 6) {
+ return FLB_FALSE;
+ }
+
+ if (i + hex_bytes > str_len) {
+ break; /* skip truncated UTF-8 */
+ }
+
+ state = FLB_UTF8_ACCEPT;
+ codepoint = 0;
+
+ for (b = 0; b < hex_bytes; b++) {
+ s = (unsigned char *) str + i + b;
+ ret = flb_utf8_decode(&state, &codepoint, *s);
+ if (ret == 0) {
+ break;
+ }
+ }
+
+ if (state != FLB_UTF8_ACCEPT) {
+ /* Invalid UTF-8 hex, just skip utf-8 bytes */
+ flb_warn("[pack] invalid UTF-8 bytes found, skipping bytes");
+ }
+ else {
+ len = snprintf(tmp, sizeof(tmp) - 1, "\\u%.4x", codepoint);
+ if ((available - written) < len) {
+ return FLB_FALSE;
+ }
+ encoded_to_buf(p, tmp, len);
+ p += len;
+ }
+ i += (hex_bytes - 1);
+ }
+ else if (c > 0xFFFF) {
+ utf_sequence_length = flb_utf8_len(str + i);
+
+ if (i + utf_sequence_length > str_len) {
+ break; /* skip truncated UTF-8 */
+ }
+
+ is_valid = FLB_TRUE;
+ for (utf_sequence_number = 0; utf_sequence_number < utf_sequence_length;
+ utf_sequence_number++) {
+ /* Leading characters must start with bits 11 */
+ if (utf_sequence_number == 0 && ((str[i] & 0xC0) != 0xC0)) {
+ /* Invalid unicode character. replace */
+ flb_debug("[pack] unexpected UTF-8 leading byte, "
+ "substituting character with replacement character");
+ tmp[utf_sequence_number] = str[i];
+ ++i; /* Consume invalid leading byte */
+ utf_sequence_length = utf_sequence_number + 1;
+ is_valid = FLB_FALSE;
+ break;
+ }
+ /* Trailing characters must start with bits 10 */
+ else if (utf_sequence_number > 0 && ((str[i] & 0xC0) != 0x80)) {
+ /* Invalid unicode character. replace */
+ flb_debug("[pack] unexpected UTF-8 continuation byte, "
+ "substituting character with replacement character");
+ /* This byte, i, is the start of the next unicode character */
+ utf_sequence_length = utf_sequence_number;
+ is_valid = FLB_FALSE;
+ break;
+ }
+
+ tmp[utf_sequence_number] = str[i];
+ ++i;
+ }
+ --i;
+
+ if (is_valid) {
+ if (available - written < utf_sequence_length) {
+ return FLB_FALSE;
+ }
+
+ encoded_to_buf(p, tmp, utf_sequence_length);
+ p += utf_sequence_length;
+ }
+ else {
+ if (available - written < utf_sequence_length * 3) {
+ return FLB_FALSE;
+ }
+
+ /*
+ * Utf-8 sequence is invalid. Map fragments to private use area
+ * codepoints in range:
+ * 0x<FLB_UTILS_FRAGMENT_PRIVATE_BLOCK_DESCRIPTOR>00 to
+ * 0x<FLB_UTILS_FRAGMENT_PRIVATE_BLOCK_DESCRIPTOR>FF
+ */
+ for (b = 0; b < utf_sequence_length; ++b) {
+ /*
+ * Utf-8 private block invalid hex mapping. Format unicode charpoint
+ * in the following format:
+ *
+ * +--------+--------+--------+
+ * |1110PPPP|10PPPPHH|10HHHHHH|
+ * +--------+--------+--------+
+ *
+ * Where:
+ * P is FLB_UTILS_FRAGMENT_PRIVATE_BLOCK_DESCRIPTOR bits (1 byte)
+ * H is Utf-8 fragment hex bits (1 byte)
+ * 1 is bit 1
+ * 0 is bit 0
+ */
+
+ /* unicode codepoint start */
+ *p = 0xE0;
+
+ /* print unicode private block header first 4 bits */
+ *p |= FLB_UTILS_FRAGMENT_PRIVATE_BLOCK_DESCRIPTOR >> 4;
+ ++p;
+
+ /* unicode codepoint middle */
+ *p = 0x80;
+
+ /* print end of unicode private block header last 4 bits */
+ *p |= ((FLB_UTILS_FRAGMENT_PRIVATE_BLOCK_DESCRIPTOR << 2) & 0x3f);
+
+ /* print hex fragment first 2 bits */
+ *p |= (tmp[b] >> 6) & 0x03;
+ ++p;
+
+ /* unicode codepoint middle */
+ *p = 0x80;
+
+ /* print hex fragment last 6 bits */
+ *p |= tmp[b] & 0x3f;
+ ++p;
+ }
+ }
+ }
+ else {
+ *p++ = c;
+ }
+ written = (p - (buf + *off));
+ }
+
+ *off += written;
+
+ return FLB_TRUE;
+}
+
+
+int flb_utils_write_str_buf(const char *str, size_t str_len, char **out, size_t *out_size)
+{
+ int ret;
+ int off;
+ char *tmp;
+ char *buf;
+ size_t s;
+
+ s = str_len + 1;
+ buf = flb_malloc(s);
+ if (!buf) {
+ flb_errno();
+ return -1;
+ }
+
+ while (1) {
+ off = 0;
+ ret = flb_utils_write_str(buf, &off, s, str, str_len);
+ if (ret == FLB_FALSE) {
+ s += 256;
+ tmp = flb_realloc(buf, s);
+ if (!tmp) {
+ flb_errno();
+ flb_free(buf);
+ return -1;
+ }
+ buf = tmp;
+ }
+ else {
+ /* done */
+ break;
+ }
+ }
+
+ *out = buf;
+ *out_size = off;
+ return 0;
+}
+
+static char *flb_copy_host(const char *string, int pos_init, int pos_end)
+{
+ if (string[pos_init] == '[') { /* IPv6 */
+ if (string[pos_end-1] != ']')
+ return NULL;
+
+ return mk_string_copy_substr(string, pos_init + 1, pos_end - 1);
+ }
+ else
+ return mk_string_copy_substr(string, pos_init, pos_end);
+}
+
+int flb_utils_url_split(const char *in_url, char **out_protocol,
+ char **out_host, char **out_port, char **out_uri)
+{
+ char *protocol = NULL;
+ char *host = NULL;
+ char *port = NULL;
+ char *uri = NULL;
+ char *p;
+ char *tmp;
+ char *sep;
+
+ /* Protocol */
+ p = strstr(in_url, "://");
+ if (!p) {
+ return -1;
+ }
+ if (p == in_url) {
+ return -1;
+ }
+
+ protocol = mk_string_copy_substr(in_url, 0, p - in_url);
+ if (!protocol) {
+ flb_errno();
+ return -1;
+ }
+
+ /* Advance position after protocol */
+ p += 3;
+
+ /* Check for first '/' */
+ sep = strchr(p, '/');
+ tmp = strchr(p, ':');
+
+ /* Validate port separator is found before the first slash */
+ if (sep && tmp) {
+ if (tmp > sep) {
+ tmp = NULL;
+ }
+ }
+
+ if (tmp) {
+ host = flb_copy_host(p, 0, tmp - p);
+ if (!host) {
+ flb_errno();
+ goto error;
+ }
+ p = tmp + 1;
+
+ /* Look for an optional URI */
+ tmp = strchr(p, '/');
+ if (tmp) {
+ port = mk_string_copy_substr(p, 0, tmp - p);
+ uri = flb_strdup(tmp);
+ }
+ else {
+ port = flb_strdup(p);
+ uri = flb_strdup("/");
+ }
+ }
+ else {
+ tmp = strchr(p, '/');
+ if (tmp) {
+ host = flb_copy_host(p, 0, tmp - p);
+ uri = flb_strdup(tmp);
+ }
+ else {
+ host = flb_copy_host(p, 0, strlen(p));
+ uri = flb_strdup("/");
+ }
+ }
+
+ if (!port) {
+ if (strcmp(protocol, "http") == 0) {
+ port = flb_strdup("80");
+ }
+ else if (strcmp(protocol, "https") == 0) {
+ port = flb_strdup("443");
+ }
+ }
+
+ *out_protocol = protocol;
+ *out_host = host;
+ *out_port = port;
+ *out_uri = uri;
+
+ return 0;
+
+ error:
+ if (protocol) {
+ flb_free(protocol);
+ }
+
+ return -1;
+}
+
+
+/*
+ * flb_utils_proxy_url_split parses a proxy's information from a http_proxy URL.
+ * The URL is in the form like `http://username:password@myproxy.com:8080`.
+ * Note: currently only HTTP is supported.
+ */
+int flb_utils_proxy_url_split(const char *in_url, char **out_protocol,
+ char **out_username, char **out_password,
+ char **out_host, char **out_port)
+{
+ char *protocol = NULL;
+ char *username = NULL;
+ char *password = NULL;
+ char *host = NULL;
+ char *port = NULL;
+ char *proto_sep;
+ char *at_sep;
+ char *tmp;
+
+ /* Parse protocol */
+ proto_sep = strstr(in_url, "://");
+ if (!proto_sep) {
+ return -1;
+ }
+ if (proto_sep == in_url) {
+ return -1;
+ }
+
+ protocol = mk_string_copy_substr(in_url, 0, proto_sep - in_url);
+ if (!protocol) {
+ flb_errno();
+ return -1;
+ }
+ /* Only HTTP proxy is supported for now. */
+ if (strcmp(protocol, "http") != 0) {
+ flb_free(protocol);
+ return -1;
+ }
+
+ /* Advance position after protocol */
+ proto_sep += 3;
+
+ /* Seperate `username:password` and `host:port` */
+ at_sep = strrchr(proto_sep, '@');
+ if (at_sep) {
+ /* Parse username:passwrod part. */
+ tmp = strchr(proto_sep, ':');
+ if (!tmp) {
+ flb_free(protocol);
+ return -1;
+ }
+ username = mk_string_copy_substr(proto_sep, 0, tmp - proto_sep);
+ tmp += 1;
+ password = mk_string_copy_substr(tmp, 0, at_sep - tmp);
+
+ /* Parse host:port part. */
+ at_sep += 1;
+ tmp = strchr(at_sep, ':');
+ if (tmp) {
+ host = flb_copy_host(at_sep, 0, tmp - at_sep);
+ tmp += 1;
+ port = strdup(tmp);
+ }
+ else {
+ host = flb_copy_host(at_sep, 0, strlen(at_sep));
+ port = flb_strdup("80");
+ }
+ }
+ else {
+ /* Parse host:port part. */
+ tmp = strchr(proto_sep, ':');
+ if (tmp) {
+ host = flb_copy_host(proto_sep, 0, tmp - proto_sep);
+ tmp += 1;
+ port = strdup(tmp);
+ }
+ else {
+ host = flb_copy_host(proto_sep, 0, strlen(proto_sep));
+ port = flb_strdup("80");
+ }
+ }
+
+ *out_protocol = protocol;
+ *out_host = host;
+ *out_port = port;
+ if (username) {
+ *out_username = username;
+ }
+ if (password) {
+ *out_password = password;
+ }
+
+ return 0;
+}
+
+
+char *flb_utils_get_os_name()
+{
+#ifdef _WIN64
+ return "win64";
+#elif _WIN32
+ return "win32";
+#elif __APPLE__ || __MACH__
+ return "macos";
+#elif __linux__
+ return "linux";
+#elif __FreeBSD__
+ return "freebsd";
+#elif __unix || __unix__
+ return "unix";
+#else
+ return "other";
+#endif
+}
+
+#ifdef FLB_HAVE_OPENSSL
+int flb_utils_uuid_v4_gen(char *buf)
+{
+ int ret;
+ union {
+ struct {
+ uint32_t time_low;
+ uint16_t time_mid;
+ uint16_t time_hi_and_version;
+ uint8_t clk_seq_hi_res;
+ uint8_t clk_seq_low;
+ uint8_t node[6];
+ };
+ uint8_t __rnd[16];
+ } uuid;
+
+ ret = RAND_bytes(uuid.__rnd, sizeof(uuid));
+
+ uuid.clk_seq_hi_res = (uint8_t) ((uuid.clk_seq_hi_res & 0x3F) | 0x80);
+ uuid.time_hi_and_version = (uint16_t) ((uuid.time_hi_and_version & 0x0FFF) | 0x4000);
+
+ snprintf(buf, 38, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ uuid.time_low, uuid.time_mid, uuid.time_hi_and_version,
+ uuid.clk_seq_hi_res, uuid.clk_seq_low,
+ uuid.node[0], uuid.node[1], uuid.node[2],
+ uuid.node[3], uuid.node[4], uuid.node[5]);
+
+ if (ret == 1) {
+ return 0;
+ }
+
+ return -1;
+}
+#else
+int flb_utils_uuid_v4_gen(char *buf)
+{
+ snprintf(buf, 38, "ddad00f1-3806-46ab-88d1-277a8c863cd6");
+ return 0;
+}
+#endif
+
+int flb_utils_read_file(char *path, char **out_buf, size_t *out_size)
+{
+ int fd;
+ int ret;
+ size_t bytes;
+ struct stat st;
+ flb_sds_t buf;
+ FILE *fp;
+
+ fp = fopen(path, "rb");
+ if (!fp) {
+ return -1;
+ }
+ fd = fileno(fp);
+
+ ret = fstat(fd, &st);
+ if (ret == -1) {
+ flb_errno();
+ fclose(fp);
+ return -1;
+ }
+
+ buf = flb_calloc(1, st.st_size + 1);
+ if (!buf) {
+ flb_errno();
+ fclose(fp);
+ return -1;
+ }
+
+ bytes = fread(buf, st.st_size, 1, fp);
+ if (bytes < 1) {
+ if (ferror(fp)) {
+ flb_errno();
+ }
+ flb_free(buf);
+ fclose(fp);
+ return -1;
+ }
+ fclose(fp);
+
+ *out_buf = buf;
+ *out_size = st.st_size;
+ return 0;
+}
+
+static int machine_id_read_and_sanitize(char *path,
+ char **out_buf, size_t *out_size)
+{
+ int ret;
+ size_t s;
+ char *p;
+ char *buf;
+ size_t bytes;
+
+ ret = flb_utils_read_file(path, &buf, &bytes);
+ if (ret != 0) {
+ return -1;
+ }
+
+ p = buf + bytes - 1;
+ while (*p == ' ' || *p == '\n') {
+ p--;
+ }
+
+ /* set new size */
+ s = p - buf + 1;
+
+ buf[s] = '\0';
+ *out_size = s;
+ *out_buf = buf;
+
+ return 0;
+}
+
+int flb_utils_get_machine_id(char **out_id, size_t *out_size)
+{
+ int ret;
+ char *id;
+ size_t bytes;
+ char *uuid;
+
+#ifdef __linux__
+ char *dbus_var = "/var/lib/dbus/machine-id";
+ char *dbus_etc = "/etc/machine-id";
+
+ /* dbus */
+ if (access(dbus_var, F_OK) == 0) { /* check if the file exists first */
+ ret = machine_id_read_and_sanitize(dbus_var, &id, &bytes);
+ if (ret == 0) {
+ *out_id = id;
+ *out_size = bytes;
+ return 0;
+ }
+ }
+
+ /* etc */
+ if (access(dbus_etc, F_OK) == 0) { /* check if the file exists first */
+ ret = machine_id_read_and_sanitize(dbus_etc, &id, &bytes);
+ if (ret == 0) {
+ *out_id = id;
+ *out_size = bytes;
+ return 0;
+ }
+ }
+#elif defined(__FreeBSD__) || defined(__NetBSD__) || \
+ defined(__OpenBSD__) || defined(__DragonFly__)
+
+ char *hostid = "/etc/hostid";
+
+ /* hostid */
+ ret = machine_id_read_and_sanitize(hostid, &id, &bytes);
+ if (ret == 0) {
+ *out_id = id;
+ *out_size = bytes;
+ return 0;
+ }
+#endif
+
+ /* generate a random uuid */
+ uuid = flb_malloc(38);
+ if (!uuid) {
+ flb_errno();
+ return -1;
+ }
+ ret = flb_utils_uuid_v4_gen(uuid);
+ if (ret == 0) {
+ *out_id = uuid;
+ *out_size = strlen(uuid);
+ return 0;
+ }
+
+ return -1;
+}
+
+void flb_utils_set_plugin_string_property(const char *name,
+ flb_sds_t *field_storage,
+ flb_sds_t new_value)
+{
+ if (field_storage == NULL) {
+ flb_error("[utils] invalid field storage pointer for property '%s'",
+ name);
+
+ return;
+ }
+
+ if (*field_storage != NULL) {
+ flb_warn("[utils] property '%s' is already specified with value '%s'."
+ " Overwriting with '%s'",
+ name,
+ *field_storage,
+ new_value);
+
+ flb_sds_destroy(*field_storage);
+
+ *field_storage = NULL;
+ }
+
+ *field_storage = new_value;
+}
diff --git a/fluent-bit/src/flb_worker.c b/fluent-bit/src/flb_worker.c
new file mode 100644
index 000000000..47154f8f3
--- /dev/null
+++ b/fluent-bit/src/flb_worker.c
@@ -0,0 +1,173 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <monkey/mk_core.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_worker.h>
+#include <fluent-bit/flb_log.h>
+
+FLB_TLS_DEFINE(struct flb_worker, flb_worker_ctx);
+
+/*
+ * The step_callback runs in a POSIX thread context, it have been started
+ * by flb_worker_create(...). Here we setup specific FLB requirements and
+ * then we jump into the target/original callback.
+ */
+static void step_callback(void *data)
+{
+ struct flb_worker *worker = data;
+
+ /* Set the worker context global */
+ FLB_TLS_SET(flb_worker_ctx, worker);
+
+ /* not too scary :) */
+ worker->func(worker->data);
+
+ /* FIXME: add a good plan for pthread_exit and 'worker' release */
+ pthread_exit(NULL);
+}
+
+struct flb_worker *flb_worker_context_create(void (*func) (void *), void *arg,
+ struct flb_config *config)
+{
+ struct flb_worker *worker;
+
+ worker = flb_calloc(1, sizeof(struct flb_worker));
+ if (!worker) {
+ flb_errno();
+ return NULL;
+ }
+ MK_EVENT_ZERO(&worker->event);
+ worker->func = func;
+ worker->data = arg;
+ worker->config = config;
+ worker->log_ctx = config->log;
+
+ return worker;
+}
+
+/*
+ * Creates a worker (POSIX thread). This function creates a worker
+ * context and also setup the 'step' callback to initialize generic
+ * Fluent Bit requirements before to invoke the real target callback
+ * set by the caller.
+ *
+ * E.g: We do this intermediary 'step' to initialize the required
+ * logging context and possible others.
+ */
+int flb_worker_create(void (*func) (void *), void *arg, pthread_t *tid,
+ struct flb_config *config)
+{
+ int ret;
+ struct flb_worker *worker;
+
+ worker = flb_worker_context_create(func, arg, config);
+ if (!worker) {
+ return -1;
+ }
+
+ /* Initialize log-specific */
+ ret = flb_log_worker_init(worker);
+ if (ret == -1) {
+ flb_free(worker);
+ return -1;
+ }
+
+ /* Spawn the step_callback and the func() */
+ ret = mk_utils_worker_spawn(step_callback, worker, &worker->tid);
+ if (ret != 0) {
+ flb_free(worker);
+ return -1;
+ }
+ memcpy(tid, &worker->tid, sizeof(pthread_t));
+ mk_list_add(&worker->_head, &config->workers);
+
+ return 0;
+}
+
+/*
+ * The worker interface aims to prepare any context required by Threads when
+ * running, this function is called just one time.
+ */
+int flb_worker_init(struct flb_config *config)
+{
+ FLB_TLS_INIT(flb_worker_ctx);
+
+ return 0;
+}
+
+/* Lookup a worker using it pthread id */
+struct flb_worker *flb_worker_lookup(pthread_t tid, struct flb_config *config)
+{
+ struct mk_list *head;
+ struct flb_worker *worker;
+
+ mk_list_foreach(head, &config->workers) {
+ worker = mk_list_entry(head, struct flb_worker, _head);
+ if (pthread_equal(worker->tid, tid) != 0) {
+ return worker;
+ }
+ }
+
+ return NULL;
+}
+
+struct flb_worker *flb_worker_get()
+{
+ return FLB_TLS_GET(flb_worker_ctx);
+}
+
+void flb_worker_destroy(struct flb_worker *worker)
+{
+ if (!worker) {
+ return;
+ }
+
+ if (worker->log_cache) {
+ flb_log_cache_destroy(worker->log_cache);
+ }
+
+ mk_list_del(&worker->_head);
+ flb_free(worker);
+}
+
+int flb_worker_exit(struct flb_config *config)
+{
+ int c = 0;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_worker *worker;
+
+ mk_list_foreach_safe(head, tmp, &config->workers) {
+ worker = mk_list_entry(head, struct flb_worker, _head);
+ flb_worker_destroy(worker);
+ c++;
+ }
+
+ return c;
+}
+
+int flb_worker_log_level(struct flb_worker *worker)
+{
+ struct flb_log *log = worker->log_ctx;
+ return log->level;
+};
diff --git a/fluent-bit/src/fluent-bit.c b/fluent-bit/src/fluent-bit.c
new file mode 100644
index 000000000..51b814cfd
--- /dev/null
+++ b/fluent-bit/src/fluent-bit.c
@@ -0,0 +1,1417 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <ctype.h>
+
+#include <cfl/cfl.h>
+#include <cfl/cfl_array.h>
+#include <cfl/cfl_kvlist.h>
+
+#include <fluent-bit/flb_compat.h>
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_dump.h>
+#include <fluent-bit/flb_stacktrace.h>
+#include <fluent-bit/flb_env.h>
+#include <fluent-bit/flb_macros.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_meta.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_version.h>
+#include <fluent-bit/flb_error.h>
+#include <fluent-bit/flb_custom.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_filter.h>
+#include <fluent-bit/flb_processor.h>
+#include <fluent-bit/flb_engine.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_slist.h>
+#include <fluent-bit/flb_plugin.h>
+#include <fluent-bit/flb_parser.h>
+#include <fluent-bit/flb_lib.h>
+#include <fluent-bit/flb_help.h>
+#include <fluent-bit/flb_record_accessor.h>
+#include <fluent-bit/flb_ra_key.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_reload.h>
+#include <fluent-bit/flb_config_format.h>
+
+#ifdef FLB_HAVE_MTRACE
+#include <mcheck.h>
+#endif
+
+#ifdef FLB_SYSTEM_WINDOWS
+extern int win32_main(int, char**);
+extern void win32_started(void);
+#endif
+
+flb_ctx_t *ctx;
+struct flb_config *config;
+volatile sig_atomic_t exit_signal = 0;
+volatile sig_atomic_t flb_bin_restarting = FLB_RELOAD_IDLE;
+
+#ifdef FLB_HAVE_LIBBACKTRACE
+struct flb_stacktrace flb_st;
+#endif
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+
+#include <fluent-bit/flb_chunk_trace.h>
+
+#define FLB_LONG_TRACE (1024 + 1)
+#define FLB_LONG_TRACE_INPUT (1024 + 2)
+#define FLB_LONG_TRACE_OUTPUT (1024 + 3)
+#define FLB_LONG_TRACE_OUTPUT_PROPERTY (1024 + 4)
+
+#endif
+
+#define FLB_HELP_TEXT 0
+#define FLB_HELP_JSON 1
+
+
+#define PLUGIN_CUSTOM 0
+#define PLUGIN_INPUT 1
+#define PLUGIN_OUTPUT 2
+#define PLUGIN_FILTER 3
+
+#define print_opt(a, b) printf(" %-24s%s\n", a, b)
+#define print_opt_i(a, b, c) printf(" %-24s%s (default: %i)\n", a, b, c)
+#define print_opt_s(a, b, c) printf(" %-24s%s (default: %s)\n", a, b, c)
+
+#define get_key(a, b, c) mk_rconf_section_get_key(a, b, c)
+#define n_get_key(a, b, c) (intptr_t) get_key(a, b, c)
+#define s_get_key(a, b, c) (char *) get_key(a, b, c)
+
+static char *prog_name;
+
+static void flb_signal_init();
+
+static void flb_help(int rc, struct flb_config *config)
+{
+ struct mk_list *head;
+ struct flb_input_plugin *in;
+ struct flb_output_plugin *out;
+ struct flb_filter_plugin *filter;
+ struct flb_processor_plugin *processor;
+
+ printf("Usage: %s [OPTION]\n\n", prog_name);
+ printf("%sAvailable Options%s\n", ANSI_BOLD, ANSI_RESET);
+ print_opt("-b --storage_path=PATH", "specify a storage buffering path");
+ print_opt("-c --config=FILE", "specify an optional configuration file");
+#ifdef FLB_HAVE_FORK
+ print_opt("-d, --daemon", "run Fluent Bit in background mode");
+#endif
+ print_opt("-D, --dry-run", "dry run");
+ print_opt_i("-f, --flush=SECONDS", "flush timeout in seconds",
+ FLB_CONFIG_FLUSH_SECS);
+ print_opt("-C, --custom=CUSTOM", "enable a custom plugin");
+ print_opt("-i, --input=INPUT", "set an input");
+ print_opt("-F --filter=FILTER", "set a filter");
+ print_opt("-m, --match=MATCH", "set plugin match, same as '-p match=abc'");
+ print_opt("-o, --output=OUTPUT", "set an output");
+ print_opt("-p, --prop=\"A=B\"", "set plugin configuration property");
+#ifdef FLB_HAVE_PARSER
+ print_opt("-R, --parser=FILE", "specify a parser configuration file");
+#endif
+ print_opt("-e, --plugin=FILE", "load an external plugin (shared lib)");
+ print_opt("-l, --log_file=FILE", "write log info to a file");
+ print_opt("-t, --tag=TAG", "set plugin tag, same as '-p tag=abc'");
+#ifdef FLB_HAVE_STREAM_PROCESSOR
+ print_opt("-T, --sp-task=SQL", "define a stream processor task");
+#endif
+ print_opt("-v, --verbose", "increase logging verbosity (default: info)");
+#ifdef FLB_TRACE
+ print_opt("-vv", "trace mode (available)");
+#endif
+#ifdef FLB_HAVE_CHUNK_TRACE
+ print_opt("-Z, --enable-chunk-trace", "enable chunk tracing, it can be activated either through the http api or the command line");
+ print_opt("--trace-input", "input to start tracing on startup.");
+ print_opt("--trace-output", "output to use for tracing on startup.");
+ print_opt("--trace-output-property", "set a property for output tracing on startup.");
+ print_opt("--trace", "setup a trace pipeline on startup. Uses a single line, ie: \"input=dummy.0 output=stdout output.format='json'\"");
+#endif
+ print_opt("-w, --workdir", "set the working directory");
+#ifdef FLB_HAVE_HTTP_SERVER
+ print_opt("-H, --http", "enable monitoring HTTP server");
+ print_opt_s("-P, --port", "set HTTP server TCP port",
+ FLB_CONFIG_HTTP_PORT);
+#endif
+ print_opt_i("-s, --coro_stack_size", "set coroutines stack size in bytes",
+ config->coro_stack_size);
+ print_opt("-q, --quiet", "quiet mode");
+ print_opt("-S, --sosreport", "support report for Enterprise customers");
+ print_opt("-Y, --enable-hot-reload", "enable for hot reloading");
+ print_opt("-W, --disable-thread-safety-on-hot-reloading", "disable thread safety on hot reloading");
+ print_opt("-V, --version", "show version number");
+ print_opt("-h, --help", "print this help");
+
+ printf("\n%sInputs%s\n", ANSI_BOLD, ANSI_RESET);
+
+ /* Iterate each supported input */
+ mk_list_foreach(head, &config->in_plugins) {
+ in = mk_list_entry(head, struct flb_input_plugin, _head);
+ if (strcmp(in->name, "lib") == 0 || (in->flags & FLB_INPUT_PRIVATE)) {
+ /* useless..., just skip it. */
+ continue;
+ }
+ print_opt(in->name, in->description);
+ }
+
+ printf("\n%sProcessors%s\n", ANSI_BOLD, ANSI_RESET);
+ mk_list_foreach(head, &config->processor_plugins) {
+ processor = mk_list_entry(head, struct flb_processor_plugin, _head);
+ print_opt(processor->name, processor->description);
+ }
+
+ printf("\n%sFilters%s\n", ANSI_BOLD, ANSI_RESET);
+ mk_list_foreach(head, &config->filter_plugins) {
+ filter = mk_list_entry(head, struct flb_filter_plugin, _head);
+ print_opt(filter->name, filter->description);
+ }
+
+ printf("\n%sOutputs%s\n", ANSI_BOLD, ANSI_RESET);
+ mk_list_foreach(head, &config->out_plugins) {
+ out = mk_list_entry(head, struct flb_output_plugin, _head);
+ if (strcmp(out->name, "lib") == 0 || (out->flags & FLB_OUTPUT_PRIVATE)) {
+ /* useless..., just skip it. */
+ continue;
+ }
+ print_opt(out->name, out->description);
+ }
+
+ printf("\n%sInternal%s\n", ANSI_BOLD, ANSI_RESET);
+ printf(" Event Loop = %s\n", mk_event_backend());
+ printf(" Build Flags =%s\n", FLB_INFO_FLAGS);
+ exit(rc);
+}
+
+/*
+ * If the description is larger than the allowed 80 chars including left
+ * padding, split the content in multiple lines and align it properly.
+ */
+static void help_plugin_description(int left_padding, flb_sds_t str)
+{
+ int len;
+ int max;
+ int line = 0;
+ char *c;
+ char *p;
+ char *end;
+ char fmt[32];
+
+ if (!str) {
+ printf("no description available\n");
+ return;
+ }
+
+ max = 90 - left_padding;
+ len = strlen(str);
+
+ if (len <= max) {
+ printf("%s\n", str);
+ return;
+ }
+
+ p = str;
+ len = flb_sds_len(str);
+ end = str + len;
+
+ while (p < end) {
+ if ((p + max) > end) {
+ c = end;
+ }
+ else {
+ c = p + max;
+ while (*c != ' ' && c > p) {
+ c--;
+ }
+ }
+
+ if (c == p) {
+ len = end - p;
+ }
+ else {
+ len = c - p;
+ }
+
+ snprintf(fmt, sizeof(fmt) - 1, "%%*s%%.%is\n", len);
+ if (line == 0) {
+ printf(fmt, 0, "", p);
+ }
+ else {
+ printf(fmt, left_padding, " ", p);
+ }
+ line++;
+ p += len + 1;
+ }
+}
+
+static flb_sds_t help_get_value(msgpack_object map, char *key)
+{
+ flb_sds_t k;
+ flb_sds_t val;
+ msgpack_object *o;
+ struct flb_ra_value *rval = NULL;
+ struct flb_record_accessor *ra = NULL;
+
+ k = flb_sds_create(key);
+ ra = flb_ra_create(k, FLB_FALSE);
+ flb_sds_destroy(k);
+ if (!ra) {
+ return NULL;
+ }
+
+ rval = flb_ra_get_value_object(ra, map);
+ if (!rval) {
+ flb_ra_destroy(ra);
+ return NULL;
+ }
+
+ o = &rval->o;
+ val = flb_sds_create_len(o->via.str.ptr, o->via.str.size);
+
+ flb_ra_key_value_destroy(rval);
+ flb_ra_destroy(ra);
+
+ return val;
+}
+
+static void help_print_property(int max, msgpack_object k, msgpack_object v)
+{
+ int i;
+ int len = 0;
+ char buf[32];
+ char fmt[32];
+ char fmt_prf[32];
+ char def[32];
+ msgpack_object map;
+ flb_sds_t tmp;
+ flb_sds_t name;
+ flb_sds_t type;
+ flb_sds_t desc;
+ flb_sds_t defv;
+
+ /* Convert property type to uppercase and print it */
+ for (i = 0; i < k.via.str.size; i++) {
+ buf[i] = toupper(k.via.str.ptr[i]);
+ }
+ buf[k.via.str.size] = '\0';
+ printf(ANSI_BOLD "\n%s\n" ANSI_RESET, buf);
+
+ snprintf(fmt, sizeof(fmt) - 1, "%%-%is", max);
+ snprintf(fmt_prf, sizeof(fmt_prf) - 1, "%%-%is", max);
+ snprintf(def, sizeof(def) - 1, "%%*s> default: %%s, type: ");
+
+ for (i = 0; i < v.via.array.size; i++) {
+ map = v.via.array.ptr[i];
+
+ name = help_get_value(map, "$name");
+ type = help_get_value(map, "$type");
+ desc = help_get_value(map, "$description");
+ defv = help_get_value(map, "$default");
+
+ if (strcmp(type, "prefix") == 0) {
+ len = flb_sds_len(name);
+ tmp = flb_sds_create_size(len + 2);
+ flb_sds_printf(&tmp, "%sN", name);
+ printf(fmt_prf, tmp);
+ flb_sds_destroy(tmp);
+ }
+ else {
+ printf(fmt, name);
+ }
+
+ help_plugin_description(max, desc);
+
+ if (defv) {
+ printf(def, max, " ", defv);
+ }
+ else {
+ printf("%*s> type: ", max, " ");
+ }
+ printf("%s", type);
+ printf("\n\n");
+ }
+}
+
+static void help_format_json(void *help_buf, size_t help_size)
+{
+ flb_sds_t json;
+
+ json = flb_msgpack_raw_to_json_sds(help_buf, help_size);
+ printf("%s\n", json);
+ flb_sds_destroy(json);
+}
+
+static void help_format_text(void *help_buf, size_t help_size)
+{
+ int i;
+ int x;
+ int max = 0;
+ int len = 0;
+ int ret;
+ size_t off = 0;
+ flb_sds_t name;
+ flb_sds_t type;
+ flb_sds_t desc;
+ msgpack_unpacked result;
+ msgpack_object map;
+ msgpack_object p;
+ msgpack_object k;
+ msgpack_object v;
+
+ msgpack_unpacked_init(&result);
+ ret = msgpack_unpack_next(&result, help_buf, help_size, &off);
+ if (ret != MSGPACK_UNPACK_SUCCESS) {
+ return;
+ }
+ map = result.data;
+
+ type = help_get_value(map, "$type");
+ name = help_get_value(map, "$name");
+ desc = help_get_value(map, "$description");
+
+ printf("%sHELP%s\n%s %s plugin\n", ANSI_BOLD, ANSI_RESET,
+ name, type);
+ flb_sds_destroy(type);
+ flb_sds_destroy(name);
+
+ if (desc) {
+ printf(ANSI_BOLD "\nDESCRIPTION\n" ANSI_RESET "%s\n", desc);
+ flb_sds_destroy(desc);
+ }
+
+ /* Properties */
+ p = map.via.map.ptr[3].val;
+
+ /* Calculate padding */
+ for (i = 0; i < p.via.map.size; i++) {
+ v = p.via.map.ptr[i].val;
+ for (x = 0; x < v.via.map.size; x++) {
+ msgpack_object ptr = v.via.array.ptr[x];
+ name = help_get_value(ptr, "$name");
+ len = flb_sds_len(name);
+ flb_sds_destroy(name);
+ if (len > max) {
+ max = len;
+ }
+ }
+ }
+ max += 2;
+
+ /* Iterate each section of properties */
+ for (i = 0; i < p.via.map.size; i++) {
+ k = p.via.map.ptr[i].key;
+ v = p.via.map.ptr[i].val;
+ help_print_property(max, k, v);
+ }
+}
+
+static void flb_help_plugin(int rc, int format,
+ struct flb_config *config, int type,
+ struct flb_cf *cf,
+ struct flb_cf_section *s)
+{
+ struct flb_config_map *opt = NULL;
+ void *help_buf;
+ size_t help_size;
+ char *name;
+ struct flb_custom_instance *c = NULL;
+ struct flb_input_instance *i = NULL;
+ struct flb_filter_instance *f = NULL;
+ struct flb_output_instance *o = NULL;
+
+ flb_version_banner();
+
+ name = flb_cf_section_property_get_string(cf, s, "name");
+ if (!name) {
+ exit(EXIT_FAILURE);
+ }
+
+ if (type == PLUGIN_CUSTOM) {
+ c = flb_custom_new(config, name, NULL);
+ if (!c) {
+ fprintf(stderr, "invalid custom plugin '%s'", name);
+ return;
+ }
+ opt = c->p->config_map;
+ flb_help_custom(c, &help_buf, &help_size);
+ flb_custom_instance_destroy(c);
+ }
+ else if (type == PLUGIN_INPUT) {
+ i = flb_input_new(config, name, 0, FLB_TRUE);
+ if (!i) {
+ fprintf(stderr, "invalid input plugin '%s'", name);
+ return;
+ }
+ opt = i->p->config_map;
+ flb_help_input(i, &help_buf, &help_size);
+ flb_input_instance_destroy(i);
+ }
+ else if (type == PLUGIN_FILTER) {
+ f = flb_filter_new(config, name, 0);
+ if (!f) {
+ fprintf(stderr, "invalid filter plugin '%s'", name);
+ return;
+ }
+ opt = f->p->config_map;
+ flb_help_filter(f, &help_buf, &help_size);
+ flb_filter_instance_destroy(f);
+ }
+ else if (type == PLUGIN_OUTPUT) {
+ o = flb_output_new(config, name, 0, FLB_TRUE);
+ if (!o) {
+ fprintf(stderr, "invalid output plugin '%s'", name);
+ return;
+ }
+ opt = o->p->config_map;
+ flb_help_output(o, &help_buf, &help_size);
+ flb_output_instance_destroy(o);
+ }
+
+ if (!opt) {
+ exit(rc);
+ }
+
+ if (format == FLB_HELP_TEXT) {
+ help_format_text(help_buf, help_size);
+ }
+ else if (format == FLB_HELP_JSON) {
+ help_format_json(help_buf, help_size);
+ }
+
+ flb_free(help_buf);
+ exit(rc);
+}
+
+#define flb_print_signal(X) case X: \
+ write (STDERR_FILENO, #X ")\n", sizeof(#X ")\n")-1); \
+ break;
+
+static void flb_signal_handler_break_loop(int signal)
+{
+ exit_signal = signal;
+}
+
+static void flb_signal_exit(int signal)
+{
+ int len;
+ char ts[32];
+ char s[] = "[engine] caught signal (";
+ time_t now;
+ struct tm *cur;
+
+ now = time(NULL);
+ cur = localtime(&now);
+ len = snprintf(ts, sizeof(ts) - 1, "[%i/%02i/%02i %02i:%02i:%02i] ",
+ cur->tm_year + 1900,
+ cur->tm_mon + 1,
+ cur->tm_mday,
+ cur->tm_hour,
+ cur->tm_min,
+ cur->tm_sec);
+
+ /* write signal number */
+ write(STDERR_FILENO, ts, len);
+ write(STDERR_FILENO, s, sizeof(s) - 1);
+ switch (signal) {
+ flb_print_signal(SIGINT);
+#ifndef FLB_SYSTEM_WINDOWS
+ flb_print_signal(SIGQUIT);
+ flb_print_signal(SIGHUP);
+ flb_print_signal(SIGCONT);
+#endif
+ flb_print_signal(SIGTERM);
+ flb_print_signal(SIGSEGV);
+ };
+}
+
+static void flb_signal_handler(int signal)
+{
+ int len;
+ char ts[32];
+ char s[] = "[engine] caught signal (";
+ time_t now;
+ struct tm *cur;
+ flb_ctx_t *ctx = flb_context_get();
+ struct flb_cf *cf_opts = flb_cf_context_get();
+
+ now = time(NULL);
+ cur = localtime(&now);
+ len = snprintf(ts, sizeof(ts) - 1, "[%i/%02i/%02i %02i:%02i:%02i] ",
+ cur->tm_year + 1900,
+ cur->tm_mon + 1,
+ cur->tm_mday,
+ cur->tm_hour,
+ cur->tm_min,
+ cur->tm_sec);
+
+ /* write signal number */
+ write(STDERR_FILENO, ts, len);
+ write(STDERR_FILENO, s, sizeof(s) - 1);
+ switch (signal) {
+ flb_print_signal(SIGINT);
+#ifndef FLB_SYSTEM_WINDOWS
+ flb_print_signal(SIGQUIT);
+ flb_print_signal(SIGHUP);
+ flb_print_signal(SIGCONT);
+#endif
+ flb_print_signal(SIGTERM);
+ flb_print_signal(SIGSEGV);
+ flb_print_signal(SIGFPE);
+ };
+
+ flb_signal_init();
+
+ switch(signal) {
+ case SIGSEGV:
+ case SIGFPE:
+#ifdef FLB_HAVE_LIBBACKTRACE
+ /* To preserve stacktrace */
+ flb_stacktrace_print(&flb_st);
+#endif
+ abort();
+#ifndef FLB_SYSTEM_WINDOWS
+ case SIGCONT:
+ flb_dump(ctx->config);
+ break;
+ case SIGHUP:
+#ifndef FLB_HAVE_STATIC_CONF
+ if (flb_bin_restarting == FLB_RELOAD_IDLE) {
+ flb_bin_restarting = FLB_RELOAD_IN_PROGRESS;
+ /* reload by using same config files/path */
+ flb_reload(ctx, cf_opts);
+ flb_bin_restarting = FLB_RELOAD_IDLE;
+ }
+ else {
+ flb_utils_error(FLB_ERR_RELOADING_IN_PROGRESS);
+ }
+ break;
+#endif
+#endif
+ }
+}
+
+#ifdef FLB_SYSTEM_WINDOWS
+#include <ConsoleApi.h>
+
+static flb_ctx_t *handler_ctx = NULL;
+static struct flb_cf *handler_opts = NULL;
+static int handler_signal = 0;
+
+void flb_console_handler_set_ctx(flb_ctx_t *ctx, struct flb_cf *cf_opts)
+{
+ handler_ctx = ctx;
+ handler_opts = cf_opts;
+}
+
+static BOOL WINAPI flb_console_handler(DWORD evType)
+{
+ switch(evType) {
+ case 1 /* CTRL_BREAK_EVENT_1 */:
+ if (flb_bin_restarting == FLB_RELOAD_IDLE) {
+ flb_bin_restarting = FLB_RELOAD_IN_PROGRESS;
+ /* signal the main loop to execute reload. this is necessary since
+ * all signal handlers in win32 are executed on their own thread.
+ */
+ handler_signal = 1;
+ flb_bin_restarting = FLB_RELOAD_IDLE;
+ }
+ else {
+ flb_utils_error(FLB_ERR_RELOADING_IN_PROGRESS);
+ }
+ break;
+ }
+ return 1;
+}
+#endif
+
+static void flb_signal_init()
+{
+ signal(SIGINT, &flb_signal_handler_break_loop);
+#ifndef FLB_SYSTEM_WINDOWS
+ signal(SIGQUIT, &flb_signal_handler_break_loop);
+ signal(SIGHUP, &flb_signal_handler);
+ signal(SIGCONT, &flb_signal_handler);
+#else
+ /* Use SetConsoleCtrlHandler on windows to simulate SIGHUP */
+ SetConsoleCtrlHandler(flb_console_handler, 1);
+#endif
+ signal(SIGTERM, &flb_signal_handler_break_loop);
+ signal(SIGSEGV, &flb_signal_handler);
+ signal(SIGFPE, &flb_signal_handler);
+}
+
+static int set_property(struct flb_cf *cf, struct flb_cf_section *s, char *kv)
+{
+ int len;
+ int sep;
+ char *key;
+ char *value;
+ struct cfl_variant *tmp;
+
+ len = strlen(kv);
+ sep = mk_string_char_search(kv, '=', len);
+ if (sep == -1) {
+ return -1;
+ }
+
+ key = mk_string_copy_substr(kv, 0, sep);
+ value = kv + sep + 1;
+
+ if (!key) {
+ return -1;
+ }
+
+ tmp = flb_cf_section_property_add(cf, s->properties, key, 0, value, 0);
+ if (tmp == NULL) {
+ fprintf(stderr, "[error] setting up section '%s' plugin property '%s'\n",
+ s->name, key);
+ }
+ mk_mem_free(key);
+ return 0;
+}
+
+static int flb_service_conf_path_set(struct flb_config *config, char *file)
+{
+ char *end;
+ char *path;
+
+ path = realpath(file, NULL);
+ if (!path) {
+ return -1;
+ }
+
+ /* lookup path ending and truncate */
+ end = strrchr(path, FLB_DIRCHAR);
+ if (!end) {
+ free(path);
+ return -1;
+ }
+
+ end++;
+ *end = '\0';
+ config->conf_path = flb_strdup(path);
+ free(path);
+
+ /* Store the relative file path */
+ config->conf_path_file = flb_sds_create(file);
+
+ return 0;
+}
+
+
+static struct flb_cf *service_configure(struct flb_cf *cf,
+ struct flb_config *config, char *file)
+{
+ int ret = -1;
+
+#ifdef FLB_HAVE_STATIC_CONF
+ cf = flb_config_static_open(file);
+#else
+ if (file) {
+ cf = flb_cf_create_from_file(cf, file);
+ }
+#endif
+
+ if (!cf) {
+ return NULL;
+ }
+
+
+ /* Set configuration root path */
+ if (file) {
+ flb_service_conf_path_set(config, file);
+ }
+
+ ret = flb_config_load_config_format(config, cf);
+ if (ret != 0) {
+ return NULL;
+ }
+
+ config->cf_main = cf;
+ return cf;
+}
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+static struct flb_input_instance *find_input(flb_ctx_t *ctx, const char *name)
+{
+ struct mk_list *head;
+ struct flb_input_instance *in;
+
+
+ mk_list_foreach(head, &ctx->config->inputs) {
+ in = mk_list_entry(head, struct flb_input_instance, _head);
+ if (strcmp(name, in->name) == 0) {
+ return in;
+ }
+ if (in->alias) {
+ if (strcmp(name, in->alias) == 0) {
+ return in;
+ }
+ }
+ }
+ return NULL;
+}
+
+static int enable_trace_input(flb_ctx_t *ctx, const char *name, const char *prefix, const char *output_name, struct mk_list *props)
+{
+ struct flb_input_instance *in;
+
+
+ in = find_input(ctx, name);
+ if (in == NULL) {
+ return FLB_ERROR;
+ }
+
+ flb_chunk_trace_context_new(in, output_name, prefix, NULL, props);
+ return (in->chunk_trace_ctxt == NULL ? FLB_ERROR : FLB_OK);
+}
+
+static int disable_trace_input(flb_ctx_t *ctx, const char *name)
+{
+ struct flb_input_instance *in;
+
+
+ in = find_input(ctx, name);
+ if (in == NULL) {
+ return FLB_ERROR;
+ }
+
+ if (in->chunk_trace_ctxt != NULL) {
+ flb_chunk_trace_context_destroy(in);
+ }
+ return FLB_OK;
+}
+
+static int set_trace_property(struct mk_list *props, char *kv)
+{
+ int len;
+ int sep;
+ char *key;
+ char *value;
+
+ len = strlen(kv);
+ sep = mk_string_char_search(kv, '=', len);
+ if (sep == -1) {
+ return -1;
+ }
+
+ key = mk_string_copy_substr(kv, 0, sep);
+ value = kv + sep + 1;
+
+ if (!key) {
+ return -1;
+ }
+
+ flb_kv_item_create_len(props,
+ (char *)key, strlen(key),
+ (char *)value, strlen(value));
+
+ mk_mem_free(key);
+ return 0;
+}
+
+static int parse_trace_pipeline_prop(flb_ctx_t *ctx, const char *kv, char **key, char **value)
+{
+ int len;
+ int sep;
+
+ len = strlen(kv);
+ sep = mk_string_char_search(kv, '=', len);
+ if (sep == -1) {
+ return FLB_ERROR;
+ }
+
+ *key = mk_string_copy_substr(kv, 0, sep);
+ if (!key) {
+ return FLB_ERROR;
+ }
+
+ *value = flb_strdup(kv + sep + 1);
+ return FLB_OK;
+}
+
+static int parse_trace_pipeline(flb_ctx_t *ctx, const char *pipeline, char **trace_input, char **trace_output, struct mk_list **props)
+{
+ struct mk_list *parts = NULL;
+ struct mk_list *cur;
+ struct flb_split_entry *part;
+ char *key;
+ char *value;
+ const char *propname;
+ const char *propval;
+
+
+ parts = flb_utils_split(pipeline, (int)' ', 0);
+ if (parts == NULL) {
+ return FLB_ERROR;
+ }
+
+ mk_list_foreach(cur, parts) {
+ key = NULL;
+ value = NULL;
+
+ part = mk_list_entry(cur, struct flb_split_entry, _head);
+
+ if (parse_trace_pipeline_prop(ctx, part->value, &key, &value) == FLB_ERROR) {
+ return FLB_ERROR;
+ }
+
+ if (strcmp(key, "input") == 0) {
+ if (*trace_input != NULL) {
+ flb_free(*trace_input);
+ }
+ *trace_input = flb_strdup(value);
+ }
+ else if (strcmp(key, "output") == 0) {
+ if (*trace_output != NULL) {
+ flb_free(*trace_output);
+ }
+ *trace_output = flb_strdup(value);
+ }
+ else if (strncmp(key, "output.", strlen("output.")) == 0) {
+ propname = mk_string_copy_substr(key, strlen("output."), strlen(key));
+ if (propname == NULL) {
+ return FLB_ERROR;
+ }
+
+ propval = flb_strdup(value);
+ if (propval == NULL) {
+ return FLB_ERROR;
+ }
+
+ if (*props == NULL) {
+ *props = flb_calloc(1, sizeof(struct mk_list));
+ flb_kv_init(*props);
+ }
+
+ flb_kv_item_create_len(*props,
+ (char *)propname, strlen(propname),
+ (char *)propval, strlen(propval));
+ }
+
+ if (key != NULL) {
+ mk_mem_free(key);
+ }
+
+ if (value != NULL) {
+ flb_free(value);
+ }
+ }
+
+ flb_utils_split_free(parts);
+ return FLB_OK;
+}
+#endif
+
+int flb_main(int argc, char **argv)
+{
+ int opt;
+ int ret;
+ flb_sds_t json;
+
+ /* handle plugin properties: -1 = none, 0 = input, 1 = output */
+ int last_plugin = -1;
+
+ /* local variables to handle config options */
+ char *cfg_file = NULL;
+
+ /* config format context */
+ struct flb_cf *cf;
+ struct flb_cf *tmp;
+ struct flb_cf_section *service;
+ struct flb_cf_section *s;
+ struct flb_cf_section *section;
+ struct flb_cf *cf_opts;
+
+ prog_name = argv[0];
+
+ cf_opts = flb_cf_create();
+ if (!cf_opts) {
+ exit(EXIT_FAILURE);
+ }
+ section = flb_cf_section_create(cf_opts, "service", 0);
+ if (!section) {
+ flb_cf_destroy(cf_opts);
+ exit(EXIT_FAILURE);
+ }
+
+#ifdef FLB_HAVE_LIBBACKTRACE
+ flb_stacktrace_init(argv[0], &flb_st);
+#endif
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+ char *trace_input = NULL;
+ char *trace_output = flb_strdup("stdout");
+ struct mk_list *trace_props = NULL;
+#endif
+
+ /* Setup long-options */
+ static const struct option long_opts[] = {
+ { "storage_path", required_argument, NULL, 'b' },
+ { "config", required_argument, NULL, 'c' },
+#ifdef FLB_HAVE_FORK
+ { "daemon", no_argument , NULL, 'd' },
+#endif
+ { "dry-run", no_argument , NULL, 'D' },
+ { "flush", required_argument, NULL, 'f' },
+ { "http", no_argument , NULL, 'H' },
+ { "log_file", required_argument, NULL, 'l' },
+ { "port", required_argument, NULL, 'P' },
+ { "custom", required_argument, NULL, 'C' },
+ { "input", required_argument, NULL, 'i' },
+ { "match", required_argument, NULL, 'm' },
+ { "output", required_argument, NULL, 'o' },
+ { "filter", required_argument, NULL, 'F' },
+#ifdef FLB_HAVE_PARSER
+ { "parser", required_argument, NULL, 'R' },
+#endif
+ { "prop", required_argument, NULL, 'p' },
+ { "plugin", required_argument, NULL, 'e' },
+ { "tag", required_argument, NULL, 't' },
+#ifdef FLB_HAVE_STREAM_PROCESSOR
+ { "sp-task", required_argument, NULL, 'T' },
+#endif
+ { "version", no_argument , NULL, 'V' },
+ { "verbose", no_argument , NULL, 'v' },
+ { "workdir", required_argument, NULL, 'w' },
+ { "quiet", no_argument , NULL, 'q' },
+ { "help", no_argument , NULL, 'h' },
+ { "help-json", no_argument , NULL, 'J' },
+ { "coro_stack_size", required_argument, NULL, 's' },
+ { "sosreport", no_argument , NULL, 'S' },
+#ifdef FLB_HAVE_HTTP_SERVER
+ { "http_server", no_argument , NULL, 'H' },
+ { "http_listen", required_argument, NULL, 'L' },
+ { "http_port", required_argument, NULL, 'P' },
+#endif
+ { "enable-hot-reload", no_argument, NULL, 'Y' },
+#ifdef FLB_HAVE_CHUNK_TRACE
+ { "enable-chunk-trace", no_argument, NULL, 'Z' },
+ { "trace", required_argument, NULL, FLB_LONG_TRACE },
+ { "trace-input", required_argument, NULL, FLB_LONG_TRACE_INPUT },
+ { "trace-output", required_argument, NULL, FLB_LONG_TRACE_OUTPUT },
+ { "trace-output-property", required_argument, NULL, FLB_LONG_TRACE_OUTPUT_PROPERTY },
+#endif
+ { "disable-thread-safety-on-hot-reload", no_argument, NULL, 'W' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ /* Signal handler */
+ flb_signal_init();
+
+ /* Initialize Monkey Core library */
+ mk_core_init();
+
+ /* Create Fluent Bit context */
+ ctx = flb_create();
+ if (!ctx) {
+ exit(EXIT_FAILURE);
+ }
+ config = ctx->config;
+ cf = config->cf_main;
+ service = cf_opts->service;
+
+#ifdef FLB_SYSTEM_WINDOWS
+ flb_console_handler_set_ctx(ctx, cf_opts);
+#endif
+
+ /* Add reference for cf_opts */
+ config->cf_opts = cf_opts;
+
+#ifndef FLB_HAVE_STATIC_CONF
+
+ /* Parse the command line options */
+ while ((opt = getopt_long(argc, argv,
+ "b:c:dDf:C:i:m:o:R:F:p:e:"
+ "t:T:l:vw:qVhJL:HP:s:SWYZ",
+ long_opts, NULL)) != -1) {
+
+ switch (opt) {
+ case 'b':
+ flb_cf_section_property_add(cf_opts, service->properties,
+ "storage.path", 0, optarg, 0);
+ break;
+ case 'c':
+ cfg_file = flb_strdup(optarg);
+ break;
+#ifdef FLB_HAVE_FORK
+ case 'd':
+ flb_cf_section_property_add(cf_opts, service->properties,
+ "daemon", 0, "on", 0);
+ config->daemon = FLB_TRUE;
+ break;
+#endif
+ case 'D':
+ config->dry_run = FLB_TRUE;
+ break;
+ case 'e':
+ ret = flb_plugin_load_router(optarg, config);
+ if (ret == -1) {
+ exit(EXIT_FAILURE);
+ }
+ /* Store the relative file path for external plugin */
+ flb_slist_add(&config->external_plugins, optarg);
+ break;
+ case 'f':
+ flb_cf_section_property_add(cf_opts, service->properties,
+ "flush", 0, optarg, 0);
+ break;
+ case 'C':
+ s = flb_cf_section_create(cf_opts, "custom", 0);
+ if (!s) {
+ flb_utils_error(FLB_ERR_CUSTOM_INVALID);
+ }
+ flb_cf_section_property_add(cf_opts, s->properties, "name", 0, optarg, 0);
+ last_plugin = PLUGIN_CUSTOM;
+ break;
+ case 'i':
+ s = flb_cf_section_create(cf_opts, "input", 0);
+ if (!s) {
+ flb_utils_error(FLB_ERR_INPUT_INVALID);
+ }
+ flb_cf_section_property_add(cf_opts, s->properties, "name", 0, optarg, 0);
+ last_plugin = PLUGIN_INPUT;
+ break;
+ case 'm':
+ if (last_plugin == PLUGIN_FILTER || last_plugin == PLUGIN_OUTPUT) {
+ flb_cf_section_property_add(cf_opts, s->properties, "match", 0, optarg, 0);
+ }
+ break;
+ case 'o':
+ s = flb_cf_section_create(cf_opts, "output", 0);
+ if (!s) {
+ flb_utils_error(FLB_ERR_OUTPUT_INVALID);
+ }
+ flb_cf_section_property_add(cf_opts, s->properties, "name", 0, optarg, 0);
+ last_plugin = PLUGIN_OUTPUT;
+ break;
+#ifdef FLB_HAVE_PARSER
+ case 'R':
+ ret = flb_parser_conf_file_stat(optarg, config);
+ if (ret == -1) {
+ flb_cf_destroy(cf_opts);
+ flb_destroy(ctx);
+ exit(EXIT_FAILURE);
+ }
+ flb_cf_section_property_add(cf_opts, service->properties, FLB_CONF_STR_PARSERS_FILE, 0, optarg, 0);
+ break;
+#endif
+ case 'F':
+ s = flb_cf_section_create(cf_opts, "filter", 0);
+ if (!s) {
+ flb_utils_error(FLB_ERR_FILTER_INVALID);
+ }
+ flb_cf_section_property_add(cf_opts, s->properties, "name", 0, optarg, 0);
+ last_plugin = PLUGIN_FILTER;
+ break;
+ case 'l':
+ flb_cf_section_property_add(cf_opts, service->properties,
+ "log_file", 0, optarg, 0);
+ break;
+ case 'p':
+ if (s) {
+ set_property(cf_opts, s, optarg);
+ }
+ break;
+ case 't':
+ if (s) {
+ flb_cf_section_property_add(cf_opts, s->properties, "tag", 0, optarg, 0);
+ }
+ break;
+#ifdef FLB_HAVE_STREAM_PROCESSOR
+ case 'T':
+ flb_slist_add(&config->stream_processor_tasks, optarg);
+ break;
+#endif
+ case 'h':
+ if (last_plugin == -1) {
+ flb_help(EXIT_SUCCESS, config);
+ }
+ else {
+ flb_help_plugin(EXIT_SUCCESS, FLB_HELP_TEXT,
+ config,
+ last_plugin, cf_opts, s);
+ }
+ break;
+ case 'J':
+ if (last_plugin == -1) {
+ json = flb_help_build_json_schema(config);
+ if (!json) {
+ exit(EXIT_FAILURE);
+ }
+
+ printf("%s\n", json);
+ flb_sds_destroy(json);
+ exit(EXIT_SUCCESS);
+ }
+ else {
+ flb_help_plugin(EXIT_SUCCESS, FLB_HELP_JSON, config,
+ last_plugin, cf_opts, s);
+ }
+ break;
+#ifdef FLB_HAVE_HTTP_SERVER
+ case 'H':
+ flb_cf_section_property_add(cf_opts, service->properties, "http_server", 0, "on", 0);
+ break;
+ case 'L':
+ flb_cf_section_property_add(cf_opts, service->properties, FLB_CONF_STR_HTTP_LISTEN, 0, optarg, 0);
+ break;
+ case 'P':
+ flb_cf_section_property_add(cf_opts, service->properties, FLB_CONF_STR_HTTP_PORT, 0, optarg, 0);
+ break;
+#endif
+ case 'V':
+ flb_version();
+ exit(EXIT_SUCCESS);
+ case 'v':
+ config->verbose++;
+ break;
+ case 'w':
+ config->workdir = flb_strdup(optarg);
+ break;
+ case 'q':
+ config->verbose = FLB_LOG_OFF;
+ break;
+ case 's':
+ flb_cf_section_property_add(cf_opts, service->properties, FLB_CONF_STR_CORO_STACK_SIZE, 0, optarg, 0);
+ break;
+ case 'S':
+ config->support_mode = FLB_TRUE;
+ break;
+ case 'Y':
+ flb_cf_section_property_add(cf_opts, service->properties, FLB_CONF_STR_HOT_RELOAD, 0, "on", 0);
+ break;
+ case 'W':
+ flb_cf_section_property_add(cf_opts, service->properties,
+ FLB_CONF_STR_HOT_RELOAD_ENSURE_THREAD_SAFETY, 0, "off", 0);
+ break;
+#ifdef FLB_HAVE_CHUNK_TRACE
+ case 'Z':
+ flb_cf_section_property_add(cf_opts, service->properties, FLB_CONF_STR_ENABLE_CHUNK_TRACE, 0, "on", 0);
+ break;
+ case FLB_LONG_TRACE:
+ parse_trace_pipeline(ctx, optarg, &trace_input, &trace_output, &trace_props);
+ break;
+ case FLB_LONG_TRACE_INPUT:
+ if (trace_input != NULL) {
+ flb_free(trace_input);
+ }
+ trace_input = flb_strdup(optarg);
+ break;
+ case FLB_LONG_TRACE_OUTPUT:
+ if (trace_output != NULL) {
+ flb_free(trace_output);
+ }
+ trace_output = flb_strdup(optarg);
+ break;
+ case FLB_LONG_TRACE_OUTPUT_PROPERTY:
+ if (trace_props == NULL) {
+ trace_props = flb_calloc(1, sizeof(struct mk_list));
+ flb_kv_init(trace_props);
+ }
+ set_trace_property(trace_props, optarg);
+ break;
+#endif /* FLB_HAVE_CHUNK_TRACE */
+ default:
+ flb_help(EXIT_FAILURE, config);
+ }
+ }
+#endif /* !FLB_HAVE_STATIC_CONF */
+
+ set_log_level_from_env(config);
+
+ if (config->verbose != FLB_LOG_OFF) {
+ flb_version_banner();
+ }
+
+ /* Program name */
+ flb_config_set_program_name(config, argv[0]);
+
+ /* Set the current directory */
+ if (config->workdir) {
+ ret = chdir(config->workdir);
+ if (ret == -1) {
+ flb_cf_destroy(cf_opts);
+ flb_errno();
+ return -1;
+ }
+ }
+
+ /* Validate config file */
+#ifndef FLB_HAVE_STATIC_CONF
+ if (cfg_file) {
+ if (access(cfg_file, R_OK) != 0) {
+ flb_free(cfg_file);
+ flb_cf_destroy(cf_opts);
+ flb_utils_error(FLB_ERR_CFG_FILE);
+ }
+ }
+
+ if (flb_reload_reconstruct_cf(cf_opts, cf) != 0) {
+ flb_free(cfg_file);
+ flb_cf_destroy(cf_opts);
+ fprintf(stderr, "reconstruct format context is failed\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Load the service configuration file */
+ tmp = service_configure(cf, config, cfg_file);
+ flb_free(cfg_file);
+ if (!tmp) {
+ flb_cf_destroy(cf_opts);
+ flb_utils_error(FLB_ERR_CFG_FILE_STOP);
+ }
+#else
+ tmp = service_configure(cf, config, "fluent-bit.conf");
+ if (!tmp) {
+ flb_cf_destroy(cf_opts);
+ flb_utils_error(FLB_ERR_CFG_FILE_STOP);
+ }
+
+ /* destroy previous context and override */
+ flb_cf_destroy(cf);
+ config->cf_main = tmp;
+ cf = tmp;
+#endif
+
+ /* Check co-routine stack size */
+ if (config->coro_stack_size < getpagesize()) {
+ flb_cf_destroy(cf_opts);
+ flb_utils_error(FLB_ERR_CORO_STACK_SIZE);
+ }
+
+ /* Validate flush time (seconds) */
+ if (config->flush <= (double) 0.0) {
+ flb_cf_destroy(cf_opts);
+ flb_utils_error(FLB_ERR_CFG_FLUSH);
+ }
+
+ /* debug or trace */
+ if (config->verbose >= FLB_LOG_DEBUG) {
+ flb_utils_print_setup(config);
+ }
+
+#ifdef FLB_HAVE_FORK
+ /* Run in background/daemon mode */
+ if (config->daemon == FLB_TRUE) {
+ flb_utils_set_daemon(config);
+ }
+#endif
+
+#ifdef FLB_SYSTEM_WINDOWS
+ win32_started();
+#endif
+
+ if (config->dry_run == FLB_TRUE) {
+ fprintf(stderr, "configuration test is successful\n");
+ flb_cf_destroy(cf_opts);
+ flb_destroy(ctx);
+ exit(EXIT_SUCCESS);
+ }
+
+ /* start Fluent Bit library */
+ ret = flb_start(ctx);
+ if (ret != 0) {
+ flb_cf_destroy(cf_opts);
+ flb_destroy(ctx);
+ return ret;
+ }
+
+ /* Store the current config format context from command line */
+ flb_cf_context_set(cf_opts);
+
+ /*
+ * Always re-set the original context that was started, note that during a flb_start() a 'reload' could happen so the context
+ * will be different. Use flb_context_get() to get the current context.
+ */
+ ctx = flb_context_get();
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+ if (trace_input != NULL) {
+ enable_trace_input(ctx, trace_input, NULL /* prefix ... */, trace_output, trace_props);
+ }
+#endif
+
+ while (ctx->status == FLB_LIB_OK && exit_signal == 0) {
+ sleep(1);
+
+#ifdef FLB_SYSTEM_WINDOWS
+ if (handler_signal == 1) {
+ handler_signal = 0;
+ flb_reload(ctx, cf_opts);
+ }
+#endif
+
+ /* set the context again before checking the status again */
+ ctx = flb_context_get();
+
+#ifdef FLB_SYSTEM_WINDOWS
+ flb_console_handler_set_ctx(ctx, cf_opts);
+#endif
+ }
+
+ if (exit_signal) {
+ flb_signal_exit(exit_signal);
+ }
+ ret = config->exit_status_code;
+
+ cf_opts = flb_cf_context_get();
+
+ if (cf_opts != NULL) {
+ flb_cf_destroy(cf_opts);
+ }
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+ if (trace_input != NULL) {
+ disable_trace_input(ctx, trace_input);
+ flb_free(trace_input);
+ }
+ if (trace_output) {
+ flb_free(trace_output);
+ }
+ if (trace_props != NULL) {
+ flb_kv_release(trace_props);
+ flb_free(trace_props);
+ }
+#endif
+
+ flb_stop(ctx);
+ flb_destroy(ctx);
+
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+#ifdef FLB_SYSTEM_WINDOWS
+ return win32_main(argc, argv);
+#else
+ return flb_main(argc, argv);
+#endif
+}
diff --git a/fluent-bit/src/http_server/CMakeLists.txt b/fluent-bit/src/http_server/CMakeLists.txt
new file mode 100644
index 000000000..acded936d
--- /dev/null
+++ b/fluent-bit/src/http_server/CMakeLists.txt
@@ -0,0 +1,20 @@
+if(NOT FLB_METRICS)
+ message(FATAL_ERROR "FLB_HTTP_SERVER requires FLB_METRICS=On.")
+endif()
+
+# Core Source
+set(src
+ flb_hs.c
+ flb_hs_endpoints.c
+ flb_hs_utils.c
+ )
+
+# api/v1
+add_subdirectory(api/v1)
+
+# api/v2
+add_subdirectory(api/v2)
+
+include_directories(${MONKEY_INCLUDE_DIR})
+add_library(flb-http-server STATIC ${src})
+target_link_libraries(flb-http-server monkey-core-static api-v1 api-v2)
diff --git a/fluent-bit/src/http_server/api/v1/CMakeLists.txt b/fluent-bit/src/http_server/api/v1/CMakeLists.txt
new file mode 100644
index 000000000..af86e43f8
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v1/CMakeLists.txt
@@ -0,0 +1,20 @@
+# api/v1
+set(src
+ uptime.c
+ metrics.c
+ storage.c
+ plugins.c
+ register.c
+ health.c
+ )
+
+if(FLB_CHUNK_TRACE)
+ set(src
+ ${src}
+ trace.c
+ )
+endif()
+
+include_directories(${MONKEY_INCLUDE_DIR})
+add_library(api-v1 STATIC ${src})
+target_link_libraries(api-v1 monkey-core-static fluent-bit-static)
diff --git a/fluent-bit/src/http_server/api/v1/health.c b/fluent-bit/src/http_server/api/v1/health.c
new file mode 100644
index 000000000..713d4b877
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v1/health.c
@@ -0,0 +1,335 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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<stdio.h>
+#include <stdlib.h>
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_macros.h>
+#include <fluent-bit/flb_http_server.h>
+#include <msgpack.h>
+
+#include "health.h"
+
+struct flb_health_check_metrics_counter *metrics_counter;
+
+pthread_key_t hs_health_key;
+
+static struct mk_list *hs_health_key_create()
+{
+ struct mk_list *metrics_list = NULL;
+
+ metrics_list = flb_malloc(sizeof(struct mk_list));
+ if (!metrics_list) {
+ flb_errno();
+ return NULL;
+ }
+ mk_list_init(metrics_list);
+ pthread_setspecific(hs_health_key, metrics_list);
+
+ return metrics_list;
+}
+
+static void hs_health_key_destroy(void *data)
+{
+ struct mk_list *metrics_list = (struct mk_list*)data;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_hs_hc_buf *entry;
+
+ if (metrics_list == NULL) {
+ return;
+ }
+ mk_list_foreach_safe(head, tmp, metrics_list) {
+ entry = mk_list_entry(head, struct flb_hs_hc_buf, _head);
+ if (entry != NULL) {
+ mk_list_del(&entry->_head);
+ flb_free(entry);
+ }
+ }
+
+ flb_free(metrics_list);
+}
+
+/* initialize the metrics counters */
+static void counter_init(struct flb_hs *hs) {
+
+ metrics_counter = flb_malloc(sizeof(struct flb_health_check_metrics_counter));
+
+ if (!metrics_counter) {
+ flb_errno();
+ return;
+ }
+
+ metrics_counter->error_counter = 0;
+ metrics_counter->retry_failure_counter = 0;
+ metrics_counter->error_limit = hs->config->hc_errors_count;
+ metrics_counter->retry_failure_limit = hs->config->hc_retry_failure_count;
+ metrics_counter->period_counter = 0;
+ metrics_counter->period_limit = hs->config->health_check_period;
+
+}
+
+/*
+* tell what's the current status for health check
+* One default background is that the metrics received and saved into
+* message queue every time is a accumulation of error numbers,
+* not a error number in recent second. So to get the error number
+* in a period, we need to use:
+* the error number of the newest metrics message minus
+* the error number in oldest metrics of period
+*/
+static int is_healthy() {
+
+ struct mk_list *metrics_list;
+ struct flb_hs_hc_buf *buf;
+ int period_errors;
+ int period_retry_failure;
+
+ metrics_list = pthread_getspecific(hs_health_key);
+ if (metrics_list == NULL) {
+ metrics_list = hs_health_key_create();
+ if (metrics_list == NULL) {
+ return FLB_FALSE;
+ }
+ }
+
+ if (mk_list_is_empty(metrics_list) == 0) {
+ return FLB_TRUE;
+ }
+
+ /* Get the error metrics entry from the start time of current period */
+ buf = mk_list_entry_first(metrics_list, struct flb_hs_hc_buf, _head);
+
+ /*
+ * increase user so clean up function won't
+ * free the memory and delete the data
+ */
+ buf->users++;
+
+ /* the error count saved in message queue is the number of
+ * error count at that time. So the math is that:
+ * the error count in current period = (current error count in total) -
+ * (begin error count in the period)
+ */
+ period_errors = metrics_counter->error_counter - buf->error_count;
+ period_retry_failure = metrics_counter->retry_failure_counter -
+ buf->retry_failure_count;
+ buf->users--;
+
+ if (period_errors > metrics_counter->error_limit ||
+ period_retry_failure > metrics_counter->retry_failure_limit) {
+
+ return FLB_FALSE;
+ }
+
+ return FLB_TRUE;
+}
+
+/* read the metrics from message queue and update the counter*/
+static void read_metrics(void *data, size_t size, int* error_count,
+ int* retry_failure_count)
+{
+ int i;
+ int j;
+ int m;
+ msgpack_unpacked result;
+ msgpack_object map;
+ size_t off = 0;
+ int errors = 0;
+ int retry_failure = 0;
+
+ msgpack_unpacked_init(&result);
+ msgpack_unpack_next(&result, data, size, &off);
+ map = result.data;
+
+ for (i = 0; i < map.via.map.size; i++) {
+ msgpack_object k;
+ msgpack_object v;
+
+ /* Keys: input, output */
+ k = map.via.map.ptr[i].key;
+ v = map.via.map.ptr[i].val;
+ if (k.via.str.size != sizeof("output") - 1 ||
+ strncmp(k.via.str.ptr, "output", k.via.str.size) != 0) {
+
+ continue;
+ }
+ /* Iterate sub-map */
+ for (j = 0; j < v.via.map.size; j++) {
+ msgpack_object sv;
+
+ /* Keys: plugin name , values: metrics */
+ sv = v.via.map.ptr[j].val;
+
+ for (m = 0; m < sv.via.map.size; m++) {
+ msgpack_object mk;
+ msgpack_object mv;
+
+ mk = sv.via.map.ptr[m].key;
+ mv = sv.via.map.ptr[m].val;
+
+ if (mk.via.str.size == sizeof("errors") - 1 &&
+ strncmp(mk.via.str.ptr, "errors", mk.via.str.size) == 0) {
+ errors += mv.via.u64;
+ }
+ else if (mk.via.str.size == sizeof("retries_failed") - 1 &&
+ strncmp(mk.via.str.ptr, "retries_failed",
+ mk.via.str.size) == 0) {
+ retry_failure += mv.via.u64;
+ }
+ }
+ }
+ }
+
+ *error_count = errors;
+ *retry_failure_count = retry_failure;
+ msgpack_unpacked_destroy(&result);
+}
+
+/*
+* Delete unused metrics, note that we only care about the latest node
+* we use this function to maintain the metrics queue only save the metrics
+* in a period. The old metrics which is out of period will be removed
+*/
+static int cleanup_metrics()
+{
+ int c = 0;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct mk_list *metrics_list;
+ struct flb_hs_hc_buf *entry;
+
+ metrics_list = pthread_getspecific(hs_health_key);
+ if (!metrics_list) {
+ return -1;
+ }
+
+ if (metrics_counter->period_counter < metrics_counter->period_limit) {
+ return 0;
+ }
+
+ /* remove the oldest metrics if it's out of period */
+ mk_list_foreach_safe(head, tmp, metrics_list) {
+ entry = mk_list_entry(head, struct flb_hs_hc_buf, _head);
+ if (metrics_counter->period_counter > metrics_counter->period_limit &&
+ entry->users == 0) {
+ metrics_counter->period_counter--;
+ mk_list_del(&entry->_head);
+ flb_free(entry);
+ c++;
+ }
+ else {
+ break;
+ }
+ }
+
+ return c;
+}
+
+/*
+ * Callback invoked every time some metrics are received through a
+ * message queue channel. This function runs in a Monkey HTTP thread
+ * worker and it purpose is to take the metrics data and record the health
+ * status based on the metrics.
+ * This happens every second based on the event config.
+ * So we treat period_counter to count the time.
+ * And we maintain a message queue with the size of period limit number
+ * so every time we get a new metrics data in, if the message queue size is
+ * large than period limit, we will do the clean up func to
+ * remove the oldest metrics.
+ */
+static void cb_mq_health(mk_mq_t *queue, void *data, size_t size)
+{
+ struct flb_hs_hc_buf *buf;
+ struct mk_list *metrics_list = NULL;
+ int error_count = 0;
+ int retry_failure_count = 0;
+
+ metrics_list = pthread_getspecific(hs_health_key);
+
+ if (metrics_list == NULL) {
+ metrics_list = hs_health_key_create();
+ if (metrics_list == NULL) {
+ return;
+ }
+ }
+
+ metrics_counter->period_counter++;
+
+ /* this is to remove the metrics out of period*/
+ cleanup_metrics();
+
+ buf = flb_malloc(sizeof(struct flb_hs_hc_buf));
+ if (!buf) {
+ flb_errno();
+ return;
+ }
+
+ buf->users = 0;
+
+ read_metrics(data, size, &error_count, &retry_failure_count);
+
+ metrics_counter->error_counter = error_count;
+ metrics_counter->retry_failure_counter = retry_failure_count;
+
+ buf->error_count = error_count;
+ buf->retry_failure_count = retry_failure_count;
+
+ mk_list_add(&buf->_head, metrics_list);
+}
+
+/* API: Get fluent Bit Health Status */
+static void cb_health(mk_request_t *request, void *data)
+{
+ int status = is_healthy();
+
+ if (status == FLB_TRUE) {
+ mk_http_status(request, 200);
+ mk_http_send(request, "ok\n", strlen("ok\n"), NULL);
+ mk_http_done(request);
+ }
+ else {
+ mk_http_status(request, 500);
+ mk_http_send(request, "error\n", strlen("error\n"), NULL);
+ mk_http_done(request);
+ }
+}
+
+/* Perform registration */
+int api_v1_health(struct flb_hs *hs)
+{
+
+ pthread_key_create(&hs_health_key, hs_health_key_destroy);
+
+ counter_init(hs);
+ /* Create a message queue */
+ hs->qid_health = mk_mq_create(hs->ctx, "/health",
+ cb_mq_health, NULL);
+
+ mk_vhost_handler(hs->ctx, hs->vid, "/api/v1/health", cb_health, hs);
+ return 0;
+}
+
+void flb_hs_health_destroy()
+{
+ flb_free(metrics_counter);
+}
diff --git a/fluent-bit/src/http_server/api/v1/health.h b/fluent-bit/src/http_server/api/v1/health.h
new file mode 100644
index 000000000..27a826f43
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v1/health.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 FLB_HS_API_V1_HEALTH_H
+#define FLB_HS_API_V1_HEALTH_H
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_http_server.h>
+
+struct flb_health_check_metrics_counter {
+
+ /*
+ * health check error limit,
+ * setup by customer through config: HC_Errors_Count
+ */
+ int error_limit;
+
+ /* counter the error number in metrics*/
+ int error_counter;
+
+ /*
+ * health check retry failed limit,
+ * setup by customer through config: HC_Retry_Failure_Count
+ */
+ int retry_failure_limit;
+
+ /* count the retry failed number in metrics*/
+ int retry_failure_counter;
+
+ /*period limit, setup by customer through config: HC_Period*/
+ int period_limit;
+
+ /* count the seconds in one period*/
+ int period_counter;
+
+};
+
+
+/*
+ * error and retry failure buffers that contains certain cached data to be used
+ * by health check.
+ */
+struct flb_hs_hc_buf {
+ int users;
+ int error_count;
+ int retry_failure_count;
+ struct mk_list _head;
+};
+
+/* health endpoint*/
+int api_v1_health(struct flb_hs *hs);
+
+/* clean up health resource when shutdown*/
+void flb_hs_health_destroy();
+#endif
diff --git a/fluent-bit/src/http_server/api/v1/metrics.c b/fluent-bit/src/http_server/api/v1/metrics.c
new file mode 100644
index 000000000..4a541eaa0
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v1/metrics.c
@@ -0,0 +1,579 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_filter.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_version.h>
+#include <fluent-bit/flb_time.h>
+#include "metrics.h"
+
+#include <fluent-bit/flb_http_server.h>
+#include <msgpack.h>
+
+#define null_check(x) do { if (!x) { goto error; } else {sds = x;} } while (0)
+
+pthread_key_t hs_metrics_key;
+
+static struct mk_list *hs_metrics_key_create()
+{
+ struct mk_list *metrics_list = NULL;
+
+ metrics_list = flb_malloc(sizeof(struct mk_list));
+ if (metrics_list == NULL) {
+ flb_errno();
+ return NULL;
+ }
+ mk_list_init(metrics_list);
+ pthread_setspecific(hs_metrics_key, metrics_list);
+
+ return metrics_list;
+}
+
+static void hs_metrics_key_destroy(void *data)
+{
+ struct mk_list *metrics_list = (struct mk_list*)data;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_hs_buf *entry;
+
+ if (metrics_list == NULL) {
+ return;
+ }
+ mk_list_foreach_safe(head, tmp, metrics_list) {
+ entry = mk_list_entry(head, struct flb_hs_buf, _head);
+ if (entry != NULL) {
+ if (entry->raw_data != NULL) {
+ flb_free(entry->raw_data);
+ entry->raw_data = NULL;
+ }
+ if (entry->data) {
+ flb_sds_destroy(entry->data);
+ entry->data = NULL;
+ }
+ mk_list_del(&entry->_head);
+ flb_free(entry);
+ }
+ }
+
+ flb_free(metrics_list);
+}
+
+/* Return the newest metrics buffer */
+static struct flb_hs_buf *metrics_get_latest()
+{
+ struct flb_hs_buf *buf;
+ struct mk_list *metrics_list;
+
+ metrics_list = pthread_getspecific(hs_metrics_key);
+ if (!metrics_list) {
+ return NULL;
+ }
+
+ if (mk_list_size(metrics_list) == 0) {
+ return NULL;
+ }
+
+ buf = mk_list_entry_last(metrics_list, struct flb_hs_buf, _head);
+ return buf;
+}
+
+/* Delete unused metrics, note that we only care about the latest node */
+static int cleanup_metrics()
+{
+ int c = 0;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct mk_list *metrics_list;
+ struct flb_hs_buf *last;
+ struct flb_hs_buf *entry;
+
+ metrics_list = pthread_getspecific(hs_metrics_key);
+ if (!metrics_list) {
+ return -1;
+ }
+
+ last = metrics_get_latest();
+ if (!last) {
+ return -1;
+ }
+
+ mk_list_foreach_safe(head, tmp, metrics_list) {
+ entry = mk_list_entry(head, struct flb_hs_buf, _head);
+ if (entry != last && entry->users == 0) {
+ mk_list_del(&entry->_head);
+ flb_sds_destroy(entry->data);
+ flb_free(entry->raw_data);
+ flb_free(entry);
+ c++;
+ }
+ }
+
+ return c;
+}
+
+/*
+ * Callback invoked every time some metrics are received through a
+ * message queue channel. This function runs in a Monkey HTTP thread
+ * worker and it purpose is to take the metrics data and store it
+ * somewhere so then it can be available by the end-points upon
+ * HTTP client requests.
+ */
+static void cb_mq_metrics(mk_mq_t *queue, void *data, size_t size)
+{
+ flb_sds_t out_data;
+ struct flb_hs_buf *buf;
+ struct mk_list *metrics_list = NULL;
+
+ metrics_list = pthread_getspecific(hs_metrics_key);
+ if (!metrics_list) {
+ metrics_list = hs_metrics_key_create();
+ if (metrics_list == NULL) {
+ return;
+ }
+ }
+
+ /* Convert msgpack to JSON */
+ out_data = flb_msgpack_raw_to_json_sds(data, size);
+ if (!out_data) {
+ return;
+ }
+
+ buf = flb_malloc(sizeof(struct flb_hs_buf));
+ if (!buf) {
+ flb_errno();
+ flb_sds_destroy(out_data);
+ return;
+ }
+ buf->users = 0;
+ buf->data = out_data;
+
+ buf->raw_data = flb_malloc(size);
+ if (!buf->raw_data) {
+ flb_errno();
+ flb_sds_destroy(out_data);
+ flb_free(buf);
+ return;
+ }
+ memcpy(buf->raw_data, data, size);
+ buf->raw_size = size;
+
+ mk_list_add(&buf->_head, metrics_list);
+
+ cleanup_metrics();
+}
+
+int string_cmp(const void* a_arg, const void* b_arg) {
+ char *a = *(char **)a_arg;
+ char *b = *(char **)b_arg;
+
+ return strcmp(a, b);
+}
+
+size_t extract_metric_name_end_position(char *s) {
+ int i;
+
+ for (i = 0; i < flb_sds_len(s); i++) {
+ if (s[i] == '{') {
+ return i;
+ }
+ }
+ return 0;
+}
+
+int is_same_metric(char *s1, char *s2) {
+ int i;
+ int p1 = extract_metric_name_end_position(s1);
+ int p2 = extract_metric_name_end_position(s2);
+
+ if (p1 != p2) {
+ return 0;
+ }
+
+ for (i = 0; i < p1; i++) {
+ if (s1[i] != s2[i]) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/* derive HELP text from metricname */
+/* if help text length > 128, increase init memory for metric_helptxt */
+flb_sds_t metrics_help_txt(char *metric_name, flb_sds_t *metric_helptxt)
+{
+ if (strstr(metric_name, "input_bytes")) {
+ return flb_sds_cat(*metric_helptxt, " Number of input bytes.\n", 24);
+ }
+ else if (strstr(metric_name, "input_records")) {
+ return flb_sds_cat(*metric_helptxt, " Number of input records.\n", 26);
+ }
+ else if (strstr(metric_name, "output_bytes")) {
+ return flb_sds_cat(*metric_helptxt, " Number of output bytes.\n", 25);
+ }
+ else if (strstr(metric_name, "output_records")) {
+ return flb_sds_cat(*metric_helptxt, " Number of output records.\n", 27);
+ }
+ else if (strstr(metric_name, "output_errors")) {
+ return flb_sds_cat(*metric_helptxt, " Number of output errors.\n", 26);
+ }
+ else if (strstr(metric_name, "output_retries_failed")) {
+ return flb_sds_cat(*metric_helptxt, " Number of abandoned batches because the maximum number of re-tries was reached.\n", 81);
+ }
+ else if (strstr(metric_name, "output_retries")) {
+ return flb_sds_cat(*metric_helptxt, " Number of output retries.\n", 27);
+ }
+ else if (strstr(metric_name, "output_proc_records")) {
+ return flb_sds_cat(*metric_helptxt, " Number of processed output records.\n", 37);
+ }
+ else if (strstr(metric_name, "output_proc_bytes")) {
+ return flb_sds_cat(*metric_helptxt, " Number of processed output bytes.\n", 35);
+ }
+ else if (strstr(metric_name, "output_dropped_records")) {
+ return flb_sds_cat(*metric_helptxt, " Number of dropped records.\n", 28);
+ }
+ else if (strstr(metric_name, "output_retried_records")) {
+ return flb_sds_cat(*metric_helptxt, " Number of retried records.\n", 28);
+ }
+ else {
+ return (flb_sds_cat(*metric_helptxt, " Fluentbit metrics.\n", 20));
+ }
+}
+
+/* API: expose metrics in Prometheus format /api/v1/metrics/prometheus */
+void cb_metrics_prometheus(mk_request_t *request, void *data)
+{
+ int i;
+ int j;
+ int m;
+ int len;
+ int time_len;
+ int start_time_len;
+ uint64_t uptime;
+ size_t index;
+ size_t num_metrics = 0;
+ long now;
+ flb_sds_t sds;
+ flb_sds_t sds_metric;
+ flb_sds_t tmp_sds;
+ struct flb_sds *metric_helptxt_head;
+ flb_sds_t metric_helptxt;
+ size_t off = 0;
+ struct flb_hs_buf *buf;
+ msgpack_unpacked result;
+ msgpack_object map;
+ char tmp[32];
+ char time_str[64];
+ char start_time_str[64];
+ char* *metrics_arr;
+ struct flb_time tp;
+ struct flb_hs *hs = data;
+ struct flb_config *config = hs->config;
+
+ buf = metrics_get_latest();
+ if (!buf) {
+ mk_http_status(request, 404);
+ mk_http_done(request);
+ return;
+ }
+
+ /* ref count */
+ buf->users++;
+
+ /* Compose outgoing buffer string */
+ sds = flb_sds_create_size(1024);
+ if (!sds) {
+ mk_http_status(request, 500);
+ mk_http_done(request);
+ buf->users--;
+ return;
+ }
+
+ /* length of HELP text */
+ metric_helptxt = flb_sds_create_size(128);
+ if (!metric_helptxt) {
+ flb_sds_destroy(sds);
+ mk_http_status(request, 500);
+ mk_http_done(request);
+ buf->users--;
+ return;
+ }
+ metric_helptxt_head = FLB_SDS_HEADER(metric_helptxt);
+
+ /*
+ * fluentbit_input_records[name="cpu0", hostname="${HOSTNAME}"] NUM TIMESTAMP
+ * fluentbit_input_bytes[name="cpu0", hostname="${HOSTNAME}"] NUM TIMESTAMP
+ */
+ index = 0;
+ msgpack_unpacked_init(&result);
+ msgpack_unpack_next(&result, buf->raw_data, buf->raw_size, &off);
+ map = result.data;
+
+ /* we need to know number of exposed metrics to reserve a memory */
+ for (i = 0; i < map.via.map.size; i++) {
+ msgpack_object v = map.via.map.ptr[i].val;
+ /* Iterate sub-map */
+ for (j = 0; j < v.via.map.size; j++) {
+ msgpack_object sv = v.via.map.ptr[j].val;
+ for (m = 0; m < sv.via.map.size; m++) {
+ num_metrics++;
+ }
+ }
+ }
+ metrics_arr = flb_malloc(num_metrics * sizeof(char*));
+ if (!metrics_arr) {
+ flb_errno();
+
+ mk_http_status(request, 500);
+ mk_http_done(request);
+ buf->users--;
+
+ flb_sds_destroy(sds);
+ flb_sds_destroy(metric_helptxt);
+ msgpack_unpacked_destroy(&result);
+ return;
+ }
+
+ flb_time_get(&tp);
+ now = flb_time_to_nanosec(&tp) / 1000000; /* in milliseconds */
+ time_len = snprintf(time_str, sizeof(time_str) - 1, "%lu", now);
+
+ for (i = 0; i < map.via.map.size; i++) {
+ msgpack_object k;
+ msgpack_object v;
+
+ /* Keys: input, output */
+ k = map.via.map.ptr[i].key;
+ v = map.via.map.ptr[i].val;
+
+ /* Iterate sub-map */
+ for (j = 0; j < v.via.map.size; j++) {
+ msgpack_object sk;
+ msgpack_object sv;
+
+ /* Keys: plugin name , values: metrics */
+ sk = v.via.map.ptr[j].key;
+ sv = v.via.map.ptr[j].val;
+
+ for (m = 0; m < sv.via.map.size; m++) {
+ msgpack_object mk;
+ msgpack_object mv;
+
+ mk = sv.via.map.ptr[m].key;
+ mv = sv.via.map.ptr[m].val;
+
+ /* Convert metric value to string */
+ len = snprintf(tmp, sizeof(tmp) - 1, "%" PRIu64 " ", mv.via.u64);
+ if (len < 0) {
+ goto error;
+ }
+
+ /* Allocate buffer */
+ sds_metric = flb_sds_create_size(k.via.str.size
+ + mk.via.str.size
+ + sk.via.str.size
+ + len + time_len + 28);
+ if (sds_metric == NULL) {
+ goto error;
+ }
+
+ sds_metric = flb_sds_cat(sds_metric, "fluentbit_", 10);
+ sds_metric = flb_sds_cat(sds_metric, k.via.str.ptr, k.via.str.size);
+ sds_metric = flb_sds_cat(sds_metric, "_", 1);
+ sds_metric = flb_sds_cat(sds_metric, mk.via.str.ptr, mk.via.str.size);
+ sds_metric = flb_sds_cat(sds_metric, "_total{name=\"", 13);
+ sds_metric = flb_sds_cat(sds_metric, sk.via.str.ptr, sk.via.str.size);
+ sds_metric = flb_sds_cat(sds_metric, "\"} ", 3);
+ sds_metric = flb_sds_cat(sds_metric, tmp, len);
+ sds_metric = flb_sds_cat(sds_metric, time_str, time_len);
+ sds_metric = flb_sds_cat(sds_metric, "\n", 1);
+ metrics_arr[index] = sds_metric;
+ index++;
+ }
+ }
+ }
+
+ /* Sort metrics in alphabetic order, so we can group them later. */
+ qsort(metrics_arr, num_metrics, sizeof(char *), string_cmp);
+
+ /* When a new metric starts add HELP and TYPE annotation. */
+ tmp_sds = flb_sds_cat(sds, "# HELP ", 7);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, metrics_arr[0], extract_metric_name_end_position(metrics_arr[0]));
+ null_check(tmp_sds);
+ if (!metrics_help_txt(metrics_arr[0], &metric_helptxt)) {
+ goto error;
+ }
+ tmp_sds = flb_sds_cat(sds, metric_helptxt, metric_helptxt_head->len);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, "# TYPE ", 7);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, metrics_arr[0], extract_metric_name_end_position(metrics_arr[0]));
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, " counter\n", 9);
+ null_check(tmp_sds);
+
+ for (i = 0; i < num_metrics; i++) {
+ tmp_sds = flb_sds_cat(sds, metrics_arr[i], strlen(metrics_arr[i]));
+ null_check(tmp_sds);
+ if ((i != num_metrics - 1) && (is_same_metric(metrics_arr[i], metrics_arr[i+1]) == 0)) {
+ tmp_sds = flb_sds_cat(sds, "# HELP ", 7);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, metrics_arr[i+1], extract_metric_name_end_position(metrics_arr[i+1]));
+ null_check(tmp_sds);
+ metric_helptxt_head->len = 0;
+ if (!metrics_help_txt(metrics_arr[i+1], &metric_helptxt)) {
+ goto error;
+ }
+ tmp_sds = flb_sds_cat(sds, metric_helptxt, metric_helptxt_head->len);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, "# TYPE ", 7);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, metrics_arr[i+1], extract_metric_name_end_position(metrics_arr[i+1]));
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, " counter\n", 9);
+ null_check(tmp_sds);
+ }
+ }
+
+ /* Attach uptime */
+ uptime = time(NULL) - config->init_time;
+ len = snprintf(time_str, sizeof(time_str) - 1, "%lu", uptime);
+
+ tmp_sds = flb_sds_cat(sds,
+ "# HELP fluentbit_uptime Number of seconds that Fluent Bit has "
+ "been running.\n", 76);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, "# TYPE fluentbit_uptime counter\n", 32);
+ null_check(tmp_sds);
+
+ tmp_sds = flb_sds_cat(sds, "fluentbit_uptime ", 17);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, time_str, len);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, "\n", 1);
+ null_check(tmp_sds);
+
+ /* Attach process_start_time_seconds metric. */
+ start_time_len = snprintf(start_time_str, sizeof(start_time_str) - 1,
+ "%lu", config->init_time);
+
+ tmp_sds = flb_sds_cat(sds, "# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.\n", 89);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, "# TYPE process_start_time_seconds gauge\n", 40);
+ null_check(tmp_sds);
+
+ tmp_sds = flb_sds_cat(sds, "process_start_time_seconds ", 27);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, start_time_str, start_time_len);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, "\n", 1);
+ null_check(tmp_sds);
+
+ /* Attach fluentbit_build_info metric. */
+ tmp_sds = flb_sds_cat(sds, "# HELP fluentbit_build_info Build version information.\n", 55);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, "# TYPE fluentbit_build_info gauge\n", 34);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, "fluentbit_build_info{version=\"", 30);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, FLB_VERSION_STR, sizeof(FLB_VERSION_STR) - 1);
+ null_check(tmp_sds);
+ tmp_sds = flb_sds_cat(sds, "\",edition=\"", 11);
+ null_check(tmp_sds);
+#ifdef FLB_ENTERPRISE
+ tmp_sds = flb_sds_cat(sds, "Enterprise\"} 1\n", 15);
+ null_check(tmp_sds);
+#else
+ tmp_sds = flb_sds_cat(sds, "Community\"} 1\n", 14);
+ null_check(tmp_sds);
+#endif
+
+ msgpack_unpacked_destroy(&result);
+ buf->users--;
+
+ mk_http_status(request, 200);
+ flb_hs_add_content_type_to_req(request, FLB_HS_CONTENT_TYPE_PROMETHEUS);
+ mk_http_send(request, sds, flb_sds_len(sds), NULL);
+ for (i = 0; i < num_metrics; i++) {
+ flb_sds_destroy(metrics_arr[i]);
+ }
+ flb_free(metrics_arr);
+ flb_sds_destroy(sds);
+ flb_sds_destroy(metric_helptxt);
+
+ mk_http_done(request);
+ return;
+
+error:
+ mk_http_status(request, 500);
+ mk_http_done(request);
+ buf->users--;
+
+ for (i = 0; i < index; i++) {
+ flb_sds_destroy(metrics_arr[i]);
+ }
+ flb_free(metrics_arr);
+ flb_sds_destroy(sds);
+ flb_sds_destroy(metric_helptxt);
+ msgpack_unpacked_destroy(&result);
+}
+
+/* API: expose built-in metrics /api/v1/metrics */
+static void cb_metrics(mk_request_t *request, void *data)
+{
+ struct flb_hs_buf *buf;
+
+ buf = metrics_get_latest();
+ if (!buf) {
+ mk_http_status(request, 404);
+ mk_http_done(request);
+ return;
+ }
+
+ buf->users++;
+
+ mk_http_status(request, 200);
+ flb_hs_add_content_type_to_req(request, FLB_HS_CONTENT_TYPE_JSON);
+ mk_http_send(request, buf->data, flb_sds_len(buf->data), NULL);
+ mk_http_done(request);
+
+ buf->users--;
+}
+
+/* Perform registration */
+int api_v1_metrics(struct flb_hs *hs)
+{
+
+ pthread_key_create(&hs_metrics_key, hs_metrics_key_destroy);
+
+ /* Create a message queue */
+ hs->qid_metrics = mk_mq_create(hs->ctx, "/metrics",
+ cb_mq_metrics, NULL);
+
+ /* HTTP end-points */
+ mk_vhost_handler(hs->ctx, hs->vid, "/api/v1/metrics/prometheus",
+ cb_metrics_prometheus, hs);
+ mk_vhost_handler(hs->ctx, hs->vid, "/api/v1/metrics", cb_metrics, hs);
+
+ return 0;
+}
diff --git a/fluent-bit/src/http_server/api/v1/metrics.h b/fluent-bit/src/http_server/api/v1/metrics.h
new file mode 100644
index 000000000..f6bb0d017
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v1/metrics.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 FLB_HS_API_V1_METRICS_H
+#define FLB_HS_API_V1_METRICS_H
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_http_server.h>
+#include <fluent-bit/flb_sds.h>
+
+int api_v1_metrics(struct flb_hs *hs);
+flb_sds_t metrics_help_txt(char *metric_name, flb_sds_t *metric_helptxt);
+
+#endif
diff --git a/fluent-bit/src/http_server/api/v1/plugins.c b/fluent-bit/src/http_server/api/v1/plugins.c
new file mode 100644
index 000000000..1b63843cf
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v1/plugins.c
@@ -0,0 +1,109 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_filter.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_http_server.h>
+#include <msgpack.h>
+
+/* API: List all built-in plugins */
+static void cb_plugins(mk_request_t *request, void *data)
+{
+ int len;
+ flb_sds_t out_buf;
+ msgpack_packer mp_pck;
+ msgpack_sbuffer mp_sbuf;
+ struct mk_list *head;
+ struct flb_input_plugin *in;
+ struct flb_filter_plugin *filter;
+ struct flb_output_plugin *out;
+ struct flb_hs *hs = data;
+ struct flb_config *config = hs->config;
+
+ /* initialize buffers */
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ msgpack_pack_map(&mp_pck, 1);
+ msgpack_pack_str(&mp_pck, 7);
+ msgpack_pack_str_body(&mp_pck, "plugins", 7);
+
+ /*
+ * plugins are: inputs, filters, outputs
+ */
+ msgpack_pack_map(&mp_pck, 3);
+
+ /* Inputs */
+ msgpack_pack_str(&mp_pck, 6);
+ msgpack_pack_str_body(&mp_pck, "inputs", 6);
+ len = mk_list_size(&config->in_plugins);
+ msgpack_pack_array(&mp_pck, len);
+ mk_list_foreach(head, &hs->config->in_plugins) {
+ in = mk_list_entry(head, struct flb_input_plugin, _head);
+ len = strlen(in->name);
+ msgpack_pack_str(&mp_pck, len);
+ msgpack_pack_str_body(&mp_pck, in->name, len);
+ }
+
+ /* Filters */
+ msgpack_pack_str(&mp_pck, 7);
+ msgpack_pack_str_body(&mp_pck, "filters", 7);
+ len = mk_list_size(&config->filter_plugins);
+ msgpack_pack_array(&mp_pck, len);
+ mk_list_foreach(head, &config->filter_plugins) {
+ filter = mk_list_entry(head, struct flb_filter_plugin, _head);
+ len = strlen(filter->name);
+ msgpack_pack_str(&mp_pck, len);
+ msgpack_pack_str_body(&mp_pck, filter->name, len);
+ }
+
+ /* Outputs */
+ msgpack_pack_str(&mp_pck, 7);
+ msgpack_pack_str_body(&mp_pck, "outputs", 7);
+ len = mk_list_size(&config->out_plugins);
+ msgpack_pack_array(&mp_pck, len);
+ mk_list_foreach(head, &config->out_plugins) {
+ out = mk_list_entry(head, struct flb_output_plugin, _head);
+ len = strlen(out->name);
+ msgpack_pack_str(&mp_pck, len);
+ msgpack_pack_str_body(&mp_pck, out->name, len);
+ }
+
+ /* Export to JSON */
+ out_buf = flb_msgpack_raw_to_json_sds(mp_sbuf.data, mp_sbuf.size);
+ msgpack_sbuffer_destroy(&mp_sbuf);
+
+ mk_http_status(request, 200);
+ mk_http_send(request,
+ out_buf, flb_sds_len(out_buf), NULL);
+ mk_http_done(request);
+
+ flb_sds_destroy(out_buf);
+}
+
+/* Perform registration */
+int api_v1_plugins(struct flb_hs *hs)
+{
+ mk_vhost_handler(hs->ctx, hs->vid, "/api/v1/plugins", cb_plugins, hs);
+ return 0;
+}
diff --git a/fluent-bit/src/http_server/api/v1/plugins.h b/fluent-bit/src/http_server/api/v1/plugins.h
new file mode 100644
index 000000000..d20940321
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v1/plugins.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 FLB_HS_API_V1_PLUGINS_H
+#define FLB_HS_API_V1_PLUGINS_H
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_http_server.h>
+
+int api_v1_plugins(struct flb_hs *hs);
+
+#endif
diff --git a/fluent-bit/src/http_server/api/v1/register.c b/fluent-bit/src/http_server/api/v1/register.c
new file mode 100644
index 000000000..093644b3e
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v1/register.c
@@ -0,0 +1,49 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_http_server.h>
+
+#include "uptime.h"
+#include "metrics.h"
+#include "storage.h"
+#include "plugins.h"
+#include "health.h"
+#include "trace.h"
+
+int api_v1_registration(struct flb_hs *hs)
+{
+ api_v1_uptime(hs);
+ api_v1_metrics(hs);
+ api_v1_plugins(hs);
+
+#ifdef FLB_HAVE_CHUNK_TRACE
+ api_v1_trace(hs);
+#endif /* FLB_HAVE_CHUNK_TRACE */
+
+ if (hs->config->health_check == FLB_TRUE) {
+ api_v1_health(hs);
+ }
+
+ if (hs->config->storage_metrics == FLB_TRUE) {
+ api_v1_storage_metrics(hs);
+ }
+
+ return 0;
+}
diff --git a/fluent-bit/src/http_server/api/v1/register.h b/fluent-bit/src/http_server/api/v1/register.h
new file mode 100644
index 000000000..978db9e0e
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v1/register.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 FLB_API_V1_REG_H
+#define FLB_API_V1_REG_H
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_http_server.h>
+
+int api_v1_registration(struct flb_hs *hs);
+
+#endif
diff --git a/fluent-bit/src/http_server/api/v1/storage.c b/fluent-bit/src/http_server/api/v1/storage.c
new file mode 100644
index 000000000..df47859d6
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v1/storage.c
@@ -0,0 +1,204 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_sds.h>
+#include "storage.h"
+
+#include <fluent-bit/flb_http_server.h>
+#include <msgpack.h>
+
+pthread_key_t hs_storage_metrics_key;
+
+/* Return the newest storage metrics buffer */
+static struct flb_hs_buf *storage_metrics_get_latest()
+{
+ struct flb_hs_buf *buf;
+ struct mk_list *metrics_list;
+
+ metrics_list = pthread_getspecific(hs_storage_metrics_key);
+ if (!metrics_list) {
+ return NULL;
+ }
+
+ if (mk_list_size(metrics_list) == 0) {
+ return NULL;
+ }
+
+ buf = mk_list_entry_last(metrics_list, struct flb_hs_buf, _head);
+ return buf;
+}
+
+/* Delete unused metrics, note that we only care about the latest node */
+static int cleanup_metrics()
+{
+ int c = 0;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct mk_list *metrics_list;
+ struct flb_hs_buf *last;
+ struct flb_hs_buf *entry;
+
+ metrics_list = pthread_getspecific(hs_storage_metrics_key);
+ if (!metrics_list) {
+ return -1;
+ }
+
+ last = storage_metrics_get_latest();
+ if (!last) {
+ return -1;
+ }
+
+ mk_list_foreach_safe(head, tmp, metrics_list) {
+ entry = mk_list_entry(head, struct flb_hs_buf, _head);
+ if (entry != last && entry->users == 0) {
+ mk_list_del(&entry->_head);
+ flb_sds_destroy(entry->data);
+ flb_free(entry->raw_data);
+ flb_free(entry);
+ c++;
+ }
+ }
+
+ return c;
+}
+
+/*
+ * Callback invoked every time some storage metrics are received through a
+ * message queue channel. This function runs in a Monkey HTTP thread
+ * worker and it purpose is to take the metrics data and store it
+ * somewhere so then it can be available by the end-points upon
+ * HTTP client requests.
+ */
+static void cb_mq_storage_metrics(mk_mq_t *queue, void *data, size_t size)
+{
+ flb_sds_t out_data;
+ struct flb_hs_buf *buf;
+ struct mk_list *metrics_list = NULL;
+
+ metrics_list = pthread_getspecific(hs_storage_metrics_key);
+ if (!metrics_list) {
+ metrics_list = flb_malloc(sizeof(struct mk_list));
+ if (!metrics_list) {
+ flb_errno();
+ return;
+ }
+ mk_list_init(metrics_list);
+ pthread_setspecific(hs_storage_metrics_key, metrics_list);
+ }
+
+ /* Convert msgpack to JSON */
+ out_data = flb_msgpack_raw_to_json_sds(data, size);
+ if (!out_data) {
+ return;
+ }
+
+ buf = flb_malloc(sizeof(struct flb_hs_buf));
+ if (!buf) {
+ flb_errno();
+ flb_sds_destroy(out_data);
+ return;
+ }
+ buf->users = 0;
+ buf->data = out_data;
+
+ buf->raw_data = flb_malloc(size);
+ memcpy(buf->raw_data, data, size);
+ buf->raw_size = size;
+
+ mk_list_add(&buf->_head, metrics_list);
+
+ cleanup_metrics();
+}
+
+/* FIXME: pending implementation of metrics exit interface
+static void cb_mq_storage_metrics_exit(mk_mq_t *queue, void *data)
+{
+
+}
+*/
+
+/* API: expose built-in storage metrics /api/v1/storage */
+static void cb_storage(mk_request_t *request, void *data)
+{
+ struct flb_hs_buf *buf;
+
+ buf = storage_metrics_get_latest();
+ if (!buf) {
+ mk_http_status(request, 404);
+ mk_http_done(request);
+ return;
+ }
+
+ buf->users++;
+
+ mk_http_status(request, 200);
+ flb_hs_add_content_type_to_req(request, FLB_HS_CONTENT_TYPE_JSON);
+ mk_http_send(request, buf->data, flb_sds_len(buf->data), NULL);
+ mk_http_done(request);
+
+ buf->users--;
+}
+
+static void hs_storage_metrics_key_destroy(void *data)
+{
+ struct mk_list *metrics_list = (struct mk_list*)data;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_hs_buf *entry;
+
+ if (metrics_list == NULL) {
+ return;
+ }
+
+ mk_list_foreach_safe(head, tmp, metrics_list) {
+ entry = mk_list_entry(head, struct flb_hs_buf, _head);
+ if (entry != NULL) {
+ if (entry->raw_data != NULL) {
+ flb_free(entry->raw_data);
+ entry->raw_data = NULL;
+ }
+ if (entry->data) {
+ flb_sds_destroy(entry->data);
+ entry->data = NULL;
+ }
+ mk_list_del(&entry->_head);
+ flb_free(entry);
+ }
+ }
+
+ flb_free(metrics_list);
+}
+
+/* Perform registration */
+int api_v1_storage_metrics(struct flb_hs *hs)
+{
+ pthread_key_create(&hs_storage_metrics_key, hs_storage_metrics_key_destroy);
+
+ /* Create a message queue */
+ hs->qid_storage = mk_mq_create(hs->ctx, "/storage",
+ cb_mq_storage_metrics,
+ NULL);
+
+ /* HTTP end-point */
+ mk_vhost_handler(hs->ctx, hs->vid, "/api/v1/storage", cb_storage, hs);
+
+ return 0;
+}
diff --git a/fluent-bit/src/http_server/api/v1/storage.h b/fluent-bit/src/http_server/api/v1/storage.h
new file mode 100644
index 000000000..27410c79b
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v1/storage.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 FLB_HS_API_V1_STORAGE_METRICS_H
+#define FLB_HS_API_V1_STORAGE_METRICS_H
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_http_server.h>
+
+int api_v1_storage_metrics(struct flb_hs *hs);
+
+#endif
diff --git a/fluent-bit/src/http_server/api/v1/trace.c b/fluent-bit/src/http_server/api/v1/trace.c
new file mode 100644
index 000000000..95da17343
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v1/trace.c
@@ -0,0 +1,615 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_filter.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_http_server.h>
+#include <fluent-bit/flb_lib.h>
+#include <fluent-bit/flb_chunk_trace.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_utils.h>
+#include <msgpack.h>
+
+
+static struct flb_input_instance *find_input(struct flb_hs *hs, const char *name)
+{
+ struct mk_list *head;
+ struct flb_input_instance *in;
+
+
+ mk_list_foreach(head, &hs->config->inputs) {
+ in = mk_list_entry(head, struct flb_input_instance, _head);
+ if (strcmp(name, in->name) == 0) {
+ return in;
+ }
+ if (in->alias) {
+ if (strcmp(name, in->alias) == 0) {
+ return in;
+ }
+ }
+ }
+ return NULL;
+}
+
+static int enable_trace_input(struct flb_hs *hs, const char *name, const char *prefix, const char *output_name, struct mk_list *props)
+{
+ struct flb_input_instance *in;
+
+
+ in = find_input(hs, name);
+ if (in == NULL) {
+ return 404;
+ }
+
+ flb_chunk_trace_context_new(in, output_name, prefix, NULL, props);
+ return (in->chunk_trace_ctxt == NULL ? 503 : 0);
+}
+
+static int disable_trace_input(struct flb_hs *hs, const char *name)
+{
+ struct flb_input_instance *in;
+
+
+ in = find_input(hs, name);
+ if (in == NULL) {
+ return 404;
+ }
+
+ if (in->chunk_trace_ctxt != NULL) {
+ flb_chunk_trace_context_destroy(in);
+ }
+ return 201;
+}
+
+static flb_sds_t get_input_name(mk_request_t *request)
+{
+ const char *base = "/api/v1/trace/";
+
+
+ if (request->real_path.data == NULL) {
+ return NULL;
+ }
+ if (request->real_path.len < strlen(base)) {
+ return NULL;
+ }
+
+ return flb_sds_create_len(&request->real_path.data[strlen(base)],
+ request->real_path.len - strlen(base));
+}
+
+static int http_disable_trace(mk_request_t *request, void *data, const char *input_name, msgpack_packer *mp_pck)
+{
+ struct flb_hs *hs = data;
+ int toggled_on = 503;
+
+
+ toggled_on = disable_trace_input(hs, input_name);
+ if (toggled_on < 300) {
+ msgpack_pack_map(mp_pck, 1);
+ msgpack_pack_str_with_body(mp_pck, "status", strlen("status"));
+ msgpack_pack_str_with_body(mp_pck, "ok", strlen("ok"));
+ return 201;
+ }
+
+ return toggled_on;
+}
+
+static int msgpack_params_enable_trace(struct flb_hs *hs, msgpack_unpacked *result, const char *input_name)
+{
+ int ret = -1;
+ int i;
+ int x;
+ flb_sds_t prefix = NULL;
+ flb_sds_t output_name = NULL;
+ int toggled_on = -1;
+ msgpack_object *key;
+ msgpack_object *val;
+ struct mk_list *props = NULL;
+ msgpack_object_kv *param;
+ msgpack_object_str *param_key;
+ msgpack_object_str *param_val;
+
+
+ if (result->data.type == MSGPACK_OBJECT_MAP) {
+ for (i = 0; i < result->data.via.map.size; i++) {
+ key = &result->data.via.map.ptr[i].key;
+ val = &result->data.via.map.ptr[i].val;
+
+ if (key->type != MSGPACK_OBJECT_STR) {
+ ret = -1;
+ goto parse_error;
+ }
+
+ if (strncmp(key->via.str.ptr, "prefix", key->via.str.size) == 0) {
+ if (val->type != MSGPACK_OBJECT_STR) {
+ ret = -1;
+ goto parse_error;
+ }
+ if (prefix != NULL) {
+ flb_sds_destroy(prefix);
+ }
+ prefix = flb_sds_create_len(val->via.str.ptr, val->via.str.size);
+ }
+ else if (strncmp(key->via.str.ptr, "output", key->via.str.size) == 0) {
+ if (val->type != MSGPACK_OBJECT_STR) {
+ ret = -1;
+ goto parse_error;
+ }
+ if (output_name != NULL) {
+ flb_sds_destroy(output_name);
+ }
+ output_name = flb_sds_create_len(val->via.str.ptr, val->via.str.size);
+ }
+ else if (strncmp(key->via.str.ptr, "params", key->via.str.size) == 0) {
+ if (val->type != MSGPACK_OBJECT_MAP) {
+ ret = -1;
+ goto parse_error;
+ }
+ if (props != NULL) {
+ flb_free(props);
+ }
+ props = flb_calloc(1, sizeof(struct mk_list));
+ flb_kv_init(props);
+ for (x = 0; x < val->via.map.size; x++) {
+ param = &val->via.map.ptr[x];
+ if (param->val.type != MSGPACK_OBJECT_STR) {
+ ret = -1;
+ goto parse_error;
+ }
+ if (param->key.type != MSGPACK_OBJECT_STR) {
+ ret = -1;
+ goto parse_error;
+ }
+ param_key = &param->key.via.str;
+ param_val = &param->val.via.str;
+ flb_kv_item_create_len(props,
+ (char *)param_key->ptr, param_key->size,
+ (char *)param_val->ptr, param_val->size);
+ }
+ }
+ }
+
+ if (output_name == NULL) {
+ output_name = flb_sds_create("stdout");
+ }
+
+ toggled_on = enable_trace_input(hs, input_name, prefix, output_name, props);
+ if (!toggled_on) {
+ ret = -1;
+ goto parse_error;
+ }
+ }
+
+parse_error:
+ if (prefix) flb_sds_destroy(prefix);
+ if (output_name) flb_sds_destroy(output_name);
+ if (props != NULL) {
+ flb_kv_release(props);
+ flb_free(props);
+ }
+ return ret;
+}
+
+static int http_enable_trace(mk_request_t *request, void *data, const char *input_name, msgpack_packer *mp_pck)
+{
+ char *buf = NULL;
+ size_t buf_size;
+ msgpack_unpacked result;
+ int ret = -1;
+ int rc = -1;
+ int i;
+ int x;
+ size_t off = 0;
+ int root_type = MSGPACK_OBJECT_ARRAY;
+ struct flb_hs *hs = data;
+ flb_sds_t prefix = NULL;
+ flb_sds_t output_name = NULL;
+ msgpack_object *key;
+ msgpack_object *val;
+ struct mk_list *props = NULL;
+ struct flb_chunk_trace_limit limit = { 0 };
+ struct flb_input_instance *input_instance;
+
+
+ if (request->method == MK_METHOD_GET) {
+ ret = enable_trace_input(hs, input_name, "trace.", "stdout", NULL);
+ if (ret == 0) {
+ msgpack_pack_map(mp_pck, 1);
+ msgpack_pack_str_with_body(mp_pck, "status", strlen("status"));
+ msgpack_pack_str_with_body(mp_pck, "ok", strlen("ok"));
+ return 200;
+ }
+ else {
+ flb_error("unable to enable tracing for %s", input_name);
+ goto input_error;
+ }
+ }
+
+ msgpack_unpacked_init(&result);
+ rc = flb_pack_json(request->data.data, request->data.len, &buf, &buf_size,
+ &root_type, NULL);
+ if (rc == -1) {
+ ret = 503;
+ flb_error("unable to parse json parameters");
+ goto unpack_error;
+ }
+
+ rc = msgpack_unpack_next(&result, buf, buf_size, &off);
+ if (rc != MSGPACK_UNPACK_SUCCESS) {
+ ret = 503;
+ flb_error("unable to unpack msgpack parameters for %s", input_name);
+ goto unpack_error;
+ }
+
+ if (result.data.type == MSGPACK_OBJECT_MAP) {
+ for (i = 0; i < result.data.via.map.size; i++) {
+ key = &result.data.via.map.ptr[i].key;
+ val = &result.data.via.map.ptr[i].val;
+
+ if (key->type != MSGPACK_OBJECT_STR) {
+ ret = 503;
+ flb_error("non string key in parameters");
+ goto parse_error;
+ }
+
+ if (strncmp(key->via.str.ptr, "prefix", key->via.str.size) == 0) {
+ if (val->type != MSGPACK_OBJECT_STR) {
+ ret = 503;
+ flb_error("prefix is not a string");
+ goto parse_error;
+ }
+ if (prefix != NULL) {
+ flb_sds_destroy(prefix);
+ }
+ prefix = flb_sds_create_len(val->via.str.ptr, val->via.str.size);
+ }
+ else if (strncmp(key->via.str.ptr, "output", key->via.str.size) == 0) {
+ if (val->type != MSGPACK_OBJECT_STR) {
+ ret = 503;
+ flb_error("output is not a string");
+ goto parse_error;
+ }
+ if (output_name != NULL) {
+ flb_sds_destroy(output_name);
+ }
+ output_name = flb_sds_create_len(val->via.str.ptr, val->via.str.size);
+ }
+ else if (strncmp(key->via.str.ptr, "params", key->via.str.size) == 0) {
+ if (val->type != MSGPACK_OBJECT_MAP) {
+ ret = 503;
+ flb_error("output params is not a maps");
+ goto parse_error;
+ }
+ props = flb_calloc(1, sizeof(struct mk_list));
+ flb_kv_init(props);
+ for (x = 0; x < val->via.map.size; x++) {
+ if (val->via.map.ptr[x].val.type != MSGPACK_OBJECT_STR) {
+ ret = 503;
+ flb_error("output parameter key is not a string");
+ goto parse_error;
+ }
+ if (val->via.map.ptr[x].key.type != MSGPACK_OBJECT_STR) {
+ ret = 503;
+ flb_error("output parameter value is not a string");
+ goto parse_error;
+ }
+ flb_kv_item_create_len(props,
+ (char *)val->via.map.ptr[x].key.via.str.ptr, val->via.map.ptr[x].key.via.str.size,
+ (char *)val->via.map.ptr[x].val.via.str.ptr, val->via.map.ptr[x].val.via.str.size);
+ }
+ }
+ else if (strncmp(key->via.str.ptr, "limit", key->via.str.size) == 0) {
+ if (val->type != MSGPACK_OBJECT_MAP) {
+ ret = 503;
+ flb_error("limit must be a map of limit types");
+ goto parse_error;
+ }
+ if (val->via.map.size != 1) {
+ ret = 503;
+ flb_error("limit must have a single limit type");
+ goto parse_error;
+ }
+ if (val->via.map.ptr[0].key.type != MSGPACK_OBJECT_STR) {
+ ret = 503;
+ flb_error("limit type (key) must be a string");
+ goto parse_error;
+ }
+ if (val->via.map.ptr[0].val.type != MSGPACK_OBJECT_POSITIVE_INTEGER) {
+ ret = 503;
+ flb_error("limit type must be an integer");
+ goto parse_error;
+ }
+ if (strncmp(val->via.map.ptr[0].key.via.str.ptr, "seconds", val->via.map.ptr[0].key.via.str.size) == 0) {
+ limit.type = FLB_CHUNK_TRACE_LIMIT_TIME;
+ limit.seconds = val->via.map.ptr[0].val.via.u64;
+ }
+ else if (strncmp(val->via.map.ptr[0].key.via.str.ptr, "count", val->via.map.ptr[0].key.via.str.size) == 0) {
+ limit.type = FLB_CHUNK_TRACE_LIMIT_COUNT;
+ limit.count = val->via.map.ptr[0].val.via.u64;
+ }
+ else {
+ ret = 503;
+ flb_error("unknown limit type");
+ goto parse_error;
+ }
+ }
+ }
+
+ if (output_name == NULL) {
+ output_name = flb_sds_create("stdout");
+ }
+
+ ret = enable_trace_input(hs, input_name, prefix, output_name, props);
+ if (ret != 0) {
+ flb_error("error when enabling tracing");
+ goto parse_error;
+ }
+
+ if (limit.type != 0) {
+ input_instance = find_input(hs, input_name);
+ if (limit.type == FLB_CHUNK_TRACE_LIMIT_TIME) {
+ flb_chunk_trace_context_set_limit(input_instance->chunk_trace_ctxt, limit.type, limit.seconds);
+ }
+ else if (limit.type == FLB_CHUNK_TRACE_LIMIT_COUNT) {
+ flb_chunk_trace_context_set_limit(input_instance->chunk_trace_ctxt, limit.type, limit.count);
+ }
+ }
+ }
+
+ msgpack_pack_map(mp_pck, 1);
+ msgpack_pack_str_with_body(mp_pck, "status", strlen("status"));
+ msgpack_pack_str_with_body(mp_pck, "ok", strlen("ok"));
+
+ ret = 200;
+parse_error:
+ if (prefix) flb_sds_destroy(prefix);
+ if (output_name) flb_sds_destroy(output_name);
+ if (props != NULL) {
+ flb_kv_release(props);
+ flb_free(props);
+ }
+unpack_error:
+ msgpack_unpacked_destroy(&result);
+ if (buf != NULL) {
+ flb_free(buf);
+ }
+input_error:
+ return ret;
+}
+
+static void cb_trace(mk_request_t *request, void *data)
+{
+ flb_sds_t out_buf;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+ int response = 404;
+ flb_sds_t input_name = NULL;
+
+
+ /* initialize buffers */
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ input_name = get_input_name(request);
+ if (input_name == NULL) {
+ response = 404;
+ goto error;
+ }
+
+ if (request->method == MK_METHOD_POST || request->method == MK_METHOD_GET) {
+ response = http_enable_trace(request, data, input_name, &mp_pck);
+ }
+ else if (request->method == MK_METHOD_DELETE) {
+ response = http_disable_trace(request, data, input_name, &mp_pck);
+ }
+error:
+ if (response == 404) {
+ msgpack_pack_map(&mp_pck, 1);
+ msgpack_pack_str_with_body(&mp_pck, "status", strlen("status"));
+ msgpack_pack_str_with_body(&mp_pck, "not found", strlen("not found"));
+ }
+ else if (response == 503) {
+ msgpack_pack_map(&mp_pck, 1);
+ msgpack_pack_str_with_body(&mp_pck, "status", strlen("status"));
+ msgpack_pack_str_with_body(&mp_pck, "error", strlen("error"));
+ }
+
+ if (input_name != NULL) {
+ flb_sds_destroy(input_name);
+ }
+
+ /* Export to JSON */
+ out_buf = flb_msgpack_raw_to_json_sds(mp_sbuf.data, mp_sbuf.size);
+ if (out_buf == NULL) {
+ mk_http_status(request, 503);
+ mk_http_done(request);
+ return;
+ }
+
+ mk_http_status(request, response);
+ mk_http_send(request, out_buf, flb_sds_len(out_buf), NULL);
+ mk_http_done(request);
+
+ msgpack_sbuffer_destroy(&mp_sbuf);
+ flb_sds_destroy(out_buf);
+}
+
+static void cb_traces(mk_request_t *request, void *data)
+{
+ flb_sds_t out_buf;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+ int ret;
+ char *buf = NULL;
+ size_t buf_size;
+ int root_type = MSGPACK_OBJECT_ARRAY;
+ msgpack_unpacked result;
+ flb_sds_t error_msg = NULL;
+ int response = 200;
+ flb_sds_t input_name;
+ msgpack_object_array *inputs = NULL;
+ size_t off = 0;
+ int i;
+
+
+ /* initialize buffers */
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ msgpack_unpacked_init(&result);
+ ret = flb_pack_json(request->data.data, request->data.len, &buf, &buf_size,
+ &root_type, NULL);
+ if (ret == -1) {
+ goto unpack_error;
+ }
+
+ ret = msgpack_unpack_next(&result, buf, buf_size, &off);
+ if (ret != MSGPACK_UNPACK_SUCCESS) {
+ ret = -1;
+ error_msg = flb_sds_create("unfinished input");
+ goto unpack_error;
+ }
+
+ if (result.data.type != MSGPACK_OBJECT_MAP) {
+ response = 503;
+ error_msg = flb_sds_create("input is not an object");
+ goto unpack_error;
+ }
+
+ for (i = 0; i < result.data.via.map.size; i++) {
+ if (result.data.via.map.ptr[i].val.type != MSGPACK_OBJECT_ARRAY) {
+ continue;
+ }
+ if (result.data.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR) {
+ continue;
+ }
+ if (result.data.via.map.ptr[i].key.via.str.size < strlen("inputs")) {
+ continue;
+ }
+ if (strncmp(result.data.via.map.ptr[i].key.via.str.ptr, "inputs", strlen("inputs"))) {
+ continue;
+ }
+ inputs = &result.data.via.map.ptr[i].val.via.array;
+ }
+
+ if (inputs == NULL) {
+ response = 503;
+ error_msg = flb_sds_create("inputs not found");
+ goto unpack_error;
+ }
+
+ msgpack_pack_map(&mp_pck, 2);
+
+ msgpack_pack_str_with_body(&mp_pck, "inputs", strlen("inputs"));
+ msgpack_pack_map(&mp_pck, inputs->size);
+
+ for (i = 0; i < inputs->size; i++) {
+ input_name = flb_sds_create_len(inputs->ptr[i].via.str.ptr, inputs->ptr[i].via.str.size);
+ msgpack_pack_str_with_body(&mp_pck, input_name, flb_sds_len(input_name));
+
+ if (inputs->ptr[i].type != MSGPACK_OBJECT_STR) {
+ msgpack_pack_map(&mp_pck, 1);
+ msgpack_pack_str_with_body(&mp_pck, "status", strlen("status"));
+ msgpack_pack_str_with_body(&mp_pck, "error", strlen("error"));
+ }
+ else {
+ if (request->method == MK_METHOD_POST || request->method == MK_METHOD_GET) {
+ ret = msgpack_params_enable_trace((struct flb_hs *)data, &result, input_name);
+ if (ret != 0) {
+ msgpack_pack_map(&mp_pck, 2);
+ msgpack_pack_str_with_body(&mp_pck, "status", strlen("status"));
+ msgpack_pack_str_with_body(&mp_pck, "error", strlen("error"));
+ msgpack_pack_str_with_body(&mp_pck, "returncode", strlen("returncode"));
+ msgpack_pack_int64(&mp_pck, ret);
+ }
+ else {
+ msgpack_pack_map(&mp_pck, 1);
+ msgpack_pack_str_with_body(&mp_pck, "status", strlen("status"));
+ msgpack_pack_str_with_body(&mp_pck, "ok", strlen("ok"));
+ }
+ }
+ else if (request->method == MK_METHOD_DELETE) {
+ disable_trace_input((struct flb_hs *)data, input_name);
+ }
+ else {
+ msgpack_pack_map(&mp_pck, 2);
+ msgpack_pack_str_with_body(&mp_pck, "status", strlen("status"));
+ msgpack_pack_str_with_body(&mp_pck, "error", strlen("error"));
+ msgpack_pack_str_with_body(&mp_pck, "message", strlen("message"));
+ msgpack_pack_str_with_body(&mp_pck, "method not allowed", strlen("method not allowed"));
+ }
+ }
+ }
+
+ msgpack_pack_str_with_body(&mp_pck, "result", strlen("result"));
+unpack_error:
+ if (buf != NULL) {
+ flb_free(buf);
+ }
+ msgpack_unpacked_destroy(&result);
+ if (response == 404) {
+ msgpack_pack_map(&mp_pck, 1);
+ msgpack_pack_str_with_body(&mp_pck, "status", strlen("status"));
+ msgpack_pack_str_with_body(&mp_pck, "not found", strlen("not found"));
+ }
+ else if (response == 503) {
+ msgpack_pack_map(&mp_pck, 2);
+ msgpack_pack_str_with_body(&mp_pck, "status", strlen("status"));
+ msgpack_pack_str_with_body(&mp_pck, "error", strlen("error"));
+ msgpack_pack_str_with_body(&mp_pck, "message", strlen("message"));
+ if (error_msg) {
+ msgpack_pack_str_with_body(&mp_pck, error_msg, flb_sds_len(error_msg));
+ flb_sds_destroy(error_msg);
+ }
+ else {
+ msgpack_pack_str_with_body(&mp_pck, "unknown error", strlen("unknown error"));
+ }
+ }
+ else {
+ msgpack_pack_map(&mp_pck, 1);
+ msgpack_pack_str_with_body(&mp_pck, "status", strlen("status"));
+ msgpack_pack_str_with_body(&mp_pck, "ok", strlen("ok"));
+ }
+
+ /* Export to JSON */
+ out_buf = flb_msgpack_raw_to_json_sds(mp_sbuf.data, mp_sbuf.size);
+ if (out_buf == NULL) {
+ out_buf = flb_sds_create("serialization error");
+ }
+ msgpack_sbuffer_destroy(&mp_sbuf);
+
+ mk_http_status(request, response);
+ mk_http_send(request,
+ out_buf, flb_sds_len(out_buf), NULL);
+ mk_http_done(request);
+
+ flb_sds_destroy(out_buf);
+}
+
+/* Perform registration */
+int api_v1_trace(struct flb_hs *hs)
+{
+ mk_vhost_handler(hs->ctx, hs->vid, "/api/v1/traces/", cb_traces, hs);
+ mk_vhost_handler(hs->ctx, hs->vid, "/api/v1/trace/*", cb_trace, hs);
+ return 0;
+}
diff --git a/fluent-bit/src/http_server/api/v1/trace.h b/fluent-bit/src/http_server/api/v1/trace.h
new file mode 100644
index 000000000..236925de8
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v1/trace.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 FLB_HS_API_V1_TRACE_H
+#define FLB_HS_API_V1_TRACE_H
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_http_server.h>
+
+int api_v1_trace(struct flb_hs *hs);
+
+#endif
diff --git a/fluent-bit/src/http_server/api/v1/uptime.c b/fluent-bit/src/http_server/api/v1/uptime.c
new file mode 100644
index 000000000..37f971871
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v1/uptime.c
@@ -0,0 +1,111 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_http_server.h>
+#include <fluent-bit/flb_mem.h>
+
+#define FLB_UPTIME_ONEDAY 86400
+#define FLB_UPTIME_ONEHOUR 3600
+#define FLB_UPTIME_ONEMINUTE 60
+
+/* Append human-readable uptime */
+static void uptime_hr(time_t uptime, msgpack_packer *mp_pck)
+{
+ int len;
+ int days;
+ int hours;
+ int minutes;
+ int seconds;
+ long int upmind;
+ long int upminh;
+ char buf[256];
+
+ /* days */
+ days = uptime / FLB_UPTIME_ONEDAY;
+ upmind = uptime - (days * FLB_UPTIME_ONEDAY);
+
+ /* hours */
+ hours = upmind / FLB_UPTIME_ONEHOUR;
+ upminh = upmind - hours * FLB_UPTIME_ONEHOUR;
+
+ /* minutes */
+ minutes = upminh / FLB_UPTIME_ONEMINUTE;
+ seconds = upminh - minutes * FLB_UPTIME_ONEMINUTE;
+
+ len = snprintf(buf, sizeof(buf) - 1,
+ "Fluent Bit has been running: "
+ " %i day%s, %i hour%s, %i minute%s and %i second%s",
+ days, (days > 1) ? "s" : "", hours, \
+ (hours > 1) ? "s" : "", minutes, \
+ (minutes > 1) ? "s" : "", seconds, \
+ (seconds > 1) ? "s" : "");
+ msgpack_pack_str(mp_pck, 9);
+ msgpack_pack_str_body(mp_pck, "uptime_hr", 9);
+ msgpack_pack_str(mp_pck, len);
+ msgpack_pack_str_body(mp_pck, buf, len);
+}
+
+/* API: List all built-in plugins */
+static void cb_uptime(mk_request_t *request, void *data)
+{
+ flb_sds_t out_buf;
+ size_t out_size;
+ time_t uptime;
+ msgpack_packer mp_pck;
+ msgpack_sbuffer mp_sbuf;
+ struct flb_hs *hs = data;
+ struct flb_config *config = hs->config;
+
+ /* initialize buffers */
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ msgpack_pack_map(&mp_pck, 2);
+ msgpack_pack_str(&mp_pck, 10);
+ msgpack_pack_str_body(&mp_pck, "uptime_sec", 10);
+
+ uptime = time(NULL) - config->init_time;
+ msgpack_pack_uint64(&mp_pck, uptime);
+
+ uptime_hr(uptime, &mp_pck);
+
+ /* Export to JSON */
+ out_buf = flb_msgpack_raw_to_json_sds(mp_sbuf.data, mp_sbuf.size);
+ msgpack_sbuffer_destroy(&mp_sbuf);
+ if (!out_buf) {
+ return;
+ }
+ out_size = flb_sds_len(out_buf);
+
+ mk_http_status(request, 200);
+ flb_hs_add_content_type_to_req(request, FLB_HS_CONTENT_TYPE_JSON);
+ mk_http_send(request, out_buf, out_size, NULL);
+ mk_http_done(request);
+
+ flb_sds_destroy(out_buf);
+}
+
+/* Perform registration */
+int api_v1_uptime(struct flb_hs *hs)
+{
+ mk_vhost_handler(hs->ctx, hs->vid, "/api/v1/uptime", cb_uptime, hs);
+ return 0;
+}
diff --git a/fluent-bit/src/http_server/api/v1/uptime.h b/fluent-bit/src/http_server/api/v1/uptime.h
new file mode 100644
index 000000000..5a4247798
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v1/uptime.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 FLB_HS_API_V1_UPTIME_H
+#define FLB_HS_API_V1_UPTIME_H
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_http_server.h>
+
+int api_v1_uptime(struct flb_hs *hs);
+
+#endif
diff --git a/fluent-bit/src/http_server/api/v2/CMakeLists.txt b/fluent-bit/src/http_server/api/v2/CMakeLists.txt
new file mode 100644
index 000000000..a9d590fbd
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v2/CMakeLists.txt
@@ -0,0 +1,10 @@
+# api/v2
+set(src
+ metrics.c
+ reload.c
+ register.c
+ )
+
+include_directories(${MONKEY_INCLUDE_DIR})
+add_library(api-v2 STATIC ${src})
+target_link_libraries(api-v2 monkey-core-static fluent-bit-static)
diff --git a/fluent-bit/src/http_server/api/v2/metrics.c b/fluent-bit/src/http_server/api/v2/metrics.c
new file mode 100644
index 000000000..27513b7a4
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v2/metrics.c
@@ -0,0 +1,259 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_filter.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_version.h>
+#include <fluent-bit/flb_time.h>
+#include "metrics.h"
+
+#include <fluent-bit/flb_http_server.h>
+
+#define null_check(x) do { if (!x) { goto error; } else {sds = x;} } while (0)
+
+pthread_key_t hs_metrics_v2_key;
+
+static struct mk_list *hs_metrics_v2_key_create()
+{
+ struct mk_list *metrics_list = NULL;
+
+ metrics_list = flb_malloc(sizeof(struct mk_list));
+ if (metrics_list == NULL) {
+ flb_errno();
+ return NULL;
+ }
+ mk_list_init(metrics_list);
+ pthread_setspecific(hs_metrics_v2_key, metrics_list);
+
+ return metrics_list;
+}
+
+static void hs_metrics_v2_key_destroy(void *data)
+{
+ struct mk_list *metrics_list = (struct mk_list*) data;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_hs_buf *entry;
+
+ if (metrics_list == NULL) {
+ return;
+ }
+ mk_list_foreach_safe(head, tmp, metrics_list) {
+ entry = mk_list_entry(head, struct flb_hs_buf, _head);
+ if (entry != NULL) {
+ if (entry->raw_data != NULL) {
+ cmt_destroy(entry->raw_data);
+ entry->raw_data = NULL;
+ }
+ mk_list_del(&entry->_head);
+ flb_free(entry);
+ }
+ }
+
+ flb_free(metrics_list);
+}
+
+/* Return the newest metrics buffer */
+static struct flb_hs_buf *metrics_get_latest()
+{
+ struct flb_hs_buf *buf;
+ struct mk_list *metrics_list;
+
+ metrics_list = pthread_getspecific(hs_metrics_v2_key);
+ if (!metrics_list) {
+ return NULL;
+ }
+
+ if (mk_list_size(metrics_list) == 0) {
+ return NULL;
+ }
+
+ buf = mk_list_entry_last(metrics_list, struct flb_hs_buf, _head);
+ return buf;
+}
+
+/* Delete unused metrics, note that we only care about the latest node */
+static int cleanup_metrics()
+{
+ int c = 0;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct mk_list *metrics_list;
+ struct flb_hs_buf *last;
+ struct flb_hs_buf *entry;
+
+ metrics_list = pthread_getspecific(hs_metrics_v2_key);
+ if (!metrics_list) {
+ return -1;
+ }
+
+ last = metrics_get_latest();
+ if (!last) {
+ return -1;
+ }
+
+ mk_list_foreach_safe(head, tmp, metrics_list) {
+ entry = mk_list_entry(head, struct flb_hs_buf, _head);
+ if (entry != last && entry->users == 0) {
+ mk_list_del(&entry->_head);
+ cmt_destroy(entry->raw_data);
+ flb_free(entry);
+ c++;
+ }
+ }
+
+ return c;
+}
+
+/*
+ * Callback invoked every time some metrics are received through a message queue channel.
+ * This function runs in a Monkey HTTP thread worker and it purpose is to take the metrics
+ * data and store it somewhere so then it can be available by the end-points upon
+ * HTTP client requests.
+ */
+static void cb_mq_metrics(mk_mq_t *queue, void *data, size_t size)
+{
+ int ret;
+ size_t off = 0;
+ struct cmt *cmt;
+ struct flb_hs_buf *buf;
+ struct mk_list *metrics_list = NULL;
+
+ metrics_list = pthread_getspecific(hs_metrics_v2_key);
+ if (!metrics_list) {
+ metrics_list = hs_metrics_v2_key_create();
+ if (metrics_list == NULL) {
+ return;
+ }
+ }
+
+ /* decode cmetrics */
+ ret = cmt_decode_msgpack_create(&cmt, data, size, &off);
+ if (ret != 0) {
+ return;
+ }
+
+ buf = flb_malloc(sizeof(struct flb_hs_buf));
+ if (!buf) {
+ flb_errno();
+ return;
+ }
+ buf->users = 0;
+ buf->data = NULL;
+
+ /* Store CMetrics context as the raw_data */
+ buf->raw_data = cmt;
+ buf->raw_size = 0;
+
+ mk_list_add(&buf->_head, metrics_list);
+ cleanup_metrics();
+}
+
+/* API: expose metrics in Prometheus format /api/v2/metrics/prometheus */
+static void cb_metrics_prometheus(mk_request_t *request, void *data)
+{
+ struct cmt *cmt;
+ struct flb_hs_buf *buf;
+ cfl_sds_t payload;
+
+ buf = metrics_get_latest();
+ if (!buf) {
+ mk_http_status(request, 404);
+ mk_http_done(request);
+ return;
+ }
+
+ cmt = (struct cmt *) buf->raw_data;
+
+ /* convert CMetrics to text */
+ payload = cmt_encode_prometheus_create(cmt, CMT_FALSE);
+ if (!payload) {
+ mk_http_status(request, 500);
+ mk_http_done(request);
+ return;
+ }
+
+ buf->users++;
+
+ mk_http_status(request, 200);
+ flb_hs_add_content_type_to_req(request, FLB_HS_CONTENT_TYPE_PROMETHEUS);
+ mk_http_send(request, payload, cfl_sds_len(payload), NULL);
+ mk_http_done(request);
+
+ cmt_encode_prometheus_destroy(payload);
+
+ buf->users--;
+}
+
+/* API: expose built-in metrics /api/v1/metrics (JSON format) */
+static void cb_metrics(mk_request_t *request, void *data)
+{
+ struct cmt *cmt;
+ struct flb_hs_buf *buf;
+ cfl_sds_t payload;
+
+ buf = metrics_get_latest();
+ if (!buf) {
+ mk_http_status(request, 404);
+ mk_http_done(request);
+ return;
+ }
+
+ cmt = (struct cmt *) buf->raw_data;
+
+ /* convert CMetrics to text */
+ payload = cmt_encode_text_create(cmt);
+ if (!payload) {
+ mk_http_status(request, 500);
+ mk_http_done(request);
+ return;
+ }
+
+ buf->users++;
+
+ mk_http_status(request, 200);
+ mk_http_send(request, payload, cfl_sds_len(payload), NULL);
+ mk_http_done(request);
+
+ cmt_encode_text_destroy(payload);
+
+ buf->users--;
+}
+
+/* Perform registration */
+int api_v2_metrics(struct flb_hs *hs)
+{
+
+ pthread_key_create(&hs_metrics_v2_key, hs_metrics_v2_key_destroy);
+
+ /* Create a message queue */
+ hs->qid_metrics_v2 = mk_mq_create(hs->ctx, "/metrics_v2",
+ cb_mq_metrics, NULL);
+ /* HTTP end-points */
+ mk_vhost_handler(hs->ctx, hs->vid, "/api/v2/metrics/prometheus",
+ cb_metrics_prometheus, hs);
+
+ mk_vhost_handler(hs->ctx, hs->vid, "/api/v2/metrics", cb_metrics, hs);
+
+ return 0;
+}
diff --git a/fluent-bit/src/http_server/api/v2/metrics.h b/fluent-bit/src/http_server/api/v2/metrics.h
new file mode 100644
index 000000000..5336865dd
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v2/metrics.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 FLB_HS_API_V2_METRICS_H
+#define FLB_HS_API_V2_METRICS_H
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_http_server.h>
+
+int api_v2_metrics(struct flb_hs *hs);
+
+#endif
diff --git a/fluent-bit/src/http_server/api/v2/register.c b/fluent-bit/src/http_server/api/v2/register.c
new file mode 100644
index 000000000..7a0956fbf
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v2/register.c
@@ -0,0 +1,31 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2023 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_http_server.h>
+
+#include "metrics.h"
+#include "reload.h"
+
+int api_v2_registration(struct flb_hs *hs)
+{
+ api_v2_reload(hs);
+ api_v2_metrics(hs);
+ return 0;
+}
diff --git a/fluent-bit/src/http_server/api/v2/register.h b/fluent-bit/src/http_server/api/v2/register.h
new file mode 100644
index 000000000..da6d78f3a
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v2/register.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2023 The Fluent Bit Authors
+ *
+ * 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 FLB_API_V2_REG_H
+#define FLB_API_V2_REG_H
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_http_server.h>
+
+int api_v2_registration(struct flb_hs *hs);
+
+#endif
diff --git a/fluent-bit/src/http_server/api/v2/reload.c b/fluent-bit/src/http_server/api/v2/reload.c
new file mode 100644
index 000000000..3bb5159fa
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v2/reload.c
@@ -0,0 +1,161 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2023 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_filter.h>
+#include <fluent-bit/flb_output.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_version.h>
+#include <fluent-bit/flb_time.h>
+#include <fluent-bit/flb_lib.h>
+#include <fluent-bit/flb_reload.h>
+#include "reload.h"
+
+#include <signal.h>
+
+#include <fluent-bit/flb_http_server.h>
+
+static void handle_reload_request(mk_request_t *request, struct flb_config *config)
+{
+ int ret;
+ flb_sds_t out_buf;
+ size_t out_size;
+ msgpack_packer mp_pck;
+ msgpack_sbuffer mp_sbuf;
+
+ /* initialize buffers */
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ msgpack_pack_map(&mp_pck, 2);
+ msgpack_pack_str(&mp_pck, 6);
+ msgpack_pack_str_body(&mp_pck, "reload", 6);
+
+#ifdef FLB_SYSTEM_WINDOWS
+ ret = -1;
+
+ msgpack_pack_str(&mp_pck, 11);
+ msgpack_pack_str_body(&mp_pck, "unsupported", 11);
+ msgpack_pack_str(&mp_pck, 6);
+ msgpack_pack_str_body(&mp_pck, "status", 6);
+ msgpack_pack_int64(&mp_pck, ret);
+#else
+ if (config->enable_hot_reload != FLB_TRUE) {
+ msgpack_pack_str(&mp_pck, 11);
+ msgpack_pack_str_body(&mp_pck, "not enabled", 11);
+ msgpack_pack_str(&mp_pck, 6);
+ msgpack_pack_str_body(&mp_pck, "status", 6);
+ msgpack_pack_int64(&mp_pck, -1);
+ }
+ else {
+ ret = kill(getpid(), SIGHUP);
+ if (ret != 0) {
+ mk_http_status(request, 500);
+ mk_http_done(request);
+ return;
+ }
+
+ msgpack_pack_str(&mp_pck, 4);
+ msgpack_pack_str_body(&mp_pck, "done", 4);
+ msgpack_pack_str(&mp_pck, 6);
+ msgpack_pack_str_body(&mp_pck, "status", 6);
+ msgpack_pack_int64(&mp_pck, ret);
+ }
+
+#endif
+
+ /* Export to JSON */
+ out_buf = flb_msgpack_raw_to_json_sds(mp_sbuf.data, mp_sbuf.size);
+ msgpack_sbuffer_destroy(&mp_sbuf);
+ if (!out_buf) {
+ mk_http_status(request, 400);
+ mk_http_done(request);
+ return;
+ }
+ out_size = flb_sds_len(out_buf);
+
+ mk_http_status(request, 200);
+ flb_hs_add_content_type_to_req(request, FLB_HS_CONTENT_TYPE_JSON);
+ mk_http_send(request, out_buf, out_size, NULL);
+ mk_http_done(request);
+
+ flb_sds_destroy(out_buf);
+}
+
+static void handle_get_reload_status(mk_request_t *request, struct flb_config *config)
+{
+ flb_sds_t out_buf;
+ size_t out_size;
+ msgpack_packer mp_pck;
+ msgpack_sbuffer mp_sbuf;
+
+ /* initialize buffers */
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ msgpack_pack_map(&mp_pck, 1);
+ msgpack_pack_str(&mp_pck, 16);
+ msgpack_pack_str_body(&mp_pck, "hot_reload_count", 16);
+ msgpack_pack_int64(&mp_pck, config->hot_reloaded_count);
+
+ /* Export to JSON */
+ out_buf = flb_msgpack_raw_to_json_sds(mp_sbuf.data, mp_sbuf.size);
+ msgpack_sbuffer_destroy(&mp_sbuf);
+ if (!out_buf) {
+ mk_http_status(request, 400);
+ mk_http_done(request);
+ return;
+ }
+ out_size = flb_sds_len(out_buf);
+
+ mk_http_status(request, 200);
+ flb_hs_add_content_type_to_req(request, FLB_HS_CONTENT_TYPE_JSON);
+ mk_http_send(request, out_buf, out_size, NULL);
+ mk_http_done(request);
+
+ flb_sds_destroy(out_buf);
+}
+
+static void cb_reload(mk_request_t *request, void *data)
+{
+ struct flb_hs *hs = data;
+ struct flb_config *config = hs->config;
+
+ if (request->method == MK_METHOD_POST ||
+ request->method == MK_METHOD_PUT) {
+ handle_reload_request(request, config);
+ }
+ else if (request->method == MK_METHOD_GET) {
+ handle_get_reload_status(request, config);
+ }
+ else {
+ mk_http_status(request, 400);
+ mk_http_done(request);
+ }
+}
+
+/* Perform registration */
+int api_v2_reload(struct flb_hs *hs)
+{
+ mk_vhost_handler(hs->ctx, hs->vid, "/api/v2/reload", cb_reload, hs);
+
+ return 0;
+}
diff --git a/fluent-bit/src/http_server/api/v2/reload.h b/fluent-bit/src/http_server/api/v2/reload.h
new file mode 100644
index 000000000..e64e867d6
--- /dev/null
+++ b/fluent-bit/src/http_server/api/v2/reload.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2023 The Fluent Bit Authors
+ *
+ * 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 FLB_HS_API_V2_RELOAD_H
+#define FLB_HS_API_V2_RELOAD_H
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_http_server.h>
+
+int api_v2_reload(struct flb_hs *hs);
+
+#endif
diff --git a/fluent-bit/src/http_server/flb_hs.c b/fluent-bit/src/http_server/flb_hs.c
new file mode 100644
index 000000000..40cc8467a
--- /dev/null
+++ b/fluent-bit/src/http_server/flb_hs.c
@@ -0,0 +1,147 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2019-2020 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_http_server.h>
+
+#include <monkey/mk_lib.h>
+
+/* v1 */
+#include "api/v1/register.h"
+#include "api/v1/health.h"
+
+/* v2 */
+#include "api/v2/register.h"
+
+static void cb_root(mk_request_t *request, void *data)
+{
+ struct flb_hs *hs = data;
+
+ mk_http_status(request, 200);
+ flb_hs_add_content_type_to_req(request, FLB_HS_CONTENT_TYPE_JSON);
+ mk_http_send(request, hs->ep_root_buf, hs->ep_root_size, NULL);
+ mk_http_done(request);
+}
+
+/* Ingest health metrics into the web service context */
+int flb_hs_push_health_metrics(struct flb_hs *hs, void *data, size_t size)
+{
+ return mk_mq_send(hs->ctx, hs->qid_health, data, size);
+}
+
+/* Ingest pipeline metrics into the web service context */
+int flb_hs_push_pipeline_metrics(struct flb_hs *hs, void *data, size_t size)
+{
+ return mk_mq_send(hs->ctx, hs->qid_metrics, data, size);
+}
+
+/* Ingest pipeline metrics into the web service context */
+int flb_hs_push_metrics(struct flb_hs *hs, void *data, size_t size)
+{
+ return mk_mq_send(hs->ctx, hs->qid_metrics_v2, data, size);
+}
+
+/* Ingest storage metrics into the web service context */
+int flb_hs_push_storage_metrics(struct flb_hs *hs, void *data, size_t size)
+{
+ return mk_mq_send(hs->ctx, hs->qid_storage, data, size);
+}
+
+/* Create ROOT endpoints */
+struct flb_hs *flb_hs_create(const char *listen, const char *tcp_port,
+ struct flb_config *config)
+{
+ int vid;
+ char tmp[32];
+ struct flb_hs *hs;
+
+ hs = flb_calloc(1, sizeof(struct flb_hs));
+ if (!hs) {
+ flb_errno();
+ return NULL;
+ }
+ hs->config = config;
+
+ /* Setup endpoint specific data */
+ flb_hs_endpoints(hs);
+
+ /* Create HTTP server context */
+ hs->ctx = mk_create();
+ if (!hs->ctx) {
+ flb_error("[http_server] could not create context");
+ flb_free(hs);
+ return NULL;
+ }
+
+ /* Compose listen address */
+ snprintf(tmp, sizeof(tmp) -1, "%s:%s", listen, tcp_port);
+ mk_config_set(hs->ctx, "Listen", tmp, NULL);
+ vid = mk_vhost_create(hs->ctx, NULL);
+ hs->vid = vid;
+
+ /* Setup virtual host */
+ mk_vhost_set(hs->ctx, vid,
+ "Name", "fluent-bit",
+ NULL);
+
+
+ /* Register endpoints for /api/v1 */
+ api_v1_registration(hs);
+
+ /* Register endpoints for /api/v2 */
+ api_v2_registration(hs);
+
+ /* Root */
+ mk_vhost_handler(hs->ctx, vid, "/", cb_root, hs);
+
+ return hs;
+}
+
+int flb_hs_start(struct flb_hs *hs)
+{
+ int ret;
+ struct flb_config *config = hs->config;
+
+ ret = mk_start(hs->ctx);
+
+ if (ret == 0) {
+ flb_info("[http_server] listen iface=%s tcp_port=%s",
+ config->http_listen, config->http_port);
+ }
+ return ret;
+}
+
+int flb_hs_destroy(struct flb_hs *hs)
+{
+ if (!hs) {
+ return 0;
+ }
+ flb_hs_health_destroy();
+ mk_stop(hs->ctx);
+ mk_destroy(hs->ctx);
+
+ flb_hs_endpoints_free(hs);
+ flb_free(hs);
+
+
+ return 0;
+}
diff --git a/fluent-bit/src/http_server/flb_hs_endpoints.c b/fluent-bit/src/http_server/flb_hs_endpoints.c
new file mode 100644
index 000000000..2ea12a981
--- /dev/null
+++ b/fluent-bit/src/http_server/flb_hs_endpoints.c
@@ -0,0 +1,121 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2019-2020 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_version.h>
+#include <fluent-bit/flb_http_server.h>
+#include <msgpack.h>
+
+/* Create a JSON buffer with informational data about the running service */
+static int endpoint_root(struct flb_hs *hs)
+{
+ int c;
+ flb_sds_t out_buf;
+ msgpack_packer mp_pck;
+ msgpack_sbuffer mp_sbuf;
+ struct mk_list *head;
+ struct mk_list *list;
+ struct flb_split_entry *entry;
+
+ /* initialize buffers */
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ msgpack_pack_map(&mp_pck, 1);
+ msgpack_pack_str(&mp_pck, 10);
+ msgpack_pack_str_body(&mp_pck, "fluent-bit", 10);
+
+ /* entries under fluent-bit parent:
+ *
+ * - version
+ * - edition
+ * - built flags
+ */
+ msgpack_pack_map(&mp_pck, 3);
+
+ /* fluent-bit['version'] */
+ msgpack_pack_str(&mp_pck, 7);
+ msgpack_pack_str_body(&mp_pck, "version", 7);
+ msgpack_pack_str(&mp_pck, sizeof(FLB_VERSION_STR) - 1);
+ msgpack_pack_str_body(&mp_pck, FLB_VERSION_STR, sizeof(FLB_VERSION_STR) - 1);
+
+ /* fluent-bit['edition'] */
+ msgpack_pack_str(&mp_pck, 7);
+ msgpack_pack_str_body(&mp_pck, "edition", 7);
+#ifdef FLB_ENTERPRISE
+ msgpack_pack_str(&mp_pck, 10);
+ msgpack_pack_str_body(&mp_pck, "Enterprise", 10);
+#else
+ msgpack_pack_str(&mp_pck, 9);
+ msgpack_pack_str_body(&mp_pck, "Community", 9);
+#endif
+
+ /* fluent-bit['flags'] */
+ msgpack_pack_str(&mp_pck, 5);
+ msgpack_pack_str_body(&mp_pck, "flags", 5);
+
+ c = 0;
+ list = flb_utils_split(FLB_INFO_FLAGS, ' ', -1);
+ mk_list_foreach(head, list) {
+ entry = mk_list_entry(head, struct flb_split_entry, _head);
+ if (strncmp(entry->value, "FLB_", 4) == 0) {
+ c++;
+ }
+ }
+
+ msgpack_pack_array(&mp_pck, c);
+ mk_list_foreach(head, list) {
+ entry = mk_list_entry(head, struct flb_split_entry, _head);
+ if (strncmp(entry->value, "FLB_", 4) == 0) {
+ msgpack_pack_str(&mp_pck, entry->len);
+ msgpack_pack_str_body(&mp_pck, entry->value, entry->len);
+ }
+ }
+ flb_utils_split_free(list);
+
+ /* export as JSON */
+ out_buf = flb_msgpack_raw_to_json_sds(mp_sbuf.data, mp_sbuf.size);
+ msgpack_sbuffer_destroy(&mp_sbuf);
+
+ if (out_buf) {
+ hs->ep_root_buf = out_buf;
+ hs->ep_root_size = flb_sds_len(out_buf);
+ }
+
+ return -1;
+}
+
+int flb_hs_endpoints(struct flb_hs *hs)
+{
+ endpoint_root(hs);
+ return 0;
+}
+
+/* Release cached data from endpoints */
+int flb_hs_endpoints_free(struct flb_hs *hs)
+{
+ if (hs->ep_root_buf) {
+ flb_sds_destroy(hs->ep_root_buf);
+ }
+
+ return 0;
+}
diff --git a/fluent-bit/src/http_server/flb_hs_utils.c b/fluent-bit/src/http_server/flb_hs_utils.c
new file mode 100644
index 000000000..7b39bff28
--- /dev/null
+++ b/fluent-bit/src/http_server/flb_hs_utils.c
@@ -0,0 +1,48 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_http_server.h>
+#include <monkey/mk_lib.h>
+
+int flb_hs_add_content_type_to_req(mk_request_t *request, int type)
+{
+ if (request == NULL) {
+ return -1;
+ }
+
+ switch (type) {
+ case FLB_HS_CONTENT_TYPE_JSON:
+ mk_http_header(request,
+ FLB_HS_CONTENT_TYPE_KEY_STR, FLB_HS_CONTENT_TYPE_KEY_LEN,
+ FLB_HS_CONTENT_TYPE_JSON_STR, FLB_HS_CONTENT_TYPE_JSON_LEN);
+ break;
+ case FLB_HS_CONTENT_TYPE_PROMETHEUS:
+ mk_http_header(request,
+ FLB_HS_CONTENT_TYPE_KEY_STR, FLB_HS_CONTENT_TYPE_KEY_LEN,
+ FLB_HS_CONTENT_TYPE_PROMETHEUS_STR, FLB_HS_CONTENT_TYPE_PROMETHEUS_LEN);
+ break;
+ default:
+ flb_error("[%s] unknown type=%d", __FUNCTION__, type);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/fluent-bit/src/multiline/CMakeLists.txt b/fluent-bit/src/multiline/CMakeLists.txt
new file mode 100644
index 000000000..294ef3e8f
--- /dev/null
+++ b/fluent-bit/src/multiline/CMakeLists.txt
@@ -0,0 +1,15 @@
+set(src_multiline
+ # built-in parsers
+ multiline/flb_ml_parser_cri.c
+ multiline/flb_ml_parser_docker.c
+ multiline/flb_ml_parser_python.c
+ multiline/flb_ml_parser_java.c
+ multiline/flb_ml_parser_go.c
+ multiline/flb_ml_parser_ruby.c
+ # core
+ multiline/flb_ml_stream.c
+ multiline/flb_ml_parser.c
+ multiline/flb_ml_group.c
+ multiline/flb_ml_rule.c
+ multiline/flb_ml.c PARENT_SCOPE
+ )
diff --git a/fluent-bit/src/multiline/flb_ml.c b/fluent-bit/src/multiline/flb_ml.c
new file mode 100644
index 000000000..28e123cee
--- /dev/null
+++ b/fluent-bit/src/multiline/flb_ml.c
@@ -0,0 +1,1562 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_regex.h>
+#include <fluent-bit/flb_time.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_scheduler.h>
+#include <fluent-bit/multiline/flb_ml.h>
+#include <fluent-bit/multiline/flb_ml_rule.h>
+#include <fluent-bit/multiline/flb_ml_group.h>
+
+#include <stdarg.h>
+#include <math.h>
+
+static inline int match_negate(struct flb_ml_parser *ml_parser, int matched)
+{
+ int rule_match = matched;
+
+ /* Validate pattern matching against expected 'negate' condition */
+ if (matched == FLB_TRUE) {
+ if (ml_parser->negate == FLB_FALSE) {
+ rule_match = FLB_TRUE;
+ }
+ else {
+ rule_match = FLB_FALSE;
+ }
+ }
+ else {
+ if (ml_parser->negate == FLB_TRUE) {
+ rule_match = FLB_TRUE;
+ }
+ }
+
+ return rule_match;
+}
+
+static uint64_t time_ms_now()
+{
+ uint64_t ms;
+ struct flb_time tm;
+
+ flb_time_get(&tm);
+ ms = (tm.tm.tv_sec * 1000) + lround(tm.tm.tv_nsec/1.0e6);
+ return ms;
+}
+
+
+int flb_ml_flush_stdout(struct flb_ml_parser *parser,
+ struct flb_ml_stream *mst,
+ void *data, char *buf_data, size_t buf_size)
+{
+ fprintf(stdout, "\n%s----- MULTILINE FLUSH (stream_id=%" PRIu64 ") -----%s\n",
+ ANSI_GREEN, mst->id, ANSI_RESET);
+
+ /* Print incoming flush buffer */
+ flb_pack_print(buf_data, buf_size);
+
+ fprintf(stdout, "%s----------- EOF -----------%s\n",
+ ANSI_GREEN, ANSI_RESET);
+ return 0;
+}
+
+int flb_ml_type_lookup(char *str)
+{
+ int type = -1;
+
+ if (strcasecmp(str, "regex") == 0) {
+ type = FLB_ML_REGEX;
+ }
+ else if (strcasecmp(str, "endswith") == 0) {
+ type = FLB_ML_ENDSWITH;
+ }
+ else if (strcasecmp(str, "equal") == 0 || strcasecmp(str, "eq") == 0) {
+ type = FLB_ML_EQ;
+ }
+
+ return type;
+}
+
+void flb_ml_flush_parser_instance(struct flb_ml *ml,
+ struct flb_ml_parser_ins *parser_i,
+ uint64_t stream_id, int forced_flush)
+{
+ struct mk_list *head;
+ struct mk_list *head_group;
+ struct flb_ml_stream *mst;
+ struct flb_ml_stream_group *group;
+
+ mk_list_foreach(head, &parser_i->streams) {
+ mst = mk_list_entry(head, struct flb_ml_stream, _head);
+ if (stream_id != 0 && mst->id != stream_id) {
+ continue;
+ }
+
+ /* Iterate stream groups */
+ mk_list_foreach(head_group, &mst->groups) {
+ group = mk_list_entry(head_group, struct flb_ml_stream_group, _head);
+ flb_ml_flush_stream_group(parser_i->ml_parser, mst, group, forced_flush);
+ }
+ }
+}
+
+void flb_ml_flush_pending(struct flb_ml *ml, uint64_t now, int forced_flush)
+{
+ struct mk_list *head;
+ struct flb_ml_parser_ins *parser_i;
+ struct flb_ml_group *group;
+
+ /* set the last flush time */
+ ml->last_flush = now;
+
+ /* flush only the first group of the context */
+ group = mk_list_entry_first(&ml->groups, struct flb_ml_group, _head);
+
+ /* iterate group parser instances */
+ mk_list_foreach(head, &group->parsers) {
+ parser_i = mk_list_entry(head, struct flb_ml_parser_ins, _head);
+ flb_ml_flush_parser_instance(ml, parser_i, 0, forced_flush);
+ }
+}
+
+void flb_ml_flush_pending_now(struct flb_ml *ml)
+{
+ uint64_t now;
+
+ now = time_ms_now();
+ flb_ml_flush_pending(ml, now, FLB_TRUE);
+}
+
+static void cb_ml_flush_timer(struct flb_config *ctx, void *data)
+{
+ uint64_t now;
+ struct flb_ml *ml = data;
+
+ now = time_ms_now();
+ if (ml->last_flush + ml->flush_ms > now) {
+ return;
+ }
+
+ /*
+ * Iterate over all streams and groups and for a flush for expired groups
+ * which has not flushed in the last N milliseconds.
+ */
+ flb_ml_flush_pending(ml, now, FLB_TRUE);
+}
+
+int flb_ml_register_context(struct flb_ml_stream_group *group,
+ struct flb_time *tm, msgpack_object *map)
+{
+ if (tm) {
+ flb_time_copy(&group->mp_time, tm);
+ }
+
+ if (map) {
+ msgpack_pack_object(&group->mp_pck, *map);
+ }
+
+ return 0;
+}
+
+static inline void breakline_prepare(struct flb_ml_parser_ins *parser_i,
+ struct flb_ml_stream_group *stream_group)
+{
+ int len;
+
+ if (parser_i->key_content) {
+ return;
+ }
+
+ len = flb_sds_len(stream_group->buf);
+ if (len <= 0) {
+ return;
+ }
+
+ if (stream_group->buf[len - 1] != '\n') {
+ flb_sds_cat_safe(&stream_group->buf, "\n", 1);
+ }
+}
+
+/*
+ * package content into a multiline stream:
+ *
+ * full_map: if the original content to process comes in msgpack map, this variable
+ * reference the map. It's only used in case we will package a first line so we
+ * store a copy of the other key values in the map for flush time.
+ */
+static int package_content(struct flb_ml_stream *mst,
+ msgpack_object *metadata,
+ msgpack_object *full_map,
+ void *buf, size_t size, struct flb_time *tm,
+ msgpack_object *val_content,
+ msgpack_object *val_pattern,
+ msgpack_object *val_group)
+{
+ int len;
+ int ret;
+ int rule_match = FLB_FALSE;
+ int processed = FLB_FALSE;
+ int type;
+ size_t offset = 0;
+ size_t buf_size;
+ char *buf_data;
+ msgpack_object *val = val_content;
+ struct flb_ml_parser *parser;
+ struct flb_ml_parser_ins *parser_i;
+ struct flb_ml_stream_group *stream_group;
+
+ parser_i = mst->parser;
+ parser = parser_i->ml_parser;
+
+ /* Get stream group */
+ stream_group = flb_ml_stream_group_get(mst->parser, mst, val_group);
+ if (!mst->last_stream_group) {
+ mst->last_stream_group = stream_group;
+ }
+ else {
+ if (mst->last_stream_group != stream_group) {
+ mst->last_stream_group = stream_group;
+ }
+ }
+
+ /* Set the parser type */
+ type = parser->type;
+
+ if (val_pattern) {
+ val = val_pattern;
+ }
+
+ if (val) {
+ buf_data = (char *) val->via.str.ptr;
+ buf_size = val->via.str.size;
+ }
+ else {
+ buf_data = buf;
+ buf_size = size;
+
+ }
+ if (type == FLB_ML_REGEX) {
+ ret = flb_ml_rule_process(parser, mst,
+ stream_group, full_map, buf, size, tm,
+ val_content, val_pattern);
+ if (ret == -1) {
+ processed = FLB_FALSE;
+ }
+ else {
+ processed = FLB_TRUE;
+ }
+ }
+ else if (type == FLB_ML_ENDSWITH) {
+ len = flb_sds_len(parser->match_str);
+ if (buf_data && len <= buf_size) {
+ /* Validate if content ends with expected string */
+ offset = buf_size - len;
+ ret = memcmp(buf_data + offset, parser->match_str, len);
+ if (ret == 0) {
+ rule_match = match_negate(parser, FLB_TRUE);
+ }
+ else {
+ rule_match = match_negate(parser, FLB_FALSE);
+ }
+
+ if (stream_group->mp_sbuf.size == 0) {
+ flb_ml_register_context(stream_group, tm, full_map);
+ }
+
+ /* Prepare concatenation */
+ breakline_prepare(parser_i, stream_group);
+
+ /* Concatenate value */
+ if (val_content) {
+ flb_sds_cat_safe(&stream_group->buf,
+ val_content->via.str.ptr,
+ val_content->via.str.size);
+ }
+ else {
+ flb_sds_cat_safe(&stream_group->buf, buf_data, buf_size);
+ }
+
+ /* on ENDSWITH mode, a rule match means flush the content */
+ if (rule_match) {
+ flb_ml_flush_stream_group(parser, mst, stream_group, FLB_FALSE);
+ }
+ processed = FLB_TRUE;
+ }
+ }
+ else if (type == FLB_ML_EQ) {
+ if (buf_size == flb_sds_len(parser->match_str) &&
+ memcmp(buf_data, parser->match_str, buf_size) == 0) {
+ /* EQ match */
+ rule_match = match_negate(parser, FLB_TRUE);
+ }
+ else {
+ rule_match = match_negate(parser, FLB_FALSE);
+ }
+
+ if (stream_group->mp_sbuf.size == 0) {
+ flb_ml_register_context(stream_group, tm, full_map);
+ }
+
+ /* Prepare concatenation */
+ breakline_prepare(parser_i, stream_group);
+
+ /* Concatenate value */
+ if (val_content) {
+ flb_sds_cat_safe(&stream_group->buf,
+ val_content->via.str.ptr,
+ val_content->via.str.size);
+ }
+ else {
+ flb_sds_cat_safe(&stream_group->buf, buf_data, buf_size);
+ }
+
+ /* on ENDSWITH mode, a rule match means flush the content */
+ if (rule_match) {
+ flb_ml_flush_stream_group(parser, mst, stream_group, FLB_FALSE);
+ }
+ processed = FLB_TRUE;
+ }
+
+ if (processed && metadata != NULL) {
+ msgpack_pack_object(&stream_group->mp_md_pck, *metadata);
+ }
+
+ return processed;
+}
+
+/*
+ * Retrieve the ID of a specific key name in a map. This function might be
+ * extended later to use record accessor, since all current cases are solved
+ * now quering the first level of keys in the map, we avoid record accessor
+ * to avoid extra memory allocations.
+ */
+static int get_key_id(msgpack_object *map, flb_sds_t key_name)
+{
+ int i;
+ int len;
+ int found = FLB_FALSE;
+ msgpack_object key;
+ msgpack_object val;
+
+ if (!key_name) {
+ return -1;
+ }
+
+ len = flb_sds_len(key_name);
+ for (i = 0; i < map->via.map.size; i++) {
+ key = map->via.map.ptr[i].key;
+ val = map->via.map.ptr[i].val;
+
+ if (key.type != MSGPACK_OBJECT_STR || val.type != MSGPACK_OBJECT_STR) {
+ continue;
+ }
+
+ if (key.via.str.size != len) {
+ continue;
+ }
+
+ if (strncmp(key.via.str.ptr, key_name, len) == 0) {
+ found = FLB_TRUE;
+ break;
+ }
+ }
+
+ if (found) {
+ return i;
+ }
+
+ return -1;
+}
+
+static int process_append(struct flb_ml_parser_ins *parser_i,
+ struct flb_ml_stream *mst,
+ int type,
+ struct flb_time *tm,
+ msgpack_object *metadata,
+ msgpack_object *obj,
+ void *buf, size_t size)
+{
+ int ret;
+ int id_content = -1;
+ int id_pattern = -1;
+ int id_group = -1;
+ int unpacked = FLB_FALSE;
+ size_t off = 0;
+ msgpack_object *full_map = NULL;
+ msgpack_object *val_content = NULL;
+ msgpack_object *val_pattern = NULL;
+ msgpack_object *val_group = NULL;
+ msgpack_unpacked result;
+
+ /* Lookup the key */
+ if (type == FLB_ML_TYPE_TEXT) {
+ ret = package_content(mst, NULL, NULL, buf, size, tm, NULL, NULL, NULL);
+ if (ret == FLB_FALSE) {
+ return -1;
+ }
+ return 0;
+ }
+ else if (type == FLB_ML_TYPE_MAP) {
+ full_map = obj;
+ /*
+ * When full_map and buf is not NULL,
+ * we use 'buf' since buf is already processed from full_map at
+ * ml_append_try_parser_type_map.
+ */
+ if (!full_map || (buf != NULL && full_map != NULL)) {
+ off = 0;
+ msgpack_unpacked_init(&result);
+ ret = msgpack_unpack_next(&result, buf, size, &off);
+ if (ret != MSGPACK_UNPACK_SUCCESS) {
+ return -1;
+ }
+ full_map = &result.data;
+ unpacked = FLB_TRUE;
+ }
+ else if (full_map->type != MSGPACK_OBJECT_MAP) {
+ msgpack_unpacked_destroy(&result);
+ return -1;
+ }
+ }
+
+ /* Lookup for key_content entry */
+ id_content = get_key_id(full_map, parser_i->key_content);
+ if (id_content == -1) {
+ if (unpacked) {
+ msgpack_unpacked_destroy(&result);
+ }
+ return -1;
+ }
+
+ val_content = &full_map->via.map.ptr[id_content].val;
+ if (val_content->type != MSGPACK_OBJECT_STR) {
+ val_content = NULL;
+ }
+
+ /* Optional: Lookup for key_pattern entry */
+ if (parser_i->key_pattern) {
+ id_pattern = get_key_id(full_map, parser_i->key_pattern);
+ if (id_pattern >= 0) {
+ val_pattern = &full_map->via.map.ptr[id_pattern].val;
+ if (val_pattern->type != MSGPACK_OBJECT_STR) {
+ val_pattern = NULL;
+ }
+ }
+ }
+
+ /* Optional: lookup for key_group entry */
+ if (parser_i->key_group) {
+ id_group = get_key_id(full_map, parser_i->key_group);
+ if (id_group >= 0) {
+ val_group = &full_map->via.map.ptr[id_group].val;
+ if (val_group->type != MSGPACK_OBJECT_STR) {
+ val_group = NULL;
+ }
+ }
+ }
+
+ /* Package the content */
+ ret = package_content(mst, metadata, full_map, buf, size, tm,
+ val_content, val_pattern, val_group);
+ if (unpacked) {
+ msgpack_unpacked_destroy(&result);
+ }
+ if (ret == FLB_FALSE) {
+ return -1;
+ }
+ return 0;
+}
+
+static int ml_append_try_parser_type_text(struct flb_ml_parser_ins *parser,
+ uint64_t stream_id,
+ int *type,
+ struct flb_time *tm, void *buf, size_t size,
+ msgpack_object *map,
+ void **out_buf, size_t *out_size, int *out_release,
+ struct flb_time *out_time)
+{
+ int ret;
+
+ if (parser->ml_parser->parser) {
+ /* Parse incoming content */
+ ret = flb_parser_do(parser->ml_parser->parser, (char *) buf, size,
+ out_buf, out_size, out_time);
+ if (flb_time_to_nanosec(out_time) == 0L) {
+ flb_time_copy(out_time, tm);
+ }
+ if (ret >= 0) {
+ *out_release = FLB_TRUE;
+ *type = FLB_ML_TYPE_MAP;
+ }
+ else {
+ *out_buf = buf;
+ *out_size = size;
+ return -1;
+ }
+ }
+ else {
+ *out_buf = buf;
+ *out_size = size;
+ }
+ return 0;
+}
+
+static int ml_append_try_parser_type_map(struct flb_ml_parser_ins *parser,
+ uint64_t stream_id,
+ int *type,
+ struct flb_time *tm, void *buf, size_t size,
+ msgpack_object *map,
+ void **out_buf, size_t *out_size, int *out_release,
+ struct flb_time *out_time)
+{
+ int map_size;
+ int i;
+ int len;
+ msgpack_object key;
+ msgpack_object val;
+
+ if (map == NULL || map->type != MSGPACK_OBJECT_MAP) {
+ flb_error("%s:invalid map", __FUNCTION__);
+ return -1;
+ }
+
+ if (parser->ml_parser->parser) {
+ /* lookup key_content */
+
+ len = flb_sds_len(parser->key_content);
+ map_size = map->via.map.size;
+ for(i = 0; i < map_size; i++) {
+ key = map->via.map.ptr[i].key;
+ val = map->via.map.ptr[i].val;
+ if (key.type == MSGPACK_OBJECT_STR &&
+ parser->key_content &&
+ key.via.str.size == len &&
+ strncmp(key.via.str.ptr, parser->key_content, len) == 0) {
+ /* key_content found */
+ if (val.type == MSGPACK_OBJECT_STR) {
+ /* try parse the value of key_content e*/
+ return ml_append_try_parser_type_text(parser, stream_id, type,
+ tm, (void*) val.via.str.ptr,
+ val.via.str.size,
+ map,
+ out_buf, out_size, out_release,
+ out_time);
+ } else {
+ flb_error("%s: not string", __FUNCTION__);
+ return -1;
+ }
+ }
+ }
+ }
+ else {
+ *out_buf = buf;
+ *out_size = size;
+ }
+ return 0;
+}
+
+static int ml_append_try_parser(struct flb_ml_parser_ins *parser,
+ uint64_t stream_id,
+ int type,
+ struct flb_time *tm, void *buf, size_t size,
+ msgpack_object *metadata,
+ msgpack_object *map)
+{
+ int ret;
+ int release = FLB_FALSE;
+ void *out_buf = NULL;
+ size_t out_size = 0;
+ struct flb_time out_time;
+ struct flb_ml_stream *mst;
+
+ flb_time_zero(&out_time);
+
+ switch (type) {
+ case FLB_ML_TYPE_TEXT:
+ ret = ml_append_try_parser_type_text(parser, stream_id, &type,
+ tm, buf, size, map,
+ &out_buf, &out_size, &release,
+ &out_time);
+ if (ret < 0) {
+ return -1;
+ }
+ break;
+ case FLB_ML_TYPE_MAP:
+ ret = ml_append_try_parser_type_map(parser, stream_id, &type,
+ tm, buf, size, map,
+ &out_buf, &out_size, &release,
+ &out_time);
+ if (ret < 0) {
+ return -1;
+ }
+ break;
+
+ default:
+ flb_error("[multiline] unknown type=%d", type);
+ return -1;
+ }
+
+ if (flb_time_to_nanosec(&out_time) == 0L) {
+ if (tm && flb_time_to_nanosec(tm) != 0L) {
+ flb_time_copy(&out_time, tm);
+ }
+ else {
+ flb_time_get(&out_time);
+ }
+ }
+
+ /* Get the stream */
+ mst = flb_ml_stream_get(parser, stream_id);
+ if (!mst) {
+ flb_error("[multiline] invalid stream_id %" PRIu64 ", could not "
+ "append content to multiline context", stream_id);
+ goto exit;
+ }
+
+ /* Process the binary record */
+ ret = process_append(parser, mst, type, &out_time, metadata, map, out_buf, out_size);
+ if (ret == -1) {
+ if (release == FLB_TRUE) {
+ flb_free(out_buf);
+ }
+ return -1;
+ }
+
+ exit:
+ if (release == FLB_TRUE) {
+ flb_free(out_buf);
+ }
+
+ return 0;
+}
+
+int flb_ml_append_text(struct flb_ml *ml, uint64_t stream_id,
+ struct flb_time *tm, void *buf, size_t size)
+{
+ int ret;
+ int processed = FLB_FALSE;
+ struct mk_list *head;
+ struct mk_list *head_group;
+ struct flb_ml_group *group;
+ struct flb_ml_stream *mst;
+ struct flb_ml_parser_ins *lru_parser = NULL;
+ struct flb_ml_parser_ins *parser_i;
+ struct flb_time out_time;
+ struct flb_ml_stream_group *st_group;
+ int type;
+
+ type = FLB_ML_TYPE_TEXT;
+
+ flb_time_zero(&out_time);
+
+ mk_list_foreach(head, &ml->groups) {
+ group = mk_list_entry(head, struct flb_ml_group, _head);
+
+ /* Check if the incoming data matches the last recently used parser */
+ lru_parser = group->lru_parser;
+
+ if (lru_parser && lru_parser->last_stream_id == stream_id) {
+ ret = ml_append_try_parser(lru_parser, lru_parser->last_stream_id, type,
+ tm, buf, size, NULL, NULL);
+ if (ret == 0) {
+ processed = FLB_TRUE;
+ break;
+ }
+ else {
+ flb_ml_flush_parser_instance(ml,
+ lru_parser,
+ lru_parser->last_stream_id,
+ FLB_FALSE);
+ }
+ }
+ else if (lru_parser && lru_parser->last_stream_id > 0) {
+ /*
+ * Clear last recently used parser to match new parser.
+ * Do not flush last_stream_id since it should continue to parsing.
+ */
+ lru_parser = NULL;
+ }
+ }
+
+ mk_list_foreach(head_group, &group->parsers) {
+ parser_i = mk_list_entry(head_group, struct flb_ml_parser_ins, _head);
+ if (lru_parser && lru_parser == parser_i &&
+ lru_parser->last_stream_id == stream_id) {
+ continue;
+ }
+
+ ret = ml_append_try_parser(parser_i, stream_id, type,
+ tm, buf, size, NULL, NULL);
+ if (ret == 0) {
+ group->lru_parser = parser_i;
+ group->lru_parser->last_stream_id = stream_id;
+ lru_parser = parser_i;
+ processed = FLB_TRUE;
+ break;
+ }
+ else {
+ parser_i = NULL;
+ }
+ }
+
+ if (!processed) {
+ if (lru_parser) {
+ flb_ml_flush_parser_instance(ml, lru_parser, stream_id, FLB_FALSE);
+ parser_i = lru_parser;
+ }
+ else {
+ /* get the first parser (just to make use of it buffers) */
+ parser_i = mk_list_entry_first(&group->parsers,
+ struct flb_ml_parser_ins,
+ _head);
+ }
+
+ flb_ml_flush_parser_instance(ml, parser_i, stream_id, FLB_FALSE);
+ mst = flb_ml_stream_get(parser_i, stream_id);
+ if (!mst) {
+ flb_error("[multiline] invalid stream_id %" PRIu64 ", could not "
+ "append content to multiline context", stream_id);
+ return -1;
+ }
+
+ /* Get stream group */
+ st_group = flb_ml_stream_group_get(mst->parser, mst, NULL);
+ flb_sds_cat_safe(&st_group->buf, buf, size);
+ flb_ml_flush_stream_group(parser_i->ml_parser, mst, st_group, FLB_FALSE);
+ }
+
+ return 0;
+}
+
+
+
+int flb_ml_append_object(struct flb_ml *ml,
+ uint64_t stream_id,
+ struct flb_time *tm,
+ msgpack_object *metadata,
+ msgpack_object *obj)
+{
+ int ret;
+ int type;
+ int processed = FLB_FALSE;
+ struct mk_list *head;
+ struct mk_list *head_group;
+ struct flb_ml_group *group;
+ struct flb_ml_parser_ins *lru_parser = NULL;
+ struct flb_ml_parser_ins *parser_i;
+ struct flb_ml_stream *mst;
+ struct flb_ml_stream_group *st_group;
+ struct flb_log_event event;
+
+ if (metadata == NULL) {
+ metadata = ml->log_event_decoder.empty_map;
+ }
+
+ /*
+ * As incoming objects, we accept packed events
+ * and msgpack Maps containing key/value pairs.
+ */
+ if (obj->type == MSGPACK_OBJECT_ARRAY) {
+ flb_error("[multiline] appending object with invalid type, expected "
+ "map, received type=%i", obj->type);
+ return -1;
+
+
+ flb_log_event_decoder_reset(&ml->log_event_decoder, NULL, 0);
+
+ ret = flb_event_decoder_decode_object(&ml->log_event_decoder,
+ &event,
+ obj);
+
+ if (ret != FLB_EVENT_DECODER_SUCCESS) {
+ flb_error("[multiline] invalid event object");
+
+ return -1;
+ }
+
+ tm = &event.timestamp;
+ obj = event.body;
+ metadata = event.metadata;
+
+ type = FLB_ML_TYPE_MAP;
+ }
+ else if (obj->type == MSGPACK_OBJECT_MAP) {
+ type = FLB_ML_TYPE_MAP;
+ }
+ else {
+ flb_error("[multiline] appending object with invalid type, expected "
+ "array or map, received type=%i", obj->type);
+ return -1;
+ }
+
+ mk_list_foreach(head, &ml->groups) {
+ group = mk_list_entry(head, struct flb_ml_group, _head);
+
+ /* Check if the incoming data matches the last recently used parser */
+ lru_parser = group->lru_parser;
+
+ if (lru_parser && lru_parser->last_stream_id == stream_id) {
+ ret = ml_append_try_parser(lru_parser, lru_parser->last_stream_id, type,
+ tm, NULL, 0, metadata, obj);
+ if (ret == 0) {
+ processed = FLB_TRUE;
+ break;
+ }
+ else {
+ flb_ml_flush_parser_instance(ml,
+ lru_parser,
+ lru_parser->last_stream_id,
+ FLB_FALSE);
+ }
+ }
+ else if (lru_parser && lru_parser->last_stream_id > 0) {
+ /*
+ * Clear last recently used parser to match new parser.
+ * Do not flush last_stream_id since it should continue to parsing.
+ */
+ lru_parser = NULL;
+ }
+ }
+
+ mk_list_foreach(head_group, &group->parsers) {
+ parser_i = mk_list_entry(head_group, struct flb_ml_parser_ins, _head);
+ if (lru_parser && parser_i == lru_parser) {
+ continue;
+ }
+
+ ret = ml_append_try_parser(parser_i, stream_id, type,
+ tm, NULL, 0, metadata, obj);
+ if (ret == 0) {
+ group->lru_parser = parser_i;
+ group->lru_parser->last_stream_id = stream_id;
+ lru_parser = parser_i;
+ processed = FLB_TRUE;
+ break;
+ }
+ else {
+ parser_i = NULL;
+ }
+ }
+
+ if (!processed) {
+ if (lru_parser) {
+ flb_ml_flush_parser_instance(ml, lru_parser, stream_id, FLB_FALSE);
+ parser_i = lru_parser;
+ }
+ else {
+ /* get the first parser (just to make use of it buffers) */
+ parser_i = mk_list_entry_first(&group->parsers,
+ struct flb_ml_parser_ins,
+ _head);
+ }
+
+ flb_ml_flush_parser_instance(ml, parser_i, stream_id, FLB_FALSE);
+ mst = flb_ml_stream_get(parser_i, stream_id);
+ if (!mst) {
+ flb_error("[multiline] invalid stream_id %" PRIu64 ", could not "
+ "append content to multiline context", stream_id);
+
+ return -1;
+ }
+
+ /* Get stream group */
+ st_group = flb_ml_stream_group_get(mst->parser, mst, NULL);
+
+ ret = flb_log_event_encoder_begin_record(&ml->log_event_encoder);
+
+ if (ret == FLB_EVENT_ENCODER_SUCCESS) {
+ ret = flb_log_event_encoder_set_timestamp(
+ &ml->log_event_encoder, tm);
+ }
+
+ if (ret == FLB_EVENT_ENCODER_SUCCESS) {
+ if (metadata != ml->log_event_decoder.empty_map) {
+ ret = flb_log_event_encoder_set_metadata_from_msgpack_object(
+ &ml->log_event_encoder, metadata);
+ }
+ }
+
+ if (ret == FLB_EVENT_ENCODER_SUCCESS) {
+ ret = flb_log_event_encoder_set_body_from_msgpack_object(
+ &ml->log_event_encoder, obj);
+ }
+
+ if (ret == FLB_EVENT_ENCODER_SUCCESS) {
+ ret = flb_log_event_encoder_commit_record(&ml->log_event_encoder);
+ }
+
+ if (ret == FLB_EVENT_ENCODER_SUCCESS) {
+ mst->cb_flush(parser_i->ml_parser,
+ mst,
+ mst->cb_data,
+ ml->log_event_encoder.output_buffer,
+ ml->log_event_encoder.output_length);
+ }
+ else {
+ flb_error("[multiline] log event encoder error : %d", ret);
+ }
+
+ flb_log_event_encoder_reset(&ml->log_event_encoder);
+
+ /* reset group buffer counters */
+ st_group->mp_sbuf.size = 0;
+ flb_sds_len_set(st_group->buf, 0);
+
+ /* Update last flush time */
+ st_group->last_flush = time_ms_now();
+ }
+
+ return 0;
+}
+
+int flb_ml_append_event(struct flb_ml *ml, uint64_t stream_id,
+ struct flb_log_event *event)
+{
+ return flb_ml_append_object(ml,
+ stream_id,
+ &event->timestamp,
+ event->metadata,
+ event->body);
+}
+
+
+struct flb_ml *flb_ml_create(struct flb_config *ctx, char *name)
+{
+ int result;
+ struct flb_ml *ml;
+
+ ml = flb_calloc(1, sizeof(struct flb_ml));
+ if (!ml) {
+ flb_errno();
+ return NULL;
+ }
+ ml->name = flb_sds_create(name);
+ if (!ml) {
+ flb_free(ml);
+ return NULL;
+ }
+
+ ml->config = ctx;
+ ml->last_flush = time_ms_now();
+ mk_list_init(&ml->groups);
+
+ result = flb_log_event_decoder_init(&ml->log_event_decoder,
+ NULL,
+ 0);
+
+ if (result != FLB_EVENT_DECODER_SUCCESS) {
+ flb_error("cannot initialize log event decoder");
+
+ flb_ml_destroy(ml);
+
+ return NULL;
+ }
+
+ result = flb_log_event_encoder_init(&ml->log_event_encoder,
+ FLB_LOG_EVENT_FORMAT_DEFAULT);
+
+ if (result != FLB_EVENT_ENCODER_SUCCESS) {
+ flb_error("cannot initialize log event encoder");
+
+ flb_ml_destroy(ml);
+
+ return NULL;
+ }
+
+ return ml;
+}
+
+/*
+ * Some multiline contexts might define a parser name but not a parser context,
+ * for missing contexts, just lookup the parser and perform the assignment.
+ *
+ * The common use case is when reading config files with [PARSER] and [MULTILINE_PARSER]
+ * definitions, so we need to delay the parser loading.
+ */
+int flb_ml_parsers_init(struct flb_config *ctx)
+{
+ struct mk_list *head;
+ struct flb_parser *p;
+ struct flb_ml_parser *ml_parser;
+
+ mk_list_foreach(head, &ctx->multiline_parsers) {
+ ml_parser = mk_list_entry(head, struct flb_ml_parser, _head);
+ if (ml_parser->parser_name && !ml_parser->parser) {
+ p = flb_parser_get(ml_parser->parser_name, ctx);
+ if (!p) {
+ flb_error("multiline parser '%s' points to an undefined parser '%s'",
+ ml_parser->name, ml_parser->parser_name);
+ return -1;
+ }
+ ml_parser->parser = p;
+ }
+ }
+
+ return 0;
+}
+
+int flb_ml_auto_flush_init(struct flb_ml *ml)
+{
+ struct flb_sched *scheduler;
+ int ret;
+
+ if (ml == NULL) {
+ return -1;
+ }
+
+ scheduler = flb_sched_ctx_get();
+
+ if (scheduler == NULL) {
+ flb_error("[multiline] scheduler context has not been created");
+ return -1;
+ }
+
+ if (ml->flush_ms < 500) {
+ flb_error("[multiline] flush timeout '%i' is too low", ml->flush_ms);
+ return -1;
+ }
+
+ /* Create flush timer */
+ ret = flb_sched_timer_cb_create(scheduler,
+ FLB_SCHED_TIMER_CB_PERM,
+ ml->flush_ms,
+ cb_ml_flush_timer,
+ ml, NULL);
+ return ret;
+}
+
+int flb_ml_destroy(struct flb_ml *ml)
+{
+ struct mk_list *head;
+ struct mk_list *tmp;
+ struct flb_ml_group *group;
+
+ if (!ml) {
+ return 0;
+ }
+
+ flb_log_event_decoder_destroy(&ml->log_event_decoder);
+ flb_log_event_encoder_destroy(&ml->log_event_encoder);
+
+ if (ml->name) {
+ flb_sds_destroy(ml->name);
+ }
+
+ /* destroy groups */
+ mk_list_foreach_safe(head, tmp, &ml->groups) {
+ group = mk_list_entry(head, struct flb_ml_group, _head);
+ flb_ml_group_destroy(group);
+ }
+
+ flb_free(ml);
+ return 0;
+}
+
+static int flb_msgpack_object_hash_internal(cfl_hash_state_t *state,
+ msgpack_object *object)
+{
+ void *dummy_pointer;
+ int result;
+ int index;
+
+ if (object == NULL) {
+ return 0;
+ }
+
+ dummy_pointer = NULL;
+ result = 0;
+
+ if (object->type == MSGPACK_OBJECT_NIL) {
+ cfl_hash_64bits_update(state,
+ &dummy_pointer,
+ sizeof(dummy_pointer));
+ }
+ else if (object->type == MSGPACK_OBJECT_BOOLEAN) {
+ cfl_hash_64bits_update(state,
+ &object->via.boolean,
+ sizeof(object->via.boolean));
+ }
+ else if (object->type == MSGPACK_OBJECT_POSITIVE_INTEGER ||
+ object->type == MSGPACK_OBJECT_NEGATIVE_INTEGER) {
+ cfl_hash_64bits_update(state,
+ &object->via.u64,
+ sizeof(object->via.u64));
+ }
+ else if (object->type == MSGPACK_OBJECT_FLOAT32 ||
+ object->type == MSGPACK_OBJECT_FLOAT64 ||
+ object->type == MSGPACK_OBJECT_FLOAT) {
+ cfl_hash_64bits_update(state,
+ &object->via.f64,
+ sizeof(object->via.f64));
+ }
+ else if (object->type == MSGPACK_OBJECT_STR) {
+ cfl_hash_64bits_update(state,
+ object->via.str.ptr,
+ object->via.str.size);
+ }
+ else if (object->type == MSGPACK_OBJECT_ARRAY) {
+ for (index = 0 ;
+ index < object->via.array.size &&
+ result == 0;
+ index++) {
+ result = flb_msgpack_object_hash_internal(
+ state,
+ &object->via.array.ptr[index]);
+ }
+ }
+ else if (object->type == MSGPACK_OBJECT_MAP) {
+ for (index = 0 ;
+ index < object->via.map.size &&
+ result == 0;
+ index++) {
+ result = flb_msgpack_object_hash_internal(
+ state,
+ &object->via.map.ptr[index].key);
+
+ if (result == 0) {
+ result = flb_msgpack_object_hash_internal(
+ state,
+ &object->via.map.ptr[index].val);
+ }
+ }
+ }
+ else if (object->type == MSGPACK_OBJECT_BIN) {
+ cfl_hash_64bits_update(state,
+ object->via.bin.ptr,
+ object->via.bin.size);
+ }
+ else if (object->type == MSGPACK_OBJECT_EXT) {
+ cfl_hash_64bits_update(state,
+ &object->via.ext.type,
+ sizeof(object->via.ext.type));
+
+ cfl_hash_64bits_update(state,
+ object->via.ext.ptr,
+ object->via.ext.size);
+ }
+
+ return result;
+}
+
+static int flb_hash_msgpack_object_list(cfl_hash_64bits_t *hash,
+ size_t entry_count,
+ ...)
+{
+ cfl_hash_state_t hash_state;
+ va_list arguments;
+ msgpack_object *object;
+ int result;
+ size_t index;
+
+ cfl_hash_64bits_reset(&hash_state);
+
+ va_start(arguments, entry_count);
+
+ result = 0;
+
+ for (index = 0 ;
+ index < entry_count &&
+ result == 0 ;
+ index++) {
+ object = va_arg(arguments, msgpack_object *);
+
+ if (object == NULL) {
+ break;
+ }
+
+ result = flb_msgpack_object_hash_internal(&hash_state, object);
+ }
+
+ va_end(arguments);
+
+ if (result == 0) {
+ *hash = cfl_hash_64bits_digest(&hash_state);
+ }
+
+ return result;
+}
+
+struct flb_deduplication_list_entry {
+ cfl_hash_64bits_t hash;
+ struct cfl_list _head;
+};
+
+void flb_deduplication_list_init(struct cfl_list *deduplication_list)
+{
+ cfl_list_init(deduplication_list);
+}
+
+int flb_deduplication_list_validate(struct cfl_list *deduplication_list,
+ cfl_hash_64bits_t hash)
+{
+ struct cfl_list *iterator;
+ struct flb_deduplication_list_entry *entry;
+
+ cfl_list_foreach(iterator, deduplication_list) {
+ entry = cfl_list_entry(iterator,
+ struct flb_deduplication_list_entry,
+ _head);
+
+ if (entry->hash == hash) {
+ return FLB_TRUE;
+ }
+ }
+
+ return FLB_FALSE;
+}
+
+int flb_deduplication_list_add(struct cfl_list *deduplication_list,
+ cfl_hash_64bits_t hash)
+{
+ struct flb_deduplication_list_entry *entry;
+
+ entry = (struct flb_deduplication_list_entry *)
+ flb_calloc(1,
+ sizeof(struct flb_deduplication_list_entry));
+
+ if (entry == NULL) {
+ return -1;
+ }
+
+ cfl_list_entry_init(&entry->_head);
+ entry->hash = hash;
+
+ cfl_list_append(&entry->_head, deduplication_list);
+
+ return 0;
+}
+
+void flb_deduplication_list_purge(struct cfl_list *deduplication_list)
+{
+ struct cfl_list *iterator;
+ struct cfl_list *backup;
+ struct flb_deduplication_list_entry *entry;
+
+ cfl_list_foreach_safe(iterator, backup, deduplication_list) {
+ entry = cfl_list_entry(iterator,
+ struct flb_deduplication_list_entry,
+ _head);
+
+ cfl_list_del(&entry->_head);
+
+ free(entry);
+ }
+}
+
+int flb_ml_flush_metadata_buffer(struct flb_ml_stream *mst,
+ struct flb_ml_stream_group *group,
+ int deduplicate_metadata)
+{
+ int append_metadata_entry;
+ cfl_hash_64bits_t metadata_entry_hash;
+ struct cfl_list deduplication_list;
+ msgpack_unpacked metadata_map;
+ size_t offset;
+ size_t index;
+ msgpack_object value;
+ msgpack_object key;
+ int ret;
+
+ ret = FLB_EVENT_ENCODER_SUCCESS;
+
+ if (deduplicate_metadata) {
+ flb_deduplication_list_init(&deduplication_list);
+ }
+
+ msgpack_unpacked_init(&metadata_map);
+
+ offset = 0;
+ while (ret == FLB_EVENT_ENCODER_SUCCESS &&
+ msgpack_unpack_next(&metadata_map,
+ group->mp_md_sbuf.data,
+ group->mp_md_sbuf.size,
+ &offset) == MSGPACK_UNPACK_SUCCESS) {
+
+ for (index = 0;
+ index < metadata_map.data.via.map.size &&
+ ret == FLB_EVENT_ENCODER_SUCCESS;
+ index++) {
+ key = metadata_map.data.via.map.ptr[index].key;
+ value = metadata_map.data.via.map.ptr[index].val;
+
+ append_metadata_entry = FLB_TRUE;
+
+ if (deduplicate_metadata) {
+ ret = flb_hash_msgpack_object_list(&metadata_entry_hash,
+ 2,
+ &key,
+ &value);
+ if (ret != 0) {
+ ret = FLB_EVENT_ENCODER_ERROR_INVALID_ARGUMENT;
+ }
+ else {
+ ret = flb_deduplication_list_validate(
+ &deduplication_list,
+ metadata_entry_hash);
+
+ if (ret) {
+ append_metadata_entry = FLB_FALSE;
+
+ ret = FLB_EVENT_ENCODER_SUCCESS;
+ }
+ else {
+ ret = flb_deduplication_list_add(
+ &deduplication_list,
+ metadata_entry_hash);
+
+ if (ret == 0) {
+ ret = FLB_EVENT_ENCODER_SUCCESS;
+ }
+ else {
+ ret = FLB_EVENT_ENCODER_ERROR_ALLOCATION_ERROR;
+ }
+ }
+ }
+ }
+
+ if (append_metadata_entry) {
+ if (ret == FLB_EVENT_ENCODER_SUCCESS) {
+ ret = flb_log_event_encoder_append_metadata_values(
+ &mst->ml->log_event_encoder,
+ FLB_LOG_EVENT_MSGPACK_OBJECT_VALUE(&key),
+ FLB_LOG_EVENT_MSGPACK_OBJECT_VALUE(&value));
+ }
+ }
+ }
+ }
+
+ msgpack_unpacked_destroy(&metadata_map);
+
+ if (deduplicate_metadata) {
+ flb_deduplication_list_purge(&deduplication_list);
+ }
+
+ return ret;
+}
+
+int flb_ml_flush_stream_group(struct flb_ml_parser *ml_parser,
+ struct flb_ml_stream *mst,
+ struct flb_ml_stream_group *group,
+ int forced_flush)
+{
+ int i;
+ int ret;
+ int size;
+ int len;
+ size_t off = 0;
+ msgpack_object map;
+ msgpack_object k;
+ msgpack_object v;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+ msgpack_unpacked result;
+ struct flb_ml_parser_ins *parser_i = mst->parser;
+ struct flb_time *group_time;
+ struct flb_time now;
+
+ breakline_prepare(parser_i, group);
+ len = flb_sds_len(group->buf);
+
+ /* init msgpack buffer */
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ /* if the group don't have a time set, use current time */
+ if (flb_time_to_nanosec(&group->mp_time) == 0L) {
+ flb_time_get(&now);
+ group_time = &now;
+ } else {
+ group_time = &group->mp_time;
+ }
+
+ /* compose final record if we have a first line context */
+ if (group->mp_sbuf.size > 0) {
+ msgpack_unpacked_init(&result);
+ ret = msgpack_unpack_next(&result,
+ group->mp_sbuf.data, group->mp_sbuf.size,
+ &off);
+ if (ret != MSGPACK_UNPACK_SUCCESS) {
+ flb_error("[multiline] could not unpack first line state buffer");
+ msgpack_unpacked_destroy(&result);
+ group->mp_sbuf.size = 0;
+ return -1;
+ }
+ map = result.data;
+
+ if (map.type != MSGPACK_OBJECT_MAP) {
+ flb_error("[multiline] expected MAP type in first line state buffer");
+ msgpack_unpacked_destroy(&result);
+ group->mp_sbuf.size = 0;
+ return -1;
+ }
+
+ /* Take the first line keys and repack */
+ len = flb_sds_len(parser_i->key_content);
+ size = map.via.map.size;
+ msgpack_pack_map(&mp_pck, size);
+
+ for (i = 0; i < size; i++) {
+ k = map.via.map.ptr[i].key;
+ v = map.via.map.ptr[i].val;
+
+ /*
+ * Check if the current key is the key that will contain the
+ * concatenated multiline buffer
+ */
+ if (k.type == MSGPACK_OBJECT_STR &&
+ parser_i->key_content &&
+ k.via.str.size == len &&
+ strncmp(k.via.str.ptr, parser_i->key_content, len) == 0) {
+
+ /* key */
+ msgpack_pack_object(&mp_pck, k);
+
+ /* value */
+ len = flb_sds_len(group->buf);
+ msgpack_pack_str(&mp_pck, len);
+ msgpack_pack_str_body(&mp_pck, group->buf, len);
+ }
+ else {
+ /* key / val */
+ msgpack_pack_object(&mp_pck, k);
+ msgpack_pack_object(&mp_pck, v);
+ }
+ }
+ msgpack_unpacked_destroy(&result);
+ group->mp_sbuf.size = 0;
+ }
+ else if (len > 0) {
+ /* Pack raw content as Fluent Bit record */
+ msgpack_pack_map(&mp_pck, 1);
+
+ /* key */
+ if (parser_i->key_content) {
+ len = flb_sds_len(parser_i->key_content);
+ msgpack_pack_str(&mp_pck, len);
+ msgpack_pack_str_body(&mp_pck, parser_i->key_content, len);
+ }
+ else {
+ msgpack_pack_str(&mp_pck, 3);
+ msgpack_pack_str_body(&mp_pck, "log", 3);
+ }
+
+ /* val */
+ len = flb_sds_len(group->buf);
+ msgpack_pack_str(&mp_pck, len);
+ msgpack_pack_str_body(&mp_pck, group->buf, len);
+ }
+
+ if (mp_sbuf.size > 0) {
+ /*
+ * a 'forced_flush' means to alert the caller that the data 'must be flushed to it destination'. This flag is
+ * only enabled when the flush process has been triggered by the multiline timer, e.g:
+ *
+ * - the message is complete or incomplete and its time to dispatch it.
+ */
+ if (forced_flush) {
+ mst->forced_flush = FLB_TRUE;
+ }
+
+ /* encode and invoke the user callback */
+
+ ret = flb_log_event_encoder_begin_record(
+ &mst->ml->log_event_encoder);
+
+ if (ret == FLB_EVENT_ENCODER_SUCCESS) {
+ ret = flb_log_event_encoder_set_timestamp(
+ &mst->ml->log_event_encoder,
+ group_time);
+ }
+
+ if (ret == FLB_EVENT_ENCODER_SUCCESS) {
+ ret = flb_ml_flush_metadata_buffer(mst,
+ group,
+ FLB_TRUE);
+ }
+
+ if (ret == FLB_EVENT_ENCODER_SUCCESS) {
+ ret = flb_log_event_encoder_set_body_from_raw_msgpack(
+ &mst->ml->log_event_encoder,
+ mp_sbuf.data,
+ mp_sbuf.size);
+ }
+
+ if (ret == FLB_EVENT_ENCODER_SUCCESS) {
+ ret = flb_log_event_encoder_commit_record(
+ &mst->ml->log_event_encoder);
+ }
+
+ if (ret != FLB_EVENT_ENCODER_SUCCESS) {
+ flb_error("[multiline] error packing event");
+
+ return -1;
+ }
+
+ if (ret == FLB_EVENT_ENCODER_SUCCESS) {
+ mst->cb_flush(ml_parser,
+ mst,
+ mst->cb_data,
+ mst->ml->log_event_encoder.output_buffer,
+ mst->ml->log_event_encoder.output_length);
+ }
+ else {
+ flb_error("[multiline] log event encoder error : %d", ret);
+ }
+
+ flb_log_event_encoder_reset(&mst->ml->log_event_encoder);
+
+ if (forced_flush) {
+ mst->forced_flush = FLB_FALSE;
+ }
+ }
+
+ msgpack_sbuffer_destroy(&mp_sbuf);
+ flb_sds_len_set(group->buf, 0);
+
+ /* Update last flush time */
+ group->last_flush = time_ms_now();
+
+ return 0;
+}
+
+/*
+ * Initialize multiline global environment.
+ *
+ * note: function must be invoked before any flb_ml_create() call.
+ */
+int flb_ml_init(struct flb_config *config)
+{
+ int ret;
+
+ ret = flb_ml_parser_builtin_create(config);
+ if (ret == -1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int flb_ml_exit(struct flb_config *config)
+{
+ flb_ml_parser_destroy_all(&config->multiline_parsers);
+ return 0;
+}
diff --git a/fluent-bit/src/multiline/flb_ml_group.c b/fluent-bit/src/multiline/flb_ml_group.c
new file mode 100644
index 000000000..895a71056
--- /dev/null
+++ b/fluent-bit/src/multiline/flb_ml_group.c
@@ -0,0 +1,86 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/multiline/flb_ml.h>
+#include <fluent-bit/multiline/flb_ml_parser.h>
+
+struct flb_ml_group *flb_ml_group_create(struct flb_ml *ml)
+{
+ struct flb_ml_group *group;
+
+ group = flb_calloc(1, sizeof(struct flb_ml_group));
+ if (!group) {
+ flb_errno();
+ return NULL;
+ }
+ group->id = mk_list_size(&ml->groups);
+ group->ml = ml;
+ group->lru_parser = NULL;
+ mk_list_init(&group->parsers);
+
+ mk_list_add(&group->_head, &ml->groups);
+
+ return group;
+}
+
+/*
+ * Link a parser instance into the active group, if no group exists, a default
+ * one is created.
+ */
+int flb_ml_group_add_parser(struct flb_ml *ctx, struct flb_ml_parser_ins *p)
+{
+ struct flb_ml_group *group = NULL;
+
+ if (mk_list_size(&ctx->groups) == 0) {
+ group = flb_ml_group_create(ctx);
+ if (!group) {
+ return -1;
+ }
+ }
+ else {
+ /* retrieve the latest active group */
+ group = mk_list_entry_last(&ctx->groups, struct flb_ml_group, _head);
+ }
+
+ if (!group) {
+ return -1;
+ }
+
+ mk_list_add(&p->_head, &group->parsers);
+ return 0;
+}
+
+void flb_ml_group_destroy(struct flb_ml_group *group)
+{
+ struct mk_list *head;
+ struct mk_list *tmp;
+ struct flb_ml_parser_ins *parser_i;
+
+ /* destroy parser instances */
+ mk_list_foreach_safe(head, tmp, &group->parsers) {
+ parser_i = mk_list_entry(head, struct flb_ml_parser_ins, _head);
+ flb_ml_parser_instance_destroy(parser_i);
+ }
+
+ mk_list_del(&group->_head);
+ flb_free(group);
+}
diff --git a/fluent-bit/src/multiline/flb_ml_mode.c b/fluent-bit/src/multiline/flb_ml_mode.c
new file mode 100644
index 000000000..964672d82
--- /dev/null
+++ b/fluent-bit/src/multiline/flb_ml_mode.c
@@ -0,0 +1,111 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/multiline/flb_ml.h>
+#include <fluent-bit/multiline/flb_ml_mode.h>
+
+struct flb_ml *flb_ml_mode_create(struct flb_config *config, char *mode, int flush_ms,
+ char *key)
+{
+ if (strcmp(mode, "docker") == 0) {
+ return flb_ml_mode_docker(config, flush_ms);
+ }
+ else if (strcmp(mode, "cri") == 0) {
+ return flb_ml_mode_cri(config, flush_ms);
+ }
+ else if (strcmp(mode, "python") == 0) {
+ return flb_ml_mode_python(config, flush_ms, key);
+ }
+ else if (strcmp(mode, "java") == 0) {
+ return flb_ml_mode_java(config, flush_ms, key);
+ }
+ else if (strcmp(mode, "go") == 0) {
+ return flb_ml_mode_go(config, flush_ms, key);
+ }
+
+ flb_error("[multiline] built-in mode '%s' not found", mode);
+ return NULL;
+}
+
+
+struct flb_ml_mode *flb_ml_parser_create(struct flb_config *ctx,
+ char *name,
+ int type, char *match_str, int negate,
+ int flush_ms,
+ char *key_content,
+ char *key_group,
+ char *key_pattern,
+ struct flb_parser *parser_ctx,
+ char *parser_name)
+{
+ struct flb_ml_mode *ml;
+
+ ml = flb_calloc(1, sizeof(struct flb_ml));
+ if (!ml) {
+ flb_errno();
+ return NULL;
+ }
+ ml->name = flb_sds_create(name);
+ ml->type = type;
+
+ if (match_str) {
+ ml->match_str = flb_sds_create(match_str);
+ if (!ml->match_str) {
+ flb_free(ml);
+ return NULL;
+ }
+ }
+
+ ml->parser = parser_ctx;
+ if (parser_name) {
+ ml->parser_name = flb_sds_create(parser_name);
+ }
+
+ ml->negate = negate;
+ mk_list_init(&ml->streams);
+ mk_list_init(&ml->regex_rules);
+ mk_list_add(&ml->_head, &ctx->multilines);
+
+ if (key_content) {
+ ml->key_content = flb_sds_create(key_content);
+ if (!ml->key_content) {
+ flb_ml_destroy(ml);
+ return NULL;
+ }
+ }
+
+ if (key_group) {
+ ml->key_group = flb_sds_create(key_group);
+ if (!ml->key_group) {
+ flb_ml_destroy(ml);
+ return NULL;
+ }
+ }
+
+ if (key_pattern) {
+ ml->key_pattern = flb_sds_create(key_pattern);
+ if (!ml->key_pattern) {
+ flb_ml_destroy(ml);
+ return NULL;
+ }
+ }
+ return ml;
+}
diff --git a/fluent-bit/src/multiline/flb_ml_parser.c b/fluent-bit/src/multiline/flb_ml_parser.c
new file mode 100644
index 000000000..7aa33789d
--- /dev/null
+++ b/fluent-bit/src/multiline/flb_ml_parser.c
@@ -0,0 +1,347 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/multiline/flb_ml.h>
+#include <fluent-bit/multiline/flb_ml_rule.h>
+#include <fluent-bit/multiline/flb_ml_mode.h>
+#include <fluent-bit/multiline/flb_ml_group.h>
+
+int flb_ml_parser_init(struct flb_ml_parser *ml_parser)
+{
+ int ret;
+
+ ret = flb_ml_rule_init(ml_parser);
+ if (ret == -1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Create built-in multiline parsers */
+int flb_ml_parser_builtin_create(struct flb_config *config)
+{
+ struct flb_ml_parser *mlp;
+
+ /* Docker */
+ mlp = flb_ml_parser_docker(config);
+ if (!mlp) {
+ flb_error("[multiline] could not init 'docker' built-in parser");
+ return -1;
+ }
+
+ /* CRI */
+ mlp = flb_ml_parser_cri(config);
+ if (!mlp) {
+ flb_error("[multiline] could not init 'cri' built-in parser");
+ return -1;
+ }
+
+ /* Java */
+ mlp = flb_ml_parser_java(config, NULL);
+ if (!mlp) {
+ flb_error("[multiline] could not init 'java' built-in parser");
+ return -1;
+ }
+
+ /* Go */
+ mlp = flb_ml_parser_go(config, NULL);
+ if (!mlp) {
+ flb_error("[multiline] could not init 'go' built-in parser");
+ return -1;
+ }
+
+ /* Ruby */
+ mlp = flb_ml_parser_ruby(config, NULL);
+ if (!mlp) {
+ flb_error("[multiline] could not init 'ruby' built-in parser");
+ return -1;
+ }
+
+ /* Python */
+ mlp = flb_ml_parser_python(config, NULL);
+ if (!mlp) {
+ flb_error("[multiline] could not init 'python' built-in parser");
+ return -1;
+ }
+
+ return 0;
+}
+
+struct flb_ml_parser *flb_ml_parser_create(struct flb_config *ctx,
+ char *name,
+ int type, char *match_str, int negate,
+ int flush_ms,
+ char *key_content,
+ char *key_group,
+ char *key_pattern,
+ struct flb_parser *parser_ctx,
+ char *parser_name)
+{
+ struct flb_ml_parser *ml_parser;
+
+ ml_parser = flb_calloc(1, sizeof(struct flb_ml_parser));
+ if (!ml_parser) {
+ flb_errno();
+ return NULL;
+ }
+ ml_parser->name = flb_sds_create(name);
+ ml_parser->type = type;
+
+ if (match_str) {
+ ml_parser->match_str = flb_sds_create(match_str);
+ if (!ml_parser->match_str) {
+ if (ml_parser->name) {
+ flb_sds_destroy(ml_parser->name);
+ }
+ flb_free(ml_parser);
+ return NULL;
+ }
+ }
+
+ ml_parser->parser = parser_ctx;
+
+ if (parser_name) {
+ ml_parser->parser_name = flb_sds_create(parser_name);
+ }
+ ml_parser->negate = negate;
+ ml_parser->flush_ms = flush_ms;
+ mk_list_init(&ml_parser->regex_rules);
+ mk_list_add(&ml_parser->_head, &ctx->multiline_parsers);
+
+ if (key_content) {
+ ml_parser->key_content = flb_sds_create(key_content);
+ if (!ml_parser->key_content) {
+ flb_ml_parser_destroy(ml_parser);
+ return NULL;
+ }
+ }
+
+ if (key_group) {
+ ml_parser->key_group = flb_sds_create(key_group);
+ if (!ml_parser->key_group) {
+ flb_ml_parser_destroy(ml_parser);
+ return NULL;
+ }
+ }
+
+ if (key_pattern) {
+ ml_parser->key_pattern = flb_sds_create(key_pattern);
+ if (!ml_parser->key_pattern) {
+ flb_ml_parser_destroy(ml_parser);
+ return NULL;
+ }
+ }
+
+ return ml_parser;
+}
+
+struct flb_ml_parser *flb_ml_parser_get(struct flb_config *ctx, char *name)
+{
+ struct mk_list *head;
+ struct flb_ml_parser *ml_parser;
+
+ mk_list_foreach(head, &ctx->multiline_parsers) {
+ ml_parser = mk_list_entry(head, struct flb_ml_parser, _head);
+ if (strcasecmp(ml_parser->name, name) == 0) {
+ return ml_parser;
+ }
+ }
+
+ return NULL;
+}
+
+int flb_ml_parser_instance_has_data(struct flb_ml_parser_ins *ins)
+{
+ struct mk_list *head;
+ struct mk_list *head_group;
+ struct flb_ml_stream *st;
+ struct flb_ml_stream_group *st_group;
+
+ mk_list_foreach(head, &ins->streams) {
+ st = mk_list_entry(head, struct flb_ml_stream, _head);
+ mk_list_foreach(head_group, &st->groups) {
+ st_group = mk_list_entry(head_group, struct flb_ml_stream_group, _head);
+ if (st_group->mp_sbuf.size > 0) {
+ return FLB_TRUE;
+ }
+ }
+ }
+
+ return FLB_FALSE;
+}
+
+struct flb_ml_parser_ins *flb_ml_parser_instance_create(struct flb_ml *ml,
+ char *name)
+{
+ int ret;
+ struct flb_ml_parser_ins *ins;
+ struct flb_ml_parser *parser;
+
+ parser = flb_ml_parser_get(ml->config, name);
+ if (!parser) {
+ flb_error("[multiline] parser '%s' not registered", name);
+ return NULL;
+ }
+
+ ins = flb_calloc(1, sizeof(struct flb_ml_parser_ins));
+ if (!ins) {
+ flb_errno();
+ return NULL;
+ }
+ ins->last_stream_id = 0;
+ ins->ml_parser = parser;
+ mk_list_init(&ins->streams);
+
+ /* Copy parent configuration */
+ if (parser->key_content) {
+ ins->key_content = flb_sds_create(parser->key_content);
+ }
+ if (parser->key_pattern) {
+ ins->key_pattern = flb_sds_create(parser->key_pattern);
+ }
+ if (parser->key_group) {
+ ins->key_group = flb_sds_create(parser->key_group);
+ }
+
+ /* Append this multiline parser instance to the active multiline group */
+ ret = flb_ml_group_add_parser(ml, ins);
+ if (ret != 0) {
+ flb_error("[multiline] could not register parser '%s' on "
+ "multiline '%s 'group", name, ml->name);
+ flb_free(ins);
+ return NULL;
+ }
+
+ /*
+ * Update flush_interval for pending records on multiline context. We always
+ * use the greater value found.
+ */
+ if (parser->flush_ms > ml->flush_ms) {
+ ml->flush_ms = parser->flush_ms;
+ }
+
+ return ins;
+}
+
+/* Override a fixed parser property for the instance only*/
+int flb_ml_parser_instance_set(struct flb_ml_parser_ins *p, char *prop, char *val)
+{
+ if (strcasecmp(prop, "key_content") == 0) {
+ if (p->key_content) {
+ flb_sds_destroy(p->key_content);
+ }
+ p->key_content = flb_sds_create(val);
+ }
+ else if (strcasecmp(prop, "key_pattern") == 0) {
+ if (p->key_pattern) {
+ flb_sds_destroy(p->key_pattern);
+ }
+ p->key_pattern = flb_sds_create(val);
+ }
+ else if (strcasecmp(prop, "key_group") == 0) {
+ if (p->key_group) {
+ flb_sds_destroy(p->key_group);
+ }
+ p->key_group = flb_sds_create(val);
+ }
+ else {
+ return -1;
+ }
+
+ return 0;
+}
+
+int flb_ml_parser_destroy(struct flb_ml_parser *ml_parser)
+{
+ if (!ml_parser) {
+ return 0;
+ }
+
+ if (ml_parser->name) {
+ flb_sds_destroy(ml_parser->name);
+ }
+
+ if (ml_parser->parser_name) {
+ flb_sds_destroy(ml_parser->parser_name);
+ }
+
+ if (ml_parser->match_str) {
+ flb_sds_destroy(ml_parser->match_str);
+ }
+ if (ml_parser->key_content) {
+ flb_sds_destroy(ml_parser->key_content);
+ }
+ if (ml_parser->key_group) {
+ flb_sds_destroy(ml_parser->key_group);
+ }
+ if (ml_parser->key_pattern) {
+ flb_sds_destroy(ml_parser->key_pattern);
+ }
+
+ /* Regex rules */
+ flb_ml_rule_destroy_all(ml_parser);
+
+ /* Unlink from struct flb_config->multiline_parsers */
+ mk_list_del(&ml_parser->_head);
+
+ flb_free(ml_parser);
+ return 0;
+}
+
+int flb_ml_parser_instance_destroy(struct flb_ml_parser_ins *ins)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_ml_stream *stream;
+
+ /* Destroy streams */
+ mk_list_foreach_safe(head, tmp, &ins->streams) {
+ stream = mk_list_entry(head, struct flb_ml_stream, _head);
+ flb_ml_stream_destroy(stream);
+ }
+
+ if (ins->key_content) {
+ flb_sds_destroy(ins->key_content);
+ }
+ if (ins->key_pattern) {
+ flb_sds_destroy(ins->key_pattern);
+ }
+ if (ins->key_group) {
+ flb_sds_destroy(ins->key_group);
+ }
+
+ flb_free(ins);
+
+ return 0;
+}
+
+void flb_ml_parser_destroy_all(struct mk_list *list)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_ml_parser *parser;
+
+ mk_list_foreach_safe(head, tmp, list) {
+ parser = mk_list_entry(head, struct flb_ml_parser, _head);
+ flb_ml_parser_destroy(parser);
+ }
+}
diff --git a/fluent-bit/src/multiline/flb_ml_parser_cri.c b/fluent-bit/src/multiline/flb_ml_parser_cri.c
new file mode 100644
index 000000000..669fa39a2
--- /dev/null
+++ b/fluent-bit/src/multiline/flb_ml_parser_cri.c
@@ -0,0 +1,81 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/multiline/flb_ml.h>
+#include <fluent-bit/multiline/flb_ml_parser.h>
+
+#define FLB_ML_CRI_REGEX \
+ "^(?<time>.+?) (?<stream>stdout|stderr) (?<_p>F|P) (?<log>.*)$"
+#define FLB_ML_CRI_TIME \
+ "%Y-%m-%dT%H:%M:%S.%L%z"
+
+/* Creates a parser for Docker */
+static struct flb_parser *cri_parser_create(struct flb_config *config)
+{
+ struct flb_parser *p;
+
+ p = flb_parser_create("_ml_cri", /* parser name */
+ "regex", /* backend type */
+ FLB_ML_CRI_REGEX, /* regex */
+ FLB_FALSE, /* skip_empty */
+ FLB_ML_CRI_TIME, /* time format */
+ "time", /* time key */
+ NULL, /* time offset */
+ FLB_TRUE, /* time keep */
+ FLB_FALSE, /* time strict */
+ FLB_FALSE, /* no bare keys */
+ NULL, /* parser types */
+ 0, /* types len */
+ NULL, /* decoders */
+ config); /* Fluent Bit context */
+ return p;
+}
+
+/* Our first multiline mode: 'docker' */
+struct flb_ml_parser *flb_ml_parser_cri(struct flb_config *config)
+{
+ struct flb_parser *parser;
+ struct flb_ml_parser *mlp;
+
+ /* Create a Docker parser */
+ parser = cri_parser_create(config);
+ if (!parser) {
+ return NULL;
+ }
+
+ mlp = flb_ml_parser_create(config,
+ "cri", /* name */
+ FLB_ML_EQ, /* type */
+ "F", /* match_str */
+ FLB_FALSE, /* negate */
+ FLB_ML_FLUSH_TIMEOUT, /* flush_ms */
+ "log", /* key_content */
+ "stream", /* key_group */
+ "_p", /* key_pattern */
+ parser, /* parser ctx */
+ NULL); /* parser name */
+
+ if (!mlp) {
+ flb_error("[multiline] could not create 'cri mode'");
+ return NULL;
+ }
+
+ return mlp;
+}
diff --git a/fluent-bit/src/multiline/flb_ml_parser_docker.c b/fluent-bit/src/multiline/flb_ml_parser_docker.c
new file mode 100644
index 000000000..5b622d32c
--- /dev/null
+++ b/fluent-bit/src/multiline/flb_ml_parser_docker.c
@@ -0,0 +1,110 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/multiline/flb_ml.h>
+#include <fluent-bit/multiline/flb_ml_parser.h>
+
+/* Creates a parser for Docker */
+static struct flb_parser *docker_parser_create(struct flb_config *config)
+{
+ struct flb_parser *p;
+
+ p = flb_parser_create("_ml_json_docker", /* parser name */
+ "json", /* backend type */
+ NULL, /* regex */
+ FLB_TRUE, /* skip_empty */
+ "%Y-%m-%dT%H:%M:%S.%L", /* time format */
+ "time", /* time key */
+ NULL, /* time offset */
+ FLB_TRUE, /* time keep */
+ FLB_FALSE, /* time strict */
+ FLB_FALSE, /* no bare keys */
+ NULL, /* parser types */
+ 0, /* types len */
+ NULL, /* decoders */
+ config); /* Fluent Bit context */
+ return p;
+}
+
+/* Our first multiline mode: 'docker' */
+struct flb_ml_parser *flb_ml_parser_docker(struct flb_config *config)
+{
+ struct flb_parser *parser;
+ struct flb_ml_parser *mlp;
+
+ /* Create a Docker parser */
+ parser = docker_parser_create(config);
+ if (!parser) {
+ return NULL;
+ }
+
+ /*
+ * Let's explain this multiline mode, then you (the reader) might want
+ * to submit a PR with new built-in modes :)
+ *
+ * Containerized apps under Docker writes logs to stdout/stderr. These streams
+ * (stdout/stderr) are handled by Docker, in most of cases the content is
+ * stored in a .json file in your file system. A message like "hey!" gets into
+ * a JSON map like this:
+ *
+ * {"log": "hey!\n", "stream": "stdout", "time": "2021-02-01T01:40:03.53412Z"}
+ *
+ * By Docker log spec, any 'log' key that "ends with a \n" it's a complete
+ * log record, but Docker also limits the log record size to 16KB, so a long
+ * message that does not fit into 16KB can be split in multiple JSON lines,
+ * the following example use short words to describe the context:
+ *
+ * - original message: 'one, two, three\n'
+ *
+ * Docker log interpretation:
+ *
+ * - {"log": "one, ", "stream": "stdout", "time": "2021-02-01T01:40:03.53413Z"}
+ * - {"log": "two, ", "stream": "stdout", "time": "2021-02-01T01:40:03.53414Z"}
+ * - {"log": "three\n", "stream": "stdout", "time": "2021-02-01T01:40:03.53415Z"}
+ *
+ * So every 'log' key that does not ends with '\n', it's a partial log record
+ * and for logging purposes it needs to be concatenated with further messages
+ * until a final '\n' is found.
+ *
+ * We setup the Multiline mode as follows:
+ *
+ * - Use the type 'FLB_ML_ENDSWITH' to specify that we expect the 'log'
+ * key must ends with a '\n' for complete messages, otherwise it means is
+ * a continuation message. In case a message is not complete just wait until
+ * 500 milliseconds (0.5 second) and flush the buffer.
+ */
+ mlp = flb_ml_parser_create(config, /* Fluent Bit context */
+ "docker", /* name */
+ FLB_ML_ENDSWITH, /* type */
+ "\n", /* match_str */
+ FLB_FALSE, /* negate */
+ FLB_ML_FLUSH_TIMEOUT, /* flush_ms */
+ "log", /* key_content */
+ "stream", /* key_group */
+ NULL, /* key_pattern */
+ parser, /* parser ctx */
+ NULL); /* parser name */
+ if (!mlp) {
+ flb_error("[multiline] could not create 'docker mode'");
+ return NULL;
+ }
+
+ return mlp;
+}
diff --git a/fluent-bit/src/multiline/flb_ml_parser_go.c b/fluent-bit/src/multiline/flb_ml_parser_go.c
new file mode 100644
index 000000000..f1cd5407f
--- /dev/null
+++ b/fluent-bit/src/multiline/flb_ml_parser_go.c
@@ -0,0 +1,140 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/multiline/flb_ml.h>
+#include <fluent-bit/multiline/flb_ml_rule.h>
+#include <fluent-bit/multiline/flb_ml_parser.h>
+
+#define rule flb_ml_rule_create
+
+static void rule_error(struct flb_ml_parser *mlp)
+{
+ int id;
+
+ id = mk_list_size(&mlp->regex_rules);
+ flb_error("[multiline: go] rule #%i could not be created", id);
+ flb_ml_parser_destroy(mlp);
+}
+
+/* Go mode */
+struct flb_ml_parser *flb_ml_parser_go(struct flb_config *config, char *key)
+{
+ int ret;
+ struct flb_ml_parser *mlp;
+
+ mlp = flb_ml_parser_create(config, /* Fluent Bit context */
+ "go", /* name */
+ FLB_ML_REGEX, /* type */
+ NULL, /* match_str */
+ FLB_FALSE, /* negate */
+ FLB_ML_FLUSH_TIMEOUT, /* flush_ms */
+ key, /* key_content */
+ NULL, /* key_group */
+ NULL, /* key_pattern */
+ NULL, /* parser ctx */
+ NULL); /* parser name */
+
+ if (!mlp) {
+ flb_error("[multiline] could not create 'go mode'");
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "start_state",
+ "/\\bpanic: /",
+ "go_after_panic", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "start_state",
+ "/http: panic serving/",
+ "go_goroutine", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "go_after_panic",
+ "/^$/",
+ "go_goroutine", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "go_after_panic, go_after_signal, go_frame_1",
+ "/^$/",
+ "go_goroutine", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "go_after_panic",
+ "/^\\[signal /",
+ "go_after_signal", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "go_goroutine",
+ "/^goroutine \\d+ \\[[^\\]]+\\]:$/",
+ "go_frame_1", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "go_frame_1",
+ "/^(?:[^\\s.:]+\\.)*[^\\s.():]+\\(|^created by /",
+ "go_frame_2", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "go_frame_2",
+ "/^\\s/",
+ "go_frame_1", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ /* Map the rules (mandatory for regex rules) */
+ ret = flb_ml_parser_init(mlp);
+ if (ret != 0) {
+ flb_error("[multiline: go] error on mapping rules");
+ flb_ml_parser_destroy(mlp);
+ return NULL;
+ }
+
+ return mlp;
+}
diff --git a/fluent-bit/src/multiline/flb_ml_parser_java.c b/fluent-bit/src/multiline/flb_ml_parser_java.c
new file mode 100644
index 000000000..4df5a00f7
--- /dev/null
+++ b/fluent-bit/src/multiline/flb_ml_parser_java.c
@@ -0,0 +1,143 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/multiline/flb_ml.h>
+#include <fluent-bit/multiline/flb_ml_rule.h>
+#include <fluent-bit/multiline/flb_ml_parser.h>
+
+#define rule flb_ml_rule_create
+
+static void rule_error(struct flb_ml_parser *ml_parser)
+{
+ int id;
+
+ id = mk_list_size(&ml_parser->regex_rules);
+ flb_error("[multiline: java] rule #%i could not be created", id);
+ flb_ml_parser_destroy(ml_parser);
+}
+
+/* Java mode */
+struct flb_ml_parser *flb_ml_parser_java(struct flb_config *config, char *key)
+{
+ int ret;
+ struct flb_ml_parser *mlp;
+
+ mlp = flb_ml_parser_create(config, /* Fluent Bit context */
+ "java", /* name */
+ FLB_ML_REGEX, /* type */
+ NULL, /* match_str */
+ FLB_FALSE, /* negate */
+ FLB_ML_FLUSH_TIMEOUT, /* flush_ms */
+ key, /* key_content */
+ NULL, /* key_group */
+ NULL, /* key_pattern */
+ NULL, /* parser ctx */
+ NULL); /* parser name */
+
+ if (!mlp) {
+ flb_error("[multiline] could not create 'java mode'");
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "start_state, java_start_exception",
+ "/(.)(?:Exception|Error|Throwable|V8 errors stack trace)[:\\r\\n]/",
+ "java_after_exception", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "java_after_exception",
+ "/^[\\t ]*nested exception is:[\\t ]*/",
+ "java_start_exception", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "java_after_exception",
+ "/^[\\r\\n]*$/",
+ "java_after_exception", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "java_after_exception, java",
+ "/^[\\t ]+(?:eval )?at /",
+ "java", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "java_after_exception, java",
+ /* C# nested exception */
+ "/^[\\t ]+--- End of inner exception stack trace ---$/",
+ "java", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "java_after_exception, java",
+ /* C# exception from async code */
+ "/^--- End of stack trace from previous (?x:"
+ ")location where exception was thrown ---$/",
+ "java", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "java_after_exception, java",
+ "/^[\\t ]*(?:Caused by|Suppressed):/",
+ "java_after_exception", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "java_after_exception, java",
+ "/^[\\t ]*... \\d+ (?:more|common frames omitted)/",
+ "java", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ /* Map the rules (mandatory for regex rules) */
+ ret = flb_ml_parser_init(mlp);
+ if (ret != 0) {
+ flb_error("[multiline: java] error on mapping rules");
+ flb_ml_parser_destroy(mlp);
+ return NULL;
+ }
+
+ return mlp;
+}
diff --git a/fluent-bit/src/multiline/flb_ml_parser_python.c b/fluent-bit/src/multiline/flb_ml_parser_python.c
new file mode 100644
index 000000000..a92088397
--- /dev/null
+++ b/fluent-bit/src/multiline/flb_ml_parser_python.c
@@ -0,0 +1,98 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/multiline/flb_ml.h>
+#include <fluent-bit/multiline/flb_ml_rule.h>
+#include <fluent-bit/multiline/flb_ml_parser.h>
+
+#define rule flb_ml_rule_create
+
+static void rule_error(struct flb_ml_parser *mlp)
+{
+ int id;
+
+ id = mk_list_size(&mlp->regex_rules);
+ flb_error("[multiline: python] rule #%i could not be created", id);
+ flb_ml_parser_destroy(mlp);
+}
+
+/* Python */
+struct flb_ml_parser *flb_ml_parser_python(struct flb_config *config, char *key)
+{
+ int ret;
+ struct flb_ml_parser *mlp;
+
+ mlp = flb_ml_parser_create(config, /* Fluent Bit context */
+ "python", /* name */
+ FLB_ML_REGEX, /* type */
+ NULL, /* match_str */
+ FLB_FALSE, /* negate */
+ FLB_ML_FLUSH_TIMEOUT, /* flush_ms */
+ key, /* key_content */
+ NULL, /* key_group */
+ NULL, /* key_pattern */
+ NULL, /* parser ctx */
+ NULL); /* parser name */
+
+ if (!mlp) {
+ flb_error("[multiline] could not create 'python mode'");
+ return NULL;
+ }
+
+ /* rule(:start_state, /^Traceback \(most recent call last\):$/, :python) */
+ ret = rule(mlp,
+ "start_state", "/^Traceback \\(most recent call last\\):$/",
+ "python", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ /* rule(:python, /^[\t ]+File /, :python_code) */
+ ret = rule(mlp, "python", "/^[\\t ]+File /", "python_code", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ /* rule(:python_code, /[^\t ]/, :python) */
+ ret = rule(mlp, "python_code", "/[^\\t ]/", "python", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ /* rule(:python, /^(?:[^\s.():]+\.)*[^\s.():]+:/, :start_state) */
+ ret = rule(mlp, "python", "/^(?:[^\\s.():]+\\.)*[^\\s.():]+:/", "start_state", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ /* Map the rules (mandatory for regex rules) */
+ ret = flb_ml_parser_init(mlp);
+ if (ret != 0) {
+ flb_error("[multiline: python] error on mapping rules");
+ flb_ml_parser_destroy(mlp);
+ return NULL;
+ }
+
+ return mlp;
+}
diff --git a/fluent-bit/src/multiline/flb_ml_parser_ruby.c b/fluent-bit/src/multiline/flb_ml_parser_ruby.c
new file mode 100644
index 000000000..780f829d0
--- /dev/null
+++ b/fluent-bit/src/multiline/flb_ml_parser_ruby.c
@@ -0,0 +1,87 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/multiline/flb_ml.h>
+#include <fluent-bit/multiline/flb_ml_rule.h>
+#include <fluent-bit/multiline/flb_ml_parser.h>
+
+#define rule flb_ml_rule_create
+
+static void rule_error(struct flb_ml_parser *mlp)
+{
+ int id;
+
+ id = mk_list_size(&mlp->regex_rules);
+ flb_error("[multiline: ruby] rule #%i could not be created", id);
+ flb_ml_parser_destroy(mlp);
+}
+
+/* Ruby mode */
+struct flb_ml_parser *flb_ml_parser_ruby(struct flb_config *config, char *key)
+{
+ int ret;
+ struct flb_ml_parser *mlp;
+
+ mlp = flb_ml_parser_create(config, /* Fluent Bit context */
+ "ruby", /* name */
+ FLB_ML_REGEX, /* type */
+ NULL, /* match_str */
+ FLB_FALSE, /* negate */
+ FLB_ML_FLUSH_TIMEOUT, /* flush_ms */
+ key, /* key_content */
+ NULL, /* key_group */
+ NULL, /* key_pattern */
+ NULL, /* parser ctx */
+ NULL); /* parser name */
+
+ if (!mlp) {
+ flb_error("[multiline] could not create 'ruby mode'");
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "start_state, ruby_start_exception",
+ "/^.+:\\d+:in\\s+.*/",
+ "ruby_after_exception", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+ ret = rule(mlp,
+ "ruby_after_exception, ruby",
+ "/^\\s+from\\s+.*:\\d+:in\\s+.*/",
+ "ruby", NULL);
+ if (ret != 0) {
+ rule_error(mlp);
+ return NULL;
+ }
+
+
+ /* Map the rules (mandatory for regex rules) */
+ ret = flb_ml_parser_init(mlp);
+ if (ret != 0) {
+ flb_error("[multiline: ruby] error on mapping rules");
+ flb_ml_parser_destroy(mlp);
+ return NULL;
+ }
+
+ return mlp;
+}
diff --git a/fluent-bit/src/multiline/flb_ml_rule.c b/fluent-bit/src/multiline/flb_ml_rule.c
new file mode 100644
index 000000000..26520dfed
--- /dev/null
+++ b/fluent-bit/src/multiline/flb_ml_rule.c
@@ -0,0 +1,421 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_regex.h>
+#include <fluent-bit/flb_slist.h>
+
+#include <fluent-bit/multiline/flb_ml.h>
+#include <fluent-bit/multiline/flb_ml_rule.h>
+
+struct to_state {
+ struct flb_ml_rule *rule;
+ struct mk_list _head;
+};
+
+struct flb_slist_entry *get_start_state(struct mk_list *list)
+{
+ struct mk_list *head;
+ struct flb_slist_entry *e;
+
+ mk_list_foreach(head, list) {
+ e = mk_list_entry(head, struct flb_slist_entry, _head);
+ if (strcmp(e->str, "start_state") == 0) {
+ return e;
+ }
+ }
+
+ return NULL;
+}
+
+int flb_ml_rule_create(struct flb_ml_parser *ml_parser,
+ flb_sds_t from_states,
+ char *regex_pattern,
+ flb_sds_t to_state,
+ char *end_pattern)
+{
+ int ret;
+ int first_rule = FLB_FALSE;
+ struct flb_ml_rule *rule;
+
+ rule = flb_calloc(1, sizeof(struct flb_ml_rule));
+ if (!rule) {
+ flb_errno();
+ return -1;
+ }
+ flb_slist_create(&rule->from_states);
+ mk_list_init(&rule->to_state_map);
+
+ if (mk_list_size(&ml_parser->regex_rules) == 0) {
+ first_rule = FLB_TRUE;
+ }
+ mk_list_add(&rule->_head, &ml_parser->regex_rules);
+
+ /* from_states */
+ ret = flb_slist_split_string(&rule->from_states, from_states, ',', -1);
+ if (ret <= 0) {
+ flb_error("[multiline] rule is empty or has invalid 'from_states' tokens");
+ flb_ml_rule_destroy(rule);
+ return -1;
+ }
+
+ /* Check if the rule contains a 'start_state' */
+ if (get_start_state(&rule->from_states)) {
+ rule->start_state = FLB_TRUE;
+ }
+ else if (first_rule) {
+ flb_error("[multiline] rule don't contain a 'start_state'");
+ flb_ml_rule_destroy(rule);
+ return -1;
+ }
+
+ /* regex content pattern */
+ rule->regex = flb_regex_create(regex_pattern);
+ if (!rule->regex) {
+ flb_ml_rule_destroy(rule);
+ return -1;
+ }
+
+ /* to_state */
+ if (to_state) {
+ rule->to_state = flb_sds_create(to_state);
+ if (!rule->to_state) {
+ flb_ml_rule_destroy(rule);
+ return -1;
+ }
+ }
+
+ /* regex end pattern */
+ if (end_pattern) {
+ rule->regex_end = flb_regex_create(end_pattern);
+ if (!rule->regex_end) {
+ flb_ml_rule_destroy(rule);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void flb_ml_rule_destroy(struct flb_ml_rule *rule)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct to_state *st;
+
+ flb_slist_destroy(&rule->from_states);
+
+ if (rule->regex) {
+ flb_regex_destroy(rule->regex);
+ }
+
+
+ if (rule->to_state) {
+ flb_sds_destroy(rule->to_state);
+ }
+
+ mk_list_foreach_safe(head, tmp, &rule->to_state_map) {
+ st = mk_list_entry(head, struct to_state, _head);
+ mk_list_del(&st->_head);
+ flb_free(st);
+ }
+
+ if (rule->regex_end) {
+ flb_regex_destroy(rule->regex_end);
+ }
+
+ mk_list_del(&rule->_head);
+ flb_free(rule);
+}
+
+void flb_ml_rule_destroy_all(struct flb_ml_parser *ml_parser)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_ml_rule *rule;
+
+ mk_list_foreach_safe(head, tmp, &ml_parser->regex_rules) {
+ rule = mk_list_entry(head, struct flb_ml_rule, _head);
+ flb_ml_rule_destroy(rule);
+ }
+}
+
+static inline int to_states_exists(struct flb_ml_parser *ml_parser,
+ flb_sds_t state)
+{
+ struct mk_list *head;
+ struct mk_list *r_head;
+ struct flb_ml_rule *rule;
+ struct flb_slist_entry *e;
+
+ mk_list_foreach(head, &ml_parser->regex_rules) {
+ rule = mk_list_entry(head, struct flb_ml_rule, _head);
+
+ mk_list_foreach(r_head, &rule->from_states) {
+ e = mk_list_entry(r_head, struct flb_slist_entry, _head);
+ if (strcmp(e->str, state) == 0) {
+ return FLB_TRUE;
+ }
+ }
+ }
+
+ return FLB_FALSE;
+}
+
+static inline int to_states_matches_rule(struct flb_ml_rule *rule,
+ flb_sds_t state)
+{
+ struct mk_list *head;
+ struct flb_slist_entry *e;
+
+ mk_list_foreach(head, &rule->from_states) {
+ e = mk_list_entry(head, struct flb_slist_entry, _head);
+ if (strcmp(e->str, state) == 0) {
+ return FLB_TRUE;
+ }
+ }
+
+ return FLB_FALSE;
+}
+
+static int set_to_state_map(struct flb_ml_parser *ml_parser,
+ struct flb_ml_rule *rule)
+{
+ int ret;
+ struct to_state *s;
+ struct mk_list *head;
+ struct flb_ml_rule *r;
+
+ if (!rule->to_state) {
+ /* no to_state */
+ return 0;
+ }
+
+ /* Iterate all rules that matches the to_state */
+ mk_list_foreach(head, &ml_parser->regex_rules) {
+ r = mk_list_entry(head, struct flb_ml_rule, _head);
+
+ /* Check if rule->to_state, matches an existing (registered) from_state */
+ ret = to_states_exists(ml_parser, rule->to_state);
+ if (!ret) {
+ flb_error("[multiline parser: %s] to_state='%s' is not registered",
+ ml_parser->name, rule->to_state);
+ return -1;
+ }
+
+ /*
+ * A rule can have many 'from_states', check if the current 'rule->to_state'
+ * matches any 'r->from_states'
+ */
+ ret = to_states_matches_rule(r, rule->to_state);
+ if (!ret) {
+ continue;
+ }
+
+ /* We have a match. Create a 'to_state' entry into the 'to_state_map' list */
+ s = flb_malloc(sizeof(struct to_state));
+ if (!s) {
+ flb_errno();
+ return -1;
+ }
+ s->rule = r;
+ mk_list_add(&s->_head, &rule->to_state_map);
+ }
+
+ return 0;
+}
+
+static int try_flushing_buffer(struct flb_ml_parser *ml_parser,
+ struct flb_ml_stream *mst,
+ struct flb_ml_stream_group *group)
+{
+ int next_start = FLB_FALSE;
+ struct mk_list *head;
+ struct to_state *st;
+ struct flb_ml_rule *rule;
+
+ rule = group->rule_to_state;
+ if (!rule) {
+ if (flb_sds_len(group->buf) > 0) {
+ flb_ml_flush_stream_group(ml_parser, mst, group, FLB_FALSE);
+ group->first_line = FLB_TRUE;
+ }
+ return 0;
+ }
+
+ /* Check if any 'to_state_map' referenced rules is a possible start */
+ mk_list_foreach(head, &rule->to_state_map) {
+ st = mk_list_entry(head, struct to_state, _head);
+ if (st->rule->start_state) {
+ next_start = FLB_TRUE;
+ break;
+ }
+ }
+
+ if (next_start && flb_sds_len(group->buf) > 0) {
+ flb_ml_flush_stream_group(ml_parser, mst, group, FLB_FALSE);
+ group->first_line = FLB_TRUE;
+ }
+
+ return 0;
+}
+
+/* Initialize all rules */
+int flb_ml_rule_init(struct flb_ml_parser *ml_parser)
+{
+ int ret;
+ struct mk_list *head;
+ struct flb_ml_rule *rule;
+
+ /* FIXME: sort rules by start_state first (let's trust in the caller) */
+
+ /* For each rule, compose it to_state_map list */
+ mk_list_foreach(head, &ml_parser->regex_rules) {
+ rule = mk_list_entry(head, struct flb_ml_rule, _head);
+ /* Populate 'rule->to_state_map' list */
+ ret = set_to_state_map(ml_parser, rule);
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* Search any 'start_state' matching the incoming 'buf_data' */
+static struct flb_ml_rule *try_start_state(struct flb_ml_parser *ml_parser,
+ char *buf_data, size_t buf_size)
+{
+ int ret = -1;
+ struct mk_list *head;
+ struct flb_ml_rule *rule = NULL;
+
+ mk_list_foreach(head, &ml_parser->regex_rules) {
+ rule = mk_list_entry(head, struct flb_ml_rule, _head);
+
+ /* Is this rule matching a start_state ? */
+ if (!rule->start_state) {
+ rule = NULL;
+ continue;
+ }
+
+ /* Matched a start_state. Check if we have a regex match */
+ ret = flb_regex_match(rule->regex, (unsigned char *) buf_data, buf_size);
+ if (ret) {
+ return rule;
+ }
+ }
+
+ return NULL;
+}
+
+int flb_ml_rule_process(struct flb_ml_parser *ml_parser,
+ struct flb_ml_stream *mst,
+ struct flb_ml_stream_group *group,
+ msgpack_object *full_map,
+ void *buf, size_t size, struct flb_time *tm,
+ msgpack_object *val_content,
+ msgpack_object *val_pattern)
+{
+ int ret;
+ int len;
+ char *buf_data = NULL;
+ size_t buf_size = 0;
+ struct mk_list *head;
+ struct to_state *st = NULL;
+ struct flb_ml_rule *rule = NULL;
+ struct flb_ml_rule *tmp_rule = NULL;
+
+ if (val_content) {
+ buf_data = (char *) val_content->via.str.ptr;
+ buf_size = val_content->via.str.size;
+ }
+ else {
+ buf_data = buf;
+ buf_size = size;
+ }
+
+ if (group->rule_to_state) {
+ /* are we in a continuation ? */
+ tmp_rule = group->rule_to_state;
+
+ /* Lookup all possible next rules by state reference */
+ rule = NULL;
+ mk_list_foreach(head, &tmp_rule->to_state_map) {
+ st = mk_list_entry(head, struct to_state, _head);
+
+ /* skip start states */
+ if (st->rule->start_state) {
+ continue;
+ }
+
+ /* Try regex match */
+ ret = flb_regex_match(st->rule->regex,
+ (unsigned char *) buf_data, buf_size);
+ if (ret) {
+ /* Regex matched */
+ len = flb_sds_len(group->buf);
+ if (len >= 1 && group->buf[len - 1] != '\n') {
+ flb_sds_cat_safe(&group->buf, "\n", 1);
+ }
+
+ if (buf_size == 0) {
+ flb_sds_cat_safe(&group->buf, "\n", 1);
+ }
+ else {
+ flb_sds_cat_safe(&group->buf, buf_data, buf_size);
+ }
+ rule = st->rule;
+ break;
+ }
+ rule = NULL;
+ }
+
+ }
+
+ if (!rule) {
+ /* Check if we are in a 'start_state' */
+ rule = try_start_state(ml_parser, buf_data, buf_size);
+ if (rule) {
+ /* if the group buffer has any previous data just flush it */
+ if (flb_sds_len(group->buf) > 0) {
+ flb_ml_flush_stream_group(ml_parser, mst, group, FLB_FALSE);
+ }
+
+ /* set the rule state */
+ group->rule_to_state = rule;
+
+ /* concatenate the data */
+ flb_sds_cat_safe(&group->buf, buf_data, buf_size);
+
+ /* Copy full map content in stream buffer */
+ flb_ml_register_context(group, tm, full_map);
+
+ return 0;
+ }
+ }
+
+ if (rule) {
+ group->rule_to_state = rule;
+ try_flushing_buffer(ml_parser, mst, group);
+ return 0;
+ }
+
+ return -1;
+}
diff --git a/fluent-bit/src/multiline/flb_ml_stream.c b/fluent-bit/src/multiline/flb_ml_stream.c
new file mode 100644
index 000000000..c92ba3220
--- /dev/null
+++ b/fluent-bit/src/multiline/flb_ml_stream.c
@@ -0,0 +1,338 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/multiline/flb_ml.h>
+#include <fluent-bit/multiline/flb_ml_rule.h>
+#include <cfl/cfl.h>
+
+static int ml_flush_stdout(struct flb_ml_parser *parser,
+ struct flb_ml_stream *mst,
+ void *data, char *buf_data, size_t buf_size)
+{
+ fprintf(stdout, "\n%s----- MULTILINE FLUSH -----%s\n",
+ ANSI_GREEN, ANSI_RESET);
+
+ /* Print incoming flush buffer */
+ flb_pack_print(buf_data, buf_size);
+
+ fprintf(stdout, "%s----------- EOF -----------%s\n",
+ ANSI_GREEN, ANSI_RESET);
+ return 0;
+}
+
+static struct flb_ml_stream_group *stream_group_create(struct flb_ml_stream *mst,
+ char *name, int len)
+{
+ struct flb_ml_stream_group *group;
+
+ if (!name) {
+ name = "_default";
+ }
+
+ group = flb_calloc(1, sizeof(struct flb_ml_stream_group));
+ if (!group) {
+ flb_errno();
+ return NULL;
+ }
+ group->name = flb_sds_create_len(name, len);
+ if (!group->name) {
+ flb_free(group);
+ return NULL;
+ }
+
+ /* status */
+ group->first_line = FLB_TRUE;
+
+ /* multiline buffer */
+ group->buf = flb_sds_create_size(FLB_ML_BUF_SIZE);
+ if (!group->buf) {
+ flb_error("cannot allocate multiline stream buffer in group %s", name);
+ flb_sds_destroy(group->name);
+ flb_free(group);
+ return NULL;
+ }
+
+ /* msgpack buffer */
+ msgpack_sbuffer_init(&group->mp_md_sbuf);
+ msgpack_packer_init(&group->mp_md_pck, &group->mp_md_sbuf, msgpack_sbuffer_write);
+
+ msgpack_sbuffer_init(&group->mp_sbuf);
+ msgpack_packer_init(&group->mp_pck, &group->mp_sbuf, msgpack_sbuffer_write);
+
+ mk_list_add(&group->_head, &mst->groups);
+
+ return group;
+}
+
+struct flb_ml_stream_group *flb_ml_stream_group_get(struct flb_ml_parser_ins *parser_i,
+ struct flb_ml_stream *mst,
+ msgpack_object *group_name)
+{
+ int len;
+ char *name;
+ struct flb_ml_parser *mlp;
+ struct mk_list *head;
+ struct flb_ml_stream_group *group = NULL;
+
+ mlp = parser_i->ml_parser;
+
+ /* If key_group was not defined, we already have a default group */
+ if (!mlp->key_group || !group_name) {
+ group = mk_list_entry_first(&mst->groups,
+ struct flb_ml_stream_group,
+ _head);
+ return group;
+ }
+
+ /* Lookup for a candidate group */
+ len = group_name->via.str.size;
+ name = (char *)group_name->via.str.ptr;
+
+ mk_list_foreach(head, &mst->groups) {
+ group = mk_list_entry(head, struct flb_ml_stream_group, _head);
+ if (flb_sds_cmp(group->name, name, len) == 0) {
+ return group;
+ }
+ else {
+ group = NULL;
+ continue;
+ }
+ }
+
+ /* No group has been found, create a new one */
+ if (mk_list_size(&mst->groups) >= FLB_ML_MAX_GROUPS) {
+ flb_error("[multiline] stream %s exceeded number of allowed groups (%i)",
+ mst->name, FLB_ML_MAX_GROUPS);
+ return NULL;
+ }
+
+ group = stream_group_create(mst, name, len);
+ return group;
+}
+
+static void stream_group_destroy(struct flb_ml_stream_group *group)
+{
+ if (group->name) {
+ flb_sds_destroy(group->name);
+ }
+ if (group->buf) {
+ flb_sds_destroy(group->buf);
+ }
+
+ msgpack_sbuffer_destroy(&group->mp_md_sbuf);
+ msgpack_sbuffer_destroy(&group->mp_sbuf);
+
+ mk_list_del(&group->_head);
+ flb_free(group);
+}
+
+static void stream_group_destroy_all(struct flb_ml_stream *mst)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_ml_stream_group *group;
+
+ mk_list_foreach_safe(head, tmp, &mst->groups) {
+ group = mk_list_entry(head, struct flb_ml_stream_group, _head);
+ stream_group_destroy(group);
+ }
+}
+
+static int stream_group_init(struct flb_ml_stream *stream)
+{
+ struct flb_ml_stream_group *group = NULL;
+
+ mk_list_init(&stream->groups);
+
+ /* create a default group */
+ group = stream_group_create(stream, NULL, 0);
+ if (!group) {
+ flb_error("[multiline] error initializing default group for "
+ "stream '%s'", stream->name);
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct flb_ml_stream *stream_create(struct flb_ml *ml,
+ uint64_t id,
+ struct flb_ml_parser_ins *parser,
+ int (*cb_flush) (struct flb_ml_parser *,
+ struct flb_ml_stream *,
+ void *cb_data,
+ char *buf_data,
+ size_t buf_size),
+ void *cb_data)
+{
+ int ret;
+ struct flb_ml_stream *stream;
+
+ stream = flb_calloc(1, sizeof(struct flb_ml_stream));
+ if (!stream) {
+ flb_errno();
+ return NULL;
+ }
+ stream->ml = ml;
+ stream->id = id;
+ stream->parser = parser;
+
+ /* Flush Callback and opaque data type */
+ if (cb_flush) {
+ stream->cb_flush = cb_flush;
+ }
+ else {
+ stream->cb_flush = ml_flush_stdout;
+ }
+ stream->cb_data = cb_data;
+
+ ret = stream_group_init(stream);
+ if (ret != 0) {
+ flb_free(stream);
+ return NULL;
+ }
+
+ mk_list_add(&stream->_head, &parser->streams);
+ return stream;
+}
+
+int flb_ml_stream_create(struct flb_ml *ml,
+ char *name,
+ int name_len,
+ int (*cb_flush) (struct flb_ml_parser *,
+ struct flb_ml_stream *,
+ void *cb_data,
+ char *buf_data,
+ size_t buf_size),
+ void *cb_data,
+ uint64_t *stream_id)
+{
+ uint64_t id;
+ struct mk_list *head;
+ struct mk_list *head_group;
+ struct flb_ml_stream *mst;
+ struct flb_ml_group *group;
+ struct flb_ml_parser_ins *parser;
+
+ if (!name) {
+ return -1;
+ }
+
+ if (name_len <= 0) {
+ name_len = strlen(name);
+ }
+
+ /* Set the stream id by creating a hash using the name */
+ id = cfl_hash_64bits(name, name_len);
+
+ /* For every group and parser, create a stream for this stream_id/hash */
+ mk_list_foreach(head, &ml->groups) {
+ group = mk_list_entry(head, struct flb_ml_group, _head);
+ mk_list_foreach(head_group, &group->parsers) {
+ parser = mk_list_entry(head_group, struct flb_ml_parser_ins, _head);
+
+ /* Check if the stream already exists on the parser */
+ if (flb_ml_stream_get(parser, id) != NULL) {
+ continue;
+ }
+
+ /* Create the stream */
+ mst = stream_create(ml, id, parser, cb_flush, cb_data);
+ if (!mst) {
+ flb_error("[multiline] could not create stream_id=%" PRIu64
+ "for stream '%s' on parser '%s'",
+ *stream_id, name, parser->ml_parser->name);
+ return -1;
+ }
+ }
+ }
+
+ *stream_id = id;
+ return 0;
+}
+
+struct flb_ml_stream *flb_ml_stream_get(struct flb_ml_parser_ins *parser,
+ uint64_t stream_id)
+{
+ struct mk_list *head;
+ struct flb_ml_stream *mst = NULL;
+
+ mk_list_foreach(head, &parser->streams) {
+ mst = mk_list_entry(head, struct flb_ml_stream, _head);
+ if (mst->id == stream_id) {
+ return mst;
+ }
+ }
+
+ return NULL;
+}
+
+void flb_ml_stream_id_destroy_all(struct flb_ml *ml, uint64_t stream_id)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct mk_list *head_group;
+ struct mk_list *head_stream;
+ struct flb_ml_group *group;
+ struct flb_ml_stream *mst;
+ struct flb_ml_parser_ins *parser_i;
+
+ /* groups */
+ mk_list_foreach(head, &ml->groups) {
+ group = mk_list_entry(head, struct flb_ml_group, _head);
+
+ /* parser instances */
+ mk_list_foreach(head_group, &group->parsers) {
+ parser_i = mk_list_entry(head_group, struct flb_ml_parser_ins, _head);
+
+ /* streams */
+ mk_list_foreach_safe(head_stream, tmp, &parser_i->streams) {
+ mst = mk_list_entry(head_stream, struct flb_ml_stream, _head);
+ if (mst->id != stream_id) {
+ continue;
+ }
+
+ /* flush any pending data */
+ flb_ml_flush_parser_instance(ml, parser_i, stream_id, FLB_TRUE);
+
+ /* destroy internal groups of the stream */
+ flb_ml_stream_destroy(mst);
+ }
+ }
+ }
+}
+
+int flb_ml_stream_destroy(struct flb_ml_stream *mst)
+{
+ mk_list_del(&mst->_head);
+ if (mst->name) {
+ flb_sds_destroy(mst->name);
+ }
+
+ /* destroy groups */
+ stream_group_destroy_all(mst);
+
+ flb_free(mst);
+
+ return 0;
+}
diff --git a/fluent-bit/src/proxy/CMakeLists.txt b/fluent-bit/src/proxy/CMakeLists.txt
new file mode 100644
index 000000000..2e07e7681
--- /dev/null
+++ b/fluent-bit/src/proxy/CMakeLists.txt
@@ -0,0 +1,3 @@
+if(FLB_PROXY_GO)
+ add_subdirectory(go)
+endif()
diff --git a/fluent-bit/src/proxy/go/CMakeLists.txt b/fluent-bit/src/proxy/go/CMakeLists.txt
new file mode 100644
index 000000000..93e500353
--- /dev/null
+++ b/fluent-bit/src/proxy/go/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(src
+ go.c)
+
+add_library(flb-plugin-proxy-go STATIC ${src})
+if(FLB_JEMALLOC)
+ target_link_libraries(flb-plugin-proxy-go libjemalloc)
+endif()
+if(FLB_REGEX)
+ target_link_libraries(flb-plugin-proxy-go onigmo-static)
+endif()
diff --git a/fluent-bit/src/proxy/go/go.c b/fluent-bit/src/proxy/go/go.c
new file mode 100644
index 000000000..9d95a88af
--- /dev/null
+++ b/fluent-bit/src/proxy/go/go.c
@@ -0,0 +1,289 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_plugin_proxy.h>
+#include <fluent-bit/flb_output.h>
+#include "./go.h"
+
+/*
+ * These functions needs to be moved to a better place, still in
+ * experimental mode.
+ *
+ * ------------------------start------------------------------------------------
+ */
+
+/*
+ * Go Plugin phases
+ * ================
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 1. FLBPluginRegister(context)
+ * 2. Inside FLBPluginRegister, it needs to register it self using Fluent Bit API
+ * where it basically set:
+ *
+ * - name: shortname of the plugin.
+ * - description: plugin description.
+ * - type: input, output, filter, whatever.
+ * - proxy: type of proxy e.g. GOLANG
+ * - flags: optional flags, not used by Go plugins at the moment.
+ *
+ * this is done through Go Wrapper:
+ *
+ * output.FLBPluginRegister(ctx, name, description, type, flags);
+ *
+ * 3. Plugin Initialization
+ */
+/*------------------------EOF------------------------------------------------*/
+
+int proxy_go_output_register(struct flb_plugin_proxy *proxy,
+ struct flb_plugin_proxy_def *def)
+{
+ struct flbgo_output_plugin *plugin;
+
+ plugin = flb_malloc(sizeof(struct flbgo_output_plugin));
+ if (!plugin) {
+ return -1;
+ }
+
+ /*
+ * Lookup the entry point function:
+ *
+ * - FLBPluginInit
+ * - FLBPluginFlush
+ * - FLBPluginFlushCtx
+ * - FLBPluginExit
+ *
+ * note: registration callback FLBPluginRegister() is resolved by the
+ * parent proxy interface.
+ */
+
+ plugin->cb_init = flb_plugin_proxy_symbol(proxy, "FLBPluginInit");
+ if (!plugin->cb_init) {
+ flb_error("[go proxy]: could not load FLBPluginInit symbol");
+ flb_free(plugin);
+ return -1;
+ }
+
+ plugin->cb_flush = flb_plugin_proxy_symbol(proxy, "FLBPluginFlush");
+ plugin->cb_flush_ctx = flb_plugin_proxy_symbol(proxy, "FLBPluginFlushCtx");
+ plugin->cb_exit = flb_plugin_proxy_symbol(proxy, "FLBPluginExit");
+ plugin->cb_exit_ctx = flb_plugin_proxy_symbol(proxy, "FLBPluginExitCtx");
+ plugin->name = flb_strdup(def->name);
+
+ /* This Go plugin context is an opaque data for the parent proxy */
+ proxy->data = plugin;
+
+ return 0;
+}
+
+int proxy_go_output_init(struct flb_plugin_proxy *proxy)
+{
+ int ret;
+ struct flbgo_output_plugin *plugin = proxy->data;
+
+ /* set the API */
+ plugin->api = proxy->api;
+ plugin->o_ins = proxy->instance;
+ // In order to avoid having the whole instance as part of the ABI we
+ // copy the context pointer into the plugin.
+ plugin->context = ((struct flb_output_instance *)proxy->instance)->context;
+
+ ret = plugin->cb_init(plugin);
+ if (ret <= 0) {
+ flb_error("[go proxy]: plugin '%s' failed to initialize",
+ plugin->name);
+ flb_free(plugin);
+ return -1;
+ }
+
+ return ret;
+}
+
+int proxy_go_output_flush(struct flb_plugin_proxy_context *ctx,
+ const void *data, size_t size,
+ const char *tag, int tag_len)
+{
+ int ret;
+ char *buf;
+ struct flbgo_output_plugin *plugin = ctx->proxy->data;
+
+ /* temporary buffer for the tag */
+ buf = flb_malloc(tag_len + 1);
+ if (!buf) {
+ flb_errno();
+ return -1;
+ }
+
+ memcpy(buf, tag, tag_len);
+ buf[tag_len] = '\0';
+
+ if (plugin->cb_flush_ctx) {
+ ret = plugin->cb_flush_ctx(ctx->remote_context, data, size, buf);
+ }
+ else {
+ ret = plugin->cb_flush(data, size, buf);
+ }
+ flb_free(buf);
+ return ret;
+}
+
+int proxy_go_output_destroy(struct flb_plugin_proxy_context *ctx)
+{
+ int ret = 0;
+ struct flbgo_output_plugin *plugin;
+
+ plugin = (struct flbgo_output_plugin *) ctx->proxy->data;
+ flb_debug("[GO] running exit callback");
+
+ if (plugin->cb_exit_ctx) {
+ ret = plugin->cb_exit_ctx(ctx->remote_context);
+ }
+ else if (plugin->cb_exit) {
+ ret = plugin->cb_exit();
+ }
+ return ret;
+}
+
+void proxy_go_output_unregister(void *data) {
+ struct flbgo_output_plugin *plugin;
+
+ plugin = (struct flbgo_output_plugin *) data;
+ flb_free(plugin->name);
+ flb_free(plugin);
+}
+
+int proxy_go_input_register(struct flb_plugin_proxy *proxy,
+ struct flb_plugin_proxy_def *def)
+{
+ struct flbgo_input_plugin *plugin;
+
+ plugin = flb_malloc(sizeof(struct flbgo_input_plugin));
+ if (!plugin) {
+ return -1;
+ }
+
+ /*
+ * Lookup the entry point function:
+ *
+ * - FLBPluginInit
+ * - FLBPluginInputCallback
+ * - FLBPluginExit
+ *
+ * note: registration callback FLBPluginRegister() is resolved by the
+ * parent proxy interface.
+ */
+
+ plugin->cb_init = flb_plugin_proxy_symbol(proxy, "FLBPluginInit");
+ if (!plugin->cb_init) {
+ flb_error("[go proxy]: could not load FLBPluginInit symbol");
+ flb_free(plugin);
+ return -1;
+ }
+
+ plugin->cb_collect = flb_plugin_proxy_symbol(proxy, "FLBPluginInputCallback");
+ plugin->cb_cleanup = flb_plugin_proxy_symbol(proxy, "FLBPluginInputCleanupCallback");
+ plugin->cb_exit = flb_plugin_proxy_symbol(proxy, "FLBPluginExit");
+ plugin->name = flb_strdup(def->name);
+
+ /* This Go plugin context is an opaque data for the parent proxy */
+ proxy->data = plugin;
+
+ return 0;
+}
+
+int proxy_go_input_init(struct flb_plugin_proxy *proxy)
+{
+ int ret;
+ struct flbgo_input_plugin *plugin = proxy->data;
+
+ /* set the API */
+ plugin->api = proxy->api;
+ plugin->i_ins = proxy->instance;
+ // In order to avoid having the whole instance as part of the ABI we
+ // copy the context pointer into the plugin.
+ plugin->context = ((struct flb_input_instance *)proxy->instance)->context;
+
+ ret = plugin->cb_init(plugin);
+ if (ret <= 0) {
+ flb_error("[go proxy]: plugin '%s' failed to initialize",
+ plugin->name);
+ flb_free(plugin);
+ return -1;
+ }
+
+ return ret;
+}
+
+int proxy_go_input_collect(struct flb_plugin_proxy *ctx,
+ void **collected_data, size_t *len)
+{
+ int ret;
+ void *data = NULL;
+ struct flbgo_input_plugin *plugin = ctx->data;
+
+ ret = plugin->cb_collect(&data, len);
+
+ *collected_data = data;
+
+ return ret;
+}
+
+int proxy_go_input_cleanup(struct flb_plugin_proxy *ctx,
+ void *allocated_data)
+{
+ int ret = 0;
+ struct flbgo_input_plugin *plugin = ctx->data;
+
+ if (plugin->cb_cleanup) {
+ ret = plugin->cb_cleanup(allocated_data);
+ }
+ else {
+ /* If cleanup callback is not registered, we need to cleanup
+ * allocated memory on fluent-bit side. */
+ if (allocated_data != NULL) {
+ free(allocated_data);
+ }
+ }
+
+ return ret;
+}
+
+int proxy_go_input_destroy(struct flb_plugin_input_proxy_context *ctx)
+{
+ int ret = 0;
+ struct flbgo_input_plugin *plugin;
+
+ plugin = (struct flbgo_input_plugin *) ctx->proxy->data;
+ flb_debug("[GO] running exit callback");
+
+ if (plugin->cb_exit) {
+ ret = plugin->cb_exit();
+ }
+ return ret;
+}
+
+void proxy_go_input_unregister(void *data) {
+ struct flbgo_input_plugin *plugin;
+
+ plugin = (struct flbgo_input_plugin *) data;
+ flb_free(plugin->name);
+ flb_free(plugin);
+}
diff --git a/fluent-bit/src/proxy/go/go.h b/fluent-bit/src/proxy/go/go.h
new file mode 100644
index 000000000..4c3fedb23
--- /dev/null
+++ b/fluent-bit/src/proxy/go/go.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 FLB_PROXY_GO_H
+#define FLB_PROXY_GO_H
+
+#include <fluent-bit/flb_config.h>
+#include <fluent-bit/flb_plugin_proxy.h>
+
+struct flbgo_output_plugin {
+ char *name;
+ void *api;
+ void *o_ins;
+ struct flb_plugin_proxy_context *context;
+
+ int (*cb_init)();
+ int (*cb_flush)(const void *, size_t, const char *);
+ int (*cb_flush_ctx)(void *, const void *, size_t, char *);
+ int (*cb_exit)();
+ int (*cb_exit_ctx)(void *);
+};
+
+struct flbgo_input_plugin {
+ char *name;
+ void *api;
+ void *i_ins;
+ struct flb_plugin_proxy_context *context;
+
+ int (*cb_init)();
+ int (*cb_collect)(void **, size_t *);
+ int (*cb_cleanup)(void *);
+ int (*cb_exit)();
+};
+
+int proxy_go_output_register(struct flb_plugin_proxy *proxy,
+ struct flb_plugin_proxy_def *def);
+
+int proxy_go_output_init(struct flb_plugin_proxy *proxy);
+
+int proxy_go_output_flush(struct flb_plugin_proxy_context *ctx,
+ const void *data, size_t size,
+ const char *tag, int tag_len);
+int proxy_go_output_destroy(struct flb_plugin_proxy_context *ctx);
+void proxy_go_output_unregister(void *data);
+
+int proxy_go_input_register(struct flb_plugin_proxy *proxy,
+ struct flb_plugin_proxy_def *def);
+
+int proxy_go_input_init(struct flb_plugin_proxy *proxy);
+int proxy_go_input_collect(struct flb_plugin_proxy *ctx,
+ void **collected_data, size_t *len);
+int proxy_go_input_cleanup(struct flb_plugin_proxy *ctx,
+ void *allocated_data);
+int proxy_go_input_destroy(struct flb_plugin_input_proxy_context *ctx);
+void proxy_go_input_unregister(void *data);
+#endif
diff --git a/fluent-bit/src/record_accessor/CMakeLists.txt b/fluent-bit/src/record_accessor/CMakeLists.txt
new file mode 100644
index 000000000..9eb3825d9
--- /dev/null
+++ b/fluent-bit/src/record_accessor/CMakeLists.txt
@@ -0,0 +1,31 @@
+flex_target(lexer ra.l "${CMAKE_CURRENT_BINARY_DIR}/ra_lex.c"
+ DEFINES_FILE "${CMAKE_CURRENT_BINARY_DIR}/ra_lex.h"
+ )
+bison_target(parser ra.y "${CMAKE_CURRENT_BINARY_DIR}/ra_parser.c")
+
+set(sources
+ flb_ra_parser.c
+ )
+
+if(CMAKE_SYSTEM_NAME MATCHES "Windows")
+ FLB_DEFINITION(YY_NO_UNISTD_H)
+ message(STATUS "Specifying YY_NO_UNISTD_H")
+endif()
+
+include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR}
+ )
+
+add_library(flb-ra-parser STATIC
+ ${sources}
+ "${CMAKE_CURRENT_BINARY_DIR}/ra_lex.c"
+ "${CMAKE_CURRENT_BINARY_DIR}/ra_parser.c"
+ )
+
+add_flex_bison_dependency(lexer parser)
+add_dependencies(flb-ra-parser onigmo-static)
+
+if(FLB_JEMALLOC)
+ target_link_libraries(flb-ra-parser libjemalloc)
+endif()
diff --git a/fluent-bit/src/record_accessor/flb_ra_parser.c b/fluent-bit/src/record_accessor/flb_ra_parser.c
new file mode 100644
index 000000000..9f7142950
--- /dev/null
+++ b/fluent-bit/src/record_accessor/flb_ra_parser.c
@@ -0,0 +1,365 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_slist.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/record_accessor/flb_ra_parser.h>
+
+#include "ra_parser.h"
+#include "ra_lex.h"
+
+int flb_ra_parser_subkey_count(struct flb_ra_parser *rp)
+{
+ if (rp == NULL || rp->key == NULL) {
+ return -1;
+ }
+ else if (rp->type != FLB_RA_PARSER_KEYMAP) {
+ return 0;
+ }
+ else if(rp->key->subkeys == NULL) {
+ return -1;
+ }
+
+ return mk_list_size(rp->key->subkeys);
+}
+
+void flb_ra_parser_dump(struct flb_ra_parser *rp)
+{
+ struct mk_list *head;
+ struct flb_ra_key *key;
+ struct flb_ra_subentry *entry;
+
+ key = rp->key;
+ if (rp->type == FLB_RA_PARSER_STRING) {
+ printf("type : STRING\n");
+ printf("string : '%s'\n", key->name);
+ }
+ if (rp->type == FLB_RA_PARSER_REGEX_ID) {
+ printf("type : REGEX_ID\n");
+ printf("integer : '%i'\n", rp->id);
+ }
+ if (rp->type == FLB_RA_PARSER_TAG) {
+ printf("type : TAG\n");
+ }
+ if (rp->type == FLB_RA_PARSER_TAG_PART) {
+ printf("type : TAG[%i]\n", rp->id);
+ }
+ else if (rp->type == FLB_RA_PARSER_KEYMAP) {
+ printf("type : KEYMAP\n");
+ if (rp->key) {
+ printf("key name : %s\n", key->name);
+ mk_list_foreach(head, key->subkeys) {
+ entry = mk_list_entry(head, struct flb_ra_subentry, _head);
+ if (entry->type == FLB_RA_PARSER_STRING) {
+ printf(" - subkey : %s\n", entry->str);
+ }
+ else if (entry->type == FLB_RA_PARSER_ARRAY_ID) {
+ printf(" - array id: %i\n", entry->array_id);
+ }
+ }
+ }
+ }
+}
+
+static void ra_parser_subentry_destroy_all(struct mk_list *list)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_ra_subentry *entry;
+
+ mk_list_foreach_safe(head, tmp, list) {
+ entry = mk_list_entry(head, struct flb_ra_subentry, _head);
+ mk_list_del(&entry->_head);
+ if (entry->type == FLB_RA_PARSER_STRING) {
+ flb_sds_destroy(entry->str);
+ }
+ flb_free(entry);
+ }
+}
+
+int flb_ra_parser_subentry_add_string(struct flb_ra_parser *rp, char *key)
+{
+ struct flb_ra_subentry *entry;
+
+ entry = flb_malloc(sizeof(struct flb_ra_subentry));
+ if (!entry) {
+ flb_errno();
+ return -1;
+ }
+
+ entry->type = FLB_RA_PARSER_STRING;
+ entry->str = flb_sds_create(key);
+ if (!entry->str) {
+ flb_errno();
+ flb_free(entry);
+ return -1;
+ }
+ mk_list_add(&entry->_head, rp->slist);
+
+ return 0;
+}
+
+int flb_ra_parser_subentry_add_array_id(struct flb_ra_parser *rp, int id)
+{
+ struct flb_ra_subentry *entry;
+
+ entry = flb_malloc(sizeof(struct flb_ra_subentry));
+ if (!entry) {
+ flb_errno();
+ return -1;
+ }
+
+ entry->type = FLB_RA_PARSER_ARRAY_ID;
+ entry->array_id = id;
+ mk_list_add(&entry->_head, rp->slist);
+
+ return 0;
+}
+
+
+struct flb_ra_key *flb_ra_parser_key_add(struct flb_ra_parser *rp, char *key)
+{
+ struct flb_ra_key *k;
+
+ k = flb_malloc(sizeof(struct flb_ra_key));
+ if (!k) {
+ flb_errno();
+ return NULL;
+ }
+
+ k->name = flb_sds_create(key);
+ if (!k->name) {
+ flb_errno();
+ flb_free(k);
+ return NULL;
+ }
+ k->subkeys = NULL;
+
+ return k;
+}
+
+struct flb_ra_array *flb_ra_parser_array_add(struct flb_ra_parser *rp, int index)
+{
+ struct flb_ra_array *arr;
+
+ if (index < 0) {
+ return NULL;
+ }
+
+ arr = flb_malloc(sizeof(struct flb_ra_array));
+ if (!arr) {
+ flb_errno();
+ return NULL;
+ }
+
+ arr->index = index;
+ arr->subkeys = NULL;
+
+ return arr;
+}
+
+struct flb_ra_key *flb_ra_parser_string_add(struct flb_ra_parser *rp,
+ char *str, int len)
+{
+ struct flb_ra_key *k;
+
+ k = flb_malloc(sizeof(struct flb_ra_key));
+ if (!k) {
+ flb_errno();
+ return NULL;
+ }
+
+ k->name = flb_sds_create_len(str, len);
+ if (!k->name) {
+ flb_errno();
+ flb_free(k);
+ return NULL;
+ }
+ k->subkeys = NULL;
+
+ return k;
+}
+
+static struct flb_ra_parser *flb_ra_parser_create()
+{
+ struct flb_ra_parser *rp;
+
+ rp = flb_calloc(1, sizeof(struct flb_ra_parser));
+ if (!rp) {
+ flb_errno();
+ return NULL;
+ }
+ rp->type = -1;
+ rp->key = NULL;
+ rp->slist = flb_malloc(sizeof(struct mk_list));
+ if (!rp->slist) {
+ flb_errno();
+ flb_free(rp);
+ return NULL;
+ }
+ mk_list_init(rp->slist);
+
+ return rp;
+}
+
+struct flb_ra_parser *flb_ra_parser_string_create(char *str, int len)
+{
+ struct flb_ra_parser *rp;
+
+ rp = flb_ra_parser_create();
+ if (!rp) {
+ flb_error("[record accessor] could not create string context");
+ return NULL;
+ }
+
+ rp->type = FLB_RA_PARSER_STRING;
+ rp->key = flb_malloc(sizeof(struct flb_ra_key));
+ if (!rp->key) {
+ flb_errno();
+ flb_ra_parser_destroy(rp);
+ return NULL;
+ }
+ rp->key->subkeys = NULL;
+ rp->key->name = flb_sds_create_len(str, len);
+ if (!rp->key->name) {
+ flb_ra_parser_destroy(rp);
+ return NULL;
+ }
+
+ return rp;
+}
+
+struct flb_ra_parser *flb_ra_parser_regex_id_create(int id)
+{
+ struct flb_ra_parser *rp;
+
+ rp = flb_ra_parser_create();
+ if (!rp) {
+ flb_error("[record accessor] could not create string context");
+ return NULL;
+ }
+
+ rp->type = FLB_RA_PARSER_REGEX_ID;
+ rp->id = id;
+ return rp;
+}
+
+struct flb_ra_parser *flb_ra_parser_tag_create()
+{
+ struct flb_ra_parser *rp;
+
+ rp = flb_ra_parser_create();
+ if (!rp) {
+ flb_error("[record accessor] could not create tag context");
+ return NULL;
+ }
+
+ rp->type = FLB_RA_PARSER_TAG;
+ return rp;
+}
+
+struct flb_ra_parser *flb_ra_parser_tag_part_create(int id)
+{
+ struct flb_ra_parser *rp;
+
+ rp = flb_ra_parser_create();
+ if (!rp) {
+ flb_error("[record accessor] could not create tag context");
+ return NULL;
+ }
+
+ rp->type = FLB_RA_PARSER_TAG_PART;
+ rp->id = id;
+
+ return rp;
+}
+
+struct flb_ra_parser *flb_ra_parser_meta_create(char *str, int len)
+{
+ int ret;
+ yyscan_t scanner;
+ YY_BUFFER_STATE buf;
+ flb_sds_t s;
+ struct flb_ra_parser *rp;
+ struct flb_ra_key *key;
+
+ rp = flb_ra_parser_create();
+ if (!rp) {
+ flb_error("[record accessor] could not create meta context");
+ return NULL;
+ }
+
+ /* Temporal buffer of string with fixed length */
+ s = flb_sds_create_len(str, len);
+ if (!s) {
+ flb_errno();
+ flb_ra_parser_destroy(rp);
+ return NULL;
+ }
+
+ /* Flex/Bison work */
+ flb_ra_lex_init(&scanner);
+ buf = flb_ra__scan_string(s, scanner);
+
+ ret = flb_ra_parse(rp, s, scanner);
+
+ /* release resources */
+ flb_sds_destroy(s);
+ flb_ra__delete_buffer(buf, scanner);
+ flb_ra_lex_destroy(scanner);
+
+ /* Finish structure mapping */
+ if (rp->type == FLB_RA_PARSER_KEYMAP) {
+ if (rp->key) {
+ key = rp->key;
+ key->subkeys = rp->slist;
+ rp->slist = NULL;
+ }
+ }
+
+ if (ret != 0) {
+ flb_ra_parser_destroy(rp);
+ return NULL;
+ }
+
+ return rp;
+}
+
+void flb_ra_parser_destroy(struct flb_ra_parser *rp)
+{
+ struct flb_ra_key *key;
+
+ key = rp->key;
+ if (key) {
+ flb_sds_destroy(key->name);
+ if (key->subkeys) {
+ ra_parser_subentry_destroy_all(key->subkeys);
+ flb_free(key->subkeys);
+ }
+ flb_free(rp->key);
+ }
+ if (rp->slist) {
+ ra_parser_subentry_destroy_all(rp->slist);
+ flb_free(rp->slist);
+ }
+ flb_free(rp);
+}
diff --git a/fluent-bit/src/record_accessor/ra.l b/fluent-bit/src/record_accessor/ra.l
new file mode 100644
index 000000000..b35d6bd26
--- /dev/null
+++ b/fluent-bit/src/record_accessor/ra.l
@@ -0,0 +1,69 @@
+%option prefix="flb_ra_"
+%option caseless
+%{
+#include <stdio.h>
+#include <stdbool.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/record_accessor/flb_ra_parser.h>
+
+#include "ra_parser.h"
+
+static inline char *remove_dup_quotes(const char *s, size_t n)
+{
+ char *str;
+ int dups;
+ int i, j;
+
+ dups = 0;
+ for (i = 0; i < n; i++) {
+ if (s[i] == '\'') {
+ dups++;
+ i++;
+ }
+ }
+
+ str = (char *) flb_malloc(n - dups + 1);
+ if (!str) {
+ return NULL;
+ }
+
+ j = 0;
+ for (i = 0; i < n; i++, j++) {
+ if (s[i] == '\'') {
+ str[j] = '\'';
+ i++;
+ } else {
+ str[j] = s[i];
+ }
+ }
+ str[j] = '\0';
+
+ return str;
+}
+
+%}
+
+%option 8bit reentrant bison-bridge
+%option warn noyywrap nodefault
+%option nounput
+%option noinput
+
+%%
+
+[1-9][0-9]*|0 { yylval->integer = atoi(yytext); return INTEGER; }
+\'([^']|'{2})*\' { yylval->string = remove_dup_quotes(yytext + 1, yyleng - 2); return STRING; }
+[_A-Za-z][A-Za-z0-9_.\-/]* { yylval->string = flb_strdup(yytext); return IDENTIFIER; }
+
+"$" |
+"[" |
+"]" |
+"." |
+"," |
+";" { return yytext[0]; }
+\n
+[ \t]+ /* ignore whitespace */;
+
+. flb_error("[record accessor] bad input character '%s' at line %d", yytext, yylineno);
+
+%%
diff --git a/fluent-bit/src/record_accessor/ra.y b/fluent-bit/src/record_accessor/ra.y
new file mode 100644
index 000000000..9c4e25b87
--- /dev/null
+++ b/fluent-bit/src/record_accessor/ra.y
@@ -0,0 +1,99 @@
+%define api.pure full
+%name-prefix "flb_ra_"
+
+%parse-param { struct flb_ra_parser *rp };
+%parse-param { const char *str };
+%lex-param { void *scanner }
+%parse-param { void *scanner }
+
+%{
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_slist.h>
+#include <fluent-bit/record_accessor/flb_ra_parser.h>
+
+#include "ra_parser.h"
+#include "ra_lex.h"
+
+extern int flb_ra_lex();
+
+void flb_ra_error(struct flb_ra_parser *rp, const char *query, void *scanner,
+ const char *str)
+{
+ flb_error("[record accessor] %s at '%s'", str, query);
+}
+
+%} /* EOF C code */
+
+
+/* Known Tokens (refer to sql.l) */
+
+/* Keywords */
+%token IDENTIFIER STRING INTEGER
+
+%define parse.error verbose
+
+/* Union and field types */
+%union
+{
+ int integer;
+ float fval;
+ char *string;
+ struct flb_sp_cmd *cmd;
+ struct flb_exp *expression;
+}
+
+%type <string> IDENTIFIER
+%type <integer> INTEGER
+%type <string> STRING
+%type <string> record_key
+
+%destructor { flb_free ($$); } IDENTIFIER
+%destructor { flb_free ($$); } STRING
+
+%% /* rules section */
+
+statements: record_accessor
+
+/* Parse record accessor string: $key, $key['x'], $key['x'][...] */
+record_accessor: record_key
+ record_key:
+ '$' IDENTIFIER
+ {
+ void *key;
+
+ rp->type = FLB_RA_PARSER_KEYMAP;
+ key = flb_ra_parser_key_add(rp, $2);
+ if (key) {
+ rp->key = key;
+ }
+ flb_free($2);
+ }
+ |
+ '$' IDENTIFIER record_subkey
+ {
+ void *key;
+ rp->type = FLB_RA_PARSER_KEYMAP;
+ key = flb_ra_parser_key_add(rp, $2);
+ if (key) {
+ rp->key = key;
+ }
+ flb_free($2);
+ }
+ record_subkey: record_subkey record_subkey_index | record_subkey_index
+ record_subkey_index:
+ '[' STRING ']'
+ {
+ flb_ra_parser_subentry_add_string(rp, $2);
+ flb_free($2);
+ }
+ |
+ '[' INTEGER ']'
+ {
+ flb_ra_parser_subentry_add_array_id(rp, $2);
+ }
+ ;
diff --git a/fluent-bit/src/stream_processor/CMakeLists.txt b/fluent-bit/src/stream_processor/CMakeLists.txt
new file mode 100644
index 000000000..de2c2fe38
--- /dev/null
+++ b/fluent-bit/src/stream_processor/CMakeLists.txt
@@ -0,0 +1,21 @@
+project(stream-processor C)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+
+add_subdirectory(parser)
+
+set(src
+ flb_sp.c
+ flb_sp_key.c
+ flb_sp_func_time.c
+ flb_sp_func_record.c
+ flb_sp_stream.c
+ flb_sp_snapshot.c
+ flb_sp_window.c
+ flb_sp_groupby.c
+ flb_sp_aggregate_func.c
+ )
+
+add_library(flb-sp STATIC ${src})
+target_link_libraries(flb-sp rbtree)
+target_link_libraries(flb-sp flb-sp-parser)
diff --git a/fluent-bit/src/stream_processor/README.md b/fluent-bit/src/stream_processor/README.md
new file mode 100644
index 000000000..d39a51ef5
--- /dev/null
+++ b/fluent-bit/src/stream_processor/README.md
@@ -0,0 +1,84 @@
+## SQL Statement Syntax
+
+The following is the SQL statement syntax supported by Fluent Bit stream processor in EBNF form. For readability, we assume the conventional definition for integer, float and string values. A single quote in a constant string literal has to be escaped with an extra one. For instance, the string representation of `O'Keefe` in the query will be `'O''Keefe'`.
+
+```xml
+<sql_stmt> := <create> | <select>
+<create> := CREATE STREAM <id> AS <select> | CREATE STREAM <id> WITH (<properties>) AS <select>
+<properties> := <property> | <property>, <properties>
+<property> := <id> = '<id>'
+<select> := SELECT <keys> FROM <source> [WHERE <condition>]
+ [WINDOW TUMBLING (<integer> SECOND) | WINDOW HOPPING (<integer> SECOND, ADVANCE BY <integer> SECOND)]
+ [GROUP BY <record_keys>]
+<keys> := '*' | <record_keys>
+<record_keys> := <record_key> | <record_key>, <record_keys>
+<record_key> := <exp> | <exp> AS <id>
+<exp> := <key> | <fun>
+<fun> := AVG(<key>) | SUM(<key>) | COUNT(<key>) | COUNT(*) | MIN(<key>) | MAX(<key>) | TIMESERIES_FORECAST(<key>, <integer>)
+<source> := STREAM:<id> | TAG:<id>
+<condition> := <key> | <value> | <key> <relation> <value> | (<condition>)
+ | NOT <condition> | <condition> AND <condition> | <condition> OR <condition>
+ | @record.contains(<key>) | <id> IS NULL | <id> IS NOT NULL
+<key> := <id> | <id><subkey-idx>
+<subkey-idx> := [<id>] | <subkey-idx>[<id>]
+<relation> := = | != | <> | < | <= | > | >=
+<id> := <letter> <characters>
+<characters> := <letter> | <digit> | _ | <characters> <characters>
+<value> := true | false | <integer> | <float> | '<string>'
+```
+
+In addition to the common aggregation functions, Stream Processor provides the timeseries function `TIMESERIES_FORECAST`, which uses [simple linear regression algorithm](<https://en.wikipedia.org/wiki/Simple_linear_regression) to predict the value of a (dependent) variable in future.
+
+### Timeseries Functions
+
+| name | description |
+| ------------------------- | ------------------------------------------------------ |
+| TIMESERIES_FORECAST(x, t) | forecasts the value of x at current time + t seconds |
+
+### Time Functions
+
+| name | description | example |
+| ---------------- | ------------------------------------------------- | ------------------- |
+| NOW() | adds system time using format: %Y-%m-%d %H:%M:%S | 2019-03-09 21:36:05 |
+| UNIX_TIMESTAMP() | add current Unix timestamp | 1552196165 |
+
+### Record Functions
+
+| name | description | example |
+| ------------- | ------------------------------------------------------------ | ----------------- |
+| RECORD_TAG() | append Tag string associated to the record | samples |
+| RECORD_TIME() | append record Timestamp in _double_ format: seconds.nanoseconds | 1552196165.705683 |
+
+## Type of windows
+
+FluentBit stream processor has implemented two time-based windows: hopping window and tumbling window.
+
+### Hopping window
+
+In hopping window (also known as sliding window), records are stored in a time window of the interval in seconds defined as the parameter. The `ADVANCE BY` parameter determines the time the window slides forward. Aggregation functions are computed over the records inside a window, and reported right before window moves.
+
+For example. the hopping window `WINDOW HOPPING (10 SECOND, ADVANCE BY 2 SECOND)` behaves like this:
+
+```
+[ x x x x x ... x x x x x ]
+<--------- 10 sec -------->
+ [ x x x x x ... x x x x x ]
+<- 2 sec -><--------- 10 sec -------->
+ [ x x x x x ... x x x x x ]
+ <- 2 sec -><--------- 10 sec -------->
+```
+
+### Tumbling window
+
+A tumbling window is similar to a hopping window where `ADVANCE BY` value is the same as the window size. That means the new window doesn't include any record from the previous one.
+
+For example. the tumbling window `WINDOW TUMBLING (10 SECOND)` works like this:
+
+```
+[ x x x x x ... x x x x x ]
+<--------- 10 sec -------->
+ [ x x x x x ... x x x x x ]
+ <--------- 10 sec -------->
+ [ x x x x x ... x x x x x ]
+ <--------- 10 sec -------->
+```
diff --git a/fluent-bit/src/stream_processor/flb_sp.c b/fluent-bit/src/stream_processor/flb_sp.c
new file mode 100644
index 000000000..00eb2f18b
--- /dev/null
+++ b/fluent-bit/src/stream_processor/flb_sp.c
@@ -0,0 +1,2157 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_slist.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_time.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/flb_router.h>
+#include <fluent-bit/flb_config_format.h>
+#include <fluent-bit/stream_processor/flb_sp.h>
+#include <fluent-bit/stream_processor/flb_sp_key.h>
+#include <fluent-bit/stream_processor/flb_sp_stream.h>
+#include <fluent-bit/stream_processor/flb_sp_snapshot.h>
+#include <fluent-bit/stream_processor/flb_sp_parser.h>
+#include <fluent-bit/stream_processor/flb_sp_func_time.h>
+#include <fluent-bit/stream_processor/flb_sp_func_record.h>
+#include <fluent-bit/stream_processor/flb_sp_aggregate_func.h>
+#include <fluent-bit/stream_processor/flb_sp_window.h>
+#include <fluent-bit/stream_processor/flb_sp_groupby.h>
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+/* don't do this at home */
+#define pack_uint16(buf, d) _msgpack_store16(buf, (uint16_t) d)
+#define pack_uint32(buf, d) _msgpack_store32(buf, (uint32_t) d)
+
+/* String type to numerical conversion */
+#define FLB_STR_INT 1
+#define FLB_STR_FLOAT 2
+
+/* Read and process file system configuration file */
+static int sp_config_file(struct flb_config *config, struct flb_sp *sp,
+ const char *file)
+{
+ int ret;
+ flb_sds_t name;
+ flb_sds_t exec;
+ char *cfg = NULL;
+ char tmp[PATH_MAX + 1];
+ struct stat st;
+ struct mk_list *head;
+ struct flb_sp_task *task;
+ struct flb_cf *cf;
+ struct flb_cf_section *section;
+
+#ifndef FLB_HAVE_STATIC_CONF
+ ret = stat(file, &st);
+ if (ret == -1 && errno == ENOENT) {
+ /* Try to resolve the real path (if exists) */
+ if (file[0] == '/') {
+ flb_error("[sp] cannot open configuration file: %s", file);
+ return -1;
+ }
+
+ if (config->conf_path) {
+ snprintf(tmp, PATH_MAX, "%s%s", config->conf_path, file);
+ cfg = tmp;
+ }
+ }
+ else {
+ cfg = (char *) file;
+ }
+
+ cf = flb_cf_create_from_file(NULL, cfg);
+#else
+ cf = flb_config_static_open(file);
+#endif
+
+ if (!cf) {
+ return -1;
+ }
+
+ /* Read all 'stream_task' sections */
+ mk_list_foreach(head, &cf->sections) {
+ section = mk_list_entry(head, struct flb_cf_section, _head);
+ if (strcasecmp(section->name, "stream_task") != 0) {
+ continue;
+ }
+
+ name = NULL;
+ exec = NULL;
+
+ /* name */
+ name = flb_cf_section_property_get_string(cf, section, "name");
+ if (!name) {
+ flb_error("[sp] task 'name' not found in file '%s'", cfg);
+ goto fconf_error;
+ }
+
+ /* exec */
+ exec = flb_cf_section_property_get_string(cf, section, "exec");
+ if (!exec) {
+ flb_error("[sp] task '%s' don't have an 'exec' command", name);
+ goto fconf_error;
+ }
+
+ /* Register the task */
+ task = flb_sp_task_create(sp, name, exec);
+ if (!task) {
+ goto fconf_error;
+ }
+ flb_sds_destroy(name);
+ flb_sds_destroy(exec);
+ name = NULL;
+ exec = NULL;
+ }
+
+ flb_cf_destroy(cf);
+ return 0;
+
+fconf_error:
+ if (name) {
+ flb_sds_destroy(name);
+ }
+ if (exec) {
+ flb_sds_destroy(exec);
+ }
+ flb_cf_destroy(cf);
+ return -1;
+}
+
+static int sp_task_to_instance(struct flb_sp_task *task, struct flb_sp *sp)
+{
+ struct mk_list *head;
+ struct flb_input_instance *in;
+
+ if (task->cmd->source_type != FLB_SP_STREAM) {
+ return -1;
+ }
+
+ mk_list_foreach(head, &sp->config->inputs) {
+ in = mk_list_entry(head, struct flb_input_instance, _head);
+ if (in->alias) {
+ if (strcasecmp(in->alias, task->cmd->source_name) == 0) {
+ task->source_instance = in;
+ return 0;
+ }
+ }
+
+ if (strcasecmp(in->name, task->cmd->source_name) == 0) {
+ task->source_instance = in;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+static void sp_info(struct flb_sp *sp)
+{
+ struct mk_list *head;
+ struct flb_sp_task *task;
+
+ flb_info("[sp] stream processor started");
+
+ mk_list_foreach(head, &sp->tasks) {
+ task = mk_list_entry(head, struct flb_sp_task, _head);
+ flb_info("[sp] registered task: %s", task->name);
+ }
+}
+
+int subkeys_compare(struct mk_list *subkeys1, struct mk_list *subkeys2)
+{
+ int i;
+ struct flb_slist_entry *entry1;
+ struct flb_slist_entry *entry2;
+
+ if (!subkeys1 && !subkeys2) {
+ return 0;
+ }
+
+ if (!subkeys1 || !subkeys2) {
+ return -1;
+ }
+
+ if (mk_list_size(subkeys1) != mk_list_size(subkeys2)) {
+ return -1;
+ }
+
+ entry1 = mk_list_entry_first(subkeys1, struct flb_slist_entry, _head);
+ entry2 = mk_list_entry_first(subkeys2, struct flb_slist_entry, _head);
+
+ for (i = 0; i < mk_list_size(subkeys1); i++) {
+ if (flb_sds_cmp(entry1->str, entry2->str, flb_sds_len(entry2->str)) != 0) {
+ return -1;
+ }
+
+ entry1 = mk_list_entry_next(&entry1->_head, struct flb_slist_entry,
+ _head, subkeys1);
+ entry2 = mk_list_entry_next(&entry2->_head, struct flb_slist_entry,
+ _head, subkeys2);
+ }
+
+ return 0;
+}
+
+static int sp_cmd_aggregated_keys(struct flb_sp_cmd *cmd)
+{
+ int aggr = 0;
+ int not_aggr = 0;
+ struct mk_list *head;
+ struct mk_list *head_gb;
+ struct flb_sp_cmd_key *key;
+ struct flb_sp_cmd_gb_key *gb_key;
+
+ mk_list_foreach(head, &cmd->keys) {
+ key = mk_list_entry(head, struct flb_sp_cmd_key, _head);
+ if (key->time_func > 0 || key->record_func > 0) {
+ continue;
+ }
+
+ if (key->aggr_func > 0) {
+ /* AVG, SUM, COUNT or timeseries functions */
+ aggr++;
+ }
+ else {
+ mk_list_foreach(head_gb, &cmd->gb_keys) {
+ gb_key = mk_list_entry(head_gb, struct flb_sp_cmd_gb_key, _head);
+
+ if (!key->name) { /* Key name is a wildcard '*' */
+ break;
+ }
+
+ if (flb_sds_cmp(key->name, gb_key->name,
+ flb_sds_len(gb_key->name)) == 0) {
+ if (subkeys_compare(key->subkeys, gb_key->subkeys) != 0) {
+ continue;
+ }
+
+ not_aggr--;
+
+ /* Map key selector with group-by */
+ key->gb_key = gb_key;
+ break;
+ }
+ }
+
+ not_aggr++;
+ }
+ }
+
+ /*
+ * if some aggregated function is required, not aggregated keys are
+ * not allowed so we return an error (-1).
+ */
+ if (aggr > 0 && not_aggr == 0) {
+ return aggr;
+ }
+ else if (aggr > 0 && not_aggr > 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Convert a string to a numerical representation:
+ *
+ * - if output number is an integer, 'i' is set and returns FLB_STR_INT
+ * - if output number is a float, 'd' is set and returns FLB_STR_FLOAT
+ * - if no conversion is possible (not a number), returns -1
+ */
+static int string_to_number(const char *str, int len, int64_t *i, double *d)
+{
+ int c;
+ int dots = 0;
+ char *end;
+ int64_t i_out;
+ double d_out;
+
+ /* Detect if this is a floating point number */
+ for (c = 0; c < len; c++) {
+ if (str[c] == '.') {
+ dots++;
+ }
+ }
+
+ if (dots > 1) {
+ return -1;
+ }
+ else if (dots == 1) {
+ /* Floating point number */
+ errno = 0;
+ d_out = strtold(str, &end);
+
+ /* Check for various possible errors */
+ if ((errno == ERANGE || (errno != 0 && d_out == 0))) {
+ return -1;
+ }
+
+ if (end == str) {
+ return -1;
+ }
+
+ *d = d_out;
+ return FLB_STR_FLOAT;
+ }
+ else {
+ /* Integer */
+ errno = 0;
+ i_out = strtoll(str, &end, 10);
+
+ /* Check for various possible errors */
+ if ((errno == ERANGE || (errno != 0 && i_out == 0))) {
+ return -1;
+ }
+
+ if (end == str) {
+ return -1;
+ }
+
+ *i = i_out;
+ return FLB_STR_INT;
+ }
+
+ return -1;
+}
+
+/*
+ * Convert a msgpack object value to a number 'if possible'. The conversion
+ * result is either stored on 'i' for 64 bits integers or in 'd' for
+ * float/doubles.
+ *
+ * This function aims to take care of strings representing a value too.
+ */
+static int object_to_number(msgpack_object obj, int64_t *i, double *d,
+ int convert_str_to_num)
+{
+ int ret;
+ int64_t i_out;
+ double d_out;
+ char str_num[20];
+
+ if (obj.type == MSGPACK_OBJECT_POSITIVE_INTEGER ||
+ obj.type == MSGPACK_OBJECT_NEGATIVE_INTEGER) {
+ *i = obj.via.i64;
+ return FLB_STR_INT;
+ }
+ else if (obj.type == MSGPACK_OBJECT_FLOAT32 ||
+ obj.type == MSGPACK_OBJECT_FLOAT) {
+ *d = obj.via.f64;
+ return FLB_STR_FLOAT;
+ }
+ else if (obj.type == MSGPACK_OBJECT_STR && convert_str_to_num == FLB_TRUE) {
+ /* A numeric representation of a string should not exceed 19 chars */
+ if (obj.via.str.size > 19) {
+ return -1;
+ }
+
+ memcpy(str_num, obj.via.str.ptr, obj.via.str.size);
+ str_num[obj.via.str.size] = '\0';
+
+ ret = string_to_number(str_num, obj.via.str.size,
+ &i_out, &d_out);
+ if (ret == FLB_STR_FLOAT) {
+ *d = d_out;
+ return FLB_STR_FLOAT;
+ }
+ else if (ret == FLB_STR_INT) {
+ *i = i_out;
+ return FLB_STR_INT;
+ }
+ }
+
+ return -1;
+}
+
+int flb_sp_snapshot_create(struct flb_sp_task *task)
+{
+ struct flb_sp_cmd *cmd;
+ struct flb_sp_snapshot *snapshot;
+
+ cmd = task->cmd;
+
+ snapshot = (struct flb_sp_snapshot *) flb_calloc(1, sizeof(struct flb_sp_snapshot));
+ if (!snapshot) {
+ flb_error("[sp] could not create snapshot '%s'", cmd->stream_name);
+ return -1;
+ }
+
+ mk_list_init(&snapshot->pages);
+ snapshot->record_limit = cmd->limit;
+
+ if (flb_sp_cmd_stream_prop_get(cmd, "seconds") != NULL) {
+ snapshot->time_limit = atoi(flb_sp_cmd_stream_prop_get(cmd, "seconds"));
+ }
+
+ if (snapshot->time_limit == 0 && snapshot->record_limit == 0) {
+ flb_error("[sp] could not create snapshot '%s': size is not defined",
+ cmd->stream_name);
+ flb_sp_snapshot_destroy(snapshot);
+ return -1;
+ }
+
+ task->snapshot = snapshot;
+ return 0;
+}
+
+struct flb_sp_task *flb_sp_task_create(struct flb_sp *sp, const char *name,
+ const char *query)
+{
+ int fd;
+ int ret;
+ struct mk_event *event;
+ struct flb_sp_cmd *cmd;
+ struct flb_sp_task *task;
+
+ /*
+ * Parse and validate the incoming exec query and create the 'command'
+ * context (this will be associated to the task in a later step
+ */
+ cmd = flb_sp_cmd_create(query);
+
+ if (!cmd) {
+ flb_error("[sp] invalid query on task '%s': '%s'", name, query);
+ return NULL;
+ }
+
+ /* Check if we got an invalid type due an error/restriction */
+ if (cmd->status == FLB_SP_ERROR) {
+ flb_error("[sp] invalid query on task '%s': '%s'", name, query);
+ flb_sp_cmd_destroy(cmd);
+ return NULL;
+ }
+
+ /* Create the task context */
+ task = flb_calloc(1, sizeof(struct flb_sp_task));
+ if (!task) {
+ flb_errno();
+ flb_sp_cmd_destroy(cmd);
+ return NULL;
+ }
+ task->name = flb_sds_create(name);
+ if (!task->name) {
+ flb_free(task);
+ flb_sp_cmd_destroy(cmd);
+ return NULL;
+ }
+
+ task->query = flb_sds_create(query);
+ if (!task->query) {
+ flb_sds_destroy(task->name);
+ flb_free(task);
+ flb_sp_cmd_destroy(cmd);
+ return NULL;
+ }
+
+ task->sp = sp;
+ task->cmd = cmd;
+ mk_list_add(&task->_head, &sp->tasks);
+
+ /*
+ * Assume no aggregated keys exists, if so, a different strategy is
+ * required to process the records.
+ */
+ task->aggregate_keys = FLB_FALSE;
+
+ mk_list_init(&task->window.data);
+ mk_list_init(&task->window.aggregate_list);
+ rb_tree_new(&task->window.aggregate_tree, flb_sp_groupby_compare);
+
+ mk_list_init(&task->window.hopping_slot);
+
+ /* Check and validate aggregated keys */
+ ret = sp_cmd_aggregated_keys(task->cmd);
+ if (ret == -1) {
+ flb_error("[sp] aggregated query cannot mix not aggregated keys: %s",
+ query);
+ flb_sp_task_destroy(task);
+ return NULL;
+ }
+ else if (ret > 0) {
+ task->aggregate_keys = FLB_TRUE;
+
+ task->window.type = cmd->window.type;
+
+ /* Register a timer event when task contains aggregation rules */
+ if (task->window.type != FLB_SP_WINDOW_DEFAULT) {
+ /* Initialize event loop context */
+ event = &task->window.event;
+ MK_EVENT_ZERO(event);
+
+ /* Run every 'size' seconds */
+ fd = mk_event_timeout_create(sp->config->evl,
+ cmd->window.size, (long) 0,
+ &task->window.event);
+ if (fd == -1) {
+ flb_error("[sp] registration for task %s failed", task->name);
+ flb_free(task);
+ return NULL;
+ }
+ task->window.fd = fd;
+
+ if (task->window.type == FLB_SP_WINDOW_HOPPING) {
+ /* Initialize event loop context */
+ event = &task->window.event_hop;
+ MK_EVENT_ZERO(event);
+
+ /* Run every 'size' seconds */
+ fd = mk_event_timeout_create(sp->config->evl,
+ cmd->window.advance_by, (long) 0,
+ &task->window.event_hop);
+ if (fd == -1) {
+ flb_error("[sp] registration for task %s failed", task->name);
+ flb_free(task);
+ return NULL;
+ }
+ task->window.advance_by = cmd->window.advance_by;
+ task->window.fd_hop = fd;
+ task->window.first_hop = true;
+ }
+ }
+ }
+
+ /* Init snapshot page list */
+ if (cmd->type == FLB_SP_CREATE_SNAPSHOT) {
+ if (flb_sp_snapshot_create(task) == -1) {
+ flb_sp_task_destroy(task);
+ return NULL;
+ }
+ }
+
+ /*
+ * If the task involves a stream creation (CREATE STREAM abc..), create
+ * the stream.
+ */
+ if (cmd->type == FLB_SP_CREATE_STREAM ||
+ cmd->type == FLB_SP_CREATE_SNAPSHOT ||
+ cmd->type == FLB_SP_FLUSH_SNAPSHOT) {
+
+ ret = flb_sp_stream_create(cmd->stream_name, task, sp);
+ if (ret == -1) {
+ flb_error("[sp] could not create stream '%s'", cmd->stream_name);
+ flb_sp_task_destroy(task);
+ return NULL;
+ }
+ }
+
+ /*
+ * Based in the command type, check if the source of data is a known
+ * stream so make a reference on this task for a quick comparisson and
+ * access it when processing data.
+ */
+ sp_task_to_instance(task, sp);
+ return task;
+}
+
+void groupby_nums_destroy(struct aggregate_num *groupby_nums, int size)
+{
+ int i;
+
+ for (i = 0; i < size; i++) {
+ if (groupby_nums[i].type == FLB_SP_STRING) {
+ flb_sds_destroy(groupby_nums[i].string);
+ }
+ }
+
+ flb_free(groupby_nums);
+}
+
+/*
+ * Destroy aggregation node context: before to use this function make sure
+ * to unlink from the linked list.
+ */
+void flb_sp_aggregate_node_destroy(struct flb_sp_cmd *cmd,
+ struct aggregate_node *aggr_node)
+{
+ int i;
+ int key_id;
+ struct mk_list *head;
+ struct aggregate_num *num;
+ struct flb_sp_cmd_key *ckey;
+
+ for (i = 0; i < aggr_node->nums_size; i++) {
+ num = &aggr_node->nums[i];
+ if (num->type == FLB_SP_STRING) {
+ flb_sds_destroy(num->string);
+ }
+ }
+
+ groupby_nums_destroy(aggr_node->groupby_nums, aggr_node->groupby_keys);
+
+ key_id = 0;
+ mk_list_foreach(head, &cmd->keys) {
+ ckey = mk_list_entry(head, struct flb_sp_cmd_key, _head);
+
+ if (!ckey->aggr_func) {
+ key_id++;
+ continue;
+ }
+
+ aggregate_func_destroy[ckey->aggr_func - 1](aggr_node, key_id);
+ key_id++;
+ }
+
+ flb_free(aggr_node->nums);
+ flb_free(aggr_node->aggregate_data);
+ flb_free(aggr_node);
+}
+
+void flb_sp_window_destroy(struct flb_sp_cmd *cmd,
+ struct flb_sp_task_window *window)
+{
+ struct flb_sp_window_data *data;
+ struct aggregate_node *aggr_node;
+ struct flb_sp_hopping_slot *hs;
+ struct mk_list *head;
+ struct mk_list *tmp;
+ struct mk_list *head_hs;
+ struct mk_list *tmp_hs;
+
+ mk_list_foreach_safe(head, tmp, &window->data) {
+ data = mk_list_entry(head, struct flb_sp_window_data, _head);
+ flb_free(data->buf_data);
+ mk_list_del(&data->_head);
+ flb_free(data);
+ }
+
+ mk_list_foreach_safe(head, tmp, &window->aggregate_list) {
+ aggr_node = mk_list_entry(head, struct aggregate_node, _head);
+ mk_list_del(&aggr_node->_head);
+ flb_sp_aggregate_node_destroy(cmd, aggr_node);
+ }
+
+ mk_list_foreach_safe(head, tmp, &window->hopping_slot) {
+ hs = mk_list_entry(head, struct flb_sp_hopping_slot, _head);
+ mk_list_foreach_safe(head_hs, tmp_hs, &hs->aggregate_list) {
+ aggr_node = mk_list_entry(head_hs, struct aggregate_node, _head);
+ mk_list_del(&aggr_node->_head);
+ flb_sp_aggregate_node_destroy(cmd, aggr_node);
+ }
+ rb_tree_destroy(&hs->aggregate_tree);
+ flb_free(hs);
+ }
+
+ rb_tree_destroy(&window->aggregate_tree);
+}
+
+void flb_sp_task_destroy(struct flb_sp_task *task)
+{
+ flb_sds_destroy(task->name);
+ flb_sds_destroy(task->query);
+ flb_sp_window_destroy(task->cmd, &task->window);
+ flb_sp_snapshot_destroy(task->snapshot);
+ mk_list_del(&task->_head);
+
+ if (task->stream) {
+ flb_sp_stream_destroy(task->stream, task->sp);
+ }
+
+ flb_sp_cmd_destroy(task->cmd);
+ flb_free(task);
+}
+
+/* Create the stream processor context */
+struct flb_sp *flb_sp_create(struct flb_config *config)
+{
+ int i = 0;
+ int ret;
+ char buf[32];
+ struct mk_list *head;
+ struct flb_sp *sp;
+ struct flb_slist_entry *e;
+ struct flb_sp_task *task;
+
+ /* Allocate context */
+ sp = flb_malloc(sizeof(struct flb_sp));
+ if (!sp) {
+ flb_errno();
+ return NULL;
+ }
+ sp->config = config;
+ mk_list_init(&sp->tasks);
+
+ /* Check for pre-configured Tasks (command line) */
+ mk_list_foreach(head, &config->stream_processor_tasks) {
+ e = mk_list_entry(head, struct flb_slist_entry, _head);
+ snprintf(buf, sizeof(buf) - 1, "flb-console:%i", i);
+ i++;
+ task = flb_sp_task_create(sp, buf, e->str);
+ if (!task) {
+ continue;
+ }
+ }
+
+ /* Lookup configuration file if any */
+ if (config->stream_processor_file) {
+ ret = sp_config_file(config, sp, config->stream_processor_file);
+ if (ret == -1) {
+ flb_error("[sp] could not initialize stream processor");
+ flb_sp_destroy(sp);
+ return NULL;
+ }
+ }
+
+ /* Write sp info to stdout */
+ sp_info(sp);
+
+ return sp;
+}
+
+void free_value(struct flb_exp_val *v)
+{
+ if (!v) {
+ return;
+ }
+
+ if (v->type == FLB_EXP_STRING) {
+ flb_sds_destroy(v->val.string);
+ }
+
+ flb_free(v);
+}
+
+static void itof_convert(struct flb_exp_val *val)
+{
+ if (val->type != FLB_EXP_INT) {
+ return;
+ }
+
+ val->type = FLB_EXP_FLOAT;
+ val->val.f64 = (double) val->val.i64;
+}
+
+/* Convert (string) expression to number */
+static void exp_string_to_number(struct flb_exp_val *val)
+{
+ int ret;
+ int len;
+ int64_t i = 0;
+ char *str;
+ double d = 0.0;
+
+ len = flb_sds_len(val->val.string);
+ str = val->val.string;
+
+ ret = string_to_number(str, len, &i, &d);
+ if (ret == -1) {
+ return;
+ }
+
+ /* Assign to proper type */
+ if (ret == FLB_STR_FLOAT) {
+ flb_sds_destroy(val->val.string);
+ val->type = FLB_EXP_FLOAT;
+ val->val.f64 = d;
+ }
+ else if (ret == FLB_STR_INT) {
+ flb_sds_destroy(val->val.string);
+ val->type = FLB_EXP_INT;
+ val->val.i64 = i;
+ }
+}
+
+static void numerical_comp(struct flb_exp_val *left,
+ struct flb_exp_val *right,
+ struct flb_exp_val *result, int op)
+{
+ result->type = FLB_EXP_BOOL;
+
+ if (left == NULL || right == NULL) {
+ result->val.boolean = false;
+ return;
+ }
+
+ /* Check if left expression value is a number, if so, convert it */
+ if (left->type == FLB_EXP_STRING && right->type != FLB_EXP_STRING) {
+ exp_string_to_number(left);
+ }
+
+ if (left->type == FLB_EXP_INT && right->type == FLB_EXP_FLOAT) {
+ itof_convert(left);
+ }
+ else if (left->type == FLB_EXP_FLOAT && right->type == FLB_EXP_INT) {
+ itof_convert(right);
+ }
+
+ switch (op) {
+ case FLB_EXP_EQ:
+ if (left->type == right->type) {
+ switch(left->type) {
+ case FLB_EXP_NULL:
+ result->val.boolean = true;
+ break;
+ case FLB_EXP_BOOL:
+ result->val.boolean = (left->val.boolean == right->val.boolean);
+ break;
+ case FLB_EXP_INT:
+ result->val.boolean = (left->val.i64 == right->val.i64);
+ break;
+ case FLB_EXP_FLOAT:
+ result->val.boolean = (left->val.f64 == right->val.f64);
+ break;
+ case FLB_EXP_STRING:
+ if (flb_sds_len(left->val.string) !=
+ flb_sds_len(right->val.string)) {
+ result->val.boolean = false;
+ }
+ else if (strncmp(left->val.string, right->val.string,
+ flb_sds_len(left->val.string)) != 0) {
+ result->val.boolean = false;
+ }
+ else {
+ result->val.boolean = true;
+ }
+ break;
+ default:
+ result->val.boolean = false;
+ break;
+ }
+ }
+ else {
+ result->val.boolean = false;
+ }
+ break;
+ case FLB_EXP_LT:
+ if (left->type == right->type) {
+ switch(left->type) {
+ case FLB_EXP_INT:
+ result->val.boolean = (left->val.i64 < right->val.i64);
+ break;
+ case FLB_EXP_FLOAT:
+ result->val.boolean = (left->val.f64 < right->val.f64);
+ break;
+ case FLB_EXP_STRING:
+ if (strncmp(left->val.string, right->val.string,
+ flb_sds_len(left->val.string)) < 0) {
+ result->val.boolean = true;
+ }
+ else {
+ result->val.boolean = false;
+ }
+ break;
+ default:
+ result->val.boolean = false;
+ break;
+ }
+ }
+ else {
+ result->val.boolean = false;
+ }
+ break;
+ case FLB_EXP_LTE:
+ if (left->type == right->type) {
+ switch(left->type) {
+ case FLB_EXP_INT:
+ result->val.boolean = (left->val.i64 <= right->val.i64);
+ break;
+ case FLB_EXP_FLOAT:
+ result->val.boolean = (left->val.f64 <= right->val.f64);
+ break;
+ case FLB_EXP_STRING:
+ if (strncmp(left->val.string, right->val.string,
+ flb_sds_len(left->val.string)) <= 0) {
+ result->val.boolean = true;
+ }
+ else {
+ result->val.boolean = false;
+ }
+ break;
+ default:
+ result->val.boolean = false;
+ break;
+ }
+ }
+ else {
+ result->val.boolean = false;
+ }
+ break;
+ case FLB_EXP_GT:
+ if (left->type == right->type) {
+ switch(left->type) {
+ case FLB_EXP_INT:
+ result->val.boolean = (left->val.i64 > right->val.i64);
+ break;
+ case FLB_EXP_FLOAT:
+ result->val.boolean = (left->val.f64 > right->val.f64);
+ break;
+ case FLB_EXP_STRING:
+ if (strncmp(left->val.string, right->val.string,
+ flb_sds_len(left->val.string)) > 0) {
+ result->val.boolean = true;
+ }
+ else {
+ result->val.boolean = false;
+ }
+ break;
+ default:
+ result->val.boolean = false;
+ break;
+ }
+ }
+ else {
+ result->val.boolean = false;
+ }
+ break;
+ case FLB_EXP_GTE:
+ if (left->type == right->type) {
+ switch(left->type) {
+ case FLB_EXP_INT:
+ result->val.boolean = (left->val.i64 >= right->val.i64);
+ break;
+ case FLB_EXP_FLOAT:
+ result->val.boolean = (left->val.f64 >= right->val.f64);
+ break;
+ case FLB_EXP_STRING:
+ if (strncmp(left->val.string, right->val.string,
+ flb_sds_len(left->val.string)) >= 0) {
+ result->val.boolean = true;
+ }
+ else {
+ result->val.boolean = false;
+ }
+ break;
+ default:
+ result->val.boolean = false;
+ break;
+ }
+ }
+ else {
+ result->val.boolean = false;
+ }
+ break;
+ }
+}
+
+static bool value_to_bool(struct flb_exp_val *val) {
+ bool result = FLB_FALSE;
+
+ switch (val->type) {
+ case FLB_EXP_BOOL:
+ result = val->val.boolean;
+ break;
+ case FLB_EXP_INT:
+ result = val->val.i64 > 0;
+ break;
+ case FLB_EXP_FLOAT:
+ result = val->val.f64 > 0;
+ break;
+ case FLB_EXP_STRING:
+ result = true;
+ break;
+ }
+
+ return result;
+}
+
+
+static void logical_operation(struct flb_exp_val *left,
+ struct flb_exp_val *right,
+ struct flb_exp_val *result, int op)
+{
+ bool lval;
+ bool rval;
+
+ result->type = FLB_EXP_BOOL;
+
+ /* Null is always interpreted as false in a logical operation */
+ lval = left ? value_to_bool(left) : false;
+ rval = right ? value_to_bool(right) : false;
+
+ switch (op) {
+ case FLB_EXP_NOT:
+ result->val.boolean = !lval;
+ break;
+ case FLB_EXP_AND:
+ result->val.boolean = lval & rval;
+ break;
+ case FLB_EXP_OR:
+ result->val.boolean = lval | rval;
+ break;
+ }
+}
+
+static struct flb_exp_val *reduce_expression(struct flb_exp *expression,
+ const char *tag, int tag_len,
+ struct flb_time *tms,
+ msgpack_object *map)
+{
+ int operation;
+ flb_sds_t s;
+ flb_sds_t tmp_sds = NULL;
+ struct flb_exp_key *key;
+ struct flb_sp_value *sval;
+ struct flb_exp_val *ret, *left, *right;
+ struct flb_exp_val *result;
+
+ if (!expression) {
+ return NULL;
+ }
+
+ result = flb_calloc(1, sizeof(struct flb_exp_val));
+ if (!result) {
+ flb_errno();
+ return NULL;
+ }
+
+ switch (expression->type) {
+ case FLB_EXP_NULL:
+ result->type = expression->type;
+ break;
+ case FLB_EXP_BOOL:
+ result->type = expression->type;
+ result->val.boolean = ((struct flb_exp_val *) expression)->val.boolean;
+ break;
+ case FLB_EXP_INT:
+ result->type = expression->type;
+ result->val.i64 = ((struct flb_exp_val *) expression)->val.i64;
+ break;
+ case FLB_EXP_FLOAT:
+ result->type = expression->type;
+ result->val.f64 = ((struct flb_exp_val *) expression)->val.f64;
+ break;
+ case FLB_EXP_STRING:
+ s = ((struct flb_exp_val *) expression)->val.string;
+ result->type = expression->type;
+ result->val.string = flb_sds_create_size(flb_sds_len(s));
+ tmp_sds = flb_sds_copy(result->val.string, s, flb_sds_len(s));
+ if (tmp_sds != result->val.string) {
+ result->val.string = tmp_sds;
+ }
+ break;
+ case FLB_EXP_KEY:
+ key = (struct flb_exp_key *) expression;
+ sval = flb_sp_key_to_value(key->name, *map, key->subkeys);
+ if (sval) {
+ result->type = sval->type;
+ result->val = sval->val;
+ flb_free(sval);
+ return result;
+ }
+ else {
+ flb_free(result);
+ return NULL;
+ }
+ break;
+ case FLB_EXP_FUNC:
+ /* we don't need result */
+ flb_free(result);
+ ret = reduce_expression(((struct flb_exp_func *) expression)->param,
+ tag, tag_len, tms, map);
+ result = ((struct flb_exp_func *) expression)->cb_func(tag, tag_len,
+ tms, ret);
+ free_value(ret);
+ break;
+ case FLB_LOGICAL_OP:
+ left = reduce_expression(expression->left,
+ tag, tag_len, tms, map);
+ right = reduce_expression(expression->right,
+ tag, tag_len, tms, map);
+
+ operation = ((struct flb_exp_op *) expression)->operation;
+
+ switch (operation) {
+ case FLB_EXP_PAR:
+ if (left == NULL) { /* Null is always interpreted as false in a
+ logical operation */
+ result->type = FLB_EXP_BOOL;
+ result->val.boolean = false;
+ }
+ else { /* Left and right sides of a logical operation reduce to
+ boolean values */
+ result->type = FLB_EXP_BOOL;
+ result->val.boolean = left->val.boolean;
+ }
+ break;
+ case FLB_EXP_EQ:
+ case FLB_EXP_LT:
+ case FLB_EXP_LTE:
+ case FLB_EXP_GT:
+ case FLB_EXP_GTE:
+ numerical_comp(left, right, result, operation);
+ break;
+ case FLB_EXP_NOT:
+ case FLB_EXP_AND:
+ case FLB_EXP_OR:
+ logical_operation(left, right, result, operation);
+ break;
+ }
+ free_value(left);
+ free_value(right);
+ }
+ return result;
+}
+
+
+void package_results(const char *tag, int tag_len,
+ char **out_buf, size_t *out_size,
+ struct flb_sp_task *task)
+{
+ int i;
+ int len;
+ int map_entries;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+ struct aggregate_num *num;
+ struct flb_time tm;
+ struct flb_sp_cmd_key *ckey;
+ struct flb_sp_cmd *cmd = task->cmd;
+ struct mk_list *head;
+ struct aggregate_node *aggr_node;
+ struct flb_sp_cmd_gb_key *gb_key = NULL;
+
+ map_entries = mk_list_size(&cmd->keys);
+
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ mk_list_foreach(head, &task->window.aggregate_list) {
+ aggr_node = mk_list_entry(head, struct aggregate_node, _head);
+
+ /* set outgoing array + map and it fixed size */
+ msgpack_pack_array(&mp_pck, 2);
+
+ flb_time_get(&tm);
+ flb_time_append_to_msgpack(&tm, &mp_pck, 0);
+ msgpack_pack_map(&mp_pck, map_entries);
+
+ /* Packaging results */
+ ckey = mk_list_entry_first(&cmd->keys, struct flb_sp_cmd_key, _head);
+ for (i = 0; i < map_entries; i++) {
+ num = &aggr_node->nums[i];
+
+ /* Check if there is a defined function */
+ if (ckey->time_func > 0) {
+ flb_sp_func_time(&mp_pck, ckey);
+ goto next;
+ }
+ else if (ckey->record_func > 0) {
+ flb_sp_func_record(tag, tag_len, &tm, &mp_pck, ckey);
+ goto next;
+ }
+
+ /* Pack key */
+ if (ckey->alias) {
+ msgpack_pack_str(&mp_pck, flb_sds_len(ckey->alias));
+ msgpack_pack_str_body(&mp_pck,
+ ckey->alias,
+ flb_sds_len(ckey->alias));
+ }
+ else {
+ len = 0;
+ char *c_name;
+ if (!ckey->name) {
+ c_name = "*";
+ }
+ else {
+ c_name = ckey->name;
+ }
+
+ msgpack_pack_str(&mp_pck, len);
+ msgpack_pack_str_body(&mp_pck, c_name, len);
+ }
+
+ /*
+ * If a group_by key is mapped as a source of this key,
+ * change the 'num' reference to obtain the proper information
+ * for the grouped key value.
+ */
+ if (ckey->gb_key != NULL) {
+ gb_key = ckey->gb_key;
+ if (aggr_node->groupby_keys > 0) {
+ num = &aggr_node->groupby_nums[gb_key->id];
+ }
+ }
+
+ /* Pack value */
+ switch (ckey->aggr_func) {
+ case FLB_SP_NOP:
+ if (num->type == FLB_SP_NUM_I64) {
+ msgpack_pack_int64(&mp_pck, num->i64);
+ }
+ else if (num->type == FLB_SP_NUM_F64) {
+ msgpack_pack_float(&mp_pck, num->f64);
+ }
+ else if (num->type == FLB_SP_STRING) {
+ msgpack_pack_str(&mp_pck,
+ flb_sds_len(num->string));
+ msgpack_pack_str_body(&mp_pck,
+ num->string,
+ flb_sds_len(num->string));
+ }
+ else if (num->type == FLB_SP_BOOLEAN) {
+ if (num->boolean) {
+ msgpack_pack_true(&mp_pck);
+ }
+ else {
+ msgpack_pack_false(&mp_pck);
+ }
+ }
+ break;
+ default:
+ aggregate_func_calc[ckey->aggr_func - 1](aggr_node, ckey, &mp_pck, i);
+ break;
+ }
+
+next:
+ ckey = mk_list_entry_next(&ckey->_head, struct flb_sp_cmd_key,
+ _head, &cmd->keys);
+ }
+ }
+
+ *out_buf = mp_sbuf.data;
+ *out_size = mp_sbuf.size;
+}
+
+static struct aggregate_node * sp_process_aggregate_data(struct flb_sp_task *task,
+ msgpack_object map,
+ int convert_str_to_num)
+{
+ int i;
+ int ret;
+ int map_size;
+ int key_id;
+ int map_entries;
+ int gb_entries;
+ int values_found;
+ int64_t ival;
+ double dval;
+ struct flb_sp_value *sval;
+ struct aggregate_num *gb_nums;
+ struct aggregate_node *aggr_node;
+ struct flb_sp_cmd *cmd;
+ struct flb_sp_cmd_gb_key *gb_key;
+ struct mk_list *head;
+ struct rb_tree_node *rb_result;
+ msgpack_object key;
+
+ aggr_node = NULL;
+ cmd = task->cmd;
+ map_size = map.via.map.size;
+ values_found = 0;
+
+ /* Number of expected output entries in the map */
+ map_entries = mk_list_size(&cmd->keys);
+ gb_entries = mk_list_size(&cmd->gb_keys);
+
+ if (gb_entries > 0) {
+ gb_nums = flb_calloc(1, sizeof(struct aggregate_num) * gb_entries);
+ if (!gb_nums) {
+ return NULL;
+ }
+
+ /* extract GROUP BY values */
+ for (i = 0; i < map_size; i++) { /* extract group-by values */
+ key = map.via.map.ptr[i].key;
+
+ key_id = 0;
+ mk_list_foreach(head, &cmd->gb_keys) {
+ gb_key = mk_list_entry(head, struct flb_sp_cmd_gb_key,
+ _head);
+ if (flb_sds_cmp(gb_key->name, key.via.str.ptr,
+ key.via.str.size) != 0) {
+ key_id++;
+ continue;
+ }
+
+ sval = flb_sp_key_to_value(gb_key->name, map, gb_key->subkeys);
+ if (!sval) {
+ /* If evaluation fails/sub-key doesn't exist */
+ key_id++;
+ continue;
+ }
+
+ values_found++;
+
+ /* Convert string to number if that is possible */
+ ret = object_to_number(sval->o, &ival, &dval, convert_str_to_num);
+ if (ret == -1) {
+ if (sval->o.type == MSGPACK_OBJECT_STR) {
+ gb_nums[key_id].type = FLB_SP_STRING;
+ gb_nums[key_id].string =
+ flb_sds_create_len(sval->o.via.str.ptr,
+ sval->o.via.str.size);
+ }
+ else if (sval->o.type == MSGPACK_OBJECT_BOOLEAN) {
+ gb_nums[key_id].type = FLB_SP_NUM_I64;
+ gb_nums[key_id].i64 = sval->o.via.boolean;
+ }
+ }
+ else if (ret == FLB_STR_INT) {
+ gb_nums[key_id].type = FLB_SP_NUM_I64;
+ gb_nums[key_id].i64 = ival;
+ }
+ else if (ret == FLB_STR_FLOAT) {
+ gb_nums[key_id].type = FLB_SP_NUM_F64;
+ gb_nums[key_id].f64 = dval;
+ }
+
+ key_id++;
+ flb_sp_key_value_destroy(sval);
+ }
+ }
+
+ /* if some GROUP BY keys are not found in the record */
+ if (values_found < gb_entries) {
+ groupby_nums_destroy(gb_nums, gb_entries);
+ return NULL;
+ }
+
+ aggr_node = (struct aggregate_node *) flb_calloc(1, sizeof(struct aggregate_node));
+ if (!aggr_node) {
+ flb_errno();
+ groupby_nums_destroy(gb_nums, gb_entries);
+ return NULL;
+ }
+
+ aggr_node->groupby_keys = gb_entries;
+ aggr_node->groupby_nums = gb_nums;
+
+ rb_tree_find_or_insert(&task->window.aggregate_tree, aggr_node, &aggr_node->_rb_head, &rb_result);
+ if (&aggr_node->_rb_head != rb_result) {
+ /* We don't need aggr_node anymore */
+ flb_sp_aggregate_node_destroy(cmd, aggr_node);
+
+ aggr_node = container_of(rb_result, struct aggregate_node, _rb_head);
+ container_of(rb_result, struct aggregate_node, _rb_head)->records++;
+ }
+ else {
+ aggr_node->nums = flb_calloc(1, sizeof(struct aggregate_num) * map_entries);
+ if (!aggr_node->nums) {
+ flb_sp_aggregate_node_destroy(cmd, aggr_node);
+ return NULL;
+ }
+ aggr_node->records = 1;
+ aggr_node->nums_size = map_entries;
+ aggr_node->aggregate_data = (struct aggregate_data **) flb_calloc(1, sizeof(struct aggregate_data *) * map_entries);
+ mk_list_add(&aggr_node->_head, &task->window.aggregate_list);
+ }
+ }
+ else { /* If query doesn't have GROUP BY */
+ if (!mk_list_size(&task->window.aggregate_list)) {
+ aggr_node = flb_calloc(1, sizeof(struct aggregate_node));
+ if (!aggr_node) {
+ flb_errno();
+ return NULL;
+ }
+ aggr_node->nums = flb_calloc(1, sizeof(struct aggregate_num) * map_entries);
+ if (!aggr_node->nums) {
+ flb_sp_aggregate_node_destroy(cmd, aggr_node);
+ return NULL;
+ }
+
+ aggr_node->nums_size = map_entries;
+ aggr_node->records = 1;
+ aggr_node->aggregate_data = (struct aggregate_data **) flb_calloc(1, sizeof(struct aggregate_data *) * map_entries);
+ mk_list_add(&aggr_node->_head, &task->window.aggregate_list);
+ }
+ else {
+ aggr_node = mk_list_entry_first(&task->window.aggregate_list, struct aggregate_node, _head);
+ aggr_node->records++;
+ }
+ }
+
+ return aggr_node;
+}
+
+/*
+ * Process data, task and it defined command involves the call of aggregation
+ * functions (AVG, SUM, COUNT, MIN, MAX).
+ */
+int sp_process_data_aggr(const char *buf_data, size_t buf_size,
+ const char *tag, int tag_len,
+ struct flb_sp_task *task,
+ struct flb_sp *sp,
+ int convert_str_to_num)
+{
+ int i;
+ int ok;
+ int ret;
+ int map_size;
+ int key_id;
+ size_t off;
+ int64_t ival;
+ double dval;
+ msgpack_object root;
+ msgpack_object map;
+ msgpack_unpacked result;
+ msgpack_object key;
+ msgpack_object *obj;
+ struct aggregate_num *nums = NULL;
+ struct mk_list *head;
+ struct flb_time tms;
+ struct flb_sp_cmd *cmd = task->cmd;
+ struct flb_sp_cmd_key *ckey;
+ struct flb_sp_value *sval;
+ struct flb_exp_val *condition;
+ struct aggregate_node *aggr_node;
+
+ /* Number of expected output entries in the map */
+ off = 0;
+
+ /* vars initialization */
+ ok = MSGPACK_UNPACK_SUCCESS;
+ msgpack_unpacked_init(&result);
+
+ /* Iterate incoming records */
+ while (msgpack_unpack_next(&result, buf_data, buf_size, &off) == ok) {
+ root = result.data;
+
+ /* extract timestamp */
+ flb_time_pop_from_msgpack(&tms, &result, &obj);
+
+ /* get the map data and it size (number of items) */
+ map = root.via.array.ptr[1];
+ map_size = map.via.map.size;
+
+ /* Evaluate condition */
+ if (cmd->condition) {
+ condition = reduce_expression(cmd->condition,
+ tag, tag_len, &tms, &map);
+ if (!condition) {
+ continue;
+ }
+ else if (!condition->val.boolean) {
+ flb_free(condition);
+ continue;
+ }
+ else {
+ flb_free(condition);
+ }
+ }
+
+ aggr_node = sp_process_aggregate_data(task, map, convert_str_to_num);
+ if (!aggr_node)
+ {
+ continue;
+ }
+
+ task->window.records++;
+
+ nums = aggr_node->nums;
+
+ /* Iterate each map key and see if it matches any command key */
+ for (i = 0; i < map_size; i++) {
+ key = map.via.map.ptr[i].key;
+
+ if (key.type != MSGPACK_OBJECT_STR) {
+ continue;
+ }
+
+
+ /*
+ * Iterate each command key. Note that since the command key
+ * can have different aggregation functions to the same key
+ * we should compare all of them.
+ */
+ key_id = 0;
+ mk_list_foreach(head, &cmd->keys) {
+ ckey = mk_list_entry(head, struct flb_sp_cmd_key, _head);
+
+ if (!ckey->name) {
+ key_id++;
+ continue;
+ }
+
+ if (flb_sds_cmp(ckey->name, key.via.str.ptr,
+ key.via.str.size) != 0) {
+ key_id++;
+ continue;
+ }
+
+ /* convert the value if it string */
+ sval = flb_sp_key_to_value(ckey->name, map, ckey->subkeys);
+ if (!sval) {
+ key_id++;
+ continue;
+ }
+
+ /*
+ * Convert value to a numeric representation only if key has an
+ * assigned aggregation function
+ */
+ ival = 0;
+ dval = 0.0;
+ if (ckey->aggr_func != FLB_SP_NOP) {
+ ret = object_to_number(sval->o, &ival, &dval, convert_str_to_num);
+ if (ret == -1) {
+ /* Value cannot be represented as a number */
+ key_id++;
+ flb_sp_key_value_destroy(sval);
+ continue;
+ }
+
+ /*
+ * If a floating pointer number exists, we use the same data
+ * type for the output.
+ */
+ if (dval != 0.0 && nums[key_id].type == FLB_SP_NUM_I64) {
+ nums[key_id].type = FLB_SP_NUM_F64;
+ nums[key_id].f64 = (double) nums[key_id].i64;
+ }
+
+ aggregate_func_add[ckey->aggr_func - 1](aggr_node, ckey, key_id, &tms, ival, dval);
+ }
+ else {
+ if (sval->o.type == MSGPACK_OBJECT_BOOLEAN) {
+ nums[key_id].type = FLB_SP_BOOLEAN;
+ nums[key_id].boolean = sval->o.via.boolean;
+ }
+ if (sval->o.type == MSGPACK_OBJECT_POSITIVE_INTEGER ||
+ sval->o.type == MSGPACK_OBJECT_NEGATIVE_INTEGER) {
+ nums[key_id].type = FLB_SP_NUM_I64;
+ nums[key_id].i64 = sval->o.via.i64;
+ }
+ else if (sval->o.type == MSGPACK_OBJECT_FLOAT32 ||
+ sval->o.type == MSGPACK_OBJECT_FLOAT) {
+ nums[key_id].type = FLB_SP_NUM_F64;
+ nums[key_id].f64 = sval->o.via.f64;
+ }
+ else if (sval->o.type == MSGPACK_OBJECT_STR) {
+ nums[key_id].type = FLB_SP_STRING;
+ if (nums[key_id].string == NULL) {
+ nums[key_id].string =
+ flb_sds_create_len(sval->o.via.str.ptr,
+ sval->o.via.str.size);
+ }
+ }
+ }
+
+ key_id++;
+ flb_sp_key_value_destroy(sval);
+ }
+ }
+ }
+
+ msgpack_unpacked_destroy(&result);
+ return task->window.records;
+}
+
+/*
+ * Data processing (no aggregation functions)
+ */
+int sp_process_data(const char *tag, int tag_len,
+ const char *buf_data, size_t buf_size,
+ char **out_buf, size_t *out_size,
+ struct flb_sp_task *task,
+ struct flb_sp *sp)
+{
+ int i;
+ int ok;
+ int ret;
+ int map_size;
+ int map_entries;
+ int records;
+ uint8_t h;
+ off_t map_off;
+ off_t no_data;
+ size_t off;
+ size_t off_copy;
+ size_t snapshot_out_size;
+ char *tmp;
+ char *snapshot_out_buffer;
+ msgpack_object root;
+ msgpack_object *obj;
+ msgpack_object key;
+ msgpack_object val;
+ msgpack_unpacked result;
+ msgpack_sbuffer mp_sbuf;
+ msgpack_packer mp_pck;
+ msgpack_object map;
+ struct flb_time tms;
+ struct mk_list *head;
+ struct flb_sp_cmd *cmd;
+ struct flb_sp_cmd_key *cmd_key;
+ struct flb_exp_val *condition;
+ struct flb_sp_value *sval;
+
+ /* Vars initialization */
+ off = 0;
+ off_copy = off;
+ records = 0;
+ cmd = task->cmd;
+ ok = MSGPACK_UNPACK_SUCCESS;
+ msgpack_unpacked_init(&result);
+ msgpack_sbuffer_init(&mp_sbuf);
+ msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
+
+ snapshot_out_size = 0;
+ snapshot_out_buffer = NULL;
+
+ /* Iterate incoming records */
+ while (msgpack_unpack_next(&result, buf_data, buf_size, &off) == ok) {
+ root = result.data;
+
+ /* extract timestamp */
+ flb_time_pop_from_msgpack(&tms, &result, &obj);
+
+ /* Store the buffer if the stream is a snapshot */
+ if (cmd->type == FLB_SP_CREATE_SNAPSHOT) {
+ flb_sp_snapshot_update(task, buf_data + off_copy, off - off_copy, &tms);
+ off_copy = off;
+ continue;
+ }
+
+ /* get the map data and it size (number of items) */
+ map = root.via.array.ptr[1];
+ map_size = map.via.map.size;
+
+ /* Evaluate condition */
+ if (cmd->condition) {
+ condition = reduce_expression(cmd->condition,
+ tag, tag_len, &tms, &map);
+ if (!condition) {
+ continue;
+ }
+ else if (!condition->val.boolean) {
+ flb_free(condition);
+ continue;
+ }
+ else {
+ flb_free(condition);
+ }
+ }
+
+ records++;
+
+ /* Flush the snapshot if condition holds */
+ if (cmd->type == FLB_SP_FLUSH_SNAPSHOT) {
+ if (flb_sp_snapshot_flush(sp, task, &snapshot_out_buffer,
+ &snapshot_out_size) == -1) {
+ msgpack_unpacked_destroy(&result);
+ msgpack_sbuffer_destroy(&mp_sbuf);
+ return -1;
+ }
+ continue;
+ }
+
+
+ /*
+ * If for some reason the Task keys did not insert any data, we will
+ * need to discard any changes and reset the buffer position, let's
+ * keep the memory size for that purpose.
+ */
+ no_data = mp_sbuf.size;
+
+ /* Pack main array */
+ msgpack_pack_array(&mp_pck, 2);
+ msgpack_pack_object(&mp_pck, root.via.array.ptr[0]);
+
+ /*
+ * Save the current size/position of the buffer since this is
+ * where the Map header will be stored.
+ */
+ map_off = mp_sbuf.size;
+
+ /*
+ * In the new record register the same number of items, if due to
+ * fields selection the number is lower, we perform an adjustment
+ */
+ msgpack_pack_map(&mp_pck, map_size);
+
+ /* Counter for new entries added to the outgoing map */
+ map_entries = 0;
+
+ /* Iterate key selection */
+ mk_list_foreach(head, &cmd->keys) {
+ cmd_key = mk_list_entry(head, struct flb_sp_cmd_key, _head);
+ if (cmd_key->time_func > 0) {
+ /* Process time function */
+ ret = flb_sp_func_time(&mp_pck, cmd_key);
+ if (ret > 0) {
+ map_entries += ret;
+ }
+ continue;
+ }
+ else if (cmd_key->record_func > 0) {
+ ret = flb_sp_func_record(tag, tag_len, &tms, &mp_pck, cmd_key);
+ if (ret > 0) {
+ map_entries += ret;
+ }
+ continue;
+ }
+
+ /* Lookup selection key in the incoming map */
+ for (i = 0; i < map_size; i++) {
+ key = map.via.map.ptr[i].key;
+ val = map.via.map.ptr[i].val;
+
+ if (key.type != MSGPACK_OBJECT_STR) {
+ continue;
+ }
+
+ /* Wildcard selection: * */
+ if (cmd_key->name == NULL) {
+ msgpack_pack_object(&mp_pck, key);
+ msgpack_pack_object(&mp_pck, val);
+ map_entries++;
+ continue;
+ }
+
+ /* Compare lengths */
+ if (flb_sds_cmp(cmd_key->name,
+ key.via.str.ptr, key.via.str.size) != 0) {
+ continue;
+ }
+
+ /*
+ * Package key name:
+ *
+ * Check if the command ask for an alias 'key AS abc'
+ */
+ if (cmd_key->alias) {
+ msgpack_pack_str(&mp_pck,
+ flb_sds_len(cmd_key->alias));
+ msgpack_pack_str_body(&mp_pck,
+ cmd_key->alias,
+ flb_sds_len(cmd_key->alias));
+ }
+ else {
+ msgpack_pack_object(&mp_pck, key);
+ }
+
+ /* Package value */
+ sval = flb_sp_key_to_value(cmd_key->name, map,
+ cmd_key->subkeys);
+ if (sval) {
+ msgpack_pack_object(&mp_pck, sval->o);
+ flb_sp_key_value_destroy(sval);
+ }
+
+ map_entries++;
+ }
+ }
+
+ /* Final Map size adjustment */
+ if (map_entries == 0) {
+ mp_sbuf.size = no_data;
+ }
+ else {
+ /*
+ * The fields were packed, now we need to adjust the map size
+ * to set the proper number of fields appended to the record.
+ */
+ tmp = mp_sbuf.data + map_off;
+ h = tmp[0];
+ if (h >> 4 == 0x8) {
+ *tmp = (uint8_t) 0x8 << 4 | ((uint8_t) map_entries);
+ }
+ else if (h == 0xde) {
+ tmp++;
+ pack_uint16(tmp, map_entries);
+ }
+ else if (h == 0xdf) {
+ tmp++;
+ pack_uint32(tmp, map_entries);
+ }
+ }
+ }
+
+ msgpack_unpacked_destroy(&result);
+
+ if (records == 0) {
+ msgpack_sbuffer_destroy(&mp_sbuf);
+ return 0;
+ }
+
+ /* Use snapshot out buffer if it is flush stream */
+ if (cmd->type == FLB_SP_FLUSH_SNAPSHOT) {
+ if (snapshot_out_size == 0) {
+ msgpack_sbuffer_destroy(&mp_sbuf);
+ flb_free(snapshot_out_buffer);
+ return 0;
+ }
+ else {
+ *out_buf = snapshot_out_buffer;
+ *out_size = snapshot_out_size;
+ return records;
+ }
+ }
+
+ /* set outgoing results */
+ *out_buf = mp_sbuf.data;
+ *out_size = mp_sbuf.size;
+
+ return records;
+}
+
+int sp_process_hopping_slot(const char *tag, int tag_len,
+ struct flb_sp_task *task)
+{
+ int i;
+ int key_id;
+ int map_entries;
+ int gb_entries;
+ struct flb_sp_cmd *cmd = task->cmd;
+ struct mk_list *head;
+ struct mk_list *head_hs;
+ struct aggregate_node *aggr_node;
+ struct aggregate_node *aggr_node_hs;
+ struct aggregate_node *aggr_node_prev;
+ struct flb_sp_hopping_slot *hs;
+ struct flb_sp_hopping_slot *hs_;
+ struct rb_tree_node *rb_result;
+ struct flb_sp_cmd_key *ckey;
+ rb_result_t result;
+
+ map_entries = mk_list_size(&cmd->keys);
+ gb_entries = mk_list_size(&cmd->gb_keys);
+
+ /* Initialize a hoping slot */
+ hs = flb_calloc(1, sizeof(struct flb_sp_hopping_slot));
+ if (!hs) {
+ flb_errno();
+ return -1;
+ }
+
+ mk_list_init(&hs->aggregate_list);
+ rb_tree_new(&hs->aggregate_tree, flb_sp_groupby_compare);
+
+ /* Loop over aggregation nodes on window */
+ mk_list_foreach(head, &task->window.aggregate_list) {
+ /* Window aggregation node */
+ aggr_node = mk_list_entry(head, struct aggregate_node, _head);
+
+ /* Create a hopping slot aggregation node */
+ aggr_node_hs = flb_calloc(1, sizeof(struct aggregate_node));
+ if (!aggr_node_hs) {
+ flb_errno();
+ flb_free(hs);
+ return -1;
+ }
+
+ aggr_node_hs->nums = malloc(sizeof(struct aggregate_node) * map_entries);
+ if (!aggr_node_hs->nums) {
+ flb_errno();
+ flb_free(hs);
+ flb_free(aggr_node_hs);
+ return -1;
+ }
+
+ memcpy(aggr_node_hs->nums, aggr_node->nums, sizeof(struct aggregate_num) * map_entries);
+ aggr_node_hs->records = aggr_node->records;
+
+ /* Clone aggregate data */
+ key_id = 0;
+ mk_list_foreach(head_hs, &cmd->keys) {
+ ckey = mk_list_entry(head_hs, struct flb_sp_cmd_key, _head);
+
+ if (ckey->aggr_func) {
+ if (!aggr_node_hs->aggregate_data) {
+ aggr_node_hs->aggregate_data = (struct aggregate_data **)
+ flb_calloc(1, sizeof(struct aggregate_data *) * map_entries);
+ if (!aggr_node_hs->aggregate_data) {
+ flb_errno();
+ flb_free(hs);
+ flb_free(aggr_node_hs->nums);
+ flb_free(aggr_node_hs);
+ return -1;
+ }
+ }
+
+ if (aggregate_func_clone[ckey->aggr_func - 1](aggr_node_hs, aggr_node, ckey, key_id) == -1) {
+ flb_errno();
+ flb_free(aggr_node_hs->nums);
+ flb_free(aggr_node_hs->aggregate_data);
+ flb_free(aggr_node_hs);
+ flb_free(hs);
+ return -1;
+ }
+ }
+
+ key_id++;
+ }
+
+ /* Traverse over previous slots to calculate values/record numbers */
+ mk_list_foreach(head_hs, &task->window.hopping_slot) {
+ hs_ = mk_list_entry(head_hs, struct flb_sp_hopping_slot, _head);
+ result = rb_tree_find(&hs_->aggregate_tree, aggr_node, &rb_result);
+ /* If corresponding aggregation node exists in previous hopping slot,
+ * calculate aggregation values
+ */
+ if (result == RB_OK) {
+ aggr_node_prev = mk_list_entry(rb_result, struct aggregate_node,
+ _rb_head);
+ aggr_node_hs->records -= aggr_node_prev->records;
+
+ key_id = 0;
+ ckey = mk_list_entry_first(&cmd->keys, struct flb_sp_cmd_key,
+ _head);
+ for (i = 0; i < map_entries; i++) {
+ if (ckey->aggr_func) {
+ aggregate_func_remove[ckey->aggr_func - 1](aggr_node_hs, aggr_node_prev, i);
+ }
+
+ ckey = mk_list_entry_next(&ckey->_head, struct flb_sp_cmd_key,
+ _head, &cmd->keys);
+ }
+ }
+ }
+
+ if (aggr_node_hs->records > 0) {
+ aggr_node_hs->groupby_nums =
+ flb_calloc(1, sizeof(struct aggregate_node) * gb_entries);
+ if (gb_entries > 0 && !aggr_node_hs->groupby_nums) {
+ flb_errno();
+ flb_free(hs);
+ flb_free(aggr_node_hs->nums);
+ flb_free(aggr_node_hs->aggregate_data);
+ flb_free(aggr_node_hs);
+ return -1;
+ }
+
+ if (aggr_node_hs->groupby_nums != NULL) {
+ memcpy(aggr_node_hs->groupby_nums, aggr_node->groupby_nums,
+ sizeof(struct aggregate_num) * gb_entries);
+ }
+
+ aggr_node_hs->nums_size = aggr_node->nums_size;
+ aggr_node_hs->groupby_keys = aggr_node->groupby_keys;
+
+ rb_tree_insert(&hs->aggregate_tree, aggr_node_hs, &aggr_node_hs->_rb_head);
+ mk_list_add(&aggr_node_hs->_head, &hs->aggregate_list);
+ }
+ else {
+ flb_free(aggr_node_hs->nums);
+ flb_free(aggr_node_hs->aggregate_data);
+ flb_free(aggr_node_hs);
+ }
+ }
+
+ hs->records = task->window.records;
+ mk_list_foreach(head_hs, &task->window.hopping_slot) {
+ hs_ = mk_list_entry(head_hs, struct flb_sp_hopping_slot, _head);
+ hs->records -= hs_->records;
+ }
+
+ mk_list_add(&hs->_head, &task->window.hopping_slot);
+
+ return 0;
+}
+
+/* Iterate and find input chunks to process */
+int flb_sp_do(struct flb_sp *sp, struct flb_input_instance *in,
+ const char *tag, int tag_len,
+ const char *buf_data, size_t buf_size)
+
+{
+ int ret;
+ size_t out_size;
+ char *out_buf;
+ struct mk_list *head;
+ struct flb_sp_task *task;
+ struct flb_sp_cmd *cmd;
+
+ /* Lookup tasks that match the incoming instance data */
+ mk_list_foreach(head, &sp->tasks) {
+ task = mk_list_entry(head, struct flb_sp_task, _head);
+ cmd = task->cmd;
+
+ if (cmd->source_type == FLB_SP_STREAM) {
+ if (task->source_instance != in) {
+ continue;
+ }
+ }
+ else if (cmd->source_type == FLB_SP_TAG) {
+ ret = flb_router_match(tag, tag_len, cmd->source_name, NULL);
+ if (ret == FLB_FALSE) {
+ continue;
+ }
+ }
+
+ /* We found a task that matches the stream rule */
+ if (task->aggregate_keys == FLB_TRUE) {
+ ret = sp_process_data_aggr(buf_data, buf_size,
+ tag, tag_len,
+ task, sp, in->config->stream_processor_str_conv);
+
+ if (ret == -1) {
+ flb_error("[sp] error processing records for '%s'",
+ task->name);
+ continue;
+ }
+
+ if (flb_sp_window_populate(task, buf_data, buf_size) == -1) {
+ flb_error("[sp] error populating window for '%s'",
+ task->name);
+ continue;
+ }
+
+ if (task->window.type == FLB_SP_WINDOW_DEFAULT) {
+ package_results(tag, tag_len, &out_buf, &out_size, task);
+ flb_sp_window_prune(task);
+ }
+ }
+ else {
+ ret = sp_process_data(tag, tag_len,
+ buf_data, buf_size,
+ &out_buf, &out_size,
+ task, sp);
+
+ if (ret == -1) {
+ flb_error("[sp] error processing records for '%s'",
+ task->name);
+ continue;
+ }
+ }
+
+ if (ret == 0) {
+ /* no records */
+ continue;
+ }
+
+ /*
+ * This task involves append data to a stream, which
+ * means: register the output of the query as data
+ * generated by an input instance plugin.
+ */
+ if (task->aggregate_keys != FLB_TRUE ||
+ task->window.type == FLB_SP_WINDOW_DEFAULT) {
+ /*
+ * Add to stream processing stream if there is no
+ * aggregation function. Otherwise, write it at timer event
+ */
+ if (task->stream) {
+ flb_sp_stream_append_data(out_buf, out_size, task->stream);
+ }
+ else {
+ flb_pack_print(out_buf, out_size);
+ flb_free(out_buf);
+ }
+ }
+ }
+
+ return -1;
+}
+
+int flb_sp_fd_event(int fd, struct flb_sp *sp)
+{
+ bool update_timer_event;
+ char *out_buf;
+ char *tag = NULL;
+ int tag_len = 0;
+ int fd_timeout = 0;
+ size_t out_size;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_sp_task *task;
+ struct flb_input_instance *in = NULL;
+
+ /* Lookup Tasks that matches the incoming event */
+ mk_list_foreach_safe(head, tmp, &sp->tasks) {
+ task = mk_list_entry(head, struct flb_sp_task, _head);
+
+ if (fd == task->window.fd) {
+ update_timer_event = task->window.type == FLB_SP_WINDOW_HOPPING &&
+ task->window.first_hop;
+
+ in = task->source_instance;
+ if (in) {
+ if (in->tag && in->tag_len > 0) {
+ tag = in->tag;
+ tag_len = in->tag_len;
+ }
+ else {
+ tag = in->name;
+ tag_len = strlen(in->name);
+ }
+ }
+ else {
+ in = NULL;
+ }
+
+ if (task->window.records > 0) {
+ /* find input tag from task source */
+ package_results(tag, tag_len, &out_buf, &out_size, task);
+ if (task->stream) {
+ flb_sp_stream_append_data(out_buf, out_size, task->stream);
+ }
+ else {
+ flb_pack_print(out_buf, out_size);
+ flb_free(out_buf);
+ }
+
+ }
+
+ flb_sp_window_prune(task);
+
+ flb_utils_timer_consume(fd);
+
+ if (update_timer_event && in) {
+ task->window.first_hop = false;
+ mk_event_timeout_destroy(in->config->evl, &task->window.event);
+ mk_event_closesocket(fd);
+
+ fd_timeout = mk_event_timeout_create(in->config->evl,
+ task->window.advance_by, (long) 0,
+ &task->window.event);
+ if (fd_timeout == -1) {
+ flb_error("[sp] registration for task (updating timer event) %s failed", task->name);
+ return -1;
+ }
+ task->window.fd = fd_timeout;
+ }
+
+ break;
+ }
+ else if (fd == task->window.fd_hop) {
+ in = task->source_instance;
+ if (in) {
+ if (in->tag && in->tag_len > 0) {
+ tag = in->tag;
+ tag_len = in->tag_len;
+ }
+ else {
+ tag = in->name;
+ tag_len = strlen(in->name);
+ }
+ }
+ sp_process_hopping_slot(tag, tag_len, task);
+ flb_utils_timer_consume(fd);
+ }
+ }
+ return 0;
+}
+
+/* Destroy stream processor context */
+void flb_sp_destroy(struct flb_sp *sp)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_sp_task *task;
+
+ /* destroy tasks */
+ mk_list_foreach_safe(head, tmp, &sp->tasks) {
+ task = mk_list_entry(head, struct flb_sp_task, _head);
+ flb_sp_task_destroy(task);
+ }
+
+ flb_free(sp);
+}
diff --git a/fluent-bit/src/stream_processor/flb_sp_aggregate_func.c b/fluent-bit/src/stream_processor/flb_sp_aggregate_func.c
new file mode 100644
index 000000000..624be878c
--- /dev/null
+++ b/fluent-bit/src/stream_processor/flb_sp_aggregate_func.c
@@ -0,0 +1,364 @@
+#include <fluent-bit/stream_processor/flb_sp.h>
+#include <fluent-bit/stream_processor/flb_sp_parser.h>
+#include <fluent-bit/stream_processor/flb_sp_aggregate_func.h>
+
+char aggregate_func_string[AGGREGATE_FUNCTIONS][sizeof("TIMESERIES_FORECAST") + 1] = {
+ "AVG",
+ "SUM",
+ "COUNT",
+ "MIN",
+ "MAX",
+ "TIMESERIES_FORECAST"
+};
+
+int aggregate_func_clone_nop(struct aggregate_node *aggr_node,
+ struct aggregate_node *aggr_node_prev,
+ struct flb_sp_cmd_key *ckey,
+ int key_id) {
+ return 0;
+}
+
+int aggregate_func_clone_timeseries_forecast(struct aggregate_node *aggr_node_clone,
+ struct aggregate_node *aggr_node,
+ struct flb_sp_cmd_key *ckey,
+ int key_id) {
+ struct timeseries_forecast *forecast_clone;
+ struct timeseries_forecast *forecast;
+
+ forecast_clone = (struct timeseries_forecast *) aggr_node_clone->aggregate_data[key_id];
+ if (!forecast_clone) {
+ forecast_clone = (struct timeseries_forecast *) flb_calloc(1, sizeof(struct timeseries_forecast));
+ if (!forecast_clone) {
+ return -1;
+ }
+
+ forecast_clone->future_time = ckey->constant;
+ aggr_node_clone->aggregate_data[key_id] = (struct aggregate_data *) forecast_clone;
+ }
+
+ forecast = (struct timeseries_forecast *) aggr_node->aggregate_data[key_id];
+
+ forecast_clone->sigma_x = forecast->sigma_x;
+ forecast_clone->sigma_y = forecast->sigma_y;
+ forecast_clone->sigma_xy = forecast->sigma_xy;
+ forecast_clone->sigma_x2 = forecast->sigma_x2;
+
+ return 0;
+}
+
+/* Summarize a value into the temporary array considering data type */
+void aggregate_func_add_sum(struct aggregate_node *aggr_node,
+ struct flb_sp_cmd_key *ckey,
+ int key_id,
+ struct flb_time *tms,
+ int64_t ival, double dval) {
+ if (aggr_node->nums[key_id].type == FLB_SP_NUM_I64) {
+ aggr_node->nums[key_id].i64 += ival;
+ aggr_node->nums[key_id].ops++;
+ }
+ else if (aggr_node->nums[key_id].type == FLB_SP_NUM_F64) {
+ if (dval != 0.0) {
+ aggr_node->nums[key_id].f64 += dval;
+ }
+ else {
+ aggr_node->nums[key_id].f64 += (double) ival;
+ }
+ aggr_node->nums[key_id].ops++;
+ }
+}
+
+void aggregate_func_add_count(struct aggregate_node *aggr_node,
+ struct flb_sp_cmd_key *ckey,
+ int key_id,
+ struct flb_time *tms,
+ int64_t ival, double dval) {
+}
+
+/* Calculate the minimum value considering data type */
+void aggregate_func_add_min(struct aggregate_node *aggr_node,
+ struct flb_sp_cmd_key *ckey,
+ int key_id,
+ struct flb_time *tms,
+ int64_t ival, double dval) {
+
+ if (aggr_node->nums[key_id].type == FLB_SP_NUM_I64) {
+ if (aggr_node->nums[key_id].ops == 0) {
+ aggr_node->nums[key_id].i64 = ival;
+ aggr_node->nums[key_id].ops++;
+ }
+ else {
+ if (aggr_node->nums[key_id].i64 > ival) {
+ aggr_node->nums[key_id].i64 = ival;
+ aggr_node->nums[key_id].ops++;
+ }
+ }
+ }
+ else if (aggr_node->nums[key_id].type == FLB_SP_NUM_F64) {
+ if (dval != 0.0) {
+ if (aggr_node->nums[key_id].ops == 0) {
+ aggr_node->nums[key_id].f64 = dval;
+ aggr_node->nums[key_id].ops++;
+ }
+ else {
+ if (aggr_node->nums[key_id].f64 > dval) {
+ aggr_node->nums[key_id].f64 = dval;
+ aggr_node->nums[key_id].ops++;
+ }
+ }
+ }
+ else {
+ if (aggr_node->nums[key_id].ops == 0) {
+ aggr_node->nums[key_id].f64 = (double) ival;
+ aggr_node->nums[key_id].ops++;
+ }
+ else {
+ if (aggr_node->nums[key_id].f64 > (double) ival) {
+ aggr_node->nums[key_id].f64 = ival;
+ aggr_node->nums[key_id].ops++;
+ }
+ }
+ }
+ }
+}
+
+/* Calculate the maximum value considering data type */
+void aggregate_func_add_max(struct aggregate_node *aggr_node,
+ struct flb_sp_cmd_key *ckey,
+ int key_id,
+ struct flb_time *tms,
+ int64_t ival, double dval) {
+ if (aggr_node->nums[key_id].type == FLB_SP_NUM_I64) {
+ if (aggr_node->nums[key_id].ops == 0) {
+ aggr_node->nums[key_id].i64 = ival;
+ aggr_node->nums[key_id].ops++;
+ }
+ else {
+ if (aggr_node->nums[key_id].i64 < ival) {
+ aggr_node->nums[key_id].i64 = ival;
+ aggr_node->nums[key_id].ops++;
+ }
+ }
+ }
+ else if (aggr_node->nums[key_id].type == FLB_SP_NUM_F64) {
+ if (dval != 0.0) {
+ if (aggr_node->nums[key_id].ops == 0) {
+ aggr_node->nums[key_id].f64 = dval;
+ aggr_node->nums[key_id].ops++;
+ }
+ else {
+ if (aggr_node->nums[key_id].f64 < dval) {
+ aggr_node->nums[key_id].f64 = dval;
+ aggr_node->nums[key_id].ops++;
+ }
+ }
+ }
+ else {
+ if (aggr_node->nums[key_id].ops == 0) {
+ aggr_node->nums[key_id].f64 = (double) ival;
+ aggr_node->nums[key_id].ops++;
+ }
+ else {
+ if (aggr_node->nums[key_id].f64 < (double) ival) {
+ aggr_node->nums[key_id].f64 = (double) ival;
+ aggr_node->nums[key_id].ops++;
+ }
+ }
+ }
+ }
+}
+
+void aggregate_func_calc_avg(struct aggregate_node *aggr_node,
+ struct flb_sp_cmd_key *ckey,
+ msgpack_packer *mp_pck,
+ int key_id) {
+ double dval = 0.0;
+ /* average = sum(values) / records */
+ if (aggr_node->nums[key_id].type == FLB_SP_NUM_I64) {
+ dval = (double) aggr_node->nums[key_id].i64 / aggr_node->records;
+ }
+ else if (aggr_node->nums[key_id].type == FLB_SP_NUM_F64) {
+ dval = (double) aggr_node->nums[key_id].f64 / aggr_node->records;
+ }
+
+ msgpack_pack_float(mp_pck, dval);
+}
+
+void aggregate_func_calc_sum(struct aggregate_node *aggr_node,
+ struct flb_sp_cmd_key *ckey,
+ msgpack_packer *mp_pck,
+ int key_id) {
+ /* pack result stored in nums[key_id] */
+ if (aggr_node->nums[key_id].type == FLB_SP_NUM_I64) {
+ msgpack_pack_int64(mp_pck, aggr_node->nums[key_id].i64);
+ }
+ else if (aggr_node->nums[key_id].type == FLB_SP_NUM_F64) {
+ msgpack_pack_float(mp_pck, aggr_node->nums[key_id].f64);
+ }
+}
+
+void aggregate_func_calc_count(struct aggregate_node *aggr_node,
+ struct flb_sp_cmd_key *ckey,
+ msgpack_packer *mp_pck,
+ int key_id) {
+ /* number of records in total */
+ msgpack_pack_int64(mp_pck, aggr_node->records);
+}
+
+void aggregate_func_remove_sum(struct aggregate_node *aggr_node,
+ struct aggregate_node *aggr_node_prev,
+ int key_id) {
+ if (aggr_node->nums[key_id].type == FLB_SP_NUM_I64) {
+ aggr_node->nums[key_id].i64 -= aggr_node_prev->nums[key_id].i64;
+ }
+ else if (aggr_node->nums[key_id].type == FLB_SP_NUM_F64) {
+ aggr_node->nums[key_id].f64 -= aggr_node_prev->nums[key_id].f64;
+ }
+}
+
+void aggregate_func_remove_nop(struct aggregate_node *aggr_node,
+ struct aggregate_node *aggr_node_prev,
+ int key_id) {
+}
+
+void aggregate_func_add_timeseries_forecast(struct aggregate_node *aggr_node,
+ struct flb_sp_cmd_key *ckey,
+ int key_id,
+ struct flb_time *tms,
+ int64_t ival, double dval)
+{
+ double x;
+ double y;
+ struct timeseries_forecast *forecast;
+
+ forecast = (struct timeseries_forecast *) aggr_node->aggregate_data[key_id];
+ if (!forecast) {
+ forecast = (struct timeseries_forecast *) flb_calloc(1, sizeof(struct timeseries_forecast));
+ /* fixme: return if error */
+
+ forecast->future_time = ckey->constant;
+ aggr_node->aggregate_data[key_id] = (struct aggregate_data *) forecast;
+ }
+
+ if (!forecast->offset) {
+ forecast->offset = flb_time_to_double(tms);
+ }
+
+ x = flb_time_to_double(tms) - forecast->offset;
+
+ forecast->latest_x = x;
+
+ if (ival) {
+ y = (double) ival;
+ }
+ else {
+ y = dval;
+ }
+
+ forecast->sigma_x += x;
+ forecast->sigma_y += y;
+
+ forecast->sigma_xy += x * y;
+ forecast->sigma_x2 += x * x;
+}
+
+void aggregate_func_calc_timeseries_forecast(struct aggregate_node *aggr_node,
+ struct flb_sp_cmd_key *ckey,
+ msgpack_packer *mp_pck,
+ int key_id)
+{
+ double mean_x;
+ double mean_y;
+ double var_x;
+ double cov_xy;
+ double result;
+ /* y = b0 + b1 * x */
+ double b0;
+ double b1;
+ struct timeseries_forecast *forecast;
+
+ forecast = (struct timeseries_forecast *) aggr_node->aggregate_data[key_id];
+
+ mean_x = forecast->sigma_x / aggr_node->records;
+ mean_y = forecast->sigma_y / aggr_node->records;
+ cov_xy = (forecast->sigma_xy / (double) aggr_node->records) - mean_x * mean_y;
+ var_x = (forecast->sigma_x2 / aggr_node->records) - mean_x * mean_x;
+
+ b1 = cov_xy / var_x;
+ b0 = mean_y - b1 * mean_x;
+
+ result = b0 + b1 * (forecast->future_time + forecast->latest_x);
+
+ msgpack_pack_float(mp_pck, result);
+}
+
+void aggregate_func_remove_timeseries_forecast(struct aggregate_node *aggr_node,
+ struct aggregate_node *aggr_node_prev,
+ int key_id)
+{
+ struct timeseries_forecast *forecast_w;
+ struct timeseries_forecast *forecast_h;
+
+ forecast_w = (struct timeseries_forecast *) aggr_node->aggregate_data[key_id];
+ forecast_h = (struct timeseries_forecast *) aggr_node_prev->aggregate_data[key_id];
+
+ forecast_w->sigma_x -= forecast_h->sigma_x;
+ forecast_w->sigma_y -= forecast_h->sigma_y;
+ forecast_w->sigma_xy -= forecast_h->sigma_xy;
+ forecast_w->sigma_x2 -= forecast_h->sigma_x2;
+}
+
+void aggregate_func_destroy_sum(struct aggregate_node *aggr_node,
+ int key_id)
+{
+}
+
+void aggregate_func_destroy_timeseries_forecast(struct aggregate_node *aggr_node,
+ int key_id)
+{
+ flb_free(aggr_node->aggregate_data[key_id]);
+}
+
+aggregate_function_clone aggregate_func_clone[AGGREGATE_FUNCTIONS] = {
+ aggregate_func_clone_nop,
+ aggregate_func_clone_nop,
+ aggregate_func_clone_nop,
+ aggregate_func_clone_nop,
+ aggregate_func_clone_nop,
+ aggregate_func_clone_timeseries_forecast,
+};
+
+aggregate_function_add aggregate_func_add[AGGREGATE_FUNCTIONS] = {
+ aggregate_func_add_sum,
+ aggregate_func_add_sum,
+ aggregate_func_add_count,
+ aggregate_func_add_min,
+ aggregate_func_add_max,
+ aggregate_func_add_timeseries_forecast,
+};
+
+aggregate_function_calc aggregate_func_calc[AGGREGATE_FUNCTIONS] = {
+ aggregate_func_calc_avg,
+ aggregate_func_calc_sum,
+ aggregate_func_calc_count,
+ aggregate_func_calc_sum,
+ aggregate_func_calc_sum,
+ aggregate_func_calc_timeseries_forecast,
+};
+
+aggregate_function_remove aggregate_func_remove[AGGREGATE_FUNCTIONS] = {
+ aggregate_func_remove_sum,
+ aggregate_func_remove_sum,
+ aggregate_func_remove_nop,
+ aggregate_func_remove_nop,
+ aggregate_func_remove_nop,
+ aggregate_func_remove_timeseries_forecast,
+};
+
+aggregate_function_destroy aggregate_func_destroy[AGGREGATE_FUNCTIONS] = {
+ aggregate_func_destroy_sum,
+ aggregate_func_destroy_sum,
+ aggregate_func_destroy_sum,
+ aggregate_func_destroy_sum,
+ aggregate_func_destroy_sum,
+ aggregate_func_destroy_timeseries_forecast,
+};
diff --git a/fluent-bit/src/stream_processor/flb_sp_func_record.c b/fluent-bit/src/stream_processor/flb_sp_func_record.c
new file mode 100644
index 000000000..26625a1ec
--- /dev/null
+++ b/fluent-bit/src/stream_processor/flb_sp_func_record.c
@@ -0,0 +1,77 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/stream_processor/flb_sp.h>
+#include <fluent-bit/stream_processor/flb_sp_parser.h>
+
+static inline void pack_key(msgpack_packer *mp_pck,
+ struct flb_sp_cmd_key *cmd_key,
+ const char *name, int len)
+{
+ if (cmd_key->alias) {
+ msgpack_pack_str(mp_pck, flb_sds_len(cmd_key->alias));
+ msgpack_pack_str_body(mp_pck, cmd_key->alias,
+ flb_sds_len(cmd_key->alias));
+ }
+ else {
+ msgpack_pack_str(mp_pck, len);
+ msgpack_pack_str_body(mp_pck, name, len);
+ }
+}
+
+static int func_tag(const char *tag, int tag_len,
+ msgpack_packer *mp_pck, struct flb_sp_cmd_key *cmd_key)
+{
+ pack_key(mp_pck, cmd_key, "RECORD_TAG()", 12);
+ msgpack_pack_str(mp_pck, tag_len);
+ msgpack_pack_str_body(mp_pck, tag, tag_len);
+
+ return 1;
+}
+
+static int func_time(struct flb_time *tms, msgpack_packer *mp_pck,
+ struct flb_sp_cmd_key *cmd_key)
+{
+ double t;
+
+ t = flb_time_to_double(tms);
+ pack_key(mp_pck, cmd_key, "RECORD_TIME()", 13);
+ msgpack_pack_double(mp_pck, t);
+
+ return 1;
+}
+
+/*
+ * Wrapper to handle record functions, returns the number of entries added
+ * to the map.
+ */
+int flb_sp_func_record(const char *tag, int tag_len, struct flb_time *tms,
+ msgpack_packer *mp_pck, struct flb_sp_cmd_key *cmd_key)
+{
+ switch (cmd_key->record_func) {
+ case FLB_SP_RECORD_TAG:
+ return func_tag(tag, tag_len, mp_pck, cmd_key);
+ case FLB_SP_RECORD_TIME:
+ return func_time(tms, mp_pck, cmd_key);
+ };
+
+ return 0;
+}
diff --git a/fluent-bit/src/stream_processor/flb_sp_func_time.c b/fluent-bit/src/stream_processor/flb_sp_func_time.c
new file mode 100644
index 000000000..3e75eb775
--- /dev/null
+++ b/fluent-bit/src/stream_processor/flb_sp_func_time.c
@@ -0,0 +1,95 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_pack.h>
+#include <fluent-bit/stream_processor/flb_sp.h>
+#include <fluent-bit/stream_processor/flb_sp_parser.h>
+
+static inline void pack_key(msgpack_packer *mp_pck,
+ struct flb_sp_cmd_key *cmd_key,
+ const char *name, int len)
+{
+ if (cmd_key->alias) {
+ msgpack_pack_str(mp_pck, flb_sds_len(cmd_key->alias));
+ msgpack_pack_str_body(mp_pck, cmd_key->alias,
+ flb_sds_len(cmd_key->alias));
+ }
+ else {
+ msgpack_pack_str(mp_pck, len);
+ msgpack_pack_str_body(mp_pck, name, len);
+ }
+}
+
+static int func_now(msgpack_packer *mp_pck, struct flb_sp_cmd_key *cmd_key)
+{
+ size_t len;
+ time_t now;
+ char buf[32];
+ struct tm *local;
+
+ local = flb_malloc(sizeof(struct tm));
+ if (!local) {
+ flb_errno();
+ return 0;
+ }
+
+ /* Get current system time */
+ now = time(NULL);
+ localtime_r(&now, local);
+
+ /* Format string value */
+ len = strftime(buf, sizeof(buf) - 1, "%Y-%m-%d %H:%M:%S", local);
+ flb_free(local);
+
+ pack_key(mp_pck, cmd_key, "NOW()", 5);
+ msgpack_pack_str(mp_pck, len);
+ msgpack_pack_str_body(mp_pck, buf, len);
+
+ return 1;
+}
+
+static int func_unix_timestamp(msgpack_packer *mp_pck,
+ struct flb_sp_cmd_key *cmd_key)
+{
+ time_t now;
+
+ /* Get unix timestamp */
+ now = time(NULL);
+
+ pack_key(mp_pck, cmd_key, "UNIX_TIMESTAMP()", 16);
+ msgpack_pack_uint64(mp_pck, now);
+ return 1;
+}
+
+/*
+ * Wrapper to handle time functions, returns the number of entries added
+ * to the map.
+ */
+int flb_sp_func_time(msgpack_packer *mp_pck, struct flb_sp_cmd_key *cmd_key)
+{
+ switch (cmd_key->time_func) {
+ case FLB_SP_NOW:
+ return func_now(mp_pck, cmd_key);
+ case FLB_SP_UNIX_TIMESTAMP:
+ return func_unix_timestamp(mp_pck, cmd_key);
+ };
+
+ return 0;
+}
diff --git a/fluent-bit/src/stream_processor/flb_sp_groupby.c b/fluent-bit/src/stream_processor/flb_sp_groupby.c
new file mode 100644
index 000000000..9cd72c230
--- /dev/null
+++ b/fluent-bit/src/stream_processor/flb_sp_groupby.c
@@ -0,0 +1,82 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/stream_processor/flb_sp.h>
+
+int flb_sp_groupby_compare(const void *lhs, const void *rhs)
+{
+ int i;
+ int strcmp_result;
+ struct aggregate_node *left = (struct aggregate_node *) lhs;
+ struct aggregate_node *right = (struct aggregate_node *) rhs;
+ struct aggregate_num *lval;
+ struct aggregate_num *rval;
+
+ for (i = 0; i < left->groupby_keys; i++) {
+ lval = &left->groupby_nums[i];
+ rval = &right->groupby_nums[i];
+
+ /* Convert integer to double if a float value appears on one side */
+ if (lval->type == FLB_SP_NUM_I64 && rval->type == FLB_SP_NUM_F64) {
+ lval->type = FLB_SP_NUM_F64;
+ lval->f64 = (double) lval->i64;
+ }
+ else if (lval->type == FLB_SP_NUM_F64 && rval->type == FLB_SP_NUM_I64) {
+ rval->type = FLB_SP_NUM_F64;
+ rval->f64 = (double) rval->i64;
+ }
+
+ /* Comparison */
+ if (lval->type == FLB_SP_BOOLEAN && rval->type == FLB_SP_BOOLEAN) {
+ if (lval->boolean != rval->boolean) {
+ return 1;
+ }
+ }
+ else if (lval->type == FLB_SP_NUM_I64 && rval->type == FLB_SP_NUM_I64) {
+ if (lval->i64 > rval->i64) {
+ return 1;
+ }
+
+ if (lval->i64 < rval->i64) {
+ return -1;
+ }
+ }
+ else if (lval->type == FLB_SP_NUM_F64 && rval->type == FLB_SP_NUM_F64) {
+ if (lval->f64 > rval->f64) {
+ return 1;
+ }
+
+ if (lval->f64 < rval->f64) {
+ return -1;
+ }
+ }
+ else if (lval->type == FLB_SP_STRING && rval->type == FLB_SP_STRING) {
+ strcmp_result = strcmp((const char *) lval->string, (const char *) rval->string);
+ if (strcmp_result != 0) {
+ return strcmp_result;
+ }
+ }
+ else { /* Sides have different types */
+ return -1;
+ }
+ }
+
+ return 0;
+}
diff --git a/fluent-bit/src/stream_processor/flb_sp_key.c b/fluent-bit/src/stream_processor/flb_sp_key.c
new file mode 100644
index 000000000..945621843
--- /dev/null
+++ b/fluent-bit/src/stream_processor/flb_sp_key.c
@@ -0,0 +1,231 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_slist.h>
+
+#include <fluent-bit/stream_processor/flb_sp.h>
+#include <fluent-bit/stream_processor/flb_sp_parser.h>
+
+void flb_sp_key_value_print(struct flb_sp_value *v)
+{
+ if (v->type == FLB_EXP_BOOL) {
+ if (v->val.boolean) {
+ printf("true");
+ }
+ else {
+ printf("false");
+ }
+ }
+ else if (v->type == FLB_EXP_INT) {
+ printf("%" PRId64, v->val.i64);
+ }
+ else if (v->type == FLB_EXP_FLOAT) {
+ printf("%f", v->val.f64);
+ }
+ else if (v->type == FLB_EXP_STRING) {
+ printf("%s", v->val.string);
+ }
+ else if (v->type == FLB_EXP_NULL) {
+ printf("NULL");
+ }
+}
+
+/* Map msgpack object intp flb_sp_value representation */
+static int msgpack_object_to_sp_value(msgpack_object o,
+ struct flb_sp_value *result)
+{
+ result->o = o;
+
+ /* Compose result with found value */
+ if (o.type == MSGPACK_OBJECT_BOOLEAN) {
+ result->type = FLB_EXP_BOOL;
+ result->val.boolean = o.via.boolean;
+ return 0;
+ }
+ else if (o.type == MSGPACK_OBJECT_POSITIVE_INTEGER ||
+ o.type == MSGPACK_OBJECT_NEGATIVE_INTEGER) {
+ result->type = FLB_EXP_INT;
+ result->val.i64 = o.via.i64;
+ return 0;
+ }
+ else if (o.type == MSGPACK_OBJECT_FLOAT32 ||
+ o.type == MSGPACK_OBJECT_FLOAT) {
+ result->type = FLB_EXP_FLOAT;
+ result->val.f64 = o.via.f64;
+ return 0;
+ }
+ else if (o.type == MSGPACK_OBJECT_STR) {
+ result->type = FLB_EXP_STRING;
+ result->val.string = flb_sds_create_len((char *) o.via.str.ptr,
+ o.via.str.size);
+ return 0;
+ }
+ else if (o.type == MSGPACK_OBJECT_MAP) {
+ /* return boolean 'true', just denoting the existence of the key */
+ result->type = FLB_EXP_BOOL;
+ result->val.boolean = true;
+ return 0;
+ }
+ else if (o.type == MSGPACK_OBJECT_NIL) {
+ result->type = FLB_EXP_NULL;
+ return 0;
+ }
+
+ return -1;
+}
+
+/* Lookup perfect match of sub-keys and map content */
+static int subkey_to_value(msgpack_object *map, struct mk_list *subkeys,
+ struct flb_sp_value *result)
+{
+ int i = 0;
+ int ret;
+ int levels;
+ int matched = 0;
+ msgpack_object *key_found = NULL;
+ msgpack_object key;
+ msgpack_object val;
+ msgpack_object cur_map;
+ struct mk_list *head;
+ struct flb_slist_entry *entry;
+
+ /* Expected number of map levels in the map */
+ levels = mk_list_size(subkeys);
+
+ cur_map = *map;
+
+ mk_list_foreach(head, subkeys) {
+ /* Key expected key entry */
+ entry = mk_list_entry(head, struct flb_slist_entry, _head);
+
+ if (cur_map.type != MSGPACK_OBJECT_MAP) {
+ break;
+ }
+
+ /* Get map entry that matches entry name */
+ for (i = 0; i < cur_map.via.map.size; i++) {
+ key = cur_map.via.map.ptr[i].key;
+ val = cur_map.via.map.ptr[i].val;
+
+ /* A bit obvious, but it's better to validate data type */
+ if (key.type != MSGPACK_OBJECT_STR) {
+ continue;
+ }
+
+ /* Compare strings by length and content */
+ if (flb_sds_cmp(entry->str,
+ (char *) key.via.str.ptr,
+ key.via.str.size) != 0) {
+ key_found = NULL;
+ continue;
+ }
+
+ key_found = &key;
+ cur_map = val;
+ matched++;
+ break;
+ }
+
+ if (levels == matched) {
+ break;
+ }
+ }
+
+ /* No matches */
+ if (!key_found || (matched > 0 && levels != matched)) {
+ return -1;
+ }
+
+ ret = msgpack_object_to_sp_value(val, result);
+ if (ret == -1) {
+ //flb_error("[sp key] cannot process key value");
+ return -1;
+ }
+
+ return 0;
+}
+
+struct flb_sp_value *flb_sp_key_to_value(flb_sds_t ckey,
+ msgpack_object map,
+ struct mk_list *subkeys)
+{
+ int i;
+ int ret;
+ int map_size;
+ msgpack_object key;
+ msgpack_object val;
+ struct flb_sp_value *result;
+
+ map_size = map.via.map.size;
+ for (i = 0; i < map_size; i++) {
+ key = map.via.map.ptr[i].key;
+ val = map.via.map.ptr[i].val;
+
+ /* Compare by length and by key name */
+ if (flb_sds_cmp(ckey, key.via.str.ptr, key.via.str.size) != 0) {
+ continue;
+ }
+
+ result = flb_calloc(1, sizeof(struct flb_sp_value));
+ if (!result) {
+ flb_errno();
+ return NULL;
+ }
+ result->o = val;
+
+ if (val.type == MSGPACK_OBJECT_MAP && subkeys != NULL) {
+ ret = subkey_to_value(&val, subkeys, result);
+ if (ret == 0) {
+ return result;
+ }
+ else {
+ flb_free(result);
+ return NULL;
+ }
+ }
+ else {
+ ret = msgpack_object_to_sp_value(val, result);
+ if (ret == -1) {
+ flb_error("[sp key] cannot process key value");
+ flb_free(result);
+ return NULL;
+ }
+ }
+
+ return result;
+ }
+
+ /*
+ * NULL return means: failed memory allocation, an invalid value,
+ * or non-existing key.
+ */
+ return NULL;
+}
+
+void flb_sp_key_value_destroy(struct flb_sp_value *v)
+{
+ if (v->type == FLB_EXP_STRING) {
+ flb_sds_destroy(v->val.string);
+ }
+ flb_free(v);
+}
diff --git a/fluent-bit/src/stream_processor/flb_sp_snapshot.c b/fluent-bit/src/stream_processor/flb_sp_snapshot.c
new file mode 100644
index 000000000..edef823b4
--- /dev/null
+++ b/fluent-bit/src/stream_processor/flb_sp_snapshot.c
@@ -0,0 +1,277 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_mem.h>
+#include <fluent-bit/stream_processor/flb_sp.h>
+#include <fluent-bit/stream_processor/flb_sp_parser.h>
+#include <fluent-bit/stream_processor/flb_sp_snapshot.h>
+
+static struct flb_sp_snapshot_page *snapshot_page_create()
+{
+ struct flb_sp_snapshot_page *page;
+
+ page = (struct flb_sp_snapshot_page *)
+ flb_calloc(1, sizeof(struct flb_sp_snapshot_page));
+ if (!page) {
+ flb_errno();
+ return NULL;
+ }
+
+ page->snapshot_page = (char *) flb_malloc(SNAPSHOT_PAGE_SIZE);
+ if (!page->snapshot_page) {
+ flb_errno();
+ flb_free(page);
+ return NULL;
+ }
+
+ return page;
+}
+
+static int snapshot_cleanup(struct flb_sp_snapshot *snapshot, struct flb_time *tms)
+{
+ int ok;
+ size_t off;
+ size_t off_copy;
+ msgpack_unpacked result;
+ msgpack_object *obj;
+ struct flb_time tms0;
+ struct flb_sp_snapshot_page *page;
+
+ ok = MSGPACK_UNPACK_SUCCESS;
+ off = 0;
+
+ while (mk_list_is_empty(&snapshot->pages) != 0) {
+ page = mk_list_entry_first(&snapshot->pages, struct flb_sp_snapshot_page,
+ _head);
+ off = page->start_pos;
+ off_copy = off;
+
+ msgpack_unpacked_init(&result);
+
+ while (msgpack_unpack_next(&result, page->snapshot_page, page->end_pos,
+ &off) == ok) {
+
+ if (snapshot->record_limit > 0 &&
+ snapshot->records > snapshot->record_limit) {
+ page->start_pos = off;
+ snapshot->records--;
+ snapshot->size = snapshot->size - (off - off_copy);
+ off_copy = off;
+
+ continue;
+ }
+
+ /* extract timestamp */
+ flb_time_pop_from_msgpack(&tms0, &result, &obj);
+
+ if (snapshot->time_limit > 0 &&
+ tms->tm.tv_sec - tms0.tm.tv_sec > snapshot->time_limit) {
+ page->start_pos = off;
+ snapshot->records--;
+ snapshot->size = snapshot->size - (off - off_copy);
+ off_copy = off;
+
+ continue;
+ }
+
+ break;
+ }
+
+ msgpack_unpacked_destroy(&result);
+
+ /* If page is empty, free the page and move to the next one */
+ if (page->start_pos != page->end_pos) {
+ break;
+ }
+
+ mk_list_del(&page->_head);
+ flb_free(page->snapshot_page);
+ flb_free(page);
+ }
+
+ return 0;
+}
+
+static bool snapshot_page_is_full(struct flb_sp_snapshot_page *page, size_t buf_size)
+{
+ return SNAPSHOT_PAGE_SIZE - page->end_pos < buf_size;
+}
+
+char *flb_sp_snapshot_name_from_flush(flb_sds_t name)
+{
+ return name + sizeof("__flush_") - 1;
+}
+
+int flb_sp_snapshot_update(struct flb_sp_task *task, const char *buf_data,
+ size_t buf_size, struct flb_time *tms)
+{
+ int ok;
+ size_t off = 0;
+ struct flb_time tm;
+ struct flb_sp_snapshot *snapshot;
+ struct flb_sp_snapshot_page *page;
+ msgpack_unpacked result;
+ msgpack_object *obj;
+
+ ok = MSGPACK_UNPACK_SUCCESS;
+ msgpack_unpacked_init(&result);
+
+ if (buf_size <= 0) {
+ return -1;
+ }
+
+ snapshot = (struct flb_sp_snapshot *) task->snapshot;
+
+ /* Create a snapshot pgae if the list is empty */
+ if (mk_list_is_empty(&snapshot->pages) == 0) {
+ page = snapshot_page_create();
+ if (!page) {
+ flb_errno();
+ return -1;
+ }
+
+ mk_list_add(&page->_head, &snapshot->pages);
+ }
+ else {
+ page = mk_list_entry_last(&snapshot->pages, struct flb_sp_snapshot_page, _head);
+
+ if (snapshot_page_is_full(page, buf_size)) {
+ page = snapshot_page_create();
+ if (!page) {
+ flb_errno();
+ return -1;
+ }
+
+ mk_list_add(&page->_head, &snapshot->pages);
+ }
+ }
+
+ memcpy(page->snapshot_page + page->end_pos, buf_data, buf_size);
+ page->end_pos = page->end_pos + buf_size;
+
+ /* Get the last timestamp */
+ while (msgpack_unpack_next(&result, page->snapshot_page,
+ page->end_pos - page->start_pos, &off) == ok) {
+ flb_time_pop_from_msgpack(&tm, &result, &obj);
+ }
+
+ msgpack_unpacked_destroy(&result);
+
+ snapshot->records++;
+ snapshot->size = snapshot->size + buf_size;
+
+ /* Remove records from snapshot pages based on time/length window */
+ snapshot_cleanup(snapshot, tms);
+
+ return 0;
+}
+
+int flb_sp_snapshot_flush(struct flb_sp *sp, struct flb_sp_task *task,
+ char **out_buf_data, size_t *out_buf_size)
+{
+ size_t off;
+ size_t page_size;
+ char *snapshot_name;
+ char *out_buf_data_tmp;
+ struct flb_sp_cmd *cmd;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct mk_list *snapshot_head;
+ struct flb_sp_task *snapshot_task;
+ struct flb_sp_snapshot *snapshot;
+ struct flb_sp_snapshot_page *page;
+
+ off = 0;
+ cmd = task->cmd;
+ snapshot_name = flb_sp_snapshot_name_from_flush(cmd->stream_name);
+
+ /* Lookup Tasks that matches the incoming instance data */
+ mk_list_foreach(head, &sp->tasks) {
+ snapshot_task = mk_list_entry(head, struct flb_sp_task, _head);
+ cmd = snapshot_task->cmd;
+
+ if (cmd->type == FLB_SP_CREATE_SNAPSHOT &&
+ flb_sds_cmp(cmd->stream_name, snapshot_name,
+ strlen(snapshot_name)) == 0) {
+
+ snapshot = (struct flb_sp_snapshot *) snapshot_task->snapshot;
+
+ if (snapshot->size == 0) {
+ break;
+ }
+
+ if (*out_buf_data == NULL) {
+ *out_buf_data = (char *) flb_malloc(snapshot->size);
+ if (!*out_buf_data) {
+ flb_errno();
+ return -1;
+ }
+ *out_buf_size = snapshot->size;
+ }
+ else {
+ out_buf_data_tmp = (char *) flb_realloc(*out_buf_data,
+ *out_buf_size + snapshot->size);
+ if (!out_buf_data_tmp) {
+ flb_errno();
+ return -1;
+ }
+ *out_buf_data = out_buf_data_tmp;
+ *out_buf_size = *out_buf_size + snapshot->size;
+ }
+
+ mk_list_foreach_safe(snapshot_head, tmp, &snapshot->pages) {
+ page = mk_list_entry_first(&snapshot->pages,
+ struct flb_sp_snapshot_page, _head);
+ page_size = page->end_pos - page->start_pos;
+ memcpy(*out_buf_data + off,
+ page->snapshot_page + page->start_pos, page_size);
+ off = off + page_size;
+
+ /* Remove page from list */
+ mk_list_del(&page->_head);
+ flb_free(page->snapshot_page);
+ flb_free(page);
+ }
+
+ mk_list_init(&snapshot->pages);
+
+ snapshot->records = 0;
+ snapshot->size = 0;
+ }
+ }
+
+ return 0;
+}
+
+void flb_sp_snapshot_destroy(struct flb_sp_snapshot *snapshot)
+{
+ struct mk_list *head;
+ struct mk_list *tmp;
+ struct flb_sp_snapshot_page *page;
+
+ if (snapshot != NULL) {
+ mk_list_foreach_safe(head, tmp, &snapshot->pages) {
+ page = mk_list_entry(head, struct flb_sp_snapshot_page, _head);
+ mk_list_del(&page->_head);
+ flb_free(page->snapshot_page);
+ flb_free(page);
+ }
+ flb_free(snapshot);
+ }
+}
diff --git a/fluent-bit/src/stream_processor/flb_sp_stream.c b/fluent-bit/src/stream_processor/flb_sp_stream.c
new file mode 100644
index 000000000..b4f8a37a2
--- /dev/null
+++ b/fluent-bit/src/stream_processor/flb_sp_stream.c
@@ -0,0 +1,168 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_input.h>
+#include <fluent-bit/flb_metrics.h>
+#include <fluent-bit/flb_storage.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/stream_processor/flb_sp.h>
+#include <fluent-bit/stream_processor/flb_sp_parser.h>
+#include <fluent-bit/stream_processor/flb_sp_stream.h>
+
+/* Function defined in plugins/in_stream_processor/sp.c */
+int in_stream_processor_add_chunk(const char *buf_data, size_t buf_size,
+ struct flb_input_instance *in);
+
+int flb_sp_stream_create(const char *name, struct flb_sp_task *task,
+ struct flb_sp *sp)
+{
+ int ret;
+ const char *tmp;
+ struct flb_input_instance *in;
+ struct flb_sp_stream *stream;
+
+ /* The name must be different than an input plugin instance name or alias */
+ ret = flb_input_name_exists(name, sp->config);
+ if (ret == FLB_TRUE) {
+ flb_error("[sp] stream name '%s' already exists", name);
+ return -1;
+ }
+
+ /* Create stream context for 'stream processor' */
+ stream = flb_calloc(1, sizeof(struct flb_sp_stream));
+ if (!stream) {
+ flb_errno();
+ return -1;
+ }
+ stream->name = flb_sds_create(name);
+ if (!stream->name) {
+ flb_free(stream);
+ return -1;
+ }
+
+ /*
+ * Register an input plugin instance using 'in_stream_processor', that one
+ * is used as the parent plugin to ingest data back into Fluent Bit
+ * data pipeline.
+ */
+ in = flb_input_new(sp->config, "stream_processor", NULL, FLB_FALSE);
+ if (!in) {
+ flb_error("[sp] cannot create instance of in_stream_processor");
+ flb_free(stream);
+ return -1;
+ }
+
+ /* Set an alias, otherwise the stream will be called stream_processor.N */
+ ret = flb_input_set_property(in, "alias", name);
+ if (ret == -1) {
+ flb_warn("[sp] cannot set stream name, using fallback name %s",
+ in->name);
+ }
+
+ /*
+ * Lookup for Stream properties: at this point we only care about a
+ * possible Tag defined in the query, e.g:
+ *
+ * CREATE STREAM data WITH(tag='mydata') as SELECT * FROM STREAM:apache;
+ */
+ tmp = flb_sp_cmd_stream_prop_get(task->cmd, "tag");
+ if (tmp) {
+ /*
+ * Duplicate value in a new variable since input instance property
+ * will be released upon plugin exit.
+ */
+ stream->tag = flb_sds_create(tmp);
+ if (!stream->tag) {
+ flb_error("[sp] cannot set Tag property");
+ flb_sp_stream_destroy(stream, sp);
+ return -1;
+ }
+
+ /* Tag property is just an assignation, cannot fail */
+ flb_input_set_property(in, "tag", stream->tag);
+ }
+
+ /*
+ * Check if the new stream is 'routable' or not
+ */
+ tmp = flb_sp_cmd_stream_prop_get(task->cmd, "routable");
+ if (tmp) {
+ stream->routable = flb_utils_bool(tmp);
+ flb_input_set_property(in, "routable", tmp);
+ }
+
+ /*
+ * Set storage type
+ */
+ tmp = flb_sp_cmd_stream_prop_get(task->cmd, "storage.type");
+ if (tmp) {
+ flb_input_set_property(in, "storage.type", tmp);
+ }
+
+ /* Initialize instance */
+ ret = flb_input_instance_init(in, sp->config);
+ if (ret == -1) {
+ flb_error("[sp] cannot initialize instance of in_stream_processor");
+ flb_input_instance_exit(in, sp->config);
+ flb_input_instance_destroy(in);
+ }
+ stream->in = in;
+
+ /* Initialize plugin collector (event callback) */
+ flb_input_collector_start(0, in);
+
+#ifdef FLB_HAVE_METRICS
+ /* Override Metrics title */
+ ret = flb_metrics_title(name, in->metrics);
+ if (ret == -1) {
+ flb_warn("[sp] cannot set metrics title, using fallback name %s",
+ in->name);
+ }
+#endif
+
+ /* Storage context */
+ ret = flb_storage_input_create(sp->config->cio, in);
+ if (ret == -1) {
+ flb_error("[sp] cannot initialize storage for stream '%s'",
+ name);
+ flb_sp_stream_destroy(stream, sp);
+ return -1;
+ }
+
+ task->stream = stream;
+ return 0;
+}
+
+int flb_sp_stream_append_data(const char *buf_data, size_t buf_size,
+ struct flb_sp_stream *stream)
+{
+ return in_stream_processor_add_chunk(buf_data, buf_size, stream->in);
+}
+
+void flb_sp_stream_destroy(struct flb_sp_stream *stream, struct flb_sp *sp)
+{
+ flb_sds_destroy(stream->name);
+ flb_sds_destroy(stream->tag);
+ flb_input_instance_exit(stream->in, sp->config);
+ flb_input_instance_destroy(stream->in);
+ flb_free(stream);
+}
diff --git a/fluent-bit/src/stream_processor/flb_sp_window.c b/fluent-bit/src/stream_processor/flb_sp_window.c
new file mode 100644
index 000000000..3937b4273
--- /dev/null
+++ b/fluent-bit/src/stream_processor/flb_sp_window.c
@@ -0,0 +1,122 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/stream_processor/flb_sp.h>
+#include <fluent-bit/stream_processor/flb_sp_window.h>
+#include <fluent-bit/stream_processor/flb_sp_parser.h>
+#include <fluent-bit/stream_processor/flb_sp_groupby.h>
+#include <fluent-bit/stream_processor/flb_sp_aggregate_func.h>
+
+void flb_sp_window_prune(struct flb_sp_task *task)
+{
+ int i;
+ int map_entries;
+ rb_result_t result;
+ struct aggregate_node *aggr_node;
+ struct aggregate_node *aggr_node_hs;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_sp_hopping_slot *hs;
+ struct rb_tree_node *rb_result;
+ struct flb_sp_cmd_key *ckey;
+ struct flb_sp_cmd *cmd = task->cmd;
+
+ switch (task->window.type) {
+ case FLB_SP_WINDOW_DEFAULT:
+ case FLB_SP_WINDOW_TUMBLING:
+ if (task->window.records > 0) {
+ mk_list_foreach_safe(head, tmp, &task->window.aggregate_list) {
+ aggr_node = mk_list_entry(head, struct aggregate_node, _head);
+ mk_list_del(&aggr_node->_head);
+ flb_sp_aggregate_node_destroy(cmd, aggr_node);
+ }
+
+ rb_tree_destroy(&task->window.aggregate_tree);
+ mk_list_init(&task->window.aggregate_list);
+ rb_tree_new(&task->window.aggregate_tree, flb_sp_groupby_compare);
+ task->window.records = 0;
+ }
+ break;
+ case FLB_SP_WINDOW_HOPPING:
+ if (mk_list_size(&task->window.hopping_slot) == 0) {
+ return;
+ }
+
+ hs = mk_list_entry_first(&task->window.hopping_slot,
+ struct flb_sp_hopping_slot, _head);
+ mk_list_foreach_safe(head, tmp, &task->window.aggregate_list) {
+ aggr_node = mk_list_entry(head, struct aggregate_node, _head);
+ result = rb_tree_find(&hs->aggregate_tree, aggr_node, &rb_result);
+ if (result == RB_OK) {
+ aggr_node_hs = mk_list_entry(rb_result, struct aggregate_node, _rb_head);
+ if (aggr_node_hs->records == aggr_node->records) {
+ rb_tree_remove(&task->window.aggregate_tree, &aggr_node->_rb_head);
+ mk_list_del(&aggr_node->_head);
+ // Destroy aggregation node
+ flb_sp_aggregate_node_destroy(cmd, aggr_node);
+ }
+ else {
+ aggr_node->records -= aggr_node_hs->records;
+ map_entries = mk_list_size(&cmd->keys);
+
+ ckey = mk_list_entry_first(&cmd->keys,
+ struct flb_sp_cmd_key, _head);
+ for (i = 0; i < map_entries; i++) {
+ if (ckey->aggr_func) {
+ aggregate_func_remove[ckey->aggr_func - 1](aggr_node, aggr_node_hs, i);
+ }
+
+ ckey = mk_list_entry_next(&ckey->_head, struct flb_sp_cmd_key,
+ _head, &cmd->keys);
+ }
+ }
+ }
+ }
+ task->window.records -= hs->records;
+
+ /* Destroy hopping slot */
+ mk_list_foreach_safe(head, tmp, &hs->aggregate_list) {
+ aggr_node_hs = mk_list_entry(head, struct aggregate_node, _head);
+ mk_list_del(&aggr_node_hs->_head);
+ flb_sp_aggregate_node_destroy(cmd, aggr_node_hs);
+ }
+ rb_tree_destroy(&hs->aggregate_tree);
+ mk_list_del(&hs->_head);
+ flb_free(hs);
+
+ break;
+ }
+}
+
+int flb_sp_window_populate(struct flb_sp_task *task, const char *buf_data,
+ size_t buf_size)
+{
+ switch (task->window.type) {
+ case FLB_SP_WINDOW_DEFAULT:
+ case FLB_SP_WINDOW_TUMBLING:
+ case FLB_SP_WINDOW_HOPPING:
+ break;
+ default:
+ flb_error("[sp] error populating window for '%s': window type unknown",
+ task->name);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/fluent-bit/src/stream_processor/parser/CMakeLists.txt b/fluent-bit/src/stream_processor/parser/CMakeLists.txt
new file mode 100644
index 000000000..d14b02bbd
--- /dev/null
+++ b/fluent-bit/src/stream_processor/parser/CMakeLists.txt
@@ -0,0 +1,31 @@
+flex_target(lexer sql.l "${CMAKE_CURRENT_BINARY_DIR}/sql_lex.c"
+ DEFINES_FILE "${CMAKE_CURRENT_BINARY_DIR}/sql_lex.h"
+ )
+bison_target(parser sql.y "${CMAKE_CURRENT_BINARY_DIR}/sql_parser.c")
+
+set(sources
+ flb_sp_parser.c
+ )
+
+if(CMAKE_SYSTEM_NAME MATCHES "Windows")
+ FLB_DEFINITION(YY_NO_UNISTD_H)
+ message(STATUS "Specifying YY_NO_UNISTD_H")
+endif()
+
+include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR}
+ )
+
+add_library(flb-sp-parser STATIC
+ ${sources}
+ "${CMAKE_CURRENT_BINARY_DIR}/sql_lex.c"
+ "${CMAKE_CURRENT_BINARY_DIR}/sql_parser.c"
+ )
+
+add_flex_bison_dependency(lexer parser)
+add_dependencies(flb-sp-parser onigmo-static)
+
+if(FLB_JEMALLOC)
+ target_link_libraries(flb-sp-parser libjemalloc)
+endif()
diff --git a/fluent-bit/src/stream_processor/parser/flb_sp_parser.c b/fluent-bit/src/stream_processor/parser/flb_sp_parser.c
new file mode 100644
index 000000000..429d0b4c1
--- /dev/null
+++ b/fluent-bit/src/stream_processor/parser/flb_sp_parser.c
@@ -0,0 +1,851 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_slist.h>
+#include <fluent-bit/stream_processor/flb_sp_parser.h>
+#include <fluent-bit/stream_processor/flb_sp_aggregate_func.h>
+#include <fluent-bit/stream_processor/flb_sp_record_func.h>
+
+#include "sql_parser.h"
+#include "sql_lex.h"
+
+static int swap_tmp_subkeys(struct mk_list **target, struct flb_sp_cmd *cmd)
+{
+ /* Map context keys into this command key structure */
+ *target = cmd->tmp_subkeys;
+
+ cmd->tmp_subkeys = flb_malloc(sizeof(struct mk_list));
+ if (!cmd->tmp_subkeys) {
+ flb_errno();
+ cmd->tmp_subkeys = *target;
+ cmd->status = FLB_SP_ERROR;
+ return -1;
+ }
+
+ flb_slist_create(cmd->tmp_subkeys);
+ return 0;
+}
+
+void flb_sp_cmd_destroy(struct flb_sp_cmd *cmd)
+{
+ struct mk_list *head;
+ struct mk_list *tmp;
+ struct flb_sp_cmd_key *key;
+ struct flb_sp_cmd_gb_key *gb_key;
+ struct flb_sp_cmd_prop *prop;
+
+ /* remove keys */
+ mk_list_foreach_safe(head, tmp, &cmd->keys) {
+ key = mk_list_entry(head, struct flb_sp_cmd_key, _head);
+ mk_list_del(&key->_head);
+ flb_sp_cmd_key_del(key);
+ }
+
+ /* remove groupby keys */
+ mk_list_foreach_safe(head, tmp, &cmd->gb_keys) {
+ gb_key = mk_list_entry(head, struct flb_sp_cmd_gb_key, _head);
+ mk_list_del(&gb_key->_head);
+ flb_sp_cmd_gb_key_del(gb_key);
+ }
+
+ /* stream */
+ if (cmd->stream_name) {
+ mk_list_foreach_safe(head, tmp, &cmd->stream_props) {
+ prop = mk_list_entry(head, struct flb_sp_cmd_prop, _head);
+ mk_list_del(&prop->_head);
+ flb_sp_cmd_stream_prop_del(prop);
+ }
+ flb_sds_destroy(cmd->stream_name);
+ }
+ flb_sds_destroy(cmd->source_name);
+
+ if (mk_list_size(&cmd->cond_list) > 0) {
+ flb_sp_cmd_condition_del(cmd);
+ }
+
+ if (cmd->tmp_subkeys) {
+ flb_slist_destroy(cmd->tmp_subkeys);
+ flb_free(cmd->tmp_subkeys);
+ }
+
+ flb_free(cmd);
+}
+
+void flb_sp_cmd_key_del(struct flb_sp_cmd_key *key)
+{
+ if (key->name) {
+ flb_sds_destroy(key->name);
+ }
+ if (key->alias) {
+ flb_sds_destroy(key->alias);
+ }
+ if (key->subkeys) {
+ flb_slist_destroy(key->subkeys);
+ flb_free(key->subkeys);
+ }
+ flb_free(key);
+}
+
+void flb_sp_cmd_gb_key_del(struct flb_sp_cmd_gb_key *key)
+{
+ if (key->name) {
+ flb_sds_destroy(key->name);
+ }
+ if (key->subkeys) {
+ flb_slist_destroy(key->subkeys);
+ flb_free(key->subkeys);
+ }
+ flb_free(key);
+}
+
+struct flb_sp_cmd_key *flb_sp_key_create(struct flb_sp_cmd *cmd, int func,
+ const char *key_name,
+ const char *key_alias)
+{
+ char tmp_alias[256];
+ int s;
+ int ret;
+ int len;
+ int aggr_func = 0;
+ int time_func = 0;
+ int record_func = 0;
+ char *tmp;
+ struct mk_list *head;
+ struct flb_sp_cmd_key *key;
+ struct flb_slist_entry *entry;
+
+ /* aggregation function ? */
+ if (func >= FLB_SP_AVG && func <= FLB_SP_FORECAST) {
+ aggr_func = func;
+ }
+ else if (func >= FLB_SP_NOW && func <= FLB_SP_UNIX_TIMESTAMP) {
+ /* Time function */
+ time_func = func;
+ }
+ else if (func >= FLB_SP_RECORD_TAG && func <= FLB_SP_RECORD_TIME) {
+ /* Record function */
+ record_func = func;
+ }
+
+ key = flb_calloc(1, sizeof(struct flb_sp_cmd_key));
+ if (!key) {
+ flb_errno();
+ cmd->status = FLB_SP_ERROR;
+ return NULL;
+ }
+ key->gb_key = NULL;
+ key->subkeys = NULL;
+
+ /* key name and aliases works when the selection is not a wildcard */
+ if (key_name) {
+ key->name = flb_sds_create(key_name);
+ if (!key->name) {
+ flb_sp_cmd_key_del(key);
+ cmd->status = FLB_SP_ERROR;
+ return NULL;
+ }
+ }
+ else {
+ /*
+ * Wildcard key only allowed on:
+ * - no aggregation mode (left side / first entry)
+ * - aggregation using COUNT(*)
+ */
+ if (mk_list_size(&cmd->keys) > 0 && aggr_func == 0 &&
+ record_func == 0 && time_func == 0) {
+ flb_sp_cmd_key_del(key);
+ cmd->status = FLB_SP_ERROR;
+ return NULL;
+ }
+ }
+
+ if (key_alias) {
+ key->alias = flb_sds_create(key_alias);
+ if (!key->alias) {
+ flb_sp_cmd_key_del(key);
+ cmd->status = FLB_SP_ERROR;
+ return NULL;
+ }
+ }
+
+ /* Aggregation function */
+ if (aggr_func > 0) {
+ key->aggr_func = aggr_func;
+ }
+ else if (time_func > 0) {
+ key->time_func = time_func;
+ }
+ else if (record_func > 0) {
+ key->record_func = record_func;
+ }
+
+ /* Lookup for any subkeys in the temporary list */
+ if (mk_list_size(cmd->tmp_subkeys) > 0) {
+ ret = swap_tmp_subkeys(&key->subkeys, cmd);
+ if (ret == -1) {
+ flb_sp_cmd_key_del(key);
+ cmd->status = FLB_SP_ERROR;
+ return NULL;
+ }
+
+ /* Compose a name key that include listed sub keys */
+ if (!key->alias) {
+ s = flb_sds_len(key->name) + (16 * mk_list_size(key->subkeys));
+ key->alias = flb_sds_create_size(s);
+ if (!key->alias) {
+ flb_sp_cmd_key_del(key);
+ return NULL;
+ }
+
+ tmp = flb_sds_cat(key->alias, key->name, flb_sds_len(key->name));
+ if (tmp != key->alias) {
+ key->alias = tmp;
+ }
+
+ mk_list_foreach(head, key->subkeys) {
+ entry = mk_list_entry(head, struct flb_slist_entry, _head);
+
+ /* prefix */
+ tmp = flb_sds_cat(key->alias, "['", 2);
+ if (tmp) {
+ key->alias = tmp;
+ }
+ else {
+ flb_sp_cmd_key_del(key);
+ return NULL;
+ }
+
+ /* selected key name */
+ tmp = flb_sds_cat(key->alias,
+ entry->str, flb_sds_len(entry->str));
+ if (tmp) {
+ key->alias = tmp;
+ }
+ else {
+ flb_sp_cmd_key_del(key);
+ return NULL;
+ }
+
+ /* suffix */
+ tmp = flb_sds_cat(key->alias, "']", 2);
+ if (tmp) {
+ key->alias = tmp;
+ }
+ else {
+ flb_sp_cmd_key_del(key);
+ return NULL;
+ }
+ }
+
+ if (aggr_func) {
+ len = snprintf(tmp_alias, sizeof(tmp_alias) - 1, "%s(%s)",
+ aggregate_func_string[aggr_func - 1], key->alias);
+
+ tmp = flb_sds_copy(key->alias, tmp_alias, len);
+ if (tmp) {
+ key->alias = tmp;
+ }
+ else {
+ flb_sp_cmd_key_del(key);
+ return NULL;
+ }
+ }
+ }
+ }
+ else if (aggr_func && !key->alias) {
+ if (key->name) {
+ len = snprintf(tmp_alias, sizeof(tmp_alias) - 1, "%s(%s)",
+ aggregate_func_string[aggr_func - 1], key->name);
+ } else {
+ len = snprintf(tmp_alias, sizeof(tmp_alias) - 1, "%s(*)",
+ aggregate_func_string[aggr_func - 1]);
+ }
+
+ key->alias = flb_sds_create_len(tmp_alias, len);
+ if (!key->alias) {
+ flb_sp_cmd_key_del(key);
+ return NULL;
+ }
+ }
+
+ return key;
+}
+
+int flb_sp_cmd_key_add(struct flb_sp_cmd *cmd, int func, const char *key_name)
+{
+ struct flb_sp_cmd_key *key;
+
+ key = flb_sp_key_create(cmd, func, key_name, cmd->alias);
+
+ if (!key) {
+ return -1;
+ }
+
+ mk_list_add(&key->_head, &cmd->keys);
+
+ /* free key alias and set cmd->alias to null */
+ if (cmd->alias) {
+ flb_free(cmd->alias);
+ cmd->alias = NULL;
+ }
+
+ return 0;
+}
+
+void flb_sp_cmd_alias_add(struct flb_sp_cmd *cmd, const char *key_alias)
+{
+ cmd->alias = (char *) key_alias;
+}
+
+int flb_sp_cmd_source(struct flb_sp_cmd *cmd, int type, const char *source)
+{
+ cmd->source_type = type;
+ cmd->source_name = flb_sds_create(source);
+ if (!cmd->source_name) {
+ flb_errno();
+ return -1;
+ }
+
+ return 0;
+}
+
+void flb_sp_cmd_dump(struct flb_sp_cmd *cmd)
+{
+ struct mk_list *head;
+ struct mk_list *tmp;
+ struct flb_sp_cmd_key *key;
+
+ /* Lookup keys */
+ printf("== KEYS ==\n");
+ mk_list_foreach_safe(head, tmp, &cmd->keys) {
+ key = mk_list_entry(head, struct flb_sp_cmd_key, _head);
+ printf("- '%s'\n", key->name);
+ }
+ printf("== SOURCE ==\n");
+ if (cmd->source_type == FLB_SP_STREAM) {
+ printf("stream => ");
+ }
+ else if (cmd->source_type == FLB_SP_TAG) {
+ printf("tag match => ");
+ }
+
+ printf("'%s'\n", cmd->source_name);
+}
+
+struct flb_sp_cmd *flb_sp_cmd_create(const char *sql)
+{
+ int ret;
+ yyscan_t scanner;
+ YY_BUFFER_STATE buf;
+ struct flb_sp_cmd *cmd;
+
+ /* create context */
+ cmd = flb_calloc(1, sizeof(struct flb_sp_cmd));
+ if (!cmd) {
+ flb_errno();
+ return NULL;
+ }
+ cmd->status = FLB_SP_OK;
+ cmd->type = FLB_SP_SELECT;
+
+ mk_list_init(&cmd->stream_props);
+ mk_list_init(&cmd->keys);
+
+ /* Condition linked list (we use them to free resources) */
+ mk_list_init(&cmd->cond_list);
+ mk_list_init(&cmd->gb_keys);
+
+ /* Allocate temporary list and initialize */
+ cmd->tmp_subkeys = flb_malloc(sizeof(struct mk_list));
+ if (!cmd->tmp_subkeys) {
+ flb_errno();
+ flb_free(cmd);
+ return NULL;
+ }
+ flb_slist_create(cmd->tmp_subkeys);
+
+ /* Flex/Bison work */
+ flb_sp_lex_init(&scanner);
+ buf = flb_sp__scan_string(sql, scanner);
+
+ ret = flb_sp_parse(cmd, sql, scanner);
+
+ flb_sp__delete_buffer(buf, scanner);
+ flb_sp_lex_destroy(scanner);
+
+ if (ret != 0) {
+ flb_sp_cmd_destroy(cmd);
+ return NULL;
+ }
+
+ return cmd;
+}
+
+int flb_sp_cmd_stream_new(struct flb_sp_cmd *cmd, const char *stream_name)
+{
+ cmd->stream_name = flb_sds_create(stream_name);
+ if (!cmd->stream_name) {
+ return -1;
+ }
+
+ cmd->type = FLB_SP_CREATE_STREAM;
+ return 0;
+}
+
+int flb_sp_cmd_snapshot_new(struct flb_sp_cmd *cmd, const char *snapshot_name)
+{
+ const char *tmp;
+
+ cmd->stream_name = flb_sds_create(snapshot_name);
+ if (!cmd->stream_name) {
+ return -1;
+ }
+
+ tmp = flb_sp_cmd_stream_prop_get(cmd, "tag");
+ if (!tmp) {
+ cmd->status = FLB_SP_ERROR;
+ flb_error("[sp] tag for snapshot is required. Add WITH(tag = <TAG>) to the snapshot %s",
+ snapshot_name);
+ return -1;
+ }
+
+ cmd->type = FLB_SP_CREATE_SNAPSHOT;
+
+ return 0;
+}
+
+int flb_sp_cmd_snapshot_flush_new(struct flb_sp_cmd *cmd, const char *snapshot_name)
+{
+ cmd->stream_name = flb_sds_cat(flb_sds_create("__flush_"),
+ snapshot_name, strlen(snapshot_name));
+
+ if (!cmd->stream_name) {
+ return -1;
+ }
+
+ cmd->type = FLB_SP_FLUSH_SNAPSHOT;
+
+ return 0;
+}
+
+int flb_sp_cmd_stream_prop_add(struct flb_sp_cmd *cmd, const char *key, const char *val)
+{
+ struct flb_sp_cmd_prop *prop;
+
+ prop = flb_malloc(sizeof(struct flb_sp_cmd_prop));
+ if (!prop) {
+ flb_errno();
+ return -1;
+ }
+
+ prop->key = flb_sds_create(key);
+ if (!prop->key) {
+ flb_free(prop);
+ return -1;
+ }
+
+ prop->val = flb_sds_create(val);
+ if (!prop->val) {
+ flb_free(prop->key);
+ flb_free(prop);
+ return -1;
+ }
+
+ mk_list_add(&prop->_head, &cmd->stream_props);
+ return 0;
+}
+
+void flb_sp_cmd_stream_prop_del(struct flb_sp_cmd_prop *prop)
+{
+ if (prop->key) {
+ flb_sds_destroy(prop->key);
+ }
+ if (prop->val) {
+ flb_sds_destroy(prop->val);
+ }
+ flb_free(prop);
+}
+
+const char *flb_sp_cmd_stream_prop_get(struct flb_sp_cmd *cmd, const char *key)
+{
+ int len;
+ struct mk_list *head;
+ struct flb_sp_cmd_prop *prop;
+
+ if (!key) {
+ return NULL;
+ }
+ len = strlen(key);
+
+ mk_list_foreach(head, &cmd->stream_props) {
+ prop = mk_list_entry(head, struct flb_sp_cmd_prop, _head);
+ if (flb_sds_len(prop->key) != len) {
+ continue;
+ }
+
+ if (strcmp(prop->key, key) == 0) {
+ return prop->val;
+ }
+ }
+
+ return NULL;
+}
+
+/* WINDOW functions */
+
+int flb_sp_cmd_window(struct flb_sp_cmd *cmd,
+ int window_type, int size, int time_unit,
+ int advance_by_size, int advance_by_time_unit)
+{
+ cmd->window.type = window_type;
+
+ switch (time_unit) {
+ case FLB_SP_TIME_SECOND:
+ cmd->window.size = (time_t) size;
+ break;
+ case FLB_SP_TIME_MINUTE:
+ cmd->window.size = (time_t) size * 60;
+ break;
+ case FLB_SP_TIME_HOUR:
+ cmd->window.size = (time_t) size * 3600;
+ break;
+ }
+
+ if (window_type == FLB_SP_WINDOW_HOPPING) {
+ switch (advance_by_time_unit) {
+ case FLB_SP_TIME_SECOND:
+ cmd->window.advance_by = (time_t) advance_by_size;
+ break;
+ case FLB_SP_TIME_MINUTE:
+ cmd->window.advance_by = (time_t) advance_by_size * 60;
+ break;
+ case FLB_SP_TIME_HOUR:
+ cmd->window.advance_by = (time_t) advance_by_size * 3600;
+ break;
+ }
+
+ if (cmd->window.advance_by >= cmd->window.size) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* WHERE <condition> functions */
+
+struct flb_exp *flb_sp_cmd_operation(struct flb_sp_cmd *cmd,
+ struct flb_exp *e1, struct flb_exp *e2,
+ int operation)
+{
+ struct flb_exp_op *expression;
+
+ expression = flb_malloc(sizeof(struct flb_exp_op));
+ if (!expression) {
+ flb_errno();
+ return NULL;
+ }
+
+ expression->type = FLB_LOGICAL_OP;
+ expression->left = e1;
+ expression->right = e2;
+ expression->operation = operation;
+ mk_list_add(&expression->_head, &cmd->cond_list);
+
+ return (struct flb_exp *) expression;
+}
+
+struct flb_exp *flb_sp_cmd_comparison(struct flb_sp_cmd *cmd,
+ struct flb_exp *key, struct flb_exp *val,
+ int operation)
+{
+ struct flb_exp_op *expression;
+
+ expression = flb_malloc(sizeof(struct flb_exp_op));
+ if (!expression) {
+ flb_errno();
+ return NULL;
+ }
+
+ expression->type = FLB_LOGICAL_OP;
+ expression->left = (struct flb_exp *) key;
+ expression->right = (struct flb_exp *) val;
+ expression->operation = operation;
+ mk_list_add(&expression->_head, &cmd->cond_list);
+
+ return (struct flb_exp *) expression;
+}
+
+struct flb_exp *flb_sp_cmd_condition_key(struct flb_sp_cmd *cmd,
+ const char *identifier)
+{
+ int ret;
+ struct flb_exp_key *key;
+
+ key = flb_calloc(1, sizeof(struct flb_exp_key));
+ if (!key) {
+ flb_errno();
+ return NULL;
+ }
+
+ key->type = FLB_EXP_KEY;
+ key->name = flb_sds_create(identifier);
+ mk_list_add(&key->_head, &cmd->cond_list);
+
+ if (mk_list_size(cmd->tmp_subkeys) > 0) {
+ ret = swap_tmp_subkeys(&key->subkeys, cmd);
+ if (ret == -1) {
+ flb_sds_destroy(key->name);
+ mk_list_del(&key->_head);
+ flb_free(key);
+ return NULL;
+ }
+ }
+
+ return (struct flb_exp *) key;
+}
+
+struct flb_exp *flb_sp_cmd_condition_integer(struct flb_sp_cmd *cmd,
+ int integer)
+{
+ struct flb_exp_val *val;
+
+ val = flb_malloc(sizeof(struct flb_exp_val));
+ if (!val) {
+ flb_errno();
+ return NULL;
+ }
+
+ val->type = FLB_EXP_INT;
+ val->val.i64 = integer;
+ mk_list_add(&val->_head, &cmd->cond_list);
+
+ return (struct flb_exp *) val;
+}
+
+struct flb_exp *flb_sp_cmd_condition_float(struct flb_sp_cmd *cmd, float fval)
+{
+ struct flb_exp_val *val;
+
+ val = flb_malloc(sizeof(struct flb_exp_val));
+ if (!val) {
+ flb_errno();
+ return NULL;
+ }
+
+ val->type = FLB_EXP_FLOAT;
+ val->val.f64 = fval;
+ mk_list_add(&val->_head, &cmd->cond_list);
+
+ return (struct flb_exp *) val;
+}
+
+struct flb_exp *flb_sp_cmd_condition_string(struct flb_sp_cmd *cmd,
+ const char *string)
+{
+ struct flb_exp_val *val;
+
+ val = flb_malloc(sizeof(struct flb_exp_val));
+ if (!val) {
+ flb_errno();
+ return NULL;
+ }
+
+ val->type = FLB_EXP_STRING;
+ val->val.string = flb_sds_create(string);
+ mk_list_add(&val->_head, &cmd->cond_list);
+
+ return (struct flb_exp *) val;
+}
+
+struct flb_exp *flb_sp_cmd_condition_boolean(struct flb_sp_cmd *cmd,
+ bool boolean)
+{
+ struct flb_exp_val *val;
+
+ val = flb_malloc(sizeof(struct flb_exp_val));
+ if (!val) {
+ flb_errno();
+ return NULL;
+ }
+
+ val->type = FLB_EXP_BOOL;
+ val->val.boolean = boolean;
+ mk_list_add(&val->_head, &cmd->cond_list);
+
+ return (struct flb_exp *) val;
+}
+
+struct flb_exp *flb_sp_cmd_condition_null(struct flb_sp_cmd *cmd)
+{
+ struct flb_exp_val *val;
+
+ val = flb_malloc(sizeof(struct flb_exp_val));
+ if (!val) {
+ flb_errno();
+ return NULL;
+ }
+
+ val->type = FLB_EXP_NULL;
+ mk_list_add(&val->_head, &cmd->cond_list);
+
+ return (struct flb_exp *) val;
+}
+
+struct flb_exp *flb_sp_record_function_add(struct flb_sp_cmd *cmd,
+ char *name, struct flb_exp *param)
+{
+ char *rf_name;
+ int i;
+ struct flb_exp_func *func;
+
+ for (i = 0; i < RECORD_FUNCTIONS_SIZE; i++)
+ {
+ rf_name = record_functions[i];
+ if (strncmp(rf_name, name, strlen(rf_name)) == 0)
+ {
+ func = flb_calloc(1, sizeof(struct flb_exp_func));
+ if (!func) {
+ flb_errno();
+ return NULL;
+ }
+
+ func->type = FLB_EXP_FUNC;
+ func->name = flb_sds_create(name);
+ func->cb_func = record_functions_ptr[i];
+ func->param = param;
+
+ mk_list_add(&func->_head, &cmd->cond_list);
+
+ return (struct flb_exp *) func;
+ }
+ }
+
+ return NULL;
+}
+
+void flb_sp_cmd_condition_add(struct flb_sp_cmd *cmd, struct flb_exp *e)
+
+{
+ cmd->condition = e;
+}
+
+int flb_sp_cmd_gb_key_add(struct flb_sp_cmd *cmd, const char *key)
+{
+ int ret;
+ struct flb_sp_cmd_gb_key *gb_key;
+
+ gb_key = flb_calloc(1, sizeof(struct flb_sp_cmd_gb_key));
+ if (!gb_key) {
+ flb_errno();
+ return -1;
+ }
+
+ gb_key->name = flb_sds_create(key);
+ if (!gb_key->name) {
+ flb_free(gb_key);
+ return -1;
+ }
+
+ gb_key->id = mk_list_size(&cmd->gb_keys);
+ mk_list_add(&gb_key->_head, &cmd->gb_keys);
+
+ /* Lookup for any subkeys in the temporary list */
+ if (mk_list_size(cmd->tmp_subkeys) > 0) {
+ ret = swap_tmp_subkeys(&gb_key->subkeys, cmd);
+ if (ret == -1) {
+ flb_sds_destroy(gb_key->name);
+ mk_list_del(&gb_key->_head);
+ flb_free(gb_key);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void flb_sp_cmd_condition_del(struct flb_sp_cmd *cmd)
+{
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_exp *exp;
+ struct flb_exp_key *key;
+ struct flb_exp_val *val;
+ struct flb_exp_func *func;
+
+ mk_list_foreach_safe(head, tmp, &cmd->cond_list) {
+ exp = mk_list_entry(head, struct flb_exp, _head);
+ if (exp->type == FLB_EXP_KEY) {
+ key = (struct flb_exp_key *) exp;
+ flb_sds_destroy(key->name);
+ if (key->subkeys) {
+ flb_slist_destroy(key->subkeys);
+ flb_free(key->subkeys);
+ }
+ }
+ else if (exp->type == FLB_EXP_STRING) {
+ val = (struct flb_exp_val *) exp;
+ flb_sds_destroy(val->val.string);
+ }
+ else if (exp->type == FLB_EXP_FUNC) {
+ func = (struct flb_exp_func *) exp;
+ flb_sds_destroy(func->name);
+ }
+
+ mk_list_del(&exp->_head);
+ flb_free(exp);
+ }
+}
+
+void flb_sp_cmd_limit_add(struct flb_sp_cmd *cmd, int limit)
+{
+ cmd->limit = limit;
+}
+
+int flb_sp_cmd_timeseries_forecast(struct flb_sp_cmd *cmd, int func, const char *key_name, int seconds)
+{
+ struct flb_sp_cmd_key *key;
+
+ key = flb_sp_key_create(cmd, func, key_name, cmd->alias);
+
+ if (!key) {
+ return -1;
+ }
+
+ mk_list_add(&key->_head, &cmd->keys);
+
+ key->constant = seconds;
+
+ /* free key alias and set cmd->alias to null */
+ if (cmd->alias) {
+ flb_free(cmd->alias);
+ cmd->alias = NULL;
+ }
+
+ return 0;
+}
diff --git a/fluent-bit/src/stream_processor/parser/sql.l b/fluent-bit/src/stream_processor/parser/sql.l
new file mode 100644
index 000000000..91e5398e1
--- /dev/null
+++ b/fluent-bit/src/stream_processor/parser/sql.l
@@ -0,0 +1,190 @@
+%option prefix="flb_sp_"
+%option caseless
+%option 8bit reentrant bison-bridge
+%option warn noyywrap nodefault
+%option nounput
+%option noinput
+
+
+%{
+#include <stdio.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <fluent-bit/flb_str.h>
+#include <fluent-bit/flb_log.h>
+#include "sql_parser.h"
+#include <fluent-bit/stream_processor/flb_sp_parser.h>
+
+static inline char *remove_dup_qoutes(const char *s, size_t n)
+{
+ char *str;
+ int dups;
+ int i, j;
+
+ dups = 0;
+ for (i = 0; i < n; i++) {
+ if (s[i] == '\'') {
+ dups++;
+ i++;
+ }
+ }
+
+ str = (char *) flb_malloc(n - dups + 1);
+ if (!str) {
+ return NULL;
+ }
+
+ j = 0;
+ for (i = 0; i < n; i++, j++) {
+ if (s[i] == '\'') {
+ str[j] = '\'';
+ i++;
+ } else {
+ str[j] = s[i];
+ }
+ }
+ str[j] = '\0';
+
+ return str;
+}
+
+char* to_upper(char* token, size_t len)
+{
+ int i;
+ char* token_;
+
+ token_ = flb_malloc(len * sizeof(char) + 1);
+
+ for (i = 0; i < len; i++) {
+ token_[i] = toupper(token[i]);
+ }
+
+ token_[len] = '\0';
+ return token_;
+}
+
+int func_to_code(char* name, size_t len)
+{
+ int code;
+ char* name_;
+
+ name_ = to_upper(name, len);
+ code = -1;
+
+ if (!strcmp(name_, "AVG")) {
+ code = FLB_SP_AVG;
+ } else if (!strcmp(name_, "SUM")) {
+ code = FLB_SP_SUM;
+ } else if (!strcmp(name_, "COUNT")) {
+ code = FLB_SP_COUNT;
+ } else if (!strcmp(name_, "MIN")) {
+ code = FLB_SP_MIN;
+ } else if (!strcmp(name_, "MAX")) {
+ code = FLB_SP_MAX;
+ } else if (!strcmp(name_, "TIMESERIES_FORECAST")) {
+ code = FLB_SP_FORECAST;
+ } else if (!strcmp(name_, "NOW")) {
+ code = FLB_SP_NOW;
+ } else if (!strcmp(name_, "UNIX_TIMESTAMP")) {
+ code = FLB_SP_UNIX_TIMESTAMP;
+ } else if (!strcmp(name_, "RECORD_TAG")) {
+ code = FLB_SP_RECORD_TAG;
+ } else if (!strcmp(name_, "RECORD_TIME")) {
+ code = FLB_SP_RECORD_TIME;
+ }
+
+ flb_free(name_);
+ return code;
+}
+
+%}
+
+%%
+
+ /* SQL */
+CREATE return CREATE;
+FLUSH return FLUSH;
+STREAM return STREAM;
+SNAPSHOT return SNAPSHOT;
+WITH return WITH;
+SELECT return SELECT;
+AS return AS;
+FROM return FROM;
+STREAM: return FROM_STREAM;
+TAG: return FROM_TAG;
+WHERE return WHERE;
+AND return AND;
+OR return OR;
+NOT return NOT;
+WINDOW return WINDOW;
+"GROUP BY" return GROUP_BY;
+LIMIT return LIMIT;
+
+IS return IS;
+NULL return NUL;
+
+ /* Aggregation Functions */
+SUM {yylval->integer = func_to_code(yytext, yyleng); return SUM;}
+AVG {yylval->integer = func_to_code(yytext, yyleng); return AVG;}
+COUNT {yylval->integer = func_to_code(yytext, yyleng); return COUNT;}
+MIN {yylval->integer = func_to_code(yytext, yyleng); return MIN;}
+MAX {yylval->integer = func_to_code(yytext, yyleng); return MAX;}
+TIMESERIES_FORECAST {yylval->integer = func_to_code(yytext, yyleng); return TIMESERIES_FORECAST;};
+
+ /* Record Functions */
+@RECORD return RECORD;
+CONTAINS return CONTAINS;
+TIME return TIME;
+
+
+ /* Window Types */
+TUMBLING return TUMBLING;
+HOPPING return HOPPING;
+"ADVANCE BY" return ADVANCE_BY;
+
+ /* Time */
+HOUR return HOUR;
+MINUTE return MINUTE;
+SECOND return SECOND;
+
+ /* Date / Time Functions */
+NOW {yylval->integer = func_to_code(yytext, yyleng); return NOW;}
+UNIX_TIMESTAMP {yylval->integer = func_to_code(yytext, yyleng); return UNIX_TIMESTAMP;}
+
+ /* Record information */
+RECORD_TAG {yylval->integer = func_to_code(yytext, yyleng); return RECORD_TAG;}
+RECORD_TIME {yylval->integer = func_to_code(yytext, yyleng); return RECORD_TIME;}
+
+"true" { yylval->boolean = true; return BOOLTYPE; };
+"false" { yylval->boolean = false; return BOOLTYPE; };
+
+-?[1-9][0-9]*|0 { yylval->integer = atoi(yytext); return INTEGER; }
+(-?[1-9][0-9]*|0)\.[0-9]+ { yylval->fval = atof(yytext); return FLOATING; }
+\'([^']|'{2})*\' { yylval->string = remove_dup_qoutes(yytext + 1, yyleng - 2); return STRING; }
+
+[_A-Za-z][A-Za-z0-9_.]* { yylval->string = flb_strdup(yytext); return IDENTIFIER; }
+
+"*" |
+"," |
+"=" |
+"(" |
+")" |
+"[" |
+"]" |
+"." |
+";" { return yytext[0]; }
+
+"!=" return NEQ;
+"<>" return NEQ;
+"<" return LT;
+"<=" return LTE;
+">" return GT;
+">=" return GTE;
+
+\' return QUOTE;
+\n
+[ \t]+ /* ignore whitespace */;
+
+. flb_error("[sp] bad input character '%s' at line %d", yytext, yylineno);
+
+%%
diff --git a/fluent-bit/src/stream_processor/parser/sql.y b/fluent-bit/src/stream_processor/parser/sql.y
new file mode 100644
index 000000000..866f95cc0
--- /dev/null
+++ b/fluent-bit/src/stream_processor/parser/sql.y
@@ -0,0 +1,437 @@
+%name-prefix="flb_sp_" // replace with %define api.prefix {flb_sp_}
+%define api.pure full
+%define parse.error verbose
+%parse-param { struct flb_sp_cmd *cmd };
+%parse-param { const char *query };
+%lex-param { void *scanner }
+%parse-param { void *scanner }
+
+%{ // definition section (prologue)
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_slist.h>
+#include <fluent-bit/stream_processor/flb_sp_parser.h>
+
+#include "sql_parser.h"
+#include "sql_lex.h"
+
+extern int yylex();
+
+void yyerror(struct flb_sp_cmd *cmd, const char *query, void *scanner, const char *str)
+{
+ flb_error("[sp] %s at '%s'", str, query);
+}
+
+%} /* EOF C code */
+
+/* Bison declarations */
+/* Known Tokens (refer to sql.l) */
+
+/* Keywords */
+%token IDENTIFIER QUOTE
+
+/* Basic keywords for statements */
+%token CREATE STREAM SNAPSHOT FLUSH WITH SELECT AS FROM FROM_STREAM FROM_TAG
+%token WHERE WINDOW GROUP_BY LIMIT
+
+/* Null keywords */
+%token IS NUL
+
+/* Aggregation functions */
+%token AVG SUM COUNT MAX MIN TIMESERIES_FORECAST
+
+/* Record functions */
+%token RECORD CONTAINS TIME
+
+/* Time functions */
+%token NOW UNIX_TIMESTAMP
+
+ /* Record functions */
+%token RECORD_TAG RECORD_TIME
+
+/* Value types */
+%token INTEGER FLOATING STRING BOOLTYPE
+
+/* Logical operation tokens */
+%token AND OR NOT NEQ LT LTE GT GTE
+
+/* Time tokens */
+%token HOUR MINUTE SECOND
+
+/* Window tokens */
+%token TUMBLING HOPPING ADVANCE_BY
+
+/* Union and field types */
+%union
+{
+ bool boolean;
+ int integer;
+ float fval;
+ char *string;
+ struct flb_sp_cmd *cmd;
+ struct flb_exp *expression;
+}
+
+%type <boolean> BOOLTYPE
+%type <integer> INTEGER
+%type <fval> FLOATING
+%type <string> IDENTIFIER
+%type <string> STRING
+%type <string> record_keys
+%type <string> record_key
+%type <string> prop_key
+%type <string> prop_val
+%type <expression> condition
+%type <expression> comparison
+%type <expression> key
+%type <expression> record_func
+%type <expression> value
+%type <expression> null
+%type <integer> time
+
+%type <integer> time_record_func
+%type <integer> NOW UNIX_TIMESTAMP RECORD_TAG RECORD_TIME
+
+%type <integer> aggregate_func
+%type <integer> COUNT AVG SUM MAX MIN TIMESERIES_FORECAST
+
+
+%destructor { flb_free ($$); } IDENTIFIER
+
+%% /* rules section */
+
+statements: create | select
+
+/* Parser for 'CREATE STREAM' statement */
+create:
+ CREATE STREAM IDENTIFIER AS select
+ {
+ flb_sp_cmd_stream_new(cmd, $3);
+ flb_free($3);
+ }
+ |
+ CREATE STREAM IDENTIFIER WITH '(' properties ')' AS select
+ {
+ flb_sp_cmd_stream_new(cmd, $3);
+ flb_free($3);
+ }
+ |
+ CREATE SNAPSHOT IDENTIFIER AS SELECT '*' FROM source limit ';'
+ {
+ flb_sp_cmd_snapshot_new(cmd, $3);
+ flb_free($3);
+ }
+ |
+ CREATE SNAPSHOT IDENTIFIER WITH '(' properties ')' AS SELECT '*' FROM source limit ';'
+ {
+ flb_sp_cmd_snapshot_new(cmd, $3);
+ flb_free($3);
+ }
+ |
+ FLUSH SNAPSHOT IDENTIFIER AS SELECT '*' FROM source where ';'
+ {
+ flb_sp_cmd_snapshot_flush_new(cmd, $3);
+ flb_free($3);
+ }
+ |
+ FLUSH SNAPSHOT IDENTIFIER WITH '(' properties ')' AS SELECT '*' FROM source where ';'
+ {
+ flb_sp_cmd_snapshot_flush_new(cmd, $3);
+ flb_free($3);
+ }
+ properties: property
+ |
+ properties ',' property
+ property: prop_key '=' prop_val
+ {
+ flb_sp_cmd_stream_prop_add(cmd, $1, $3);
+ flb_free($1);
+ flb_free($3);
+ }
+ prop_key: IDENTIFIER
+ prop_val: STRING
+
+/* Parser for 'SELECT' statement */
+select: SELECT keys FROM source window where groupby limit ';'
+ {
+ cmd->type = FLB_SP_SELECT;
+ }
+ keys: record_keys
+ record_keys: record_key
+ |
+ record_keys ',' record_key
+ record_key: '*'
+ {
+ flb_sp_cmd_key_add(cmd, -1, NULL);
+ }
+ |
+ IDENTIFIER key_alias
+ {
+ flb_sp_cmd_key_add(cmd, -1, $1);
+ flb_free($1);
+ }
+ |
+ IDENTIFIER record_subkey key_alias
+ {
+ flb_sp_cmd_key_add(cmd, -1, $1);
+ flb_free($1);
+ }
+ |
+ COUNT '(' '*' ')' key_alias
+ {
+ flb_sp_cmd_key_add(cmd, $1, NULL);
+ }
+ |
+ COUNT '(' IDENTIFIER ')' key_alias
+ {
+ flb_sp_cmd_key_add(cmd, $1, $3);
+ flb_free($3);
+ }
+ |
+ COUNT '(' IDENTIFIER record_subkey ')' key_alias
+ {
+ flb_sp_cmd_key_add(cmd, $1, $3);
+ flb_free($3);
+ }
+ |
+ aggregate_func '(' IDENTIFIER ')' key_alias
+ {
+ flb_sp_cmd_key_add(cmd, $1, $3);
+ flb_free($3);
+ }
+ |
+ aggregate_func '(' IDENTIFIER record_subkey ')' key_alias
+ {
+ flb_sp_cmd_key_add(cmd, $1, $3);
+ flb_free($3);
+ }
+ |
+ TIMESERIES_FORECAST '(' IDENTIFIER ',' INTEGER ')' key_alias
+ {
+ flb_sp_cmd_timeseries_forecast(cmd, $1, $3, $5);
+ flb_free($3);
+ }
+ |
+ time_record_func '(' ')' key_alias
+ {
+ flb_sp_cmd_key_add(cmd, $1, NULL);
+ }
+ aggregate_func:
+ AVG | SUM | MAX | MIN
+ time_record_func:
+ NOW | UNIX_TIMESTAMP | RECORD_TAG | RECORD_TIME
+ key_alias:
+ %empty
+ |
+ AS IDENTIFIER
+ {
+ flb_sp_cmd_alias_add(cmd, $2);
+ }
+ record_subkey: '[' STRING ']'
+ {
+ flb_slist_add(cmd->tmp_subkeys, $2);
+ flb_free($2);
+ }
+ |
+ record_subkey record_subkey
+ source: FROM_STREAM IDENTIFIER
+ {
+ flb_sp_cmd_source(cmd, FLB_SP_STREAM, $2);
+ flb_free($2);
+ }
+ |
+ FROM_TAG STRING
+ {
+ flb_sp_cmd_source(cmd, FLB_SP_TAG, $2);
+ flb_free($2);
+ }
+ window: %empty
+ |
+ WINDOW window_spec
+ where: %empty
+ |
+ WHERE condition
+ {
+ flb_sp_cmd_condition_add(cmd, $2);
+ }
+ groupby: %empty
+ |
+ GROUP_BY gb_keys
+ limit: %empty
+ |
+ LIMIT INTEGER
+ {
+ flb_sp_cmd_limit_add(cmd, $2);
+ }
+ window_spec:
+ TUMBLING '(' INTEGER time ')'
+ {
+ flb_sp_cmd_window(cmd, FLB_SP_WINDOW_TUMBLING, $3, $4, 0, 0);
+ }
+ |
+ HOPPING '(' INTEGER time ',' ADVANCE_BY INTEGER time ')'
+ {
+ flb_sp_cmd_window(cmd, FLB_SP_WINDOW_HOPPING, $3, $4, $7, $8);
+ }
+ condition: comparison
+ |
+ key
+ {
+ $$ = flb_sp_cmd_operation(cmd, $1, NULL, FLB_EXP_OR);
+ }
+ |
+ value
+ {
+ $$ = flb_sp_cmd_operation(cmd, NULL, $1, FLB_EXP_OR);
+ }
+ |
+ '(' condition ')'
+ {
+ $$ = flb_sp_cmd_operation(cmd, $2, NULL, FLB_EXP_PAR);
+ }
+ |
+ NOT condition
+ {
+ $$ = flb_sp_cmd_operation(cmd, $2, NULL, FLB_EXP_NOT);
+ }
+ |
+ condition AND condition
+ {
+ $$ = flb_sp_cmd_operation(cmd, $1, $3, FLB_EXP_AND);
+ }
+ |
+ condition OR condition
+ {
+ $$ = flb_sp_cmd_operation(cmd, $1, $3, FLB_EXP_OR);
+ }
+ comparison:
+ key IS null
+ {
+ $$ = flb_sp_cmd_comparison(cmd, $1, $3, FLB_EXP_EQ);
+ }
+ |
+ key IS NOT null
+ {
+ $$ = flb_sp_cmd_operation(cmd,
+ flb_sp_cmd_comparison(cmd, $1, $4, FLB_EXP_EQ),
+ NULL, FLB_EXP_NOT);
+ }
+ |
+ record_func
+ {
+ $$ = flb_sp_cmd_comparison(cmd,
+ $1,
+ flb_sp_cmd_condition_boolean(cmd, true),
+ FLB_EXP_EQ);
+ }
+ |
+ record_func '=' value
+ {
+ $$ = flb_sp_cmd_comparison(cmd, $1, $3, FLB_EXP_EQ);
+ }
+ |
+ record_func NEQ value
+ {
+ $$ = flb_sp_cmd_operation(cmd,
+ flb_sp_cmd_comparison(cmd, $1, $3, FLB_EXP_EQ),
+ NULL, FLB_EXP_NOT)
+ ;
+ }
+ |
+ record_func LT value
+ {
+ $$ = flb_sp_cmd_comparison(cmd, $1, $3, FLB_EXP_LT);
+ }
+ |
+ record_func LTE value
+ {
+ $$ = flb_sp_cmd_comparison(cmd, $1, $3, FLB_EXP_LTE);
+ }
+ |
+ record_func GT value
+ {
+ $$ = flb_sp_cmd_comparison(cmd, $1, $3, FLB_EXP_GT);
+ }
+ |
+ record_func GTE value
+ {
+ $$ = flb_sp_cmd_comparison(cmd, $1, $3, FLB_EXP_GTE);
+ }
+ record_func: key /* Similar to an identity function */
+ |
+ RECORD '.' CONTAINS '(' key ')'
+ {
+ $$ = flb_sp_record_function_add(cmd, "contains", $5);
+ }
+ |
+ RECORD '.' TIME '(' ')'
+ {
+ $$ = flb_sp_record_function_add(cmd, "time", NULL);
+ }
+ key: IDENTIFIER
+ {
+ $$ = flb_sp_cmd_condition_key(cmd, $1);
+ flb_free($1);
+ }
+ |
+ IDENTIFIER record_subkey
+ {
+ $$ = flb_sp_cmd_condition_key(cmd, $1);
+ flb_free($1);
+ }
+ value: INTEGER
+ {
+ $$ = flb_sp_cmd_condition_integer(cmd, $1);
+ }
+ |
+ FLOATING
+ {
+ $$ = flb_sp_cmd_condition_float(cmd, $1);
+ }
+ |
+ STRING
+ {
+ $$ = flb_sp_cmd_condition_string(cmd, $1);
+ flb_free($1);
+ }
+ |
+ BOOLTYPE
+ {
+ $$ = flb_sp_cmd_condition_boolean(cmd, $1);
+ }
+ null: NUL
+ {
+ $$ = flb_sp_cmd_condition_null(cmd);
+ }
+ time: SECOND
+ {
+ $$ = FLB_SP_TIME_SECOND;
+ }
+ |
+ MINUTE
+ {
+ $$ = FLB_SP_TIME_MINUTE;
+ }
+ |
+ HOUR
+ {
+ $$ = FLB_SP_TIME_HOUR;
+ }
+ gb_keys: gb_key
+ |
+ gb_key ',' gb_keys
+ gb_key: IDENTIFIER
+ {
+ flb_sp_cmd_gb_key_add(cmd, $1);
+ flb_free($1);
+ }
+ |
+ IDENTIFIER record_subkey
+ {
+ flb_sp_cmd_gb_key_add(cmd, $1);
+ flb_free($1);
+ }
+ ;
diff --git a/fluent-bit/src/tls/flb_tls.c b/fluent-bit/src/tls/flb_tls.c
new file mode 100644
index 000000000..8fc711ab3
--- /dev/null
+++ b/fluent-bit/src/tls/flb_tls.c
@@ -0,0 +1,665 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2019-2020 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_time.h>
+#include <fluent-bit/flb_socket.h>
+
+#include "openssl.c"
+
+/* Config map for Upstream networking setup */
+struct flb_config_map tls_configmap[] = {
+ {
+ FLB_CONFIG_MAP_BOOL, "tls", "off",
+ 0, FLB_FALSE, 0,
+ "Enable or disable TLS/SSL support",
+ },
+ {
+ FLB_CONFIG_MAP_BOOL, "tls.verify", "on",
+ 0, FLB_FALSE, 0,
+ "Force certificate validation",
+ },
+ {
+ FLB_CONFIG_MAP_INT, "tls.debug", "1",
+ 0, FLB_FALSE, 0,
+ "Set TLS debug verbosity level. It accept the following "
+ "values: 0 (No debug), 1 (Error), 2 (State change), 3 "
+ "(Informational) and 4 Verbose"
+ },
+ {
+ FLB_CONFIG_MAP_STR, "tls.ca_file", NULL,
+ 0, FLB_FALSE, 0,
+ "Absolute path to CA certificate file"
+ },
+ {
+ FLB_CONFIG_MAP_STR, "tls.ca_path", NULL,
+ 0, FLB_FALSE, 0,
+ "Absolute path to scan for certificate files"
+ },
+ {
+ FLB_CONFIG_MAP_STR, "tls.crt_file", NULL,
+ 0, FLB_FALSE, 0,
+ "Absolute path to Certificate file"
+ },
+ {
+ FLB_CONFIG_MAP_STR, "tls.key_file", NULL,
+ 0, FLB_FALSE, 0,
+ "Absolute path to private Key file"
+ },
+ {
+ FLB_CONFIG_MAP_STR, "tls.key_passwd", NULL,
+ 0, FLB_FALSE, 0,
+ "Optional password for tls.key_file file"
+ },
+
+ {
+ FLB_CONFIG_MAP_STR, "tls.vhost", NULL,
+ 0, FLB_FALSE, 0,
+ "Hostname to be used for TLS SNI extension"
+ },
+
+ /* EOF */
+ {0}
+};
+
+struct mk_list *flb_tls_get_config_map(struct flb_config *config)
+{
+ struct mk_list *config_map;
+
+ config_map = flb_config_map_create(config, tls_configmap);
+ return config_map;
+}
+
+
+static inline void io_tls_backup_event(struct flb_connection *connection,
+ struct mk_event *backup)
+{
+ if (connection != NULL && backup != NULL) {
+ memcpy(backup, &connection->event, sizeof(struct mk_event));
+ }
+}
+
+static inline void io_tls_restore_event(struct flb_connection *connection,
+ struct mk_event *backup)
+{
+ int result;
+
+ if (connection != NULL && backup != NULL) {
+ if (MK_EVENT_IS_REGISTERED((&connection->event))) {
+ result = mk_event_del(connection->evl, &connection->event);
+
+ assert(result == 0);
+ }
+
+ if (MK_EVENT_IS_REGISTERED(backup)) {
+ connection->event.priority = backup->priority;
+ connection->event.handler = backup->handler;
+
+ result = mk_event_add(connection->evl,
+ connection->fd,
+ backup->type,
+ backup->mask,
+ &connection->event);
+
+ assert(result == 0);
+ }
+ }
+}
+
+
+static inline int io_tls_event_switch(struct flb_tls_session *session,
+ int mask)
+{
+ struct mk_event_loop *event_loop;
+ struct mk_event *event;
+ int ret;
+
+ event = &session->connection->event;
+ event_loop = session->connection->evl;
+
+ if ((event->mask & mask) == 0) {
+ ret = mk_event_add(event_loop,
+ event->fd,
+ FLB_ENGINE_EV_THREAD,
+ mask, event);
+
+ event->priority = FLB_ENGINE_PRIORITY_CONNECT;
+
+ if (ret == -1) {
+ flb_error("[io_tls] error changing mask to %i", mask);
+
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int flb_tls_load_system_certificates(struct flb_tls *tls)
+{
+ return load_system_certificates(tls->ctx);
+}
+
+struct flb_tls *flb_tls_create(int mode,
+ int verify,
+ int debug,
+ const char *vhost,
+ const char *ca_path,
+ const char *ca_file,
+ const char *crt_file,
+ const char *key_file,
+ const char *key_passwd)
+{
+ void *backend;
+ struct flb_tls *tls;
+
+ /* Assuming the TLS role based on the connection direction is wrong
+ * but it's something we'll accept for the moment.
+ */
+
+ backend = tls_context_create(verify, debug, mode,
+ vhost, ca_path, ca_file,
+ crt_file, key_file, key_passwd);
+ if (!backend) {
+ flb_error("[tls] could not create TLS backend");
+ return NULL;
+ }
+
+ tls = flb_calloc(1, sizeof(struct flb_tls));
+ if (!tls) {
+ flb_errno();
+ tls_context_destroy(backend);
+ return NULL;
+ }
+
+ tls->verify = verify;
+ tls->debug = debug;
+ tls->mode = mode;
+
+ if (vhost != NULL) {
+ tls->vhost = flb_strdup(vhost);
+ }
+ tls->ctx = backend;
+
+ tls->api = &tls_openssl;
+
+ return tls;
+}
+
+int flb_tls_init()
+{
+ return tls_init();
+}
+
+int flb_tls_destroy(struct flb_tls *tls)
+{
+ if (tls->ctx) {
+ tls->api->context_destroy(tls->ctx);
+ }
+
+ if (tls->vhost != NULL) {
+ flb_free(tls->vhost);
+ }
+
+ flb_free(tls);
+
+ return 0;
+}
+
+int flb_tls_net_read(struct flb_tls_session *session, void *buf, size_t len)
+{
+ time_t timeout_timestamp;
+ time_t current_timestamp;
+ struct flb_tls *tls;
+ int ret;
+
+ tls = session->tls;
+
+ if (session->connection->net->io_timeout > 0) {
+ timeout_timestamp = time(NULL) + session->connection->net->io_timeout;
+ }
+ else {
+ timeout_timestamp = 0;
+ }
+
+ retry_read:
+ ret = tls->api->net_read(session, buf, len);
+
+ current_timestamp = time(NULL);
+
+ if (ret == FLB_TLS_WANT_READ) {
+ if (timeout_timestamp > 0 &&
+ timeout_timestamp <= current_timestamp) {
+ return ret;
+ }
+
+ goto retry_read;
+ }
+ else if (ret == FLB_TLS_WANT_WRITE) {
+ goto retry_read;
+ }
+ else if (ret < 0) {
+ return -1;
+ }
+ else if (ret == 0) {
+ return -1;
+ }
+
+ return ret;
+}
+
+int flb_tls_net_read_async(struct flb_coro *co,
+ struct flb_tls_session *session,
+ void *buf, size_t len)
+{
+ int event_restore_needed;
+ struct mk_event event_backup;
+ struct flb_tls *tls;
+ int ret;
+
+ tls = session->tls;
+
+ event_restore_needed = FLB_FALSE;
+
+ io_tls_backup_event(session->connection, &event_backup);
+
+ retry_read:
+ ret = tls->api->net_read(session, buf, len);
+
+ if (ret == FLB_TLS_WANT_READ) {
+ event_restore_needed = FLB_TRUE;
+
+ session->connection->coroutine = co;
+
+ io_tls_event_switch(session, MK_EVENT_READ);
+ flb_coro_yield(co, FLB_FALSE);
+
+ goto retry_read;
+ }
+ else if (ret == FLB_TLS_WANT_WRITE) {
+ event_restore_needed = FLB_TRUE;
+
+ session->connection->coroutine = co;
+
+ io_tls_event_switch(session, MK_EVENT_WRITE);
+ flb_coro_yield(co, FLB_FALSE);
+
+ goto retry_read;
+ }
+ else
+ {
+ /* We want this field to hold NULL at all times unless we are explicitly
+ * waiting to be resumed.
+ */
+ session->connection->coroutine = NULL;
+
+ if (ret <= 0) {
+ ret = -1;
+ }
+ }
+
+ if (event_restore_needed) {
+ /* If we enter here it means we registered this connection
+ * in the event loop, in which case we need to unregister it
+ * and restore the original registration if there was one.
+ *
+ * We do it conditionally because in those cases in which
+ * send succeeds on the first try we don't touch the event
+ * and it wouldn't make sense to unregister and register for
+ * the same event.
+ */
+
+ io_tls_restore_event(session->connection, &event_backup);
+ }
+
+ return ret;
+}
+
+int flb_tls_net_write(struct flb_tls_session *session,
+ const void *data, size_t len, size_t *out_len)
+{
+ size_t total;
+ int ret;
+ struct flb_tls *tls;
+
+ total = 0;
+ tls = session->tls;
+
+retry_write:
+ ret = tls->api->net_write(session,
+ (unsigned char *) data + total,
+ len - total);
+
+ if (ret == FLB_TLS_WANT_WRITE) {
+ goto retry_write;
+ }
+ else if (ret == FLB_TLS_WANT_READ) {
+ goto retry_write;
+ }
+ else if (ret < 0) {
+ *out_len = total;
+
+ return -1;
+ }
+
+ /* Update counter and check if we need to continue writing */
+ total += ret;
+
+ if (total < len) {
+ goto retry_write;
+ }
+
+ *out_len = total;
+
+ return ret;
+}
+
+int flb_tls_net_write_async(struct flb_coro *co,
+ struct flb_tls_session *session,
+ const void *data, size_t len, size_t *out_len)
+{
+ int event_restore_needed;
+ struct mk_event event_backup;
+ size_t total;
+ int ret;
+ struct flb_tls *tls;
+
+ total = 0;
+ tls = session->tls;
+
+ event_restore_needed = FLB_FALSE;
+
+ io_tls_backup_event(session->connection, &event_backup);
+
+retry_write:
+ session->connection->coroutine = co;
+
+ ret = tls->api->net_write(session,
+ (unsigned char *) data + total,
+ len - total);
+
+ if (ret == FLB_TLS_WANT_WRITE) {
+ event_restore_needed = FLB_TRUE;
+
+ io_tls_event_switch(session, MK_EVENT_WRITE);
+
+ flb_coro_yield(co, FLB_FALSE);
+
+ goto retry_write;
+ }
+ else if (ret == FLB_TLS_WANT_READ) {
+ event_restore_needed = FLB_TRUE;
+
+ io_tls_event_switch(session, MK_EVENT_READ);
+
+ flb_coro_yield(co, FLB_FALSE);
+
+ goto retry_write;
+ }
+ else if (ret < 0) {
+ /* We want this field to hold NULL at all times unless we are explicitly
+ * waiting to be resumed.
+ */
+
+ session->connection->coroutine = NULL;
+ *out_len = total;
+
+ io_tls_restore_event(session->connection, &event_backup);
+
+ return -1;
+ }
+
+ /* Update counter and check if we need to continue writing */
+ total += ret;
+
+ if (total < len) {
+ io_tls_event_switch(session, MK_EVENT_WRITE);
+
+ flb_coro_yield(co, FLB_FALSE);
+
+ goto retry_write;
+ }
+
+ /* We want this field to hold NULL at all times unless we are explicitly
+ * waiting to be resumed.
+ */
+
+ session->connection->coroutine = NULL;
+
+ *out_len = total;
+
+ if (event_restore_needed) {
+ /* If we enter here it means we registered this connection
+ * in the event loop, in which case we need to unregister it
+ * and restore the original registration if there was one.
+ *
+ * We do it conditionally because in those cases in which
+ * send succeeds on the first try we don't touch the event
+ * and it wouldn't make sense to unregister and register for
+ * the same event.
+ */
+
+ io_tls_restore_event(session->connection, &event_backup);
+ }
+
+ return total;
+}
+
+int flb_tls_client_session_create(struct flb_tls *tls,
+ struct flb_connection *u_conn,
+ struct flb_coro *co)
+{
+ return flb_tls_session_create(tls, u_conn, co);
+}
+
+int flb_tls_server_session_create(struct flb_tls *tls,
+ struct flb_connection *connection,
+ struct flb_coro *co)
+{
+ return flb_tls_session_create(tls, connection, co);
+}
+
+/* Create a TLS session (server) */
+int flb_tls_session_create(struct flb_tls *tls,
+ struct flb_connection *connection,
+ struct flb_coro *co)
+{
+ int event_restore_needed;
+ struct mk_event event_backup;
+ struct flb_tls_session *session;
+ int result;
+ char *vhost;
+ int flag;
+
+ session = flb_calloc(1, sizeof(struct flb_tls_session));
+
+ if (session == NULL) {
+ return -1;
+ }
+
+ vhost = NULL;
+
+ if (connection->type == FLB_UPSTREAM_CONNECTION) {
+ if (connection->upstream->proxied_host != NULL) {
+ vhost = flb_rtrim(connection->upstream->proxied_host, '.');
+ }
+ else {
+ if (tls->vhost == NULL) {
+ vhost = flb_rtrim(connection->upstream->tcp_host, '.');
+ }
+ }
+ }
+
+ /* Create TLS session */
+ session->ptr = tls->api->session_create(tls, connection->fd);
+
+ if (session == NULL) {
+ flb_error("[tls] could not create TLS session for %s",
+ flb_connection_get_remote_address(connection));
+
+ return -1;
+ }
+
+ session->tls = tls;
+ session->connection = connection;
+
+ result = 0;
+
+ event_restore_needed = FLB_FALSE;
+
+ io_tls_backup_event(session->connection, &event_backup);
+
+ retry_handshake:
+ result = tls->api->net_handshake(tls, vhost, session->ptr);
+
+ if (result != 0) {
+ if (result != FLB_TLS_WANT_READ && result != FLB_TLS_WANT_WRITE) {
+ result = -1;
+
+ goto cleanup;
+ }
+
+ flag = 0;
+
+ if (result == FLB_TLS_WANT_WRITE) {
+ flag = MK_EVENT_WRITE;
+ }
+ else if (result == FLB_TLS_WANT_READ) {
+ flag = MK_EVENT_READ;
+ }
+
+ /*
+ * If there are no coroutine thread context (th == NULL) it means this
+ * TLS handshake is happening from a blocking code. Just sleep a bit
+ * and retry.
+ *
+ * In the other case for an async socket 'th' is NOT NULL so the code
+ * is under a coroutine context and it can yield.
+ */
+ if (co == NULL) {
+ flb_trace("[io_tls] server handshake connection #%i in process to %s",
+ connection->fd,
+ flb_connection_get_remote_address(connection));
+
+ /* Connect timeout */
+ if (connection->net->connect_timeout > 0 &&
+ connection->ts_connect_timeout > 0 &&
+ connection->ts_connect_timeout <= time(NULL)) {
+ flb_error("[io_tls] handshake connection #%i to %s timed out after "
+ "%i seconds",
+ connection->fd,
+ flb_connection_get_remote_address(connection),
+ connection->net->connect_timeout);
+
+ result = -1;
+
+ goto cleanup;
+ }
+
+ flb_time_msleep(500);
+
+ goto retry_handshake;
+ }
+
+ event_restore_needed = FLB_TRUE;
+
+ /*
+ * FIXME: if we need multiple reads we are invoking the same
+ * system call multiple times.
+ */
+
+ result = mk_event_add(connection->evl,
+ connection->fd,
+ FLB_ENGINE_EV_THREAD,
+ flag,
+ &connection->event);
+
+ connection->event.priority = FLB_ENGINE_PRIORITY_CONNECT;
+
+ if (result == -1) {
+ goto cleanup;
+ }
+
+ connection->coroutine = co;
+
+ flb_coro_yield(co, FLB_FALSE);
+
+ /* We want this field to hold NULL at all times unless we are explicitly
+ * waiting to be resumed.
+ */
+
+ connection->coroutine = NULL;
+
+ /* This check's purpose is to abort when a timeout is detected.
+ */
+ if (connection->net_error == -1) {
+ goto retry_handshake;
+ }
+ else {
+ result = -1;
+ }
+ }
+
+cleanup:
+ if (event_restore_needed) {
+ /* If we enter here it means we registered this connection
+ * in the event loop, in which case we need to unregister it
+ * and restore the original registration if there was one.
+ *
+ * We do it conditionally because in those cases in which
+ * send succeeds on the first try we don't touch the event
+ * and it wouldn't make sense to unregister and register for
+ * the same event.
+ */
+
+ io_tls_restore_event(session->connection, &event_backup);
+ }
+
+ if (result != 0) {
+ flb_tls_session_destroy(session);
+ }
+ else {
+ connection->tls_session = session;
+ }
+
+ if (vhost != NULL) {
+ flb_free(vhost);
+ }
+
+ return result;
+}
+
+int flb_tls_session_destroy(struct flb_tls_session *session)
+{
+ int ret;
+
+ session->connection->tls_session = NULL;
+
+ if (session->ptr != NULL) {
+ ret = session->tls->api->session_destroy(session->ptr);
+
+ if (ret == -1) {
+ return -1;
+ }
+
+ flb_free(session);
+ }
+
+ return 0;
+}
diff --git a/fluent-bit/src/tls/openssl.c b/fluent-bit/src/tls/openssl.c
new file mode 100644
index 000000000..0d5d60bb6
--- /dev/null
+++ b/fluent-bit/src/tls/openssl.c
@@ -0,0 +1,616 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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 <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_compat.h>
+#include <fluent-bit/tls/flb_tls.h>
+#include <fluent-bit/tls/flb_tls_info.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/opensslv.h>
+
+/*
+ * OPENSSL_VERSION_NUMBER has the following semantics
+ *
+ * 0x010100000L M = major F = fix S = status
+ * MMNNFFPPS N = minor P = patch
+ */
+#define OPENSSL_1_1_0 0x010100000L
+
+/* OpenSSL library context */
+struct tls_context {
+ int debug_level;
+ SSL_CTX *ctx;
+ int mode;
+ pthread_mutex_t mutex;
+};
+
+struct tls_session {
+ SSL *ssl;
+ int fd;
+ int continuation_flag;
+ struct tls_context *parent; /* parent struct tls_context ref */
+};
+
+
+static int tls_init(void)
+{
+/*
+ * Explicit initialization is needed for older versions of
+ * OpenSSL (before v1.1.0).
+ *
+ * https://wiki.openssl.org/index.php/Library_Initialization
+ */
+#if OPENSSL_VERSION_NUMBER < OPENSSL_1_1_0
+ OPENSSL_add_all_algorithms_noconf();
+ SSL_load_error_strings();
+ SSL_library_init();
+#endif
+ return 0;
+}
+
+static void tls_info_callback(const SSL *s, int where, int ret)
+{
+ int w;
+ int fd;
+ const char *str;
+
+ fd = SSL_get_fd(s);
+ w = where & ~SSL_ST_MASK;
+ if (w & SSL_ST_CONNECT) {
+ str = "SSL_connect";
+ }
+ else if (w & SSL_ST_ACCEPT) {
+ str = "SSL_accept";
+ }
+ else {
+ str = "undefined";
+ }
+
+ if (where & SSL_CB_LOOP) {
+ flb_debug("[tls] connection #%i %s: %s",
+ fd, str, SSL_state_string_long(s));
+ }
+ else if (where & SSL_CB_ALERT) {
+ str = (where & SSL_CB_READ) ? "read" : "write";
+ flb_debug("[tls] connection #%i SSL3 alert %s:%s:%s",
+ fd, str,
+ SSL_alert_type_string_long(ret),
+ SSL_alert_desc_string_long(ret));
+ }
+ else if (where & SSL_CB_EXIT) {
+ if (ret == 0) {
+ flb_error("[tls] connection #%i %s: failed in %s",
+ fd, str, SSL_state_string_long(s));
+ }
+ else if (ret < 0) {
+ ret = SSL_get_error(s, ret);
+ if (ret == SSL_ERROR_WANT_WRITE) {
+ flb_debug("[tls] connection #%i WANT_WRITE", fd);
+ }
+ else if (ret == SSL_ERROR_WANT_READ) {
+ flb_debug("[tls] connection #%i WANT_READ", fd);
+ }
+ else {
+ flb_error("[tls] connection #%i %s: error in %s",
+ fd, str, SSL_state_string_long(s));
+ }
+ }
+ }
+}
+
+static void tls_context_destroy(void *ctx_backend)
+{
+ struct tls_context *ctx = ctx_backend;
+
+ pthread_mutex_lock(&ctx->mutex);
+ SSL_CTX_free(ctx->ctx);
+ pthread_mutex_unlock(&ctx->mutex);
+
+ flb_free(ctx);
+}
+
+#ifdef _MSC_VER
+static int windows_load_system_certificates(struct tls_context *ctx)
+{
+ int ret;
+ HANDLE win_store;
+ PCCERT_CONTEXT win_cert = NULL;
+ const unsigned char *win_cert_data;
+ X509_STORE *ossl_store = SSL_CTX_get_cert_store(ctx->ctx);
+ X509 *ossl_cert;
+
+ win_store = CertOpenSystemStoreA(0, "Root");
+ if (win_store == NULL) {
+ flb_error("[tls] Cannot open cert store: %i", GetLastError());
+ return -1;
+ }
+
+ while (win_cert = CertEnumCertificatesInStore(win_store, win_cert)) {
+ if (win_cert->dwCertEncodingType & X509_ASN_ENCODING) {
+ /*
+ * Decode the certificate into X509 struct.
+ *
+ * The additional pointer variable is necessary per OpenSSL docs because the
+ * pointer is incremented by d2i_X509.
+ */
+ win_cert_data = win_cert->pbCertEncoded;
+ ossl_cert = d2i_X509(NULL, &win_cert_data, win_cert->cbCertEncoded);
+ if (!ossl_cert) {
+ flb_debug("[tls] Cannot parse a certificate. skipping...");
+ continue;
+ }
+
+ /* Add X509 struct to the openssl cert store */
+ ret = X509_STORE_add_cert(ossl_store, ossl_cert);
+ if (!ret) {
+ flb_warn("[tls] Failed to add a certificate to the store: %lu: %s",
+ ERR_get_error(), ERR_error_string(ERR_get_error(), NULL));
+ }
+ X509_free(ossl_cert);
+ }
+ }
+
+ if (!CertCloseStore(win_store, 0)) {
+ flb_error("[tls] Cannot close cert store: %i", GetLastError());
+ return -1;
+ }
+ return 0;
+}
+#endif
+
+static int load_system_certificates(struct tls_context *ctx)
+{
+ int ret;
+ const char *ca_file = FLB_DEFAULT_SEARCH_CA_BUNDLE;
+
+ /* For Windows use specific API to read the certs store */
+#ifdef _MSC_VER
+ return windows_load_system_certificates(ctx);
+#endif
+ if (access(ca_file, R_OK) != 0) {
+ ca_file = NULL;
+ }
+
+ ret = SSL_CTX_load_verify_locations(ctx->ctx, ca_file, FLB_DEFAULT_CA_DIR);
+
+ if (ret != 1) {
+ ERR_print_errors_fp(stderr);
+ }
+ return 0;
+}
+
+static void *tls_context_create(int verify,
+ int debug,
+ int mode,
+ const char *vhost,
+ const char *ca_path,
+ const char *ca_file,
+ const char *crt_file,
+ const char *key_file,
+ const char *key_passwd)
+{
+ int ret;
+ SSL_CTX *ssl_ctx;
+ struct tls_context *ctx;
+ char err_buf[256];
+
+ /*
+ * Init library ? based in the documentation on OpenSSL >= 1.1.0 is not longer
+ * necessary since the library will initialize it self:
+ *
+ * https://wiki.openssl.org/index.php/Library_Initialization
+ */
+
+ /* Create OpenSSL context */
+#if OPENSSL_VERSION_NUMBER < OPENSSL_1_1_0
+ /*
+ * SSLv23_method() is actually an equivalent of TLS_client_method()
+ * in OpenSSL v1.0.x.
+ *
+ * https://www.openssl.org/docs/man1.0.2/man3/SSLv23_method.html
+ */
+ if (mode == FLB_TLS_SERVER_MODE) {
+ ssl_ctx = SSL_CTX_new(SSLv23_server_method());
+ }
+ else {
+ ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+ }
+
+#else
+ if (mode == FLB_TLS_SERVER_MODE) {
+ ssl_ctx = SSL_CTX_new(TLS_server_method());
+ }
+ else {
+ ssl_ctx = SSL_CTX_new(TLS_client_method());
+ }
+#endif
+ if (!ssl_ctx) {
+ flb_error("[openssl] could not create context");
+ return NULL;
+ }
+
+ ctx = flb_calloc(1, sizeof(struct tls_context));
+ if (!ctx) {
+ flb_errno();
+ return NULL;
+ }
+ ctx->ctx = ssl_ctx;
+ ctx->mode = mode;
+ ctx->debug_level = debug;
+ pthread_mutex_init(&ctx->mutex, NULL);
+
+ /* Verify peer: by default OpenSSL always verify peer */
+ if (verify == FLB_FALSE) {
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
+ }
+ else {
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
+ }
+
+ /* ca_path | ca_file */
+ if (ca_path) {
+ ret = SSL_CTX_load_verify_locations(ctx->ctx, NULL, ca_path);
+ if (ret != 1) {
+ ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)-1);
+ flb_error("[tls] ca_path '%s' %lu: %s",
+ ca_path, ERR_get_error(), err_buf);
+ goto error;
+ }
+ }
+ else if (ca_file) {
+ ret = SSL_CTX_load_verify_locations(ctx->ctx, ca_file, NULL);
+ if (ret != 1) {
+ ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)-1);
+ flb_error("[tls] ca_file '%s' %lu: %s",
+ ca_file, ERR_get_error(), err_buf);
+ goto error;
+ }
+ }
+ else {
+ load_system_certificates(ctx);
+ }
+
+ /* crt_file */
+ if (crt_file) {
+ ret = SSL_CTX_use_certificate_chain_file(ssl_ctx, crt_file);
+ if (ret != 1) {
+ ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)-1);
+ flb_error("[tls] crt_file '%s' %lu: %s",
+ crt_file, ERR_get_error(), err_buf);
+ goto error;
+ }
+ }
+
+ /* key_file */
+ if (key_file) {
+ if (key_passwd) {
+ SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx,
+ (void *) key_passwd);
+ }
+ ret = SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file,
+ SSL_FILETYPE_PEM);
+ if (ret != 1) {
+ ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)-1);
+ flb_error("[tls] key_file '%s' %lu: %s",
+ crt_file, ERR_get_error(), err_buf);
+ }
+
+ /* Make sure the key and certificate file match */
+ if (SSL_CTX_check_private_key(ssl_ctx) != 1) {
+ flb_error("[tls] private_key '%s' and password don't match",
+ key_file);
+ goto error;
+ }
+ }
+
+ return ctx;
+
+ error:
+ tls_context_destroy(ctx);
+ return NULL;
+}
+
+static void *tls_session_create(struct flb_tls *tls,
+ int fd)
+{
+ struct tls_session *session;
+ struct tls_context *ctx = tls->ctx;
+ SSL *ssl;
+
+ session = flb_calloc(1, sizeof(struct tls_session));
+ if (!session) {
+ flb_errno();
+ return NULL;
+ }
+ session->parent = ctx;
+
+ pthread_mutex_lock(&ctx->mutex);
+ ssl = SSL_new(ctx->ctx);
+
+ if (!ssl) {
+ flb_error("[openssl] could create new SSL context");
+ flb_free(session);
+ pthread_mutex_unlock(&ctx->mutex);
+ return NULL;
+ }
+
+ session->continuation_flag = FLB_FALSE;
+ session->ssl = ssl;
+ session->fd = fd;
+ SSL_set_fd(ssl, fd);
+
+ /*
+ * TLS Debug Levels:
+ *
+ * 0: No debug,
+ * 1: Error
+ * 2: State change
+ * 3: Informational
+ * 4: Verbose
+ */
+ if (tls->debug == 1) {
+ SSL_set_info_callback(session->ssl, tls_info_callback);
+ }
+ pthread_mutex_unlock(&ctx->mutex);
+ return session;
+}
+
+static int tls_session_destroy(void *session)
+{
+ struct tls_session *ptr = session;
+ struct tls_context *ctx;
+
+ if (!ptr) {
+ return 0;
+ }
+ ctx = ptr->parent;
+
+ pthread_mutex_lock(&ctx->mutex);
+
+ if (flb_socket_error(ptr->fd) == 0) {
+ SSL_shutdown(ptr->ssl);
+ }
+ SSL_free(ptr->ssl);
+ flb_free(ptr);
+
+ pthread_mutex_unlock(&ctx->mutex);
+
+ return 0;
+}
+
+static int tls_net_read(struct flb_tls_session *session,
+ void *buf, size_t len)
+{
+ int ret;
+ char err_buf[256];
+ struct tls_context *ctx;
+ struct tls_session *backend_session;
+
+ if (session->ptr == NULL) {
+ flb_error("[tls] error: uninitialized backend session");
+
+ return -1;
+ }
+
+ backend_session = (struct tls_session *) session->ptr;
+
+ ctx = backend_session->parent;
+
+ pthread_mutex_lock(&ctx->mutex);
+
+ ERR_clear_error();
+
+ ret = SSL_read(backend_session->ssl, buf, len);
+
+ if (ret <= 0) {
+ ret = SSL_get_error(backend_session->ssl, ret);
+
+ if (ret == SSL_ERROR_WANT_READ) {
+ ret = FLB_TLS_WANT_READ;
+ }
+ else if (ret == SSL_ERROR_WANT_WRITE) {
+ ret = FLB_TLS_WANT_WRITE;
+ }
+ else if (ret == SSL_ERROR_SYSCALL) {
+ flb_errno();
+ ERR_error_string_n(ret, err_buf, sizeof(err_buf)-1);
+ flb_error("[tls] syscall error: %s", err_buf);
+
+ /* According to the documentation these are non-recoverable
+ * errors so we don't need to screen them before saving them
+ * to the net_error field.
+ */
+
+ session->connection->net_error = errno;
+
+ ret = -1;
+ }
+ else if (ret < 0) {
+ ERR_error_string_n(ret, err_buf, sizeof(err_buf)-1);
+ flb_error("[tls] error: %s", err_buf);
+ }
+ else {
+ ret = -1;
+ }
+ }
+
+ pthread_mutex_unlock(&ctx->mutex);
+ return ret;
+}
+
+static int tls_net_write(struct flb_tls_session *session,
+ const void *data, size_t len)
+{
+ int ret;
+ char err_buf[256];
+ size_t total = 0;
+ struct tls_context *ctx;
+ struct tls_session *backend_session;
+
+ if (session->ptr == NULL) {
+ flb_error("[tls] error: uninitialized backend session");
+
+ return -1;
+ }
+
+ backend_session = (struct tls_session *) session->ptr;
+ ctx = backend_session->parent;
+
+ pthread_mutex_lock(&ctx->mutex);
+
+ ERR_clear_error();
+
+ ret = SSL_write(backend_session->ssl,
+ (unsigned char *) data + total,
+ len - total);
+
+ if (ret <= 0) {
+ ret = SSL_get_error(backend_session->ssl, ret);
+
+ if (ret == SSL_ERROR_WANT_WRITE) {
+ ret = FLB_TLS_WANT_WRITE;
+ }
+ else if (ret == SSL_ERROR_WANT_READ) {
+ ret = FLB_TLS_WANT_READ;
+ }
+ else if (ret == SSL_ERROR_SYSCALL) {
+ flb_errno();
+ ERR_error_string_n(ret, err_buf, sizeof(err_buf)-1);
+ flb_error("[tls] syscall error: %s", err_buf);
+
+ /* According to the documentation these are non-recoverable
+ * errors so we don't need to screen them before saving them
+ * to the net_error field.
+ */
+
+ session->connection->net_error = errno;
+
+ ret = -1;
+ }
+ else {
+ ERR_error_string_n(ret, err_buf, sizeof(err_buf)-1);
+ flb_error("[tls] error: %s", err_buf);
+
+ ret = -1;
+ }
+ }
+
+ pthread_mutex_unlock(&ctx->mutex);
+
+ /* Update counter and check if we need to continue writing */
+ return ret;
+}
+
+static int tls_net_handshake(struct flb_tls *tls,
+ char *vhost,
+ void *ptr_session)
+{
+ int ret = 0;
+ char err_buf[256];
+ struct tls_session *session = ptr_session;
+ struct tls_context *ctx;
+
+ ctx = session->parent;
+ pthread_mutex_lock(&ctx->mutex);
+
+ if (!session->continuation_flag) {
+ if (tls->mode == FLB_TLS_CLIENT_MODE) {
+ SSL_set_connect_state(session->ssl);
+ }
+ else if (tls->mode == FLB_TLS_SERVER_MODE) {
+ SSL_set_accept_state(session->ssl);
+ }
+ else {
+ flb_error("[tls] error: invalid tls mode : %d", tls->mode);
+ pthread_mutex_unlock(&ctx->mutex);
+ return -1;
+ }
+
+ if (vhost != NULL) {
+ SSL_set_tlsext_host_name(session->ssl, vhost);
+ }
+ else if (tls->vhost) {
+ SSL_set_tlsext_host_name(session->ssl, tls->vhost);
+ }
+ }
+
+ ERR_clear_error();
+
+ if (tls->mode == FLB_TLS_CLIENT_MODE) {
+ ret = SSL_connect(session->ssl);
+ }
+ else if (tls->mode == FLB_TLS_SERVER_MODE) {
+ ret = SSL_accept(session->ssl);
+ }
+
+ if (ret != 1) {
+ ret = SSL_get_error(session->ssl, ret);
+ if (ret != SSL_ERROR_WANT_READ &&
+ ret != SSL_ERROR_WANT_WRITE) {
+ ret = SSL_get_error(session->ssl, ret);
+ // The SSL_ERROR_SYSCALL with errno value of 0 indicates unexpected
+ // EOF from the peer. This is fixed in OpenSSL 3.0.
+ if (ret == 0) {
+ flb_error("[tls] error: unexpected EOF");
+ } else {
+ ERR_error_string_n(ret, err_buf, sizeof(err_buf)-1);
+ flb_error("[tls] error: %s", err_buf);
+ }
+
+ pthread_mutex_unlock(&ctx->mutex);
+
+ return -1;
+ }
+
+ if (ret == SSL_ERROR_WANT_WRITE) {
+ pthread_mutex_unlock(&ctx->mutex);
+
+ session->continuation_flag = FLB_TRUE;
+
+ return FLB_TLS_WANT_WRITE;
+ }
+ else if (ret == SSL_ERROR_WANT_READ) {
+ pthread_mutex_unlock(&ctx->mutex);
+
+ session->continuation_flag = FLB_TRUE;
+
+ return FLB_TLS_WANT_READ;
+ }
+ }
+
+ session->continuation_flag = FLB_FALSE;
+
+ pthread_mutex_unlock(&ctx->mutex);
+ flb_trace("[tls] connection and handshake OK");
+ return 0;
+}
+
+/* OpenSSL backend registration */
+static struct flb_tls_backend tls_openssl = {
+ .name = "openssl",
+ .context_create = tls_context_create,
+ .context_destroy = tls_context_destroy,
+ .session_create = tls_session_create,
+ .session_destroy = tls_session_destroy,
+ .net_read = tls_net_read,
+ .net_write = tls_net_write,
+ .net_handshake = tls_net_handshake,
+};
diff --git a/fluent-bit/src/wamrc/CMakeLists.txt b/fluent-bit/src/wamrc/CMakeLists.txt
new file mode 100644
index 000000000..f8de6449f
--- /dev/null
+++ b/fluent-bit/src/wamrc/CMakeLists.txt
@@ -0,0 +1,210 @@
+string (TOLOWER ${CMAKE_HOST_SYSTEM_NAME} WAMR_BUILD_PLATFORM)
+enable_language (CXX)
+
+if (FLB_SYSTEM_WINDOWS)
+ enable_language(ASM_MASM)
+endif()
+if (APPLE)
+ add_definitions(-DBH_PLATFORM_DARWIN)
+endif ()
+
+set (CMAKE_CXX_STANDARD 14)
+
+if (FLB_SYSTEM_WINDOWS)
+ add_definitions(-DCOMPILING_WASM_RUNTIME_API=1)
+endif ()
+
+# Reset default linker flags
+set (CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
+set (CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")
+
+# WAMR features switch
+
+# Set WAMR_BUILD_TARGET, currently values supported:
+# "X86_64", "AMD_64", "X86_32", "AARCH64[sub]", "ARM[sub]", "THUMB[sub]",
+# "MIPS", "XTENSA", "RISCV64[sub]", "RISCV32[sub]"
+if (NOT DEFINED WAMR_BUILD_TARGET)
+ if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|aarch64)")
+ set (WAMR_BUILD_TARGET "AARCH64")
+ add_definitions(-DBUILD_TARGET="${WAMR_BUILD_TARGET}")
+ # For raspbian/buster: armv6l-unknown-linux-gnueabihf
+ elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "^(armv6.*|armv7.*)")
+ set (WAMR_BUILD_TARGET "ARM")
+ elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv64")
+ set (WAMR_BUILD_TARGET "RISCV64")
+ elseif (CMAKE_SIZEOF_VOID_P EQUAL 8)
+ # Build as X86_64 by default in 64-bit platform
+ set (WAMR_BUILD_TARGET "X86_64")
+ elseif (CMAKE_SIZEOF_VOID_P EQUAL 4)
+ # Build as X86_32 by default in 32-bit platform
+ set (WAMR_BUILD_TARGET "X86_32")
+ else ()
+ message(SEND_ERROR "Unsupported build target platform!")
+ endif ()
+endif ()
+
+if (NOT CMAKE_BUILD_TYPE)
+ set (CMAKE_BUILD_TYPE Release)
+endif ()
+
+add_definitions(-DWASM_ENABLE_INTERP=1)
+add_definitions(-DWASM_ENABLE_WAMR_COMPILER=1)
+add_definitions(-DWASM_ENABLE_BULK_MEMORY=1)
+add_definitions(-DWASM_DISABLE_HW_BOUND_CHECK=1)
+add_definitions(-DWASM_ENABLE_SHARED_MEMORY=1)
+add_definitions(-DWASM_ENABLE_THREAD_MGR=1)
+add_definitions(-DWASM_ENABLE_TAIL_CALL=1)
+add_definitions(-DWASM_ENABLE_SIMD=1)
+add_definitions(-DWASM_ENABLE_REF_TYPES=1)
+add_definitions(-DWASM_ENABLE_CUSTOM_NAME_SECTION=1)
+add_definitions(-DWASM_ENABLE_DUMP_CALL_STACK=1)
+add_definitions(-DWASM_ENABLE_PERF_PROFILING=1)
+add_definitions(-DWASM_ENABLE_LOAD_CUSTOM_SECTION=1)
+if (WAMR_BUILD_LLVM_LEGACY_PM EQUAL 1)
+ add_definitions(-DWASM_ENABLE_LLVM_LEGACY_PM=1)
+endif()
+
+if (DEFINED WAMR_BUILD_AOT_FUNC_PREFIX)
+ add_definitions(-DAOT_FUNC_PREFIX="${WAMR_BUILD_AOT_FUNC_PREFIX}")
+endif ()
+
+if (NOT MSVC)
+ # linker flags
+ set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie -fPIE")
+ if (NOT (CMAKE_C_COMPILER MATCHES ".*clang.*" OR CMAKE_C_COMPILER_ID MATCHES ".*Clang"))
+ set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")
+ endif ()
+ set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wformat -Wformat-security")
+ if (WAMR_BUILD_TARGET MATCHES "X86_.*" OR WAMR_BUILD_TARGET STREQUAL "AMD_64")
+ if (NOT (CMAKE_C_COMPILER MATCHES ".*clang.*" OR CMAKE_C_COMPILER_ID MATCHES ".*Clang"))
+ check_c_compiler_flag(-mindirect-branch-register FLB_WAMRC_INDIRECT_BRANCH_REGISTER_SUPPORTED)
+ if (FLB_WAMRC_INDIRECT_BRANCH_REGISTER_SUPPORTE)
+ set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mindirect-branch-register")
+ endif ()
+ endif ()
+ endif ()
+endif ()
+
+# Searching homebrewed LLVM automatically on macOS.
+if(FLB_SYSTEM_MACOS)
+ execute_process(
+ COMMAND brew --prefix llvm
+ RESULT_VARIABLE HOMEBREW_LLVM
+ OUTPUT_VARIABLE HOMEBREW_LLVM_PREFIX
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+ if (HOMEBREW_LLVM EQUAL 0 AND EXISTS "${HOMEBREW_LLVM_PREFIX}")
+ message(STATUS "Using llvm keg installed by Homebrew at ${HOMEBREW_LLVM_PREFIX}")
+ set(ENV{LLVM_DIR} "${HOMEBREW_LLVM_PREFIX}")
+ endif()
+endif()
+
+# Enable LLVM
+set (WAMR_BUILD_WITH_SYSTEM_LLVM 1)
+if (NOT WAMR_BUILD_WITH_SYSTEM_LLVM)
+ set (LLVM_SRC_ROOT "${PROJECT_SOURCE_DIR}/../core/deps/llvm")
+ if (WAMR_BUILD_PLATFORM STREQUAL "windows")
+ if (NOT EXISTS "${LLVM_SRC_ROOT}/win32build")
+ message (FATAL_ERROR "Cannot find LLVM dir: ${LLVM_SRC_ROOT}/win32build")
+ endif ()
+ set (CMAKE_PREFIX_PATH "${LLVM_SRC_ROOT}/win32build;${CMAKE_PREFIX_PATH}")
+ else()
+ if (NOT EXISTS "${LLVM_SRC_ROOT}/build")
+ message (FATAL_ERROR "Cannot find LLVM dir: ${LLVM_SRC_ROOT}/build")
+ endif ()
+ set (CMAKE_PREFIX_PATH "${LLVM_SRC_ROOT}/build;${CMAKE_PREFIX_PATH}")
+ endif ()
+endif ()
+
+if(LLVM_ENABLE_CURL STREQUAL FORCE_ON)
+ find_package(CURL REQUIRED)
+else()
+ find_package(CURL)
+endif()
+
+find_package(LLVM CONFIG)
+if (LLVM_FOUND)
+ if (LLVM_PACKAGE_VERSION VERSION_LESS 13.0)
+ message(STATUS "Outdated LLVM ${LLVM_PACKAGE_VERSION} is found. WAMRC won't be built.")
+ set(LLVM_FOUND 0)
+ endif()
+else()
+ message(STATUS "LLVM is not found. WAMRC won't be built.")
+endif()
+if (LLVM_FOUND)
+ include_directories(${LLVM_INCLUDE_DIRS})
+ add_definitions(${LLVM_DEFINITIONS})
+
+ message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
+ message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
+
+ set (WAMR_ROOT_DIR ../../${FLB_PATH_LIB_WASM_MICRO_RUNTIME})
+ set (SHARED_DIR ${WAMR_ROOT_DIR}/core/shared)
+ set (IWASM_DIR ${WAMR_ROOT_DIR}/core/iwasm)
+ set (APP_FRAMEWORK_DIR ${WAMER_ROOT_DIR}/core/app-framework)
+
+ include_directories (${SHARED_DIR}/include
+ ${IWASM_DIR}/include)
+
+ enable_language (ASM)
+
+ include (${SHARED_DIR}/platform/${WAMR_BUILD_PLATFORM}/shared_platform.cmake)
+ include (${SHARED_DIR}/mem-alloc/mem_alloc.cmake)
+ include (${SHARED_DIR}/utils/shared_utils.cmake)
+ include (${SHARED_DIR}/utils/uncommon/shared_uncommon.cmake)
+ include (${IWASM_DIR}/libraries/thread-mgr/thread_mgr.cmake)
+ include (${IWASM_DIR}/libraries/libc-builtin/libc_builtin.cmake)
+ if (NOT MINGW)
+ if (NOT MSVC)
+ include (${IWASM_DIR}/libraries/libc-wasi/libc_wasi.cmake)
+ else()
+ include (${IWASM_DIR}/libraries/libc-uvwasi/libc_uvwasi.cmake)
+ endif()
+ endif()
+ include (${IWASM_DIR}/libraries/lib-pthread/lib_pthread.cmake)
+ include (${IWASM_DIR}/common/iwasm_common.cmake)
+ include (${IWASM_DIR}/interpreter/iwasm_interp.cmake)
+ include (${IWASM_DIR}/aot/iwasm_aot.cmake)
+ include (${IWASM_DIR}/compilation/iwasm_compl.cmake)
+
+ add_library (vmlib-wamrc-static STATIC
+ ${PLATFORM_SHARED_SOURCE}
+ ${MEM_ALLOC_SHARED_SOURCE}
+ ${UTILS_SHARED_SOURCE}
+ ${UNCOMMON_SHARED_SOURCE}
+ ${THREAD_MGR_SOURCE}
+ ${LIBC_BUILTIN_SOURCE}
+ ${LIBC_WASI_SOURCE}
+ ${LIB_PTHREAD_SOURCE}
+ ${IWASM_COMMON_SOURCE}
+ ${IWASM_INTERP_SOURCE}
+ ${IWASM_AOT_SOURCE})
+ add_library (aotclib-static STATIC ${IWASM_COMPL_SOURCE})
+ add_executable (flb-wamrc-bin ${WAMR_ROOT_DIR}/wamr-compiler/main.c)
+ if (NOT MSVC)
+ target_link_libraries (flb-wamrc-bin aotclib-static vmlib-wamrc-static LLVMDemangle ${LLVM_AVAILABLE_LIBS} ${lib_ubsan}
+ -lm -lpthread ${lib_lldb} ${UV_A_LIBS})
+ if (MINGW)
+ target_link_libraries (flb-wamrc-bin -lssp -lWs2_32)
+ else()
+ target_link_libraries (flb-wamrc-bin -ldl)
+ endif()
+ else()
+ target_link_libraries (flb-wamrc-bin aotclib-static vmlib-wamrc-static ${lib_lldb} ${LLVM_AVAILABLE_LIBS} ${lib_ubsan}
+ ${UV_A_LIBS})
+ endif()
+
+ set_target_properties(flb-wamrc-bin
+ PROPERTIES
+ OUTPUT_NAME flb-wamrc
+ ENABLE_EXPORTS ON)
+ install(TARGETS flb-wamrc-bin RUNTIME DESTINATION ${FLB_INSTALL_BINDIR} COMPONENT binary)
+
+ # Include PDB file (if available)
+ if (MSVC)
+ target_link_options(flb-wamrc-bin
+ PUBLIC /pdb:$<TARGET_PDB_FILE:flb-wamrc-bin>)
+ install(FILES $<TARGET_PDB_FILE:flb-wamrc-bin>
+ DESTINATION "${FLB_INSTALL_BINDIR}")
+ endif()
+endif () \ No newline at end of file
diff --git a/fluent-bit/src/wasm/CMakeLists.txt b/fluent-bit/src/wasm/CMakeLists.txt
new file mode 100644
index 000000000..b345c4b45
--- /dev/null
+++ b/fluent-bit/src/wasm/CMakeLists.txt
@@ -0,0 +1,111 @@
+string (TOLOWER ${CMAKE_HOST_SYSTEM_NAME} WAMR_BUILD_PLATFORM)
+if (FLB_SYSTEM_WINDOWS)
+ enable_language(ASM_MASM)
+endif()
+if (APPLE)
+ add_definitions(-DBH_PLATFORM_DARWIN)
+endif ()
+
+set (CMAKE_C_STANDARD 99)
+
+if (FLB_SYSTEM_WINDOWS)
+ add_definitions(-DCOMPILING_WASM_RUNTIME_API=1)
+endif ()
+
+# WAMR features switch
+
+# Set WAMR_BUILD_TARGET, currently values supported:
+# "X86_64", "AMD_64", "X86_32", "AARCH64[sub]", "ARM[sub]", "THUMB[sub]",
+# "MIPS", "XTENSA", "RISCV64[sub]", "RISCV32[sub]"
+if (NOT DEFINED WAMR_BUILD_TARGET)
+ if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|aarch64)")
+ set (WAMR_BUILD_TARGET "AARCH64")
+ if (FLB_SYSTEM_MACOS)
+ message(STATUS "macOS arm64 platform is poor support for AOT loading. Now disabling for it.")
+ set (WAMR_DISABLE_AOT_LOADING 1)
+ FLB_DEFINITION(FLB_WAMR_DISABLE_AOT_LOADING)
+ endif ()
+ # For raspbian/buster: armv6l-unknown-linux-gnueabihf
+ elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "^(armv6.*|armv7.*)")
+ set (WAMR_BUILD_TARGET "ARM")
+ elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv64")
+ set (WAMR_BUILD_TARGET "RISCV64")
+ elseif (CMAKE_SIZEOF_VOID_P EQUAL 8)
+ # Build as X86_64 by default in 64-bit platform
+ set (WAMR_BUILD_TARGET "X86_64")
+ elseif (CMAKE_SIZEOF_VOID_P EQUAL 4)
+ # Build as X86_32 by default in 32-bit platform
+ set (WAMR_BUILD_TARGET "X86_32")
+ else ()
+ message(SEND_ERROR "Unsupported build target platform!")
+ endif ()
+endif ()
+
+if (NOT CMAKE_BUILD_TYPE)
+ set (CMAKE_BUILD_TYPE Release)
+endif ()
+
+set (WAMR_BUILD_MINI_LOADER 0)
+set (WAMR_BUILD_INTERP 1)
+set (WAMR_BUILD_FAST_INTERP 1)
+if (NOT DEFINED WAMR_DISABLE_AOT_LOADING)
+ set (WAMR_BUILD_AOT 1)
+endif ()
+set (WAMR_BUILD_JIT 0)
+set (WAMR_BUILD_LIBC_BUILTIN 1)
+if (MSVC)
+ # Currently, LIBC_UVWASI build is disabled.
+ # FIXME: Need to investigate how to build libuv and uvwasi without fetching repos.
+ set (WAMR_BUILD_LIBC_UVWASI 0)
+else ()
+ set (WAMR_BUILD_LIBC_WASI 1)
+endif ()
+if (NOT MSVC)
+ set (WAMR_BUILD_LIB_PTHREAD 1)
+endif ()
+set (WAMR_BUILD_REF_TYPES 0)
+
+if (NOT MSVC)
+ # linker flags
+ set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie -fPIE")
+ if (NOT (CMAKE_C_COMPILER MATCHES ".*clang.*" OR CMAKE_C_COMPILER_ID MATCHES ".*Clang"))
+ set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")
+ endif ()
+ set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wformat -Wformat-security")
+ if (WAMR_BUILD_TARGET MATCHES "X86_.*" OR WAMR_BUILD_TARGET STREQUAL "AMD_64")
+ if (NOT (CMAKE_C_COMPILER MATCHES ".*clang.*" OR CMAKE_C_COMPILER_ID MATCHES ".*Clang"))
+ check_c_compiler_flag(-mindirect-branch-register FLB_WASM_INDIRECT_BRANCH_REGISTER_SUPPORTED)
+ if (FLB_WASM_INDIRECT_BRANCH_REGISTER_SUPPORTED)
+ set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mindirect-branch-register")
+ endif ()
+ endif ()
+ endif ()
+endif ()
+
+set (WAMR_BUILD_SIMD 0)
+set (WAMR_ROOT_DIR ../../${FLB_PATH_LIB_WASM_MICRO_RUNTIME})
+
+# build out vmlib-static
+include (${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake)
+add_library(vmlib-static STATIC ${WAMR_RUNTIME_LIB_SOURCE})
+
+# Application related
+include (${SHARED_DIR}/utils/uncommon/shared_uncommon.cmake)
+
+set(src
+ flb_wasm.c
+ ${UNCOMMON_SHARED_SOURCE}) # link wasm-micro-runtime's uncommon object symbols (for bh_read_file_to_buffer)
+
+add_library(flb-wasm-static STATIC ${src})
+
+if (FLB_JEMALLOC AND ${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+ set(${JEMALLOC_LIBS} libjemalloc)
+ add_dependencies(flb-wasm-static libjemalloc)
+ include_directories("${CMAKE_BINARY_DIR}/include/")
+endif ()
+
+if (WAMR_BUILD_LIBC_UVWASI)
+ target_link_libraries(flb-wasm-static vmlib-static ${UV_A_LIBS})
+else ()
+ target_link_libraries(flb-wasm-static vmlib-static ${JEMALLOC_LIBS})
+endif()
diff --git a/fluent-bit/src/wasm/flb_wasm.c b/fluent-bit/src/wasm/flb_wasm.c
new file mode 100644
index 000000000..2f75c9bba
--- /dev/null
+++ b/fluent-bit/src/wasm/flb_wasm.c
@@ -0,0 +1,316 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * 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.
+ */
+
+/* Don't use and expose bh_ prefixed headers in flb_wasm.h.
+ Their definitions are tightly coupled in wasm-micro-runtime library. */
+#include "bh_read_file.h"
+#include "bh_getopt.h"
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_mem.h>
+#include <fluent-bit/flb_log.h>
+#include <fluent-bit/flb_slist.h>
+#include <fluent-bit/wasm/flb_wasm.h>
+
+#ifdef FLB_SYSTEM_WINDOWS
+#define STDIN_FILENO (_fileno( stdin ))
+#define STDOUT_FILENO (_fileno( stdout ))
+#define STDERR_FILENO (_fileno( stderr ))
+#else
+#include <unistd.h>
+#endif
+
+void flb_wasm_init(struct flb_config *config)
+{
+ mk_list_init(&config->wasm_list);
+}
+
+static int flb_wasm_load_wasm_binary(const char *wasm_path, int8_t **out_buf, uint32_t *out_size)
+{
+ char *buffer;
+ uint32_t buf_size;
+ buffer = bh_read_file_to_buffer(wasm_path, &buf_size);
+ if (!buffer) {
+ flb_error("Open wasm file [%s] failed.", wasm_path);
+ goto error;
+ }
+
+#if defined(FLB_WAMR_DISABLE_AOT_LOADING)
+ if ((get_package_type((const uint8_t *)buffer, buf_size) != Wasm_Module_Bytecode)) {
+ flb_error("WASM bytecode is expected but other file format");
+ goto error;
+ }
+#else
+ if ((get_package_type((const uint8_t *)buffer, buf_size) != Wasm_Module_Bytecode) &&
+ (get_package_type((const uint8_t *)buffer, buf_size) != Wasm_Module_AoT)) {
+ flb_error("WASM bytecode or AOT object is expected but other file format");
+ goto error;
+ }
+#endif
+
+ *out_buf = buffer;
+ *out_size = buf_size;
+
+ return buffer != NULL;
+
+error:
+ if (buffer != NULL) {
+ BH_FREE(buffer);
+ }
+
+ return FLB_FALSE;
+}
+
+struct flb_wasm *flb_wasm_instantiate(struct flb_config *config, const char *wasm_path,
+ struct mk_list *accessible_dir_list,
+ int stdinfd, int stdoutfd, int stderrfd)
+{
+ struct flb_wasm *fw;
+ uint32_t buf_size, stack_size = 8 * 1024, heap_size = 8 * 1024;
+ int8_t *buffer = NULL;
+ char error_buf[128];
+#if WASM_ENABLE_LIBC_WASI != 0
+ struct mk_list *head;
+ struct flb_slist_entry *wasi_dir;
+ const size_t accessible_dir_list_size = mk_list_size(accessible_dir_list);
+ const char **wasi_dir_list = NULL;
+ size_t dir_index = 0;
+#endif
+
+ wasm_module_t module = NULL;
+ wasm_module_inst_t module_inst = NULL;
+ wasm_exec_env_t exec_env = NULL;
+
+ RuntimeInitArgs wasm_args;
+
+ fw = flb_malloc(sizeof(struct flb_wasm));
+ if (!fw) {
+ flb_errno();
+ return NULL;
+ }
+ fw->tag_buffer = 0;
+ fw->record_buffer = 0;
+
+#if WASM_ENABLE_LIBC_WASI != 0
+ wasi_dir_list = flb_malloc(sizeof(char *) * accessible_dir_list_size);
+ if (!wasi_dir_list) {
+ flb_errno();
+ flb_free(fw);
+ return NULL;
+ }
+ mk_list_foreach(head, accessible_dir_list) {
+ wasi_dir = mk_list_entry(head, struct flb_slist_entry, _head);
+ wasi_dir_list[dir_index] = wasi_dir->str;
+ dir_index++;
+ }
+#endif
+
+ fw->config = config;
+
+ memset(&wasm_args, 0, sizeof(RuntimeInitArgs));
+
+ wasm_args.mem_alloc_type = Alloc_With_Allocator;
+ wasm_args.mem_alloc_option.allocator.malloc_func = flb_malloc;
+ wasm_args.mem_alloc_option.allocator.realloc_func = flb_realloc;
+ wasm_args.mem_alloc_option.allocator.free_func = flb_free;
+
+ if (!wasm_runtime_full_init(&wasm_args)) {
+ flb_error("Init runtime environment failed.");
+ return NULL;
+ }
+
+ if(!flb_wasm_load_wasm_binary(wasm_path, &buffer, &buf_size)) {
+ goto error;
+ }
+
+ module = wasm_runtime_load((uint8_t *)buffer, buf_size, error_buf, sizeof(error_buf));
+ if (!module) {
+ flb_error("Load wasm module failed. error: %s", error_buf);
+ goto error;
+ }
+
+#if WASM_ENABLE_LIBC_WASI != 0
+ wasm_runtime_set_wasi_args_ex(module, wasi_dir_list, accessible_dir_list_size, NULL, 0,
+ NULL, 0, NULL, 0,
+ (stdinfd != -1) ? stdinfd : STDIN_FILENO,
+ (stdoutfd != -1) ? stdoutfd : STDOUT_FILENO,
+ (stderrfd != -1) ? stderrfd : STDERR_FILENO);
+#endif
+
+ module_inst = wasm_runtime_instantiate(module, stack_size, heap_size,
+ error_buf, sizeof(error_buf));
+ if (!module_inst) {
+ flb_error("Instantiate wasm module failed. error: %s", error_buf);
+ goto error;
+ }
+
+ exec_env = wasm_runtime_create_exec_env(module_inst, stack_size);
+ if (!exec_env) {
+ flb_error("Create wasm execution environment failed.");
+ goto error;
+ }
+
+ fw->buffer = buffer;
+ fw->module = module;
+ fw->module_inst = module_inst;
+ fw->exec_env = exec_env;
+
+ mk_list_add(&fw->_head, &config->wasm_list);
+
+#if WASM_ENABLE_LIBC_WASI != 0
+ flb_free(wasi_dir_list);
+#endif
+
+ return fw;
+
+error:
+#if WASM_ENABLE_LIBC_WASI != 0
+ if (wasi_dir_list != NULL) {
+ flb_free(wasi_dir_list);
+ }
+#endif
+ if (exec_env) {
+ wasm_runtime_destroy_exec_env(exec_env);
+ }
+ if (module_inst) {
+ wasm_runtime_deinstantiate(module_inst);
+ }
+ if (module) {
+ wasm_runtime_unload(module);
+ }
+ if (buffer != NULL) {
+ BH_FREE(buffer);
+ }
+ if (fw != NULL) {
+ flb_free(fw);
+ }
+
+ wasm_runtime_destroy();
+
+ return NULL;
+}
+
+char *flb_wasm_call_function_format_json(struct flb_wasm *fw, const char *function_name,
+ const char* tag_data, size_t tag_len,
+ struct flb_time t,
+ const char* record_data, size_t record_len)
+{
+ const char *exception;
+ uint8_t *func_result;
+ wasm_function_inst_t func = NULL;
+ /* We should pass the length that is null terminator included into
+ * WASM runtime. This is why we add +1 for tag_len and record_len.
+ */
+ fw->tag_buffer = wasm_runtime_module_dup_data(fw->module_inst, tag_data, tag_len+1);
+ fw->record_buffer = wasm_runtime_module_dup_data(fw->module_inst, record_data, record_len+1);
+ uint32_t func_args[6] = {fw->tag_buffer, tag_len,
+ t.tm.tv_sec, t.tm.tv_nsec,
+ fw->record_buffer, record_len};
+ size_t args_size = sizeof(func_args) / sizeof(uint32_t);
+
+ if (!(func = wasm_runtime_lookup_function(fw->module_inst, function_name, NULL))) {
+ flb_error("The %s wasm function is not found.", function_name);
+ return NULL;
+ }
+
+ if (!wasm_runtime_call_wasm(fw->exec_env, func, args_size, func_args)) {
+ exception = wasm_runtime_get_exception(fw->module_inst);
+ flb_error("Got exception running wasm code: %s", exception);
+ wasm_runtime_clear_exception(fw->module_inst);
+ return NULL;
+ }
+
+ // The return value is stored in the first element of the function argument array.
+ // It's a WASM pointer to null-terminated c char string.
+ // WAMR allows us to map WASM pointers to native pointers.
+ if (!wasm_runtime_validate_app_str_addr(fw->module_inst, func_args[0])) {
+ flb_warn("[wasm] returned value is invalid");
+ return NULL;
+ }
+ func_result = wasm_runtime_addr_app_to_native(fw->module_inst, func_args[0]);
+
+ if (func_result == NULL) {
+ return NULL;
+ }
+
+ return (char *)flb_strdup(func_result);
+}
+
+int flb_wasm_call_wasi_main(struct flb_wasm *fw)
+{
+#if WASM_ENABLE_LIBC_WASI != 0
+ wasm_function_inst_t func = NULL;
+
+ if (!(func = wasm_runtime_lookup_wasi_start_function(fw->module_inst))) {
+ flb_error("The wasi mode main function is not found.");
+ return -1;
+ }
+
+ return wasm_runtime_call_wasm(fw->exec_env, func, 0, NULL);
+#else
+ return -1;
+#endif
+}
+
+void flb_wasm_buffer_free(struct flb_wasm *fw)
+{
+ if (fw->tag_buffer != 0) {
+ wasm_runtime_module_free(fw->module_inst, fw->tag_buffer);
+ }
+ if (fw->record_buffer != 0) {
+ wasm_runtime_module_free(fw->module_inst, fw->record_buffer);
+ }
+}
+
+void flb_wasm_destroy(struct flb_wasm *fw)
+{
+ if (fw->exec_env) {
+ wasm_runtime_destroy_exec_env(fw->exec_env);
+ }
+ if (fw->module_inst) {
+ flb_wasm_buffer_free(fw);
+ wasm_runtime_deinstantiate(fw->module_inst);
+ }
+ if (fw->module) {
+ wasm_runtime_unload(fw->module);
+ }
+ if (fw->buffer) {
+ BH_FREE(fw->buffer);
+ }
+ wasm_runtime_destroy();
+
+ mk_list_del(&fw->_head);
+ flb_free(fw);
+}
+
+int flb_wasm_destroy_all(struct flb_config *ctx)
+{
+ int c = 0;
+ struct mk_list *tmp;
+ struct mk_list *head;
+ struct flb_wasm *fw;
+
+ mk_list_foreach_safe(head, tmp, &ctx->wasm_list) {
+ fw = mk_list_entry(head, struct flb_wasm, _head);
+ flb_wasm_destroy(fw);
+ c++;
+ }
+
+ return c;
+}
diff --git a/fluent-bit/src/win32/winsvc.c b/fluent-bit/src/win32/winsvc.c
new file mode 100644
index 000000000..9e0942ee5
--- /dev/null
+++ b/fluent-bit/src/win32/winsvc.c
@@ -0,0 +1,148 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2019-2020 The Fluent Bit Authors
+ *
+ * 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 <Windows.h>
+#include <Shlwapi.h>
+
+struct flb_config;
+extern struct flb_config *config;
+extern int flb_engine_exit(struct flb_config*);
+extern int flb_main(int, char**);
+
+/* Windows Service utils */
+#define svc_name "fluent-bit"
+static SERVICE_STATUS_HANDLE hstatus;
+static int win32_argc;
+static char **win32_argv;
+
+/*
+ * A Windows Service uses 'C:\Windows\System32' as working directory
+ * by default. Here we use a more intuitive default path (where
+ * fluent-bit.exe exists).
+ */
+static int update_default_workdir(void)
+{
+ char path[MAX_PATH];
+
+ if (win32_argc < 1) {
+ return -1;
+ }
+
+ if (strcpy_s(path, MAX_PATH, win32_argv[0])) {
+ return -1;
+ }
+
+ if (!PathRemoveFileSpecA(path)) {
+ return -1;
+ }
+
+ if (!SetCurrentDirectoryA(path)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static void svc_notify(DWORD status)
+{
+ SERVICE_STATUS ss;
+
+ ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ ss.dwCurrentState = status;
+ ss.dwWin32ExitCode = NO_ERROR;
+ ss.dwServiceSpecificExitCode = NO_ERROR;
+ ss.dwControlsAccepted = SERVICE_ACCEPT_STOP;
+ ss.dwWaitHint = 30000;
+ ss.dwCheckPoint = 0;
+
+ /*
+ * According to MSDN (SetServiceStatus), accepting control on
+ * SERVICE_START_PENDING can crash the service.
+ */
+ if (status == SERVICE_START_PENDING) {
+ ss.dwControlsAccepted = 0;
+ }
+
+ SetServiceStatus(hstatus, &ss);
+}
+
+static void WINAPI svc_handler(DWORD ctrl)
+{
+ switch (ctrl)
+ {
+ case SERVICE_CONTROL_STOP:
+ svc_notify(SERVICE_STOP_PENDING);
+ flb_engine_exit(config);
+ return;
+ default:
+ break;
+ }
+}
+
+static void WINAPI svc_main(DWORD svc_argc, LPTSTR *svc_argv)
+{
+ hstatus = RegisterServiceCtrlHandler(svc_name, svc_handler);
+ if (!hstatus) {
+ return;
+ }
+
+ update_default_workdir();
+
+ svc_notify(SERVICE_START_PENDING);
+ flb_main(win32_argc, win32_argv);
+ svc_notify(SERVICE_STOPPED);
+}
+
+/*
+ * Notify SCM that Fluent Bit is running.
+ *
+ * Note: Call this function in the main execution flow (immediately
+ * before the engine is starting).
+ */
+void win32_started(void)
+{
+ if (hstatus) {
+ svc_notify(SERVICE_RUNNING);
+ }
+}
+
+static const SERVICE_TABLE_ENTRY svc_table[] = {
+ {svc_name, svc_main},
+ {NULL, NULL}
+};
+
+int win32_main(int argc, char **argv)
+{
+ win32_argc = argc;
+ win32_argv = argv;
+
+ if (StartServiceCtrlDispatcher(svc_table)) {
+ return 0;
+ }
+
+ if (GetLastError() != ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
+ return -1;
+ }
+
+ /*
+ * If we cannot connect to SCM, we assume that "fluent-bit.exe"
+ * was invoked from the command line.
+ */
+ return flb_main(argc, argv);
+}