Skip to content

Commit a68f28e

Browse files
committed
[libc++] Implements filebuf unbuffered.
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 26065dd commit a68f28e

File tree

2 files changed

+164
-1
lines changed

2 files changed

+164
-1
lines changed

libcxx/include/fstream

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,30 @@ private:
276276
state_type __st_;
277277
state_type __st_last_;
278278
ios_base::openmode __om_;
279+
// Used to track the currently used mode and track whether the output should
280+
// be unbuffered.
281+
// [filebuf.virtuals]/12
282+
// If setbuf(0, 0) is called on a stream before any I/O has occurred on
283+
// that stream, the stream becomes unbuffered. Otherwise the results are
284+
// implementation-defined.
285+
// This allows calling setbuf(0, 0)
286+
// - before opening a file,
287+
// - after opening a file, before
288+
// - a read
289+
// - a write
290+
// - a seek.
291+
// Note that opening a file with ios_base::ate does a seek operation.
292+
// Normally underflow, overflow, and sync change this flag to
293+
// ios_base::in, ios_base_out, or 0.
294+
//
295+
// The ios_base::trunc and ios_base::ate flags are used in the following way:
296+
// - ios_base::trunc is set upon construction to indicate the unbuffered
297+
// state can be set. When requesting unbuffered output and the file is open
298+
// sets the mode. Else places a request by adding the ios_base::ate flag.
299+
// - When a file is opened it checks whether both ios_base::trunc and
300+
// ios_base::ate are set. If so switches to unbuffered mode.
301+
// - Using ase::ate in the open mode sets the flag to 0 so future calls to
302+
// setbuf no longer try to set the unbuffered mode.
279303
ios_base::openmode __cm_;
280304
bool __owns_eb_;
281305
bool __owns_ib_;
@@ -294,7 +318,10 @@ private:
294318
return nullptr;
295319

296320
__om_ = __mode;
321+
__try_set_unbuffered_mode();
322+
297323
if (__mode & ios_base::ate) {
324+
__cm_ = 0;
298325
if (fseek(__file_, 0, SEEK_END)) {
299326
fclose(__file_);
300327
__file_ = nullptr;
@@ -304,6 +331,23 @@ private:
304331

305332
return this;
306333
}
334+
335+
_LIBCPP_HIDE_FROM_ABI void __try_set_unbuffered_mode() {
336+
if (__cm_ == (ios_base::trunc | ios_base::ate)) {
337+
std::setbuf(__file_, nullptr);
338+
__cm_ = 0;
339+
}
340+
}
341+
_LIBCPP_HIDE_FROM_ABI void __try_set_unbuffered_mode(char_type* __s, streamsize __n) {
342+
if (__cm_ == ios_base::trunc && __s == nullptr && __n == 0) {
343+
if (__file_) {
344+
std::setbuf(__file_, nullptr);
345+
__cm_ = 0;
346+
} else {
347+
__cm_ = ios_base::trunc | ios_base::ate;
348+
}
349+
}
350+
}
307351
};
308352

309353
template <class _CharT, class _Traits>
@@ -319,7 +363,7 @@ basic_filebuf<_CharT, _Traits>::basic_filebuf()
319363
__st_(),
320364
__st_last_(),
321365
__om_(0),
322-
__cm_(0),
366+
__cm_(ios_base::trunc),
323367
__owns_eb_(false),
324368
__owns_ib_(false),
325369
__always_noconv_(false) {
@@ -780,6 +824,7 @@ template <class _CharT, class _Traits>
780824
basic_streambuf<_CharT, _Traits>* basic_filebuf<_CharT, _Traits>::setbuf(char_type* __s, streamsize __n) {
781825
this->setg(nullptr, nullptr, nullptr);
782826
this->setp(nullptr, nullptr);
827+
__try_set_unbuffered_mode(__s, __n);
783828
if (__owns_eb_)
784829
delete[] __extbuf_;
785830
if (__owns_ib_)
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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 <cassert>
15+
16+
#include "test_macros.h"
17+
18+
template <class CharT>
19+
static std::size_t file_size(const char* filename) {
20+
FILE* f = std::fopen(filename, "rb");
21+
fseek(f, 0, SEEK_END);
22+
long result = ftell(f);
23+
fclose(f);
24+
return result;
25+
}
26+
27+
template <class CharT>
28+
struct filebuf : public std::basic_filebuf<CharT> {
29+
CharT* base() { return this->pbase(); }
30+
CharT* ptr() { return this->pptr(); }
31+
};
32+
33+
template <class CharT>
34+
static void buffered_request() {
35+
filebuf<CharT> buffer;
36+
37+
CharT b[10] = {0};
38+
assert(buffer.pubsetbuf(b, 10) == &buffer);
39+
40+
buffer.open("test.dat", std::ios_base::out);
41+
buffer.sputc(CharT('a'));
42+
assert(b[0] == 'a');
43+
44+
buffer.close();
45+
assert(file_size<CharT>("test.dat") == 1);
46+
}
47+
48+
template <class CharT>
49+
static void unbuffered_request_before_open() {
50+
filebuf<CharT> buffer;
51+
52+
assert(buffer.pubsetbuf(nullptr, 0) == &buffer);
53+
assert(buffer.base() == nullptr);
54+
assert(buffer.ptr() == nullptr);
55+
56+
buffer.open("test.dat", std::ios_base::out);
57+
assert(buffer.base() == nullptr);
58+
assert(buffer.ptr() == nullptr);
59+
60+
buffer.sputc(CharT('a'));
61+
assert(buffer.base() == nullptr);
62+
assert(buffer.ptr() == nullptr);
63+
64+
assert(file_size<CharT>("test.dat") == 1);
65+
}
66+
67+
template <class CharT>
68+
static void unbuffered_request_after_open() {
69+
filebuf<CharT> buffer;
70+
71+
buffer.open("test.dat", std::ios_base::out);
72+
73+
assert(buffer.pubsetbuf(nullptr, 0) == &buffer);
74+
assert(buffer.base() == nullptr);
75+
assert(buffer.ptr() == nullptr);
76+
77+
buffer.sputc(CharT('a'));
78+
assert(buffer.base() == nullptr);
79+
assert(buffer.ptr() == nullptr);
80+
81+
assert(file_size<CharT>("test.dat") == 1);
82+
}
83+
84+
template <class CharT>
85+
static void unbuffered_request_after_open_ate() {
86+
filebuf<CharT> buffer;
87+
88+
buffer.open("test.dat", std::ios_base::out | std::ios_base::ate);
89+
90+
assert(buffer.pubsetbuf(nullptr, 0) == &buffer);
91+
92+
buffer.sputc(CharT('a'));
93+
assert(file_size<CharT>("test.dat") <= 1);
94+
// on libc++ buffering is used by default.
95+
LIBCPP_ASSERT(file_size<CharT>("test.dat") == 0);
96+
97+
buffer.close();
98+
assert(file_size<CharT>("test.dat") == 1);
99+
}
100+
101+
template <class CharT>
102+
static void test() {
103+
buffered_request<CharT>();
104+
105+
unbuffered_request_before_open<CharT>();
106+
unbuffered_request_after_open<CharT>();
107+
unbuffered_request_after_open_ate<CharT>();
108+
}
109+
110+
int main(int, char**) {
111+
test<char>();
112+
113+
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
114+
test<wchar_t>();
115+
#endif
116+
117+
return 0;
118+
}

0 commit comments

Comments
 (0)