/* estream.c - Extended stream I/O/ Library Copyright (C) 2004 g10 Code GmbH This file is part of Libestream. Libestream 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 2 of the License, or (at your option) any later version. Libestream 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 Lesser General Public License for more details. You should have received a copy of the GNU General Public License along with Libestream; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include "estream.h" /* Generally used types. */ typedef void *(*func_realloc_t) (void *mem, size_t size); typedef void (*func_free_t) (void *mem); /* Buffer management layer. */ #define BUFFER_BLOCK_SIZE BUFSIZ #define BUFFER_UNREAD_SIZE 16 /* Macros. */ #define BUFFER_ROUND_TO_BLOCK(size, block_size) \ (((size) + (block_size - 1)) / block_size) /* Locking. */ typedef pth_mutex_t estream_mutex_t; #define ESTREAM_MUTEX_INITIALIZER PTH_MUTEX_INIT #define ESTREAM_MUTEX_LOCK(mutex) \ pth_mutex_acquire (&(mutex), 0, NULL) #define ESTREAM_MUTEX_UNLOCK(mutex) \ pth_mutex_release (&(mutex)) #define ESTREAM_MUTEX_TRYLOCK(mutex) \ ((pth_mutex_acquire (&(mutex), 1, NULL) == TRUE) ? 0 : -1) #define ESTREAM_MUTEX_INITIALIZE(mutex) \ pth_mutex_init (&(mutex)) #define ESTREAM_LOCK(stream) ESTREAM_MUTEX_LOCK (stream->internal->lock) #define ESTREAM_UNLOCK(stream) ESTREAM_MUTEX_UNLOCK (stream->internal->lock) #define ESTREAM_TRYLOCK(stream) ESTREAM_MUTEX_TRYLOCK (stream->internal->lock) /* Primitive system I/O. */ #define ESTREAM_SYS_READ pth_read #define ESTREAM_SYS_WRITE pth_write /* Misc definitions. */ #define ES_DEFAULT_OPEN_MODE (S_IRUSR | S_IWUSR) /* An internal stream object. */ struct estream_internal { estream_mutex_t lock; /* Lock. */ void *cookie; /* Cookie. */ void *opaque; /* Opaque data. */ unsigned int flags; /* Flags. */ off_t offset; es_cookie_read_function_t func_read; es_cookie_write_function_t func_write; es_cookie_seek_function_t func_seek; es_cookie_close_function_t func_close; int strategy; int fd; struct { unsigned int err: 1; unsigned int eof: 1; } indicators; unsigned int deallocate_buffer: 1; }; /* Stream list. */ typedef struct estream_list *estream_list_t; struct estream_list { estream_t car; estream_list_t cdr; estream_list_t *prev_cdr; }; static estream_list_t estream_list; static estream_mutex_t estream_list_lock = ESTREAM_MUTEX_INITIALIZER; #define ESTREAM_LIST_LOCK ESTREAM_MUTEX_LOCK (estream_list_lock) #define ESTREAM_LIST_UNLOCK ESTREAM_MUTEX_UNLOCK (estream_list_lock) /* Macros. */ #define ESTREAM_TMPFILE_TEMPLATE "/tmp/estream.XXXXXX" /* Calculate array dimension. */ #define DIM(array) (sizeof (array) / sizeof (*array)) /* Evaluate EXPRESSION, setting VARIABLE to the return code, if VARIABLE is zero. */ #define SET_UNLESS_NONZERO(variable, tmp_variable, expression) \ do \ { \ tmp_variable = expression; \ if ((! variable) && tmp_variable) \ variable = tmp_variable; \ } \ while (0) /* List manipulation. */ static int es_list_add (estream_t estream) { estream_list_t list_obj; int ret; list_obj = malloc (sizeof (*list_obj)); if (! list_obj) ret = -1; else { ESTREAM_LIST_LOCK; list_obj->car = estream; list_obj->cdr = estream_list; list_obj->prev_cdr = &estream_list; estream_list = list_obj; ESTREAM_LIST_UNLOCK; ret = 0; } return ret; } static void es_list_remove (estream_t estream) { estream_list_t list_obj; ESTREAM_LIST_LOCK; for (list_obj = estream_list; list_obj; list_obj = list_obj->cdr) if (list_obj->car == estream) { *list_obj->prev_cdr = list_obj->cdr; if (list_obj->cdr) list_obj->cdr->prev_cdr = list_obj->prev_cdr; free (list_obj); break; } ESTREAM_LIST_UNLOCK; } typedef int (*estream_iterator_t) (estream_t stream); static int es_list_iterate (estream_iterator_t iterator) { estream_list_t list_obj; int ret = 0; ESTREAM_LIST_LOCK; for (list_obj = estream_list; list_obj; list_obj = list_obj->cdr) ret |= (*iterator) (list_obj->car); ESTREAM_LIST_UNLOCK; return ret; } static int es_init_do (void) { int err, ret; ret = pth_init (); if (ret == TRUE) err = 0; else err = -1; return err; } /* Implementation of Memory I/O. */ typedef struct estream_cookie_mem { unsigned int flags; /* Open flags. */ char *memory; /* Data. */ size_t memory_size; /* Size of MEMORY. */ size_t offset; /* Current offset in MEMORY. */ size_t data_size; /* Size of data in MEMORY. */ size_t block_size; /* Block size. */ unsigned int grow: 1; /* MEMORY is allowed to grow. */ unsigned int append_zero: 1; /* Append zero after data. */ unsigned int dont_free: 1; /* Append zero after data. */ char **ptr; size_t *size; func_realloc_t func_realloc; func_free_t func_free; } *estream_cookie_mem_t; static int es_func_mem_create (void **cookie, unsigned char *data, size_t data_n, size_t data_size, size_t block_size, unsigned int grow, unsigned int append_zero, unsigned int dont_free, char **ptr, size_t *size, func_realloc_t func_realloc, func_free_t func_free, unsigned int flags) { estream_cookie_mem_t mem_cookie; int err; mem_cookie = malloc (sizeof (*mem_cookie)); if (! mem_cookie) err = -1; else { mem_cookie->flags = flags; mem_cookie->memory = data; mem_cookie->memory_size = data_n; mem_cookie->offset = 0; mem_cookie->data_size = data_size; mem_cookie->block_size = block_size; mem_cookie->grow = grow ? 1 : 0; mem_cookie->append_zero = append_zero ? 1 : 0; mem_cookie->dont_free = dont_free ? 1 : 0; mem_cookie->ptr = ptr; mem_cookie->size = size; mem_cookie->func_realloc = func_realloc ? func_realloc : realloc; mem_cookie->func_free = func_free ? func_free : free; mem_cookie->offset = 0; *cookie = mem_cookie; err = 0; } return err; } static ssize_t es_func_mem_read (void *cookie, char *buffer, size_t size) { estream_cookie_mem_t mem_cookie = cookie; ssize_t ret; if (size > mem_cookie->data_size - mem_cookie->offset) size = mem_cookie->data_size - mem_cookie->offset; if (size) { memcpy (buffer, mem_cookie->memory + mem_cookie->offset, size); mem_cookie->offset += size; } ret = size; return ret; } static ssize_t es_func_mem_write (void *cookie, const char *buffer, size_t size) { estream_cookie_mem_t mem_cookie = cookie; func_realloc_t func_realloc = mem_cookie->func_realloc; char *memory_new; size_t newsize; ssize_t ret; int err; if (size) { /* Regular write. */ if (mem_cookie->flags & O_APPEND) /* Append to data. */ mem_cookie->offset = mem_cookie->data_size; if (! mem_cookie->grow) if (size > mem_cookie->memory_size - mem_cookie->offset) size = mem_cookie->memory_size - mem_cookie->offset; err = 0; while (size > (mem_cookie->memory_size - mem_cookie->offset)) { memory_new = (*func_realloc) (mem_cookie->memory, mem_cookie->memory_size + mem_cookie->block_size); if (! memory_new) { err = -1; break; } else { if (mem_cookie->memory != memory_new) mem_cookie->memory = memory_new; mem_cookie->memory_size += mem_cookie->block_size; } } if (err) goto out; if (size) { memcpy (mem_cookie->memory + mem_cookie->offset, buffer, size); if (mem_cookie->offset + size > mem_cookie->data_size) mem_cookie->data_size = mem_cookie->offset + size; mem_cookie->offset += size; } } else { /* Flush. */ err = 0; if (mem_cookie->append_zero) { if (mem_cookie->data_size >= mem_cookie->memory_size) { newsize = BUFFER_ROUND_TO_BLOCK (mem_cookie->data_size + 1, mem_cookie->block_size) * mem_cookie->block_size; memory_new = (*func_realloc) (mem_cookie->memory, newsize); if (! memory_new) { err = -1; goto out; } if (mem_cookie->memory != memory_new) mem_cookie->memory = memory_new; mem_cookie->memory_size = newsize; } mem_cookie->memory[mem_cookie->data_size + 1] = 0; } /* Return information to user if necessary. */ if (mem_cookie->ptr) *mem_cookie->ptr = (char *) mem_cookie->memory; if (mem_cookie->size) *mem_cookie->size = mem_cookie->data_size; } out: if (err) ret = -1; else ret = size; return ret; } static int es_func_mem_seek (void *cookie, off_t *offset, int whence) { estream_cookie_mem_t mem_cookie = cookie; off_t pos_new; int err = 0; switch (whence) { case SEEK_SET: pos_new = *offset; break; case SEEK_CUR: pos_new = mem_cookie->offset += *offset; break; case SEEK_END: pos_new = mem_cookie->data_size += *offset; break; default: /* Never reached. */ pos_new = 0; } if (pos_new >= mem_cookie->memory_size) { /* Grow buffer if possible. */ if (mem_cookie->grow) { func_realloc_t func_realloc = mem_cookie->func_realloc; size_t newsize; void *p; newsize = BUFFER_ROUND_TO_BLOCK (pos_new, mem_cookie->block_size); p = (*func_realloc) (mem_cookie->memory, newsize); if (! p) { err = -1; goto out; } else { if (mem_cookie->memory != p) mem_cookie->memory = p; mem_cookie->memory_size = newsize; } } else { errno = EINVAL; err = -1; goto out; } } if (pos_new > mem_cookie->data_size) /* Fill spare space with zeroes. */ memset (mem_cookie->memory + mem_cookie->data_size, 0, pos_new - mem_cookie->data_size); mem_cookie->offset = pos_new; *offset = pos_new; out: return err; } static int es_func_mem_destroy (void *cookie) { estream_cookie_mem_t mem_cookie = cookie; func_free_t func_free = mem_cookie->func_free; if (! mem_cookie->dont_free) (*func_free) (mem_cookie->memory); free (mem_cookie); return 0; } static es_cookie_io_functions_t estream_functions_mem = { es_func_mem_read, es_func_mem_write, es_func_mem_seek, es_func_mem_destroy, }; /* Implementation of FD I/O. */ typedef struct estream_cookie_fd { int fd; } *estream_cookie_fd_t; static int es_func_fd_create (void **cookie, int fd, unsigned int flags) { estream_cookie_fd_t fd_cookie; int err; fd_cookie = malloc (sizeof (*fd_cookie)); if (! fd_cookie) err = -1; else { fd_cookie->fd = fd; *cookie = fd_cookie; err = 0; } return err; } static ssize_t es_func_fd_read (void *cookie, char *buffer, size_t size) { estream_cookie_fd_t file_cookie = cookie; ssize_t bytes_read; bytes_read = ESTREAM_SYS_READ (file_cookie->fd, buffer, size); return bytes_read; } static ssize_t es_func_fd_write (void *cookie, const char *buffer, size_t size) { estream_cookie_fd_t file_cookie = cookie; ssize_t bytes_written; bytes_written = ESTREAM_SYS_WRITE (file_cookie->fd, buffer, size); return bytes_written; } static int es_func_fd_seek (void *cookie, off_t *offset, int whence) { estream_cookie_fd_t file_cookie = cookie; off_t offset_new; int err; offset_new = lseek (file_cookie->fd, *offset, whence); if (offset_new == -1) err = -1; else { *offset = offset_new; err = 0; } return err; } static int es_func_fd_destroy (void *cookie) { estream_cookie_fd_t fd_cookie = cookie; int err; if (fd_cookie) { err = close (fd_cookie->fd); free (fd_cookie); } else err = 0; return err; } static es_cookie_io_functions_t estream_functions_fd = { es_func_fd_read, es_func_fd_write, es_func_fd_seek, es_func_fd_destroy }; /* Implementation of File I/O. */ static int es_func_file_create (void **cookie, const char *path, unsigned int flags) { estream_cookie_fd_t file_cookie; int err; int fd; err = 0; fd = -1; file_cookie = malloc (sizeof (*file_cookie)); if (! file_cookie) { err = -1; goto out; } fd = open (path, flags, ES_DEFAULT_OPEN_MODE); if (fd == -1) { err = -1; goto out; } file_cookie->fd = fd; *cookie = file_cookie; out: if (err) free (file_cookie); return err; } static es_cookie_io_functions_t estream_functions_file = { es_func_fd_read, es_func_fd_write, es_func_fd_seek, es_func_fd_destroy }; /* Stream primitives. */ static int es_convert_mode (const char *mode, unsigned int *flags) { struct { const char *mode; unsigned int flags; } mode_flags[] = { { "r", O_RDONLY }, { "rb", O_RDONLY }, { "w", O_WRONLY | O_TRUNC | O_CREAT }, { "wb", O_WRONLY | O_TRUNC | O_CREAT }, { "a", O_WRONLY | O_APPEND | O_CREAT }, { "ab", O_WRONLY | O_APPEND | O_CREAT }, { "r+", O_RDWR }, { "rb+", O_RDWR }, { "r+b", O_RDONLY | O_WRONLY }, { "w+", O_RDWR | O_TRUNC | O_CREAT }, { "wb+", O_RDWR | O_TRUNC | O_CREAT }, { "w+b", O_RDWR | O_TRUNC | O_CREAT }, { "a+", O_RDWR | O_CREAT | O_APPEND }, { "ab+", O_RDWR | O_CREAT | O_APPEND }, { "a+b", O_RDWR | O_CREAT | O_APPEND } }; unsigned int i; int err; for (i = 0; i < DIM (mode_flags); i++) if (! strcmp (mode_flags[i].mode, mode)) break; if (i == DIM (mode_flags)) { errno = EINVAL; err = -1; } else { err = 0; *flags = mode_flags[i].flags; } return err; } static void es_initialize (estream_t stream, void *cookie, es_cookie_io_functions_t functions) { stream->internal->cookie = cookie; stream->internal->opaque = NULL; stream->internal->offset = 0; stream->internal->func_read = functions.func_read; stream->internal->func_write = functions.func_write; stream->internal->func_seek = functions.func_seek; stream->internal->func_close = functions.func_close; stream->internal->strategy = _IOFBF; stream->internal->indicators.err = 0; stream->internal->indicators.eof = 0; stream->internal->deallocate_buffer = 0; stream->data_size = 0; stream->data_offset = 0; stream->data_flushed = 0; stream->unread_data_offset = 0; stream->dirty = 0; } static int es_flush (estream_t stream) { es_cookie_write_function_t func_write = stream->internal->func_write; int err; if (! func_write) { errno = EOPNOTSUPP; err = -1; } else if (stream->dirty && stream->data_offset) { size_t data_flushed = 0; size_t bytes_written; ssize_t ret; err = 0; /* Note: actually it should be enough to check for "stream->data_offset - data_flushed" instead of "(stream->data_offset - data_flushed) > 0", since a write callback function should never write more than it is asked to write. But obviously glibc does it this way - FIXME?*/ while (((stream->data_offset - data_flushed) > 0) && (! err)) { ret = (*func_write) (stream->internal->cookie, stream->buffer + data_flushed, stream->data_offset - data_flushed); if (ret == -1) { bytes_written = 0; err = -1; } else bytes_written = ret; data_flushed += bytes_written; if (err) break; } stream->data_flushed += data_flushed; if (stream->data_offset == data_flushed) { stream->internal->offset += stream->data_offset; stream->data_offset = 0; stream->data_flushed = 0; stream->dirty = 0; /* Propagate flush event. */ (*func_write) (stream->internal->cookie, NULL, 0); } } else err = 0; if (err) stream->internal->indicators.err = 1; return err; } static int es_deinitialize (estream_t stream) { es_cookie_close_function_t func_close; int err, tmp_err; func_close = stream->internal->func_close; err = 0; SET_UNLESS_NONZERO (err, tmp_err, es_flush (stream)); if (func_close) SET_UNLESS_NONZERO (err, tmp_err, (*func_close) (stream->internal->cookie)); /* Do not zero out "lock". */ memset (((unsigned char *) stream->internal) + offsetof (struct estream_internal, cookie), 0, sizeof (struct estream_internal) - offsetof (struct estream_internal, cookie)); /* Do not zero out "internal". */ memset (((unsigned char *) stream) + offsetof (struct estream_public, buffer), 0, sizeof (struct estream_public) - offsetof (struct estream_public, buffer)); return err; } static int es_create (estream_t *stream, void *cookie, es_cookie_io_functions_t functions) { estream_t stream_new = NULL; int initialized = 0; int err; stream_new = malloc (sizeof (*stream_new) + sizeof (struct estream_internal) + BUFFER_BLOCK_SIZE + BUFFER_UNREAD_SIZE); if (! stream_new) { err = -1; goto out; } stream_new->internal = (void *) (((unsigned char *) stream_new) + sizeof (*stream_new)); stream_new->buffer = (void *) (((unsigned char *) stream_new) + sizeof (*stream_new) + sizeof (struct estream_internal)); stream_new->buffer_size = BUFFER_BLOCK_SIZE; stream_new->unread_buffer = (void *) (((unsigned char *) stream_new) + sizeof (*stream_new) + sizeof (struct estream_internal) + BUFFER_BLOCK_SIZE); stream_new->unread_buffer_size = BUFFER_UNREAD_SIZE; ESTREAM_MUTEX_INITIALIZE (stream_new->internal->lock); es_initialize (stream_new, cookie, functions); initialized = 1; err = es_list_add (stream_new); if (err) goto out; *stream = stream_new; out: if (err) { if (stream_new) { if (initialized) es_deinitialize (stream_new); free (stream_new); } } return err; } static int es_destroy (estream_t stream) { int err = 0; if (stream) { es_list_remove (stream); err = es_deinitialize (stream); free (stream); } return err; } static void es_empty (estream_t stream) { stream->data_size = 0; stream->data_offset = 0; stream->unread_data_offset = 0; } static int es_fill (estream_t stream) { size_t bytes_read = 0; int err; if (! stream->internal->func_read) { errno = EOPNOTSUPP; err = -1; } else { es_cookie_read_function_t func_read = stream->internal->func_read; ssize_t ret; ret = (*func_read) (stream->internal->cookie, stream->buffer, stream->buffer_size); if (ret == -1) { bytes_read = 0; err = -1; } else { bytes_read = ret; err = 0; } } if (err) stream->internal->indicators.err = 1; else if (! bytes_read) stream->internal->indicators.eof = 1; stream->internal->offset += stream->data_size; stream->data_size = bytes_read; stream->data_offset = 0; return err; } static int es_read_nbf (estream_t stream, unsigned char *buffer, size_t bytes_to_read, size_t *bytes_read) { es_cookie_read_function_t func_read = stream->internal->func_read; size_t data_read; ssize_t ret; int err; data_read = 0; err = 0; while (bytes_to_read - data_read) { ret = (*func_read) (stream->internal->cookie, buffer + data_read, bytes_to_read - data_read); if (ret == -1) { err = -1; break; } else if (ret) data_read += ret; else break; } stream->internal->offset += data_read; *bytes_read = data_read; return err; } static int es_read_fbf (estream_t stream, unsigned char *buffer, size_t bytes_to_read, size_t *bytes_read) { size_t data_available; size_t data_to_read; size_t data_read; int err; data_read = 0; err = 0; while ((bytes_to_read - data_read) && (! err)) { if (stream->data_offset == stream->data_size) { /* Nothing more to read in current container, try to fill container with new data. */ err = es_fill (stream); if (! err) if (! stream->data_size) /* Filling did not result in any data read. */ break; } if (! err) { /* Filling resulted in some new data. */ data_to_read = bytes_to_read - data_read; data_available = stream->data_size - stream->data_offset; if (data_to_read > data_available) data_to_read = data_available; memcpy (buffer + data_read, stream->buffer + stream->data_offset, data_to_read); stream->data_offset += data_to_read; data_read += data_to_read; } } *bytes_read = data_read; return err; } static int es_read_lbf (estream_t stream, unsigned char *buffer, size_t bytes_to_read, size_t *bytes_read) { int err; err = es_read_fbf (stream, buffer, bytes_to_read, bytes_read); return err; } static int es_readn (estream_t stream, unsigned char *buffer, size_t bytes_to_read, size_t *bytes_read) { size_t data_read_unread, data_read; int err; data_read_unread = 0; data_read = 0; err = 0; if (stream->dirty) /* Switching to reading mode -> flush output. */ es_flush (stream); /* Read unread data first. */ while ((bytes_to_read - data_read_unread) && stream->unread_data_offset) { buffer[data_read_unread] = stream->unread_buffer[stream->unread_data_offset - 1]; stream->unread_data_offset--; data_read_unread++; } switch (stream->internal->strategy) { case _IONBF: err = es_read_nbf (stream, buffer + data_read_unread, bytes_to_read - data_read_unread, &data_read); break; case _IOLBF: err = es_read_lbf (stream, buffer + data_read_unread, bytes_to_read - data_read_unread, &data_read); break; case _IOFBF: err = es_read_fbf (stream, buffer + data_read_unread, bytes_to_read - data_read_unread, &data_read); break; } if (bytes_read) *bytes_read = data_read_unread + data_read; return err; } static void es_unreadn (estream_t stream, const unsigned char *data, size_t data_n, size_t *bytes_unread) { size_t space_left; space_left = stream->unread_buffer_size - stream->unread_data_offset; if (data_n > space_left) data_n = space_left; if (! data_n) goto out; memcpy (stream->unread_buffer + stream->unread_data_offset, data, data_n); stream->unread_data_offset += data_n; stream->internal->indicators.eof = 0; out: if (bytes_unread) *bytes_unread = data_n; } static int es_seek (estream_t stream, off_t offset, int whence, off_t *offset_new) { es_cookie_seek_function_t func_seek = stream->internal->func_seek; int err, ret; off_t off; if (! func_seek) { errno = EOPNOTSUPP; err = -1; goto out; } /* Flush data first in order to prevent flushing it to the wrong offset. */ err = es_flush (stream); if (err) goto out; off = offset; if (whence == SEEK_CUR) /* FIXME: this seems to be correct, but I'm not so happy with this exception - isn't there a nicer way? */ off = off - stream->data_size + stream->data_offset; ret = (*func_seek) (stream->internal->cookie, &off, whence); if (ret == -1) { err = -1; goto out; } else err = 0; if (offset_new) *offset_new = off; /* FIXME: verify. */ /* Discard input data. */ es_empty (stream); stream->internal->indicators.eof = 0; stream->internal->offset = off; out: if (err) stream->internal->indicators.err = 1; return err; } static int es_write_nbf (estream_t stream, const unsigned char *buffer, size_t bytes_to_write, size_t *bytes_written) { es_cookie_write_function_t func_write = stream->internal->func_write; size_t data_written; ssize_t ret; int err; data_written = 0; err = 0; while (bytes_to_write - data_written) { ret = (*func_write) (stream->internal->cookie, buffer + data_written, bytes_to_write - data_written); if (ret == -1) { err = -1; break; } else data_written += ret; } stream->internal->offset += data_written; *bytes_written = data_written; return err; } static int es_write_fbf (estream_t stream, const unsigned char *buffer, size_t bytes_to_write, size_t *bytes_written) { size_t space_available; size_t data_to_write; size_t data_written; int err; data_written = 0; err = 0; while ((bytes_to_write - data_written) && (! err)) { if (stream->data_offset == stream->buffer_size) /* Container full, flush buffer. */ err = es_flush (stream); if (! err) { /* Flushing resulted in empty container. */ data_to_write = bytes_to_write - data_written; space_available = stream->buffer_size - stream->data_offset; if (data_to_write > space_available) data_to_write = space_available; memcpy (stream->buffer + stream->data_offset, buffer + data_written, data_to_write); stream->data_offset += data_to_write; data_written += data_to_write; if (! stream->dirty) stream->dirty = 1; } } *bytes_written = data_written; return err; } static int es_write_lbf (estream_t stream, const unsigned char *buffer, size_t bytes_to_write, size_t *bytes_written) { size_t data_flushed; size_t data_buffered; unsigned int i; int err; err = 0; data_flushed = 0; data_buffered = 0; for (i = bytes_to_write; i > 0; i--) if (buffer[i - 1] == '\n') break; /* Found a newline, directly write up to (including) this character. */ err = es_flush (stream); if (err) goto out; err = es_write_nbf (stream, buffer, i, &data_flushed); if (err) goto out; /* Write remaining data fully buffered. */ err = es_write_fbf (stream, buffer + data_flushed, bytes_to_write - data_flushed, &data_buffered); if (err) goto out; out: *bytes_written = data_flushed + data_buffered; return err; } static int es_writen (estream_t stream, const unsigned char *buffer, size_t bytes_to_write, size_t *bytes_written) { size_t data_written; int err; data_written = 0; err = 0; if ((! stream->dirty) && stream->data_size) { /* Switching to writing mode -> discard input data and seek to position at which reading has stopped. */ err = es_seek (stream, 0, SEEK_CUR, NULL); if (err) goto out; } switch (stream->internal->strategy) { case _IONBF: err = es_write_nbf (stream, buffer, bytes_to_write, &data_written); break; case _IOLBF: err = es_write_lbf (stream, buffer, bytes_to_write, &data_written); break; case _IOFBF: err = es_write_fbf (stream, buffer, bytes_to_write, &data_written); break; } out: if (bytes_written) *bytes_written = data_written; return err; } static int es_peek (estream_t stream, unsigned char **data, size_t *data_size) { int err; if (stream->data_offset == stream->data_size) /* Refill container. */ err = es_fill (stream); else err = 0; if (! err) { if (data) *data = stream->buffer + stream->data_offset; if (data_size) *data_size = stream->data_size - stream->data_offset; } return err; } /* Skip SIZE bytes of input data contained in buffer. */ static int es_skip (estream_t stream, size_t size) { int err; if (stream->data_offset + size > stream->data_size) { errno = EINVAL; err = -1; } else { stream->data_offset += size; err = 0; } return err; } static int es_read_line (estream_t stream, size_t max_length, char **line, size_t *line_length) { size_t space_left; size_t line_size; estream_t line_stream; char *line_new; void *line_stream_cookie; char *newline; unsigned char *data; size_t data_size; int err; line_new = NULL; line_stream = NULL; line_stream_cookie = NULL; err = es_func_mem_create (&line_stream_cookie, NULL, 0, 0, BUFFER_BLOCK_SIZE, 1, 0, 0, NULL, 0, realloc, free, O_RDWR); if (err) goto out; err = es_create (&line_stream, line_stream_cookie, estream_functions_mem); if (err) goto out; space_left = max_length; line_size = 0; while (1) { if (max_length && (space_left == 1)) break; err = es_peek (stream, &data, &data_size); if (err || (! data_size)) break; if (data_size > (space_left - 1)) data_size = space_left - 1; newline = memchr (data, '\n', data_size); if (newline) { data_size = (newline - (char *) data) + 1; err = es_write (line_stream, data, data_size, NULL); if (! err) { space_left -= data_size; line_size += data_size; es_skip (stream, data_size); break; } } else { err = es_write (line_stream, data, data_size, NULL); if (! err) { space_left -= data_size; line_size += data_size; es_skip (stream, data_size); } } if (err) break; } if (err) goto out; /* Complete line has been written to line_stream. */ if ((max_length > 1) && (! line_size)) { stream->internal->indicators.eof = 1; goto out; } err = es_seek (line_stream, 0, SEEK_SET, NULL); if (err) goto out; if (! *line) { line_new = malloc (line_size + 1); if (! line_new) { err = -1; goto out; } } else line_new = *line; err = es_read (line_stream, line_new, line_size, NULL); if (err) goto out; line_new[line_size] = '\0'; if (! *line) *line = line_new; if (line_length) *line_length = line_size; out: if (line_stream) es_destroy (line_stream); else if (line_stream_cookie) es_func_mem_destroy (line_stream_cookie); if (err) { if (! *line) free (line_new); stream->internal->indicators.err = 1; } return err; } static int es_print (estream_t restrict stream, const char * restrict format, va_list ap) { char data[BUFFER_BLOCK_SIZE]; size_t bytes_written; size_t bytes_read; FILE *tmp_stream; int err; bytes_written = 0; tmp_stream = NULL; err = 0; tmp_stream = tmpfile (); if (err) goto out; err = vfprintf (tmp_stream, format, ap); if (err < 0) goto out; err = fseek (tmp_stream, 0, SEEK_SET); if (err) goto out; while (1) { bytes_read = fread (data, 1, sizeof (data), tmp_stream); if (ferror (tmp_stream)) { err = -1; break; } err = es_writen (stream, data, bytes_read, NULL); if (err) break; else bytes_written += bytes_read; if (feof (tmp_stream)) break; } if (err) goto out; out: if (tmp_stream) fclose (tmp_stream); return err ? -1 : bytes_written; } static void es_set_indicators (estream_t stream, int err, int eof) { if (err != -1) stream->internal->indicators.err = err ? 1 : 0; if (eof != -1) stream->internal->indicators.eof = eof ? 1 : 0; } static int es_get_indicator (estream_t stream, int err, int eof) { int ret = 0; if (err) ret = stream->internal->indicators.err; else if (eof) ret = stream->internal->indicators.eof; return ret; } static int es_set_buffering (estream_t stream, char * restrict buffer, int mode, size_t size) { int err; /* Flush buffer or discard input data. */ if (stream->dirty) err = es_flush (stream); else { es_empty (stream); es_set_indicators (stream, -1, 0); err = 0; } if (err) goto out; /* Free old buffer in case that was allocated by this function. */ if (stream->internal->deallocate_buffer) { stream->internal->deallocate_buffer = 0; free (stream->buffer); stream->buffer = NULL; } if (mode == _IONBF) stream->buffer_size = 0; else { void *buffer_new; if (buffer) buffer_new = buffer; else { buffer_new = malloc (size); if (! buffer_new) { err = -1; goto out; } } stream->buffer = buffer_new; stream->buffer_size = size; if (! buffer) stream->internal->deallocate_buffer = 1; } stream->internal->strategy = mode; out: return err; } static off_t es_offset_calculate (estream_t stream) { off_t offset; offset = stream->internal->offset + stream->data_offset; return offset; } static void es_opaque_ctrl (estream_t stream, void *opaque_new, void **opaque_old) { if (opaque_old) *opaque_old = stream->internal->opaque; if (opaque_new) stream->internal->opaque = opaque_new; } /* API. */ int es_init (void) { int err; err = es_init_do (); return err; } estream_t es_fopen (const char * restrict path, const char * restrict mode) { unsigned int flags; int create_called; estream_t stream; void *cookie; int err; stream = NULL; cookie = NULL; create_called = 0; err = es_convert_mode (mode, &flags); if (err) goto out; err = es_func_file_create (&cookie, path, flags); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, estream_functions_file); if (err) goto out; out: if (err && create_called) (*estream_functions_file.func_close) (cookie); return stream; } estream_t es_mopen (unsigned char *data, size_t data_n, size_t data_size, unsigned int grow, func_realloc_t func_realloc, func_free_t func_free, const char * restrict mode) { unsigned int flags; int create_called; estream_t stream; void *cookie; int err; /* FIXME: check for sanity. */ cookie = 0; stream = NULL; create_called = 0; err = es_convert_mode (mode, &flags); if (err) goto out; err = es_func_mem_create (&cookie, data, data_n, data_size, BUFFER_BLOCK_SIZE, grow, 0, 0, NULL, 0, func_realloc, func_free, flags); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, estream_functions_mem); if (err) goto out; out: if (err && create_called) (*estream_functions_mem.func_close) (cookie); return stream; } estream_t es_open_memstream (char **ptr, size_t *size) { unsigned int flags; int create_called; estream_t stream; void *cookie; int err; /* FIXME: check for sanity. */ flags = O_RDWR; create_called = 0; stream = NULL; cookie = 0; err = es_func_mem_create (&cookie, NULL, 0, 0, BUFFER_BLOCK_SIZE, 1, 1, 1, ptr, size, realloc, free, flags); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, estream_functions_mem); if (err) goto out; out: if (err && create_called) (*estream_functions_mem.func_close) (cookie); return stream; } estream_t es_fopencookie (void *cookie, const char * restrict mode, es_cookie_io_functions_t functions) { unsigned int flags; estream_t stream; int err; stream = NULL; flags = 0; err = es_convert_mode (mode, &flags); if (err) goto out; err = es_create (&stream, cookie, functions); if (err) goto out; out: return stream; } estream_t es_fdopen (int filedes, const char *mode) { unsigned int flags; int create_called; estream_t stream; void *cookie; int err; stream = NULL; cookie = NULL; create_called = 0; err = es_convert_mode (mode, &flags); if (err) goto out; err = es_func_fd_create (&cookie, filedes, flags); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, estream_functions_fd); if (err) goto out; out: if (err && create_called) (*estream_functions_fd.func_close) (cookie); return stream; } estream_t es_freopen (const char *path, const char *mode, estream_t stream) { int err; if (stream) { unsigned int flags; int create_called; void *cookie; cookie = NULL; create_called = 0; ESTREAM_LOCK (stream); es_deinitialize (stream); err = es_convert_mode (mode, &flags); if (err) goto leave; err = es_func_file_create (&cookie, path, flags); if (err) goto leave; create_called = 1; es_initialize (stream, cookie, estream_functions_file); leave: if (err) { if (create_called) es_func_fd_destroy (cookie); es_destroy (stream); stream = NULL; } else ESTREAM_UNLOCK (stream); } else { /* FIXME? We don't support re-opening at the moment. */ errno = EINVAL; es_deinitialize (stream); es_destroy (stream); stream = NULL; } return stream; } int es_fclose (estream_t stream) { int err; err = es_destroy (stream); return err; } int es_fileno_unlocked (estream_t stream) { int fd; /* FIXME: this is ugly. */ if (stream->internal->func_read == es_func_fd_read) fd = ((estream_cookie_fd_t) stream->internal->cookie)->fd; else fd = -1; return fd; } void es_flockfile (estream_t stream) { ESTREAM_LOCK (stream); } int es_ftrylockfile (estream_t stream) { int ret; ret = ESTREAM_TRYLOCK (stream); return ret; } void es_funlockfile (estream_t stream) { ESTREAM_UNLOCK (stream); } int es_fileno (estream_t stream) { int ret; ESTREAM_LOCK (stream); ret = es_fileno_unlocked (stream); ESTREAM_UNLOCK (stream); return ret; } int es_feof_unlocked (estream_t stream) { return es_get_indicator (stream, 0, 1); } int es_feof (estream_t stream) { int ret; ESTREAM_LOCK (stream); ret = es_feof_unlocked (stream); ESTREAM_UNLOCK (stream); return ret; } int es_ferror_unlocked (estream_t stream) { return es_get_indicator (stream, 1, 0); } int es_ferror (estream_t stream) { int ret; ESTREAM_LOCK (stream); ret = es_ferror_unlocked (stream); ESTREAM_UNLOCK (stream); return ret; } void es_clearerr_unlocked (estream_t stream) { es_set_indicators (stream, 0, 0); } void es_clearerr (estream_t stream) { ESTREAM_LOCK (stream); es_clearerr_unlocked (stream); ESTREAM_UNLOCK (stream); } int es_fflush (estream_t stream) { int err; if (stream) { ESTREAM_LOCK (stream); err = es_flush (stream); ESTREAM_UNLOCK (stream); } else err = es_list_iterate (es_fflush); return err ? EOF : 0; } int es_fseek (estream_t stream, long int offset, int whence) { int err; ESTREAM_LOCK (stream); err = es_seek (stream, offset, whence, NULL); ESTREAM_UNLOCK (stream); return err; } int es_fseeko (estream_t stream, off_t offset, int whence) { int err; ESTREAM_LOCK (stream); err = es_seek (stream, offset, whence, NULL); ESTREAM_UNLOCK (stream); return err; } long int es_ftell (estream_t stream) { long int ret; ESTREAM_LOCK (stream); ret = es_offset_calculate (stream); ESTREAM_UNLOCK (stream); return ret; } off_t es_ftello (estream_t stream) { off_t ret = -1; ESTREAM_LOCK (stream); ret = es_offset_calculate (stream); ESTREAM_UNLOCK (stream); return ret; } void es_rewind (estream_t stream) { ESTREAM_LOCK (stream); es_seek (stream, 0L, SEEK_SET, NULL); es_set_indicators (stream, 0, -1); ESTREAM_UNLOCK (stream); } int _es_getc_underflow (estream_t stream) { int err; unsigned char c; size_t bytes_read; err = es_readn (stream, &c, 1, &bytes_read); return (err || (! bytes_read)) ? EOF : c; } int _es_putc_overflow (int c, estream_t stream) { unsigned char d = c; int err; err = es_writen (stream, &d, 1, NULL); return err ? EOF : c; } int es_fgetc (estream_t stream) { int ret; ESTREAM_LOCK (stream); ret = es_getc_unlocked (stream); ESTREAM_UNLOCK (stream); return ret; } int es_fputc (int c, estream_t stream) { int ret; ESTREAM_LOCK (stream); ret = es_putc_unlocked (c, stream); ESTREAM_UNLOCK (stream); return ret; } int es_ungetc (int c, estream_t stream) { unsigned char data[] = { c }; size_t data_unread; ESTREAM_LOCK (stream); es_unreadn (stream, data, sizeof (data), &data_unread); ESTREAM_UNLOCK (stream); return data_unread ? c : EOF; } int es_read (estream_t stream, char *buffer, size_t bytes_to_read, size_t *bytes_read) { int err; if (bytes_to_read) { ESTREAM_LOCK (stream); err = es_readn (stream, buffer, bytes_to_read, bytes_read); ESTREAM_UNLOCK (stream); } else err = 0; return err; } int es_write (estream_t stream, const char *buffer, size_t bytes_to_write, size_t *bytes_written) { int err; if (bytes_to_write) { ESTREAM_LOCK (stream); err = es_writen (stream, buffer, bytes_to_write, bytes_written); ESTREAM_UNLOCK (stream); } else err = 0; return err; } size_t es_fread (void * restrict ptr, size_t size, size_t nitems, estream_t restrict stream) { int err; size_t ret, bytes; if (size * nitems) { ESTREAM_LOCK (stream); err = es_readn (stream, ptr, size * nitems, &bytes); ESTREAM_UNLOCK (stream); ret = bytes / size; } else ret = 0; return ret; } size_t es_fwrite (const void * restrict ptr, size_t size, size_t nitems, estream_t restrict stream) { int err; size_t ret, bytes; if (size * nitems) { ESTREAM_LOCK (stream); err = es_writen (stream, ptr, size * nitems, &bytes); ESTREAM_UNLOCK (stream); ret = bytes / size; } else ret = 0; return ret; } char * es_fgets (char * restrict s, int n, estream_t restrict stream) { char *ret = NULL; if (n) { size_t length; int err; ESTREAM_LOCK (stream); err = es_read_line (stream, n, &s, &length); ESTREAM_UNLOCK (stream); if (! err) ret = s; } return ret; } int es_fputs (const char * restrict s, estream_t restrict stream) { size_t length; int err; length = strlen (s); ESTREAM_LOCK (stream); err = es_writen (stream, s, length, NULL); ESTREAM_UNLOCK (stream); return err ? EOF : 0; } ssize_t es_getline (char **lineptr, size_t *n, estream_t stream) { char *line = NULL; size_t line_n = 0; int err; ESTREAM_LOCK (stream); err = es_read_line (stream, 0, &line, &line_n); ESTREAM_UNLOCK (stream); if (err) goto out; if (*lineptr || *n) { if (*n < (line_n + 1)) { void *p; /* FIXME: align? */ p = realloc (*lineptr, line_n); if (! p) { free (line); err = -1; goto out; } else { if (*lineptr != p) *lineptr = p; } } memcpy (*lineptr, line, line_n + 1); if (*n != line_n) *n = line_n; } else { *lineptr = line; *n = line_n; } out: return err ? err : line_n; } int es_vfprintf (estream_t restrict stream, const char *restrict format, va_list ap) { int ret; ESTREAM_LOCK (stream); ret = es_print (stream, format, ap); ESTREAM_UNLOCK (stream); return ret; } int es_fprintf (estream_t restrict stream, const char * restrict format, ...) { int ret; va_list ap; va_start (ap, format); ESTREAM_LOCK (stream); ret = es_print (stream, format, ap); ESTREAM_UNLOCK (stream); va_end (ap); return ret; } estream_t es_tmpfile (void) { char template[] = ESTREAM_TMPFILE_TEMPLATE; unsigned int flags; int create_called; estream_t stream; void *cookie; int err; int fd; create_called = 0; stream = NULL; flags = O_RDWR | O_TRUNC | O_CREAT; /* FIXME - correct? */ cookie = NULL; fd = -1; fd = mkstemp (template); if (fd == -1) { err = -1; goto out; } err = unlink (template); if (err == -1) goto out; err = es_func_fd_create (&cookie, fd, flags); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, estream_functions_fd); if (err) goto out; out: if (err) { if (create_called) es_func_fd_destroy (cookie); else if (fd != -1) close (fd); stream = NULL; } return stream; } int es_setvbuf (estream_t restrict stream, char * restrict buf, int type, size_t size) { int err; if (((type == _IOFBF) || (type == _IOLBF) || (type == _IONBF)) && (! ((! size) && (type != _IONBF)))) { ESTREAM_LOCK (stream); err = es_set_buffering (stream, buf, type, size); ESTREAM_UNLOCK (stream); } else { errno = EINVAL; err = -1; } return err; } void es_setbuf (estream_t restrict stream, char * restrict buf) { ESTREAM_LOCK (stream); es_set_buffering (stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ); ESTREAM_UNLOCK (stream); } void es_opaque_set (estream_t stream, void *opaque) { ESTREAM_LOCK (stream); es_opaque_ctrl (stream, opaque, NULL); ESTREAM_UNLOCK (stream); } void * es_opaque_get (estream_t stream) { void *opaque; ESTREAM_LOCK (stream); es_opaque_ctrl (stream, NULL, &opaque); ESTREAM_UNLOCK (stream); return opaque; }