Print --version etc via estream
[gnupg.git] / common / estream.c
index dfa2de4..3a17910 100644 (file)
@@ -118,10 +118,19 @@ void *memrchr (const void *block, int c, size_t size);
 
 #ifdef HAVE_W32CE_SYSTEM
 # define _set_errno(a)  gpg_err_set_errno ((a))
+/* Setmode is missing in cegcc but available since CE 5.0.  */
+int _setmode (int handle, int mode);
+# define setmode(a,b)   _setmode ((a),(b))
 #else
 # define _set_errno(a)  do { errno = (a); } while (0)
 #endif
 
+#ifdef HAVE_W32_SYSTEM
+# define IS_INVALID_FD(a) ((void*)(a) == (void*)(-1))
+#else
+# define IS_INVALID_FD(a) ((a) == -1)
+#endif
+
 
 /* Generally used types.  */
 
@@ -181,9 +190,11 @@ dummy_mutex_call_int (estream_mutex_t mutex)
 #ifdef HAVE_PTH
 # define ESTREAM_SYS_READ  es_pth_read
 # define ESTREAM_SYS_WRITE es_pth_write
+# define ESTREAM_SYS_YIELD() pth_yield (NULL)
 #else
 # define ESTREAM_SYS_READ  read
 # define ESTREAM_SYS_WRITE write
+# define ESTREAM_SYS_YIELD() do { } while (0)
 #endif
 
 /* Misc definitions.  */
@@ -213,6 +224,8 @@ struct estream_internal
     unsigned int eof: 1;
   } indicators;
   unsigned int deallocate_buffer: 1;
+  unsigned int is_stdstream:1;   /* This is a standard stream.  */
+  unsigned int stdstream_fd:2;   /* 0, 1 or 2 for a standard stream.  */
   unsigned int print_err: 1;     /* Error in print_fun_writer.  */
   int print_errno;               /* Errno from print_fun_writer.  */
   size_t print_ntotal;           /* Bytes written from in print_fun_writer. */
@@ -243,6 +256,11 @@ 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];
+
+
 #ifndef EOPNOTSUPP
 # define EOPNOTSUPP ENOSYS
 #endif
@@ -302,9 +320,11 @@ mem_free (void *p)
  * List manipulation.
  */
 
-/* Add STREAM to the list of registered stream objects.  */
+/* 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.  */
 static int
-es_list_add (estream_t stream)
+es_list_add (estream_t stream, int with_locked_list)
 {
   estream_list_t list_obj;
   int ret;
@@ -314,14 +334,16 @@ es_list_add (estream_t stream)
     ret = -1;
   else
     {
-      ESTREAM_LIST_LOCK;
+      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;
-      ESTREAM_LIST_UNLOCK;
+      if (!with_locked_list)
+        ESTREAM_LIST_UNLOCK;
       ret = 0;
     }
 
@@ -330,11 +352,12 @@ es_list_add (estream_t stream)
 
 /* Remove STREAM from the list of registered stream objects.  */
 static void
-es_list_remove (estream_t stream)
+es_list_remove (estream_t stream, int with_locked_list)
 {
   estream_list_t list_obj;
   
-  ESTREAM_LIST_LOCK;
+  if (!with_locked_list)
+    ESTREAM_LIST_LOCK;
   for (list_obj = estream_list; list_obj; list_obj = list_obj->cdr)
     if (list_obj->car == stream)
       {
@@ -344,7 +367,8 @@ es_list_remove (estream_t stream)
        mem_free (list_obj);
        break;
       }
-  ESTREAM_LIST_UNLOCK;
+  if (!with_locked_list)
+    ESTREAM_LIST_UNLOCK;
 }
 
 /* Type of an stream-iterator-function.  */
@@ -407,6 +431,14 @@ es_pth_write (int fd, const void *buffer, size_t size)
 
 \f
 
+static void
+es_deinit (void)
+{
+  /* Flush all streams. */
+  es_fflush (NULL);
+}
+
+
 /*
  * Initialization.
  */
@@ -414,17 +446,20 @@ es_pth_write (int fd, const void *buffer, size_t size)
 static int
 es_init_do (void)
 {
-#ifdef HAVE_PTH
   static int initialized;
 
   if (!initialized)
     {
+#ifdef HAVE_PTH
       if (!pth_init () && errno != EPERM )
         return -1;
       if (pth_mutex_init (&estream_list_lock))
         initialized = 1;
-    }
+#else
+      initialized = 1;
 #endif
+      atexit (es_deinit);  
+    }
   return 0;
 }
 
