diff --git a/cpp/src/file/local_random_access_read_file.cc b/cpp/src/file/local_random_access_read_file.cc index d617202a0..cc42d4161 100644 --- a/cpp/src/file/local_random_access_read_file.cc +++ b/cpp/src/file/local_random_access_read_file.cc @@ -175,6 +175,23 @@ 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. + // 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)) { + 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"); }