From 3d58659eecd41fe9a398f326dd6a0d52aea8ddd1 Mon Sep 17 00:00:00 2001 From: Hiroshi Hatake Date: Tue, 30 Jun 2026 17:57:07 +0900 Subject: [PATCH 1/7] in_tail: Implement a capability to handle unicode paths Signed-off-by: Hiroshi Hatake --- plugins/in_tail/CMakeLists.txt | 1 + plugins/in_tail/tail.c | 9 ++ plugins/in_tail/tail_config.c | 21 +++++ plugins/in_tail/tail_config.h | 8 ++ plugins/in_tail/tail_file.c | 106 +++++++++++++++++++-- plugins/in_tail/tail_fs_stat.c | 14 ++- plugins/in_tail/tail_scan_win32.c | 149 +++++++++++++++++++++++++----- plugins/in_tail/win32/interface.h | 9 ++ plugins/in_tail/win32/io.c | 32 +++++++ plugins/in_tail/win32/path.c | 125 +++++++++++++++++++++++++ plugins/in_tail/win32/stat.c | 122 ++++++++++++++++++++++++ 11 files changed, 563 insertions(+), 33 deletions(-) create mode 100644 plugins/in_tail/win32/path.c diff --git a/plugins/in_tail/CMakeLists.txt b/plugins/in_tail/CMakeLists.txt index 31d865218d5..36eca1ce2fd 100644 --- a/plugins/in_tail/CMakeLists.txt +++ b/plugins/in_tail/CMakeLists.txt @@ -28,6 +28,7 @@ endif() if(MSVC) set(src ${src} + win32/path.c win32/stat.c win32/io.c ) diff --git a/plugins/in_tail/tail.c b/plugins/in_tail/tail.c index 40373f5c184..5818f38f0ce 100644 --- a/plugins/in_tail/tail.c +++ b/plugins/in_tail/tail.c @@ -736,6 +736,15 @@ static struct flb_config_map config_map[] = { "set to false to use file stat watcher instead of inotify." }, #endif +#ifdef FLB_SYSTEM_WINDOWS + { + FLB_CONFIG_MAP_STR, "windows.path_encoding", "ansi", + 0, FLB_FALSE, 0, + "Windows-only path encoding mode. Use 'utf-8' to treat paths as UTF-8 " + "and call Windows wide-character file APIs. The default 'ansi' mode " + "keeps the legacy active ANSI code page behavior." + }, +#endif #ifdef FLB_HAVE_REGEX { FLB_CONFIG_MAP_STR, "parser", NULL, diff --git a/plugins/in_tail/tail_config.c b/plugins/in_tail/tail_config.c index f6f28dceab0..3f325989615 100644 --- a/plugins/in_tail/tail_config.c +++ b/plugins/in_tail/tail_config.c @@ -109,6 +109,9 @@ struct flb_tail_config *flb_tail_config_create(struct flb_input_instance *ins, #ifdef FLB_HAVE_SQLDB ctx->db_sync = 1; /* sqlite sync 'normal' */ #endif +#ifdef FLB_SYSTEM_WINDOWS + ctx->windows_path_encoding = FLB_TAIL_WINDOWS_PATH_ENCODING_ANSI; +#endif #ifdef FLB_HAVE_UNICODE_ENCODER ctx->preferred_input_encoding = FLB_UNICODE_ENCODING_UNSPECIFIED; #endif @@ -196,6 +199,24 @@ struct flb_tail_config *flb_tail_config_create(struct flb_input_instance *ins, return NULL; } +#ifdef FLB_SYSTEM_WINDOWS + tmp = flb_input_get_property("windows.path_encoding", ins); + if (tmp) { + if (strcasecmp(tmp, "ansi") == 0) { + ctx->windows_path_encoding = FLB_TAIL_WINDOWS_PATH_ENCODING_ANSI; + } + else if (strcasecmp(tmp, "utf-8") == 0 || + strcasecmp(tmp, "utf8") == 0) { + ctx->windows_path_encoding = FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8; + } + else { + flb_plg_error(ctx->ins, "invalid 'windows.path_encoding' value %s", tmp); + flb_tail_config_destroy(ctx); + return NULL; + } + } +#endif + #ifdef FLB_HAVE_UNICODE_ENCODER tmp = flb_input_get_property("unicode.encoding", ins); if (tmp) { diff --git a/plugins/in_tail/tail_config.h b/plugins/in_tail/tail_config.h index d39249b93ee..b1739c65621 100644 --- a/plugins/in_tail/tail_config.h +++ b/plugins/in_tail/tail_config.h @@ -46,6 +46,11 @@ #define FLB_TAIL_METRIC_L_SKIPPED 105 /* number of skipped occurrences of long lines */ #endif +#ifdef FLB_SYSTEM_WINDOWS +#define FLB_TAIL_WINDOWS_PATH_ENCODING_ANSI 0 +#define FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8 1 +#endif + struct flb_tail_config { int fd_notify; /* inotify fd */ flb_pipefd_t ch_manager[2]; /* pipe: channel manager */ @@ -96,6 +101,9 @@ struct flb_tail_config { * being ingested */ time_t last_pending; /* last time a 'pending signal' was emitted' */ struct mk_list *path_list; /* list of paths to scan (glob) */ +#ifdef FLB_SYSTEM_WINDOWS + int windows_path_encoding; /* Windows path API encoding mode */ +#endif flb_sds_t path_key; /* key name of file path */ flb_sds_t key; /* key for unstructured record */ int skip_long_lines; /* skip long lines */ diff --git a/plugins/in_tail/tail_file.c b/plugins/in_tail/tail_file.c index 836aea41e44..502d2f220f1 100644 --- a/plugins/in_tail/tail_file.c +++ b/plugins/in_tail/tail_file.c @@ -58,6 +58,62 @@ #define FLB_TAIL_DB_OFFSET_MARKER_SIZE 32 +#ifdef FLB_SYSTEM_WINDOWS +static inline int tail_file_open(struct flb_tail_config *ctx, const char *path, + int flags) +{ + if (ctx->windows_path_encoding == FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8) { + return win32_open_utf8(path, flags); + } + + return open(path, flags); +} + +static inline int tail_file_stat(struct flb_tail_config *ctx, const char *path, + struct stat *st) +{ + if (ctx->windows_path_encoding == FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8) { + return win32_stat_utf8(path, st); + } + + return stat(path, st); +} + +static inline int tail_file_lstat(struct flb_tail_config *ctx, const char *path, + struct stat *st) +{ + if (ctx->windows_path_encoding == FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8) { + return win32_lstat_utf8(path, st); + } + + return lstat(path, st); +} +#else +static inline int tail_file_open(struct flb_tail_config *ctx, const char *path, + int flags) +{ + (void) ctx; + + return open(path, flags); +} + +static inline int tail_file_stat(struct flb_tail_config *ctx, const char *path, + struct stat *st) +{ + (void) ctx; + + return stat(path, st); +} + +static inline int tail_file_lstat(struct flb_tail_config *ctx, const char *path, + struct stat *st) +{ + (void) ctx; + + return lstat(path, st); +} +#endif + static inline void consume_bytes(char *buf, int bytes, int length) { memmove(buf, buf + bytes, length - bytes); @@ -1358,7 +1414,7 @@ int flb_tail_file_append(char *path, struct stat *st, int mode, } #endif - fd = open(path, O_RDONLY); + fd = tail_file_open(ctx, path, O_RDONLY); if (fd == -1) { flb_errno(); flb_plg_error(ctx->ins, "cannot open %s", path); @@ -1378,7 +1434,7 @@ int flb_tail_file_append(char *path, struct stat *st, int mode, file->tail_mode = mode; /* On non-windows environments check if the original path is a link */ - ret = lstat(path, &lst); + ret = tail_file_lstat(ctx, path, &lst); if (ret == 0) { if (S_ISLNK(lst.st_mode)) { file->is_link = FLB_TRUE; @@ -2113,7 +2169,7 @@ int flb_tail_file_is_rotated(struct flb_tail_config *ctx, /* Check if the 'original monitored file' is a link and rotated */ if (file->is_link == FLB_TRUE) { - ret = lstat(file->name, &st); + ret = tail_file_lstat(ctx, file->name, &st); if (ret == -1) { /* Broken link or missing file */ if (errno == ENOENT) { @@ -2148,7 +2204,7 @@ int flb_tail_file_is_rotated(struct flb_tail_config *ctx, /* Get stats from the file name */ - ret = stat(name, &st); + ret = tail_file_stat(ctx, name, &st); if (ret == -1) { flb_errno(); flb_free(name); @@ -2283,6 +2339,7 @@ char *flb_tail_file_name(struct flb_tail_file *file) #elif defined(FLB_SYSTEM_WINDOWS) int len; + wchar_t *wide_buf; h = (HANDLE) _get_osfhandle(file->fd); if (h == INVALID_HANDLE_VALUE) { @@ -2294,14 +2351,43 @@ char *flb_tail_file_name(struct flb_tail_file *file) /* This function returns the length of the string excluding "\0" * and the resulting path has a "\\?\" prefix. */ - len = GetFinalPathNameByHandleA(h, buf, PATH_MAX, FILE_NAME_NORMALIZED); - if (len == 0 || len >= PATH_MAX) { + if (file->config->windows_path_encoding == FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8) { + wide_buf = flb_calloc(PATH_MAX, sizeof(wchar_t)); + if (wide_buf == NULL) { + flb_free(buf); + return NULL; + } + + len = GetFinalPathNameByHandleW(h, wide_buf, PATH_MAX, + FILE_NAME_NORMALIZED); + if (len == 0 || len >= PATH_MAX) { + flb_free(wide_buf); + flb_free(buf); + return NULL; + } + + if (wcsstr(wide_buf, L"\\\\?\\")) { + memmove(wide_buf, wide_buf + 4, (len + 1) * sizeof(wchar_t)); + } + flb_free(buf); - return NULL; + buf = win32_wide_to_utf8(wide_buf); + flb_free(wide_buf); + + if (buf == NULL) { + return NULL; + } } + else { + len = GetFinalPathNameByHandleA(h, buf, PATH_MAX, FILE_NAME_NORMALIZED); + if (len == 0 || len >= PATH_MAX) { + flb_free(buf); + return NULL; + } - if (strstr(buf, "\\\\?\\")) { - memmove(buf, buf + 4, len + 1); + if (strstr(buf, "\\\\?\\")) { + memmove(buf, buf + 4, len + 1); + } } #elif defined(FLB_SYSTEM_FREEBSD) if ((file_entries = kinfo_getfile(getpid(), &file_count)) == NULL) { @@ -2396,7 +2482,7 @@ int flb_tail_file_rotated(struct flb_tail_file *file) #endif /* Check if a new file has been created */ - ret = stat(tmp, &st); + ret = tail_file_stat(ctx, tmp, &st); if (ret == 0 && st.st_ino != file->inode) { if (flb_tail_file_exists(&st, ctx) == FLB_FALSE) { ret = flb_tail_file_append(tmp, &st, FLB_TAIL_STATIC, -1, ctx); diff --git a/plugins/in_tail/tail_fs_stat.c b/plugins/in_tail/tail_fs_stat.c index 18f3fb13695..e489ceac8fd 100644 --- a/plugins/in_tail/tail_fs_stat.c +++ b/plugins/in_tail/tail_fs_stat.c @@ -34,6 +34,18 @@ #include "win32.h" #endif +static int tail_fs_stat_path(struct flb_tail_config *ctx, const char *path, + struct stat *st) +{ +#ifdef FLB_SYSTEM_WINDOWS + if (ctx->windows_path_encoding == FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8) { + return win32_stat_utf8(path, st); + } +#endif + + return stat(path, st); +} + struct fs_stat { /* last time check */ time_t checked; @@ -233,7 +245,7 @@ int flb_tail_fs_stat_add(struct flb_tail_file *file) } fst->checked = time(NULL); - ret = stat(file->name, &fst->st); + ret = tail_fs_stat_path(file->config, file->name, &fst->st); if (ret == -1) { flb_errno(); flb_free(fst); diff --git a/plugins/in_tail/tail_scan_win32.c b/plugins/in_tail/tail_scan_win32.c index c5349ccf7b2..bef85410d33 100644 --- a/plugins/in_tail/tail_scan_win32.c +++ b/plugins/in_tail/tail_scan_win32.c @@ -41,6 +41,11 @@ static int tail_is_excluded(char *path, struct flb_tail_config *ctx) { struct mk_list *head; struct flb_slist_entry *pattern; +#ifdef FLB_SYSTEM_WINDOWS + int matched; + wchar_t *wide_path; + wchar_t *wide_pattern; +#endif if (!ctx->exclude_list) { return FLB_FALSE; @@ -48,6 +53,27 @@ static int tail_is_excluded(char *path, struct flb_tail_config *ctx) mk_list_foreach(head, ctx->exclude_list) { pattern = mk_list_entry(head, struct flb_slist_entry, _head); +#ifdef FLB_SYSTEM_WINDOWS + if (ctx->windows_path_encoding == FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8) { + wide_path = win32_utf8_to_wide(path); + wide_pattern = win32_utf8_to_wide(pattern->str); + if (wide_path == NULL || wide_pattern == NULL) { + flb_free(wide_path); + flb_free(wide_pattern); + continue; + } + + matched = PathMatchSpecW(wide_path, wide_pattern); + flb_free(wide_path); + flb_free(wide_pattern); + + if (matched) { + return FLB_TRUE; + } + + continue; + } +#endif if (PathMatchSpecA(path, pattern->str)) { return FLB_TRUE; } @@ -63,21 +89,48 @@ static int tail_is_excluded(char *path, struct flb_tail_config *ctx) static int tail_register_file(const char *target, struct flb_tail_config *ctx, time_t ts) { + int ret; int64_t mtime; struct stat st; - char path[MAX_PATH]; + char legacy_path[MAX_PATH]; + char *path; ssize_t ignored_file_size; uint64_t aged_out_inode; ignored_file_size = -1; - - if (_fullpath(path, target, MAX_PATH) == NULL) { - flb_plg_error(ctx->ins, "cannot get absolute path of %s", target); - return -1; + path = legacy_path; + +#ifdef FLB_SYSTEM_WINDOWS + if (ctx->windows_path_encoding == FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8) { + path = win32_fullpath_utf8(target); + if (path == NULL) { + flb_plg_error(ctx->ins, "cannot get UTF-8 absolute path of %s", target); + return -1; + } + } + else { +#endif + if (_fullpath(path, target, MAX_PATH) == NULL) { + flb_plg_error(ctx->ins, "cannot get absolute path of %s", target); + return -1; + } +#ifdef FLB_SYSTEM_WINDOWS } +#endif - if (stat(path, &st) != 0 || !S_ISREG(st.st_mode)) { - return -1; +#ifdef FLB_SYSTEM_WINDOWS + if (ctx->windows_path_encoding == FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8) { + ret = win32_stat_utf8(path, &st); + } + else { + ret = stat(path, &st); + } +#else + ret = stat(path, &st); +#endif + if (ret != 0 || !S_ISREG(st.st_mode)) { + ret = -1; + goto out; } if (ctx->ignore_older > 0) { @@ -93,14 +146,16 @@ static int tail_register_file(const char *target, struct flb_tail_config *ctx, strlen(path), st.st_size); - return -1; + ret = -1; + goto out; } } } if (tail_is_excluded(path, ctx) == FLB_TRUE) { flb_plg_trace(ctx->ins, "skip '%s' (excluded)", path); - return -1; + ret = -1; + goto out; } if (ctx->ignore_active_older_files && @@ -113,7 +168,8 @@ static int tail_register_file(const char *target, struct flb_tail_config *ctx, if (mtime > 0 && (ts - ctx->ignore_older) > mtime) { flb_plg_debug(ctx->ins, "excluded=%s (ignore_active_older_files)", path); - return -1; + ret = -1; + goto out; } } else { @@ -142,7 +198,14 @@ static int tail_register_file(const char *target, struct flb_tail_config *ctx, } } - return flb_tail_file_append(path, &st, FLB_TAIL_STATIC, ignored_file_size, ctx); + ret = flb_tail_file_append(path, &st, FLB_TAIL_STATIC, ignored_file_size, ctx); + + out: + if (path != legacy_path) { + flb_free(path); + } + + return ret; } /* @@ -165,9 +228,12 @@ static int tail_scan_pattern(const char *path, struct flb_tail_config *ctx) int64_t mtime; HANDLE h; WIN32_FIND_DATA data; + WIN32_FIND_DATAW data_w; + wchar_t *wide_pattern; + char *filename; if (strlen(path) > MAX_PATH - 1) { - flb_plg_error(ctx->ins, "path too long '%s'"); + flb_plg_error(ctx->ins, "path too long '%s'", path); return -1; } @@ -197,32 +263,59 @@ static int tail_scan_pattern(const char *path, struct flb_tail_config *ctx) memcpy(pattern, path, (p1 - path)); pattern[p1 - path] = '\0'; +#ifdef FLB_SYSTEM_WINDOWS + wide_pattern = NULL; + if (ctx->windows_path_encoding == FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8) { + wide_pattern = win32_utf8_to_wide(pattern); + if (wide_pattern == NULL) { + flb_plg_error(ctx->ins, "invalid UTF-8 path pattern '%s'", pattern); + return -1; + } + + h = FindFirstFileW(wide_pattern, &data_w); + flb_free(wide_pattern); + } + else { + h = FindFirstFileA(pattern, &data); + } +#else h = FindFirstFileA(pattern, &data); +#endif if (h == INVALID_HANDLE_VALUE) { return 0; /* none matched */ } now = time(NULL); do { + filename = data.cFileName; +#ifdef FLB_SYSTEM_WINDOWS + if (ctx->windows_path_encoding == FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8) { + filename = win32_wide_to_utf8(data_w.cFileName); + if (filename == NULL) { + continue; + } + } +#endif + /* Ignore the current and parent dirs */ - if (!strcmp(".", data.cFileName) || !strcmp("..", data.cFileName)) { - continue; + if (!strcmp(".", filename) || !strcmp("..", filename)) { + goto next; } /* Avoid an infinite loop */ - if (strchr(data.cFileName, '*')) { - continue; + if (strchr(filename, '*')) { + goto next; } /* Create a path (prefix + filename + suffix) */ memcpy(buf, path, p0 - path + 1); buf[p0 - path + 1] = '\0'; - if (strlen(buf) + strlen(data.cFileName) + strlen(p1) > MAX_PATH - 1) { - flb_plg_warn(ctx->ins, "'%s%s%s' is too long", buf, data.cFileName, p1); - continue; + if (strlen(buf) + strlen(filename) + strlen(p1) > MAX_PATH - 1) { + flb_plg_warn(ctx->ins, "'%s%s%s' is too long", buf, filename, p1); + goto next; } - strcat(buf, data.cFileName); + strcat(buf, filename); strcat(buf, p1); if (strchr(p1, '*')) { @@ -230,7 +323,7 @@ static int tail_scan_pattern(const char *path, struct flb_tail_config *ctx) if (ret >= 0) { n_added += ret; } - continue; + goto next; } /* Try to register the target file */ @@ -238,7 +331,19 @@ static int tail_scan_pattern(const char *path, struct flb_tail_config *ctx) if (ret == 0) { n_added++; } - } while (FindNextFileA(h, &data) != 0); + + next: +#ifdef FLB_SYSTEM_WINDOWS + if (ctx->windows_path_encoding == FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8) { + flb_free(filename); + } +#endif + } while ( +#ifdef FLB_SYSTEM_WINDOWS + ctx->windows_path_encoding == FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8 ? + FindNextFileW(h, &data_w) != 0 : +#endif + FindNextFileA(h, &data) != 0); FindClose(h); return n_added; diff --git a/plugins/in_tail/win32/interface.h b/plugins/in_tail/win32/interface.h index 84b89059429..c7e97fa7ed0 100644 --- a/plugins/in_tail/win32/interface.h +++ b/plugins/in_tail/win32/interface.h @@ -20,6 +20,8 @@ #ifndef FLB_TAIL_WIN32_INTERFACE_H #define FLB_TAIL_WIN32_INTERFACE_H +#include + struct win32_stat { uint64_t st_ino; uint16_t st_mode; @@ -31,8 +33,15 @@ struct win32_stat { int win32_stat(const char *path, struct win32_stat *wst); int win32_lstat(const char *path, struct win32_stat *wst); int win32_fstat(int fd, struct win32_stat *wst); +int win32_stat_utf8(const char *path, struct win32_stat *wst); +int win32_lstat_utf8(const char *path, struct win32_stat *wst); int win32_open(const char *path, int flags); +int win32_open_utf8(const char *path, int flags); + +wchar_t *win32_utf8_to_wide(const char *str); +char *win32_wide_to_utf8(const wchar_t *str); +char *win32_fullpath_utf8(const char *path); #define WIN32_S_IFDIR 0x1000 #define WIN32_S_IFCHR 0x2000 diff --git a/plugins/in_tail/win32/io.c b/plugins/in_tail/win32/io.c index 5315e28c061..6ba7152f0cb 100644 --- a/plugins/in_tail/win32/io.c +++ b/plugins/in_tail/win32/io.c @@ -17,11 +17,18 @@ * limitations under the License. */ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + #include #include #include #include #include + +#include + #include "interface.h" /* @@ -45,3 +52,28 @@ int win32_open(const char *path, int flags) } return _open_osfhandle((intptr_t) h, _O_RDONLY); } + +int win32_open_utf8(const char *path, int flags) +{ + HANDLE h; + wchar_t *wide_path; + + wide_path = win32_utf8_to_wide(path); + if (wide_path == NULL) { + return -1; + } + + h = CreateFileW(wide_path, + GENERIC_READ, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + NULL, /* lpSecurityAttributes */ + OPEN_EXISTING, /* dwCreationDisposition */ + 0, /* dwFlagsAndAttributes */ + NULL); /* hTemplateFile */ + flb_free(wide_path); + + if (h == INVALID_HANDLE_VALUE) { + return -1; + } + return _open_osfhandle((intptr_t) h, _O_RDONLY); +} diff --git a/plugins/in_tail/win32/path.c b/plugins/in_tail/win32/path.c new file mode 100644 index 00000000000..25e89f81ceb --- /dev/null +++ b/plugins/in_tail/win32/path.c @@ -0,0 +1,125 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2026 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 WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include + +#include + +#include "interface.h" + +wchar_t *win32_utf8_to_wide(const char *str) +{ + int len; + wchar_t *buf; + + if (str == NULL) { + return NULL; + } + + len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + str, -1, NULL, 0); + if (len == 0) { + return NULL; + } + + buf = flb_calloc(len, sizeof(wchar_t)); + if (buf == NULL) { + return NULL; + } + + if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + str, -1, buf, len) == 0) { + flb_free(buf); + return NULL; + } + + return buf; +} + +char *win32_wide_to_utf8(const wchar_t *str) +{ + int len; + char *buf; + + if (str == NULL) { + return NULL; + } + + len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, + str, -1, NULL, 0, NULL, NULL); + if (len == 0) { + return NULL; + } + + buf = flb_calloc(len, sizeof(char)); + if (buf == NULL) { + return NULL; + } + + if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, + str, -1, buf, len, NULL, NULL) == 0) { + flb_free(buf); + return NULL; + } + + return buf; +} + +char *win32_fullpath_utf8(const char *path) +{ + DWORD len; + DWORD ret; + wchar_t *wide_path; + wchar_t *wide_fullpath; + char *fullpath; + + wide_path = win32_utf8_to_wide(path); + if (wide_path == NULL) { + return NULL; + } + + len = GetFullPathNameW(wide_path, 0, NULL, NULL); + if (len == 0) { + flb_free(wide_path); + return NULL; + } + + wide_fullpath = flb_calloc(len, sizeof(wchar_t)); + if (wide_fullpath == NULL) { + flb_free(wide_path); + return NULL; + } + + ret = GetFullPathNameW(wide_path, len, wide_fullpath, NULL); + flb_free(wide_path); + + if (ret == 0 || ret >= len) { + flb_free(wide_fullpath); + return NULL; + } + + fullpath = win32_wide_to_utf8(wide_fullpath); + flb_free(wide_fullpath); + + return fullpath; +} diff --git a/plugins/in_tail/win32/stat.c b/plugins/in_tail/win32/stat.c index ce6b9ffd457..e2b2728c1f3 100644 --- a/plugins/in_tail/win32/stat.c +++ b/plugins/in_tail/win32/stat.c @@ -17,10 +17,17 @@ * limitations under the License. */ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + #include #include #include #include + +#include + #include "interface.h" /* @@ -215,6 +222,41 @@ static int is_symlink(const char *path) return 0; } +static int is_symlink_utf8(const char *path) +{ + WIN32_FIND_DATAW data; + HANDLE h; + wchar_t *wide_path; + + SetLastError(0); + reset_errno(); + + wide_path = win32_utf8_to_wide(path); + if (wide_path == NULL) { + errno = EINVAL; + return 0; + } + + h = FindFirstFileW(wide_path, &data); + flb_free(wide_path); + + if (h == INVALID_HANDLE_VALUE) { + propagate_last_error_to_errno(); + + return 0; + } + + FindClose(h); + + if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + if (data.dwReserved0 == IO_REPARSE_TAG_SYMLINK) { + return 1; + } + } + + return 0; +} + static int hstat(HANDLE h, struct win32_stat *wst) { BY_HANDLE_FILE_INFORMATION info; @@ -279,6 +321,44 @@ int win32_stat(const char *path, struct win32_stat *wst) return 0; } +int win32_stat_utf8(const char *path, struct win32_stat *wst) +{ + HANDLE h; + wchar_t *wide_path; + + SetLastError(0); + reset_errno(); + + wide_path = win32_utf8_to_wide(path); + if (wide_path == NULL) { + errno = EINVAL; + return -1; + } + + h = CreateFileW(wide_path, + GENERIC_READ, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + NULL, /* lpSecurityAttributes */ + OPEN_EXISTING, /* dwCreationDisposition */ + 0, /* dwFlagsAndAttributes */ + NULL); /* hTemplateFile */ + flb_free(wide_path); + + if (h == INVALID_HANDLE_VALUE) { + propagate_last_error_to_errno(); + + return -1; + } + + if (hstat(h, wst)) { + CloseHandle(h); + return -1; + } + + CloseHandle(h); + return 0; +} + int win32_lstat(const char *path, struct win32_stat *wst) { HANDLE h; @@ -313,6 +393,48 @@ int win32_lstat(const char *path, struct win32_stat *wst) return 0; } +int win32_lstat_utf8(const char *path, struct win32_stat *wst) +{ + HANDLE h; + wchar_t *wide_path; + + SetLastError(0); + reset_errno(); + + wide_path = win32_utf8_to_wide(path); + if (wide_path == NULL) { + errno = EINVAL; + return -1; + } + + h = CreateFileW(wide_path, + GENERIC_READ, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + NULL, /* lpSecurityAttributes */ + OPEN_EXISTING, /* dwCreationDisposition */ + FILE_FLAG_OPEN_REPARSE_POINT, + NULL); /* hTemplateFile */ + flb_free(wide_path); + + if (h == INVALID_HANDLE_VALUE) { + propagate_last_error_to_errno(); + + return -1; + } + + if (hstat(h, wst)) { + CloseHandle(h); + return -1; + } + + if (is_symlink_utf8(path)) { + wst->st_mode = WIN32_S_IFLNK; + } + + CloseHandle(h); + return 0; +} + int win32_fstat(int fd, struct win32_stat *wst) { HANDLE h; From 6bb7f5f04714f8695cae859b6a43f809898c6925 Mon Sep 17 00:00:00 2001 From: Hiroshi Hatake Date: Tue, 30 Jun 2026 18:08:52 +0900 Subject: [PATCH 2/7] tests: integration: Add a case of conformation to tail Unicode paths Signed-off-by: Hiroshi Hatake --- .../in_tail/tests/test_in_tail_001.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py b/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py index f8b1b58329d..7e2ae855c48 100644 --- a/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py +++ b/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py @@ -304,6 +304,84 @@ def test_in_tail_discovers_new_files_from_head(workspace): service.stop() +def write_windows_utf8_path_config(path, tail_path, db_path): + path.write_text( + f"""service: + flush: 1 + log_level: debug + http_server: on + http_port: ${{FLUENT_BIT_HTTP_MONITORING_PORT}} + +pipeline: + inputs: + - name: tail + tag: tail.integration + path: {tail_path} + windows.path_encoding: utf-8 + read_from_head: true + read_newly_discovered_files_from_head: true + refresh_interval: 1 + watcher_interval: 1 + progress_check_interval: 1 + rotate_wait: 6 + db: {db_path} + db.sync: full + db.journal_mode: DELETE + db.compare_filename: true + path_key: file + offset_key: offset + + outputs: + - name: http + match: tail.integration + host: 127.0.0.1 + port: ${{TEST_SUITE_HTTP_PORT}} + uri: /data + format: json + json_date_key: false +""", + encoding="utf-8", + ) + + +@pytest.mark.skipif(sys.platform != "win32", reason="Windows-only path API mode") +def test_in_tail_windows_utf8_path_encoding_discovers_unicode_file(workspace): + log_dir = workspace / "utf8-\u30c6\u30b9\u30c8" + db_path = workspace / "tail.db" + config_path = workspace / "tail_windows_utf8_path.yaml" + log_dir.mkdir() + + log_file = log_dir / "\u65e5\u672c\u8a9e.log" + log_file.write_text("utf8-path-1\n", encoding="utf-8") + write_windows_utf8_path_config(config_path, log_dir / "*.log", db_path) + + service = Service( + config_path, + tail_path=log_dir / "*.log", + db_path=db_path, + ) + + try: + service.start() + records = service.wait_for_records(1) + finally: + service.stop() + + assert_log_set(records[:1], ["utf8-path-1"]) + assert records[0]["file"] == str(log_file) + + db = sqlite3.connect(db_path) + try: + row = db.execute( + "SELECT name FROM in_tail_files WHERE name = ?", + (str(log_file),), + ).fetchone() + finally: + db.close() + + assert row == (str(log_file),) + + def test_in_tail_newly_discovered_files_can_start_from_tail(workspace): log_dir = workspace / "new-tail" log_dir.mkdir() From 94eb29474890d0fe7f09a70398f37cad69205e01 Mon Sep 17 00:00:00 2001 From: Hiroshi Hatake Date: Tue, 30 Jun 2026 18:16:09 +0900 Subject: [PATCH 3/7] tests: integration: Skip cases for heavily depending on POSIX features Signed-off-by: Hiroshi Hatake --- .../in_tail/tests/test_in_tail_001.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py b/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py index 7e2ae855c48..ec3cd9c1ed7 100644 --- a/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py +++ b/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py @@ -14,6 +14,11 @@ from server.http_server import data_storage, http_server_run from utils.test_service import FluentBitTestService +skip_on_windows = pytest.mark.skipif( + sys.platform == "win32", + reason="scenario relies on POSIX rotation, symlink, permissions, or inode semantics", +) + def _timezone_available(iana_zone): if sys.platform == "win32": @@ -434,6 +439,7 @@ def test_in_tail_existing_file_can_start_from_tail_on_startup(workspace): assert records[0]["offset"] > 0 +@skip_on_windows def test_in_tail_follows_rename_rotation(workspace): active_log = workspace / "app.log" db_path = workspace / "tail.db" @@ -471,6 +477,7 @@ def test_in_tail_follows_rename_rotation(workspace): service.stop() +@skip_on_windows def test_in_tail_handles_multiple_rename_rotations(workspace): active_log = workspace / "multi-rotate.log" db_path = workspace / "tail.db" @@ -552,6 +559,7 @@ def test_in_tail_handles_copytruncate_with_stale_writer(workspace): service.stop() +@skip_on_windows def test_in_tail_handles_symlink_target_rotation(workspace): target_one = workspace / "target-one.log" target_two = workspace / "target-two.log" @@ -595,6 +603,7 @@ def test_in_tail_handles_symlink_target_rotation(workspace): service.stop() +@skip_on_windows def test_in_tail_stat_backend_covers_nfs_style_polling(workspace): active_log = workspace / "nfs-style.log" db_path = workspace / "tail.db" @@ -628,6 +637,7 @@ def test_in_tail_stat_backend_covers_nfs_style_polling(workspace): service.stop() +@skip_on_windows def test_in_tail_db_compare_filename_replays_renamed_file_after_restart(workspace): source_log = workspace / "db-source.log" moved_log = workspace / "db-moved.log" @@ -799,6 +809,7 @@ def test_in_tail_truncate_long_lines_emits_truncated_record_and_continues(worksp assert len(truncated[0]) > 0 +@skip_on_windows def test_in_tail_rotate_wait_keeps_old_inode_then_purges_it(workspace): active_log = workspace / "rotate-wait.log" db_path = workspace / "tail.db" @@ -833,6 +844,7 @@ def test_in_tail_rotate_wait_keeps_old_inode_then_purges_it(workspace): service.stop() +@skip_on_windows def test_in_tail_delete_and_recreate_same_path_is_reingested(workspace): active_log = workspace / "recreate.log" db_path = workspace / "tail.db" @@ -859,6 +871,7 @@ def test_in_tail_delete_and_recreate_same_path_is_reingested(workspace): service.stop() +@skip_on_windows def test_in_tail_restart_resumes_from_db_offset(workspace): log_file = workspace / "resume.log" db_path = workspace / "tail.db" @@ -884,6 +897,7 @@ def test_in_tail_restart_resumes_from_db_offset(workspace): second_run.stop() +@skip_on_windows def test_in_tail_copytruncate_across_restart_reads_new_content_only(workspace): log_file = workspace / "restart-copytruncate.log" archived = workspace / "restart-copytruncate.log.1" @@ -912,6 +926,7 @@ def test_in_tail_copytruncate_across_restart_reads_new_content_only(workspace): second_run.stop() +@skip_on_windows def test_in_tail_partial_line_across_restart_is_completed_once(workspace): log_file = workspace / "partial-restart.log" db_path = workspace / "tail.db" @@ -937,6 +952,7 @@ def test_in_tail_partial_line_across_restart_is_completed_once(workspace): assert_log_set(records, ["partial line"]) +@skip_on_windows def test_in_tail_db_schema_upgrade_is_automatic(workspace): log_file = workspace / "schema-upgrade.log" db_path = workspace / "tail.db" @@ -998,6 +1014,7 @@ def test_in_tail_db_schema_upgrade_is_automatic(workspace): assert "offset_marker_size" in columns +@skip_on_windows def test_in_tail_multi_file_rapid_rotation(workspace): log_dir = workspace / "rapid" log_dir.mkdir() @@ -1090,6 +1107,7 @@ def test_in_tail_exclude_path_skips_matching_files(workspace): assert records[0]["file"] == str(keep_file) +@skip_on_windows def test_in_tail_ignore_older_skips_stale_files(workspace): stale_file = workspace / "stale.log" db_path = workspace / "tail.db" @@ -1107,6 +1125,7 @@ def test_in_tail_ignore_older_skips_stale_files(workspace): service.stop() +@skip_on_windows def test_in_tail_ignore_active_older_files_stops_following_aged_file(workspace): log_file = workspace / "active-aged.log" db_path = workspace / "tail.db" @@ -1184,6 +1203,7 @@ def test_in_tail_generic_encoding_shiftjis(workspace): assert_log_set(records, [expected_text]) +@skip_on_windows def test_in_tail_discovers_file_after_permissions_are_restored(workspace): log_dir = workspace / "permissions" log_dir.mkdir() From de1e3b214a49f0bf486f631021a7a578a36d3fb6 Mon Sep 17 00:00:00 2001 From: Hiroshi Hatake Date: Tue, 30 Jun 2026 18:36:11 +0900 Subject: [PATCH 4/7] in_tail: Make more rigid handlings for Unicode paths Signed-off-by: Hiroshi Hatake --- plugins/in_tail/tail_file.c | 4 +-- plugins/in_tail/tail_scan_win32.c | 58 +++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/plugins/in_tail/tail_file.c b/plugins/in_tail/tail_file.c index 502d2f220f1..5d0c1173cc1 100644 --- a/plugins/in_tail/tail_file.c +++ b/plugins/in_tail/tail_file.c @@ -2366,8 +2366,8 @@ char *flb_tail_file_name(struct flb_tail_file *file) return NULL; } - if (wcsstr(wide_buf, L"\\\\?\\")) { - memmove(wide_buf, wide_buf + 4, (len + 1) * sizeof(wchar_t)); + if (wcsncmp(wide_buf, L"\\\\?\\", 4) == 0) { + memmove(wide_buf, wide_buf + 4, (len - 3) * sizeof(wchar_t)); } flb_free(buf); diff --git a/plugins/in_tail/tail_scan_win32.c b/plugins/in_tail/tail_scan_win32.c index bef85410d33..b0f08476611 100644 --- a/plugins/in_tail/tail_scan_win32.c +++ b/plugins/in_tail/tail_scan_win32.c @@ -220,21 +220,26 @@ static int tail_register_file(const char *target, struct flb_tail_config *ctx, static int tail_scan_pattern(const char *path, struct flb_tail_config *ctx) { char *star, *p0, *p1; - char pattern[MAX_PATH]; - char buf[MAX_PATH]; + char *pattern; + char *buf; int ret; int n_added = 0; time_t now; int64_t mtime; + size_t prefix_len; + size_t pattern_len; + size_t candidate_len; HANDLE h; WIN32_FIND_DATA data; WIN32_FIND_DATAW data_w; wchar_t *wide_pattern; char *filename; - if (strlen(path) > MAX_PATH - 1) { - flb_plg_error(ctx->ins, "path too long '%s'", path); - return -1; + if (ctx->windows_path_encoding != FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8) { + if (strlen(path) > MAX_PATH - 1) { + flb_plg_error(ctx->ins, "path too long '%s'", path); + return -1; + } } star = strchr(path, '*'); @@ -260,8 +265,15 @@ static int tail_scan_pattern(const char *path, struct flb_tail_config *ctx) p1++; } - memcpy(pattern, path, (p1 - path)); - pattern[p1 - path] = '\0'; + pattern_len = p1 - path; + pattern = flb_malloc(pattern_len + 1); + if (pattern == NULL) { + flb_errno(); + return -1; + } + + memcpy(pattern, path, pattern_len); + pattern[pattern_len] = '\0'; #ifdef FLB_SYSTEM_WINDOWS wide_pattern = NULL; @@ -269,6 +281,7 @@ static int tail_scan_pattern(const char *path, struct flb_tail_config *ctx) wide_pattern = win32_utf8_to_wide(pattern); if (wide_pattern == NULL) { flb_plg_error(ctx->ins, "invalid UTF-8 path pattern '%s'", pattern); + flb_free(pattern); return -1; } @@ -282,9 +295,12 @@ static int tail_scan_pattern(const char *path, struct flb_tail_config *ctx) h = FindFirstFileA(pattern, &data); #endif if (h == INVALID_HANDLE_VALUE) { + flb_free(pattern); return 0; /* none matched */ } + flb_free(pattern); + now = time(NULL); do { filename = data.cFileName; @@ -307,22 +323,35 @@ static int tail_scan_pattern(const char *path, struct flb_tail_config *ctx) goto next; } - /* Create a path (prefix + filename + suffix) */ - memcpy(buf, path, p0 - path + 1); - buf[p0 - path + 1] = '\0'; + prefix_len = p0 - path + 1; + candidate_len = prefix_len + strlen(filename) + strlen(p1); + + if (ctx->windows_path_encoding != FLB_TAIL_WINDOWS_PATH_ENCODING_UTF8) { + if (candidate_len > MAX_PATH - 1) { + flb_plg_warn(ctx->ins, "'%.*s%s%s' is too long", + (int) prefix_len, path, filename, p1); + goto next; + } + } - if (strlen(buf) + strlen(filename) + strlen(p1) > MAX_PATH - 1) { - flb_plg_warn(ctx->ins, "'%s%s%s' is too long", buf, filename, p1); + buf = flb_malloc(candidate_len + 1); + if (buf == NULL) { + flb_errno(); goto next; } - strcat(buf, filename); - strcat(buf, p1); + + /* Create a path (prefix + filename + suffix) */ + memcpy(buf, path, prefix_len); + memcpy(buf + prefix_len, filename, strlen(filename)); + memcpy(buf + prefix_len + strlen(filename), p1, strlen(p1)); + buf[candidate_len] = '\0'; if (strchr(p1, '*')) { ret = tail_scan_pattern(buf, ctx); /* recursive */ if (ret >= 0) { n_added += ret; } + flb_free(buf); goto next; } @@ -331,6 +360,7 @@ static int tail_scan_pattern(const char *path, struct flb_tail_config *ctx) if (ret == 0) { n_added++; } + flb_free(buf); next: #ifdef FLB_SYSTEM_WINDOWS From 6a8b6bb07372a56346144e2a5fc8746dd121adba Mon Sep 17 00:00:00 2001 From: Hiroshi Hatake Date: Tue, 30 Jun 2026 18:36:52 +0900 Subject: [PATCH 5/7] tests: integration: verify behavior of an Unicode encoded long length path Signed-off-by: Hiroshi Hatake --- .../scenarios/in_tail/tests/test_in_tail_001.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py b/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py index ec3cd9c1ed7..e348c2f60d3 100644 --- a/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py +++ b/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py @@ -357,7 +357,9 @@ def test_in_tail_windows_utf8_path_encoding_discovers_unicode_file(workspace): log_dir.mkdir() log_file = log_dir / "\u65e5\u672c\u8a9e.log" + long_log_file = log_dir / (("\u754c" * 70) + ".log") log_file.write_text("utf8-path-1\n", encoding="utf-8") + long_log_file.write_text("utf8-path-2\n", encoding="utf-8") write_windows_utf8_path_config(config_path, log_dir / "*.log", db_path) service = Service( @@ -368,23 +370,24 @@ def test_in_tail_windows_utf8_path_encoding_discovers_unicode_file(workspace): try: service.start() - records = service.wait_for_records(1) + records = service.wait_for_records(2) finally: service.stop() - assert_log_set(records[:1], ["utf8-path-1"]) - assert records[0]["file"] == str(log_file) + assert_log_set(records[:2], ["utf8-path-1", "utf8-path-2"]) + record_paths = {record["file"] for record in records[:2]} + assert record_paths == {str(log_file), str(long_log_file)} db = sqlite3.connect(db_path) try: row = db.execute( - "SELECT name FROM in_tail_files WHERE name = ?", - (str(log_file),), - ).fetchone() + "SELECT name FROM in_tail_files WHERE name IN (?, ?)", + (str(log_file), str(long_log_file)), + ).fetchall() finally: db.close() - assert row == (str(log_file),) + assert {item[0] for item in row} == {str(log_file), str(long_log_file)} def test_in_tail_newly_discovered_files_can_start_from_tail(workspace): From 8fad79ff95ee28af75c3a176575a24ced4d70031 Mon Sep 17 00:00:00 2001 From: Hiroshi Hatake Date: Tue, 30 Jun 2026 18:38:56 +0900 Subject: [PATCH 6/7] in_tail: Propagate WIN32 errors Signed-off-by: Hiroshi Hatake --- plugins/in_tail/win32/interface.h | 1 + plugins/in_tail/win32/io.c | 7 ++++++- plugins/in_tail/win32/stat.c | 20 ++++++++++---------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/plugins/in_tail/win32/interface.h b/plugins/in_tail/win32/interface.h index c7e97fa7ed0..f8c59c3f21d 100644 --- a/plugins/in_tail/win32/interface.h +++ b/plugins/in_tail/win32/interface.h @@ -39,6 +39,7 @@ int win32_lstat_utf8(const char *path, struct win32_stat *wst); int win32_open(const char *path, int flags); int win32_open_utf8(const char *path, int flags); +void win32_propagate_last_error_to_errno(void); wchar_t *win32_utf8_to_wide(const char *str); char *win32_wide_to_utf8(const wchar_t *str); char *win32_fullpath_utf8(const char *path); diff --git a/plugins/in_tail/win32/io.c b/plugins/in_tail/win32/io.c index 6ba7152f0cb..2593d98bd2c 100644 --- a/plugins/in_tail/win32/io.c +++ b/plugins/in_tail/win32/io.c @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -60,6 +61,7 @@ int win32_open_utf8(const char *path, int flags) wide_path = win32_utf8_to_wide(path); if (wide_path == NULL) { + errno = EINVAL; return -1; } @@ -70,10 +72,13 @@ int win32_open_utf8(const char *path, int flags) OPEN_EXISTING, /* dwCreationDisposition */ 0, /* dwFlagsAndAttributes */ NULL); /* hTemplateFile */ - flb_free(wide_path); if (h == INVALID_HANDLE_VALUE) { + win32_propagate_last_error_to_errno(); + flb_free(wide_path); return -1; } + + flb_free(wide_path); return _open_osfhandle((intptr_t) h, _O_RDONLY); } diff --git a/plugins/in_tail/win32/stat.c b/plugins/in_tail/win32/stat.c index e2b2728c1f3..aa7ce731e28 100644 --- a/plugins/in_tail/win32/stat.c +++ b/plugins/in_tail/win32/stat.c @@ -80,7 +80,7 @@ static void reset_errno() errno = 0; } -static void propagate_last_error_to_errno() +void win32_propagate_last_error_to_errno(void) { DWORD error_code; @@ -201,7 +201,7 @@ static int is_symlink(const char *path) h = FindFirstFileA(path, &data); if (h == INVALID_HANDLE_VALUE) { - propagate_last_error_to_errno(); + win32_propagate_last_error_to_errno(); return 0; } @@ -241,7 +241,7 @@ static int is_symlink_utf8(const char *path) flb_free(wide_path); if (h == INVALID_HANDLE_VALUE) { - propagate_last_error_to_errno(); + win32_propagate_last_error_to_errno(); return 0; } @@ -266,14 +266,14 @@ static int hstat(HANDLE h, struct win32_stat *wst) reset_errno(); if (!GetFileInformationByHandle(h, &info)) { - propagate_last_error_to_errno(); + win32_propagate_last_error_to_errno(); return -1; } if (!GetFileInformationByHandleEx(h, FileStandardInfo, &std, sizeof(std))) { - propagate_last_error_to_errno(); + win32_propagate_last_error_to_errno(); return -1; } @@ -307,7 +307,7 @@ int win32_stat(const char *path, struct win32_stat *wst) NULL); /* hTemplateFile */ if (h == INVALID_HANDLE_VALUE) { - propagate_last_error_to_errno(); + win32_propagate_last_error_to_errno(); return -1; } @@ -345,7 +345,7 @@ int win32_stat_utf8(const char *path, struct win32_stat *wst) flb_free(wide_path); if (h == INVALID_HANDLE_VALUE) { - propagate_last_error_to_errno(); + win32_propagate_last_error_to_errno(); return -1; } @@ -375,7 +375,7 @@ int win32_lstat(const char *path, struct win32_stat *wst) NULL); /* hTemplateFile */ if (h == INVALID_HANDLE_VALUE) { - propagate_last_error_to_errno(); + win32_propagate_last_error_to_errno(); return -1; } @@ -417,7 +417,7 @@ int win32_lstat_utf8(const char *path, struct win32_stat *wst) flb_free(wide_path); if (h == INVALID_HANDLE_VALUE) { - propagate_last_error_to_errno(); + win32_propagate_last_error_to_errno(); return -1; } @@ -445,7 +445,7 @@ int win32_fstat(int fd, struct win32_stat *wst) h = (HANDLE) _get_osfhandle(fd); if (h == INVALID_HANDLE_VALUE) { - propagate_last_error_to_errno(); + win32_propagate_last_error_to_errno(); return -1; } From 0a50efa922afd785b9b631382dcc5ef2af397a6d Mon Sep 17 00:00:00 2001 From: Hiroshi Hatake Date: Tue, 30 Jun 2026 18:39:16 +0900 Subject: [PATCH 7/7] tests: integration: Use emojis to make severe conditions for test Signed-off-by: Hiroshi Hatake --- .../integration/scenarios/in_tail/tests/test_in_tail_001.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py b/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py index e348c2f60d3..32aaa702cde 100644 --- a/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py +++ b/tests/integration/scenarios/in_tail/tests/test_in_tail_001.py @@ -351,13 +351,13 @@ def write_windows_utf8_path_config(path, tail_path, db_path): @pytest.mark.skipif(sys.platform != "win32", reason="Windows-only path API mode") def test_in_tail_windows_utf8_path_encoding_discovers_unicode_file(workspace): - log_dir = workspace / "utf8-\u30c6\u30b9\u30c8" + log_dir = workspace / "utf8-\U0001f600" db_path = workspace / "tail.db" config_path = workspace / "tail_windows_utf8_path.yaml" log_dir.mkdir() - log_file = log_dir / "\u65e5\u672c\u8a9e.log" - long_log_file = log_dir / (("\u754c" * 70) + ".log") + log_file = log_dir / "unicode-\U0001f600.log" + long_log_file = log_dir / (("\U0001f600" * 70) + ".log") log_file.write_text("utf8-path-1\n", encoding="utf-8") long_log_file.write_text("utf8-path-2\n", encoding="utf-8") write_windows_utf8_path_config(config_path, log_dir / "*.log", db_path)