Skip to content

Conversation

@SagiROosto
Copy link

@SagiROosto SagiROosto commented Sep 3, 2025

This PR present a new out plugin out_logrotate. This Will answer #7572 question and solve #7541 suggestion.
This feature is highly required in bare-metal or physical machines where you have finite and limited storage.
The plugin is basically a copy of out_file with a rotation feature. The reason to do that in additional plugin and not on the out_file itself is to avoid accidentally turning this feature on and having redundant info/allocs/processing where it is not needed (so normal users can still use the regular file plugin without issue).


Enter [N/A] in the box, if an item is not applicable to your change.

Testing
Before we can approve your change; please submit the following in a comment:

  • Example configuration file for the change
  • Debug log output from testing the change
  • Attached Valgrind output that shows no leaks or memory corruption was found

If this is a change to packaging of containers or native binaries then please confirm it works for all targets.

  • Run local packaging test showing all targets (including any new ones) build.
  • Set ok-package-test label to test for all targets (requires maintainer to do).

Documentation

  • Documentation required for this feature

Backporting

  • Backport to latest stable release.

Fluent Bit is licensed under Apache 2.0, by submitting this pull request I understand that this code will be released under the terms of that license.

Summary by CodeRabbit

  • New Features

    • Logrotate output plugin: write logs/metrics to files with size-based rotation, optional gzip compression, max-file retention, directory auto-creation, and multiple formats (JSON default, CSV, LTSV, plain, template, MsgPack).
  • Configuration

    • Added an example configuration demonstrating rotation, gzip, max_files, mkdir, format, and input/output matching.
  • Build

    • New build option to enable/disable the Logrotate plugin; included in "enable all" and Windows build defaults.
  • Tests

    • Runtime tests for rotation, gzip compression, and max-files cleanup.

@coderabbitai
Copy link

coderabbitai bot commented Sep 3, 2025

Walkthrough

Adds a new out_logrotate output plugin (implementation, header, CMake build, example config, and runtime tests), plus CMake options/flags to enable it under FLB_ALL and Windows defaults and unconditional plugin registration.

Changes

Cohort / File(s) Summary
Build options & top-level CMake
CMakeLists.txt, cmake/plugins_options.cmake, cmake/windows-setup.cmake
Introduces FLB_OUT_LOGROTATE option/variable, enables it under FLB_ALL, adds Windows default FLB_OUT_LOGROTATE Yes, and defines plugin option via DEFINE_OPTION.
Plugin registration
plugins/CMakeLists.txt
Registers out_logrotate via REGISTER_OUT_PLUGIN("out_logrotate").
Plugin build files
plugins/out_logrotate/CMakeLists.txt
Adds build configuration for out_logrotate (source logrotate.c), requires ZLIB, and registers plugin with FLB_PLUGIN.
Plugin implementation & header
plugins/out_logrotate/logrotate.c, plugins/out_logrotate/logrotate.h
New output plugin and public header: size-based rotation, timestamped renames, optional streaming gzip, max_files cleanup, Mkdir, multi-format output (json,csv,ltsv,plain,msgpack,template); exports out_logrotate_plugin and format enum constants.
Example config
conf/fluent-bit-logrotate.conf
New example demonstrating dummy input and out_logrotate settings (path/file, format, Max_Size, Max_Files, Gzip, Mkdir).
Runtime tests
tests/runtime/CMakeLists.txt, tests/runtime/out_logrotate.c
Adds conditional runtime test registration and tests for basic rotation, gzip compression, and max_files cleanup.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Admin as Config/Admin
  participant FB as Fluent Bit
  participant PL as out_logrotate
  participant FS as Filesystem

  Admin->>FB: Load config (input + out_logrotate)
  FB->>PL: cb_init(config)
  Note over PL: configure path,file,format,max_size,max_files,gzip,mkdir

  loop On flush
    FB->>PL: cb_flush(events)
    PL->>PL: Resolve target filename (path/file/tag)
    alt current_file_size >= max_size
      PL->>FS: Close & rename active -> file.timestamp
      opt gzip enabled
        PL->>FS: Stream-compress -> file.timestamp.gz
      end
      PL->>FS: Remove oldest rotated files if count > max_files
      PL->>PL: Reset current_file_size
    end
    PL->>FS: Ensure directory (mkdir) and open append
    alt msgpack payload
      PL->>FS: Write raw msgpack
    else metrics payload
      PL->>FS: Write rendered metrics text
    else
      PL->>PL: Render records (json/csv/ltsv/plain/template)
      PL->>FS: Append rendered lines
    end
    PL->>PL: Update current_file_size
    PL-->>FB: cb_flush return status
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Attention areas:
    • plugins/out_logrotate/logrotate.c — dense, OS-specific file handling, gzip streaming, rotation and cleanup logic.
    • plugins/out_logrotate/logrotate.h — public enum and header guards.
    • CMake and test registration changes (CMakeLists.txt, cmake/plugins_options.cmake, cmake/windows-setup.cmake, plugins/CMakeLists.txt, tests/runtime/CMakeLists.txt).
    • Runtime tests (tests/runtime/out_logrotate.c) for reliability and potential flakiness.

Suggested reviewers

  • edsiper
  • cosmo0920
  • niedbalski
  • koleini
  • fujimotos
  • celalettin1286

Poem

I nibble bytes beside the gate,
When logs grow full I rotate.
I stamp with time, then gzip tight,
Sweep old crumbs out of sight.
Mkdir digs a cozy den — rotate again. 🥕🌀

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: adding a new output plugin called 'out_logrotate'.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e967582 and 0571901.

