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