new release
[gnupg.git] / util / iobuf.c
index 466f07e..772dfa1 100644 (file)
@@ -1,14 +1,14 @@
 /* iobuf.c  -  file handling
- *     Copyright (c) 1997 by Werner Koch (dd9jn)
+ *     Copyright (C) 1998 Free Software Foundation, Inc.
  *
- * This file is part of G10.
+ * This file is part of GNUPG.
  *
- * G10 is free software; you can redistribute it and/or modify
+ * GNUPG 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,
+ * GNUPG 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.
@@ -24,6 +24,7 @@
 #include <string.h>
 #include <errno.h>
 #include <assert.h>
+#include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
@@ -40,6 +41,7 @@ typedef struct {
     int usage;
     size_t size;
     size_t count;
+    int partial;  /* 1 = partial header, 2 in last partial packet */
     int eof;
 } block_filter_ctx_t;
 
@@ -49,7 +51,7 @@ 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:
+ * control may be:
  * 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.
@@ -143,20 +145,64 @@ block_filter(void *opaque, int control, IOBUF chain, byte *buf, size_t *ret_len)
            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 ) {
+               if( a->partial == 2 ) {
                    a->eof = 1;
                    if( !n )
                        rc = -1;
                    break;
                }
+               else if( a->partial ) {
+                   if( (c = iobuf_get(chain)) == -1 ) {
+                       log_error("block_filter: 1st length byte missing\n");
+                       rc = G10ERR_READ_FILE;
+                       break;
+                   }
+                   if( c < 192 ) {
+                       a->size = c;
+                       a->partial = 2;
+                       if( !a->size ) {
+                           a->eof = 1;
+                           if( !n )
+                               rc = -1;
+                           break;
+                       }
+                   }
+                   else if( c < 224 ) {
+                       a->size = (c - 192) * 256;
+                       if( (c = iobuf_get(chain)) == -1 ) {
+                           log_error("block_filter: 2nd length byte missing\n");
+                           rc = G10ERR_READ_FILE;
+                           break;
+                       }
+                       a->size += c + 192;
+                       a->partial = 2;
+                       if( !a->size ) {
+                           a->eof = 1;
+                           if( !n )
+                               rc = -1;
+                           break;
+                       }
+                   }
+                   else { /* next partial body length */
+                       a->size = 1 << (c & 0x1f);
+                   }
+               }
+               else {
+                   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-- ) {
@@ -176,6 +222,7 @@ block_filter(void *opaque, int control, IOBUF chain, byte *buf, size_t *ret_len)
     else if( control == IOBUFCTRL_FLUSH ) {
        size_t avail, n;
 
+       assert( !a->partial );
        for(p=buf; !rc && size; ) {
            n = size;
            avail = a->size - a->count;
@@ -205,7 +252,9 @@ block_filter(void *opaque, int control, IOBUF chain, byte *buf, size_t *ret_len)
     else if( control == IOBUFCTRL_INIT ) {
        if( DBG_IOBUF )
            log_debug("init block_filter %p\n", a );
-       if( a->usage == 1 )
+       if( a->partial )
+           a->count = 0;
+       else if( a->usage == 1 )
            a->count = a->size = 0;
        else
            a->count = a->size; /* force first length bytes */
@@ -216,8 +265,12 @@ block_filter(void *opaque, int control, IOBUF chain, byte *buf, size_t *ret_len)
     }
     else if( control == IOBUFCTRL_FREE ) {
        if( a->usage == 2 ) { /* write the end markers */
-           iobuf_writebyte(chain, 0);
-           iobuf_writebyte(chain, 0);
+           if( a->partial ) {
+           }
+           else {
+               iobuf_writebyte(chain, 0);
+               iobuf_writebyte(chain, 0);
+           }
        }
        else if( a->size ) {
            log_error("block_filter: pending bytes!\n");
@@ -261,7 +314,7 @@ iobuf_close( IOBUF a )
     size_t dummy_len;
     int rc=0;
 
-    for( ; a; a = a2 ) {
+    for( ; a && !rc ; 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));
@@ -271,7 +324,6 @@ iobuf_close( IOBUF a )
        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);
     }
@@ -283,7 +335,7 @@ iobuf_cancel( IOBUF a )
 {
     const char *s;
 
-    if( a->usage == 2 ) {
+    if( a && a->usage == 2 ) {
        s = iobuf_get_fname(a);
        if( s && *s )
            remove(s);  /* remove the file. Fixme: this will fail for MSDOZE*/
@@ -320,7 +372,7 @@ iobuf_open( const char *fname )
     file_filter_ctx_t *fcx;
     size_t len;
 
-    if( !fname ) {
+    if( !fname || (*fname=='-' && !fname[1])  ) {
        fp = stdin; /* fixme: set binary mode for msdoze */
        fname = "[stdin]";
     }
@@ -341,7 +393,7 @@ iobuf_open( const char *fname )
 }
 
 /****************
- * create a iobuf for writing to a file; the file will be created.
+ * create an iobuf for writing to a file; the file will be created.
  */
 IOBUF
 iobuf_create( const char *fname )
@@ -351,7 +403,7 @@ iobuf_create( const char *fname )
     file_filter_ctx_t *fcx;
     size_t len;
 
-    if( !fname ) {
+    if( !fname || (*fname=='-' && !fname[1]) ) {
        fp = stdout;
        fname = "[stdout]";
     }
@@ -372,6 +424,62 @@ iobuf_create( const char *fname )
 }
 
 /****************
+ * append to an iobuf; if the file does not exist, create it.
+ * cannot be used for stdout.
+ */
+IOBUF
+iobuf_append( const char *fname )
+{
+    IOBUF a;
+    FILE *fp;
+    file_filter_ctx_t *fcx;
+    size_t len;
+
+    if( !fname )
+       return NULL;
+    else if( !(fp = fopen(fname, "ab")) )
+       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: append '%s'\n", a->no, a->subno, a->desc );
+
+    return a;
+}
+
+IOBUF
+iobuf_openrw( const char *fname )
+{
+    IOBUF a;
+    FILE *fp;
+    file_filter_ctx_t *fcx;
+    size_t len;
+
+    if( !fname )
+       return NULL;
+    else if( !(fp = fopen(fname, "r+b")) )
+       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: openrw '%s'\n", a->no, a->subno, a->desc );
+
+    return a;
+}
+
+/****************
  * Register an i/o filter.
  */
 int
@@ -395,6 +503,7 @@ iobuf_push_filter( IOBUF a,
     /* remove the filter stuff from the new stream */
     a->filter = NULL;
     a->filter_ov = NULL;
+    a->filter_eof = 0;
     if( a->usage == 2 ) { /* allocate a fresh buffer for the original stream */
        b->d.buf = m_alloc( a->d.size );
        b->d.len = 0;
@@ -406,9 +515,8 @@ iobuf_push_filter( IOBUF a,
        a->d.start = 0;
     }
     /* disable nlimit for the new stream */
+    a->ntotal = b->ntotal + b->nbytes;
     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;
     a->opaque = b->opaque;
@@ -472,7 +580,7 @@ iobuf_pop_filter( IOBUF a, int (*f)(void *opaque, int control,
        return rc;
     }
 
-    /* and look how to remove it */
+    /* and see 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 */
@@ -485,7 +593,7 @@ iobuf_pop_filter( IOBUF a, int (*f)(void *opaque, int control,
        m_free(b);
     }
     else if( !b->chain ) { /* remove the last iobuf from the chain */
-       log_bug("Ohh jeee, trying to a head filter\n");
+       log_bug("Ohh jeee, trying to remove a head filter\n");
     }
     else {  /* remove an intermediate iobuf from the chain */
        log_bug("Ohh jeee, trying to remove an intermediate filter\n");
@@ -525,12 +633,14 @@ underflow(IOBUF a)
            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;
+           if( a->filter != file_filter ) {
+               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;
        }
 
@@ -591,6 +701,15 @@ iobuf_readbyte(IOBUF a)
 {
     int c;
 
+    /* nlimit does not work together with unget */
+    /* nbytes is also not valid! */
+    if( a->unget.buf ) {
+       if( a->unget.start < a->unget.len )
+           return a->unget.buf[a->unget.start++];
+       m_free(a->unget.buf);
+       a->unget.buf = NULL;
+    }
+
     if( a->nlimit && a->nbytes >= a->nlimit )
        return -1; /* forced EOF */
 
@@ -601,18 +720,71 @@ iobuf_readbyte(IOBUF a)
        return -1; /* EOF */
 
     a->nbytes++;
+    return 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 );
+
+int
+iobuf_read(IOBUF a, byte *buf, unsigned buflen )
+{
+    int c, n;
+
+    if( a->unget.buf || a->nlimit ) {
+       /* handle special cases */
+       for(n=0 ; n < buflen; n++, buf++ ) {
+           if( (c = iobuf_readbyte(a)) == -1 ) {
+               if( !n )
+                   return -1; /* eof */
+               break;
+           }
+           else
+               *buf = c;
        }
-       ((byte*)a->recorder.buf)[a->recorder.len++] = c;
+       return n;
     }
-    return c;
+
+    n = 0;
+    do {
+       for( ; n < buflen && a->d.start < a->d.len; n++ )
+           *buf++ = a->d.buf[a->d.start++];
+       if( n < buflen ) {
+           if( (c=underflow(a)) == -1 ) {
+               a->nbytes += n;
+               return n? n : -1/*EOF*/;
+           }
+           *buf++ = c; n++;
+       }
+    } while( n < buflen );
+    a->nbytes += n;
+    return n;
+}
+
+
+/****************
+ * Have a look at the iobuf.
+ * NOTE: This only works in special cases.
+ */
+int
+iobuf_peek(IOBUF a, byte *buf, unsigned buflen )
+{
+    int n=0;
+
+    if( !(a->d.start < a->d.len) ) {
+       if( underflow(a) == -1 )
+           return -1;
+       /* and unget this character */
+       assert(a->d.start == 1);
+       a->d.start = 0;
+    }
+
+    for(n=0 ; n < buflen && (a->d.start+n) < a->d.len ; n++, buf++ )
+       *buf = a->d.buf[n];
+    return n;
 }
 
 
+
+
 int
 iobuf_writebyte(IOBUF a, unsigned c)
 {
@@ -629,12 +801,18 @@ iobuf_writebyte(IOBUF a, unsigned c)
 int
 iobuf_write(IOBUF a, byte *buf, unsigned buflen )
 {
-    for( ; buflen; buflen--, buf++ )
-       if( iobuf_writebyte(a, *buf) )
-           return -1;
+    do {
+       for( ; buflen && a->d.len < a->d.size; buflen--, buf++ )
+           a->d.buf[a->d.len++] = *buf;
+       if( buflen ) {
+           if( iobuf_flush(a) )
+               return -1;
+       }
+    } while( buflen );
     return 0;
 }
 
+
 int
 iobuf_writestr(IOBUF a, const char *buf )
 {
@@ -669,55 +847,41 @@ iobuf_temp_to_buffer( IOBUF a, byte *buffer, size_t buflen )
     return n;
 }
 
-
 /****************
- * Set a limit, how much bytes may be read from the input stream A.
- * Setting the limit to 0 disables this feature.
+ * unget the contents of the temp io stream to A and close temp
+ * Could be optimized!!
  */
 void
-iobuf_set_limit( IOBUF a, unsigned long nlimit )
+iobuf_unget_and_close_temp( IOBUF a, IOBUF temp )
 {
-    a->nlimit = nlimit;
-    a->nbytes = 0;
+    if( a->unget.buf ) {
+       if( a->unget.start < a->unget.len )
+           log_fatal("cannot do any more ungets on this buffer\n");
+       /* not yet cleaned up; do it now */
+       m_free(a->unget.buf);
+       a->unget.buf = NULL;
+    }
+    a->unget.size = temp->d.len;
+    a->unget.buf = m_alloc( a->unget.size );
+    a->unget.len = temp->d.len;
+    memcpy( a->unget.buf, temp->d.buf, a->unget.len );
+    iobuf_close(temp);
 }
 
 
-
-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;
-}
-
+/****************
+ * Set a limit on how many bytes may be read from the input stream A.
+ * Setting the limit to 0 disables this feature.
+ */
 void
-iobuf_push_recorder( IOBUF a, int c )
+iobuf_set_limit( IOBUF a, unsigned long nlimit )
 {
-    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;
-    }
+    a->nlimit = nlimit;
+    a->ntotal += a->nbytes;
+    a->nbytes = 0;
 }
 
 
-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
@@ -742,13 +906,62 @@ iobuf_get_filelength( IOBUF a )
 }
 
 /****************
+ * Tell the file position, where the next read will take place
+ */
+ulong
+iobuf_tell( IOBUF a )
+{
+    return a->ntotal + a->nbytes;
+}
+
+
+
+/****************
+ * This is a very limited implementation. It simply discards all internal
+ * buffering and removes all filters but the first one.
+ */
+int
+iobuf_seek( IOBUF a, ulong newpos )
+{
+    file_filter_ctx_t *b = NULL;
+
+    for( ; a; a = a->chain ) {
+       if( !a->chain && a->filter == file_filter ) {
+           b = a->filter_ov;
+           break;
+       }
+    }
+    if( !a )
+       return -1;
+
+    if( fseek( b->fp, newpos, SEEK_SET ) ) {
+       log_error("can't seek to %lu: %s\n", newpos, strerror(errno) );
+       return -1;
+    }
+    a->d.len = 0;   /* discard buffer */
+    a->d.start = 0;
+    a->nbytes = 0;
+    a->nlimit = 0;
+    a->ntotal = newpos;
+    /* remove filters, but the last */
+    while( a->chain )
+       iobuf_pop_filter( a, a->filter, NULL );
+
+
+    return 0;
+}
+
+
+
+
+
+
+/****************
  * Retrieve the filename
  */
 const char *
 iobuf_get_fname( 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;
@@ -779,16 +992,37 @@ iobuf_set_block_mode( IOBUF a, size_t n )
     }
 }
 
+/****************
+ * enable partial block mode as described in the OpenPGP draft.
+ * LEN is the first length
+ */
+void
+iobuf_set_partial_block_mode( IOBUF a, size_t len )
+{
+    block_filter_ctx_t *ctx = m_alloc_clear( sizeof *ctx );
+
+    assert( a->usage == 1 || a->usage == 2 );
+    ctx->usage = a->usage;
+    if( !len ) {
+       iobuf_pop_filter(a, block_filter, NULL );
+    }
+    else {
+       ctx->partial = 1;
+       ctx->size = len;
+       iobuf_push_filter(a, block_filter, ctx );
+    }
+}
+
 
 /****************
- * checks wether the stream is in block mode
+ * Checks whether the stream is in block mode
+ * Note: This does not work if other filters are pushed on the stream.
  */
 int
 iobuf_in_block_mode( IOBUF a )
 {
-    for(; a; a = a->chain )
-       if( a->filter == block_filter )
-           return 1; /* yes */
+    if( a && a->filter == block_filter )
+       return 1; /* yes */
     return 0; /* no */
 }