Make fflush-after-ungetc POSIX compliant on glibc systems.
[gnulib.git] / lib / fflush.c
1 /* fflush.c -- allow flushing input streams
2    Copyright (C) 2007-2009 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
16
17 /* Written by Eric Blake. */
18
19 #include <config.h>
20
21 /* Specification.  */
22 #include <stdio.h>
23
24 #include <errno.h>
25 #include <unistd.h>
26
27 #include "freading.h"
28 #include "fpurge.h"
29
30 #include "stdio-impl.h"
31
32 #undef fflush
33
34 static inline void
35 clear_ungetc_buffer (FILE *fp)
36 {
37 #if defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
38   if (fp->_flags & _IO_IN_BACKUP)
39     /* _IO_free_backup_area is a bit complicated.  Simply call fseek.  */
40     fseek (fp, 0, SEEK_CUR);
41 #elif defined __sferror || defined __DragonFly__ /* FreeBSD, NetBSD, OpenBSD, DragonFly, MacOS X, Cygwin */
42   if (HASUB (fp))
43     {
44       fp_->_p += fp_->_r;
45       fp_->_r = 0;
46     }
47 #endif
48 }
49
50 #if (defined __sferror || defined __DragonFly__) && defined __SNPT /* FreeBSD, NetBSD, OpenBSD, DragonFly, MacOS X, Cygwin */
51
52 static inline int
53 disable_seek_optimization (FILE *fp)
54 {
55   int saved_flags = fp_->_flags & (__SOPT | __SNPT);
56   fp_->_flags = (fp_->_flags & ~__SOPT) | __SNPT;
57   return saved_flags;
58 }
59
60 static inline void
61 restore_seek_optimization (FILE *fp, int saved_flags)
62 {
63   fp_->_flags = (fp_->_flags & ~(__SOPT | __SNPT)) | saved_flags;
64 }
65
66 #endif
67
68 static inline void
69 update_fpos_cache (FILE *fp, off_t pos)
70 {
71 #if defined __sferror || defined __DragonFly__ /* FreeBSD, NetBSD, OpenBSD, DragonFly, MacOS X, Cygwin */
72   fp_->_offset = pos;
73   fp_->_flags |= __SOFF;
74 #endif
75 }
76
77 /* Flush all pending data on STREAM according to POSIX rules.  Both
78    output and seekable input streams are supported.  */
79 int
80 rpl_fflush (FILE *stream)
81 {
82   int result;
83   off_t pos;
84
85   /* When stream is NULL, POSIX and C99 only require flushing of "output
86      streams and update streams in which the most recent operation was not
87      input", and all implementations do this.
88
89      When stream is "an output stream or an update stream in which the most
90      recent operation was not input", POSIX and C99 requires that fflush
91      writes out any buffered data, and all implementations do this.
92
93      When stream is, however, an input stream or an update stream in
94      which the most recent operation was input, C99 specifies nothing,
95      and POSIX only specifies behavior if the stream is seekable.
96      mingw, in particular, drops the input buffer, leaving the file
97      descriptor positioned at the end of the input buffer. I.e. ftell
98      (stream) is lost.  We don't want to call the implementation's
99      fflush in this case.
100
101      We test ! freading (stream) here, rather than fwriting (stream), because
102      what we need to know is whether the stream holds a "read buffer", and on
103      mingw this is indicated by _IOREAD, regardless of _IOWRT.  */
104   if (stream == NULL || ! freading (stream))
105     return fflush (stream);
106
107   /* Clear the ungetc buffer.
108
109      This is needed before fetching the file-position indicator, because
110      1) The file position indicator is incremented by fgetc() and decremented
111         by ungetc():
112         <http://www.opengroup.org/susv3/functions/fgetc.html>
113           "... the fgetc() function shall ... advance the associated file
114            position indicator for the stream ..."
115         <http://www.opengroup.org/susv3/functions/ungetc.html>
116           "The file-position indicator is decremented by each successful
117            call to ungetc()..."
118      2) <http://www.opengroup.org/susv3/functions/ungetc.html> says:
119           "The value of the file-position indicator for the stream after
120            reading or discarding all pushed-back bytes shall be the same
121            as it was before the bytes were pushed back."
122      3) Here we are discarding all pushed-back bytes.
123
124      Unfortunately it is impossible to implement this on platforms with
125      _IOERR, because an ungetc() on this platform prepends the pushed-back
126      bytes to the buffer without an indication of the limit between the
127      pushed-back bytes and the read-ahead bytes.  */
128   clear_ungetc_buffer (stream);
129
130 #if defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
131
132   return fflush (stream);
133
134 #else
135
136   /* POSIX does not specify fflush behavior for non-seekable input
137      streams.  Some implementations purge unread data, some return
138      EBADF, some do nothing.  */
139   pos = ftello (stream);
140   if (pos == -1)
141     {
142       errno = EBADF;
143       return EOF;
144     }
145
146   /* To get here, we must be flushing a seekable input stream, so the
147      semantics of fpurge are now appropriate to clear the buffer.  To
148      avoid losing data, the lseek is also necessary.  */
149   result = fpurge (stream);
150   if (result != 0)
151     return result;
152
153 # if (defined __sferror || defined __DragonFly__) && defined __SNPT /* FreeBSD, NetBSD, OpenBSD, DragonFly, MacOS X, Cygwin */
154
155   {
156     /* Disable seek optimization for the next fseeko call.  This tells the
157        following fseeko call to seek to the desired position directly, rather
158        than to seek to a block-aligned boundary.  */
159     int saved_flags = disable_seek_optimization (stream);
160
161     result = fseeko (stream, pos, SEEK_SET);
162
163     restore_seek_optimization (stream, saved_flags);
164   }
165   return result;
166
167 # else
168
169   pos = lseek (fileno (stream), pos, SEEK_SET);
170   if (pos == -1)
171     return EOF;
172   /* After a successful lseek, update the file descriptor's position cache
173      in the stream.  */
174   update_fpos_cache (stream, pos);
175
176   return 0;
177
178 # endif
179 #endif
180 }