fbuf: New data structure for buffered file I/O.
[pspp] / src / libpspp / fbuf.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2017 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 #include <config.h>
18
19 #include "fbuf.h"
20
21 #include <assert.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <limits.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30
31 #include "libpspp/assertion.h"
32 #include "libpspp/cast.h"
33
34 #include "gl/intprops.h"
35 #include "gl/minmax.h"
36 #include "gl/xalloc.h"
37 #include "gl/xsize.h"
38
39 #define FBUF_SIZE 4096
40
41 struct fbuf_class
42   {
43     int (*close) (struct fbuf *);
44
45     /* Reads up to N bytes from FBUF's underlying file descriptor into BUFFER.
46        Returns the number of bytes read, if successful, zero at end of file, or
47        a negative errno value on error. */
48     int (*read) (struct fbuf *fbuf, void *buffer, size_t n);
49
50     /* Writes the N bytes in BUFFER to FBUF's underlying file descriptor.  The
51      * caller guarantees N > 0.  Returns the number of bytes written, if
52      * successful, otherwise a negative errno value. */
53     int (*write) (struct fbuf *fbuf, const void *buffer, size_t n);
54
55     /* Seeks to byte offset OFFSET in FBUF's underlying file descriptor.
56        Returns 0 if successful, otherwise a positive errno value.  Returns
57        -ESPIPE if FBUF does not support positioning. */
58     int (*seek) (struct fbuf *fbuf, off_t offset);
59
60     /* Returns the current byte offset in FBUF's underlying file descriptor, or
61        a negative errno value on error.  Returns -ESPIPE
62        if FBUF does not support positioning. */
63     off_t (*tell) (struct fbuf *fbuf);
64
65     /* Returns the size of the file underlying FBUF, in bytes, or a negative
66        errno value on error.  Returns -ESPIPE if FBUF does not support
67        positioning. */
68     off_t (*get_size) (struct fbuf *fbuf);
69   };
70
71 struct fbuf_fd
72   {
73     struct fbuf up;
74     int fd;
75   };
76
77 static void
78 fbuf_init (struct fbuf *fbuf, const struct fbuf_class *class, off_t offset)
79 {
80   memset (fbuf, 0, sizeof *fbuf);
81   fbuf->class = class;
82   fbuf->buffer = xmalloc (FBUF_SIZE);
83   fbuf->offset = offset >= 0 ? offset : TYPE_MINIMUM (off_t);
84 }
85
86 /* Closes FBUF.  Returns 0 if successful, otherwise a positive errno value that
87    represents an error reading or writing the underlying fd (which could have
88    happened earlier or as part of the final flush implied by closing). */
89 int
90 fbuf_close (struct fbuf *fbuf)
91 {
92   if (!fbuf)
93     return 0;
94
95   fbuf_flush (fbuf);
96   int status = fbuf->status;
97   int error = fbuf->class->close (fbuf);
98   return status ? status : error;
99 }
100
101 /* Returns FBUF's error status, which is 0 if no error has been recorded and
102    otherwise a positive errno value.  The error, if any, reflects difficulty
103    reading or writing the underlying fd.  */
104 int
105 fbuf_get_status (const struct fbuf *fbuf)
106 {
107   return fbuf->status;
108 }
109
110 /* Clears any previously recorded error status. */
111 void
112 fbuf_clear_status (struct fbuf *fbuf)
113 {
114   fbuf->status = 0;
115 }
116
117 /* Returns the length of the file backing FBUF, in bytes, or a negative errno
118    value on error.  A return value of -ESPIPE indicates that the underlying
119    file is not seekable, i.e. does not have a length. */
120 off_t
121 fbuf_get_size (const struct fbuf *fbuf_)
122 {
123   struct fbuf *fbuf = CONST_CAST (struct fbuf *, fbuf_);
124   return fbuf->class->get_size (fbuf);
125 }
126
127 /* Returns true if FBUF is seekable, false otherwise. */
128 int
129 fbuf_is_seekable (const struct fbuf *fbuf)
130 {
131   return fbuf_tell (fbuf) != -ESPIPE;
132 }
133
134 /* Attempts to flush any data buffered for writing to the underlying file.
135    Returns 0 if successful (which includes the case where FBUF is not in write
136    mode) or a positive errno value if there is a write error. */
137 int
138 fbuf_flush (struct fbuf *fbuf)
139 {
140   for (;;)
141     {
142       assert (fbuf->write_tail <= fbuf->write_head);
143       int n = fbuf->write_head - fbuf->write_tail;
144       if (n <= 0)
145         return 0;
146
147       int retval = fbuf->class->write (fbuf, fbuf->write_tail, n);
148       if (retval < 0)
149         {
150           fbuf->status = -retval;
151           return fbuf->status;
152         }
153
154       fbuf->write_tail += n;
155       if (fbuf->offset >= 0)
156         fbuf->offset += n;
157       if (fbuf->write_tail >= fbuf->write_head)
158         {
159           fbuf->write_tail = fbuf->write_head = fbuf->buffer;
160           return 0;
161         }
162     }
163 }
164
165 /* Returns the byte offset in FBUF's file of the read byte to be read or
166    written, or a negative errno value if the offset cannot be determined.
167    Returns -ESPIPE if the underlying file is not seekable. */
168 off_t
169 fbuf_tell (const struct fbuf *fbuf_)
170 {
171   struct fbuf *fbuf = CONST_CAST (struct fbuf *, fbuf_);
172
173   if (fbuf->offset < 0)
174     {
175       if (fbuf->offset != -ESPIPE)
176         fbuf->offset = fbuf->class->tell (fbuf);
177
178       if (fbuf->offset < 0)
179         return fbuf->offset;
180     }
181
182   return (fbuf->offset
183           - (fbuf->read_head - fbuf->read_tail)
184           + (fbuf->write_head - fbuf->write_tail));
185 }
186
187 /* Attempts to seek in FBUF such that the next byte to be read or written will
188    be at byte offset OFFSET.  Returns 0 if successful or a negative errno value
189    otherwise.  Returns -ESPIPE if the underlying file is not seekable. */
190 int
191 fbuf_seek (struct fbuf *fbuf, off_t offset)
192 {
193   if (offset < 0)
194     return EINVAL;
195
196   int error = fbuf_flush (fbuf);
197   if (error)
198     return error;
199
200   fbuf->read_tail = fbuf->read_head = NULL;
201   fbuf->write_tail = fbuf->write_head = fbuf->write_end = NULL;
202
203   error = fbuf->class->seek (fbuf, offset);
204   if (!error)
205     fbuf->offset = offset;
206   return error;
207 }
208
209 /* Attempts to write the SIZE bytes of data in DATA to FBUF.  On success,
210    returns the number of bytes actually written (possibly less than SIZE), and
211    on failure returns a negative errno value.  Returns 0 only if SIZE is 0.
212
213    If the last I/O operation on FBUF was a read, the caller must call
214    fbuf_seek() before this function. */
215 ssize_t
216 fbuf_write (struct fbuf *fbuf, const void *data_, size_t size)
217 {
218   const uint8_t *data = data_;
219   size_t n_written = 0;
220   while (size > 0)
221     {
222       size_t avail = fbuf->write_end - fbuf->write_head;
223       size_t chunk = MIN (avail, size);
224       if (chunk)
225         {
226           if (chunk < FBUF_SIZE)
227             {
228               /* Normal case: copy into buffer. */
229               memcpy (fbuf->write_head, data, chunk);
230               fbuf->write_head += chunk;
231             }
232           else
233             {
234               /* Buffer is empty and we're writing more data than will fit in
235                  the buffer.  Skip the buffer. */
236               chunk = MIN (INT_MAX, size);
237               int retval = fbuf->class->write (fbuf, data, chunk);
238               if (retval < 0)
239                 return n_written ? n_written : -retval;
240               if (fbuf->offset >= 0)
241                 fbuf->offset += retval;
242             }
243           data += chunk;
244           size -= chunk;
245           n_written += chunk;
246         }
247       else
248         {
249           int error = fbuf_flush (fbuf);
250           if (error)
251             return n_written ? n_written : -error;
252
253           /* Use fbuf_seek() to switch between reading and writing. */
254           assert (!fbuf->read_head);
255
256           if (!fbuf->write_tail)
257             {
258               fbuf->write_tail = fbuf->write_head = fbuf->buffer;
259               fbuf->write_end = fbuf->buffer + FBUF_SIZE;
260             }
261         }
262     }
263   return n_written;
264 }
265
266 int
267 fbuf_getc__ (struct fbuf *fbuf)
268 {
269   uint8_t c;
270   int retval = fbuf_read (fbuf, &c, 1);
271   return retval == 1 ? c : EOF;
272 }
273
274 /* Attempts to read SIZE bytes of data from FBUF into DATA.  On success,
275    returns the number of bytes actually read (possibly less than SIZE), and on
276    failure returns a negative errno value.  Returns 0 only if end of file was
277    reached before any data could be read.
278
279    If the last I/O operation on FBUF was a write, the caller must call
280    fbuf_seek() before this function. */
281 ssize_t
282 fbuf_read (struct fbuf *fbuf, void *data_, size_t size)
283 {
284   uint8_t *data = data_;
285   size_t n_read = 0;
286   while (size > 0)
287     {
288       size_t avail = fbuf->read_head - fbuf->read_tail;
289       size_t chunk = MIN (avail, size);
290       if (chunk)
291         {
292           /* Copy out of buffer. */
293           memcpy (data, fbuf->read_tail, chunk);
294           fbuf->read_tail += chunk;
295           data += chunk;
296           size -= chunk;
297           n_read += chunk;
298         }
299       else
300         {
301           /* Buffer is empty. */
302
303           /* Use fbuf_seek() to switch between reading and writing. */
304           assert (!fbuf->write_head);
305
306           if (size < FBUF_SIZE)
307             {
308               /* Normal case: fill the buffer. */
309               int retval = fbuf->class->read (fbuf, fbuf->buffer, FBUF_SIZE);
310               if (retval < 0)
311                 {
312                   fbuf->status = -retval;
313                   return n_read ? n_read : retval;
314                 }
315               else if (retval == 0)
316                 return n_read;
317               if (fbuf->offset >= 0)
318                 fbuf->offset += retval;
319               fbuf->read_tail = fbuf->buffer;
320               fbuf->read_head = fbuf->buffer + retval;
321             }
322           else
323             {
324               /* Caller's read buffer is bigger than FBUF_SIZE.  Use it
325                  directly. */
326               int retval = fbuf->class->read (fbuf, data, size);
327               if (retval < 0)
328                 {
329                   fbuf->status = -retval;
330                   return n_read ? n_read : retval;
331                 }
332               else if (retval == 0)
333                 return n_read;
334               if (fbuf->offset >= 0)
335                 fbuf->offset += retval;
336               data += retval;
337               size -= retval;
338               n_read += retval;
339             }
340         }
341     }
342   return n_read;
343 }
344 \f
345 /* Implementation of file-based fbuf. */
346
347 static const struct fbuf_class fbuf_fd_class;
348
349 /* Returns a new fbuf that represents FD. */
350 struct fbuf *
351 fbuf_open_fd (int fd)
352 {
353   struct fbuf_fd *fbuf = xmalloc (sizeof *fbuf);
354   fbuf_init (&fbuf->up, &fbuf_fd_class, -1);
355   fbuf->fd = fd;
356   return &fbuf->up;
357 }
358
359 /* Opens FILENAME with FLAGS and MODE and stores a new fbuf that represents it
360    into *FBUFP.  Returns 0 on success, or a positive errno value on failure.
361    ON failure, *FBUFP will be NULL. */
362 int
363 fbuf_open_file (const char *filename, int flags, mode_t mode,
364                 struct fbuf **fbufp)
365 {
366   int fd = open (filename, flags, mode);
367   if (fd < 0)
368     {
369       *fbufp = NULL;
370       return errno;
371     }
372   *fbufp = fbuf_open_fd (fd);
373   return 0;
374 }
375
376 static struct fbuf_fd *
377 fbuf_fd_cast (const struct fbuf *fbuf)
378 {
379   assert (fbuf->class == &fbuf_fd_class);
380   return UP_CAST (fbuf, struct fbuf_fd, up);
381 }
382
383 static int
384 fbuf_fd_close (struct fbuf *fbuf_)
385 {
386   struct fbuf_fd *fbuf = fbuf_fd_cast (fbuf_);
387   int retval = close (fbuf->fd) == EOF ? errno : 0;
388   free (fbuf);
389   return retval;
390 }
391
392 static int
393 fbuf_fd_read (struct fbuf *fbuf_, void *buffer, size_t n)
394 {
395   struct fbuf_fd *fbuf = fbuf_fd_cast (fbuf_);
396   int retval = read (fbuf->fd, buffer, n);
397   return retval >= 0 ? retval : -errno;
398 }
399
400 static int
401 fbuf_fd_write (struct fbuf *fbuf_, const void *buffer, size_t n)
402 {
403   struct fbuf_fd *fbuf = fbuf_fd_cast (fbuf_);
404   int retval = write (fbuf->fd, buffer, n);
405   return retval > 0 ? retval : -errno;
406 }
407
408 static int
409 fbuf_fd_seek (struct fbuf *fbuf_, off_t offset)
410 {
411   struct fbuf_fd *fbuf = fbuf_fd_cast (fbuf_);
412   return lseek (fbuf->fd, offset, SEEK_SET) < 0 ? errno : 0;
413 }
414
415 static off_t
416 fbuf_fd_tell (struct fbuf *fbuf_)
417 {
418   struct fbuf_fd *fbuf = fbuf_fd_cast (fbuf_);
419   off_t offset = lseek (fbuf->fd, 0, SEEK_CUR);
420   return offset >= 0 ? offset : -errno;
421 }
422
423 static off_t
424 fbuf_fd_get_size (struct fbuf *fbuf_)
425 {
426   struct fbuf_fd *fbuf = fbuf_fd_cast (fbuf_);
427   off_t offset = lseek (fbuf->fd, 0, SEEK_END);
428   return offset >= 0 ? offset : -errno;
429 }
430
431 static const struct fbuf_class fbuf_fd_class =
432   {
433     fbuf_fd_close,
434     fbuf_fd_read,
435     fbuf_fd_write,
436     fbuf_fd_seek,
437     fbuf_fd_tell,
438     fbuf_fd_get_size,
439   };
440 \f
441 struct fbuf_memory
442   {
443     struct fbuf up;
444     uint8_t *data;
445     size_t size, allocated;
446   };
447
448 static const struct fbuf_class fbuf_memory_class;
449
450 /* Takes ownership of the N bytes of data at DATA, which must have been
451    allocated with malloc(), as a memory buffer and makes it the backing for the
452    newly returned fbuf.  Initially, the fbuf is positioned at the beginning of
453    the data, so that reads will read from it and writes will overwrite it.  (To
454    append, use fbuf_seek() to seek to the end.)
455
456    Writes beyond the end will reallocate the buffer.  Closing the returned fbuf
457    will free the buffer. */
458 struct fbuf *
459 fbuf_open_memory (void *data, size_t n)
460 {
461   struct fbuf_memory *fbuf = xmalloc (sizeof *fbuf);
462   fbuf_init (&fbuf->up, &fbuf_memory_class, 0);
463   fbuf->data = data;
464   fbuf->size = n;
465   fbuf->allocated = n;
466   return &fbuf->up;
467 }
468
469 static struct fbuf_memory *
470 fbuf_memory_cast (const struct fbuf *fbuf)
471 {
472   assert (fbuf->class == &fbuf_memory_class);
473   return UP_CAST (fbuf, struct fbuf_memory, up);
474 }
475
476 static int
477 fbuf_memory_close (struct fbuf *fbuf_)
478 {
479   struct fbuf_memory *fbuf = fbuf_memory_cast (fbuf_);
480   free (fbuf->data);
481   free (fbuf);
482   return 0;
483 }
484
485 static int
486 fbuf_memory_read (struct fbuf *fbuf_, void *buffer, size_t n)
487 {
488   struct fbuf_memory *fbuf = fbuf_memory_cast (fbuf_);
489   if (fbuf->up.offset >= fbuf->size)
490     return 0;
491
492   size_t chunk = MIN (n, fbuf->size - fbuf->up.offset);
493   memcpy (buffer, fbuf->data + fbuf->up.offset, chunk);
494   return chunk;
495 }
496
497 static int
498 fbuf_memory_write (struct fbuf *fbuf_, const void *buffer, size_t n)
499 {
500   struct fbuf_memory *fbuf = fbuf_memory_cast (fbuf_);
501
502   /* Fail if write would cause the memory block to exceed SIZE_MAX bytes. */
503   size_t end = xsum (fbuf->up.offset, n);
504   if (size_overflow_p (end))
505     return -EFBIG;
506
507   /* Expand fbuf->data if necessary to hold the write. */
508   if (end > fbuf->allocated)
509     {
510       fbuf->allocated = end < SIZE_MAX / 2 ? end * 2 : end;
511       fbuf->data = xrealloc (fbuf->data, fbuf->allocated);
512     }
513
514   /* Zero-pad to reach the current offset (although this is necessary only if
515      there has been a seek past the end), then copy in the new data. */
516   if (fbuf->up.offset > fbuf->size)
517     memset (fbuf->data + fbuf->size, 0, fbuf->up.offset - fbuf->size);
518   memcpy (fbuf->data + fbuf->up.offset, buffer, n);
519
520   if (end > fbuf->size)
521     fbuf->size = end;
522
523   return n;
524 }
525
526 static int
527 fbuf_memory_seek (struct fbuf *fbuf UNUSED, off_t offset UNUSED)
528 {
529   return 0;
530 }
531
532 static off_t
533 fbuf_memory_tell (struct fbuf *fbuf UNUSED)
534 {
535   NOT_REACHED ();
536 }
537
538 static off_t
539 fbuf_memory_get_size (struct fbuf *fbuf_)
540 {
541   struct fbuf_memory *fbuf = fbuf_memory_cast (fbuf_);
542   return fbuf->size;
543 }
544
545 static const struct fbuf_class fbuf_memory_class =
546   {
547     fbuf_memory_close,
548     fbuf_memory_read,
549     fbuf_memory_write,
550     fbuf_memory_seek,
551     fbuf_memory_tell,
552     fbuf_memory_get_size,
553   };