w32: Add icons and version information.
[gnupg.git] / common / estream.c
index a73d1f2..79e3efb 100644 (file)
@@ -1,5 +1,5 @@
 /* estream.c - Extended Stream I/O Library
- * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010 g10 Code GmbH
+ * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010, 2011 g10 Code GmbH
  *
  * This file is part of Libestream.
  *
@@ -38,7 +38,7 @@
  *    products derived from this software without specific prior
  *    written permission.
  *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 # include <gpg-error.h> /* ERRNO replacement.  */
 #endif
 
-#ifdef WITHOUT_GNU_PTH /* Give the Makefile a chance to build without Pth.  */
-# undef HAVE_PTH
-# undef USE_GNU_PTH
+#ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth.  */
+# undef HAVE_NPTH
+# undef USE_NPTH
 #endif
 
-#ifdef HAVE_PTH
-# include <pth.h>
+#ifdef HAVE_NPTH
+# include <npth.h>
 #endif
 
 /* This is for the special hack to use estream.c in GnuPG.  */
@@ -159,7 +159,7 @@ typedef void (*func_free_t) (void *mem);
 
 /* Locking.  */
 
-#ifdef HAVE_PTH
+#ifdef HAVE_NPTH
 
 typedef pth_mutex_t estream_mutex_t;
 # define ESTREAM_MUTEX_INITIALIZER PTH_MUTEX_INIT
@@ -197,7 +197,7 @@ dummy_mutex_call_int (estream_mutex_t mutex)
 
 /* Primitive system I/O.  */
 
-#ifdef HAVE_PTH
+#ifdef HAVE_NPTH
 # define ESTREAM_SYS_READ  do_pth_read
 # define ESTREAM_SYS_WRITE do_pth_write
 # define ESTREAM_SYS_YIELD() pth_yield (NULL)
@@ -217,6 +217,17 @@ struct notify_list_s
 };
 typedef struct notify_list_s *notify_list_t;
 
