diff --git a/libcxx/include/fstream b/libcxx/include/fstream index 776641b347e6c..7a084d114b185 100644 --- a/libcxx/include/fstream +++ b/libcxx/include/fstream @@ -308,6 +308,43 @@ private: state_type __st_; state_type __st_last_; ios_base::openmode __om_; + // There have been no file operations yet, which allows setting unbuffered + // I/O mode. + static const ios_base::openmode __no_io_operations = ios_base::trunc; + // Unbuffered I/O mode has been requested. + static const ios_base::openmode __use_unbuffered_io = ios_base::ate; + // Used to track the currently used mode and track whether the output should + // be unbuffered. + // [filebuf.virtuals]/12 + // If setbuf(0, 0) is called on a stream before any I/O has occurred on + // that stream, the stream becomes unbuffered. Otherwise the results are + // implementation-defined. + // This allows calling setbuf(0, 0) + // - before opening a file, + // - after opening a file, before + // - a read + // - a write + // - a seek. + // Note that opening a file with ios_base::ate does a seek operation. + // Normally underflow, overflow, and sync change this flag to ios_base::in, + // ios_base_out, or 0. + // + // The ios_base::trunc and ios_base::ate flags are not used in __cm_. They + // are used to track the state of the unbuffered request. For readability + // they have the aliases __no_io_operations and __use_unbuffered_io + // respectively. + // + // The __no_io_operations and __use_unbuffered_io flags are used in the + // following way: + // - __no_io_operations is set upon construction to indicate the unbuffered + // state can be set. + // - When requesting unbuffered output: + // - If the file is open it sets the mode. + // - Else places a request by adding the __use_unbuffered_io flag. + // - When a file is opened it checks whether both __no_io_operations and + // __use_unbuffered_io are set. If so switches to unbuffered mode. + // - All file I/O operations change the mode effectively clearing the + // __no_io_operations and __use_unbuffered_io flags. ios_base::openmode __cm_; bool __owns_eb_; bool __owns_ib_; @@ -327,7 +364,13 @@ private: return nullptr; __om_ = __mode; + if (__cm_ == (__no_io_operations | __use_unbuffered_io)) { + std::setbuf(__file_, nullptr); + __cm_ = 0; + } + if (__mode & ios_base::ate) { + __cm_ = 0; if (fseek(__file_, 0, SEEK_END)) { fclose(__file_); __file_ = nullptr; @@ -337,6 +380,20 @@ private: return this; } + + // If the file is already open, switch to unbuffered mode. Otherwise, record + // the request to use unbuffered mode so that we use that mode when we + // eventually open the file. + _LIBCPP_HIDE_FROM_ABI void __request_unbuffered_mode(char_type* __s, streamsize __n) { + if (__cm_ == __no_io_operations && __s == nullptr && __n == 0) { + if (__file_) { + std::setbuf(__file_, nullptr); + __cm_ = 0; + } else { + __cm_ = __no_io_operations | __use_unbuffered_io; + } + } + } }; template @@ -352,7 +409,7 @@ basic_filebuf<_CharT, _Traits>::basic_filebuf() __st_(), __st_last_(), __om_(0), - __cm_(0), + __cm_(__no_io_operations), __owns_eb_(false), __owns_ib_(false), __always_noconv_(false) { @@ -810,6 +867,7 @@ template basic_streambuf<_CharT, _Traits>* basic_filebuf<_CharT, _Traits>::setbuf(char_type* __s, streamsize __n) { this->setg(nullptr, nullptr, nullptr); this->setp(nullptr, nullptr); + __request_unbuffered_mode(__s, __n); if (__owns_eb_) delete[] __extbuf_; if (__owns_ib_) diff --git a/libcxx/test/std/input.output/file.streams/fstreams/filebuf.virtuals/setbuf.pass.cpp b/libcxx/test/std/input.output/file.streams/fstreams/filebuf.virtuals/setbuf.pass.cpp new file mode 100644 index 0000000000000..8bcce28162033 --- /dev/null +++ b/libcxx/test/std/input.output/file.streams/fstreams/filebuf.virtuals/setbuf.pass.cpp @@ -0,0 +1,120 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// + +// basic_streambuf* setbuf(char_type* s, streamsize n) override; + +#include +#include +#include + +#include "test_macros.h" + +template +static std::size_t file_size(const char* filename) { + FILE* f = std::fopen(filename, "rb"); + std::fseek(f, 0, SEEK_END); + long result = std::ftell(f); + std::fclose(f); + return result; +} + +// Helper class to expose some protected std::basic_filebuf members. +template +struct filebuf : public std::basic_filebuf { + CharT* base() { return this->pbase(); } + CharT* ptr() { return this->pptr(); } +}; + +template +static void buffered_request() { + filebuf buffer; + + CharT b[10] = {0}; + assert(buffer.pubsetbuf(b, 10) == &buffer); + + buffer.open("test.dat", std::ios_base::out); + buffer.sputc(CharT('a')); + assert(b[0] == 'a'); + + buffer.close(); + assert(file_size("test.dat") == 1); +} + +template +static void unbuffered_request_before_open() { + filebuf buffer; + + assert(buffer.pubsetbuf(nullptr, 0) == &buffer); + assert(buffer.base() == nullptr); + assert(buffer.ptr() == nullptr); + + buffer.open("test.dat", std::ios_base::out); + assert(buffer.base() == nullptr); + assert(buffer.ptr() == nullptr); + + buffer.sputc(CharT('a')); + assert(buffer.base() == nullptr); + assert(buffer.ptr() == nullptr); + + assert(file_size("test.dat") == 1); +} + +template +static void unbuffered_request_after_open() { + filebuf buffer; + + buffer.open("test.dat", std::ios_base::out); + + assert(buffer.pubsetbuf(nullptr, 0) == &buffer); + assert(buffer.base() == nullptr); + assert(buffer.ptr() == nullptr); + + buffer.sputc(CharT('a')); + assert(buffer.base() == nullptr); + assert(buffer.ptr() == nullptr); + + assert(file_size("test.dat") == 1); +} + +template +static void unbuffered_request_after_open_ate() { + filebuf buffer; + + buffer.open("test.dat", std::ios_base::out | std::ios_base::ate); + + assert(buffer.pubsetbuf(nullptr, 0) == &buffer); + + buffer.sputc(CharT('a')); + assert(file_size("test.dat") <= 1); + // on libc++ buffering is used by default. + LIBCPP_ASSERT(file_size("test.dat") == 0); + + buffer.close(); + assert(file_size("test.dat") == 1); +} + +template +static void test() { + buffered_request(); + + unbuffered_request_before_open(); + unbuffered_request_after_open(); + unbuffered_request_after_open_ate(); +} + +int main(int, char**) { + test(); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + + return 0; +}