summaryrefslogtreecommitdiffstats
path: root/util/iobuf.c
diff options
context:
space:
mode:
Diffstat (limited to 'util/iobuf.c')
-rw-r--r--util/iobuf.c762
1 files changed, 762 insertions, 0 deletions
diff --git a/util/iobuf.c b/util/iobuf.c
new file mode 100644
index 000000000..12fc74ff6
--- /dev/null
+++ b/util/iobuf.c
@@ -0,0 +1,762 @@
+/* iobuf.c - file handling
+ * Copyright (c) 1997 by Werner Koch (dd9jn)
+ *
+ * This file is part of G10.
+ *
+ * G10 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.
+ *
+ * G10 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "memory.h"
+#include "util.h"
+#include "iobuf.h"
+
+typedef struct {
+ FILE *fp; /* open file handle */
+ char fname[1]; /* name of the file */
+} file_filter_ctx_t ;
+
+typedef struct {
+ int usage;
+ size_t size;
+ size_t count;
+ int eof;
+} block_filter_ctx_t;
+
+static int underflow(IOBUF a);
+
+/****************
+ * Read data from a file into buf which has an allocated length of *LEN.
+ * return the number of read bytes in *LEN. OPAQUE is the FILE * of
+ * the stream. A is not used.
+ * control maybe:
+ * IOBUFCTRL_INIT: called just before the function is linked into the
+ * list of function. This can be used to prepare internal
+ * data structures of the function.
+ * IOBUFCTRL_FREE: called just before the function is removed from the
+ * list of functions and can be used to release internal
+ * data structures or close a file etc.
+ * IOBUFCTRL_UNDERFLOW: called by iobuf_underflow to fill the buffer
+ * with new stuff. *RET_LEN is the available size of the
+ * buffer, and should be set to the number of bytes
+ * which were put into the buffer. The function
+ * returns 0 to indicate success, -1 on EOF and
+ * G10ERR_xxxxx for other errors.
+ *
+ * IOBUFCTRL_FLUSH: called by iobuf_flush() to write out the collected stuff.
+ * *RET_LAN is the number of bytes in BUF.
+ *
+ */
+static int
+file_filter(void *opaque, int control, IOBUF chain, byte *buf, size_t *ret_len)
+{
+ file_filter_ctx_t *a = opaque;
+ FILE *fp = a->fp;
+ size_t size = *ret_len;
+ size_t nbytes = 0;
+ int c, rc = 0;
+ char *p;
+
+ if( control == IOBUFCTRL_UNDERFLOW ) {
+ assert( size ); /* need a buffer */
+ for(; size; size-- ) {
+ if( (c=getc(fp)) == EOF ) {
+ if( ferror(fp) ) {
+ log_error("%s: read error: %s\n",
+ a->fname, strerror(errno));
+ rc = G10ERR_READ_FILE;
+ }
+ else if( !nbytes )
+ rc = -1; /* okay: we can return EOF now. */
+ break;
+ }
+ buf[nbytes++] = c & 0xff;
+ }
+ *ret_len = nbytes;
+ }
+ else if( control == IOBUFCTRL_FLUSH ) {
+ for(p=buf; nbytes < size; nbytes++, p++ ) {
+ if( putc(*p, fp) == EOF ) {
+ log_error("%s: write error: %s\n",
+ a->fname, strerror(errno));
+ rc = G10ERR_WRITE_FILE;
+ break;
+ }
+ }
+ *ret_len = nbytes;
+ }
+ else if( control == IOBUFCTRL_INIT ) {
+ }
+ else if( control == IOBUFCTRL_DESC ) {
+ *(char**)buf = "file_filter";
+ }
+ else if( control == IOBUFCTRL_FREE ) {
+ if( fp != stdin && fp != stdout )
+ fclose(fp);
+ fp = NULL;
+ m_free(a); /* we can free our context now */
+ }
+
+ return rc;
+}
+
+
+/****************
+ * This is used to implement the block write mode.
+ * Block reading is done on a byte by byte basis in readbyte(),
+ * without a filter
+ */
+static int
+block_filter(void *opaque, int control, IOBUF chain, byte *buf, size_t *ret_len)
+{
+ block_filter_ctx_t *a = opaque;
+ size_t size = *ret_len;
+ int c, rc = 0;
+ char *p;
+
+ if( control == IOBUFCTRL_UNDERFLOW ) {
+ size_t n=0;
+
+ p = buf;
+ assert( size ); /* need a buffer */
+ if( a->eof ) /* don't read any further */
+ rc = -1;
+ while( !rc && size ) {
+ if( !a->size ) { /* get the length bytes */
+ c = iobuf_get(chain);
+ a->size = c << 8;
+ c = iobuf_get(chain);
+ a->size |= c;
+ if( c == -1 ) {
+ log_error("block_filter: error reading length info\n");
+ rc = G10ERR_READ_FILE;
+ }
+ if( !a->size ) {
+ a->eof = 1;
+ if( !n )
+ rc = -1;
+ break;
+ }
+ }
+
+ for(; !rc && size && a->size; size--, a->size-- ) {
+ if( (c=iobuf_get(chain)) == -1 ) {
+ log_error("block_filter %p: read error (size=%lu,a->size=%lu)\n",
+ a, (ulong)size, (ulong)a->size);
+ rc = G10ERR_READ_FILE;
+ }
+ else {
+ *p++ = c;
+ n++;
+ }
+ }
+ }
+ *ret_len = n;
+ }
+ else if( control == IOBUFCTRL_FLUSH ) {
+ size_t avail, n;
+
+ for(p=buf; !rc && size; ) {
+ n = size;
+ avail = a->size - a->count;
+ if( !avail ) {
+ if( n > a->size ) {
+ iobuf_put( chain, (a->size >> 8) & 0xff );
+ iobuf_put( chain, a->size & 0xff );
+ avail = a->size;
+ a->count = 0;
+ }
+ else {
+ iobuf_put( chain, (n >> 8) & 0xff );
+ iobuf_put( chain, n & 0xff );
+ avail = n;
+ a->count = a->size - n;
+ }
+ }
+ if( n > avail )
+ n = avail;
+ if( iobuf_write(chain, p, n ) )
+ rc = G10ERR_WRITE_FILE;
+ a->count += n;
+ p += n;
+ size -= n;
+ }
+ }
+ else if( control == IOBUFCTRL_INIT ) {
+ if( DBG_IOBUF )
+ log_debug("init block_filter %p\n", a );
+ if( a->usage == 1 )
+ a->count = a->size = 0;
+ else
+ a->count = a->size; /* force first length bytes */
+ a->eof = 0;
+ }
+ else if( control == IOBUFCTRL_DESC ) {
+ *(char**)buf = "block_filter";
+ }
+ else if( control == IOBUFCTRL_FREE ) {
+ if( a->usage == 2 ) { /* write the end markers */
+ iobuf_writebyte(chain, 0);
+ iobuf_writebyte(chain, 0);
+ }
+ else if( a->size ) {
+ log_error("block_filter: pending bytes!\n");
+ }
+ if( DBG_IOBUF )
+ log_debug("free block_filter %p\n", a );
+ m_free(a); /* we can free our context now */
+ }
+
+ return rc;
+}
+
+
+
+/****************
+ * Allocate a new io buffer, with no function assigned.
+ * Usage is the desired usage: 1 for input, 2 for output, 3 for temp buffer
+ * BUFSIZE is a suggested buffer size.
+ */
+IOBUF
+iobuf_alloc(int usage, size_t bufsize)
+{
+ IOBUF a;
+ static int number=0;
+
+ a = m_alloc_clear(sizeof *a);
+ a->usage = usage;
+ a->d.buf = m_alloc( bufsize );
+ a->d.size = bufsize;
+ a->no = ++number;
+ a->subno = 0;
+ return a;
+}
+
+
+int
+iobuf_close( IOBUF a )
+{
+ IOBUF a2;
+ size_t dummy_len;
+ int rc=0;
+
+ for( ; a; a = a2 ) {
+ a2 = a->chain;
+ if( a->usage == 2 && (rc=iobuf_flush(a)) )
+ log_error("iobuf_flush failed on close: %s\n", g10_errstr(rc));
+
+ if( DBG_IOBUF )
+ log_debug("iobuf-%d.%d: close '%s'\n", a->no, a->subno, a->desc );
+ if( a->filter && (rc = a->filter(a->filter_ov, IOBUFCTRL_FREE,
+ a->chain, NULL, &dummy_len)) )
+ log_error("IOBUFCTRL_FREE failed on close: %s\n", g10_errstr(rc) );
+ m_free(a->recorder.buf);
+ m_free(a->d.buf);
+ m_free(a);
+ }
+ return rc;
+}
+
+int
+iobuf_cancel( IOBUF a )
+{
+ /* FIXME: do an unlink if usage is 2 */
+ return iobuf_close(a);
+}
+
+
+/****************
+ * create a temporary iobuf, which can be used to collect stuff
+ * in an iobuf and later be written by iobuf_write_temp() to another
+ * iobuf.
+ */
+IOBUF
+iobuf_temp()
+{
+ IOBUF a;
+
+ a = iobuf_alloc(3, 8192 );
+
+ return a;
+}
+
+
+/****************
+ * Create a head iobuf for reading from a file
+ * returns: NULL if an error occures and sets errno
+ */
+IOBUF
+iobuf_open( const char *fname )
+{
+ IOBUF a;
+ FILE *fp;
+ file_filter_ctx_t *fcx;
+ size_t len;
+
+ if( !fname ) {
+ fp = stdin; /* fixme: set binary mode for msdoze */
+ fname = "[stdin]";
+ }
+ else if( !(fp = fopen(fname, "rb")) )
+ return NULL;
+ a = iobuf_alloc(1, 8192 );
+ fcx = m_alloc( sizeof *fcx + strlen(fname) );
+ fcx->fp = fp;
+ strcpy(fcx->fname, fname );
+ a->filter = file_filter;
+ a->filter_ov = fcx;
+ file_filter( fcx, IOBUFCTRL_DESC, NULL, (byte*)&a->desc, &len );
+ file_filter( fcx, IOBUFCTRL_INIT, NULL, NULL, &len );
+ if( DBG_IOBUF )
+ log_debug("iobuf-%d.%d: open '%s'\n", a->no, a->subno, fname );
+
+ return a;
+}
+
+/****************
+ * create a iobuf for writing to a file; the file will be created.
+ */
+IOBUF
+iobuf_create( const char *fname )
+{
+ IOBUF a;
+ FILE *fp;
+ file_filter_ctx_t *fcx;
+ size_t len;
+
+ if( !fname ) {
+ fp = stdout;
+ fname = "[stdout]";
+ }
+ else if( !(fp = fopen(fname, "wb")) )
+ return NULL;
+ a = iobuf_alloc(2, 8192 );
+ fcx = m_alloc( sizeof *fcx + strlen(fname) );
+ fcx->fp = fp;
+ strcpy(fcx->fname, fname );
+ a->filter = file_filter;
+ a->filter_ov = fcx;
+ file_filter( fcx, IOBUFCTRL_DESC, NULL, (byte*)&a->desc, &len );
+ file_filter( fcx, IOBUFCTRL_INIT, NULL, NULL, &len );
+ if( DBG_IOBUF )
+ log_debug("iobuf-%d.%d: create '%s'\n", a->no, a->subno, a->desc );
+
+ return a;
+}
+
+/****************
+ * Register an i/o filter.
+ */
+int
+iobuf_push_filter( IOBUF a,
+ int (*f)(void *opaque, int control,
+ IOBUF chain, byte *buf, size_t *len), void *ov )
+{
+ IOBUF b;
+ size_t dummy_len=0;
+ int rc=0;
+
+ if( a->usage == 2 && (rc=iobuf_flush(a)) )
+ return rc;
+ /* make a copy of the current stream, so that
+ * A is the new stream and B the original one.
+ * The contents of the buffers are transferred to the
+ * new stream.
+ */
+ b = m_alloc(sizeof *b);
+ memcpy(b, a, sizeof *b );
+ /* remove the filter stuff from the new stream */
+ a->filter = NULL;
+ a->filter_ov = NULL;
+ if( a->usage == 2 ) { /* allocate a fresh buffer for the original stream */
+ b->d.buf = m_alloc( a->d.size );
+ b->d.len = 0;
+ b->d.start = 0;
+ }
+ else { /* allocate a fresh buffer for the new stream */
+ a->d.buf = m_alloc( a->d.size );
+ a->d.len = 0;
+ a->d.start = 0;
+ }
+ /* disable nlimit for the new stream */
+ a->nlimit = a->nbytes = 0;
+ /* disable recorder for the original stream */
+ b->recorder.buf = NULL;
+ /* make a link from the new stream to the original stream */
+ a->chain = b;
+
+ /* setup the function on the new stream */
+ a->filter = f;
+ a->filter_ov = ov;
+
+ a->subno = b->subno + 1;
+ f( ov, IOBUFCTRL_DESC, NULL, (byte*)&a->desc, &dummy_len );
+
+ if( DBG_IOBUF ) {
+ log_debug("iobuf-%d.%d: push '%s'\n", a->no, a->subno, a->desc );
+ for(b=a; b; b = b->chain )
+ log_debug("\tchain: %d.%d '%s'\n", b->no, b->subno, b->desc );
+ }
+
+ /* now we can initialize the new function if we have one */
+ if( a->filter && (rc = a->filter(a->filter_ov, IOBUFCTRL_INIT, a->chain,
+ NULL, &dummy_len)) )
+ log_error("IOBUFCTRL_INIT failed: %s\n", g10_errstr(rc) );
+ return rc;
+}
+
+/****************
+ * Remove an i/o filter.
+ */
+int
+iobuf_pop_filter( IOBUF a, int (*f)(void *opaque, int control,
+ IOBUF chain, byte *buf, size_t *len), void *ov )
+{
+ IOBUF b;
+ size_t dummy_len=0;
+ int rc=0;
+
+ if( DBG_IOBUF )
+ log_debug("iobuf-%d.%d: pop '%s'\n", a->no, a->subno, a->desc );
+ if( !a->filter ) { /* this is simple */
+ b = a->chain;
+ assert(b);
+ m_free(a->d.buf);
+ memcpy(a,b, sizeof *a);
+ m_free(b);
+ return 0;
+ }
+ for(b=a ; b; b = b->chain )
+ if( b->filter == f && (!ov || b->filter_ov == ov) )
+ break;
+ if( !b )
+ log_bug("iobuf_pop_filter(): filter function not found\n");
+
+ /* flush this stream if it is an output stream */
+ if( a->usage == 2 && (rc=iobuf_flush(b)) ) {
+ log_error("iobuf_flush failed in pop_filter: %s\n", g10_errstr(rc));
+ return rc;
+ }
+ /* and tell the filter to free it self */
+ if( (rc = b->filter(b->filter_ov, IOBUFCTRL_FREE, b->chain,
+ NULL, &dummy_len)) ) {
+ log_error("IOBUFCTRL_FREE failed: %s\n", g10_errstr(rc) );
+ return rc;
+ }
+
+ /* and look how to remove it */
+ if( a == b && !b->chain )
+ log_bug("can't remove the last filter from the chain\n");
+ else if( a == b ) { /* remove the first iobuf from the chain */
+ /* everything from b is copied to a. This is save because
+ * a flush has been done on the to be removed entry
+ */
+ b = a->chain;
+ m_free(a->d.buf);
+ memcpy(a,b, sizeof *a);
+ m_free(b);
+ }
+ else if( !b->chain ) { /* remove the last iobuf from the chain */
+ log_bug("Ohh jeee, trying to a head filter\n");
+ }
+ else { /* remove an intermediate iobuf from the chain */
+ log_bug("Ohh jeee, trying to remove an intermediate filter\n");
+ }
+
+ return rc;
+}
+
+
+
+/****************
+ * read underflow: read more bytes into the buffer and return
+ * the first byte or -1 on EOF.
+ */
+static int
+underflow(IOBUF a)
+{
+ size_t len;
+ int rc;
+
+ /*log_debug("iobuf-%d.%d: underflow: start=%lu len=%lu\n",
+ a->no, a->subno, (ulong)a->d.start, (ulong)a->d.len );*/
+ assert( a->d.start == a->d.len );
+ if( a->usage == 3 )
+ return -1; /* EOF because a temp buffer can't do an underflow */
+ if( a->filter_eof ) {
+ if( DBG_IOBUF )
+ log_debug("iobuf-%d.%d: filter eof\n", a->no, a->subno );
+ return -1;
+ }
+
+ if( a->filter ) {
+ len = a->d.size;
+ rc = a->filter( a->filter_ov, IOBUFCTRL_UNDERFLOW, a->chain,
+ a->d.buf, &len );
+ if( a->usage == 1 && rc == -1 ) { /* EOF: we can remove the filter */
+ size_t dummy_len;
+
+ /* and tell the filter to free it self */
+ if( (rc = a->filter(a->filter_ov, IOBUFCTRL_FREE, a->chain,
+ NULL, &dummy_len)) )
+ log_error("IOBUFCTRL_FREE failed: %s\n", g10_errstr(rc) );
+ a->filter = NULL;
+ a->desc = NULL;
+ a->filter_ov = NULL;
+ a->filter_eof = 1;
+ }
+
+ if( !len )
+ return -1;
+ a->d.len = len;
+ a->d.start = 0;
+ return a->d.buf[a->d.start++];
+ }
+ else
+ return -1; /* no filter; return EOF */
+}
+
+
+void
+iobuf_clear_eof(IOBUF a)
+{
+ assert(a->usage == 1);
+
+ if( a->filter )
+ log_info("iobuf-%d.%d: clear_eof '%s' with enabled filter\n", a->no, a->subno, a->desc );
+ if( !a->filter_eof )
+ log_info("iobuf-%d.%d: clear_eof '%s' with no EOF pending\n", a->no, a->subno, a->desc );
+ iobuf_pop_filter(a, NULL, NULL);
+}
+
+
+int
+iobuf_flush(IOBUF a)
+{
+ size_t len;
+ int rc;
+
+ /*log_debug("iobuf-%d.%d: flush\n", a->no, a->subno );*/
+ if( a->usage == 3 )
+ log_bug("temp buffer too short\n");
+ else if( a->usage != 2 )
+ log_bug("flush on non-output iobuf\n");
+ else if( !a->filter )
+ log_bug("iobuf_flush: no filter\n");
+ len = a->d.len;
+ rc = a->filter( a->filter_ov, IOBUFCTRL_FLUSH, a->chain, a->d.buf, &len );
+ if( !rc && len != a->d.len ) {
+ log_info("iobuf_flush did not write all!\n");
+ rc = G10ERR_WRITE_FILE;
+ }
+ a->d.len = 0;
+
+ return rc;
+}
+
+
+/****************
+ * Read a byte from the iobuf; returns -1 on EOF
+ */
+int
+iobuf_readbyte(IOBUF a)
+{
+ int c;
+
+ if( a->nlimit && a->nbytes >= a->nlimit )
+ return -1; /* forced EOF */
+
+ if( a->d.start < a->d.len ) {
+ c = a->d.buf[a->d.start++];
+ }
+ else if( (c=underflow(a)) == -1 )
+ return -1; /* EOF */
+
+ a->nbytes++;
+
+ if( a->recorder.buf ) {
+ if( a->recorder.len >= a->recorder.size ) {
+ a->recorder.size += 500;
+ a->recorder.buf = m_realloc( a->recorder.buf, a->recorder.size );
+ }
+ ((byte*)a->recorder.buf)[a->recorder.len++] = c;
+ }
+ return c;
+}
+
+
+int
+iobuf_writebyte(IOBUF a, unsigned c)
+{
+ if( a->d.len == a->d.size )
+ if( iobuf_flush(a) )
+ return -1;
+
+ assert( a->d.len < a->d.size );
+ a->d.buf[a->d.len++] = c;
+ return 0;
+}
+
+
+int
+iobuf_write(IOBUF a, byte *buf, unsigned buflen )
+{
+ for( ; buflen; buflen--, buf++ )
+ if( iobuf_writebyte(a, *buf) )
+ return -1;
+ return 0;
+}
+
+
+
+/****************
+ * copy the contents of TEMP to A.
+ */
+int
+iobuf_write_temp( IOBUF a, IOBUF temp )
+{
+ return iobuf_write(a, temp->d.buf, temp->d.len );
+}
+
+/****************
+ * copy the contents of the temp io stream to BUFFER.
+ */
+size_t
+iobuf_temp_to_buffer( IOBUF a, byte *buffer, size_t buflen )
+{
+ size_t n = a->d.len;
+
+ if( n > buflen )
+ n = buflen;
+ memcpy( buffer, a->d.buf, n );
+ return n;
+}
+
+
+/****************
+ * Set a limit, how much bytes may be read from the input stream A.
+ * Setting the limit to 0 disables this feature.
+ */
+void
+iobuf_set_limit( IOBUF a, unsigned long nlimit )
+{
+ a->nlimit = nlimit;
+ a->nbytes = 0;
+}
+
+
+
+void
+iobuf_start_recorder( IOBUF a )
+{
+ m_free(a->recorder.buf);
+ a->recorder.size = 500;
+ a->recorder.buf = m_alloc(a->recorder.size);
+ a->recorder.len = 0;
+}
+
+void
+iobuf_push_recorder( IOBUF a, int c )
+{
+ if( a->recorder.buf ) {
+ if( a->recorder.len >= a->recorder.size ) {
+ a->recorder.size += 500;
+ a->recorder.buf = m_realloc( a->recorder.buf, a->recorder.size );
+ }
+ ((byte*)a->recorder.buf)[a->recorder.len++] = c;
+ }
+}
+
+
+char *
+iobuf_stop_recorder( IOBUF a, size_t *n )
+{
+ char *p;
+ if( !a->recorder.buf )
+ log_bug("iobuf_recorder not started\n");
+ p = a->recorder.buf;
+ if( n )
+ *n = a->recorder.len;
+ a->recorder.buf = NULL;
+ return p;
+}
+
+
+/****************
+ * Return the length of an open file
+ */
+u32
+iobuf_get_filelength( IOBUF a )
+{
+ struct stat st;
+
+ for( ; a; a = a->chain )
+ if( !a->chain && a->filter == file_filter ) {
+ file_filter_ctx_t *b = a->filter_ov;
+ FILE *fp = b->fp;
+
+ if( !fstat(fileno(fp), &st) )
+ return st.st_size;
+ log_error("fstat() failed: %s\n", strerror(errno) );
+ break;
+ }
+
+ return 0;
+}
+
+/****************
+ * Start the block write mode, see rfc1991.new for details.
+ * A value of 0 for N stops this mode (flushes and writes
+ * the end marker)
+ */
+void
+iobuf_set_block_mode( IOBUF a, size_t n )
+{
+ block_filter_ctx_t *ctx = m_alloc_clear( sizeof *ctx );
+
+ assert( a->usage == 1 || a->usage == 2 );
+ ctx->usage = a->usage;
+ if( !n ) {
+ iobuf_pop_filter(a, block_filter, NULL );
+ }
+ else {
+ ctx->size = n; /* only needed for usage 2 */
+ iobuf_push_filter(a, block_filter, ctx );
+ }
+}
+
+
+/****************
+ * checks wether the stream is in block mode
+ */
+int
+iobuf_in_block_mode( IOBUF a )
+{
+ for(; a; a = a->chain )
+ if( a->filter == block_filter )
+ return 1; /* yes */
+ return 0; /* no */
+}
+
+
+