📒 Files selected for processing (2)
  • CMakeLists.txt (1 hunks)
  • cmake/windows-setup.cmake (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • CMakeLists.txt
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-31T12:46:11.940Z
Learnt from: ThomasDevoogdt
Repo: fluent/fluent-bit PR: 9277
File: .github/workflows/pr-compile-check.yaml:147-151
Timestamp: 2025-08-31T12:46:11.940Z
Learning: In fluent-bit CMakeLists.txt, the system library preference flags are defined as FLB_PREFER_SYSTEM_LIB_ZSTD and FLB_PREFER_SYSTEM_LIB_KAFKA with the FLB_ prefix.

Applied to files:

  • cmake/windows-setup.cmake
🔇 Additional comments (1)
cmake/windows-setup.cmake (1)

103-103: LGTM! Appropriate Windows default for the new plugin.

Enabling FLB_OUT_LOGROTATE alongside FLB_OUT_FILE is consistent and logical, since the new plugin is based on out_file with added rotation capabilities. The placement and formatting match the existing pattern.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🧹 Nitpick comments (7)
plugins/out_logrotate/logrotate.h (1)

23-30: Optional: C++ friendliness and API comment

If tests or embedders include this from C++, consider extern "C" guards; also add a brief comment mapping enum values to the config's Format strings.

Example:

+#ifdef __cplusplus
+extern "C" {
+#endif
 enum {
@@
 };
+#ifdef __cplusplus
+}
+#endif
conf/fluent-bit-logrotate.conf (1)

12-21: Clarify size units and option names to match plugin config map

“Max_Size 10” is ambiguous (bytes? KB?). Prefer explicit units if supported (e.g., 10M), and ensure option keys exactly match the plugin’s config map.

-    Max_Size    10
+    Max_Size    10M     # 10 megabytes

Please confirm the accepted keys/values (Format, Max_Size, Max_Files, Gzip, Mkdir) align with logrotate.c’s flb_config_map; adjust example accordingly.

tests/runtime/out_logrotate.c (2)

322-325: Assertion doesn’t prove rotation happened.

Comparing sizes without ensuring a rotation occurred can pass spuriously. Instead, verify a rotated file exists and the active file size reset.

I can rewrite this test to:

  • set max_size to a small value (e.g., 64K),
  • push until rotation triggers,
  • assert count_files_in_directory(".", "flb_test_logrotate.log.") >= 1,
  • and the current file size < 64K.

498-500: Time-based performance assertions are brittle in CI.

CPU and I/O variance makes this flaky. Either increase the threshold generously, skip under CI, or convert to a basic smoke test.

plugins/out_logrotate/logrotate.c (3)

511-516: Comment mismatch: size is in bytes, not MB.

Avoid confusion with FLB_CONFIG_MAP_SIZE which accepts human-readable units but stores bytes.

-/* Function to check if file size exceeds max size in MB */
+/* Function to check if file size exceeds max size (bytes) */
 static int should_rotate_file(struct flb_logrotate_conf *ctx)

165-176: Delimiter handling is restrictive vs. docs.

Docs say “Set a custom delimiter,” but check_delimiter only accepts a few keywords/escapes. Consider passing through arbitrary delimiters when provided.

Minimal change inside init:

     tmp = flb_output_get_property("delimiter", ins);
     ret_str = check_delimiter(tmp);
-    if (ret_str != NULL) {
-        ctx->delimiter = ret_str;
-    }
+    if (ret_str != NULL) {
+        ctx->delimiter = ret_str;
+    } else if (tmp && *tmp) {
+        ctx->delimiter = tmp; /* accept arbitrary delimiter string */
+    }

Same for label_delimiter if desired.


180-185: Log human-friendly sizes to improve operability.

Printing raw bytes is harder to read. Optional: format as KiB/MiB when logging.

I can provide a small helper to pretty-print sizes if you want it included.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 35885ce and ad01bd5.

📒 Files selected for processing (8)
  • cmake/plugins_options.cmake (1 hunks)
  • conf/fluent-bit-logrotate.conf (1 hunks)
  • plugins/CMakeLists.txt (1 hunks)
  • plugins/out_logrotate/CMakeLists.txt (1 hunks)
  • plugins/out_logrotate/logrotate.c (1 hunks)
  • plugins/out_logrotate/logrotate.h (1 hunks)
  • tests/runtime/CMakeLists.txt (1 hunks)
  • tests/runtime/out_logrotate.c (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
plugins/out_logrotate/logrotate.c (6)
include/fluent-bit/flb_mem.h (2)
  • flb_calloc (84-96)
  • flb_free (126-128)
include/fluent-bit/flb_output.h (1)
  • flb_output_config_map_set (1298-1321)
src/flb_output.c (2)
  • flb_output_get_property (1096-1099)
  • flb_output_set_context (1524-1527)
src/flb_pack.c (1)
  • flb_msgpack_to_json_str (1250-1291)
lib/cmetrics/src/cmt_decode_msgpack.c (1)
  • cmt_decode_msgpack_create (1450-1496)
src/flb_log_event_decoder.c (3)
  • flb_log_event_decoder_init (99-116)
  • flb_log_event_decoder_next (310-406)
  • flb_log_event_decoder_destroy (147-179)
tests/runtime/out_logrotate.c (2)
src/flb_lib.c (10)
  • flb_create (138-220)
  • flb_service_set (647-673)
  • flb_input (261-271)
  • flb_input_set (300-330)
  • flb_output (274-284)
  • flb_output_set (515-546)
  • flb_start (914-925)
  • flb_lib_push (774-801)
  • flb_stop (942-985)
  • flb_destroy (223-258)
src/flb_sds.c (4)
  • flb_sds_create (78-90)
  • flb_sds_create_size (92-95)
  • flb_sds_printf (336-387)
  • flb_sds_destroy (389-399)
🔇 Additional comments (4)
plugins/CMakeLists.txt (1)

367-369: LGTM: conditional registration is correct and placed logically after out_file

Registration behind FLB_OUT_LOGROTATE matches the new option; ordering is consistent with related outputs.

cmake/plugins_options.cmake (1)

120-120: Option wiring looks correct; defaults align with existing pattern.

DEFINE_OPTION for FLB_OUT_LOGROTATE mirrors other outputs and respects FLB_MINIMAL. No blockers.

tests/runtime/out_logrotate.c (2)

263-266: File count assertion may be racy; wait until rotation settles.

After heavy writes, let the engine flush again before counting files, or poll until count stabilizes within TEST_TIMEOUT.

I can add a small polling loop to wait until count_files_in_directory(".", "flb_test_logrotate.log.") <= 3 or timeout. Want me to patch it?


380-382: Header check aligns once csv_column_names=true.

After enabling column names, "timestamp" should be present. No further action.

* add logrotate out plugin

* fix gzip

* fix test

* update max_size

* update gzip

* add logs

Signed-off-by: SagiROosto <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (4)
plugins/out_logrotate/logrotate.c (4)

31-39: Add missing headers for PATH_MAX, PRIu64, and dirname().
Compilation will fail or be non-portable without these includes.

Apply:

 #include <miniz/miniz.h>
+#include <limits.h>     /* PATH_MAX */
+#include <inttypes.h>   /* PRIu64 */
+#ifndef FLB_SYSTEM_WINDOWS
+#include <libgen.h>     /* dirname */
+#endif

869-901: Use project allocators and guard against uninitialized directory on strdup failure.
Replace strdup/free with flb_strdup/flb_free and skip cleanup if directory couldn’t be resolved.

Apply:

-        out_file_copy = strdup(out_file);
-        if (out_file_copy) {
+        out_file_copy = flb_strdup(out_file);
+        if (out_file_copy != NULL) {
 #ifdef FLB_SYSTEM_WINDOWS
             PathRemoveFileSpecA(out_file_copy);
             strncpy(directory, out_file_copy, PATH_MAX - 1);
             directory[PATH_MAX - 1] = '\0';
 #else
             strncpy(directory, dirname(out_file_copy), PATH_MAX - 1);
             directory[PATH_MAX - 1] = '\0';
 #endif
-            free(out_file_copy);
+            flb_free(out_file_copy);
         }
@@
-        /* Rotate the file */
+        /* Rotate the file */
         if (rotate_file(ctx, out_file) == 0) {
             /* Reset file size counter after rotation */
             ctx->current_file_size = 0;
-            /* Clean up old rotated files */
-            cleanup_old_files(ctx, directory, base_filename);
+            /* Clean up old rotated files (only if directory was resolved) */
+            if (directory[0] != '\0') {
+                cleanup_old_files(ctx, directory, base_filename);
+            }
         }

Also initialize directory to an empty string before use:

-    char directory[PATH_MAX];
+    char directory[PATH_MAX] = {0};

903-919: Use project allocators in mkdir branch and avoid passing possibly static buffer from dirname directly.

Apply:

-        out_file_copy = strdup(out_file);
-        if (out_file_copy) {
+        out_file_copy = flb_strdup(out_file);
+        if (out_file_copy != NULL) {
 #ifdef FLB_SYSTEM_WINDOWS
             PathRemoveFileSpecA(out_file_copy);
             ret = mkpath(ctx->ins, out_file_copy);
 #else
-            ret = mkpath(ctx->ins, dirname(out_file_copy));
+            {
+                char tmpdir[PATH_MAX];
+                const char *dd = dirname(out_file_copy);
+                strncpy(tmpdir, dd, sizeof(tmpdir) - 1);
+                tmpdir[sizeof(tmpdir) - 1] = '\0';
+                ret = mkpath(ctx->ins, tmpdir);
+            }
 #endif
-            free(out_file_copy);
+            flb_free(out_file_copy);
             if (ret == 0) {
                 fp = fopen(out_file, "ab+");
             }
         }

937-944: Update size counter after writing metrics; otherwise rotation may never trigger.

Apply:

     if (event_chunk->type == FLB_INPUT_METRICS) {
         print_metrics_text(ctx->ins, fp,
                            event_chunk->data, event_chunk->size);
+        update_file_size_counter(ctx, fp);
         fclose(fp);
         FLB_OUTPUT_RETURN(FLB_OK);
     }
🧹 Nitpick comments (4)
plugins/out_logrotate/logrotate.c (4)

81-99: Make check_delimiter const-correct and align call sites.
Avoid discarding const and storing string literals in non-const pointers.

Apply:

-static char *check_delimiter(const char *str)
+static const char *check_delimiter(const char *str)
@@
-    char *ret_str;
+    const char *ret_str;
@@
-    ret_str = check_delimiter(tmp);
+    ret_str = check_delimiter(tmp);

(No functional change; improves type safety.)

Also applies to: 165-176


511-516: Comment says “in MB” but value is bytes.
Update comment to avoid confusion.

Apply:

-/* Function to check if file size exceeds max size in MB */
+/* Check if current file size in bytes exceeds max_size (bytes) */

1110-1114: Align default value with documentation and allow size suffix parsing.
Use “100M” so FLB_CONFIG_MAP_SIZE parses the suffix and matches the doc string.

Apply:

-     FLB_CONFIG_MAP_SIZE, "max_size", "100000000",
+     FLB_CONFIG_MAP_SIZE, "max_size", "100M",

715-731: Optional: reuse flb_gzip helpers instead of hand-rolled gzip wrapper.
Reduces maintenance and ensures parity with other plugins. If feasible, replace write_gzip_header/write_gzip_footer/deflate logic with flb_gzip APIs.

I can draft a patch that streams file->gzip via flb_gzip_* and handles CRC/ISIZE internally; let me know.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ad01bd5 and d26d5e8.

📒 Files selected for processing (8)
  • cmake/plugins_options.cmake (1 hunks)
  • conf/fluent-bit-logrotate.conf (1 hunks)
  • plugins/CMakeLists.txt (1 hunks)
  • plugins/out_logrotate/CMakeLists.txt (1 hunks)
  • plugins/out_logrotate/logrotate.c (1 hunks)
  • plugins/out_logrotate/logrotate.h (1 hunks)
  • tests/runtime/CMakeLists.txt (1 hunks)
  • tests/runtime/out_logrotate.c (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • cmake/plugins_options.cmake
  • plugins/out_logrotate/CMakeLists.txt
  • conf/fluent-bit-logrotate.conf
  • tests/runtime/out_logrotate.c
  • plugins/out_logrotate/logrotate.h
  • tests/runtime/CMakeLists.txt
🧰 Additional context used
🧬 Code graph analysis (1)
plugins/out_logrotate/logrotate.c (6)
include/fluent-bit/flb_mem.h (2)
  • flb_calloc (84-96)
  • flb_free (126-128)
include/fluent-bit/flb_output.h (1)
  • flb_output_config_map_set (1298-1321)
src/flb_output.c (2)
  • flb_output_get_property (1096-1099)
  • flb_output_set_context (1524-1527)
src/flb_pack.c (1)
  • flb_msgpack_to_json_str (1250-1291)
lib/cmetrics/src/cmt_decode_msgpack.c (1)
  • cmt_decode_msgpack_create (1450-1496)
src/flb_log_event_decoder.c (3)
  • flb_log_event_decoder_init (99-116)
  • flb_log_event_decoder_next (310-406)
  • flb_log_event_decoder_destroy (147-179)
🔇 Additional comments (1)
plugins/CMakeLists.txt (1)

367-369: LGTM: conditional registration for out_logrotate is correct and in the right section.
Build-time gating via FLB_OUT_LOGROTATE matches the project’s plugin pattern.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should provide a YAML version too.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I didn't see other configs. let me know where can I put it. anyway in fluent-bit-docs I'll list both options

DEFINE_OPTION(FLB_OUT_ES "Enable Elasticsearch output plugin" ON)
DEFINE_OPTION(FLB_OUT_EXIT "Enable Exit output plugin" ON)
DEFINE_OPTION(FLB_OUT_FILE "Enable file output plugin" ON)
DEFINE_OPTION(FLB_OUT_LOGROTATE "Enable logrotate output plugin" ON)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this build for all targets including macOS and Windows? They may need configuring via their specific cmake/ config files.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the logrotate plugin is actually a fork of the out_file with gzip and couple of move/copy files.
I've matched all cmakes to be the same as the out_file

Comment on lines 233 to 237
# 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_LOGROTATE "out_logrotate.c")
endif()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is outdated right as we seem to enable them on windows @cosmo0920 ?

@SagiROosto SagiROosto changed the title add logrotate out plugin (#2) out_logrotate: add new plugin (#2) Oct 27, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
plugins/out_logrotate/logrotate.c (1)

954-960: File size counter not updated after writing metrics.

After print_metrics_text writes metrics data to the file, the current_file_size counter is not updated. This means subsequent flushes won't trigger rotation even if the metrics data causes the file to exceed max_size.

Apply this diff:

     /* Check if the event type is metrics, handle the payload differently */
     if (event_chunk->type == FLB_INPUT_METRICS) {
         print_metrics_text(ctx->ins, fp,
                            event_chunk->data, event_chunk->size);
+        update_file_size_counter(ctx, fp);
         fclose(fp);
         FLB_OUTPUT_RETURN(FLB_OK);
     }
🧹 Nitpick comments (1)
plugins/out_logrotate/logrotate.c (1)

530-536: Consider logging when file size tracking fails.

If fstat fails, current_file_size is not updated, which could prevent rotation from triggering correctly. While this is a rare scenario, logging a debug or warning message would aid troubleshooting.

Apply this diff:

 static void update_file_size_counter(struct flb_logrotate_conf *ctx, FILE *fp)
 {
     struct stat st;
     if (fstat(fileno(fp), &st) == 0 && st.st_size >= 0) {
         ctx->current_file_size = (size_t) st.st_size;
+    } else {
+        flb_plg_debug(ctx->ins, "failed to update file size counter (fstat error)");
     }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 201d5a3 and ca41053.

📒 Files selected for processing (2)
  • plugins/out_logrotate/logrotate.c (1 hunks)
  • tests/runtime/out_logrotate.c (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/runtime/out_logrotate.c
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-23T07:43:16.216Z
Learnt from: cosmo0920
Repo: fluent/fluent-bit PR: 11059
File: plugins/in_tail/tail_file.c:1618-1640
Timestamp: 2025-10-23T07:43:16.216Z
Learning: In plugins/in_tail/tail_file.c, when truncate_long_lines is enabled and the buffer is full, the early truncation path uses `lines > 0` as the validation pattern to confirm whether process_content successfully processed content. This is intentional to track occurrences of line processing rather than byte consumption, and consuming bytes based on `processed_bytes > 0` would be overkill for this validation purpose.

Applied to files:

  • plugins/out_logrotate/logrotate.c
🧬 Code graph analysis (1)
plugins/out_logrotate/logrotate.c (6)
include/fluent-bit/flb_mem.h (2)
  • flb_calloc (84-96)
  • flb_free (126-128)
include/fluent-bit/flb_output.h (1)
  • flb_output_config_map_set (1298-1321)
src/flb_output.c (2)
  • flb_output_get_property (1096-1099)
  • flb_output_set_context (1524-1527)
src/flb_pack.c (1)
  • flb_msgpack_to_json_str (1250-1291)
lib/cmetrics/src/cmt_decode_msgpack.c (1)
  • cmt_decode_msgpack_create (1450-1496)
src/flb_log_event_decoder.c (3)
  • flb_log_event_decoder_init (99-116)
  • flb_log_event_decoder_next (310-406)
  • flb_log_event_decoder_destroy (147-179)
🔇 Additional comments (13)
plugins/out_logrotate/logrotate.c (13)

31-68: Includes and constants look good.

The required headers for PATH_MAX, PRIu64, and dirname() are now properly included with platform-specific guards. The constants for compression and path handling are well-defined.


70-84: Configuration struct is well-designed.

The structure appropriately tracks both configuration settings and runtime state (current file size). All fields are clearly named and typed.


86-103: Delimiter mapping is straightforward and correct.

The function correctly maps common delimiter names to their character representations using case-insensitive comparison.


105-192: Initialization logic is robust.

The initialization function properly allocates memory, applies configuration mappings, validates the format setting, and logs the resolved configuration. Error paths correctly clean up allocated resources.


194-392: Output formatting functions are well-implemented.

All format handlers (CSV, LTSV, plain, template, metrics) correctly process their respective formats. Template parsing handles edge cases like nested and unclosed braces. Memory management in plain_output properly frees the allocated JSON string buffer.


394-514: Directory creation logic is platform-aware and robust.

The mkpath function correctly implements recursive directory creation for Windows, macOS, and other POSIX systems. Platform-specific path handling is appropriate, and error checking is thorough. Memory allocation uses project allocators consistently.


516-550: Rotation helper functions are correct and thread-safe.

The rotation check, file size tracking (using fstat), and timestamp generation (using thread-safe localtime_r/localtime_s) are all properly implemented per previous review feedback.


708-751: File rotation logic is sound with appropriate error handling.

The function correctly renames the file, compresses it if enabled, and handles failures by cleaning up partial artifacts. Keeping the uncompressed rotated file when compression fails is a reasonable fallback.


886-918: Rotation error handling is appropriate.

When rotation fails, the code correctly keeps the existing file and retries on the next flush. Errors are logged by rotate_file itself. The cleanup call at line 916 is best-effort; its return value need not be checked since cleanup failure doesn't affect correctness.


920-946: File opening and initialization logic is robust.

The code correctly handles directory creation when mkdir is enabled, retries file opening after successful mkpath, and initializes the file size counter for new or existing files.


966-1057: Event processing and file size tracking are correct for log events.

The msgpack and log event paths properly decode data, format output according to the selected format, and update the file size counter before closing. The log event decoder is correctly initialized and destroyed.


1062-1072: Exit handler correctly releases resources.

The cleanup function properly checks for NULL context and frees the allocated configuration structure.


1074-1159: Configuration and plugin definition are comprehensive.

The configuration map provides sensible defaults (100MB max size, 7 rotated files, gzip enabled) and covers all necessary parameters. The plugin structure correctly declares support for both logs and metrics, uses a single worker for sequential file writes, and registers all required callbacks.

Comment on lines +585 to +705
/* Function to compress a file using streaming gzip (memory-safe for large files) */
static int gzip_compress_file(const char *input_filename, const char *output_filename, struct flb_output_instance *ins)
{
FILE *src_fp, *dst_fp;
char *input_buffer, *output_buffer;
size_t bytes_read, output_buffer_size;
size_t total_input_size = 0;
mz_ulong crc = MZ_CRC32_INIT;
z_stream strm;
int ret = 0, flush, status;

/* Open source file */
src_fp = fopen(input_filename, "rb");
if (!src_fp) {
flb_plg_error(ins, "failed to open source file for gzip: %s", input_filename);
return -1;
}

/* Open destination file */
dst_fp = fopen(output_filename, "wb");
if (!dst_fp) {
flb_plg_error(ins, "failed to create gzip file: %s", output_filename);
fclose(src_fp);
return -1;
}

/* Allocate input and output buffers */
input_buffer = flb_malloc(GZIP_CHUNK_SIZE);
output_buffer_size = compressBound(GZIP_CHUNK_SIZE);
output_buffer = flb_malloc(output_buffer_size);

if (!input_buffer || !output_buffer) {
flb_plg_error(ins, "failed to allocate compression buffers");
ret = -1;
goto cleanup;
}

/* Write gzip header */
write_gzip_header(dst_fp);

/* Initialize deflate stream (raw deflate without gzip wrapper) */
memset(&strm, 0, sizeof(strm));
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;

status = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
-Z_DEFAULT_WINDOW_BITS, 9, Z_DEFAULT_STRATEGY);
if (status != Z_OK) {
flb_plg_error(ins, "failed to initialize deflate stream");
ret = -1;
goto cleanup;
}

/* Process file in chunks (ensure Z_FINISH is always issued) */
do {
bytes_read = fread(input_buffer, 1, GZIP_CHUNK_SIZE, src_fp);
if (bytes_read > 0) {
/* Update CRC and total size */
crc = mz_crc32(crc, (const unsigned char *)input_buffer, bytes_read);
total_input_size += bytes_read;

/* Set up deflate input */
strm.next_in = (Bytef *)input_buffer;
strm.avail_in = bytes_read;

/* Determine flush mode based on EOF after this read */
flush = feof(src_fp) ? Z_FINISH : Z_NO_FLUSH;

/* Compress chunk */
do {
strm.next_out = (Bytef *)output_buffer;
strm.avail_out = output_buffer_size;

status = deflate(&strm, flush);
if (status == Z_STREAM_ERROR) {
flb_plg_error(ins, "deflate stream error during compression");
ret = -1;
goto deflate_cleanup;
}

/* Write compressed data */
size_t compressed_bytes = output_buffer_size - strm.avail_out;
if (compressed_bytes > 0) {
if (fwrite(output_buffer, 1, compressed_bytes, dst_fp) != compressed_bytes) {
flb_plg_error(ins, "failed to write compressed data");
ret = -1;
goto deflate_cleanup;
}
}
} while (strm.avail_out == 0);

/* Verify all input was consumed */
if (strm.avail_in != 0) {
flb_plg_error(ins, "deflate did not consume all input data");
ret = -1;
goto deflate_cleanup;
}
}
} while (bytes_read > 0 && status != Z_STREAM_END);

/* Verify compression completed successfully */
if (status != Z_STREAM_END) {
flb_plg_error(ins, "compression did not complete properly");
ret = -1;
} else {
/* Write gzip footer (CRC32 + original size) */
write_gzip_footer(dst_fp, crc, total_input_size);
}

deflate_cleanup:
deflateEnd(&strm);

cleanup:
if (input_buffer) flb_free(input_buffer);
if (output_buffer) flb_free(output_buffer);
fclose(src_fp);
fclose(dst_fp);

return ret;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Gzip stream not finalized for files that are multiples of 64KB.

When a file's size is an exact multiple of GZIP_CHUNK_SIZE (64KB), the final fread fills the buffer completely. At this point, feof(src_fp) returns false because the EOF indicator is only set when a read attempts to go past EOF. Consequently, flush is set to Z_NO_FLUSH instead of Z_FINISH. On the next iteration, bytes_read is 0, the loop exits, and status != Z_STREAM_END triggers the error at line 687, or worse, produces a truncated gzip file.

Apply this fix to ensure Z_FINISH is always issued:

-    /* Process file in chunks (ensure Z_FINISH is always issued) */
-    do {
+    /* Process file in chunks */
+    for (;;) {
         bytes_read = fread(input_buffer, 1, GZIP_CHUNK_SIZE, src_fp);
         if (bytes_read > 0) {
             /* Update CRC and total size */
             crc = mz_crc32(crc, (const unsigned char *)input_buffer, bytes_read);
             total_input_size += bytes_read;

             /* Set up deflate input */
             strm.next_in = (Bytef *)input_buffer;
             strm.avail_in = bytes_read;
             
-            /* Determine flush mode based on EOF after this read */
-            flush = feof(src_fp) ? Z_FINISH : Z_NO_FLUSH;
+            /* Check if this is the last chunk by attempting to peek ahead */
+            int next_byte = fgetc(src_fp);
+            if (next_byte == EOF) {
+                flush = Z_FINISH;
+            } else {
+                ungetc(next_byte, src_fp);
+                flush = Z_NO_FLUSH;
+            }

             /* Compress chunk */
             do {
                 strm.next_out = (Bytef *)output_buffer;
                 strm.avail_out = output_buffer_size;

                 status = deflate(&strm, flush);
                 if (status == Z_STREAM_ERROR) {
                     flb_plg_error(ins, "deflate stream error during compression");
                     ret = -1;
                     goto deflate_cleanup;
                 }

                 /* Write compressed data */
                 size_t compressed_bytes = output_buffer_size - strm.avail_out;
                 if (compressed_bytes > 0) {
                     if (fwrite(output_buffer, 1, compressed_bytes, dst_fp) != compressed_bytes) {
                         flb_plg_error(ins, "failed to write compressed data");
                         ret = -1;
                         goto deflate_cleanup;
                     }
                 }
             } while (strm.avail_out == 0);

             /* Verify all input was consumed */
             if (strm.avail_in != 0) {
                 flb_plg_error(ins, "deflate did not consume all input data");
                 ret = -1;
                 goto deflate_cleanup;
             }
+            
+            if (flush == Z_FINISH) {
+                break;
+            }
+        } else {
+            /* No more data read, break out */
+            break;
         }
-    } while (bytes_read > 0 && status != Z_STREAM_END);
+    }

     /* Verify compression completed successfully */
     if (status != Z_STREAM_END) {
         flb_plg_error(ins, "compression did not complete properly");
         ret = -1;
     } else {
         /* Write gzip footer (CRC32 + original size) */
         write_gzip_footer(dst_fp, crc, total_input_size);
     }

Comment on lines +754 to +803
static int cleanup_old_files(struct flb_logrotate_conf *ctx, const char *directory, const char *base_filename)
{
DIR *dir;
struct dirent *entry;
char pattern[PATH_MAX];
char full_path[PATH_MAX];
char **files = NULL;
int file_count = 0;
int max_files = ctx->max_files;
int i, j;

/* Create pattern to match rotated files */
snprintf(pattern, PATH_MAX - 1, "%s.", base_filename);

dir = opendir(directory);
if (!dir) {
return 0; /* Directory doesn't exist or can't be opened */
}

/* Count matching files */
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, pattern, strlen(pattern)) == 0) {
file_count++;
}
}

if (file_count <= max_files) {
closedir(dir);
return 0;
}

/* Allocate array for file names */
files = flb_calloc(file_count, sizeof(char *));
if (!files) {
closedir(dir);
return -1;
}

/* Collect file names */
rewinddir(dir);
i = 0;
while ((entry = readdir(dir)) != NULL && i < file_count) {
if (strncmp(entry->d_name, pattern, strlen(pattern)) == 0) {
snprintf(full_path, PATH_MAX - 1, "%s" FLB_PATH_SEPARATOR "%s",
directory, entry->d_name);
files[i] = flb_strdup(full_path);
i++;
}
}
closedir(dir);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Pattern matching may delete unrelated files; check allocation return values.

The cleanup pattern "base_filename." matches any file starting with that prefix, which could inadvertently include non-rotated files like "base_filename.config" or "base_filename.backup". Additionally, flb_strdup at line 799 can return NULL on allocation failure, but this isn't checked before stat is called on the pointer (line 809), which would cause a crash.

Issue 1: Tighten the pattern match to avoid false positives.

Consider matching the timestamp format explicitly (e.g., \d{8}_\d{6} optionally followed by .gz) or at minimum verify that matched filenames follow the expected rotation naming scheme before including them in cleanup.

Issue 2: Check flb_strdup return value.

Apply this diff:

     while ((entry = readdir(dir)) != NULL && i < file_count) {
         if (strncmp(entry->d_name, pattern, strlen(pattern)) == 0) {
             snprintf(full_path, PATH_MAX - 1, "%s" FLB_PATH_SEPARATOR "%s", 
                     directory, entry->d_name);
             files[i] = flb_strdup(full_path);
+            if (!files[i]) {
+                flb_plg_error(ctx->ins, "failed to allocate memory for file path");
+                /* Clean up already allocated entries */
+                for (int k = 0; k < i; k++) {
+                    flb_free(files[k]);
+                }
+                flb_free(files);
+                closedir(dir);
+                return -1;
+            }
             i++;
         }
     }

Signed-off-by: SagiROosto <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
plugins/out_logrotate/logrotate.c (3)

955-958: Metrics writes never bump current_file_size.

After writing metrics we close the file without updating the size counter, so rotations never trigger for metrics-only streams and the file grows unbounded.

     if (event_chunk->type == FLB_INPUT_METRICS) {
         print_metrics_text(ctx->ins, fp,
                            event_chunk->data, event_chunk->size);
+        update_file_size_counter(ctx, fp);
         fclose(fp);
         FLB_OUTPUT_RETURN(FLB_OK);
     }

793-833: Check flb_strdup results before using them.

When flb_strdup(full_path) fails, files[i] is NULL but later passed to stat()/unlink(), leading to crashes. We need to validate the allocation, free any previous entries, and abort the cleanup gracefully on failure.

             files[i] = flb_strdup(full_path);
-            i++;
+            if (!files[i]) {
+                flb_plg_error(ctx->ins, "failed to duplicate path for rotated file");
+                while (i-- > 0) {
+                    flb_free(files[i]);
+                }
+                flb_free(files);
+                closedir(dir);
+                return -1;
+            }
+            i++;

639-694: Always finish the gzip stream when EOF aligns with the chunk boundary.

When the source file size is an exact multiple of GZIP_CHUNK_SIZE, feof(src_fp) is still false after the final full read. The loop exits with status == Z_OK, the plugin logs "compression did not complete properly", and rotation fails even though the file was fully read. Issue #7572 still reproduces this way. Make sure we always emit a Z_FINISH pass (with strm.avail_in == 0) after the read loop so deflate reaches Z_STREAM_END and the footer is written.

-    /* Process file in chunks (ensure Z_FINISH is always issued) */
-    do {
+    /* Process file in chunks */
+    do {
         bytes_read = fread(input_buffer, 1, GZIP_CHUNK_SIZE, src_fp);
         if (bytes_read > 0) {
@@
-    } while (bytes_read > 0 && status != Z_STREAM_END);
-
-    /* Verify compression completed successfully */
-    if (status != Z_STREAM_END) {
-        flb_plg_error(ins, "compression did not complete properly");
-        ret = -1;
-    } else {
-        /* Write gzip footer (CRC32 + original size) */
-        write_gzip_footer(dst_fp, crc, total_input_size);
-    }
+    } while (bytes_read > 0 && status != Z_STREAM_END);
+
+    if (status != Z_STREAM_END) {
+        flush = Z_FINISH;
+        strm.next_in = Z_NULL;
+        strm.avail_in = 0;
+
+        do {
+            strm.next_out = (Bytef *)output_buffer;
+            strm.avail_out = output_buffer_size;
+
+            status = deflate(&strm, flush);
+            if (status != Z_STREAM_END && status != Z_OK) {
+                flb_plg_error(ins, "deflate finish error during compression");
+                ret = -1;
+                goto deflate_cleanup;
+            }
+
+            size_t compressed_bytes = output_buffer_size - strm.avail_out;
+            if (compressed_bytes > 0) {
+                if (fwrite(output_buffer, 1, compressed_bytes, dst_fp) != compressed_bytes) {
+                    flb_plg_error(ins, "failed to write compressed data");
+                    ret = -1;
+                    goto deflate_cleanup;
+                }
+            }
+        } while (status != Z_STREAM_END);
+    }
+
+    if (status == Z_STREAM_END) {
+        /* Write gzip footer (CRC32 + original size) */
+        write_gzip_footer(dst_fp, crc, total_input_size);
+    }
+    else {
+        flb_plg_error(ins, "compression did not complete properly");
+        ret = -1;
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca41053 and e967582.

📒 Files selected for processing (2)
  • plugins/out_logrotate/logrotate.c (1 hunks)
  • tests/runtime/out_logrotate.c (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-04T12:32:46.030Z
Learnt from: shadowshot-x
Repo: fluent/fluent-bit PR: 10825
File: plugins/out_s3/s3.c:0-0
Timestamp: 2025-09-04T12:32:46.030Z
Learning: In Fluent Bit plugins, avoid creating plugin-local config_map entries that duplicate core functionality. The core already provides Retry_Limit parsed into ins->retry_limit, so plugins should use that directly rather than defining their own retry_limit configuration option.

Applied to files:

  • tests/runtime/out_logrotate.c
📚 Learning: 2025-10-23T07:43:16.216Z
Learnt from: cosmo0920
Repo: fluent/fluent-bit PR: 11059
File: plugins/in_tail/tail_file.c:1618-1640
Timestamp: 2025-10-23T07:43:16.216Z
Learning: In plugins/in_tail/tail_file.c, when truncate_long_lines is enabled and the buffer is full, the early truncation path uses `lines > 0` as the validation pattern to confirm whether process_content successfully processed content. This is intentional to track occurrences of line processing rather than byte consumption, and consuming bytes based on `processed_bytes > 0` would be overkill for this validation purpose.

Applied to files:

  • plugins/out_logrotate/logrotate.c
🧬 Code graph analysis (2)
tests/runtime/out_logrotate.c (1)
src/flb_lib.c (10)
  • flb_create (138-220)
  • flb_service_set (647-673)
  • flb_input (261-271)
  • flb_input_set (300-330)
  • flb_output (274-284)
  • flb_output_set (515-546)
  • flb_start (914-925)
  • flb_lib_push (774-801)
  • flb_stop (942-985)
  • flb_destroy (223-258)
plugins/out_logrotate/logrotate.c (6)
include/fluent-bit/flb_mem.h (2)
  • flb_calloc (84-96)
  • flb_free (126-128)
include/fluent-bit/flb_output.h (1)
  • flb_output_config_map_set (1298-1321)
src/flb_output.c (2)
  • flb_output_get_property (1096-1099)
  • flb_output_set_context (1524-1527)
src/flb_pack.c (1)
  • flb_msgpack_to_json_str (1250-1291)
lib/cmetrics/src/cmt_decode_msgpack.c (1)
  • cmt_decode_msgpack_create (1450-1496)
src/flb_log_event_decoder.c (3)
  • flb_log_event_decoder_init (99-116)
  • flb_log_event_decoder_next (310-406)
  • flb_log_event_decoder_destroy (147-179)

Comment on lines +765 to +826
/* Create pattern to match rotated files */
snprintf(pattern, PATH_MAX - 1, "%s.", base_filename);

dir = opendir(directory);
if (!dir) {
return 0; /* Directory doesn't exist or can't be opened */
}

/* Count matching files */
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, pattern, strlen(pattern)) == 0) {
file_count++;
}
}

if (file_count <= max_files) {
closedir(dir);
return 0;
}

/* Allocate array for file names */
files = flb_calloc(file_count, sizeof(char *));
if (!files) {
closedir(dir);
return -1;
}

/* Collect file names */
rewinddir(dir);
i = 0;
while ((entry = readdir(dir)) != NULL && i < file_count) {
if (strncmp(entry->d_name, pattern, strlen(pattern)) == 0) {
snprintf(full_path, PATH_MAX - 1, "%s" FLB_PATH_SEPARATOR "%s",
directory, entry->d_name);
files[i] = flb_strdup(full_path);
i++;
}
}
closedir(dir);

/* Sort files by modification time (oldest first) */
for (i = 0; i < file_count - 1; i++) {
for (j = i + 1; j < file_count; j++) {
struct stat st1, st2;
if (stat(files[i], &st1) == 0 && stat(files[j], &st2) == 0) {
if (st1.st_mtime > st2.st_mtime) {
char *temp = files[i];
files[i] = files[j];
files[j] = temp;
}
}
}
}

/* Remove oldest files */
if (file_count > max_files) {
flb_plg_info(ctx->ins, "cleaning up old rotated files: removing %d files (keeping %d)",
file_count - max_files, max_files);
}
for (i = 0; i < file_count - max_files; i++) {
if (unlink(files[i]) == 0) {
flb_plg_debug(ctx->ins, "removed old rotated file: %s", files[i]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Tighten the cleanup filename pattern.

Using strncmp(entry->d_name, pattern, strlen(pattern)) == 0 will happily delete anything starting with "base_filename."—for example base_filename.config or base_filename.backup. That’s data loss. Please ensure the suffix matches the rotation format (YYYYMMDD_HHMMSS optionally followed by .gz) before counting or unlinking entries.

A simple guard is to validate that the substring after pattern is 15 digits with an underscore at position 8, or 18 characters ending in .gz, and bail out otherwise.

🤖 Prompt for AI Agents
In plugins/out_logrotate/logrotate.c around lines 765-826, the code currently
treats any filename starting with "base_filename." as a rotated file; tighten
the matching so only files that follow the rotation timestamp format are
counted/removed: when scanning entries, after matching the prefix (pattern)
validate the remainder exactly matches either "YYYYMMDD_HHMMSS" (15 chars: 8
digits, '_' at pos 8, 6 digits) or that same 15-char timestamp followed by ".gz"
(18 chars total with trailing ".gz"); use isdigit checks for the digit positions
and explicit length checks, and only increment file_count or add to files[] when
the validation passes; apply the same validation before unlinking so only
strictly matching files are deleted, and ensure you continue to close the
directory and free allocated memory on error/exit.

Comment on lines +888 to +917
/* Extract directory and base filename for cleanup */
out_file_copy = flb_strdup(out_file);
if (out_file_copy) {
#ifdef FLB_SYSTEM_WINDOWS
PathRemoveFileSpecA(out_file_copy);
strncpy(directory, out_file_copy, PATH_MAX - 1);
directory[PATH_MAX - 1] = '\0';
#else
strncpy(directory, dirname(out_file_copy), PATH_MAX - 1);
directory[PATH_MAX - 1] = '\0';
#endif
flb_free(out_file_copy);
}

/* Get base filename for cleanup */
char *last_sep = strrchr(out_file, FLB_PATH_SEPARATOR[0]);
if (last_sep) {
strncpy(base_filename, last_sep + 1, PATH_MAX - 1);
} else {
strncpy(base_filename, out_file, PATH_MAX - 1);
}
base_filename[PATH_MAX - 1] = '\0';

/* Rotate the file */
if (rotate_file(ctx, out_file) == 0) {
/* Reset file size counter after rotation */
ctx->current_file_size = 0;
/* Clean up old rotated files */
cleanup_old_files(ctx, directory, base_filename);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard cleanup_old_files when directory extraction fails.

If flb_strdup(out_file) returns NULL we leave directory uninitialized, yet still pass it into cleanup_old_files, yielding undefined behavior on low-memory paths. Either bail out of rotation cleanup or set a deterministic fallback (e.g., ".") before invoking cleanup.

-    if (should_rotate_file(ctx)) {
+    if (should_rotate_file(ctx)) {
+        bool have_directory = false;
+        directory[0] = '\0';
         /* Extract directory and base filename for cleanup */
         out_file_copy = flb_strdup(out_file);
         if (out_file_copy) {
@@
-            flb_free(out_file_copy);
+            flb_free(out_file_copy);
+            have_directory = true;
         }
@@
-        if (rotate_file(ctx, out_file) == 0) {
+        if (rotate_file(ctx, out_file) == 0) {
             /* Reset file size counter after rotation */
             ctx->current_file_size = 0;
             /* Clean up old rotated files */
-            cleanup_old_files(ctx, directory, base_filename);
+            if (have_directory) {
+                cleanup_old_files(ctx, directory, base_filename);
+            }
         }
     }
🤖 Prompt for AI Agents
In plugins/out_logrotate/logrotate.c around lines 888 to 917, the code can call
cleanup_old_files with an uninitialized directory when flb_strdup(out_file)
returns NULL; ensure you guard that case by either skipping the cleanup step
when directory extraction failed or by assigning a deterministic fallback (e.g.,
directory = "." and ensure it is NUL-terminated) before calling
cleanup_old_files; also make sure base_filename remains valid and NUL-terminated
even when out_file_copy is NULL to avoid passing invalid pointers to
cleanup_old_files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs-required ok-package-test Run PR packaging tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants