From 065f9a0ff91ac61aab7669296c61ff5397ed9848 Mon Sep 17 00:00:00 2001 From: Matej Kenda Date: Wed, 8 May 2024 11:53:43 +0200 Subject: [PATCH] feature(FileStream): Allow using Poco::FileStream to wrap arbitrary file handles/descriptors as C++ streams (#4424) * Allow using Poco::FileStream to wrap arbitrary file handles/descriptors as C++ streams (#3444). * Allow opening a file descriptor/HANDLE as C++ stream. * FileStream: treat read from closed pipe as EOF. * chore(Filestream): conde style (naming) Co-Authored-By: Alex Fabijanic Co-Authored-By: Matej Kenda * enh(FileStream): make FileIOS::open a virtual function. (#3444) * test(FileStream): unit test for FileStream::openHandle (#3444) * Update CONTRIBUTORS. * test(FileStream): Win32 unit test fix. * build(CMake): Require policy minimum version 3.15. --------- Co-authored-by: Daniel Grunwald Co-authored-by: Alex Fabijanic --- CMakeLists.txt | 1 + CONTRIBUTORS | 4 +- Foundation/include/Poco/FileStream.h | 16 +++++-- Foundation/include/Poco/FileStream_POSIX.h | 3 ++ Foundation/include/Poco/FileStream_WIN32.h | 3 ++ Foundation/src/FileStream.cpp | 27 ++++++++---- Foundation/src/FileStream_POSIX.cpp | 16 +++++++ Foundation/src/FileStream_WIN32.cpp | 24 +++++++++- Foundation/testsuite/src/FileStreamTest.cpp | 49 +++++++++++++++++++++ Foundation/testsuite/src/FileStreamTest.h | 1 + 10 files changed, 131 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 36d95502f..f4284538b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.5.0) +cmake_policy(VERSION 3.15.0) project(Poco) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index bc0497c0f..a23469b1c 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -60,9 +60,11 @@ Friedrich Wilckens Pavle Dragišić Nino Belušić Kari Argillander -Alexander B +Alexander Bychuk Andrew Auclair Jochen Sprickerhof Jesse Hoogervorst Aron Budea zhuzeitou +Daniel Grunwald + diff --git a/Foundation/include/Poco/FileStream.h b/Foundation/include/Poco/FileStream.h index abe47b163..662d63e28 100644 --- a/Foundation/include/Poco/FileStream.h +++ b/Foundation/include/Poco/FileStream.h @@ -56,6 +56,16 @@ public: ~FileIOS(); /// Destroys the stream. + virtual void open(const std::string& path, std::ios::openmode mode); + /// Opens the file specified by path, using the given mode. + /// + /// Throws a FileException (or a similar exception) if the file + /// does not exist or is not accessible for other reasons and + /// a new file cannot be created. + + void openHandle(NativeHandle handle, std::ios::openmode mode); + /// Takes ownership of the handle. + void close(); /// Closes the file stream. /// @@ -108,7 +118,7 @@ public: ~FileInputStream(); /// Destroys the stream. - void open(const std::string& path, std::ios::openmode mode = std::ios::in); + void open(const std::string& path, std::ios::openmode mode = std::ios::in) override; /// Opens the file specified by path, using the given mode, which /// will always include std::ios::in (even if not specified). /// @@ -151,7 +161,7 @@ public: ~FileOutputStream(); /// Destroys the FileOutputStream. - void open(const std::string& path, std::ios::openmode mode = std::ios::out | std::ios::trunc); + void open(const std::string& path, std::ios::openmode mode = std::ios::out | std::ios::trunc) override; /// Opens the file specified by path, using the given mode, which /// always includes std::ios::out, even if not specified. /// @@ -196,7 +206,7 @@ public: ~FileStream(); /// Destroys the FileOutputStream. - void open(const std::string& path, std::ios::openmode mode = std::ios::out | std::ios::in); + void open(const std::string& path, std::ios::openmode mode = std::ios::out | std::ios::in) override; /// Opens the file specified by path, using the given mode. /// /// Throws a FileException (or a similar exception) if the file diff --git a/Foundation/include/Poco/FileStream_POSIX.h b/Foundation/include/Poco/FileStream_POSIX.h index 5788d3f3f..0c35b0a07 100644 --- a/Foundation/include/Poco/FileStream_POSIX.h +++ b/Foundation/include/Poco/FileStream_POSIX.h @@ -42,6 +42,9 @@ public: void open(const std::string& path, std::ios::openmode mode); /// Opens the given file in the given mode. + void openHandle(NativeHandle fd, std::ios::openmode mode); + /// Take ownership of the given file descriptor. + bool close(); /// Closes the File stream buffer. Returns true if successful, /// false otherwise. diff --git a/Foundation/include/Poco/FileStream_WIN32.h b/Foundation/include/Poco/FileStream_WIN32.h index 09f787c9b..97cc6fe45 100644 --- a/Foundation/include/Poco/FileStream_WIN32.h +++ b/Foundation/include/Poco/FileStream_WIN32.h @@ -41,6 +41,9 @@ public: void open(const std::string& path, std::ios::openmode mode); /// Opens the given file in the given mode. + void openHandle(NativeHandle handle, std::ios::openmode mode); + /// Take ownership of the given HANDLE. + bool close(); /// Closes the File stream buffer. Returns true if successful, /// false otherwise. diff --git a/Foundation/src/FileStream.cpp b/Foundation/src/FileStream.cpp index 54dd62c3f..f9bc006fd 100644 --- a/Foundation/src/FileStream.cpp +++ b/Foundation/src/FileStream.cpp @@ -35,6 +35,20 @@ FileIOS::~FileIOS() } +void FileIOS::open(const std::string& path, std::ios::openmode mode) +{ + clear(); + _buf.open(path, mode); +} + + +void FileIOS::openHandle(NativeHandle handle, std::ios::openmode mode) +{ + clear(); + _buf.openHandle(handle, mode); +} + + void FileIOS::close() { if (!_buf.close()) @@ -77,7 +91,7 @@ FileInputStream::FileInputStream(): FileInputStream::FileInputStream(const std::string& path, std::ios::openmode mode): std::istream(&_buf) { - open(path, mode | std::ios::in); + open(path, mode); } @@ -88,8 +102,7 @@ FileInputStream::~FileInputStream() void FileInputStream::open(const std::string& path, std::ios::openmode mode) { - clear(); - _buf.open(path, mode | std::ios::in); + FileIOS::open(path, mode | std::ios::in); } @@ -102,7 +115,7 @@ FileOutputStream::FileOutputStream(): FileOutputStream::FileOutputStream(const std::string& path, std::ios::openmode mode): std::ostream(&_buf) { - open(path, mode | std::ios::out); + open(path, mode); } @@ -113,8 +126,7 @@ FileOutputStream::~FileOutputStream() void FileOutputStream::open(const std::string& path, std::ios::openmode mode) { - clear(); - _buf.open(path, mode | std::ios::out); + FileIOS::open(path, mode | std::ios::out); } @@ -138,8 +150,7 @@ FileStream::~FileStream() void FileStream::open(const std::string& path, std::ios::openmode mode) { - clear(); - _buf.open(path, mode); + FileIOS::open(path, mode); } diff --git a/Foundation/src/FileStream_POSIX.cpp b/Foundation/src/FileStream_POSIX.cpp index c62d9c6d9..2630183df 100644 --- a/Foundation/src/FileStream_POSIX.cpp +++ b/Foundation/src/FileStream_POSIX.cpp @@ -72,6 +72,22 @@ void FileStreamBuf::open(const std::string& path, std::ios::openmode mode) } +void FileStreamBuf::openHandle(NativeHandle fd, std::ios::openmode mode) +{ + poco_assert(_fd == -1); + poco_assert(fd != -1); + + _pos = 0; + setMode(mode); + resetBuffers(); + + _fd = fd; + + if ((mode & std::ios::app) || (mode & std::ios::ate)) + seekoff(0, std::ios::end, mode); +} + + int FileStreamBuf::readFromDevice(char* buffer, std::streamsize length) { if (_fd == -1) return -1; diff --git a/Foundation/src/FileStream_WIN32.cpp b/Foundation/src/FileStream_WIN32.cpp index e79c54706..b4545f432 100644 --- a/Foundation/src/FileStream_WIN32.cpp +++ b/Foundation/src/FileStream_WIN32.cpp @@ -15,7 +15,6 @@ #include "Poco/FileStream.h" #include "Poco/File.h" #include "Poco/Exception.h" -#include "Poco/UnicodeConverter.h" namespace Poco { @@ -74,6 +73,22 @@ void FileStreamBuf::open(const std::string& path, std::ios::openmode mode) } +void FileStreamBuf::openHandle(NativeHandle handle, std::ios::openmode mode) +{ + poco_assert(_handle == INVALID_HANDLE_VALUE); + poco_assert(handle != INVALID_HANDLE_VALUE); + + _pos = 0; + setMode(mode); + resetBuffers(); + + _handle = handle; + + if ((mode & std::ios::ate) || (mode & std::ios::app)) + seekoff(0, std::ios::end, mode); +} + + int FileStreamBuf::readFromDevice(char* buffer, std::streamsize length) { if (INVALID_HANDLE_VALUE == _handle || !(getMode() & std::ios::in)) @@ -85,7 +100,14 @@ int FileStreamBuf::readFromDevice(char* buffer, std::streamsize length) DWORD bytesRead(0); BOOL rc = ReadFile(_handle, buffer, static_cast(length), &bytesRead, NULL); if (rc == 0) + { + if (GetLastError() == ERROR_BROKEN_PIPE) + { + // Read from closed pipe -> treat as EOF + return 0; + } File::handleLastError(_path); + } _pos += bytesRead; diff --git a/Foundation/testsuite/src/FileStreamTest.cpp b/Foundation/testsuite/src/FileStreamTest.cpp index 4d0fe0836..afb3152e1 100644 --- a/Foundation/testsuite/src/FileStreamTest.cpp +++ b/Foundation/testsuite/src/FileStreamTest.cpp @@ -51,6 +51,54 @@ void FileStreamTest::testRead() } +#if defined(POCO_OS_FAMILY_WINDOWS) +#include "Poco/UnicodeConverter.h" +#else +#include +#endif + +void FileStreamTest::testWriteReadNativeHandle() +{ + Poco::FileOutputStream fos; + Poco::FileInputStream fis; + Poco::FileIOS::NativeHandle outHandle; + +#if defined(POCO_OS_FAMILY_WINDOWS) + char tmp[]={'\xc3', '\x84', '\xc3', '\x96', '\xc3', '\x9c', '\xc3', '\xa4', '\xc3', '\xb6', '\xc3', '\xbc', '\0'}; + std::string file(tmp); + file.append(".txt"); + std::wstring utf16Path; + Poco::UnicodeConverter::toUTF16(file, utf16Path); + outHandle = CreateFileW(utf16Path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + assertTrue(outHandle != INVALID_HANDLE_VALUE); +#else + std::string file("testfile.txt"); + outHandle = ::open(file.c_str(), O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); +#endif + + Poco::TemporaryFile::registerForDeletion(file); + + fos.openHandle(outHandle, std::ios::binary | std::ios::out | std::ios::trunc); + fos << "sometestdata"; + fos.close(); + + Poco::FileIOS::NativeHandle inHandle; + +#if defined(POCO_OS_FAMILY_WINDOWS) + inHandle = CreateFileW(utf16Path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + assertTrue(inHandle != INVALID_HANDLE_VALUE); +#else + inHandle = ::open(file.c_str(), O_RDONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); +#endif + + fis.openHandle(inHandle, std::ios::in); + assertTrue (fis.good()); + std::string read; + fis >> read; + assertTrue (!read.empty()); +} + + void FileStreamTest::testWrite() { #if defined(POCO_OS_FAMILY_WINDOWS) @@ -304,6 +352,7 @@ CppUnit::Test* FileStreamTest::suite() CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("FileStreamTest"); CppUnit_addTest(pSuite, FileStreamTest, testRead); + CppUnit_addTest(pSuite, FileStreamTest, testWriteReadNativeHandle); CppUnit_addTest(pSuite, FileStreamTest, testWrite); CppUnit_addTest(pSuite, FileStreamTest, testReadWrite); CppUnit_addTest(pSuite, FileStreamTest, testOpen); diff --git a/Foundation/testsuite/src/FileStreamTest.h b/Foundation/testsuite/src/FileStreamTest.h index f448d1f99..533de4af6 100644 --- a/Foundation/testsuite/src/FileStreamTest.h +++ b/Foundation/testsuite/src/FileStreamTest.h @@ -25,6 +25,7 @@ public: ~FileStreamTest(); void testRead(); + void testWriteReadNativeHandle(); void testWrite(); void testReadWrite(); void testOpen();