xref: /reactos/sdk/lib/ucrt/stdio/fflush.cpp (revision 04e0dc4a)
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