1 /* Output stream referring to a file descriptor.
2    Copyright (C) 2006-2007, 2019 Free Software Foundation, Inc.
3    Written by Bruno Haible <bruno@clisp.org>, 2006.
4 
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
17 
18 #include <config.h>
19 
20 /* Specification.  */
21 #include "fd-ostream.h"
22 
23 #include <assert.h>
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #if HAVE_TCDRAIN
29 # include <termios.h>
30 #endif
31 
32 #include "error.h"
33 #include "full-write.h"
34 #include "xalloc.h"
35 #include "gettext.h"
36 
37 #define _(str) gettext (str)
38 
39 struct fd_ostream : struct ostream
40 {
41 fields:
42   int fd;
43   char *filename;
44   char *buffer;                 /* A buffer, or NULL.  */
45   size_t avail;                 /* Number of bytes available in the buffer.  */
46 };
47 
48 #define BUFSIZE 4096
49 
50 #if HAVE_TCDRAIN
51 
52 /* EINTR handling for tcdrain().
53    This function can return -1/EINTR even though we don't have any
54    signal handlers set up, namely when we get interrupted via SIGSTOP.  */
55 
56 static inline int
nonintr_tcdrain(int fd)57 nonintr_tcdrain (int fd)
58 {
59   int retval;
60 
61   do
62     retval = tcdrain (fd);
63   while (retval < 0 && errno == EINTR);
64 
65   return retval;
66 }
67 
68 #endif
69 
70 /* Implementation of ostream_t methods.  */
71 
72 static void
write_mem(fd_ostream_t stream,const void * data,size_t len)73 fd_ostream::write_mem (fd_ostream_t stream, const void *data, size_t len)
74 {
75   if (len > 0)
76     {
77       if (stream->buffer != NULL)
78         {
79           /* Buffered.  */
80           assert (stream->avail > 0);
81           #if 0 /* unoptimized */
82           do
83             {
84               size_t n = (len <= stream->avail ? len : stream->avail);
85               if (n > 0)
86                 {
87                   memcpy (stream->buffer + BUFSIZE - stream->avail, data, n);
88                   data = (char *) data + n;
89                   stream->avail -= n;
90                   len -= n;
91                 }
92               if (stream->avail == 0)
93                 {
94                   if (full_write (stream->fd, stream->buffer, BUFSIZE) < BUFSIZE)
95                     error (EXIT_FAILURE, errno, _("error writing to %s"),
96                            stream->filename);
97                   stream->avail = BUFSIZE;
98                 }
99             }
100           while (len > 0);
101           #else /* optimized */
102           if (len < stream->avail)
103             {
104               /* Move the data into the buffer.  */
105               memcpy (stream->buffer + BUFSIZE - stream->avail, data, len);
106               stream->avail -= len;
107             }
108           else
109             {
110               /* Split the data into:
111                    - a first chunk, which is added to the buffer and output,
112                    - a series of chunks of size BUFSIZE, which can be output
113                      directly, without going through the buffer, and
114                    - a last chunk, which is copied to the buffer.  */
115               size_t n = stream->avail;
116               memcpy (stream->buffer + BUFSIZE - stream->avail, data, n);
117               data = (char *) data + n;
118               len -= n;
119               if (full_write (stream->fd, stream->buffer, BUFSIZE) < BUFSIZE)
120                 error (EXIT_FAILURE, errno, _("error writing to %s"),
121                        stream->filename);
122 
123               while (len >= BUFSIZE)
124                 {
125                   if (full_write (stream->fd, data, BUFSIZE) < BUFSIZE)
126                     error (EXIT_FAILURE, errno, _("error writing to %s"),
127                            stream->filename);
128                   data = (char *) data + BUFSIZE;
129                   len -= BUFSIZE;
130                 }
131 
132               if (len > 0)
133                 memcpy (stream->buffer, data, len);
134               stream->avail = BUFSIZE - len;
135             }
136           #endif
137           assert (stream->avail > 0);
138         }
139       else
140         {
141           /* Unbuffered.  */
142           if (full_write (stream->fd, data, len) < len)
143             error (EXIT_FAILURE, errno, _("error writing to %s"),
144                    stream->filename);
145         }
146     }
147 }
148 
149 static void
flush(fd_ostream_t stream,ostream_flush_scope_t scope)150 fd_ostream::flush (fd_ostream_t stream, ostream_flush_scope_t scope)
151 {
152   if (stream->buffer != NULL && stream->avail < BUFSIZE)
153     {
154       size_t filled = BUFSIZE - stream->avail;
155       if (full_write (stream->fd, stream->buffer, filled) < filled)
156         error (EXIT_FAILURE, errno, _("error writing to %s"), stream->filename);
157       stream->avail = BUFSIZE;
158     }
159   if (scope == FLUSH_ALL)
160     {
161       /* For streams connected to a disk file:  */
162       fsync (stream->fd);
163       #if HAVE_TCDRAIN
164       /* For streams connected to a terminal:  */
165       nonintr_tcdrain (stream->fd);
166       #endif
167     }
168 }
169 
170 static void
free(fd_ostream_t stream)171 fd_ostream::free (fd_ostream_t stream)
172 {
173   fd_ostream_flush (stream, FLUSH_THIS_STREAM);
174   free (stream->filename);
175   free (stream);
176 }
177 
178 /* Constructor.  */
179 
180 fd_ostream_t
fd_ostream_create(int fd,const char * filename,bool buffered)181 fd_ostream_create (int fd, const char *filename, bool buffered)
182 {
183   fd_ostream_t stream =
184     (struct fd_ostream_representation *)
185     xmalloc (sizeof (struct fd_ostream_representation)
186              + (buffered ? BUFSIZE : 0));
187 
188   stream->base.vtable = &fd_ostream_vtable;
189   stream->fd = fd;
190   stream->filename = xstrdup (filename);
191   if (buffered)
192     {
193       stream->buffer =
194         (char *) (void *) stream + sizeof (struct fd_ostream_representation);
195       stream->avail = BUFSIZE;
196     }
197   else
198     stream->buffer = NULL;
199 
200   return stream;
201 }
202