[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH] fbuf: New data structure for buffered file I/O.
From: |
Ben Pfaff |
Subject: |
[PATCH] fbuf: New data structure for buffered file I/O. |
Date: |
Sun, 11 Feb 2018 22:35:35 -0800 |
This is a substitute for stdio that allows the client to portably work with
memory buffers in addition to files. John Darrington suggested that this
could be useful in the GUI.
---
src/data/any-reader.c | 2 +-
src/data/csv-file-writer.c | 2 +-
src/data/encrypted-file.c | 2 +-
src/data/file-name.c | 43 ++-
src/data/file-name.h | 6 +-
src/data/make-file.c | 71 ++---
src/data/make-file.h | 26 +-
src/data/pc+-file-reader.c | 2 +-
src/data/por-file-reader.c | 2 +-
src/data/por-file-writer.c | 3 +-
src/data/sys-file-reader.c | 43 +--
src/data/sys-file-writer.c | 44 +--
src/language/data-io/data-reader.c | 2 +-
src/language/data-io/data-writer.c | 2 +-
src/libpspp/automake.mk | 2 +
src/libpspp/fbuf.c | 553 +++++++++++++++++++++++++++++++++++++
src/libpspp/fbuf.h | 140 ++++++++++
src/output/ascii.c | 2 +-
src/output/csv.c | 2 +-
src/output/html.c | 2 +-
src/output/msglog.c | 2 +-
utilities/pspp-convert.c | 2 +-
22 files changed, 857 insertions(+), 98 deletions(-)
create mode 100644 src/libpspp/fbuf.c
create mode 100644 src/libpspp/fbuf.h
diff --git a/src/data/any-reader.c b/src/data/any-reader.c
index ff7f4ab6c923..0b52a6956fe7 100644
--- a/src/data/any-reader.c
+++ b/src/data/any-reader.c
@@ -66,7 +66,7 @@ any_reader_detect (const struct file_handle *file_handle,
if (classp)
*classp = NULL;
- file = fn_open (file_handle, "rb");
+ file = fn_fopen (file_handle, "rb");
if (file == NULL)
{
msg (ME, _("An error occurred while opening `%s': %s."),
diff --git a/src/data/csv-file-writer.c b/src/data/csv-file-writer.c
index c01cb342318e..94338c2bcf74 100644
--- a/src/data/csv-file-writer.c
+++ b/src/data/csv-file-writer.c
@@ -153,7 +153,7 @@ csv_writer_open (struct file_handle *fh, const struct
dictionary *dict,
goto error;
/* Create the file on disk. */
- w->rf = replace_file_start (fh, "w", 0666, &w->file);
+ w->rf = replace_file_start (fh, false, 0666, &w->file);
if (w->rf == NULL)
{
msg (ME, _("Error opening `%s' for writing as a system file: %s."),
diff --git a/src/data/encrypted-file.c b/src/data/encrypted-file.c
index f1074b4c1d15..17b95343c8ab 100644
--- a/src/data/encrypted-file.c
+++ b/src/data/encrypted-file.c
@@ -70,7 +70,7 @@ encrypted_file_open (struct encrypted_file **fp, const struct
file_handle *fh)
f = xmalloc (sizeof *f);
f->error = 0;
- f->file = fn_open (fh, "rb");
+ f->file = fn_fopen (fh, "rb");
if (f->file == NULL)
{
msg (ME, _("An error occurred while opening `%s': %s."),
diff --git a/src/data/file-name.c b/src/data/file-name.c
index afc99a88250f..edbabd5ed8f8 100644
--- a/src/data/file-name.c
+++ b/src/data/file-name.c
@@ -142,7 +142,7 @@ safety_violation (const char *fn)
NULL on failure. If NULL is returned then errno is set to a
sensible value. */
FILE *
-fn_open (const struct file_handle *fh, const char *mode)
+fn_fopen (const struct file_handle *fh, const char *mode)
{
const char *fn = fh_get_file_name (fh);
@@ -204,7 +204,46 @@ fn_open (const struct file_handle *fh, const char *mode)
#endif
}
-/* Counterpart to fn_open that closes file F with name FN; returns 0
+/* File open routine that understands `-' as stdin/stdout. Returns file
+ descriptor on success, otherwise a negative errno value. */
+int
+fn_open (const struct file_handle *fh, int flags, mode_t mode)
+{
+ const char *fn = fh_get_file_name (fh);
+
+ int orig_fd = -1;
+ if ((flags & O_ACCMODE) == O_RDONLY)
+ {
+ if (!strcmp (fn, "stdin") || !strcmp (fn, "-"))
+ orig_fd = STDIN_FILENO;
+ }
+ else
+ {
+ if (!strcmp (fn, "stdout") || !strcmp (fn, "-"))
+ orig_fd = STDOUT_FILENO;
+ else if (!strcmp (fn, "stderr"))
+ orig_fd = STDERR_FILENO;
+ }
+ if (orig_fd >= 0)
+ {
+ int fd = dup (orig_fd);
+ return fd >= 0 ? fd : -errno;
+ }
+
+#if WIN32
+ wchar_t *ss = convert_to_filename_encoding (fn, strlen (fn),
fh_get_file_name_encoding (fh));
+ wchar_t *m = (wchar_t *) recode_string ("UTF-16LE", "ASCII", mode, strlen
(mode));
+ int fd = _wopen (fn, flags, mode);
+ free (m);
+ free (ss);
+#else
+ int fd = open (fn, flags, mode);
+#endif
+
+ return fd >= 0 ? fd : -errno;
+}
+
+/* Counterpart to fn_fopen that closes file F with name FN; returns 0
on success, EOF on failure. If EOF is returned, errno is set to a
sensible value. */
int
diff --git a/src/data/file-name.h b/src/data/file-name.h
index b4eee83e76aa..23c2e9da97f3 100644
--- a/src/data/file-name.h
+++ b/src/data/file-name.h
@@ -29,8 +29,10 @@ char *fn_extension (const struct file_handle *);
bool fn_exists (const struct file_handle *);
-FILE *fn_open (const struct file_handle *fn, const char *mode);
-int fn_close (const struct file_handle *fn, FILE *file);
+FILE *fn_fopen (const struct file_handle *, const char *mode);
+int fn_close (const struct file_handle *, FILE *file);
+
+int fn_open (const struct file_handle *, int flags, mode_t mode);
const char * default_output_path (void);
diff --git a/src/data/make-file.c b/src/data/make-file.c
index 78875746f24e..a5b68051ac4d 100644
--- a/src/data/make-file.c
+++ b/src/data/make-file.c
@@ -193,13 +193,12 @@ static void free_replace_file (struct replace_file *);
static void unlink_replace_files (void);
struct replace_file *
-replace_file_start (const struct file_handle *fh, const char *mode,
- mode_t permissions, FILE **fp)
+replace_file_start_fd (const struct file_handle *fh,
+ bool binary, mode_t permissions, int *fd)
{
static bool registered;
struct TS_stat s;
struct replace_file *rf;
- int fd;
int saved_errno = errno;
const char *file_name = fh_get_file_name (fh);
@@ -211,8 +210,8 @@ replace_file_start (const struct file_handle *fh, const
char *mode,
if (Tstat (Tfile_name, &s) == 0 && !S_ISREG (s.st_mode))
{
/* Open file descriptor. */
- fd = Topen (Tfile_name, O_WRONLY);
- if (fd < 0)
+ *fd = Topen (Tfile_name, O_WRONLY);
+ if (*fd < 0)
{
saved_errno = errno;
msg (ME, _("Opening %s for writing: %s."),
@@ -221,18 +220,6 @@ replace_file_start (const struct file_handle *fh, const
char *mode,
return NULL;
}
- /* Open file as stream. */
- *fp = fdopen (fd, mode);
- if (*fp == NULL)
- {
- saved_errno = errno;
- msg (ME, _("Opening stream for %s: %s."),
- file_name, strerror (saved_errno));
- close (fd);
- free (Tfile_name);
- return NULL;
- }
-
rf = xzalloc (sizeof *rf);
rf->file_name = NULL;
rf->tmp_name = Tfile_name;
@@ -266,8 +253,9 @@ replace_file_start (const struct file_handle *fh, const
char *mode,
rf->tmp_name = convert_to_filename_encoding (rf->tmp_name_verbatim,
strlen (rf->tmp_name_verbatim), fh_get_file_name_encoding (fh));
/* Create file by that name. */
- fd = Topen (rf->tmp_name, O_WRONLY | O_CREAT | O_EXCL | O_BINARY,
permissions);
- if (fd >= 0)
+ int flags = O_WRONLY | O_CREAT | O_EXCL | (binary ? O_BINARY : O_TEXT);
+ *fd = Topen (rf->tmp_name, flags, permissions);
+ if (*fd >= 0)
break;
if (errno != EEXIST)
{
@@ -279,29 +267,50 @@ replace_file_start (const struct file_handle *fh, const
char *mode,
}
+ /* Register file for deletion. */
+ ll_push_head (&all_files, &rf->ll);
+ unblock_fatal_signals ();
+
+ return rf;
+
+ error:
+ unblock_fatal_signals ();
+ free_replace_file (rf);
+ *fd = -1;
+ errno = saved_errno;
+ return NULL;
+}
+
+struct replace_file *
+replace_file_start (const struct file_handle *fh, bool binary,
+ mode_t permissions, FILE **fp)
+{
+ struct replace_file *rf;
+ int fd;
+
+ /* Open fd. */
+ rf = replace_file_start_fd (fh, binary, permissions, &fd);
+ if (!rf)
+ goto error;
+
/* Open file as stream. */
- *fp = fdopen (fd, mode);
+ *fp = fdopen (fd, binary ? "wb" : "w");
if (*fp == NULL)
{
- saved_errno = errno;
+ int error = errno;
msg (ME, _("Opening stream for temporary file %s: %s."),
- rf->tmp_name_verbatim, strerror (saved_errno));
+ rf->tmp_name_verbatim, strerror (error));
close (fd);
- Tunlink (rf->tmp_name);
+ replace_file_abort (rf);
+ errno = error;
+
goto error;
}
- /* Register file for deletion. */
- ll_push_head (&all_files, &rf->ll);
- unblock_fatal_signals ();
-
return rf;
- error:
- unblock_fatal_signals ();
- free_replace_file (rf);
+error:
*fp = NULL;
- errno = saved_errno;
return NULL;
}
diff --git a/src/data/make-file.h b/src/data/make-file.h
index a2bcc76eb11b..d908db336681 100644
--- a/src/data/make-file.h
+++ b/src/data/make-file.h
@@ -23,25 +23,29 @@
struct file_handle;
-/* Prepares to atomically replace a (potentially) existing file
- by a new file., by creating a temporary file with the given
- PERMISSIONS bits.
+/* Prepares to atomically replace a (potentially) existing file by a new file,
+ by creating a temporary file with the given PERMISSIONS bits.
Special files are an exception: they are not atomically
replaced but simply opened for writing.
- If successful, stores a stream for it opened according to MODE (which
should be
- "w" or "wb") in *FP. Returns a ticket that can be used to
- commit or abort the file replacement. If neither action has
- yet been taken, program termination via signal will cause
- all resources to be released. The return value must not be
- explicitly freed.
+ If successful, returns a ticket that can be used to commit or abort the file
+ replacement. If neither action is taken, program termination via signal
+ will abort. Depending on the function, stores a file descriptor in *FD or a
+ stream in *FP for the newly opened file. The descriptor or stream is opened
+ for writing a binary file if BINARY is true, otherwise a text file (this
+ disctinction only matters on Windows).
- The caller is responsible for closing *FP */
+ On error, returns NULL and stores NULL in *FP or -1 in *FD.
+
+ The caller is responsible for closing *FP or *FD. */
struct replace_file *replace_file_start (const struct file_handle *fh,
- const char *mode, mode_t permissions,
+ bool binary, mode_t permissions,
FILE **fp);
+struct replace_file *replace_file_start_fd (const struct file_handle *fh,
+ bool binary,
+ mode_t permissions, int *fd);
/* Commits or aborts the replacement of a (potentially) existing
file by a new file, using the ticket returned by
diff --git a/src/data/pc+-file-reader.c b/src/data/pc+-file-reader.c
index cc80cd723b1d..33b2bfe0e919 100644
--- a/src/data/pc+-file-reader.c
+++ b/src/data/pc+-file-reader.c
@@ -209,7 +209,7 @@ pcp_open (struct file_handle *fh)
goto error;
/* Open file. */
- r->file = fn_open (fh, "rb");
+ r->file = fn_fopen (fh, "rb");
if (r->file == NULL)
{
msg (ME, _("Error opening `%s' for reading as an SPSS/PC+ "
diff --git a/src/data/por-file-reader.c b/src/data/por-file-reader.c
index 15a3b7902e45..7f6f7e6b73f2 100644
--- a/src/data/por-file-reader.c
+++ b/src/data/por-file-reader.c
@@ -288,7 +288,7 @@ pfm_open (struct file_handle *fh)
goto error;
/* Open file. */
- r->file = fn_open (r->fh, "rb");
+ r->file = fn_fopen (r->fh, "rb");
if (r->file == NULL)
{
msg (ME, _("An error occurred while opening `%s' for reading "
diff --git a/src/data/por-file-writer.c b/src/data/por-file-writer.c
index 4b25d38f31dc..1853ad3a56a5 100644
--- a/src/data/por-file-writer.c
+++ b/src/data/por-file-writer.c
@@ -152,8 +152,7 @@ pfm_open_writer (struct file_handle *fh, struct dictionary
*dict,
mode = 0444;
if (opts.create_writeable)
mode |= 0222;
- w->rf = replace_file_start (fh, "w", mode,
- &w->file);
+ w->rf = replace_file_start (fh, false, mode, &w->file);
if (w->rf == NULL)
{
msg (ME, _("Error opening `%s' for writing as a portable file: %s."),
diff --git a/src/data/sys-file-reader.c b/src/data/sys-file-reader.c
index b2db75573231..c0a7d4fc997d 100644
--- a/src/data/sys-file-reader.c
+++ b/src/data/sys-file-reader.c
@@ -19,6 +19,7 @@
#include "data/sys-file-private.h"
#include <errno.h>
+#include <fcntl.h>
#include <float.h>
#include <inttypes.h>
#include <stdlib.h>
@@ -44,6 +45,7 @@
#include "libpspp/array.h"
#include "libpspp/assertion.h"
#include "libpspp/compiler.h"
+#include "libpspp/fbuf.h"
#include "libpspp/i18n.h"
#include "libpspp/ll.h"
#include "libpspp/message.h"
@@ -191,7 +193,7 @@ struct sfm_reader
/* File state. */
struct file_handle *fh; /* File handle. */
struct fh_lock *lock; /* Mutual exclusion for file handle. */
- FILE *file; /* File stream. */
+ struct fbuf *fbuf; /* File stream. */
off_t pos; /* Position in file. */
bool error; /* I/O or corruption error? */
struct caseproto *proto; /* Format of output cases. */
@@ -412,13 +414,14 @@ sfm_open (struct file_handle *fh)
if (r->lock == NULL)
goto error;
- r->file = fn_open (fh, "rb");
- if (r->file == NULL)
+ int fd = fn_open (fh, O_RDONLY | O_BINARY, 0);
+ if (fd < 0)
{
msg (ME, _("Error opening `%s' for reading as a system file: %s."),
fh_get_file_name (r->fh), strerror (errno));
goto error;
}
+ r->fbuf = fbuf_open_fd (fd);
if (!read_dictionary (r))
goto error;
@@ -906,15 +909,16 @@ sfm_close (struct any_reader *r_)
struct sfm_reader *r = sfm_reader_cast (r_);
bool error;
- if (r->file)
+ if (r->fbuf)
{
- if (fn_close (r->fh, r->file) == EOF)
+ int error = fbuf_close (r->fbuf);
+ if (error)
{
msg (ME, _("Error closing system file `%s': %s."),
- fh_get_file_name (r->fh), strerror (errno));
+ fh_get_file_name (r->fh), strerror (error));
r->error = true;
}
- r->file = NULL;
+ r->fbuf = NULL;
}
any_read_info_destroy (&r->info);
@@ -3245,11 +3249,13 @@ static inline int
read_bytes_internal (struct sfm_reader *r, bool eof_is_ok,
void *buf, size_t byte_cnt)
{
- size_t bytes_read = fread (buf, 1, byte_cnt, r->file);
+ size_t bytes_read = fbuf_read (r->fbuf, buf, byte_cnt);
r->pos += bytes_read;
if (bytes_read == byte_cnt)
return 1;
- else if (ferror (r->file))
+
+ int status = fbuf_get_status (r->fbuf);
+ if (status > 0)
{
sys_error (r, r->pos, _("System error: %s."), strerror (errno));
return -1;
@@ -3480,9 +3486,10 @@ read_zheader (struct sfm_reader *r)
static void
seek (struct sfm_reader *r, off_t offset)
{
- if (fseeko (r->file, offset, SEEK_SET))
+ int error = fbuf_seek (r->fbuf, offset);
+ if (error)
sys_error (r, 0, _("%s: seek failed (%s)."),
- fh_get_file_name (r->fh), strerror (errno));
+ fh_get_file_name (r->fh), strerror (error));
r->pos = offset;
}
@@ -3500,26 +3507,26 @@ read_ztrailer (struct sfm_reader *r,
unsigned int block_size;
unsigned int n_blocks;
unsigned int i;
- struct stat s;
- if (fstat (fileno (r->file), &s))
+ int seekable = fbuf_is_seekable (r->fbuf);
+ if (seekable < 0)
{
sys_error (r, 0, _("%s: stat failed (%s)."),
- fh_get_file_name (r->fh), strerror (errno));
+ fh_get_file_name (r->fh), strerror (-seekable));
return false;
}
-
- if (!S_ISREG (s.st_mode))
+ else if (!seekable)
{
/* We can't seek to the trailer and then back to the data in this file,
so skip doing extra checks. */
return true;
}
- if (r->ztrailer_ofs + ztrailer_len != s.st_size)
+ off_t size = fbuf_get_size (r->fbuf);
+ if (size >= 0 && r->ztrailer_ofs + ztrailer_len != size)
sys_warn (r, r->pos,
_("End of ZLIB trailer (0x%llx) is not file size (0x%llx)."),
- r->ztrailer_ofs + ztrailer_len, (long long int) s.st_size);
+ r->ztrailer_ofs + ztrailer_len, (long long int) size);
seek (r, r->ztrailer_ofs);
diff --git a/src/data/sys-file-writer.c b/src/data/sys-file-writer.c
index df5108e2a062..af0ead79b33a 100644
--- a/src/data/sys-file-writer.c
+++ b/src/data/sys-file-writer.c
@@ -41,6 +41,7 @@
#include "data/short-names.h"
#include "data/value-labels.h"
#include "data/variable.h"
+#include "libpspp/fbuf.h"
#include "libpspp/float-format.h"
#include "libpspp/i18n.h"
#include "libpspp/integer-format.h"
@@ -69,7 +70,7 @@ struct sfm_writer
{
struct file_handle *fh; /* File handle. */
struct fh_lock *lock; /* Mutual exclusion for file. */
- FILE *file; /* File stream. */
+ struct fbuf *fbuf; /* File stream. */
struct replace_file *rf; /* Ticket for replacing output file. */
enum any_compression compression;
@@ -216,7 +217,7 @@ sfm_open_writer (struct file_handle *fh, struct dictionary
*d,
w = xzalloc (sizeof *w);
w->fh = fh_ref (fh);
w->lock = NULL;
- w->file = NULL;
+ w->fbuf = NULL;
w->rf = NULL;
/* Use the requested compression, except that no EBCDIC-based ZLIB compressed
@@ -250,13 +251,16 @@ sfm_open_writer (struct file_handle *fh, struct
dictionary *d,
mode = 0444;
if (opts.create_writeable)
mode |= 0222;
- w->rf = replace_file_start (fh, "wb", mode, &w->file);
+
+ int fd;
+ w->rf = replace_file_start_fd (fh, true, mode, &fd);
if (w->rf == NULL)
{
msg (ME, _("Error opening `%s' for writing as a system file: %s."),
fh_get_file_name (fh), strerror (errno));
goto error;
}
+ w->fbuf = fbuf_open_fd (fd);
get_encoding_info (&encoding_info, dict_get_encoding (d));
w->space = encoding_info.space[0];
@@ -309,7 +313,7 @@ sfm_open_writer (struct file_handle *fh, struct dictionary
*d,
w->zstream.zalloc = Z_NULL;
w->zstream.zfree = Z_NULL;
w->zstream.opaque = Z_NULL;
- w->zstart = ftello (w->file);
+ w->zstart = fbuf_tell (w->fbuf);
write_int64 (w, w->zstart);
write_int64 (w, 0);
@@ -985,7 +989,7 @@ write_long_string_value_labels (struct sfm_writer *w,
write_int (w, 1); /* Data item (byte) size. */
write_int (w, size); /* Number of data items. */
- start = ftello (w->file);
+ start = fbuf_tell (w->fbuf);
for (i = 0; i < n_vars; i++)
{
struct variable *var = dict_get_var (dict, i);
@@ -1022,7 +1026,7 @@ write_long_string_value_labels (struct sfm_writer *w,
free (label);
}
}
- assert (ftello (w->file) == start + size);
+ assert (fbuf_tell (w->fbuf) == start + size);
}
static void
@@ -1058,7 +1062,7 @@ write_long_string_missing_values (struct sfm_writer *w,
write_int (w, 1); /* Data item (byte) size. */
write_int (w, size); /* Number of data items. */
- start = ftello (w->file);
+ start = fbuf_tell (w->fbuf);
for (i = 0; i < n_vars; i++)
{
struct variable *var = dict_get_var (dict, i);
@@ -1087,7 +1091,7 @@ write_long_string_missing_values (struct sfm_writer *w,
write_bytes (w, value_str (value, width), 8);
}
}
- assert (ftello (w->file) == start + size);
+ assert (fbuf_tell (w->fbuf) == start + size);
}
static void
@@ -1205,7 +1209,7 @@ sys_file_casewriter_write (struct casewriter *writer,
void *w_,
{
struct sfm_writer *w = w_;
- if (ferror (w->file))
+ if (fbuf_get_status (w->fbuf) > 0)
{
casewriter_force_error (writer);
case_unref (c);
@@ -1235,7 +1239,7 @@ sys_file_casewriter_destroy (struct casewriter *writer,
void *w_)
static bool
write_error (const struct sfm_writer *writer)
{
- return ferror (writer->file);
+ return fbuf_get_status (writer->fbuf) > 0;
}
/* Closes a system file after we're done with it.
@@ -1249,7 +1253,7 @@ close_writer (struct sfm_writer *w)
return true;
ok = true;
- if (w->file != NULL)
+ if (w->fbuf != NULL)
{
/* Flush buffer. */
flush_compressed (w);
@@ -1258,20 +1262,20 @@ close_writer (struct sfm_writer *w)
finish_zstream (w);
write_ztrailer (w);
}
- fflush (w->file);
+ fbuf_flush (w->fbuf);
ok = !write_error (w);
/* Seek back to the beginning and update the number of cases.
This is just a courtesy to later readers, so there's no need
to check return values or report errors. */
- if (ok && w->case_cnt <= INT32_MAX && !fseeko (w->file, 80, SEEK_SET))
+ if (ok && w->case_cnt <= INT32_MAX && !fbuf_seek (w->fbuf, 80))
{
write_int (w, w->case_cnt);
- clearerr (w->file);
+ fbuf_clear_status (w->fbuf);
}
- if (fclose (w->file) == EOF)
+ if (fbuf_close (w->fbuf) != 0)
ok = false;
if (!ok)
@@ -1489,7 +1493,7 @@ write_ztrailer (struct sfm_writer *w)
compressed_ofs += block->compressed_size;
}
- if (!fseeko (w->file, w->zstart + 8, SEEK_SET))
+ if (!fbuf_seek (w->fbuf, w->zstart + 8))
{
write_int64 (w, compressed_ofs);
write_int64 (w, 24 + (w->n_blocks * 24));
@@ -1613,7 +1617,7 @@ write_string (struct sfm_writer *w, const char *string,
size_t width)
size_t pad_bytes = width - data_bytes;
write_bytes (w, string, data_bytes);
while (pad_bytes-- > 0)
- putc (w->space, w->file);
+ fbuf_putc (w->fbuf, w->space);
}
/* Recodes null-terminated UTF-8 encoded STRING into ENCODING, and writes the
@@ -1658,7 +1662,7 @@ write_string_record (struct sfm_writer *w,
static void
write_bytes (struct sfm_writer *w, const void *data, size_t size)
{
- fwrite (data, 1, size, w->file);
+ fbuf_write (w->fbuf, data, size);
}
/* Writes N zeros to W's output file. */
@@ -1666,7 +1670,7 @@ static void
write_zeros (struct sfm_writer *w, size_t n)
{
while (n-- > 0)
- putc (0, w->file);
+ fbuf_putc (w->fbuf, 0);
}
/* Writes N spaces to W's output file. */
@@ -1674,5 +1678,5 @@ static void
write_spaces (struct sfm_writer *w, size_t n)
{
while (n-- > 0)
- putc (w->space, w->file);
+ fbuf_putc (w->fbuf, w->space);
}
diff --git a/src/language/data-io/data-reader.c
b/src/language/data-io/data-reader.c
index 1e06d287735d..c31074e79d90 100644
--- a/src/language/data-io/data-reader.c
+++ b/src/language/data-io/data-reader.c
@@ -154,7 +154,7 @@ dfm_open_reader (struct file_handle *fh, struct lexer
*lexer,
if (fh_get_referent (fh) != FH_REF_INLINE)
{
r->line_number = 0;
- r->file = fn_open (fh, "rb");
+ r->file = fn_fopen (fh, "rb");
if (r->file == NULL)
{
msg (ME, _("Could not open `%s' for reading as a data file: %s."),
diff --git a/src/language/data-io/data-writer.c
b/src/language/data-io/data-writer.c
index bf9505e7dd82..2e03218f69c2 100644
--- a/src/language/data-io/data-writer.c
+++ b/src/language/data-io/data-writer.c
@@ -92,7 +92,7 @@ dfm_open_writer (struct file_handle *fh, const char *encoding)
w = xmalloc (sizeof *w);
w->fh = fh_ref (fh);
w->lock = lock;
- w->rf = replace_file_start (w->fh, "wb", 0666, &w->file);
+ w->rf = replace_file_start (w->fh, true, 0666, &w->file);
w->encoding = xstrdup (encoding);
w->line_ends = fh_get_line_ends (fh);
w->unit = ei.unit;
diff --git a/src/libpspp/automake.mk b/src/libpspp/automake.mk
index b68fce0d5227..81f8f1fcf38b 100644
--- a/src/libpspp/automake.mk
+++ b/src/libpspp/automake.mk
@@ -42,6 +42,8 @@ src_libpspp_liblibpspp_la_SOURCES = \
src/libpspp/encoding-guesser.h \
src/libpspp/ext-array.c \
src/libpspp/ext-array.h \
+ src/libpspp/fbuf.c \
+ src/libpspp/fbuf.h \
src/libpspp/float-format.c \
src/libpspp/float-format.h \
src/libpspp/freaderror.c \
diff --git a/src/libpspp/fbuf.c b/src/libpspp/fbuf.c
new file mode 100644
index 000000000000..a3758cebd271
--- /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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "fbuf.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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,
+ };
diff --git a/src/libpspp/fbuf.h b/src/libpspp/fbuf.h
new file mode 100644
index 000000000000..94a56a2945ed
--- /dev/null
+++ b/src/libpspp/fbuf.h
@@ -0,0 +1,140 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#ifndef LIBPSPP_FBUF_H
+#define LIBPSPP_FBUF_H 1
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include "compiler.h"
+
+/* Data structure for buffered file I/O.
+
+ fbuf is much like stdio and serves the same purpose. The main difference is
+ that it is defined entirely in PSPP and thus it is possible to extend it in
+ a portable way, in particular to have a portable way to read and write a
+ memory buffer instead of a file descriptor, since GNU and BSD libc provide
+ ways to do that, but other libcs don't. */
+
+struct fbuf;
+
+struct fbuf *fbuf_open_fd (int fd);
+int fbuf_open_file (const char *filename, int flags, mode_t mode,
+ struct fbuf **) WARN_UNUSED_RESULT;
+struct fbuf *fbuf_open_memory (void *, size_t);
+
+int fbuf_close (struct fbuf *);
+
+int fbuf_get_status (const struct fbuf *);
+void fbuf_clear_status (struct fbuf *);
+
+int fbuf_is_seekable (const struct fbuf *);
+off_t fbuf_get_size (const struct fbuf *);
+
+int fbuf_flush (struct fbuf *);
+
+off_t fbuf_tell (const struct fbuf *);
+int fbuf_seek (struct fbuf *, off_t);
+
+static inline int fbuf_putc (struct fbuf *, uint8_t);
+ssize_t fbuf_write (struct fbuf *, const void *, size_t);
+
+static inline int fbuf_getc (struct fbuf *);
+ssize_t fbuf_read (struct fbuf *, void *, size_t);
+
+/* Implementation details. */
+
+struct fbuf
+ {
+ const struct fbuf_class *class;
+ uint8_t *buffer;
+
+ /* Offset in the underlying file descriptor:
+
+ - In read mode, this is the offset of 'read_head'.
+
+ - In write mode, this is the offset of 'write_tail'.
+
+ Starts out at TYPE_MINIMUM (off_t), which indicates that the underlying
+ descriptor offset is not known. Negative errno values indicate
+ errors. */
+ off_t offset;
+
+ /*
+ In read mode, buffered data is read into the start of 'buffer'.
+ Initially 'read_tail' points to 'buffer' and 'read_head' just past the
+ last byte of buffered data. As the client reads data, 'read_tail'
+ advances until it reaches 'read_head', then the buffer is re-filled and
+ the process repeats.
+
+ offset in fd
+ |
+ v
+ +----------+------------------------+
+ | | ...data to be read... |
+ +-----------------------------------+
+ ^ ^ ^
+ | | |
+ buffer read_tail read_head
+
+ In write mode, read_tail and read_head are both NULL. */
+ uint8_t *read_tail, *read_head;
+
+ /*
+ In write mode, write_tail and write_head initially point to 'buffer' and
+ 'write_end' to 'buffer + FBUF_SIZE'. As the client writes, its data is
+ copied to and advances 'write_head', limited by 'write_end'. As the
+ fbuf flushes data to the fd, 'write_tail' advances, and when
+ 'write_tail' catches 'write_head', both reset to 'buffer'.
+
+ offset in fd
+ |
+ v
+ +----------+------------------------+-------------------+
+ | |...data to be flushed...| |
+ +-----------------------------------+-------------------+
+ ^ ^ ^ ^
+ | | | |
+ buffer write_tail write_head write_end
+
+ In read mode, write_tail, write_head, and write_end are all NULL. */
+ uint8_t *write_tail, *write_head, *write_end;
+ int status;
+ };
+
+static inline int
+fbuf_putc (struct fbuf *fbuf, uint8_t byte)
+{
+ if (fbuf->write_head < fbuf->write_end)
+ {
+ *fbuf->write_head++ = byte;
+ return 0;
+ }
+ else
+ return fbuf_write (fbuf, &byte, 1);
+}
+
+int fbuf_getc__ (struct fbuf *);
+int
+fbuf_getc (struct fbuf *fbuf)
+{
+ return (fbuf->read_tail < fbuf->read_head
+ ? *fbuf->read_tail++
+ : fbuf_getc__ (fbuf));
+}
+
+#endif /* libpspp/fbuf.h */
diff --git a/src/output/ascii.c b/src/output/ascii.c
index d7ec317d706d..5a4c8c7e5652 100644
--- a/src/output/ascii.c
+++ b/src/output/ascii.c
@@ -1100,7 +1100,7 @@ ascii_open_page (struct ascii_driver *a)
if (a->file == NULL)
{
- a->file = fn_open (a->handle, a->append ? "a" : "w");
+ a->file = fn_fopen (a->handle, a->append ? "a" : "w");
if (a->file != NULL)
{
if ( isatty (fileno (a->file)))
diff --git a/src/output/csv.c b/src/output/csv.c
index 455584914e7c..2802f4bbef3e 100644
--- a/src/output/csv.c
+++ b/src/output/csv.c
@@ -92,7 +92,7 @@ csv_create (struct file_handle *fh, enum
settings_output_devices device_type,
csv->titles = parse_boolean (opt (d, o, "titles", "true"));
csv->captions = parse_boolean (opt (d, o, "captions", "true"));
csv->handle = fh;
- csv->file = fn_open (fh, "w");
+ csv->file = fn_fopen (fh, "w");
csv->n_items = 0;
if (csv->file == NULL)
diff --git a/src/output/html.c b/src/output/html.c
index 38b89719a443..618db2795c20 100644
--- a/src/output/html.c
+++ b/src/output/html.c
@@ -109,7 +109,7 @@ html_create (struct file_handle *fh, enum
settings_output_devices device_type,
parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &html->bg);
parse_color (d, o, "foreground-color", "#000000000000", &html->fg);
#endif
- html->file = fn_open (html->handle, "w");
+ html->file = fn_fopen (html->handle, "w");
if (html->file == NULL)
{
msg_error (errno, _("error opening output file `%s'"), fh_get_file_name
(html->handle));
diff --git a/src/output/msglog.c b/src/output/msglog.c
index 0d0329571045..dbbff3ee1036 100644
--- a/src/output/msglog.c
+++ b/src/output/msglog.c
@@ -63,7 +63,7 @@ msglog_create (const char *file_name)
struct file_handle *handle = fh_create_file (NULL, file_name, NULL,
fh_default_properties ());
- file = fn_open (handle, "w");
+ file = fn_fopen (handle, "w");
if (file == NULL)
{
msg_error (errno, _("error opening output file `%s'"), file_name);
diff --git a/utilities/pspp-convert.c b/utilities/pspp-convert.c
index f21e5bdb8b90..ad91ef9da2c9 100644
--- a/utilities/pspp-convert.c
+++ b/utilities/pspp-convert.c
@@ -262,7 +262,7 @@ decrypt_file (struct encrypted_file *enc,
if (!encrypted_file_unlock (enc, password))
error (1, 0, _("sorry, wrong password"));
- out = fn_open (ofh, "wb");
+ out = fn_fopen (ofh, "wb");
if (out == NULL)
error (1, errno, ("%s: error opening output file"), output_filename);
--
2.15.1
- [PATCH] fbuf: New data structure for buffered file I/O.,
Ben Pfaff <=