Skip to content

Commit 5afb937

Browse files
authored
[libc++] Implements filebuf unbuffered. (#76629)
When calling setbuf(nullptr, 0) before performing file operations it should set the file to unbuffered mode. Currently the code avoids buffering internally, but the underlying stream still can buffer. This is addressed by disabling the buffering of the underlying stream. Fixes: #60509
1 parent 63d53ee commit 5afb937

File tree

2 files changed

+179
-1
lines changed

2 files changed

+179
-1
lines changed

libcxx/include/fstream

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,43 @@ private:
308308
state_type __st_;
309309
state_type __st_last_;
310310
ios_base::openmode __om_;
311+
// There have been no file operations yet, which allows setting unbuffered
312+
// I/O mode.
313+
static const ios_base::openmode __no_io_operations = ios_base::trunc;
314+
// Unbuffered I/O mode has been requested.
315+
static const ios_base::openmode __use_unbuffered_io = ios_base::ate;
316+
// Used to track the currently used mode and track whether the output should
317+
// be unbuffered.
318+
// [filebuf.virtuals]/12
319+
// If setbuf(0, 0) is called on a stream before any I/O has occurred on
320+
// that stream, the stream becomes unbuffered. Otherwise the results are
321+
// implementation-defined.
322+
// This allows calling setbuf(0, 0)
323+
// - before opening a file,
324+
// - after opening a file, before
325+
// - a read
326+
// - a write
327+
// - a seek.
328+
// Note that opening a file with ios_base::ate does a seek operation.
329+
// Normally underflow, overflow, and sync change this flag to ios_base::in,
330+
// ios_base_out, or 0.
331+
//
332+
// The ios_base::trunc and ios_base::ate flags are not used in __cm_. They
333+
// are used to track the state of the unbuffered request. For readability
334+
// they have the aliases __no_io_operations and __use_unbuffered_io
335+
// respectively.
336+
//
337+
// The __no_io_operations and __use_unbuffered_io flags are used in the
338+
// following way:
339+
// - __no_io_operations is set upon construction to indicate the unbuffered
340+
// state can be set.
341+
// - When requesting unbuffered output:
342+
// - If the file is open it sets the mode.
343+
// - Else places a request by adding the __use_unbuffered_io flag.
344+
// - When a file is opened it checks whether both __no_io_operations and
345+
// __use_unbuffered_io are set. If so switches to unbuffered mode.
346+
// - All file I/O operations change the mode effectively clearing the
347+
// __no_io_operations and __use_unbuffered_io flags.
311348
ios_base::openmode __cm_;
312349
bool __owns_eb_;
313350
bool __owns_ib_;
@@ -327,7 +364,13 @@ private:
327364
return nullptr;
328365

329366
__om_ = __mode;
367+
if (__cm_ == (__no_io_operations | __use_unbuffered_io)) {
368+
std::setbuf(__file_, nullptr);
369+
__cm_ = 0;
370+
}
371+
330372
if (__mode & ios_base::ate) {
373+
__cm_ = 0;
331374
if (fseek(__file_, 0, SEEK_END)) {
332375
fclose(__file_);
333376
__file_ = nullptr;
@@ -337,6 +380,20 @@ private:
337380

338381
return this;
339382
}
383+
384+
// If the file is already open, switch to unbuffered mode. Otherwise, record
385+
// the request to use unbuffered mode so that we use that mode when we
386+
// eventually open the file.
387+
_LIBCPP_HIDE_FROM_ABI void __request_unbuffered_mode(char_type* __s, streamsize __n) {
388+
if (__cm_ == __no_io_operations && __s == nullptr && __n == 0) {
389+
if (__file_) {
390+
std::setbuf(__file_, nullptr);
391+
__cm_ = 0;
392+
} else {
393+
__cm_ = __no_io_operations | __use_unbuffered_io;
394+
}
395+
}
396+
}
340397
};
341398

342399
template <class _CharT, class _Traits>
@@ -352,7 +409,7 @@ basic_filebuf<_CharT, _Traits>::basic_filebuf()
352409
__st_(),
353410
__st_last_(),
354411
__om_(0),
355-
__cm_(0),
412+
__cm_(__no_io_operations),
356413
__owns_eb_(false),
357414
__owns_ib_(false),
358415
__always_noconv_(false) {
@@ -810,6 +867,7 @@ template <class _CharT, class _Traits>
810867
basic_streambuf<_CharT, _Traits>* basic_filebuf<_CharT, _Traits>::setbuf(char_type* __s, streamsize __n) {
811868
this->setg(nullptr, nullptr, nullptr);
812869
this->setp(nullptr, nullptr);
870+
__request_unbuffered_mode(__s, __n);
813871
if (__owns_eb_)
814872
delete[] __extbuf_;
815873
if (__owns_ib_)
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// <fstream>
10+
11+
// basic_streambuf<charT, traits>* setbuf(char_type* s, streamsize n) override;
12+
13+
#include <fstream>
14+
#include <cstddef>
15+
#include <cassert>
16+
17+
#include "test_macros.h"
18+
19+
template <class CharT>
20+
static std::size_t file_size(const char* filename) {
21+
FILE* f = std::fopen(filename, "rb");
22+
std::fseek(f, 0, SEEK_END);
23+
long result = std::ftell(f);
24+
std::fclose(f);
25+
return result;
26+
}
27+
28+
// Helper class to expose some protected std::basic_filebuf<CharT> members.
29+
template <class CharT>
30+
struct filebuf : public std::basic_filebuf<CharT> {
31+
CharT* base() { return this->pbase(); }
32+
CharT* ptr() { return this->pptr(); }
33+
};
34+
35+
template <class CharT>
36+
static void buffered_request() {
37+
filebuf<CharT> buffer;
38+
39+
CharT b[10] = {0};
40+
assert(buffer.pubsetbuf(b, 10) == &buffer);
41+
42+
buffer.open("test.dat", std::ios_base::out);
43+
buffer.sputc(CharT('a'));
44+
assert(b[0] == 'a');
45+
46+
buffer.close();
47+
assert(file_size<CharT>("test.dat") == 1);
48+
}
49+
50+
template <class CharT>
51+
static void unbuffered_request_before_open() {
52+
filebuf<CharT> buffer;
53+
54+
assert(buffer.pubsetbuf(nullptr, 0) == &buffer);
55+
assert(buffer.base() == nullptr);
56+
assert(buffer.ptr() == nullptr);
57+
58+
buffer.open("test.dat", std::ios_base::out);
59+
assert(buffer.base() == nullptr);
60+
assert(buffer.ptr() == nullptr);
61+
62+
buffer.sputc(CharT('a'));
63+
assert(buffer.base() == nullptr);
64+
assert(buffer.ptr() == nullptr);
65+
66+
assert(file_size<CharT>("test.dat") == 1);
67+
}
68+
69+
template <class CharT>
70+
static void unbuffered_request_after_open() {
71+
filebuf<CharT> buffer;
72+
73+
buffer.open("test.dat", std::ios_base::out);
74+
75+
assert(buffer.pubsetbuf(nullptr, 0) == &buffer);
76+
assert(buffer.base() == nullptr);
77+
assert(buffer.ptr() == nullptr);
78+
79+
buffer.sputc(CharT('a'));
80+
assert(buffer.base() == nullptr);
81+
assert(buffer.ptr() == nullptr);
82+
83+
assert(file_size<CharT>("test.dat") == 1);
84+
}
85+
86+
template <class CharT>
87+
static void unbuffered_request_after_open_ate() {
88+
filebuf<CharT> buffer;
89+
90+
buffer.open("test.dat", std::ios_base::out | std::ios_base::ate);
91+
92+
assert(buffer.pubsetbuf(nullptr, 0) == &buffer);
93+
94+
buffer.sputc(CharT('a'));
95+
assert(file_size<CharT>("test.dat") <= 1);
96+
// on libc++ buffering is used by default.
97+
LIBCPP_ASSERT(file_size<CharT>("test.dat") == 0);
98+
99+
buffer.close();
100+
assert(file_size<CharT>("test.dat") == 1);
101+
}
102+
103+
template <class CharT>
104+
static void test() {
105+
buffered_request<CharT>();
106+
107+
unbuffered_request_before_open<CharT>();
108+
unbuffered_request_after_open<CharT>();
109+
unbuffered_request_after_open_ate<CharT>();
110+
}
111+
112+
int main(int, char**) {
113+
test<char>();
114+
115+
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
116+
test<wchar_t>();
117+
#endif
118+
119+
return 0;
120+
}

0 commit comments

Comments
 (0)