/* 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. */ #ifdef USE_ESTREAM_SUPPORT_H # include #endif #ifdef USE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_PTH # include #endif #ifndef HAVE_MKSTEMP int mkstemp (char *template); #endif #ifndef HAVE_MEMRCHR void *memrchr (const void *block, int c, size_t size); #endif #include /* 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. */ #ifdef HAVE_PTH 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_THREADING_INIT() ((pth_init () == TRUE) ? 0 : -1) #else typedef void *estream_mutex_t; # define ESTREAM_MUTEX_INITIALIZER NULL # define ESTREAM_MUTEX_LOCK(mutex) (void) 0 # define ESTREAM_MUTEX_UNLOCK(mutex) (void) 0 # define ESTREAM_MUTEX_TRYLOCK(mutex) 0 # define ESTREAM_MUTEX_INITIALIZE(mutex) (void) 0 # define ESTREAM_THREADING_INIT() 0 #endif /* Memory allocator functions. */ #define MEM_ALLOC malloc #define MEM_REALLOC realloc #define MEM_FREE free /* Primitive system I/O. */ #ifdef HAVE_PTH # define ESTREAM_SYS_READ pth_read # define ESTREAM_SYS_WRITE pth_write #else # define ESTREAM_SYS_READ read # define ESTREAM_SYS_WRITE write #endif /* Misc definitions. */ #define ES_DEFAULT_OPEN_MODE (S_IRUSR | S_IWUSR) #define ES_FLAG_WRITING ES__FLAG_WRITING /* An internal stream object. */ struct estream_internal { unsigned char buffer[BUFFER_BLOCK_SIZE]; unsigned char unread_buffer[BUFFER_UNREAD_SIZE]; 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; }; typedef struct estream_internal *estream_internal_t; #define ESTREAM_LOCK(stream) ESTREAM_MUTEX_LOCK (stream->intern->lock) #define ESTREAM_UNLOCK(stream) ESTREAM_MUTEX_UNLOCK (stream->intern->lock) #define ESTREAM_TRYLOCK(stream) ESTREAM_MUTEX_TRYLOCK (stream->intern->lock) /* 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; #ifdef HAVE_PTH static estream_mutex_t estream_list_lock = ESTREAM_MUTEX_INITIALIZER; #endif #define ESTREAM_LIST_LOCK ESTREAM_MUTEX_LOCK (estream_list_lock) #define ESTREAM_LIST_UNLOCK ESTREAM_MUTEX_UNLOCK (estream_list_lock) #ifndef EOPNOTSUPP # define EOPNOTSUPP ENOSYS #endif /* Macros. */ /* 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. */ /* Add STREAM to the list of registered stream objects. */ static int es_list_add (estream_t stream) { estream_list_t list_obj; int ret; list_obj = MEM_ALLOC (sizeof (*list_obj)); if (! list_obj) ret = -1; else { ESTREAM_LIST_LOCK; list_obj->car = stream; list_obj->cdr = estream_list; list_obj->prev_cdr = &estream_list; if (estream_list) estream_list->prev_cdr = &list_obj->cdr; estream_list = list_obj; ESTREAM_LIST_UNLOCK; ret = 0; } return ret; } /* Remove STREAM from the list of registered stream objects. */ static void es_list_remove (estream_t stream) { estream_list_t list_obj; ESTREAM_LIST_LOCK; for (list_obj = estream_list; list_obj; list_obj = list_obj->cdr) if (list_obj->car == stream) { *list_obj->prev_cdr = list_obj->cdr; if (list_obj->cdr) list_obj->cdr->prev_cdr = list_obj->prev_cdr; MEM_FREE (list_obj); break; } ESTREAM_LIST_UNLOCK; } /* Type of an stream-iterator-function. */ typedef int (*estream_iterator_t) (estream_t stream); /* Iterate over list of registered streams, calling ITERATOR for each of them. */ 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; } /* * Initialization. */ static int es_init_do (void) { int err; err = ESTREAM_THREADING_INIT (); return err; } /* * I/O methods. */ /* Implementation of Memory I/O. */ /* Cookie for memory objects. */ 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_len; /* Length 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; /* Create function for memory objects. */ static int es_func_mem_create (void *ES__RESTRICT *ES__RESTRICT cookie, unsigned char *ES__RESTRICT data, size_t data_n, size_t data_len, 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 = MEM_ALLOC (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_len = data_len; 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 : MEM_REALLOC; mem_cookie->func_free = func_free ? func_free : MEM_FREE; mem_cookie->offset = 0; *cookie = mem_cookie; err = 0; } return err; } /* Read function for memory objects. */ 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_len - mem_cookie->offset) size = mem_cookie->data_len - mem_cookie->offset; if (size) { memcpy (buffer, mem_cookie->memory + mem_cookie->offset, size); mem_cookie->offset += size; } ret = size; return ret; } /* Write function for memory objects. */ 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_len; 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_len) mem_cookie->data_len = mem_cookie->offset + size; mem_cookie->offset += size; } } else { /* Flush. */ err = 0; if (mem_cookie->append_zero) { if (mem_cookie->data_len >= mem_cookie->memory_size) { newsize = BUFFER_ROUND_TO_BLOCK (mem_cookie->data_len + 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_len + 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_len; } out: if (err) ret = -1; else ret = size; return ret; } /* Seek function for memory objects. */ 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_len += *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_len) /* Fill spare space with zeroes. */ memset (mem_cookie->memory + mem_cookie->data_len, 0, pos_new - mem_cookie->data_len); mem_cookie->offset = pos_new; *offset = pos_new; out: return err; } /* Destroy function for memory objects. */ 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); MEM_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. */ /* Cookie for fd objects. */ typedef struct estream_cookie_fd { int fd; } *estream_cookie_fd_t; /* Create function for fd objects. */ static int es_func_fd_create (void **cookie, int fd, unsigned int flags) { estream_cookie_fd_t fd_cookie; int err; fd_cookie = MEM_ALLOC (sizeof (*fd_cookie)); if (! fd_cookie) err = -1; else { fd_cookie->fd = fd; *cookie = fd_cookie; err = 0; } return err; } /* Read function for fd objects. */ 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; } /* Write function for fd objects. */ 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; } /* Seek function for fd objects. */ 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; } /* Destroy function for fd objects. */ 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); MEM_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. */ /* Create function for file objects. */ static int es_func_file_create (void **cookie, int *filedes, const char *path, unsigned int flags) { estream_cookie_fd_t file_cookie; int err; int fd; err = 0; fd = -1; file_cookie = MEM_ALLOC (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; *filedes = fd; out: if (err) MEM_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; } /* * Low level stream functionality. */ static int es_fill (estream_t stream) { size_t bytes_read = 0; int err; if (!stream->intern->func_read) { errno = EOPNOTSUPP; err = -1; } else { es_cookie_read_function_t func_read = stream->intern->func_read; ssize_t ret; ret = (*func_read) (stream->intern->cookie, stream->buffer, stream->buffer_size); if (ret == -1) { bytes_read = 0; err = -1; } else { bytes_read = ret; err = 0; } } if (err) stream->intern->indicators.err = 1; else if (!bytes_read) stream->intern->indicators.eof = 1; stream->intern->offset += stream->data_len; stream->data_len = bytes_read; stream->data_offset = 0; return err; } static int es_flush (estream_t stream) { es_cookie_write_function_t func_write = stream->intern->func_write; int err; assert (stream->flags & ES_FLAG_WRITING); if (stream->data_offset) { size_t bytes_written; size_t data_flushed; ssize_t ret; if (! func_write) { err = EOPNOTSUPP; goto out; } /* Note: to prevent an endless loop caused by user-provided write-functions that pretend to have written more bytes than they were asked to write, we have to check for "(stream->data_offset - data_flushed) > 0" instead of "stream->data_offset - data_flushed". */ data_flushed = 0; err = 0; while ((((ssize_t) (stream->data_offset - data_flushed)) > 0) && (! err)) { ret = (*func_write) (stream->intern->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->intern->offset += stream->data_offset; stream->data_offset = 0; stream->data_flushed = 0; /* Propagate flush event. */ (*func_write) (stream->intern->cookie, NULL, 0); } } else err = 0; out: if (err) stream->intern->indicators.err = 1; return err; } /* Discard buffered data for STREAM. */ static void es_empty (estream_t stream) { assert (! (stream->flags & ES_FLAG_WRITING)); stream->data_len = 0; stream->data_offset = 0; stream->unread_data_len = 0; } /* Initialize STREAM. */ static void es_initialize (estream_t stream, void *cookie, int fd, es_cookie_io_functions_t functions) { stream->intern->cookie = cookie; stream->intern->opaque = NULL; stream->intern->offset = 0; stream->intern->func_read = functions.func_read; stream->intern->func_write = functions.func_write; stream->intern->func_seek = functions.func_seek; stream->intern->func_close = functions.func_close; stream->intern->strategy = _IOFBF; stream->intern->fd = fd; stream->intern->indicators.err = 0; stream->intern->indicators.eof = 0; stream->intern->deallocate_buffer = 0; stream->data_len = 0; stream->data_offset = 0; stream->data_flushed = 0; stream->unread_data_len = 0; stream->flags = 0; } /* Deinitialize STREAM. */ static int es_deinitialize (estream_t stream) { es_cookie_close_function_t func_close; int err, tmp_err; func_close = stream->intern->func_close; err = 0; if (stream->flags & ES_FLAG_WRITING) SET_UNLESS_NONZERO (err, tmp_err, es_flush (stream)); if (func_close) SET_UNLESS_NONZERO (err, tmp_err, (*func_close) (stream->intern->cookie)); return err; } /* Create a new stream object, initialize it. */ static int es_create (estream_t *stream, void *cookie, int fd, es_cookie_io_functions_t functions) { estream_internal_t stream_internal_new; estream_t stream_new; int err; stream_new = NULL; stream_internal_new = NULL; stream_new = MEM_ALLOC (sizeof (*stream_new)); if (! stream_new) { err = -1; goto out; } stream_internal_new = MEM_ALLOC (sizeof (*stream_internal_new)); if (! stream_internal_new) { err = -1; goto out; } stream_new->buffer = stream_internal_new->buffer; stream_new->buffer_size = sizeof (stream_internal_new->buffer); stream_new->unread_buffer = stream_internal_new->unread_buffer; stream_new->unread_buffer_size = sizeof (stream_internal_new->unread_buffer); stream_new->intern = stream_internal_new; ESTREAM_MUTEX_INITIALIZE (stream_new->intern->lock); es_initialize (stream_new, cookie, fd, functions); err = es_list_add (stream_new); if (err) goto out; *stream = stream_new; out: if (err) { if (stream_new) { es_deinitialize (stream_new); MEM_FREE (stream_new); } } return err; } /* Deinitialize a stream object and destroy it. */ static int es_destroy (estream_t stream) { int err = 0; if (stream) { es_list_remove (stream); err = es_deinitialize (stream); MEM_FREE (stream->intern); MEM_FREE (stream); } return err; } /* Try to read BYTES_TO_READ bytes FROM STREAM into BUFFER in unbuffered-mode, storing the amount of bytes read in *BYTES_READ. */ static int es_read_nbf (estream_t ES__RESTRICT stream, unsigned char *ES__RESTRICT buffer, size_t bytes_to_read, size_t *ES__RESTRICT bytes_read) { es_cookie_read_function_t func_read = stream->intern->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->intern->cookie, buffer + data_read, bytes_to_read - data_read); if (ret == -1) { err = -1; break; } else if (ret) data_read += ret; else break; } stream->intern->offset += data_read; *bytes_read = data_read; return err; } /* Try to read BYTES_TO_READ bytes FROM STREAM into BUFFER in fully-buffered-mode, storing the amount of bytes read in *BYTES_READ. */ static int es_read_fbf (estream_t ES__RESTRICT stream, unsigned char *ES__RESTRICT buffer, size_t bytes_to_read, size_t *ES__RESTRICT 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_len) { /* Nothing more to read in current container, try to fill container with new data. */ err = es_fill (stream); if (! err) if (! stream->data_len) /* 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_len - 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; } /* Try to read BYTES_TO_READ bytes FROM STREAM into BUFFER in line-buffered-mode, storing the amount of bytes read in *BYTES_READ. */ static int es_read_lbf (estream_t ES__RESTRICT stream, unsigned char *ES__RESTRICT buffer, size_t bytes_to_read, size_t *ES__RESTRICT bytes_read) { int err; err = es_read_fbf (stream, buffer, bytes_to_read, bytes_read); return err; } /* Try to read BYTES_TO_READ bytes FROM STREAM into BUFFER, storing *the amount of bytes read in BYTES_READ. */ static int es_readn (estream_t ES__RESTRICT stream, unsigned char *ES__RESTRICT buffer, size_t bytes_to_read, size_t *ES__RESTRICT bytes_read) { size_t data_read_unread, data_read; int err; data_read_unread = 0; data_read = 0; err = 0; if (stream->flags & ES_FLAG_WRITING) { /* Switching to reading mode -> flush output. */ err = es_flush (stream); if (err) goto out; stream->flags &= ~ES_FLAG_WRITING; } /* Read unread data first. */ while ((bytes_to_read - data_read_unread) && stream->unread_data_len) { buffer[data_read_unread] = stream->unread_buffer[stream->unread_data_len - 1]; stream->unread_data_len--; data_read_unread++; } switch (stream->intern->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; } out: if (bytes_read) *bytes_read = data_read_unread + data_read; return err; } /* Try to unread DATA_N bytes from DATA into STREAM, storing the amount of bytes succesfully unread in *BYTES_UNREAD. */ static void es_unreadn (estream_t ES__RESTRICT stream, const unsigned char *ES__RESTRICT data, size_t data_n, size_t *ES__RESTRICT bytes_unread) { size_t space_left; space_left = stream->unread_buffer_size - stream->unread_data_len; if (data_n > space_left) data_n = space_left; if (! data_n) goto out; memcpy (stream->unread_buffer + stream->unread_data_len, data, data_n); stream->unread_data_len += data_n; stream->intern->indicators.eof = 0; out: if (bytes_unread) *bytes_unread = data_n; } /* Seek in STREAM. */ static int es_seek (estream_t ES__RESTRICT stream, off_t offset, int whence, off_t *ES__RESTRICT offset_new) { es_cookie_seek_function_t func_seek = stream->intern->func_seek; int err, ret; off_t off; if (! func_seek) { errno = EOPNOTSUPP; err = -1; goto out; } if (stream->flags & ES_FLAG_WRITING) { /* Flush data first in order to prevent flushing it to the wrong offset. */ err = es_flush (stream); if (err) goto out; stream->flags &= ~ES_FLAG_WRITING; } off = offset; if (whence == SEEK_CUR) { off = off - stream->data_len + stream->data_offset; off -= stream->unread_data_len; } ret = (*func_seek) (stream->intern->cookie, &off, whence); if (ret == -1) { err = -1; goto out; } err = 0; es_empty (stream); if (offset_new) *offset_new = off; stream->intern->indicators.eof = 0; stream->intern->offset = off; out: if (err) stream->intern->indicators.err = 1; return err; } /* Write BYTES_TO_WRITE bytes from BUFFER into STREAM in unbuffered-mode, storing the amount of bytes written in *BYTES_WRITTEN. */ static int es_write_nbf (estream_t ES__RESTRICT stream, const unsigned char *ES__RESTRICT buffer, size_t bytes_to_write, size_t *ES__RESTRICT bytes_written) { es_cookie_write_function_t func_write = stream->intern->func_write; size_t data_written; ssize_t ret; int err; if (bytes_to_write && (! func_write)) { err = EOPNOTSUPP; goto out; } data_written = 0; err = 0; while (bytes_to_write - data_written) { ret = (*func_write) (stream->intern->cookie, buffer + data_written, bytes_to_write - data_written); if (ret == -1) { err = -1; break; } else data_written += ret; } stream->intern->offset += data_written; *bytes_written = data_written; out: return err; } /* Write BYTES_TO_WRITE bytes from BUFFER into STREAM in fully-buffered-mode, storing the amount of bytes written in *BYTES_WRITTEN. */ static int es_write_fbf (estream_t ES__RESTRICT stream, const unsigned char *ES__RESTRICT buffer, size_t bytes_to_write, size_t *ES__RESTRICT 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; } } *bytes_written = data_written; return err; } /* Write BYTES_TO_WRITE bytes from BUFFER into STREAM in line-buffered-mode, storing the amount of bytes written in *BYTES_WRITTEN. */ static int es_write_lbf (estream_t ES__RESTRICT stream, const unsigned char *ES__RESTRICT buffer, size_t bytes_to_write, size_t *ES__RESTRICT bytes_written) { size_t data_flushed = 0; size_t data_buffered = 0; unsigned char *nlp; int err = 0; nlp = memrchr (buffer, '\n', bytes_to_write); if (nlp) { /* Found a newline, directly write up to (including) this character. */ err = es_flush (stream); if (!err) err = es_write_nbf (stream, buffer, nlp - buffer + 1, &data_flushed); } if (!err) { /* Write remaining data fully buffered. */ err = es_write_fbf (stream, buffer + data_flushed, bytes_to_write - data_flushed, &data_buffered); } *bytes_written = data_flushed + data_buffered; return err; } /* Write BYTES_TO_WRITE bytes from BUFFER into STREAM in, storing the amount of bytes written in BYTES_WRITTEN. */ static int es_writen (estream_t ES__RESTRICT stream, const unsigned char *ES__RESTRICT buffer, size_t bytes_to_write, size_t *ES__RESTRICT bytes_written) { size_t data_written; int err; data_written = 0; err = 0; if (! (stream->flags & ES_FLAG_WRITING)) { /* 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) { if (errno == ESPIPE) err = 0; else goto out; } } switch (stream->intern->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; if (data_written) if (! (stream->flags & ES_FLAG_WRITING)) stream->flags |= ES_FLAG_WRITING; return err; } static int es_peek (estream_t ES__RESTRICT stream, unsigned char **ES__RESTRICT data, size_t *ES__RESTRICT data_len) { int err; if (stream->flags & ES_FLAG_WRITING) { /* Switching to reading mode -> flush output. */ err = es_flush (stream); if (err) goto out; stream->flags &= ~ES_FLAG_WRITING; } if (stream->data_offset == stream->data_len) { /* Refill container. */ err = es_fill (stream); if (err) goto out; } if (data) *data = stream->buffer + stream->data_offset; if (data_len) *data_len = stream->data_len - stream->data_offset; err = 0; out: 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_len) { errno = EINVAL; err = -1; } else { stream->data_offset += size; err = 0; } return err; } static int es_read_line (estream_t ES__RESTRICT stream, size_t max_length, char *ES__RESTRICT *ES__RESTRICT line, size_t *ES__RESTRICT 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_len; 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, MEM_REALLOC, MEM_FREE, O_RDWR); if (err) goto out; err = es_create (&line_stream, line_stream_cookie, -1, 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_len); if (err || (! data_len)) break; if (data_len > (space_left - 1)) data_len = space_left - 1; newline = memchr (data, '\n', data_len); if (newline) { data_len = (newline - (char *) data) + 1; err = es_write (line_stream, data, data_len, NULL); if (! err) { space_left -= data_len; line_size += data_len; es_skip (stream, data_len); break; } } else { err = es_write (line_stream, data, data_len, NULL); if (! err) { space_left -= data_len; line_size += data_len; es_skip (stream, data_len); } } if (err) break; } if (err) goto out; /* Complete line has been written to line_stream. */ if ((max_length > 1) && (! line_size)) { stream->intern->indicators.eof = 1; goto out; } err = es_seek (line_stream, 0, SEEK_SET, NULL); if (err) goto out; if (! *line) { line_new = MEM_ALLOC (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) MEM_FREE (line_new); stream->intern->indicators.err = 1; } return err; } static int es_print (estream_t ES__RESTRICT stream, const char *ES__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 (! tmp_stream) { err = errno; 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 ind_err, int ind_eof) { if (ind_err != -1) stream->intern->indicators.err = ind_err ? 1 : 0; if (ind_eof != -1) stream->intern->indicators.eof = ind_eof ? 1 : 0; } static int es_get_indicator (estream_t stream, int ind_err, int ind_eof) { int ret = 0; if (ind_err) ret = stream->intern->indicators.err; else if (ind_eof) ret = stream->intern->indicators.eof; return ret; } static int es_set_buffering (estream_t ES__RESTRICT stream, char *ES__RESTRICT buffer, int mode, size_t size) { int err; /* Flush or empty buffer depending on mode. */ if (stream->flags & ES_FLAG_WRITING) { err = es_flush (stream); if (err) goto out; } else es_empty (stream); es_set_indicators (stream, -1, 0); /* Free old buffer in case that was allocated by this function. */ if (stream->intern->deallocate_buffer) { stream->intern->deallocate_buffer = 0; MEM_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 = MEM_ALLOC (size); if (! buffer_new) { err = -1; goto out; } } stream->buffer = buffer_new; stream->buffer_size = size; if (! buffer) stream->intern->deallocate_buffer = 1; } stream->intern->strategy = mode; err = 0; out: return err; } static off_t es_offset_calculate (estream_t stream) { off_t offset; offset = stream->intern->offset + stream->data_offset; if (offset < stream->unread_data_len) /* Offset undefined. */ offset = 0; else offset -= stream->unread_data_len; return offset; } static void es_opaque_ctrl (estream_t ES__RESTRICT stream, void *ES__RESTRICT opaque_new, void **ES__RESTRICT opaque_old) { if (opaque_old) *opaque_old = stream->intern->opaque; if (opaque_new) stream->intern->opaque = opaque_new; } static int es_get_fd (estream_t stream) { return stream->intern->fd; } /* API. */ int es_init (void) { int err; err = es_init_do (); return err; } estream_t es_fopen (const char *ES__RESTRICT path, const char *ES__RESTRICT mode) { unsigned int flags; int create_called; estream_t stream; void *cookie; int err; int fd; stream = NULL; cookie = NULL; create_called = 0; err = es_convert_mode (mode, &flags); if (err) goto out; err = es_func_file_create (&cookie, &fd, path, flags); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, fd, 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 *ES__RESTRICT data, size_t data_n, size_t data_len, unsigned int grow, func_realloc_t func_realloc, func_free_t func_free, const char *ES__RESTRICT mode) { unsigned int flags; int create_called; estream_t stream; void *cookie; int err; 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_len, 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, -1, estream_functions_mem); 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; 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, MEM_REALLOC, MEM_FREE, flags); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, -1, estream_functions_mem); out: if (err && create_called) (*estream_functions_mem.func_close) (cookie); return stream; } estream_t es_fopencookie (void *ES__RESTRICT cookie, const char *ES__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, -1, 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, filedes, estream_functions_fd); out: if (err && create_called) (*estream_functions_fd.func_close) (cookie); return stream; } estream_t es_freopen (const char *ES__RESTRICT path, const char *ES__RESTRICT mode, estream_t ES__RESTRICT stream) { int err; if (path) { unsigned int flags; int create_called; void *cookie; int fd; 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, &fd, path, flags); if (err) goto leave; create_called = 1; es_initialize (stream, cookie, fd, 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) { return es_get_fd (stream); } void es_flockfile (estream_t stream) { ESTREAM_LOCK (stream); } int es_ftrylockfile (estream_t stream) { return ESTREAM_TRYLOCK (stream); } 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); if (stream->flags & ES_FLAG_WRITING) err = es_flush (stream); else { es_empty (stream); err = 0; } 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 = (unsigned char) c; size_t data_unread; ESTREAM_LOCK (stream); es_unreadn (stream, &data, 1, &data_unread); ESTREAM_UNLOCK (stream); return data_unread ? c : EOF; } int es_read (estream_t ES__RESTRICT stream, char *ES__RESTRICT buffer, size_t bytes_to_read, size_t *ES__RESTRICT 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 ES__RESTRICT stream, const char *ES__RESTRICT buffer, size_t bytes_to_write, size_t *ES__RESTRICT 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 *ES__RESTRICT ptr, size_t size, size_t nitems, estream_t ES__RESTRICT stream) { size_t ret, bytes; int err; 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 *ES__RESTRICT ptr, size_t size, size_t nitems, estream_t ES__RESTRICT stream) { size_t ret, bytes; int err; 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 *ES__RESTRICT s, int n, estream_t ES__RESTRICT stream) { char *ret = NULL; if (n) { int err; ESTREAM_LOCK (stream); err = es_read_line (stream, n, &s, NULL); ESTREAM_UNLOCK (stream); if (! err) ret = s; } return ret; } int es_fputs (const char *ES__RESTRICT s, estream_t ES__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 *ES__RESTRICT *ES__RESTRICT lineptr, size_t *ES__RESTRICT n, estream_t ES__RESTRICT 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 (*n) { /* Caller wants us to use his buffer. */ if (*n < (line_n + 1)) { /* Provided buffer is too small -> resize. */ void *p; p = MEM_REALLOC (*lineptr, line_n + 1); if (! p) err = -1; else { if (*lineptr != p) *lineptr = p; } } if (! err) { memcpy (*lineptr, line, line_n + 1); if (*n != line_n) *n = line_n; } MEM_FREE (line); } else { /* Caller wants new buffers. */ *lineptr = line; *n = line_n; } out: return err ? err : line_n; } int es_vfprintf (estream_t ES__RESTRICT stream, const char *ES__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 ES__RESTRICT stream, const char *ES__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; } static int tmpfd (void) { FILE *fp; int fp_fd; int fd; fp = NULL; fd = -1; fp = tmpfile (); if (! fp) goto out; fp_fd = fileno (fp); fd = dup (fp_fd); out: if (fp) fclose (fp); return fd; } estream_t es_tmpfile (void) { 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; cookie = NULL; fd = tmpfd (); if (fd == -1) { err = -1; goto out; } err = es_func_fd_create (&cookie, fd, flags); if (err) goto out; create_called = 1; err = es_create (&stream, cookie, fd, estream_functions_fd); 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 ES__RESTRICT stream, char *ES__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 ES__RESTRICT stream, char *ES__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; }