From 08d61c39418a85e590e3cae0cee6a42f7a07eabc Mon Sep 17 00:00:00 2001 From: ColinLee Date: Wed, 30 Sep 2026 11:55:37 +0800 Subject: [PATCH 1/2] fix(cpp): reject special input files before open to avoid FIFO hang Opening a FIFO (named pipe) read-only blocks until a writer appears, so tsfile-cli hung forever on a FIFO input instead of failing with input problem 2 (code 37). Preflight the path with stat() on POSIX before open so non-regular files (FIFO/device/socket/directory) fail fast with E_INVALID_PATH, matching the existing post-open fstat() check and the Windows preflight. Add a FIFO regression case to the read-command input-failure e2e test. --- cpp/src/file/local_random_access_read_file.cc | 14 ++++++++++++++ cpp/test/tools/model_format_e2e_test.cc | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/cpp/src/file/local_random_access_read_file.cc b/cpp/src/file/local_random_access_read_file.cc index d617202a0..f1dfd08b6 100644 --- a/cpp/src/file/local_random_access_read_file.cc +++ b/cpp/src/file/local_random_access_read_file.cc @@ -175,6 +175,20 @@ int LocalRandomAccessReadFile::open(const std::string& file_path) { (preopen_stat.st_mode & _S_IFMT) != _S_IFREG) { return E_INVALID_PATH; } +#endif +#ifndef _WIN32 + // POSIX open(O_RDONLY) blocks on a FIFO until a writer appears, and + // succeeds on directories/device files. Preflight with stat() (which + // follows symlinks) so non-regular paths fail fast with the same stable + // E_INVALID_PATH code the post-open fstat() check below produces, instead + // of hanging forever. If stat() itself fails (missing file, dangling + // symlink), fall through to the normal open() path so those continue to + // report E_FILE_OPEN_ERR. + struct stat preopen_stat; + if (::stat(file_path_.c_str(), &preopen_stat) == 0 && + !S_ISREG(preopen_stat.st_mode)) { + return E_INVALID_PATH; + } #endif fd_ = file_internal::open_utf8(file_path_, flags); if (fd_ < 0) { diff --git a/cpp/test/tools/model_format_e2e_test.cc b/cpp/test/tools/model_format_e2e_test.cc index a9acc0354..11d20d879 100644 --- a/cpp/test/tools/model_format_e2e_test.cc +++ b/cpp/test/tools/model_format_e2e_test.cc @@ -493,6 +493,24 @@ TEST(IndependentFixtures, EmptyTreeAndInputFailuresHaveExactDiagnostics) { expect_cli_exact(input_args(command, "."), 2, "", "Error: cannot open .: invalid path (code 37)\n"); } +#ifndef _WIN32 + // A FIFO (named pipe) is a special file, not a TsFile. POSIX + // open(O_RDONLY) would otherwise block until a writer appears, so the + // reader must preflight the file type and fail fast with the same stable + // diagnostic instead of hanging. + { + const std::string fifo = tsfile_cli_test::unique_temp_path( + "tsfile_cli_fifo_input", ".tsfile"); + ASSERT_EQ(mkfifo(fifo.c_str(), 0600), 0); + for (const std::string& command : commands) { + std::remove("tsfile_cli_input_error.csv"); + expect_cli_exact( + input_args(command, fifo), 2, "", + "Error: cannot open " + fifo + ": invalid path (code 37)\n"); + } + std::remove(fifo.c_str()); + } +#endif std::remove("tsfile_cli_input_error.csv"); } From 0ad263a36c6304cd87bda14ca32fe64e92ad03d5 Mon Sep 17 00:00:00 2001 From: Colin Lee <105656576+ColinLeeo@users.noreply.github.com> Date: Mon, 5 Oct 2026 15:53:45 +0800 Subject: [PATCH 2/2] Fix race condition with O_NONBLOCK flag handling Added handling for O_NONBLOCK flag to close the stat/open race condition. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- cpp/src/file/local_random_access_read_file.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cpp/src/file/local_random_access_read_file.cc b/cpp/src/file/local_random_access_read_file.cc index f1dfd08b6..cc42d4161 100644 --- a/cpp/src/file/local_random_access_read_file.cc +++ b/cpp/src/file/local_random_access_read_file.cc @@ -184,6 +184,9 @@ int LocalRandomAccessReadFile::open(const std::string& file_path) { // of hanging forever. If stat() itself fails (missing file, dangling // symlink), fall through to the normal open() path so those continue to // report E_FILE_OPEN_ERR. + // Close the stat/open race: regular files ignore O_NONBLOCK, while a FIFO + // replacement cannot block before the descriptor check below. + flags |= O_NONBLOCK; struct stat preopen_stat; if (::stat(file_path_.c_str(), &preopen_stat) == 0 && !S_ISREG(preopen_stat.st_mode)) {