From cef4676d3ad3b63ad097025396422afbcaead1c3 Mon Sep 17 00:00:00 2001 From: Brad King Date: Tue, 6 May 2025 11:28:52 -0400 Subject: [PATCH] StdIo: Factor out helper to initialize stdin, stdout, and stderr Move logic from commit c85524a94a (Ensure stdin, stdout, and stderr pipes are always open, 2019-05-02, v3.15.0-rc1~171^2) and commit 96010cc968 (Ensure stdin, stdout, stderr FILE streams are open on Windows, 2024-01-24, v3.29.0-rc1~65^2) to a dedicated source. Expose it through an `Init` class constructor to make it optionally available during static initialization. Issue: #26924 --- Source/CMakeLists.txt | 2 + Source/CPack/cpack.cxx | 3 +- Source/CursesDialog/ccmake.cxx | 4 +- Source/QtDialog/CMakeSetup.cxx | 4 +- Source/cmStdIoInit.cxx | 102 +++++++++++++++++++++++++++++++++ Source/cmStdIoInit.h | 27 +++++++++ Source/cmSystemTools.cxx | 52 ----------------- Source/cmSystemTools.h | 2 - Source/cmakemain.cxx | 3 +- Source/ctest.cxx | 3 +- Tests/CMakeLib/CMakeLists.txt | 1 + Tests/CMakeLib/testStdIo.cxx | 15 +++++ bootstrap | 1 + 13 files changed, 160 insertions(+), 59 deletions(-) create mode 100644 Source/cmStdIoInit.cxx create mode 100644 Source/cmStdIoInit.h create mode 100644 Tests/CMakeLib/testStdIo.cxx diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 07e8eab725..d5e267ba69 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -468,6 +468,8 @@ add_library( cmStateSnapshot.cxx cmStateSnapshot.h cmStateTypes.h + cmStdIoInit.h + cmStdIoInit.cxx cmStringAlgorithms.cxx cmStringAlgorithms.h cmSyntheticTargetCache.h diff --git a/Source/CPack/cpack.cxx b/Source/CPack/cpack.cxx index a4046119fe..3940563b91 100644 --- a/Source/CPack/cpack.cxx +++ b/Source/CPack/cpack.cxx @@ -33,6 +33,7 @@ #include "cmMakefile.h" #include "cmState.h" #include "cmStateSnapshot.h" +#include "cmStdIoInit.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmValue.h" @@ -89,7 +90,7 @@ std::vector makeGeneratorDocs( // this is CPack. int main(int argc, char const* const* argv) { - cmSystemTools::EnsureStdPipes(); + cm::StdIo::Init(); // Replace streambuf so we can output Unicode to console cmConsoleBuf consoleBuf; diff --git a/Source/CursesDialog/ccmake.cxx b/Source/CursesDialog/ccmake.cxx index 8385b134f2..6de688e07a 100644 --- a/Source/CursesDialog/ccmake.cxx +++ b/Source/CursesDialog/ccmake.cxx @@ -19,6 +19,7 @@ #include "cmDocumentationEntry.h" #include "cmMessageMetadata.h" #include "cmState.h" +#include "cmStdIoInit.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmake.h" @@ -62,7 +63,8 @@ cmCursesForm* cmCursesForm::CurrentForm = nullptr; int main(int argc, char const* const* argv) { - cmSystemTools::EnsureStdPipes(); + cm::StdIo::Init(); + cmsys::Encoding::CommandLineArguments encoding_args = cmsys::Encoding::CommandLineArguments::Main(argc, argv); argc = encoding_args.argc(); diff --git a/Source/QtDialog/CMakeSetup.cxx b/Source/QtDialog/CMakeSetup.cxx index 234b587325..cf324612cc 100644 --- a/Source/QtDialog/CMakeSetup.cxx +++ b/Source/QtDialog/CMakeSetup.cxx @@ -18,6 +18,7 @@ #include "cmAlgorithms.h" #include "cmDocumentation.h" #include "cmDocumentationEntry.h" +#include "cmStdIoInit.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" // IWYU pragma: keep #include "cmake.h" @@ -66,7 +67,8 @@ void OpenReferenceManual(QString const& filename); int main(int argc, char** argv) { - cmSystemTools::EnsureStdPipes(); + cm::StdIo::Init(); + cmsys::Encoding::CommandLineArguments encoding_args = cmsys::Encoding::CommandLineArguments::Main(argc, argv); int argc2 = encoding_args.argc(); diff --git a/Source/cmStdIoInit.cxx b/Source/cmStdIoInit.cxx new file mode 100644 index 0000000000..244f384b66 --- /dev/null +++ b/Source/cmStdIoInit.cxx @@ -0,0 +1,102 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ +#include "cmStdIoInit.h" + +#include +#include +#include + +#include + +#ifdef _WIN32 +# include + +# include // for _close, _dup2, _get_osfhandle + +# include "cm_fileno.hxx" +#else +# include +#endif + +namespace cm { +namespace StdIo { + +namespace { + +#ifdef _WIN32 +void InitStdPipe(int stdFd, DWORD nStdHandle, FILE* stream, + wchar_t const* mode) +{ + if (cm_fileno(stream) >= 0) { + return; + } + _close(stdFd); + _wfreopen(L"NUL", mode, stream); + int fd = cm_fileno(stream); + if (fd < 0) { + perror("failed to open NUL for missing stdio pipe"); + abort(); + } + if (fd != stdFd) { + _dup2(fd, stdFd); + } + SetStdHandle(nStdHandle, reinterpret_cast(_get_osfhandle(fd))); +} +#else +void InitStdPipe(int fd) +{ + if (fcntl(fd, F_GETFD) != -1 || errno != EBADF) { + return; + } + + int f = open("/dev/null", fd == STDIN_FILENO ? O_RDONLY : O_WRONLY); + if (f == -1) { + perror("failed to open /dev/null for missing stdio pipe"); + abort(); + } + if (f != fd) { + dup2(f, fd); + close(f); + } +} +#endif + +struct InitStdPipes +{ + InitStdPipes() + { +#ifdef _WIN32 + InitStdPipe(0, STD_INPUT_HANDLE, stdin, L"rb"); + InitStdPipe(1, STD_OUTPUT_HANDLE, stdout, L"wb"); + InitStdPipe(2, STD_ERROR_HANDLE, stderr, L"wb"); +#else + InitStdPipe(STDIN_FILENO); + InitStdPipe(STDOUT_FILENO); + InitStdPipe(STDERR_FILENO); +#endif + } +}; + +} // anonymous namespace + +class Globals +{ +public: + InitStdPipes InitPipes; + + static Globals& Get(); +}; + +Globals& Globals::Get() +{ + static Globals globals; + return globals; +} + +Init::Init() +{ + Globals::Get(); +} + +} +} diff --git a/Source/cmStdIoInit.h b/Source/cmStdIoInit.h new file mode 100644 index 0000000000..4ae9eb0eab --- /dev/null +++ b/Source/cmStdIoInit.h @@ -0,0 +1,27 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +namespace cm { +namespace StdIo { + +/** + * Initialize process-wide `stdin`, `stdout`, and `stderr` streams. + * After construction, standard in/out/err descriptors/handles are open, + * and standard `FILE*` streams from `` are associated with them. + */ +class Init +{ +public: + Init(); + ~Init() = default; + Init(Init&&) noexcept = default; + Init(Init const&) = delete; + Init& operator=(Init&&) noexcept = default; + Init& operator=(Init const&) = delete; +}; + +} +} diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx index af59001393..08998c18b1 100644 --- a/Source/cmSystemTools.cxx +++ b/Source/cmSystemTools.cxx @@ -2866,58 +2866,6 @@ cmSystemTools::WaitForLineResult cmSystemTools::WaitForLine( } } -#ifdef _WIN32 -static void EnsureStdPipe(int stdFd, DWORD nStdHandle, FILE* stream, - wchar_t const* mode) -{ - if (fileno(stream) >= 0) { - return; - } - _close(stdFd); - _wfreopen(L"NUL", mode, stream); - int fd = fileno(stream); - if (fd < 0) { - perror("failed to open NUL for missing stdio pipe"); - abort(); - } - if (fd != stdFd) { - _dup2(fd, stdFd); - } - SetStdHandle(nStdHandle, reinterpret_cast(_get_osfhandle(fd))); -} - -void cmSystemTools::EnsureStdPipes() -{ - EnsureStdPipe(0, STD_INPUT_HANDLE, stdin, L"rb"); - EnsureStdPipe(1, STD_OUTPUT_HANDLE, stdout, L"wb"); - EnsureStdPipe(2, STD_ERROR_HANDLE, stderr, L"wb"); -} -#else -static void EnsureStdPipe(int fd) -{ - if (fcntl(fd, F_GETFD) != -1 || errno != EBADF) { - return; - } - - int f = open("/dev/null", fd == STDIN_FILENO ? O_RDONLY : O_WRONLY); - if (f == -1) { - perror("failed to open /dev/null for missing stdio pipe"); - abort(); - } - if (f != fd) { - dup2(f, fd); - close(f); - } -} - -void cmSystemTools::EnsureStdPipes() -{ - EnsureStdPipe(STDIN_FILENO); - EnsureStdPipe(STDOUT_FILENO); - EnsureStdPipe(STDERR_FILENO); -} -#endif - #ifdef _WIN32 # ifndef CRYPT_SILENT # define CRYPT_SILENT 0x40 /* Not defined by VS 6 version of header. */ diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h index 892da76c1b..948f2dcf49 100644 --- a/Source/cmSystemTools.h +++ b/Source/cmSystemTools.h @@ -554,8 +554,6 @@ public: cmTarExtractTimestamps extractTimestamps, bool verbose); - static void EnsureStdPipes(); - /** Random number generation. */ static unsigned int RandomSeed(); static unsigned int RandomNumber(); diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx index 68101fbab4..5cb23dc4c7 100644 --- a/Source/cmakemain.cxx +++ b/Source/cmakemain.cxx @@ -35,6 +35,7 @@ #include "cmMessageMetadata.h" #include "cmState.h" #include "cmStateTypes.h" +#include "cmStdIoInit.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmValue.h" @@ -1142,7 +1143,7 @@ int do_open(int ac, char const* const* av) int main(int ac, char const* const* av) { - cmSystemTools::EnsureStdPipes(); + cm::StdIo::Init(); // Replace streambuf so we can output Unicode to console auto consoleBuf = cm::make_unique(); diff --git a/Source/ctest.cxx b/Source/ctest.cxx index d0f7a18883..5b028faaab 100644 --- a/Source/ctest.cxx +++ b/Source/ctest.cxx @@ -14,6 +14,7 @@ #include "cmDocumentationEntry.h" #include "cmInstrumentation.h" #include "cmInstrumentationQuery.h" +#include "cmStdIoInit.h" #include "cmSystemTools.h" #include "CTest/cmCTestLaunch.h" @@ -166,7 +167,7 @@ cmDocumentationEntry const cmDocumentationOptions[] = { // this is a test driver program for cmCTest. int main(int argc, char const* const* argv) { - cmSystemTools::EnsureStdPipes(); + cm::StdIo::Init(); // Replace streambuf so we can output Unicode to console cmConsoleBuf consoleBuf; diff --git a/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt index 19794b8193..3f6ae04d84 100644 --- a/Tests/CMakeLib/CMakeLists.txt +++ b/Tests/CMakeLib/CMakeLists.txt @@ -24,6 +24,7 @@ set(CMakeLib_TESTS testRange.cxx testOptional.cxx testPathResolver.cxx + testStdIo.cxx testString.cxx testStringAlgorithms.cxx testSystemTools.cxx diff --git a/Tests/CMakeLib/testStdIo.cxx b/Tests/CMakeLib/testStdIo.cxx new file mode 100644 index 0000000000..6274480797 --- /dev/null +++ b/Tests/CMakeLib/testStdIo.cxx @@ -0,0 +1,15 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ + +#include "cmStdIoInit.h" + +#include "testCommon.h" + +namespace { +} + +int testStdIo(int /*unused*/, char* /*unused*/[]) +{ + cm::StdIo::Init(); + return runTests({}); +} diff --git a/bootstrap b/bootstrap index 1e87e044a0..d377af54c7 100755 --- a/bootstrap +++ b/bootstrap @@ -489,6 +489,7 @@ CMAKE_CXX_SOURCES="\ cmState \ cmStateDirectory \ cmStateSnapshot \ + cmStdIoInit \ cmString \ cmStringAlgorithms \ cmStringReplaceHelper \