1 /*------------------------------------------------------------------------------
2  *
3  * Copyright (c) 2011-2021, EURid vzw. All rights reserved.
4  * The YADIFA TM software product is provided under the BSD 3-clause license:
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  *        * Redistributions of source code must retain the above copyright
11  *          notice, this list of conditions and the following disclaimer.
12  *        * Redistributions in binary form must reproduce the above copyright
13  *          notice, this list of conditions and the following disclaimer in the
14  *          documentation and/or other materials provided with the distribution.
15  *        * Neither the name of EURid nor the names of its contributors may be
16  *          used to endorse or promote products derived from this software
17  *          without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  *
31  *------------------------------------------------------------------------------
32  *
33  */
34 
35 /** @defgroup streaming Streams
36  *  @ingroup dnscore
37  *  @brief
38  *
39  *
40  *
41  * @{
42  *
43  *----------------------------------------------------------------------------*/
44 #include "dnscore/dnscore-config.h"
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <sys/types.h>
48 #include <sys/stat.h>
49 #include <fcntl.h>
50 #include <unistd.h>
51 
52 #include "dnscore/file_output_stream.h"
53 #include "dnscore/fdtools.h"
54 
55 /*
56  * This structure is supposed to match the output_stream one
57  * It helps using the void* data as an int without a INT_AT(x) kind of macro
58  */
59 
60 typedef struct file_output_stream file_output_stream;
61 
62 struct file_output_stream
63 {
64 
65     union
66     {
67         void *voidp;
68         int fd;
69     } data;
70 
71     const output_stream_vtbl* vtbl;
72 };
73 
74 static ya_result
file_output_stream_write(output_stream * stream_,const u8 * buffer,u32 len)75 file_output_stream_write(output_stream* stream_, const u8* buffer, u32 len)
76 {
77     const file_output_stream* stream = (file_output_stream*)stream_;
78 
79     const u8* start = buffer;
80 
81     while(len > 0)
82     {
83         ssize_t ret = write(stream->data.fd, buffer, len);
84 
85         if(ret <= 0)
86         {
87             int err = errno;
88 
89             if(err == EINTR)
90             {
91                 continue;
92             }
93 
94             if(err == EAGAIN)
95             {
96 #if __FreeBSD__ || __OpenBSD__ || __APPLE__
97                 int oldflags = fcntl (stream->data.fd, F_GETFL, 0);
98                 if(oldflags < 0)
99                 {
100                      return MAKE_ERRNO_ERROR(err);
101                 }
102 #endif
103                 continue;
104             }
105 
106             /* error */
107             return MAKE_ERRNO_ERROR(err);
108         }
109 
110         buffer += ret;
111         len -= (u32)ret;
112     }
113 
114     return (ya_result)(buffer - start);
115 }
116 
117 static ya_result
file_output_stream_writefully(output_stream * stream_,const u8 * buffer,u32 len)118 file_output_stream_writefully(output_stream* stream_, const u8* buffer, u32 len)
119 {
120     const file_output_stream* stream = (file_output_stream*)stream_;
121 
122     const u8* start = buffer;
123 
124     while(len > 0)
125     {
126         ssize_t ret = write(stream->data.fd, buffer, len);
127 
128         if(ret <= 0)
129         {
130             int err = errno;
131 
132             if(err == EINTR)
133             {
134                 continue;
135             }
136 
137             if(err == EAGAIN)
138             {
139 #if __FreeBSD__ || __OpenBSD__ || __APPLE__
140                 int oldflags = fcntl (stream->data.fd, F_GETFL, 0);
141                 if(oldflags < 0)
142                 {
143                      return MAKE_ERRNO_ERROR(err);
144                 }
145 #endif
146                 continue;
147             }
148 
149             if(err == ENOSPC)
150             {
151                 // the disk is full : wait a bit, hope the admin catches it, try again later
152                 sleep((rand()&7) + 1);
153                 continue;
154             }
155 
156             /* error */
157             return MAKE_ERRNO_ERROR(err);
158         }
159 
160         buffer += ret;
161         len -= (u32)ret;
162     }
163 
164     return (ya_result)(buffer - start);
165 }
166 
167 static ya_result
file_output_stream_flush(output_stream * stream_)168 file_output_stream_flush(output_stream* stream_)
169 {
170     file_output_stream* stream = (file_output_stream*)stream_;
171 
172     if(fsync_ex(stream->data.fd) == 0) /* or fdatasync ... maybe it would be slightly better */
173     {
174         return SUCCESS;
175     }
176 
177     return ERRNO_ERROR;
178 }
179 
180 static void
file_output_stream_close(output_stream * stream_)181 file_output_stream_close(output_stream* stream_)
182 {
183     file_output_stream* stream = (file_output_stream*)stream_;
184 
185     /* don't, it's only for a test that I did this assert((stream->data.fd < 0)||(stream->data.fd >2)); */
186 
187     if(stream->data.fd != -1)   /* harmless close but still ... */
188     {
189         close_ex(stream->data.fd);
190     }
191 
192     output_stream_set_void(stream_);
193 }
194 
195 static void
file_output_stream_noclose(output_stream * stream_)196 file_output_stream_noclose(output_stream* stream_)
197 {
198     file_output_stream* stream = (file_output_stream*)stream_;
199     stream->data.fd = -1;
200     output_stream_set_void(stream_);
201 }
202 
203 static const output_stream_vtbl file_output_stream_noclose_vtbl ={
204     file_output_stream_write,
205     file_output_stream_flush,
206     file_output_stream_noclose,
207     "file_output_stream-noclose",
208 };
209 
210 static const output_stream_vtbl file_output_stream_vtbl ={
211     file_output_stream_write,
212     file_output_stream_flush,
213     file_output_stream_close,
214     "file_output_stream",
215 };
216 
217 static const output_stream_vtbl file_full_output_stream_vtbl ={
218     file_output_stream_writefully,
219     file_output_stream_flush,
220     file_output_stream_close,
221     "file_output_stream",
222 };
223 
224 ya_result
file_output_stream_open(output_stream * stream,const char * filename)225 file_output_stream_open(output_stream* stream, const char* filename)
226 {
227     ya_result ret;
228     ret = file_output_stream_open_ex(stream, filename, O_RDWR|O_CLOEXEC, 0600);
229     return ret;
230 }
231 
232 ya_result
file_output_stream_create(output_stream * stream,const char * filename,mode_t mode)233 file_output_stream_create(output_stream* stream, const char* filename, mode_t mode)
234 {
235     ya_result ret;
236     ret = file_output_stream_open_ex(stream, filename, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
237     return ret;
238 }
239 
240 ya_result
file_output_stream_create_excl(output_stream * stream,const char * filename,mode_t mode)241 file_output_stream_create_excl(output_stream* stream, const char* filename, mode_t mode)
242 {
243     ya_result ret;
244     ret = file_output_stream_open_ex(stream, filename, O_RDWR | O_CREAT | O_TRUNC | O_EXCL | O_CLOEXEC, mode);
245     return ret;
246 }
247 
248 ya_result
file_output_stream_set_full_writes(output_stream * stream,bool full_writes)249 file_output_stream_set_full_writes(output_stream* stream, bool full_writes)
250 {
251     if(is_fd_output_stream(stream))
252     {
253         if(full_writes)
254         {
255             stream->vtbl = &file_full_output_stream_vtbl;
256         }
257         else
258         {
259             stream->vtbl = &file_output_stream_vtbl;
260         }
261         return SUCCESS;
262     }
263     else
264     {
265         return INVALID_STATE_ERROR;
266     }
267 }
268 
269 ya_result
file_output_stream_open_ex(output_stream * stream_,const char * filename,int flags,mode_t mode)270 file_output_stream_open_ex(output_stream* stream_, const char* filename, int flags, mode_t mode)
271 {
272     yassert(sizeof(void*) >= sizeof(int));
273 
274     int fd = open_create_ex(filename, flags, mode);
275 
276     if(fd < 0)
277     {
278         return ERRNO_ERROR;
279     }
280 
281 #if (_XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L) && !defined(__gnu__hurd__)
282     posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
283 #endif
284 
285     file_output_stream* stream = (file_output_stream*)stream_;
286     stream->data.fd = fd;
287 
288     stream->vtbl = &file_output_stream_vtbl;
289 
290     return SUCCESS;
291 }
292 
293 ya_result
file_output_stream_open_ex_nolog(output_stream * stream_,const char * filename,int flags,mode_t mode)294 file_output_stream_open_ex_nolog(output_stream* stream_, const char* filename, int flags, mode_t mode)
295 {
296     yassert(sizeof(void*) >= sizeof(int));
297 
298     int fd = open_create_ex_nolog(filename, flags, mode);
299 
300     if(fd < 0)
301     {
302         return ERRNO_ERROR;
303     }
304 
305 #if (_XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L) && !defined(__gnu__hurd__)
306     posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
307 #endif
308 
309     file_output_stream* stream = (file_output_stream*)stream_;
310     stream->data.fd = fd;
311 
312     stream->vtbl = &file_output_stream_vtbl;
313 
314     return SUCCESS;
315 }
316 
317 void
file_output_stream_close_nolog(output_stream * stream_)318 file_output_stream_close_nolog(output_stream* stream_)
319 {
320     file_output_stream* stream = (file_output_stream*)stream_;
321 
322     /* don't, it's only for a test that I did this assert((stream->data.fd < 0)||(stream->data.fd >2)); */
323 
324     if(stream->data.fd != -1)   /* harmless close but still ... */
325     {
326         close_ex(stream->data.fd);
327         stream->data.fd = -1;
328     }
329 
330     output_stream_set_void(stream_);
331 }
332 
333 ya_result
fd_output_stream_attach(output_stream * stream_,int fd)334 fd_output_stream_attach(output_stream* stream_, int fd)
335 {
336     yassert(sizeof(void*) >= sizeof(int));
337 
338     file_output_stream* stream = (file_output_stream*)stream_;
339     stream->data.fd = fd;
340 
341     stream->vtbl = &file_output_stream_vtbl;
342 
343     return SUCCESS;
344 }
345 
346 ya_result
fd_output_stream_attach_noclose(output_stream * stream_,int fd)347 fd_output_stream_attach_noclose(output_stream* stream_, int fd)
348 {
349     yassert(sizeof(void*) >= sizeof(int));
350 
351     file_output_stream* stream = (file_output_stream*)stream_;
352     stream->data.fd = fd;
353 
354     stream->vtbl = &file_output_stream_noclose_vtbl;
355 
356     return SUCCESS;
357 }
358 
359 void
fd_output_stream_detach(output_stream * stream_)360 fd_output_stream_detach(output_stream* stream_)
361 {
362     yassert(sizeof(void*) >= sizeof(int));
363 
364     file_output_stream* stream = (file_output_stream*)stream_;
365     stream->data.fd = -1;
366 }
367 
368 ya_result
fd_output_stream_get_filedescriptor(output_stream * stream)369 fd_output_stream_get_filedescriptor(output_stream* stream)
370 {
371     file_output_stream *fos = (file_output_stream*)stream;
372     return fos->data.fd;
373 }
374 
375 bool
is_fd_output_stream(output_stream * stream_)376 is_fd_output_stream(output_stream* stream_)
377 {
378     file_output_stream* stream = (file_output_stream*)stream_;
379     return (stream != NULL) && ((stream->vtbl == &file_output_stream_vtbl) || (stream->vtbl == &file_full_output_stream_vtbl));
380 }
381 
fd_output_stream_get_size(output_stream * stream_)382 s64 fd_output_stream_get_size(output_stream* stream_)
383 {
384     file_output_stream* stream = (file_output_stream*)stream_;
385 
386     struct stat s;
387 
388     if(fstat(stream->data.fd, &s) >= 0)
389     {
390         if(S_ISREG(s.st_mode))
391         {
392             return s.st_size;
393         }
394     }
395 
396     return (s64)ERRNO_ERROR;
397 }
398 
file_output_steam_advise_sequential(output_stream * stream_)399 void file_output_steam_advise_sequential(output_stream* stream_)
400 {
401     file_output_stream* stream = (file_output_stream*)stream_;
402 
403 #if (_XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L) && !defined(__gnu__hurd__)
404     if(stream->data.fd >= 0)
405     {
406         posix_fadvise(stream->data.fd, 0, 0, POSIX_FADV_SEQUENTIAL);
407     }
408 #endif
409 }
410 
411 /** @} */
412