@@ -750,10 +785,18 @@ es_func_fd_read (void *cookie, void *buffer, size_t size)
 {
   estream_cookie_fd_t file_cookie = cookie;
   ssize_t bytes_read;
-
-  do 
-    bytes_read = ESTREAM_SYS_READ (file_cookie->fd, buffer, size);
-  while (bytes_read == -1 && errno == EINTR);
+  
+  if (IS_INVALID_FD (file_cookie->fd))
+    {
+      ESTREAM_SYS_YIELD ();
+      bytes_read = 0;
+    }
+  else
+    {
+      do 
+        bytes_read = ESTREAM_SYS_READ (file_cookie->fd, buffer, size);
+      while (bytes_read == -1 && errno == EINTR);
+    }
 
   return bytes_read;
 }
@@ -761,14 +804,21 @@ es_func_fd_read (void *cookie, void *buffer, size_t size)
 /* Write function for fd objects.  */
 static ssize_t
 es_func_fd_write (void *cookie, const void *buffer, size_t size)
-                          
 {
   estream_cookie_fd_t file_cookie = cookie;
   ssize_t bytes_written;
 
-  do
-    bytes_written = ESTREAM_SYS_WRITE (file_cookie->fd, buffer, size);
-  while (bytes_written == -1 && errno == EINTR);
+  if (IS_INVALID_FD (file_cookie->fd))
+    {
+      ESTREAM_SYS_YIELD ();
+      bytes_written = size; /* Yeah:  Success writing to the bit bucket.  */
+    }
+  else
+    {
+      do
+        bytes_written = ESTREAM_SYS_WRITE (file_cookie->fd, buffer, size);
+      while (bytes_written == -1 && errno == EINTR);
+    }
 
   return bytes_written;
 }
@@ -781,13 +831,21 @@ es_func_fd_seek (void *cookie, off_t *offset, int whence)
   off_t offset_new;
   int err;
 
-  offset_new = lseek (file_cookie->fd, *offset, whence);
-  if (offset_new == -1)
-    err = -1;
+  if (IS_INVALID_FD (file_cookie->fd))
+    {
+      _set_errno (ESPIPE);
+      err = -1;
+    }
   else
     {
-      *offset = offset_new;
-      err = 0;
+      offset_new = lseek (file_cookie->fd, *offset, whence);
+      if (offset_new == -1)
+        err = -1;
+      else
+        {
+          *offset = offset_new;
+          err = 0;
+        }
     }
 
   return err;
@@ -802,7 +860,10 @@ es_func_fd_destroy (void *cookie)
 
   if (fd_cookie)
     {
-      err = fd_cookie->no_close? 0 : close (fd_cookie->fd);
+      if (IS_INVALID_FD (fd_cookie->fd))
+        err = 0;
+      else
+        err = fd_cookie->no_close? 0 : close (fd_cookie->fd);
       mem_free (fd_cookie);
     }
   else
