From 5f48f30809570a3fc0194a38da56f43c1422d4f1 Mon Sep 17 00:00:00 2001 From: Hiroshi Hatake Date: Fri, 7 Aug 2026 13:58:15 +0900 Subject: [PATCH 1/5] out_file: Implement strftime placeholders support Signed-off-by: Hiroshi Hatake --- plugins/out_file/file.c | 129 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 122 insertions(+), 7 deletions(-) diff --git a/plugins/out_file/file.c b/plugins/out_file/file.c index 6a8bb42ebfe..49394e00633 100644 --- a/plugins/out_file/file.c +++ b/plugins/out_file/file.c @@ -88,6 +88,9 @@ struct flb_file_conf { int missing_field_action; int limit_reached_action; int dynamic_destination; + int enable_strftime; + int time_path; + int time_file; struct flb_record_accessor *ra_path; struct flb_record_accessor *ra_file; struct flb_hash_table *dynamic_files; @@ -325,6 +328,27 @@ static int compose_output_file(const char *path, return 0; } +static flb_sds_t format_event_timestamp(const char *format, + const struct flb_time *timestamp) +{ + size_t length; + time_t seconds; + struct tm utc_time; + char output[PATH_MAX * 2]; + + seconds = timestamp->tm.tv_sec; + if (gmtime_r(&seconds, &utc_time) == NULL) { + return NULL; + } + + length = strftime(output, sizeof(output), format, &utc_time); + if (length == 0) { + return NULL; + } + + return flb_sds_create_len(output, length); +} + static int use_fallback_destination(struct flb_file_conf *ctx, char *output, size_t output_size) @@ -363,6 +387,7 @@ static int apply_destination_action(struct flb_file_conf *ctx, static int resolve_dynamic_destination(struct flb_file_conf *ctx, const char *tag, msgpack_object map, + const struct flb_time *timestamp, char *output, size_t output_size, int *new_destination) @@ -373,6 +398,12 @@ static int resolve_dynamic_destination(struct flb_file_conf *ctx, size_t stored_size; flb_sds_t dynamic_path = NULL; flb_sds_t dynamic_file = NULL; + flb_sds_t timestamp_path = NULL; + flb_sds_t timestamp_file = NULL; + struct flb_record_accessor *event_ra_path = NULL; + struct flb_record_accessor *event_ra_file = NULL; + struct flb_record_accessor *ra_path; + struct flb_record_accessor *ra_file; const char *path; const char *file; @@ -380,11 +411,36 @@ static int resolve_dynamic_destination(struct flb_file_conf *ctx, path = ctx->out_path; file = ctx->out_file; + if (ctx->time_path == FLB_TRUE) { + timestamp_path = format_event_timestamp(path, timestamp); + if (timestamp_path == NULL) { + flb_plg_error(ctx->ins, "could not format timestamp placeholders in path"); + return -1; + } + path = timestamp_path; + } + if (ctx->ra_path != NULL) { - dynamic_path = flb_ra_translate_check(ctx->ra_path, + ra_path = ctx->ra_path; + if (ctx->time_path == FLB_TRUE) { + event_ra_path = flb_ra_create((char *) path, FLB_TRUE); + if (event_ra_path == NULL) { + flb_plg_error(ctx->ins, + "could not parse timestamp-expanded record accessor path"); + flb_sds_destroy(timestamp_path); + return -1; + } + ra_path = event_ra_path; + } + + dynamic_path = flb_ra_translate_check(ra_path, (char *) tag, strlen(tag), map, NULL, FLB_TRUE); + if (event_ra_path != NULL) { + flb_ra_destroy(event_ra_path); + } if (dynamic_path == NULL) { + flb_sds_destroy(timestamp_path); return apply_destination_action(ctx, ctx->missing_field_action, "record accessor field missing from path", output, output_size); @@ -392,12 +448,42 @@ static int resolve_dynamic_destination(struct flb_file_conf *ctx, path = dynamic_path; } + if (ctx->time_file == FLB_TRUE) { + timestamp_file = format_event_timestamp(file, timestamp); + if (timestamp_file == NULL) { + flb_plg_error(ctx->ins, "could not format timestamp placeholders in file"); + flb_sds_destroy(dynamic_path); + flb_sds_destroy(timestamp_path); + return -1; + } + file = timestamp_file; + } + if (ctx->ra_file != NULL) { - dynamic_file = flb_ra_translate_check(ctx->ra_file, + ra_file = ctx->ra_file; + if (ctx->time_file == FLB_TRUE) { + event_ra_file = flb_ra_create((char *) file, FLB_TRUE); + if (event_ra_file == NULL) { + flb_plg_error(ctx->ins, + "could not parse timestamp-expanded record accessor file"); + flb_sds_destroy(dynamic_path); + flb_sds_destroy(timestamp_path); + flb_sds_destroy(timestamp_file); + return -1; + } + ra_file = event_ra_file; + } + + dynamic_file = flb_ra_translate_check(ra_file, (char *) tag, strlen(tag), map, NULL, FLB_TRUE); + if (event_ra_file != NULL) { + flb_ra_destroy(event_ra_file); + } if (dynamic_file == NULL) { flb_sds_destroy(dynamic_path); + flb_sds_destroy(timestamp_path); + flb_sds_destroy(timestamp_file); return apply_destination_action(ctx, ctx->missing_field_action, "record accessor field missing from file", output, output_size); @@ -408,15 +494,20 @@ static int resolve_dynamic_destination(struct flb_file_conf *ctx, ret = sanitize_tag_name(tag, sanitized_tag, sizeof(sanitized_tag)); if (ret != 0) { flb_sds_destroy(dynamic_path); + flb_sds_destroy(timestamp_path); return -1; } file = sanitized_tag; } - if ((ctx->ra_path != NULL && validate_dynamic_path(path) != 0) || - (ctx->ra_file != NULL && validate_dynamic_file(file) != 0)) { + if (((ctx->ra_path != NULL || ctx->time_path == FLB_TRUE) && + validate_dynamic_path(path) != 0) || + ((ctx->ra_file != NULL || ctx->time_file == FLB_TRUE) && + validate_dynamic_file(file) != 0)) { flb_sds_destroy(dynamic_path); flb_sds_destroy(dynamic_file); + flb_sds_destroy(timestamp_path); + flb_sds_destroy(timestamp_file); return apply_destination_action(ctx, ctx->missing_field_action, "unsafe dynamic output destination", output, output_size); @@ -425,6 +516,8 @@ static int resolve_dynamic_destination(struct flb_file_conf *ctx, ret = compose_output_file(path, file, output, output_size); flb_sds_destroy(dynamic_path); flb_sds_destroy(dynamic_file); + flb_sds_destroy(timestamp_path); + flb_sds_destroy(timestamp_file); if (ret != 0) { return -1; } @@ -517,6 +610,12 @@ static int cb_file_init(struct flb_output_instance *ins, ctx->dynamic_destination = FLB_TRUE; } + if (ctx->enable_strftime == FLB_TRUE && + ctx->out_path != NULL && strchr(ctx->out_path, '%') != NULL) { + ctx->time_path = FLB_TRUE; + ctx->dynamic_destination = FLB_TRUE; + } + if (ctx->out_file != NULL && strchr(ctx->out_file, '$') != NULL) { ctx->ra_file = flb_ra_create((char *) ctx->out_file, FLB_TRUE); if (ctx->ra_file == NULL) { @@ -527,6 +626,12 @@ static int cb_file_init(struct flb_output_instance *ins, ctx->dynamic_destination = FLB_TRUE; } + if (ctx->enable_strftime == FLB_TRUE && + ctx->out_file != NULL && strchr(ctx->out_file, '%') != NULL) { + ctx->time_file = FLB_TRUE; + ctx->dynamic_destination = FLB_TRUE; + } + if (ctx->dynamic_destination == FLB_TRUE && ctx->fallback_file == NULL) { flb_plg_error(ctx->ins, "fallback_file is required when dynamic destinations are configured"); @@ -1047,6 +1152,7 @@ static int flush_dynamic_logs(struct flb_event_chunk *event_chunk, while ((ret = flb_log_event_decoder_next(log_decoder, log_event)) == FLB_EVENT_DECODER_SUCCESS) { ret = resolve_dynamic_destination(ctx, event_chunk->tag, *log_event->body, + &log_event->timestamp, output, output_size, &new_destination); if (ret == 1) { continue; @@ -1143,7 +1249,7 @@ static void cb_file_flush(struct flb_event_chunk *event_chunk, ret = use_fallback_destination(ctx, out_file, sizeof(out_file)); if (ret != 0) { flb_plg_error(ctx->ins, - "dynamic path and file record accessors are unsupported for metrics"); + "dynamic path and file placeholders are unsupported for metrics"); FLB_OUTPUT_RETURN(FLB_ERROR); } } @@ -1337,14 +1443,23 @@ static struct flb_config_map config_map[] = { FLB_CONFIG_MAP_STR, "path", NULL, 0, FLB_TRUE, offsetof(struct flb_file_conf, out_path), "Absolute path to store the files. Log record accessor expressions are supported, " - "and dynamic paths must retain a static prefix" + "and paths with record accessors must retain a static prefix. When enable_strftime " + "is true, strftime placeholders use the UTC event timestamp" }, { FLB_CONFIG_MAP_STR, "file", NULL, 0, FLB_TRUE, offsetof(struct flb_file_conf, out_file), "Name of the target file to write the records. If 'path' is specified, " - "the value is prefixed. Log record accessor expressions are supported" + "the value is prefixed. Log record accessor expressions are supported. When " + "enable_strftime is true, strftime placeholders use the UTC event timestamp" + }, + + { + FLB_CONFIG_MAP_BOOL, "enable_strftime", "false", + 0, FLB_TRUE, offsetof(struct flb_file_conf, enable_strftime), + "Enable strftime placeholders in path and file. Disabled by default so literal " + "percent characters in existing filenames are preserved" }, { From c84783ce93bc637786d199a3f489a11afdd51ad5 Mon Sep 17 00:00:00 2001 From: Hiroshi Hatake Date: Fri, 7 Aug 2026 13:59:12 +0900 Subject: [PATCH 2/5] tests: runtime: Provide Windows compatible mkdir/rmdir functions Signed-off-by: Hiroshi Hatake wwip --- tests/runtime/flb_tests_runtime.h.in | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/runtime/flb_tests_runtime.h.in b/tests/runtime/flb_tests_runtime.h.in index b0f5717129b..0a9e5a4523a 100644 --- a/tests/runtime/flb_tests_runtime.h.in +++ b/tests/runtime/flb_tests_runtime.h.in @@ -93,6 +93,24 @@ static inline char *flb_test_mkdtemp(char *template_path) #define mkdtemp flb_test_mkdtemp #endif +static inline int flb_test_mkdir(const char *path) +{ +#ifdef _WIN32 + return _mkdir(path); +#else + return mkdir(path, 0755); +#endif +} + +static inline int flb_test_rmdir(const char *path) +{ +#ifdef _WIN32 + return _rmdir(path); +#else + return remove(path); +#endif +} + static inline int wait_for_file(char *path, size_t minimum_size, int time_limit) From 1e7562ccf466f70e8227bb6b92e2cec27a7bc52c Mon Sep 17 00:00:00 2001 From: Hiroshi Hatake Date: Fri, 7 Aug 2026 14:00:59 +0900 Subject: [PATCH 3/5] tests: runtime: Add test cases for strftime placeholders Signed-off-by: Hiroshi Hatake --- tests/runtime/out_file.c | 65 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/tests/runtime/out_file.c b/tests/runtime/out_file.c index 3f084217a55..b13c2f86175 100644 --- a/tests/runtime/out_file.c +++ b/tests/runtime/out_file.c @@ -27,6 +27,7 @@ void flb_test_file_label_delim(void); void flb_test_file_template(void); void flb_test_file_mkdir(void); void flb_test_file_dynamic_path_file(void); +void flb_test_file_dynamic_timestamp(void); void flb_test_file_dynamic_requires_fallback(void); void flb_test_file_dynamic_missing_fallback(void); void flb_test_file_dynamic_unsafe_fallback(void); @@ -40,6 +41,7 @@ TEST_LIST = { {"path_file", flb_test_file_path_file}, {"mkdir", flb_test_file_mkdir}, {"dynamic_path_file", flb_test_file_dynamic_path_file}, + {"dynamic_timestamp", flb_test_file_dynamic_timestamp}, {"dynamic_requires_fallback", flb_test_file_dynamic_requires_fallback}, {"dynamic_missing_fallback", flb_test_file_dynamic_missing_fallback}, {"dynamic_unsafe_fallback", flb_test_file_dynamic_unsafe_fallback}, @@ -554,20 +556,23 @@ void flb_test_file_dynamic_path_file(void) char *p = JSON_DYNAMIC; char path1[256]; char path2[256]; + char tag_dir[256]; char dir1[256]; char dir2[256]; flb_ctx_t *ctx; int in_ffd; int out_ffd; - snprintf(dir1, sizeof(dir1), "%s/proxy1", TEST_LOGPATH); - snprintf(dir2, sizeof(dir2), "%s/proxy2", TEST_LOGPATH); - snprintf(path1, sizeof(path1), "%s/host1.log", dir1); - snprintf(path2, sizeof(path2), "%s/host2.log", dir2); + snprintf(tag_dir, sizeof(tag_dir), "%s/test", TEST_LOGPATH); + snprintf(dir1, sizeof(dir1), "%s/proxy1", tag_dir); + snprintf(dir2, sizeof(dir2), "%s/proxy2", tag_dir); + snprintf(path1, sizeof(path1), "%s/file.20151124", dir1); + snprintf(path2, sizeof(path2), "%s/file.20151124", dir2); remove(path1); remove(path2); rmdir(dir1); rmdir(dir2); + rmdir(tag_dir); rmdir(TEST_LOGPATH); ctx = flb_create(); @@ -581,8 +586,8 @@ void flb_test_file_dynamic_path_file(void) out_ffd = flb_output(ctx, (char *) "file", NULL); TEST_CHECK(out_ffd >= 0); flb_output_set(ctx, out_ffd, "match", "test", NULL); - flb_output_set(ctx, out_ffd, "path", TEST_LOGPATH "/$proxy_name", NULL); - flb_output_set(ctx, out_ffd, "file", "$hostname.log", NULL); + flb_output_set(ctx, out_ffd, "path", TEST_LOGPATH "/$TAG/$proxy_name", NULL); + flb_output_set(ctx, out_ffd, "file", "file.%Y%m%d", NULL); flb_output_set(ctx, out_ffd, "fallback_path", TEST_LOGPATH, NULL); flb_output_set(ctx, out_ffd, "fallback_file", "metrics.log", NULL); flb_output_set(ctx, out_ffd, "mkdir", "true", NULL); @@ -606,6 +611,54 @@ void flb_test_file_dynamic_path_file(void) remove(path2); rmdir(dir1); rmdir(dir2); + rmdir(tag_dir); + rmdir(TEST_LOGPATH); +} + +void flb_test_file_dynamic_timestamp(void) +{ + int ret; + int bytes; + char *p = JSON_BASIC; + char path[256]; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + snprintf(path, sizeof(path), "%s/events.20151124.log", TEST_LOGPATH); + remove(path); + rmdir(TEST_LOGPATH); + + ctx = flb_create(); + flb_service_set(ctx, "Flush", "1", "Grace", "1", + "Log_Level", "error", NULL); + + in_ffd = flb_input(ctx, (char *) "lib", NULL); + TEST_CHECK(in_ffd >= 0); + flb_input_set(ctx, in_ffd, "tag", "test", NULL); + + out_ffd = flb_output(ctx, (char *) "file", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "path", TEST_LOGPATH, NULL); + flb_output_set(ctx, out_ffd, "file", "events.%Y%m%d.log", NULL); + flb_output_set(ctx, out_ffd, "fallback_path", TEST_LOGPATH, NULL); + flb_output_set(ctx, out_ffd, "fallback_file", "fallback.log", NULL); + flb_output_set(ctx, out_ffd, "mkdir", "true", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + bytes = flb_lib_push(ctx, in_ffd, p, strlen(p)); + TEST_CHECK(bytes == strlen(p)); + + ret = wait_for_file(path, 1, TEST_TIMEOUT); + TEST_CHECK(ret == 0); + + flb_stop(ctx); + flb_destroy(ctx); + + remove(path); rmdir(TEST_LOGPATH); } From f9eb113ce8569061a93e296654c2447c6e1f92c1 Mon Sep 17 00:00:00 2001 From: Hiroshi Hatake Date: Fri, 7 Aug 2026 14:01:22 +0900 Subject: [PATCH 4/5] tests: runtime: Make Window compatible for out_file and rotated cases Signed-off-by: Hiroshi Hatake --- tests/runtime/CMakeLists.txt | 7 ++-- tests/runtime/out_file.c | 56 +++++++++++++++---------------- tests/runtime/out_file_rotation.c | 25 +++++++++----- 3 files changed, 47 insertions(+), 41 deletions(-) diff --git a/tests/runtime/CMakeLists.txt b/tests/runtime/CMakeLists.txt index 34652aaa03a..6db28445ace 100644 --- a/tests/runtime/CMakeLists.txt +++ b/tests/runtime/CMakeLists.txt @@ -287,11 +287,8 @@ if(FLB_IN_LIB) FLB_RT_TEST(FLB_OUT_LIB "group_counter_semantics.c") endif() endif() - # These plugins work only on Linux - if(NOT FLB_SYSTEM_WINDOWS) - FLB_RT_TEST(FLB_OUT_FILE "out_file.c") - FLB_RT_TEST(FLB_OUT_FILE "out_file_rotation.c") - endif() + FLB_RT_TEST(FLB_OUT_FILE "out_file.c") + FLB_RT_TEST(FLB_OUT_FILE "out_file_rotation.c") FLB_RT_TEST(FLB_OUT_S3 "out_s3.c") FLB_RT_TEST(FLB_OUT_GCS "out_gcs.c") if (FLB_IN_OPENTELEMETRY AND FLB_OUT_S3) diff --git a/tests/runtime/out_file.c b/tests/runtime/out_file.c index b13c2f86175..7716ba23616 100644 --- a/tests/runtime/out_file.c +++ b/tests/runtime/out_file.c @@ -426,8 +426,8 @@ void flb_test_file_path(void) flb_sds_printf(&path, "%s/%s", TEST_LOGPATH, file); remove(path); - remove(TEST_LOGPATH); - ret = mkdir(TEST_LOGPATH, S_IRUSR | S_IWUSR | S_IXUSR); + flb_test_rmdir(TEST_LOGPATH); + ret = flb_test_mkdir(TEST_LOGPATH); if (!TEST_CHECK(ret == 0)) { TEST_MSG("mkdir failed:path=%s errno=%d",TEST_LOGPATH, errno); flb_sds_destroy(path); @@ -469,7 +469,7 @@ void flb_test_file_path(void) } flb_sds_destroy(path); flb_sds_destroy(file); - remove(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); } void flb_test_file_path_file(void) @@ -492,8 +492,8 @@ void flb_test_file_path_file(void) flb_sds_printf(&path, "%s/%s", TEST_LOGPATH, TEST_LOGFILE); remove(path); - remove(TEST_LOGPATH); - ret = mkdir(TEST_LOGPATH, S_IRUSR | S_IWUSR | S_IXUSR); + flb_test_rmdir(TEST_LOGPATH); + ret = flb_test_mkdir(TEST_LOGPATH); if (!TEST_CHECK(ret == 0)) { TEST_MSG("mkdir failed:path=%s errno=%d",TEST_LOGPATH, errno); flb_sds_destroy(path); @@ -534,7 +534,7 @@ void flb_test_file_path_file(void) remove(path); } flb_sds_destroy(path); - remove(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); } #define JSON_BASIC "[1448403340,{\"key1\":\"val1\", \"key2\":\"val2\"}]" @@ -570,10 +570,10 @@ void flb_test_file_dynamic_path_file(void) snprintf(path2, sizeof(path2), "%s/file.20151124", dir2); remove(path1); remove(path2); - rmdir(dir1); - rmdir(dir2); - rmdir(tag_dir); - rmdir(TEST_LOGPATH); + flb_test_rmdir(dir1); + flb_test_rmdir(dir2); + flb_test_rmdir(tag_dir); + flb_test_rmdir(TEST_LOGPATH); ctx = flb_create(); flb_service_set(ctx, "Flush", "1", "Grace", "1", @@ -609,10 +609,10 @@ void flb_test_file_dynamic_path_file(void) remove(path1); remove(path2); - rmdir(dir1); - rmdir(dir2); - rmdir(tag_dir); - rmdir(TEST_LOGPATH); + flb_test_rmdir(dir1); + flb_test_rmdir(dir2); + flb_test_rmdir(tag_dir); + flb_test_rmdir(TEST_LOGPATH); } void flb_test_file_dynamic_timestamp(void) @@ -627,7 +627,7 @@ void flb_test_file_dynamic_timestamp(void) snprintf(path, sizeof(path), "%s/events.20151124.log", TEST_LOGPATH); remove(path); - rmdir(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); ctx = flb_create(); flb_service_set(ctx, "Flush", "1", "Grace", "1", @@ -659,7 +659,7 @@ void flb_test_file_dynamic_timestamp(void) flb_destroy(ctx); remove(path); - rmdir(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); } void flb_test_file_dynamic_requires_fallback(void) @@ -703,7 +703,7 @@ void flb_test_file_dynamic_missing_fallback(void) snprintf(fallback, sizeof(fallback), "%s/fallback.log", TEST_LOGPATH); remove(fallback); - rmdir(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); ctx = flb_create(); flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); @@ -734,7 +734,7 @@ void flb_test_file_dynamic_missing_fallback(void) flb_destroy(ctx); remove(fallback); - rmdir(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); } void flb_test_file_dynamic_unsafe_fallback(void) @@ -750,7 +750,7 @@ void flb_test_file_dynamic_unsafe_fallback(void) snprintf(fallback, sizeof(fallback), "%s/fallback.log", TEST_LOGPATH); remove(fallback); remove("escape.log"); - rmdir(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); ctx = flb_create(); flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); @@ -782,7 +782,7 @@ void flb_test_file_dynamic_unsafe_fallback(void) TEST_CHECK(access("escape.log", F_OK) != 0); remove(fallback); - rmdir(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); } void flb_test_file_dynamic_windows_unsafe_fallback(void) @@ -800,7 +800,7 @@ void flb_test_file_dynamic_windows_unsafe_fallback(void) snprintf(unsafe, sizeof(unsafe), "%s/host:one.log", TEST_LOGPATH); remove(fallback); remove(unsafe); - rmdir(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); ctx = flb_create(); flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); @@ -833,7 +833,7 @@ void flb_test_file_dynamic_windows_unsafe_fallback(void) TEST_CHECK(access(unsafe, F_OK) != 0); remove(fallback); remove(unsafe); - rmdir(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); } void flb_test_file_dynamic_limit_fallback(void) @@ -854,7 +854,7 @@ void flb_test_file_dynamic_limit_fallback(void) remove(first); remove(second); remove(overflow); - rmdir(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); ctx = flb_create(); flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); @@ -891,7 +891,7 @@ void flb_test_file_dynamic_limit_fallback(void) remove(first); remove(second); remove(overflow); - rmdir(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); } void flb_test_file_dynamic_path_traversal_fallback(void) @@ -907,7 +907,7 @@ void flb_test_file_dynamic_path_traversal_fallback(void) snprintf(fallback, sizeof(fallback), "%s/fallback.log", TEST_LOGPATH); remove(fallback); remove("unsafe.log"); - rmdir(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); ctx = flb_create(); flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); @@ -939,7 +939,7 @@ void flb_test_file_dynamic_path_traversal_fallback(void) TEST_CHECK(access("unsafe.log", F_OK) != 0); remove(fallback); - rmdir(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); } void flb_test_file_delim_csv(void) @@ -1203,7 +1203,7 @@ void flb_test_file_mkdir(void) flb_sds_printf(&path, "%s/%s", TEST_LOGPATH, file); remove(path); - remove(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); ctx = flb_create(); flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); @@ -1240,5 +1240,5 @@ void flb_test_file_mkdir(void) } flb_sds_destroy(path); flb_sds_destroy(file); - remove(TEST_LOGPATH); + flb_test_rmdir(TEST_LOGPATH); } diff --git a/tests/runtime/out_file_rotation.c b/tests/runtime/out_file_rotation.c index 5d91de4fcb1..8037d33db65 100644 --- a/tests/runtime/out_file_rotation.c +++ b/tests/runtime/out_file_rotation.c @@ -1,16 +1,16 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +#include #include "flb_tests_runtime.h" #include -#include #include #include #include +#include #include #include #include #include -#include #include #include #include @@ -18,19 +18,20 @@ #ifndef FLB_SYSTEM_WINDOWS #include #include -#define TEST_MKDIR(path) mkdir(path, 0755) #define PATH_SEPARATOR "/" #else #include #include -#define TEST_MKDIR(path) _mkdir(path) #define PATH_SEPARATOR "\\" /* Windows S_ISDIR compatibility */ #ifndef S_ISDIR #define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) #endif + #endif +#define TEST_MKDIR(path) flb_test_mkdir(path) + /* Test data */ #include "data/common/json_invalid.h" /* JSON_INVALID */ #include "data/common/json_long.h" /* JSON_LONG */ @@ -228,7 +229,7 @@ static int recursive_delete_directory(const char *dir_path) closedir(dir); /* Remove the directory itself */ - if (rmdir(dir_path) != 0) { + if (flb_test_rmdir(dir_path) != 0) { ret = -1; } @@ -1118,11 +1119,15 @@ void flb_test_file_rotation_path(void) TEST_LOGPATH); recursive_delete_directory(TEST_LOGPATH); TEST_MKDIR(TEST_LOGPATH); +#ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-truncation" +#endif snprintf(logfile, sizeof(logfile), "%s" PATH_SEPARATOR "path_test.log", test_path); +#ifdef __GNUC__ #pragma GCC diagnostic pop +#endif ctx = flb_create(); TEST_ASSERT(flb_service_set(ctx, "Flush", TEST_FLUSH_INTERVAL, "Grace", "1", "Log_Level", @@ -1179,11 +1184,15 @@ void flb_test_file_rotation_mkdir(void) "%s" PATH_SEPARATOR "nested" PATH_SEPARATOR "deep" PATH_SEPARATOR "path", TEST_LOGPATH); +#ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-truncation" +#endif snprintf(logfile, sizeof(logfile), "%s" PATH_SEPARATOR "test_mkdir.log", nested_path); +#ifdef __GNUC__ #pragma GCC diagnostic pop +#endif recursive_delete_directory(TEST_LOGPATH); @@ -2028,8 +2037,8 @@ void flb_test_file_rotation_gzip_compression_exact_chunk(void) json_payload = flb_malloc(json_size); TEST_CHECK(json_payload != NULL); - snprintf(json_payload, json_size, "[%lu, {\"message\": \"%s\"}]", - time(NULL), large_message); + snprintf(json_payload, json_size, "[%lld, {\"message\": \"%s\"}]", + (long long) time(NULL), large_message); bytes = flb_lib_push(ctx, in_ffd, json_payload, strlen(json_payload)); TEST_CHECK(bytes == strlen(json_payload)); @@ -2463,7 +2472,7 @@ void flb_test_file_rotation_open_failure_releases_lock(void) #ifdef FLB_SYSTEM_WINDOWS TEST_ASSERT(RemoveDirectoryA(logfile) != 0); #else - TEST_ASSERT(rmdir(logfile) == 0); + TEST_ASSERT(flb_test_rmdir(logfile) == 0); #endif for (i = 0; i < 3; i++) { From f21930ceb2d7a1fbb900d7fb2d5a89b80123ae00 Mon Sep 17 00:00:00 2001 From: Hiroshi Hatake Date: Fri, 7 Aug 2026 22:46:30 +0900 Subject: [PATCH 5/5] tests: runtime: Add test cases for contaminated percent characters Signed-off-by: Hiroshi Hatake --- tests/runtime/out_file.c | 116 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/tests/runtime/out_file.c b/tests/runtime/out_file.c index 7716ba23616..a7468b3c7a0 100644 --- a/tests/runtime/out_file.c +++ b/tests/runtime/out_file.c @@ -26,8 +26,10 @@ void flb_test_file_delim_ltsv(void); void flb_test_file_label_delim(void); void flb_test_file_template(void); void flb_test_file_mkdir(void); +void flb_test_file_literal_percent(void); void flb_test_file_dynamic_path_file(void); void flb_test_file_dynamic_timestamp(void); +void flb_test_file_dynamic_percent_values(void); void flb_test_file_dynamic_requires_fallback(void); void flb_test_file_dynamic_missing_fallback(void); void flb_test_file_dynamic_unsafe_fallback(void); @@ -40,8 +42,10 @@ TEST_LIST = { {"path", flb_test_file_path}, {"path_file", flb_test_file_path_file}, {"mkdir", flb_test_file_mkdir}, + {"literal_percent", flb_test_file_literal_percent}, {"dynamic_path_file", flb_test_file_dynamic_path_file}, {"dynamic_timestamp", flb_test_file_dynamic_timestamp}, + {"dynamic_percent_values", flb_test_file_dynamic_percent_values}, {"dynamic_requires_fallback", flb_test_file_dynamic_requires_fallback}, {"dynamic_missing_fallback", flb_test_file_dynamic_missing_fallback}, {"dynamic_unsafe_fallback", flb_test_file_dynamic_unsafe_fallback}, @@ -548,6 +552,56 @@ void flb_test_file_path_file(void) #define JSON_DYNAMIC_LIMIT \ "[1448403340,{\"hostname\":\"host1\"}]" \ "[1448403341,{\"hostname\":\"host2\"}]" +#define JSON_DYNAMIC_PERCENT "[1448403340,{\"proxy_name\":\"proxy%m\"}]" + +void flb_test_file_literal_percent(void) +{ + int ret; + int bytes; + char *p = JSON_BASIC; + char *file = "metrics%used.log"; + char *fallback = "fallback.log"; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + FILE *fp; + + remove(file); + remove(fallback); + + ctx = flb_create(); + flb_service_set(ctx, "Flush", "1", "Grace", "1", "Log_Level", "error", NULL); + + in_ffd = flb_input(ctx, (char *) "lib", NULL); + TEST_CHECK(in_ffd >= 0); + flb_input_set(ctx, in_ffd, "tag", "test", NULL); + + out_ffd = flb_output(ctx, (char *) "file", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "test", NULL); + flb_output_set(ctx, out_ffd, "file", file, NULL); + flb_output_set(ctx, out_ffd, "fallback_file", fallback, NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + bytes = flb_lib_push(ctx, in_ffd, p, strlen(p)); + TEST_CHECK(bytes == strlen(p)); + ret = wait_for_file(file, 1, TEST_TIMEOUT); + TEST_CHECK(ret == 0); + + flb_stop(ctx); + flb_destroy(ctx); + + fp = fopen(file, "r"); + TEST_CHECK(fp != NULL); + if (fp != NULL) { + fclose(fp); + } + TEST_CHECK(access(fallback, F_OK) != 0); + remove(file); + remove(fallback); +} void flb_test_file_dynamic_path_file(void) { @@ -588,6 +642,7 @@ void flb_test_file_dynamic_path_file(void) flb_output_set(ctx, out_ffd, "match", "test", NULL); flb_output_set(ctx, out_ffd, "path", TEST_LOGPATH "/$TAG/$proxy_name", NULL); flb_output_set(ctx, out_ffd, "file", "file.%Y%m%d", NULL); + flb_output_set(ctx, out_ffd, "enable_strftime", "true", NULL); flb_output_set(ctx, out_ffd, "fallback_path", TEST_LOGPATH, NULL); flb_output_set(ctx, out_ffd, "fallback_file", "metrics.log", NULL); flb_output_set(ctx, out_ffd, "mkdir", "true", NULL); @@ -642,6 +697,7 @@ void flb_test_file_dynamic_timestamp(void) flb_output_set(ctx, out_ffd, "match", "test", NULL); flb_output_set(ctx, out_ffd, "path", TEST_LOGPATH, NULL); flb_output_set(ctx, out_ffd, "file", "events.%Y%m%d.log", NULL); + flb_output_set(ctx, out_ffd, "enable_strftime", "true", NULL); flb_output_set(ctx, out_ffd, "fallback_path", TEST_LOGPATH, NULL); flb_output_set(ctx, out_ffd, "fallback_file", "fallback.log", NULL); flb_output_set(ctx, out_ffd, "mkdir", "true", NULL); @@ -662,6 +718,66 @@ void flb_test_file_dynamic_timestamp(void) flb_test_rmdir(TEST_LOGPATH); } +void flb_test_file_dynamic_percent_values(void) +{ + int ret; + int bytes; + char *p = JSON_DYNAMIC_PERCENT; + char path[256]; + char proxy_dir[256]; + char tag_dir[256]; + char year_dir[256]; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + snprintf(year_dir, sizeof(year_dir), "%s/2015", TEST_LOGPATH); + snprintf(tag_dir, sizeof(tag_dir), "%s/test%%d", year_dir); + snprintf(proxy_dir, sizeof(proxy_dir), "%s/proxy%%m", tag_dir); + snprintf(path, sizeof(path), "%s/file.20151124", proxy_dir); + remove(path); + flb_test_rmdir(proxy_dir); + flb_test_rmdir(tag_dir); + flb_test_rmdir(year_dir); + flb_test_rmdir(TEST_LOGPATH); + + ctx = flb_create(); + flb_service_set(ctx, "Flush", "1", "Grace", "1", + "Log_Level", "error", NULL); + + in_ffd = flb_input(ctx, (char *) "lib", NULL); + TEST_CHECK(in_ffd >= 0); + flb_input_set(ctx, in_ffd, "tag", "test%d", NULL); + + out_ffd = flb_output(ctx, (char *) "file", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "*", NULL); + flb_output_set(ctx, out_ffd, "path", + TEST_LOGPATH "/%Y/$TAG/$proxy_name", NULL); + flb_output_set(ctx, out_ffd, "file", "file.%Y%m%d", NULL); + flb_output_set(ctx, out_ffd, "enable_strftime", "true", NULL); + flb_output_set(ctx, out_ffd, "fallback_path", TEST_LOGPATH, NULL); + flb_output_set(ctx, out_ffd, "fallback_file", "fallback.log", NULL); + flb_output_set(ctx, out_ffd, "mkdir", "true", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + bytes = flb_lib_push(ctx, in_ffd, p, strlen(p)); + TEST_CHECK(bytes == strlen(p)); + ret = wait_for_file(path, 1, TEST_TIMEOUT); + TEST_CHECK(ret == 0); + + flb_stop(ctx); + flb_destroy(ctx); + + remove(path); + flb_test_rmdir(proxy_dir); + flb_test_rmdir(tag_dir); + flb_test_rmdir(year_dir); + flb_test_rmdir(TEST_LOGPATH); +} + void flb_test_file_dynamic_requires_fallback(void) { int ret;