Put system dependent routines into inline functions.
[gnulib.git] / lib / fflush.c
1 /* fflush.c -- allow flushing input streams
2    Copyright (C) 2007-2008 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 #undef fflush
31
32 static inline void
33 clear_ungetc_buffer (FILE *fp)
34 {
35 #if defined __sferror               /* FreeBSD, NetBSD, OpenBSD, MacOS X, Cygwin */
36 # if defined __NetBSD__ || defined __OpenBSD__
37   struct __sfileext
38     {
39       struct  __sbuf _ub; /* ungetc buffer */
40       /* More fields, not relevant here.  */
41     };
42 #  define HASUB(fp) (((struct __sfileext *) (fp)->_ext._base)->_ub._base != NULL)
43 # else
44 #  define HASUB(fp) ((fp)->_ub._base != NULL)
45 # endif
46   if (HASUB (fp))
47     {
48       fp->_p += stream->_r;
49       fp->_r = 0;
50     }
51 #endif
52 }
53
54 #if defined __sferror && defined __SNPT /* FreeBSD, NetBSD, OpenBSD, MacOS X, Cygwin */
55
56 static inline int
57 disable_seek_optimization (FILE *fp)
58 {
59   int saved_flags = fp->_flags & (__SOPT | __SNPT);
60   fp->_flags = (fp->_flags & ~__SOPT) | __SNPT;
61   return saved_flags;
62 }
63
64 static inline void
65 restore_seek_optimization (FILE *fp, int saved_flags)
66 {
67   fp->_flags = (fp->_flags & ~(__SOPT | __SNPT)) | saved_flags;
68 }
69
70 #endif
71
72 static inline void
73 update_fpos_cache (FILE *fp)
74 {
75 #if defined __sferror               /* FreeBSD, NetBSD, OpenBSD, MacOS X, Cygwin */
76   fp->_offset = pos;
77   fp->_flags |= __SOFF;
78 #endif
79 }
80
81 /* Flush all pending data on STREAM according to POSIX rules.  Both
82    output and seekable input streams are supported.  */
83 int
84 rpl_fflush (FILE *stream)
85 {
86   int result;
87   off_t pos;
88
89   /* When stream is NULL, POSIX and C99 only require flushing of "output
90      streams and update streams in which the most recent operation was not
91      input", and all implementations do this.
92
93      When stream is "an output stream or an update stream in which the most
94      recent operation was not input", POSIX and C99 requires that fflush
95      writes out any buffered data, and all implementations do this.
96
97      When stream is, however, an input stream or an update stream in
98      which the most recent operation was input, C99 specifies nothing,
99      and POSIX only specifies behavior if the stream is seekable.
100      mingw, in particular, drops the input buffer, leaving the file
101      descriptor positioned at the end of the input buffer. I.e. ftell
102      (stream) is lost.  We don't want to call the implementation's
103      fflush in this case.
104
105      We test ! freading (stream) here, rather than fwriting (stream), because
106      what we need to know is whether the stream holds a "read buffer", and on
107      mingw this is indicated by _IOREAD, regardless of _IOWRT.  */
108   if (stream == NULL || ! freading (stream))
109     return fflush (stream);
110
111   /* Clear the ungetc buffer.
112
113      This is needed before fetching the file-position indicator, because
114      1) The file position indicator is incremented by fgetc() and decremented
115         by ungetc():
116         <http://www.opengroup.org/susv3/functions/fgetc.html>
117           "... the fgetc() function shall ... advance the associated file
118            position indicator for the stream ..."
119         <http://www.opengroup.org/susv3/functions/ungetc.html>
120           "The file-position indicator is decremented by each successful
121            call to ungetc()..."
122      2) <http://www.opengroup.org/susv3/functions/ungetc.html> says:
123           "The value of the file-position indicator for the stream after
124            reading or discarding all pushed-back bytes shall be the same
125            as it was before the bytes were pushed back."
126      3) Here we are discarding all pushed-back bytes.
127
128      Unfortunately it is impossible to implement this on platforms with
129      _IOERR, because an ungetc() on this platform prepends the pushed-back
130      bytes to the buffer without an indication of the limit between the
131      pushed-back bytes and the read-ahead bytes.  */
132   clear_ungetc_buffer (stream);
133
134   /* POSIX does not specify fflush behavior for non-seekable input
135      streams.  Some implementations purge unread data, some return
136      EBADF, some do nothing.  */
137   pos = ftello (stream);
138   if (pos == -1)
139     {
140       errno = EBADF;
141       return EOF;
142     }
143
144   /* To get here, we must be flushing a seekable input stream, so the
145      semantics of fpurge are now appropriate to clear the buffer.  To
146      avoid losing data, the lseek is also necessary.  */
147   result = fpurge (stream);
148   if (result != 0)
149     return result;
150
151 #if defined __sferror && defined __SNPT /* FreeBSD, NetBSD, OpenBSD, MacOS X, Cygwin */
152
153   {
154     /* Disable seek optimization for the next fseeko call.  This tells the
155        following fseeko call to seek to the desired position directly, rather
156        than to seek to a block-aligned boundary.  */
157     int saved_flags = disable_seek_optimization (stream);
158
159     result = fseeko (stream, pos, SEEK_SET);
160
161     restore_seek_optimization (stream, saved_flags);
162   }
163   return result;
164
165 #else
166
167   pos = lseek (fileno (stream), pos, SEEK_SET);
168   if (pos == -1)
169     return EOF;
170   /* After a successful lseek, update the file descriptor's position cache
171      in the stream.  */
172   update_fpos_cache (stream);
173
174   return 0;
175
176 #endif
177 }