@@ -1211,6 +1272,8 @@ es_initialize (estream_t stream,
   stream->intern->print_fp = NULL;
   stream->intern->indicators.err = 0;
   stream->intern->indicators.eof = 0;
+  stream->intern->is_stdstream = 0;
+  stream->intern->stdstream_fd = 0;
   stream->intern->deallocate_buffer = 0;
 
   stream->data_len = 0;
@@ -1219,7 +1282,7 @@ es_initialize (estream_t stream,
   stream->unread_data_len = 0;
   /* Depending on the modeflags we set whether we start in writing or
      reading mode.  This is required in case we are working on a
-     wronly stream which is not seeekable (like stdout).  Without this
+     stream which is not seeekable (like stdout).  Without this
      pre-initialization we would do a seek at the first write call and
      as this will fail no utput will be delivered. */
   if ((modeflags & O_WRONLY) || (modeflags & O_RDWR) )
@@ -1258,7 +1321,8 @@ es_deinitialize (estream_t stream)
 /* Create a new stream object, initialize it.  */
 static int
 es_create (estream_t *stream, void *cookie, int fd,
-          es_cookie_io_functions_t functions, unsigned int modeflags)
+          es_cookie_io_functions_t functions, unsigned int modeflags,
+           int with_locked_list)
 {
   estream_internal_t stream_internal_new;
   estream_t stream_new;
@@ -1290,7 +1354,7 @@ es_create (estream_t *stream, void *cookie, int fd,
   ESTREAM_MUTEX_INITIALIZE (stream_new->intern->lock);
   es_initialize (stream_new, cookie, fd, functions, modeflags);
 
-  err = es_list_add (stream_new);
+  err = es_list_add (stream_new, with_locked_list);
   if (err)
     goto out;
 
@@ -1312,13 +1376,13 @@ es_create (estream_t *stream, void *cookie, int fd,
 
 /* Deinitialize a stream object and destroy it.  */
 static int
-es_destroy (estream_t stream)
+es_destroy (estream_t stream, int with_locked_list)
 {
   int err = 0;
 
   if (stream)
     {
-      es_list_remove (stream);
+      es_list_remove (stream, with_locked_list);
       err = es_deinitialize (stream);
       mem_free (stream->intern);
       mem_free (stream);
@@ -1838,7 +1902,7 @@ doreadline (estream_t ES__RESTRICT stream, size_t max_length,
     goto out;
 
   err = es_create (&line_stream, line_stream_cookie, -1,
-                  estream_functions_mem, O_RDWR);
+                  estream_functions_mem, O_RDWR, 0);
   if (err)
     goto out;
 
@@ -1923,7 +1987,7 @@ doreadline (estream_t ES__RESTRICT stream, size_t max_length,
  out:
 
   if (line_stream)
-    es_destroy (line_stream);
+    es_destroy (line_stream, 0);
   else if (line_stream_cookie)
     es_func_mem_destroy (line_stream_cookie);
 
@@ -2028,6 +2092,8 @@ es_set_buffering (estream_t ES__RESTRICT stream,
        buffer_new = buffer;
       else
        {
+          if (!size)
+            size = BUFSIZ;
          buffer_new = mem_alloc (size);
          if (! buffer_new)
            {
@@ -2120,7 +2186,7 @@ es_fopen (const char *ES__RESTRICT path, const char *ES__RESTRICT mode)
     goto out;
 
   create_called = 1;
-  err = es_create (&stream, cookie, fd, estream_functions_file, modeflags);
+  err = es_create (&stream, cookie, fd, estream_functions_file, modeflags, 0);
   if (err)
     goto out;
 
@@ -2160,7 +2226,7 @@ es_mopen (unsigned char *ES__RESTRICT data, size_t data_n, size_t data_len,
     goto out;
   
   create_called = 1;
-  err = es_create (&stream, cookie, -1, estream_functions_mem, modeflags);
+  err = es_create (&stream, cookie, -1, estream_functions_mem, modeflags, 0);
 
  out:
 
@@ -2191,7 +2257,7 @@ es_fopenmem (size_t memlimit, const char *ES__RESTRICT mode)
                           memlimit))
     return NULL;
   
-  if (es_create (&stream, cookie, -1, estream_functions_mem, modeflags))
+  if (es_create (&stream, cookie, -1, estream_functions_mem, modeflags, 0))
     (*estream_functions_mem.func_close) (cookie);
 
   return stream;
@@ -2215,7 +2281,7 @@ es_fopencookie (void *ES__RESTRICT cookie,
   if (err)
     goto out;
 
-  err = es_create (&stream, cookie, -1, functions, modeflags);
+  err = es_create (&stream, cookie, -1, functions, modeflags, 0);
   if (err)
     goto out;
 
@@ -2226,7 +2292,7 @@ es_fopencookie (void *ES__RESTRICT cookie,
 
 
 estream_t
-do_fdopen (int filedes, const char *mode, int no_close)
+do_fdopen (int filedes, const char *mode, int no_close, int with_locked_list)
 {
   unsigned int modeflags;
   int create_called;
@@ -2247,7 +2313,8 @@ do_fdopen (int filedes, const char *mode, int no_close)
     goto out;
 
   create_called = 1;
-  err = es_create (&stream, cookie, filedes, estream_functions_fd, modeflags);
+  err = es_create (&stream, cookie, filedes, estream_functions_fd,
+                   modeflags, with_locked_list);
 
  out:
 
@@ -2260,19 +2327,19 @@ do_fdopen (int filedes, const char *mode, int no_close)
 estream_t
 es_fdopen (int filedes, const char *mode)
 {
-  return do_fdopen (filedes, mode, 0);
+  return do_fdopen (filedes, mode, 0, 0);
 }
 
 /* A variant of es_fdopen which does not close FILEDES at the end.  */
 estream_t
 es_fdopen_nc (int filedes, const char *mode)
 {
-  return do_fdopen (filedes, mode, 1);
+  return do_fdopen (filedes, mode, 1, 0);
 }
 
 
 estream_t
-do_fpopen (FILE *fp, const char *mode, int no_close)
+do_fpopen (FILE *fp, const char *mode, int no_close, int with_locked_list)
 {
   unsigned int modeflags;
   int create_called;
@@ -2296,7 +2363,7 @@ do_fpopen (FILE *fp, const char *mode, int no_close)
   
   create_called = 1;
   err = es_create (&stream, cookie, fp? fileno (fp):-1, estream_functions_fp,
-                   modeflags);
+                   modeflags, with_locked_list);
 
  out:
 
@@ -2318,7 +2385,7 @@ do_fpopen (FILE *fp, const char *mode, int no_close)
 estream_t
 es_fpopen (FILE *fp, const char *mode)
 {
-  return do_fpopen (fp, mode, 0);
+  return do_fpopen (fp, mode, 0, 0);
 }
 
 
@@ -2326,7 +2393,83 @@ es_fpopen (FILE *fp, const char *mode)
 estream_t
 es_fpopen_nc (FILE *fp, const char *mode)
 {
-  return do_fpopen (fp, mode, 1);
+  return do_fpopen (fp, mode, 1, 0);
+}
+
+
+/* Set custom standard descriptors to be used for stdin, stdout and
+   stderr.  This function needs to be called before any of the
+   standard streams are accessed.  */
+void
+_es_set_std_fd (int no, int fd)
+{
+  ESTREAM_LIST_LOCK;
+  if (no >= 0 && no < 3 && !custom_std_fds_valid[no])
+    {
+      custom_std_fds[no] = fd;
+      custom_std_fds_valid[no] = 1;
+    }
+  ESTREAM_LIST_UNLOCK;
+}
+
+
+/* Return the stream used for stdin, stdout or stderr.  */
+estream_t
+_es_get_std_stream (int fd)
+{
+  estream_list_t list_obj;
+  estream_t stream = NULL;
+
+  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)
+      {
+       stream = list_obj->car;
+       break;
+      }
+  if (!stream)
+    {
+      /* Standard stream not yet created.  We first try to create them
+         from registered file descriptors.  */
+      if (!fd && custom_std_fds_valid[0])
+        stream = do_fdopen (custom_std_fds[0], "r", 1, 1);
+      else if (fd == 1 && custom_std_fds_valid[1])
+        stream = do_fdopen (custom_std_fds[1], "a", 1, 1);
+      else if (custom_std_fds_valid[2])
+        stream = do_fdopen (custom_std_fds[1], "a", 1, 1);
+      
+      if (!stream)
+        {
+          /* Second try is to use the standard C streams.  */
+          if (!fd)
+            stream = do_fpopen (stdin, "r", 1, 1);
+          else if (fd == 1)
+            stream = do_fpopen (stdout, "a", 1, 1);
+          else
+            stream = do_fpopen (stderr, "a", 1, 1);
+        }
+      
+      if (!stream) 
+        {
+          /* Last try: Create a bit bucket.  */
+          stream = do_fpopen (NULL, fd? "a":"r", 0, 1);
+          if (!stream)
+            {
+              fprintf (stderr, "fatal: error creating a dummy estream"
+                       " for %d: %s\n", fd, strerror (errno));
+              abort();
+            }
+        }
+
+      stream->intern->is_stdstream = 1;
+      stream->intern->stdstream_fd = fd;
+      if (fd == 2)
+        es_set_buffering (stream, NULL, _IOLBF, 0);
+    }
+  ESTREAM_LIST_UNLOCK;
+  return stream;
 }
 
 
@@ -2368,7 +2511,7 @@ es_freopen (const char *ES__RESTRICT path, const char *ES__RESTRICT mode,
          if (create_called)
            es_func_fd_destroy (cookie);
       
-         es_destroy (stream);
+         es_destroy (stream, 0);
          stream = NULL;
        }
       else
@@ -2379,7 +2522,7 @@ es_freopen (const char *ES__RESTRICT path, const char *ES__RESTRICT mode,
       /* FIXME?  We don't support re-opening at the moment.  */
       _set_errno (EINVAL);
       es_deinitialize (stream);
-      es_destroy (stream);
+      es_destroy (stream, 0);
       stream = NULL;
     }
 
@@ -2392,7 +2535,7 @@ es_fclose (estream_t stream)
 {
   int err;
 
-  err = es_destroy (stream);
+  err = es_destroy (stream, 0);
 
   return err;
 }
@@ -2494,6 +2637,23 @@ es_clearerr (estream_t stream)
 }
 
 
+static int
+do_fflush (estream_t stream)
+{
+  int err;
+  
+  if (stream->flags.writing)
+    err = es_flush (stream);
+  else
+    {
+      es_empty (stream);
+      err = 0;
+    }
+
+  return err;
+}
+
+
 int
 es_fflush (estream_t stream)
 {
@@ -2502,17 +2662,11 @@ es_fflush (estream_t stream)
   if (stream)
     {
       ESTREAM_LOCK (stream);
-      if (stream->flags.writing)
-       err = es_flush (stream);
-      else
-       {
-         es_empty (stream);
-         err = 0;
-       }
+      err = do_fflush (stream);
       ESTREAM_UNLOCK (stream);
     }
   else
-    err = es_list_iterate (es_fflush);
+    err = es_list_iterate (do_fflush);
 
   return err ? EOF : 0;
 }
@@ -3184,7 +3338,7 @@ es_tmpfile (void)
     goto out;
 
   create_called = 1;
-  err = es_create (&stream, cookie, fd, estream_functions_fd, modeflags);
+  err = es_create (&stream, cookie, fd, estream_functions_fd, modeflags, 0);
 
  out:
 
@@ -3207,8 +3361,8 @@ es_setvbuf (estream_t ES__RESTRICT stream,
 {
   int err;
   
-  if (((type == _IOFBF) || (type == _IOLBF) || (type == _IONBF))
-      && (! ((! size) && (type != _IONBF))))
+  if ((type == _IOFBF || type == _IOLBF || type == _IONBF)
+      && (!buf || size || type == _IONBF))
     {
       ESTREAM_LOCK (stream);
       err = es_set_buffering (stream, buf, type, size);