X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Flibpspp%2Ffbuf.c;fp=src%2Flibpspp%2Ffbuf.c;h=a3758cebd271d2e9888ce1c1bdcafa37da92b335;hb=7344ce41487d4294726d597b99bbef5c3cfa1ce6;hp=0000000000000000000000000000000000000000;hpb=3db83b515247dca69abbf7ad05d3dbdfec4b524c;p=pspp diff --git a/src/libpspp/fbuf.c b/src/libpspp/fbuf.c new file mode 100644 index 0000000000..a3758cebd2 --- /dev/null +++ b/src/libpspp/fbuf.c @@ -0,0 +1,553 @@ +/* PSPP - a program for statistical analysis. + Copyright (C) 2017 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include "fbuf.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libpspp/assertion.h" +#include "libpspp/cast.h" + +#include "gl/intprops.h" +#include "gl/minmax.h" +#include "gl/xalloc.h" +#include "gl/xsize.h" + +#define FBUF_SIZE 4096 + +struct fbuf_class + { + int (*close) (struct fbuf *); + + /* Reads up to N bytes from FBUF's underlying file descriptor into BUFFER. + Returns the number of bytes read, if successful, zero at end of file, or + a negative errno value on error. */ + int (*read) (struct fbuf *fbuf, void *buffer, size_t n); + + /* Writes the N bytes in BUFFER to FBUF's underlying file descriptor. The + * caller guarantees N > 0. Returns the number of bytes written, if + * successful, otherwise a negative errno value. */ + int (*write) (struct fbuf *fbuf, const void *buffer, size_t n); + + /* Seeks to byte offset OFFSET in FBUF's underlying file descriptor. + Returns 0 if successful, otherwise a positive errno value. Returns + -ESPIPE if FBUF does not support positioning. */ + int (*seek) (struct fbuf *fbuf, off_t offset); + + /* Returns the current byte offset in FBUF's underlying file descriptor, or + a negative errno value on error. Returns -ESPIPE + if FBUF does not support positioning. */ + off_t (*tell) (struct fbuf *fbuf); + + /* Returns the size of the file underlying FBUF, in bytes, or a negative + errno value on error. Returns -ESPIPE if FBUF does not support + positioning. */ + off_t (*get_size) (struct fbuf *fbuf); + }; + +struct fbuf_fd + { + struct fbuf up; + int fd; + }; + +static void +fbuf_init (struct fbuf *fbuf, const struct fbuf_class *class, off_t offset) +{ + memset (fbuf, 0, sizeof *fbuf); + fbuf->class = class; + fbuf->buffer = xmalloc (FBUF_SIZE); + fbuf->offset = offset >= 0 ? offset : TYPE_MINIMUM (off_t); +} + +/* Closes FBUF. Returns 0 if successful, otherwise a positive errno value that + represents an error reading or writing the underlying fd (which could have + happened earlier or as part of the final flush implied by closing). */ +int +fbuf_close (struct fbuf *fbuf) +{ + if (!fbuf) + return 0; + + fbuf_flush (fbuf); + int status = fbuf->status; + int error = fbuf->class->close (fbuf); + return status ? status : error; +} + +/* Returns FBUF's error status, which is 0 if no error has been recorded and + otherwise a positive errno value. The error, if any, reflects difficulty + reading or writing the underlying fd. */ +int +fbuf_get_status (const struct fbuf *fbuf) +{ + return fbuf->status; +} + +/* Clears any previously recorded error status. */ +void +fbuf_clear_status (struct fbuf *fbuf) +{ + fbuf->status = 0; +} + +/* Returns the length of the file backing FBUF, in bytes, or a negative errno + value on error. A return value of -ESPIPE indicates that the underlying + file is not seekable, i.e. does not have a length. */ +off_t +fbuf_get_size (const struct fbuf *fbuf_) +{ + struct fbuf *fbuf = CONST_CAST (struct fbuf *, fbuf_); + return fbuf->class->get_size (fbuf); +} + +/* Returns true if FBUF is seekable, false otherwise. */ +int +fbuf_is_seekable (const struct fbuf *fbuf) +{ + return fbuf_tell (fbuf) != -ESPIPE; +} + +/* Attempts to flush any data buffered for writing to the underlying file. + Returns 0 if successful (which includes the case where FBUF is not in write + mode) or a positive errno value if there is a write error. */ +int +fbuf_flush (struct fbuf *fbuf) +{ + for (;;) + { + assert (fbuf->write_tail <= fbuf->write_head); + int n = fbuf->write_head - fbuf->write_tail; + if (n <= 0) + return 0; + + int retval = fbuf->class->write (fbuf, fbuf->write_tail, n); + if (retval < 0) + { + fbuf->status = -retval; + return fbuf->status; + } + + fbuf->write_tail += n; + if (fbuf->offset >= 0) + fbuf->offset += n; + if (fbuf->write_tail >= fbuf->write_head) + { + fbuf->write_tail = fbuf->write_head = fbuf->buffer; + return 0; + } + } +} + +/* Returns the byte offset in FBUF's file of the read byte to be read or + written, or a negative errno value if the offset cannot be determined. + Returns -ESPIPE if the underlying file is not seekable. */ +off_t +fbuf_tell (const struct fbuf *fbuf_) +{ + struct fbuf *fbuf = CONST_CAST (struct fbuf *, fbuf_); + + if (fbuf->offset < 0) + { + if (fbuf->offset != -ESPIPE) + fbuf->offset = fbuf->class->tell (fbuf); + + if (fbuf->offset < 0) + return fbuf->offset; + } + + return (fbuf->offset + - (fbuf->read_head - fbuf->read_tail) + + (fbuf->write_head - fbuf->write_tail)); +} + +/* Attempts to seek in FBUF such that the next byte to be read or written will + be at byte offset OFFSET. Returns 0 if successful or a negative errno value + otherwise. Returns -ESPIPE if the underlying file is not seekable. */ +int +fbuf_seek (struct fbuf *fbuf, off_t offset) +{ + if (offset < 0) + return EINVAL; + + int error = fbuf_flush (fbuf); + if (error) + return error; + + fbuf->read_tail = fbuf->read_head = NULL; + fbuf->write_tail = fbuf->write_head = fbuf->write_end = NULL; + + error = fbuf->class->seek (fbuf, offset); + if (!error) + fbuf->offset = offset; + return error; +} + +/* Attempts to write the SIZE bytes of data in DATA to FBUF. On success, + returns the number of bytes actually written (possibly less than SIZE), and + on failure returns a negative errno value. Returns 0 only if SIZE is 0. + + If the last I/O operation on FBUF was a read, the caller must call + fbuf_seek() before this function. */ +ssize_t +fbuf_write (struct fbuf *fbuf, const void *data_, size_t size) +{ + const uint8_t *data = data_; + size_t n_written = 0; + while (size > 0) + { + size_t avail = fbuf->write_end - fbuf->write_head; + size_t chunk = MIN (avail, size); + if (chunk) + { + if (chunk < FBUF_SIZE) + { + /* Normal case: copy into buffer. */ + memcpy (fbuf->write_head, data, chunk); + fbuf->write_head += chunk; + } + else + { + /* Buffer is empty and we're writing more data than will fit in + the buffer. Skip the buffer. */ + chunk = MIN (INT_MAX, size); + int retval = fbuf->class->write (fbuf, data, chunk); + if (retval < 0) + return n_written ? n_written : -retval; + if (fbuf->offset >= 0) + fbuf->offset += retval; + } + data += chunk; + size -= chunk; + n_written += chunk; + } + else + { + int error = fbuf_flush (fbuf); + if (error) + return n_written ? n_written : -error; + + /* Use fbuf_seek() to switch between reading and writing. */ + assert (!fbuf->read_head); + + if (!fbuf->write_tail) + { + fbuf->write_tail = fbuf->write_head = fbuf->buffer; + fbuf->write_end = fbuf->buffer + FBUF_SIZE; + } + } + } + return n_written; +} + +int +fbuf_getc__ (struct fbuf *fbuf) +{ + uint8_t c; + int retval = fbuf_read (fbuf, &c, 1); + return retval == 1 ? c : EOF; +} + +/* Attempts to read SIZE bytes of data from FBUF into DATA. On success, + returns the number of bytes actually read (possibly less than SIZE), and on + failure returns a negative errno value. Returns 0 only if end of file was + reached before any data could be read. + + If the last I/O operation on FBUF was a write, the caller must call + fbuf_seek() before this function. */ +ssize_t +fbuf_read (struct fbuf *fbuf, void *data_, size_t size) +{ + uint8_t *data = data_; + size_t n_read = 0; + while (size > 0) + { + size_t avail = fbuf->read_head - fbuf->read_tail; + size_t chunk = MIN (avail, size); + if (chunk) + { + /* Copy out of buffer. */ + memcpy (data, fbuf->read_tail, chunk); + fbuf->read_tail += chunk; + data += chunk; + size -= chunk; + n_read += chunk; + } + else + { + /* Buffer is empty. */ + + /* Use fbuf_seek() to switch between reading and writing. */ + assert (!fbuf->write_head); + + if (size < FBUF_SIZE) + { + /* Normal case: fill the buffer. */ + int retval = fbuf->class->read (fbuf, fbuf->buffer, FBUF_SIZE); + if (retval < 0) + { + fbuf->status = -retval; + return n_read ? n_read : retval; + } + else if (retval == 0) + return n_read; + if (fbuf->offset >= 0) + fbuf->offset += retval; + fbuf->read_tail = fbuf->buffer; + fbuf->read_head = fbuf->buffer + retval; + } + else + { + /* Caller's read buffer is bigger than FBUF_SIZE. Use it + directly. */ + int retval = fbuf->class->read (fbuf, data, size); + if (retval < 0) + { + fbuf->status = -retval; + return n_read ? n_read : retval; + } + else if (retval == 0) + return n_read; + if (fbuf->offset >= 0) + fbuf->offset += retval; + data += retval; + size -= retval; + n_read += retval; + } + } + } + return n_read; +} + +/* Implementation of file-based fbuf. */ + +static const struct fbuf_class fbuf_fd_class; + +/* Returns a new fbuf that represents FD. */ +struct fbuf * +fbuf_open_fd (int fd) +{ + struct fbuf_fd *fbuf = xmalloc (sizeof *fbuf); + fbuf_init (&fbuf->up, &fbuf_fd_class, -1); + fbuf->fd = fd; + return &fbuf->up; +} + +/* Opens FILENAME with FLAGS and MODE and stores a new fbuf that represents it + into *FBUFP. Returns 0 on success, or a positive errno value on failure. + ON failure, *FBUFP will be NULL. */ +int +fbuf_open_file (const char *filename, int flags, mode_t mode, + struct fbuf **fbufp) +{ + int fd = open (filename, flags, mode); + if (fd < 0) + { + *fbufp = NULL; + return errno; + } + *fbufp = fbuf_open_fd (fd); + return 0; +} + +static struct fbuf_fd * +fbuf_fd_cast (const struct fbuf *fbuf) +{ + assert (fbuf->class == &fbuf_fd_class); + return UP_CAST (fbuf, struct fbuf_fd, up); +} + +static int +fbuf_fd_close (struct fbuf *fbuf_) +{ + struct fbuf_fd *fbuf = fbuf_fd_cast (fbuf_); + int retval = close (fbuf->fd) == EOF ? errno : 0; + free (fbuf); + return retval; +} + +static int +fbuf_fd_read (struct fbuf *fbuf_, void *buffer, size_t n) +{ + struct fbuf_fd *fbuf = fbuf_fd_cast (fbuf_); + int retval = read (fbuf->fd, buffer, n); + return retval >= 0 ? retval : -errno; +} + +static int +fbuf_fd_write (struct fbuf *fbuf_, const void *buffer, size_t n) +{ + struct fbuf_fd *fbuf = fbuf_fd_cast (fbuf_); + int retval = write (fbuf->fd, buffer, n); + return retval > 0 ? retval : -errno; +} + +static int +fbuf_fd_seek (struct fbuf *fbuf_, off_t offset) +{ + struct fbuf_fd *fbuf = fbuf_fd_cast (fbuf_); + return lseek (fbuf->fd, offset, SEEK_SET) < 0 ? errno : 0; +} + +static off_t +fbuf_fd_tell (struct fbuf *fbuf_) +{ + struct fbuf_fd *fbuf = fbuf_fd_cast (fbuf_); + off_t offset = lseek (fbuf->fd, 0, SEEK_CUR); + return offset >= 0 ? offset : -errno; +} + +static off_t +fbuf_fd_get_size (struct fbuf *fbuf_) +{ + struct fbuf_fd *fbuf = fbuf_fd_cast (fbuf_); + off_t offset = lseek (fbuf->fd, 0, SEEK_END); + return offset >= 0 ? offset : -errno; +} + +static const struct fbuf_class fbuf_fd_class = + { + fbuf_fd_close, + fbuf_fd_read, + fbuf_fd_write, + fbuf_fd_seek, + fbuf_fd_tell, + fbuf_fd_get_size, + }; + +struct fbuf_memory + { + struct fbuf up; + uint8_t *data; + size_t size, allocated; + }; + +static const struct fbuf_class fbuf_memory_class; + +/* Takes ownership of the N bytes of data at DATA, which must have been + allocated with malloc(), as a memory buffer and makes it the backing for the + newly returned fbuf. Initially, the fbuf is positioned at the beginning of + the data, so that reads will read from it and writes will overwrite it. (To + append, use fbuf_seek() to seek to the end.) + + Writes beyond the end will reallocate the buffer. Closing the returned fbuf + will free the buffer. */ +struct fbuf * +fbuf_open_memory (void *data, size_t n) +{ + struct fbuf_memory *fbuf = xmalloc (sizeof *fbuf); + fbuf_init (&fbuf->up, &fbuf_memory_class, 0); + fbuf->data = data; + fbuf->size = n; + fbuf->allocated = n; + return &fbuf->up; +} + +static struct fbuf_memory * +fbuf_memory_cast (const struct fbuf *fbuf) +{ + assert (fbuf->class == &fbuf_memory_class); + return UP_CAST (fbuf, struct fbuf_memory, up); +} + +static int +fbuf_memory_close (struct fbuf *fbuf_) +{ + struct fbuf_memory *fbuf = fbuf_memory_cast (fbuf_); + free (fbuf->data); + free (fbuf); + return 0; +} + +static int +fbuf_memory_read (struct fbuf *fbuf_, void *buffer, size_t n) +{ + struct fbuf_memory *fbuf = fbuf_memory_cast (fbuf_); + if (fbuf->up.offset >= fbuf->size) + return 0; + + size_t chunk = MIN (n, fbuf->size - fbuf->up.offset); + memcpy (buffer, fbuf->data + fbuf->up.offset, chunk); + return chunk; +} + +static int +fbuf_memory_write (struct fbuf *fbuf_, const void *buffer, size_t n) +{ + struct fbuf_memory *fbuf = fbuf_memory_cast (fbuf_); + + /* Fail if write would cause the memory block to exceed SIZE_MAX bytes. */ + size_t end = xsum (fbuf->up.offset, n); + if (size_overflow_p (end)) + return -EFBIG; + + /* Expand fbuf->data if necessary to hold the write. */ + if (end > fbuf->allocated) + { + fbuf->allocated = end < SIZE_MAX / 2 ? end * 2 : end; + fbuf->data = xrealloc (fbuf->data, fbuf->allocated); + } + + /* Zero-pad to reach the current offset (although this is necessary only if + there has been a seek past the end), then copy in the new data. */ + if (fbuf->up.offset > fbuf->size) + memset (fbuf->data + fbuf->size, 0, fbuf->up.offset - fbuf->size); + memcpy (fbuf->data + fbuf->up.offset, buffer, n); + + if (end > fbuf->size) + fbuf->size = end; + + return n; +} + +static int +fbuf_memory_seek (struct fbuf *fbuf UNUSED, off_t offset UNUSED) +{ + return 0; +} + +static off_t +fbuf_memory_tell (struct fbuf *fbuf UNUSED) +{ + NOT_REACHED (); +} + +static off_t +fbuf_memory_get_size (struct fbuf *fbuf_) +{ + struct fbuf_memory *fbuf = fbuf_memory_cast (fbuf_); + return fbuf->size; +} + +static const struct fbuf_class fbuf_memory_class = + { + fbuf_memory_close, + fbuf_memory_read, + fbuf_memory_write, + fbuf_memory_seek, + fbuf_memory_tell, + fbuf_memory_get_size, + };