1 //
2 // fflush.cpp
3 //
4 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //
6 // Defines fflush() and related functions, which flush stdio streams.
7 //
8 #include <corecrt_internal_stdio.h>
9 #include <corecrt_internal_ptd_propagation.h>
10
11
12
is_stream_allocated(long const stream_flags)13 static bool __cdecl is_stream_allocated(long const stream_flags) throw()
14 {
15 return (stream_flags & _IOALLOCATED) != 0;
16 }
17
is_stream_flushable(long const stream_flags)18 static bool __cdecl is_stream_flushable(long const stream_flags) throw()
19 {
20 if ((stream_flags & (_IOREAD | _IOWRITE)) != _IOWRITE)
21 {
22 return false;
23 }
24
25 if ((stream_flags & (_IOBUFFER_CRT | _IOBUFFER_USER)) == 0)
26 {
27 return false;
28 }
29
30 return true;
31 }
32
is_stream_flushable_or_commitable(long const stream_flags)33 static bool __cdecl is_stream_flushable_or_commitable(long const stream_flags) throw()
34 {
35 if (is_stream_flushable(stream_flags))
36 {
37 return true;
38 }
39
40 if (stream_flags & _IOCOMMIT)
41 {
42 return true;
43 }
44
45 return false;
46 }
47
48 // Returns true if the common_flush_all function should attempt to flush the
49 // stream; otherwise returns false. This function returns false for streams
50 // that are not in use (not allocated) and for streams for which the flush
51 // operation will be a no-op.
52 //
53 // In the case where this function determines that the flush would be a no-op,
54 // it increments the flushed_stream_count. This allows common_flush_all to
55 // keep track of the number of streams that it would have flushed.
common_flush_all_should_try_to_flush_stream(_In_ __crt_stdio_stream const stream,_Inout_ int * const flushed_stream_count)56 static bool __cdecl common_flush_all_should_try_to_flush_stream(
57 _In_ __crt_stdio_stream const stream,
58 _Inout_ int* const flushed_stream_count
59 ) throw()
60 {
61 if (!stream.valid())
62 {
63 return false;
64 }
65
66 long const stream_flags = stream.get_flags();
67 if (!is_stream_allocated(stream_flags))
68 {
69 return false;
70 }
71
72 if (!is_stream_flushable_or_commitable(stream_flags))
73 {
74 ++*flushed_stream_count;
75 return false;
76 }
77
78 return true;
79 }
80
81
82
83 // Internal implementation of the "flush all" functionality. If the
84 // flush_read_mode_streams argument is false, only write mode streams are
85 // flushed and the return value is zero on success, EOF on failure.
86 //
87 // If the flush_read_mode_streams argument is true, this function flushes
88 // all streams regardless of mode and returns the number of streams that it
89 // flushed.
90 //
91 // Note that in both cases, if we can determine that a call to fflush for a
92 // particular stream would be a no-op, then we will not call fflush for that
93 // stream. This allows us to avoid acquiring the stream lock unnecessarily,
94 // which can help to avoid deadlock-like lock contention.
95 //
96 // Notably, by doing this, we can avoid attempting to acquire the stream lock
97 // for most read mode streams. Attempting to acquire the stream lock for a
98 // read mode stream can be problematic beause another thread may hold the lock
99 // and be blocked on an I/O operation (e.g., a call to fread on stdin may block
100 // until the user types input into the console).
common_flush_all(bool const flush_read_mode_streams)101 static int __cdecl common_flush_all(bool const flush_read_mode_streams) throw()
102 {
103 int count = 0;
104 int error = 0;
105
106 __acrt_lock_and_call(__acrt_stdio_index_lock, [&]
107 {
108 __crt_stdio_stream_data** const first_file = __piob;
109 __crt_stdio_stream_data** const last_file = first_file + _nstream;
110
111 for (__crt_stdio_stream_data** it = first_file; it != last_file; ++it)
112 {
113 __crt_stdio_stream const stream(*it);
114
115 // Before we acquire the stream lock, check to see if flushing the
116 // stream would be a no-op. If it would be, then skip this stream.
117 if (!common_flush_all_should_try_to_flush_stream(stream, &count))
118 {
119 continue;
120 }
121
122 __acrt_lock_stream_and_call(stream.public_stream(), [&]
123 {
124 // Re-verify the state of the stream. Another thread may have
125 // closed the stream, reopened it into a different mode, or
126 // otherwise altered the state of the stream such that this
127 // flush would be a no-op.
128 if (!common_flush_all_should_try_to_flush_stream(stream, &count))
129 {
130 return;
131 }
132
133 if (!flush_read_mode_streams && !stream.has_all_of(_IOWRITE))
134 {
135 return;
136 }
137
138 if (_fflush_nolock(stream.public_stream()) != EOF)
139 {
140 ++count;
141 }
142 else
143 {
144 error = EOF;
145 }
146 });
147 }
148 });
149
150 return flush_read_mode_streams ? count : error;
151 }
152
153
154
155 // Flushes the buffer of the given stream. If the file is open for writing and
156 // is buffered, the buffer is flushed. On success, returns 0. On failure (e.g.
157 // if there is an error writing the buffer), returns EOF and sets errno.
fflush(FILE * const public_stream)158 extern "C" int __cdecl fflush(FILE* const public_stream)
159 {
160 __crt_stdio_stream const stream(public_stream);
161
162 // If the stream is null, flush all the streams:
163 if (!stream.valid())
164 {
165 return common_flush_all(false);
166 }
167
168 // Before acquiring the stream lock, inspect the stream to see if the flush
169 // is a no-op. If it will be a no-op then we can return without attempting
170 // to acquire the lock (this can help prevent locking conflicts; see the
171 // common_flush_all implementation for more information).
172 if (!is_stream_flushable_or_commitable(stream.get_flags()))
173 {
174 return 0;
175 }
176
177 return __acrt_lock_stream_and_call(stream.public_stream(), [&]
178 {
179 return _fflush_nolock(stream.public_stream());
180 });
181 }
182
183
184
_fflush_nolock_internal(FILE * const public_stream,__crt_cached_ptd_host & ptd)185 static int __cdecl _fflush_nolock_internal(FILE* const public_stream, __crt_cached_ptd_host& ptd)
186 {
187 __crt_stdio_stream const stream(public_stream);
188
189 // If the stream is null, flush all the streams.
190 if (!stream.valid())
191 {
192 return common_flush_all(false);
193 }
194
195 if (__acrt_stdio_flush_nolock(stream.public_stream(), ptd) != 0)
196 {
197 // If the flush fails, do not attempt to commit:
198 return EOF;
199 }
200
201 // Perform the lowio commit to ensure data is written to disk:
202 if (stream.has_all_of(_IOCOMMIT))
203 {
204 if (_commit(_fileno(public_stream)))
205 {
206 return EOF;
207 }
208 }
209
210 return 0;
211 }
212
_fflush_nolock(FILE * const public_stream)213 extern "C" int __cdecl _fflush_nolock(FILE* const public_stream)
214 {
215 __crt_cached_ptd_host ptd;
216 return _fflush_nolock_internal(public_stream, ptd);
217 }
218
219 // Flushes the buffer of the given stream. If the file is open for writing and
220 // is buffered, the buffer is flushed. On success, returns 0. On failure (e.g.
221 // if there is an error writing the buffer), returns EOF and sets errno.
__acrt_stdio_flush_nolock(FILE * const public_stream,__crt_cached_ptd_host & ptd)222 extern "C" int __cdecl __acrt_stdio_flush_nolock(FILE* const public_stream, __crt_cached_ptd_host& ptd)
223 {
224 __crt_stdio_stream const stream(public_stream);
225
226 if (!is_stream_flushable(stream.get_flags()))
227 {
228 return 0;
229 }
230
231 int const bytes_to_write = static_cast<int>(stream->_ptr - stream->_base);
232
233 __acrt_stdio_reset_buffer(stream);
234
235 if (bytes_to_write <= 0)
236 {
237 return 0;
238 }
239
240 int const bytes_written = _write_internal(_fileno(stream.public_stream()), stream->_base, bytes_to_write, ptd);
241 if (bytes_to_write != bytes_written)
242 {
243 stream.set_flags(_IOERROR);
244 return EOF;
245 }
246
247 // If this is a read/write file, clear _IOWRITE so that the next operation can
248 // be a read:
249 if (stream.has_all_of(_IOUPDATE))
250 {
251 stream.unset_flags(_IOWRITE);
252 }
253
254 return 0;
255 }
256
257
258
259 // Flushes the buffers for all output streams and clears all input buffers.
260 // Returns the number of open streams.
_flushall()261 extern "C" int __cdecl _flushall()
262 {
263 return common_flush_all(true);
264 }
265