+
+/* A private cookie function to implement an internal IOCTL
+   service.  */
+typedef int (*cookie_ioctl_function_t) (void *cookie, int cmd,
+                                       void *ptr, size_t *len);
+/* IOCTL commands for the private cookie function.  */
+#define COOKIE_IOCTL_SNATCH_BUFFER 1
+
+
+
+
 /* An internal stream object.  */
 struct estream_internal
 {
@@ -231,6 +242,7 @@ struct estream_internal
   es_cookie_read_function_t func_read;
   es_cookie_write_function_t func_write;
   es_cookie_seek_function_t func_seek;
+  cookie_ioctl_function_t func_ioctl;
   es_cookie_close_function_t func_close;
   int strategy;
   es_syshd_t syshd;              /* A copy of the sytem handle.  */
@@ -254,28 +266,26 @@ typedef struct estream_internal *estream_internal_t;
 #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
+/* A linked list to hold active stream objects.   */
+struct estream_list_s
 {
-  estream_t car;
-  estream_list_t cdr;
-  estream_list_t *prev_cdr;
+  struct estream_list_s *next;
+  estream_t stream;  /* Entry is not used if NULL.  */
 };
-
+typedef struct estream_list_s *estream_list_t;
 static estream_list_t estream_list;
-static estream_mutex_t estream_list_lock;
-
-#define ESTREAM_LIST_LOCK   ESTREAM_MUTEX_LOCK   (estream_list_lock)
-#define ESTREAM_LIST_UNLOCK ESTREAM_MUTEX_UNLOCK (estream_list_lock)
 
 /* File descriptors registered to be used as the standard file handles. */
 static int custom_std_fds[3];
 static unsigned char custom_std_fds_valid[3];
 
+/* A lock object for the estream list and the custom_std_fds array.  */
+static estream_mutex_t estream_list_lock;
+#define ESTREAM_LIST_LOCK   ESTREAM_MUTEX_LOCK   (estream_list_lock)
+#define ESTREAM_LIST_UNLOCK ESTREAM_MUTEX_UNLOCK (estream_list_lock)
+
 
+/* Error code replacements.  */
 #ifndef EOPNOTSUPP
 # define EOPNOTSUPP ENOSYS
 #endif
@@ -372,75 +382,63 @@ map_w32_to_errno (DWORD w32_err)
  */
 
 /* Add STREAM to the list of registered stream objects.  If
-   WITH_LOCKED_LIST is true we assumed that the list of streams is
-   already locked.  */
+   WITH_LOCKED_LIST is true it is assumed that the list of streams is
+   already locked.  The implementation is straightforward: We first
+   look for an unused entry in the list and use that; if none is
+   available we put a new item at the head.  We drawback of the
+   strategy never to shorten the list is that a one time allocation of
+   many streams will lead to scanning unused entries later.  If that
+   turns out to be a problem, we may either free some items from the
+   list or append new entries at the end; or use a table.  Returns 0
+   on success; on error or non-zero is returned and ERRNO set.  */
 static int
 do_list_add (estream_t stream, int with_locked_list)
 {
-  estream_list_t list_obj;
-  int ret;
+  estream_list_t item;
 
-  list_obj = mem_alloc (sizeof (*list_obj));
-  if (! list_obj)
-    ret = -1;
-  else
+  if (!with_locked_list)
+    ESTREAM_LIST_LOCK;
+
+  for (item = estream_list; item && item->stream; item = item->next)
+    ;
+  if (!item)
     {
-      if (!with_locked_list)
-        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;
-      if (!with_locked_list)
-        ESTREAM_LIST_UNLOCK;
-      ret = 0;
+      item = mem_alloc (sizeof *item);
+      if (item)
+        {
+          item->next = estream_list;
+          estream_list = item;
+        }
     }
+  if (item)
+    item->stream = stream;
 
-  return ret;
+  if (!with_locked_list)
+    ESTREAM_LIST_UNLOCK;
+
+  return item? 0 : -1;
 }
 
 /* Remove STREAM from the list of registered stream objects.  */
 static void
 do_list_remove (estream_t stream, int with_locked_list)
 {
-  estream_list_t list_obj;
+  estream_list_t item;
 
   if (!with_locked_list)
     ESTREAM_LIST_LOCK;
-  for (list_obj = estream_list; list_obj; list_obj = list_obj->cdr)
-    if (list_obj->car == stream)
+
+  for (item = estream_list; item; item = item->next)
+    if (item->stream == 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;
+        item->stream = NULL;
+        break;
       }
+
   if (!with_locked_list)
     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
-do_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;
-}
-
 
 \f
 /*
@@ -452,7 +450,7 @@ do_list_iterate (estream_iterator_t iterator)
  * write, assuming that we do I/O on a plain file where the operation
  * can't block.
  */
-#ifdef HAVE_PTH
+#ifdef HAVE_NPTH
 static int
 do_pth_read (int fd, void *buffer, size_t size)
 {
@@ -478,7 +476,7 @@ do_pth_write (int fd, const void *buffer, size_t size)
   return pth_write (fd, buffer, size);
 # endif /* !HAVE_W32_SYSTEM*/
 }
-#endif /*HAVE_PTH*/
+#endif /*HAVE_NPTH*/
 
 \f
 
@@ -487,6 +485,14 @@ do_deinit (void)
 {
   /* Flush all streams. */
   es_fflush (NULL);
+
+  /* We should release the estream_list.  However there is one
+     problem: That list is also used to search for the standard
+     estream file descriptors.  If we would remove the entire list,
+     any use of es_foo in another atexit function may re-create the
+     list and the streams with possible undesirable effects.  Given
+     that we don't close the stream either, it should not matter that
+     we keep the list and let the OS clean it up at process end.  */
 }
 
 
@@ -501,7 +507,7 @@ do_init (void)
 
   if (!initialized)
     {
-#ifdef HAVE_PTH
+#ifdef HAVE_NPTH
       if (!pth_init () && errno != EPERM )
         return -1;
       if (pth_mutex_init (&estream_list_lock))
@@ -545,7 +551,9 @@ typedef struct estream_cookie_mem
    supplied buffer with the initial conetnt of the memory buffer.  If
    DATA is NULL, DATA_N and DATA_LEN need to be 0 as well.  If DATA is
    not NULL, DATA_N gives the allocated size of DATA and DATA_LEN the
-   used length in DATA.  */
+   used length in DATA.  If this fucntion succeeds DATA is now owned
+   by this function.  If GROW is false FUNC_REALLOC is not
+   required. */
 static int
 func_mem_create (void *ES__RESTRICT *ES__RESTRICT cookie,
                  unsigned char *ES__RESTRICT data, size_t data_n,
@@ -563,6 +571,11 @@ func_mem_create (void *ES__RESTRICT *ES__RESTRICT cookie,
       _set_errno (EINVAL);
       return -1;
     }
+  if (grow && func_free && !func_realloc)
+    {
+      _set_errno (EINVAL);
+      return -1;
+    }
 
   mem_cookie = mem_alloc (sizeof (*mem_cookie));
   if (!mem_cookie)
@@ -577,7 +590,8 @@ func_mem_create (void *ES__RESTRICT *ES__RESTRICT cookie,
       mem_cookie->data_len = data_len;
       mem_cookie->block_size = block_size;
       mem_cookie->flags.grow = !!grow;
-      mem_cookie->func_realloc = func_realloc ? func_realloc : mem_realloc;
+      mem_cookie->func_realloc
+        = grow? (func_realloc ? func_realloc : mem_realloc) : NULL;
       mem_cookie->func_free = func_free ? func_free : mem_free;
       *cookie = mem_cookie;
       err = 0;
@@ -628,7 +642,8 @@ es_func_mem_write (void *cookie, const void *buffer, size_t size)
   assert (mem_cookie->memory_size >= mem_cookie->offset);
   nleft = mem_cookie->memory_size - mem_cookie->offset;
 
-  /* If we are not allowed to grow limit the size to the left space.  */
+  /* If we are not allowed to grow the buffer, limit the size to the
+     left space.  */
   if (!mem_cookie->flags.grow && size > nleft)
     size = nleft;
 
@@ -669,6 +684,7 @@ es_func_mem_write (void *cookie, const void *buffer, size_t size)
           return -1;
         }
 
+      assert (mem_cookie->func_realloc);
       newbuf = mem_cookie->func_realloc (mem_cookie->memory, newsize);
       if (!newbuf)
         return -1;
@@ -744,6 +760,7 @@ es_func_mem_seek (void *cookie, off_t *offset, int whence)
           return -1;
         }
 
+      assert (mem_cookie->func_realloc);
       newbuf = mem_cookie->func_realloc (mem_cookie->memory, newsize);
       if (!newbuf)
         return -1;
@@ -766,6 +783,33 @@ es_func_mem_seek (void *cookie, off_t *offset, int whence)
   return 0;
 }
 
+/* An IOCTL function for memory objects.  */
+static int
+es_func_mem_ioctl (void *cookie, int cmd, void *ptr, size_t *len)
+{
+  estream_cookie_mem_t mem_cookie = cookie;
+  int ret;
+
+  if (cmd == COOKIE_IOCTL_SNATCH_BUFFER)
+    {
+      /* Return the internal buffer of the stream to the caller and
+         invalidate it for the stream.  */
+      *(void**)ptr = mem_cookie->memory;
+      *len = mem_cookie->offset;
+      mem_cookie->memory = NULL;
+      mem_cookie->memory_size = 0;
+      mem_cookie->offset = 0;
+      ret = 0;
+    }
+  else
+    {
+      _set_errno (EINVAL);
+      ret = -1;
+    }
+
+  return ret;
+}
+
 
 /* Destroy function for memory objects.  */
 static int
@@ -989,7 +1033,7 @@ es_func_w32_read (void *cookie, void *buffer, size_t size)
     {
       do
         {
-#ifdef HAVE_PTH
+#ifdef HAVE_NPTH
           /* Note: Our pth_read actually uses HANDLE! */
           bytes_read = pth_read ((int)w32_cookie->hd, buffer, size);
 #else
@@ -1034,7 +1078,7 @@ es_func_w32_write (void *cookie, const void *buffer, size_t size)
     {
       do
         {
-#ifdef HAVE_PTH
+#ifdef HAVE_NPTH
           /* Note: Our pth_write actually uses HANDLE! */
           bytes_written = pth_write ((int)w32_cookie->hd, buffer, size);
 #else
@@ -1231,6 +1275,7 @@ es_func_fp_write (void *cookie, const void *buffer, size_t size)
 #else
       bytes_written = fwrite (buffer, 1, size, file_cookie->fp);
 #endif
+      fflush (file_cookie->fp);
     }
   else
     bytes_written = size; /* Successfully written to the bit bucket.  */
@@ -1603,6 +1648,7 @@ es_initialize (estream_t stream,
   stream->intern->func_read = functions.func_read;
   stream->intern->func_write = functions.func_write;
   stream->intern->func_seek = functions.func_seek;
+  stream->intern->func_ioctl = NULL;
   stream->intern->func_close = functions.func_close;
   stream->intern->strategy = _IOFBF;
   stream->intern->syshd = *syshd;
@@ -2586,23 +2632,33 @@ es_fopen (const char *ES__RESTRICT path, const char *ES__RESTRICT mode)
 
 
 \f
+/* Create a new estream object in memory.  If DATA is not NULL this
+   buffer will be used as the memory buffer; thus after this functions
+   returns with the success the the memory at DATA belongs to the new
+   estream.  The allocated length of DATA is given by DATA_LEN and its
+   used length by DATA_N.  Usually this is malloced buffer; if a
+   static buffer is provided, the caller must pass false for GROW and
+   provide a dummy function for FUNC_FREE.  FUNC_FREE and FUNC_REALLOC
+   allow the caller to provide custom functions for realloc and free
+   to be used by the new estream object.  Note that the realloc
+   function is also used for initial allocation.  If DATA is NULL a
+   buffer is internally allocated; either using internal function or
+   those provide by the caller.  It is an error to provide a realloc
+   function but no free function.  Providing only a free function is
+   allowed as long as GROW is false.  */
 estream_t
-es_mopen (unsigned char *ES__RESTRICT data, size_t data_n, size_t data_len,
+es_mopen (void *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)
 {
+  int create_called = 0;
+  estream_t stream = NULL;
+  void *cookie = NULL;
   unsigned int modeflags;
-  int create_called;
-  estream_t stream;
-  void *cookie;
   int err;
   es_syshd_t syshd;
 
-  cookie = 0;
-  stream = NULL;
-  create_called = 0;
-
   err = parse_mode (mode, &modeflags, NULL);
   if (err)
     goto out;
@@ -2642,7 +2698,6 @@ es_fopenmem (size_t memlimit, const char *ES__RESTRICT mode)
     return NULL;
   modeflags |= O_RDWR;
 
-
   if (func_mem_create (&cookie, NULL, 0, 0,
                        BUFFER_BLOCK_SIZE, 1,
                        mem_realloc, mem_free, modeflags,
@@ -2653,6 +2708,42 @@ es_fopenmem (size_t memlimit, const char *ES__RESTRICT mode)
   if (es_create (&stream, cookie, &syshd, estream_functions_mem, modeflags, 0))
     (*estream_functions_mem.func_close) (cookie);
 
+  if (stream)
+    stream->intern->func_ioctl = es_func_mem_ioctl;
+
+  return stream;
+}
+
+\f
+/* This is the same as es_fopenmem but intializes the memory with a
+   copy of (DATA,DATALEN).  The stream is initally set to the
+   beginning.  If MEMLIMIT is not 0 but shorter than DATALEN it
+   DATALEN will be used as the value for MEMLIMIT.  */
+estream_t
+es_fopenmem_init (size_t memlimit, const char *ES__RESTRICT mode,
+                  const void *data, size_t datalen)
+{
+  estream_t stream;
+
+  if (memlimit && memlimit < datalen)
+    memlimit = datalen;
+
+  stream = es_fopenmem (memlimit, mode);
+  if (stream && data && datalen)
+    {
+      if (es_writen (stream, data, datalen, NULL))
+        {
+          int saveerrno = errno;
+          es_fclose (stream);
+          stream = NULL;
+          _set_errno (saveerrno);
+        }
+      else
+        {
+          es_seek (stream, 0L, SEEK_SET, NULL);
+          es_set_indicators (stream, 0, 0);
+        }
+    }
   return stream;
 }
 
@@ -2905,11 +2996,11 @@ _es_get_std_stream (int fd)
 
   fd %= 3; /* We only allow 0, 1 or 2 but we don't want to return an error. */
   ESTREAM_LIST_LOCK;
-  for (list_obj = estream_list; list_obj; list_obj = list_obj->cdr)
-    if (list_obj->car->intern->is_stdstream
-        && list_obj->car->intern->stdstream_fd == fd)
+  for (list_obj = estream_list; list_obj; list_obj = list_obj->next)
+    if (list_obj->stream && list_obj->stream->intern->is_stdstream
+        && list_obj->stream->intern->stdstream_fd == fd)
       {
-       stream = list_obj->car;
+       stream = list_obj->stream;
        break;
       }
   if (!stream)
@@ -3034,6 +3125,65 @@ es_fclose (estream_t stream)
 }
 
 
+/* This is a special version of es_fclose which can be used with
+   es_fopenmem to return the memory buffer.  This is feature is useful
+   to write to a memory buffer using estream.  Note that the function
+   does not close the stream if the stream does not support snatching
+   the buffer.  On error NULL is stored at R_BUFFER.  Note that if no
+   write operation has happened, NULL may also be stored at BUFFER on
+   success.  The caller needs to release the returned memory using
+   es_free.  */
+int
+es_fclose_snatch (estream_t stream, void **r_buffer, size_t *r_buflen)
+{
+  int err;
+
+  /* Note: There is no need to lock the stream in a close call.  The
+     object will be destroyed after the close and thus any other
+     contender for the lock would work on a closed stream.  */
+
+  if (r_buffer)
+    {
+      cookie_ioctl_function_t func_ioctl = stream->intern->func_ioctl;
+      size_t buflen;
+
+      *r_buffer = NULL;
+
+      if (!func_ioctl)
+        {
+          _set_errno (EOPNOTSUPP);
+          err = -1;
+          goto leave;
+        }
+
+      if (stream->flags.writing)
+        {
+          err = es_flush (stream);
+          if (err)
+            goto leave;
+          stream->flags.writing = 0;
+        }
+
+      err = func_ioctl (stream->intern->cookie, COOKIE_IOCTL_SNATCH_BUFFER,
+                        r_buffer, &buflen);
+      if (err)
+        goto leave;
+      if (r_buflen)
+        *r_buflen = buflen;
+    }
+
+  err = do_close (stream, 0);
+
+ leave:
+  if (err && r_buffer)
+    {
+      mem_free (*r_buffer);
+      *r_buffer = NULL;
+    }
+  return err;
+}
+
+
 /* Register or unregister a close notification function for STREAM.
    FNC is the function to call and FNC_VALUE the value passed as
    second argument.  To register the notification the value for MODE
@@ -3241,8 +3391,20 @@ es_fflush (estream_t stream)
       ESTREAM_UNLOCK (stream);
     }
   else
-    err = do_list_iterate (do_fflush);
+    {
+      estream_list_t item;
 
+      err = 0;
+      ESTREAM_LIST_LOCK;
+      for (item = estream_list; item; item = item->next)
+        if (item->stream)
+          {
+            ESTREAM_LOCK (item->stream);
+            err |= do_fflush (item->stream);
+            ESTREAM_UNLOCK (item->stream);
+          }
+      ESTREAM_LIST_UNLOCK;
+    }
   return err ? EOF : 0;
 }
 
@@ -3419,12 +3581,11 @@ 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);
+      es_readn (stream, ptr, size * nitems, &bytes);
       ESTREAM_UNLOCK (stream);
 
       ret = bytes / size;
@@ -3441,12 +3602,11 @@ 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);
+      es_writen (stream, ptr, size * nitems, &bytes);
       ESTREAM_UNLOCK (stream);
 
       ret = bytes / size;