OSDN Git Service

PR libfortran/27107
[pf3gnuchains/gcc-fork.git] / libgfortran / io / unix.c
index bcf50f3..aa1dd1f 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2002, 2003, 2004, 2005
+/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007
    Free Software Foundation, Inc.
    Contributed by Andy Vaught
 
@@ -36,24 +36,23 @@ Boston, MA 02110-1301, USA.  */
 
 #include <unistd.h>
 #include <stdio.h>
+#include <stdarg.h>
 #include <sys/stat.h>
 #include <fcntl.h>
+#include <assert.h>
 
-#ifdef HAVE_SYS_MMAN_H
-#include <sys/mman.h>
-#endif
 #include <string.h>
 #include <errno.h>
 
 #include "libgfortran.h"
 #include "io.h"
 
-#ifndef PATH_MAX
-#define PATH_MAX 1024
+#ifndef SSIZE_MAX
+#define SSIZE_MAX SHRT_MAX
 #endif
 
-#ifndef MAP_FAILED
-#define MAP_FAILED ((void *) -1)
+#ifndef PATH_MAX
+#define PATH_MAX 1024
 #endif
 
 #ifndef PROT_READ
@@ -82,6 +81,42 @@ Boston, MA 02110-1301, USA.  */
 #define S_IWOTH 0
 #endif
 
+
+/* Unix stream I/O module */
+
+#define BUFFER_SIZE 8192
+
+typedef struct
+{
+  stream st;
+
+  int fd;
+  gfc_offset buffer_offset;    /* File offset of the start of the buffer */
+  gfc_offset physical_offset;  /* Current physical file offset */
+  gfc_offset logical_offset;   /* Current logical file offset */
+  gfc_offset dirty_offset;     /* Start of modified bytes in buffer */
+  gfc_offset file_length;      /* Length of the file, -1 if not seekable. */
+
+  char *buffer;
+  int len;                     /* Physical length of the current buffer */
+  int active;                  /* Length of valid bytes in the buffer */
+
+  int prot;
+  int ndirty;                  /* Dirty bytes starting at dirty_offset */
+
+  int special_file;            /* =1 if the fd refers to a special file */
+
+  unsigned unbuffered:1;
+
+  char small_buffer[BUFFER_SIZE];
+
+}
+unix_stream;
+
+extern stream *init_error_stream (unix_stream *);
+internal_proto(init_error_stream);
+
+
 /* This implementation of stream I/O is based on the paper:
  *
  *  "Exploiting the advantages of mapped files for stream I/O",
@@ -118,35 +153,6 @@ Boston, MA 02110-1301, USA.  */
  * 'where' parameter and use the current file pointer. */
 
 
-#define BUFFER_SIZE 8192
-
-typedef struct
-{
-  stream st;
-
-  int fd;
-  gfc_offset buffer_offset;    /* File offset of the start of the buffer */
-  gfc_offset physical_offset;  /* Current physical file offset */
-  gfc_offset logical_offset;   /* Current logical file offset */
-  gfc_offset dirty_offset;     /* Start of modified bytes in buffer */
-  gfc_offset file_length;      /* Length of the file, -1 if not seekable. */
-
-  char *buffer;
-  int len;                     /* Physical length of the current buffer */
-  int active;                  /* Length of valid bytes in the buffer */
-
-  int prot;
-  int ndirty;                  /* Dirty bytes starting at dirty_offset */
-
-  int special_file;            /* =1 if the fd refers to a special file */
-
-  unsigned unbuffered:1, mmaped:1;
-
-  char small_buffer[BUFFER_SIZE];
-
-}
-unix_stream;
-
 /*move_pos_offset()--  Move the record pointer right or left
  *relative to current position */
 
@@ -218,59 +224,133 @@ fix_fd (int fd)
   return fd;
 }
 
+int
+is_preconnected (stream * s)
+{
+  int fd;
 
-/* write()-- Write a buffer to a descriptor, allowing for short writes */
+  fd = ((unix_stream *) s)->fd;
+  if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO)
+    return 1;
+  else
+    return 0;
+}
 
-static int
-writen (int fd, char *buffer, int len)
+/* If the stream corresponds to a preconnected unit, we flush the
+   corresponding C stream.  This is bugware for mixed C-Fortran codes
+   where the C code doesn't flush I/O before returning.  */
+void
+flush_if_preconnected (stream * s)
 {
-  int n, n0;
+  int fd;
 
-  n0 = len;
+  fd = ((unix_stream *) s)->fd;
+  if (fd == STDIN_FILENO)
+    fflush (stdin);
+  else if (fd == STDOUT_FILENO)
+    fflush (stdout);
+  else if (fd == STDERR_FILENO)
+    fflush (stderr);
+}
 
-  while (len > 0)
-    {
-      n = write (fd, buffer, len);
-      if (n < 0)
-       return n;
 
-      buffer += n;
-      len -= n;
-    }
+/* Reset a stream after reading/writing. Assumes that the buffers have
+   been flushed.  */
 
-  return n0;
+inline static void
+reset_stream (unix_stream * s, size_t bytes_rw)
+{
+  s->physical_offset += bytes_rw;
+  s->logical_offset = s->physical_offset;
+  if (s->file_length != -1 && s->physical_offset > s->file_length)
+    s->file_length = s->physical_offset;
 }
 
 
-#if 0
-/* readn()-- Read bytes into a buffer, allowing for short reads.  If
- * fewer than len bytes are returned, it is because we've hit the end
- * of file. */
+/* Read bytes into a buffer, allowing for short reads.  If the nbytes
+ * argument is less on return than on entry, it is because we've hit
+ * the end of file. */
 
 static int
-readn (int fd, char *buffer, int len)
+do_read (unix_stream * s, void * buf, size_t * nbytes)
 {
-  int nread, n;
+  ssize_t trans;
+  size_t bytes_left;
+  char *buf_st;
+  int status;
+
+  status = 0;
+  bytes_left = *nbytes;
+  buf_st = (char *) buf;
+
+  /* We must read in a loop since some systems don't restart system
+     calls in case of a signal.  */
+  while (bytes_left > 0)
+    {
+      /* Requests between SSIZE_MAX and SIZE_MAX are undefined by SUSv3,
+        so we must read in chunks smaller than SSIZE_MAX.  */
+      trans = (bytes_left < SSIZE_MAX) ? bytes_left : SSIZE_MAX;
+      trans = read (s->fd, buf_st, trans);
+      if (trans < 0)
+       {
+         if (errno == EINTR)
+           continue;
+         else
+           {
+             status = errno;
+             break;
+           }
+       }
+      else if (trans == 0) /* We hit EOF.  */
+       break;
+      buf_st += trans;
+      bytes_left -= trans;
+    }
 
-  nread = 0;
+  *nbytes -= bytes_left;
+  return status;
+}
 
-  while (len > 0)
-    {
-      n = read (fd, buffer, len);
-      if (n < 0)
-       return n;
 
-      if (n == 0)
-       return nread;
+/* Write a buffer to a stream, allowing for short writes.  */
 
-      buffer += n;
-      nread += n;
-      len -= n;
+static int
+do_write (unix_stream * s, const void * buf, size_t * nbytes)
+{
+  ssize_t trans;
+  size_t bytes_left;
+  char *buf_st;
+  int status;
+
+  status = 0;
+  bytes_left = *nbytes;
+  buf_st = (char *) buf;
+
+  /* We must write in a loop since some systems don't restart system
+     calls in case of a signal.  */
+  while (bytes_left > 0)
+    {
+      /* Requests between SSIZE_MAX and SIZE_MAX are undefined by SUSv3,
+        so we must write in chunks smaller than SSIZE_MAX.  */
+      trans = (bytes_left < SSIZE_MAX) ? bytes_left : SSIZE_MAX;
+      trans = write (s->fd, buf_st, trans);
+      if (trans < 0)
+       {
+         if (errno == EINTR)
+           continue;
+         else
+           {
+             status = errno;
+             break;
+           }
+       }
+      buf_st += trans;
+      bytes_left -= trans;
     }
 
-  return nread;
+  *nbytes -= bytes_left;
+  return status;
 }
-#endif
 
 
 /* get_oserror()-- Get the most recent operating system error.  For
@@ -296,28 +376,35 @@ sys_exit (int code)
     File descriptor stream functions
 *********************************************************************/
 
+
 /* fd_flush()-- Write bytes that need to be written */
 
 static try
 fd_flush (unix_stream * s)
 {
-  if (s->ndirty == 0)
-    return SUCCESS;;
+  size_t writelen;
 
-  if (s->physical_offset != s->dirty_offset &&
+  if (s->ndirty == 0)
+    return SUCCESS;
+  
+  if (s->file_length != -1 && s->physical_offset != s->dirty_offset &&
       lseek (s->fd, s->dirty_offset, SEEK_SET) < 0)
     return FAILURE;
 
-  if (writen (s->fd, s->buffer + (s->dirty_offset - s->buffer_offset),
-             s->ndirty) < 0)
+  writelen = s->ndirty;
+  if (do_write (s, s->buffer + (s->dirty_offset - s->buffer_offset),
+               &writelen) != 0)
     return FAILURE;
 
-  s->physical_offset = s->dirty_offset + s->ndirty;
+  s->physical_offset = s->dirty_offset + writelen;
 
   /* don't increment file_length if the file is non-seekable */
   if (s->file_length != -1 && s->physical_offset > s->file_length)
-    s->file_length = s->physical_offset;
-  s->ndirty = 0;
+      s->file_length = s->physical_offset; 
+
+  s->ndirty -= writelen;
+  if (s->ndirty != 0)
+    return FAILURE;
 
   return SUCCESS;
 }
@@ -371,7 +458,6 @@ fd_alloc (unix_stream * s, gfc_offset where,
 
   s->buffer = new_buffer;
   s->len = read_len;
-  s->mmaped = 0;
 }
 
 
@@ -383,7 +469,6 @@ static char *
 fd_alloc_r_at (unix_stream * s, int *len, gfc_offset where)
 {
   gfc_offset m;
-  int n;
 
   if (where == -1)
     where = s->logical_offset;
@@ -405,13 +490,32 @@ fd_alloc_r_at (unix_stream * s, int *len, gfc_offset where)
   if (s->physical_offset != m && lseek (s->fd, m, SEEK_SET) < 0)
     return NULL;
 
-  n = read (s->fd, s->buffer + s->active, s->len - s->active);
-  if (n < 0)
-    return NULL;
+  /* do_read() hangs on read from terminals for *BSD-systems.  Only
+     use read() in that case.  */
+
+  if (s->special_file)
+    {
+      ssize_t n;
 
-  s->physical_offset = where + n;
+      n = read (s->fd, s->buffer + s->active, s->len - s->active);
+      if (n < 0)
+       return NULL;
+
+      s->physical_offset = where + n;
+      s->active += n;
+    }
+  else
+    {
+      size_t n;
+
+      n = s->len - s->active;
+      if (do_read (s, s->buffer + s->active, &n) != 0)
+       return NULL;
+
+      s->physical_offset = where + n;
+      s->active += n;
+    }
 
-  s->active += n;
   if (s->active < *len)
     *len = s->active;          /* Bytes actually available */
 
@@ -468,8 +572,10 @@ fd_alloc_w_at (unix_stream * s, int *len, gfc_offset where)
 
   s->logical_offset = where + *len;
 
-  if (where + *len > s->file_length)
-    s->file_length = where + *len;
+  /* Don't increment file_length if the file is non-seekable.  */
+
+  if (s->file_length != -1 && s->logical_offset > s->file_length)
+     s->file_length = s->logical_offset;
 
   n = s->logical_offset - s->buffer_offset;
   if (n > s->active)
@@ -491,10 +597,21 @@ fd_sfree (unix_stream * s)
 }
 
 
-static int
+static try
 fd_seek (unix_stream * s, gfc_offset offset)
 {
+
+  if (s->file_length == -1)
+    return SUCCESS;
+
+  if (s->physical_offset == offset) /* Are we lucky and avoid syscall?  */
+    {
+      s->logical_offset = offset;
+      return SUCCESS;
+    }
+
   s->physical_offset = s->logical_offset = offset;
+  s->active = 0;
 
   return (lseek (s->fd, offset, SEEK_SET) < 0) ? FAILURE : SUCCESS;
 }
@@ -507,13 +624,19 @@ fd_seek (unix_stream * s, gfc_offset offset)
 static try
 fd_truncate (unix_stream * s)
 {
+  /* Non-seekable files, like terminals and fifo's fail the lseek so just
+     return success, there is nothing to truncate.  If its not a pipe there
+     is a real problem.  */
   if (lseek (s->fd, s->logical_offset, SEEK_SET) == -1)
-    return FAILURE;
+    {
+      if (errno == ESPIPE)
+       return SUCCESS;
+      else
+       return FAILURE;
+    }
 
-  /* non-seekable files, like terminals and fifo's fail the lseek.
-     Using ftruncate on a seekable special file (like /dev/null)
-     is undefined, so we treat it as if the ftruncate failed.
-  */
+  /* Using ftruncate on a seekable special file (like /dev/null)
+     is undefined, so we treat it as if the ftruncate succeeded.  */
 #ifdef HAVE_FTRUNCATE
   if (s->special_file || ftruncate (s->fd, s->logical_offset))
 #else
@@ -523,247 +646,182 @@ fd_truncate (unix_stream * s)
 #endif
     {
       s->physical_offset = s->file_length = 0;
-      return FAILURE;
+      return SUCCESS;
     }
 
   s->physical_offset = s->file_length = s->logical_offset;
-
-  return SUCCESS;
-}
-
-
-static try
-fd_close (unix_stream * s)
-{
-  if (fd_flush (s) == FAILURE)
-    return FAILURE;
-
-  if (s->buffer != NULL && s->buffer != s->small_buffer)
-    free_mem (s->buffer);
-
-  if (s->fd != STDOUT_FILENO && s->fd != STDERR_FILENO)
-    {
-      if (close (s->fd) < 0)
-        return FAILURE;
-    }
-
-  free_mem (s);
-
-  return SUCCESS;
-}
-
-
-static void
-fd_open (unix_stream * s)
-{
-  if (isatty (s->fd))
-    s->unbuffered = 1;
-
-  s->st.alloc_r_at = (void *) fd_alloc_r_at;
-  s->st.alloc_w_at = (void *) fd_alloc_w_at;
-  s->st.sfree = (void *) fd_sfree;
-  s->st.close = (void *) fd_close;
-  s->st.seek = (void *) fd_seek;
-  s->st.truncate = (void *) fd_truncate;
-
-  s->buffer = NULL;
-}
-
-
-/*********************************************************************
-    mmap stream functions
-
- Because mmap() is not capable of extending a file, we have to keep
- track of how long the file is.  We also have to be able to detect end
- of file conditions.  If there are multiple writers to the file (which
- can only happen outside the current program), things will get
- confused.  Then again, things will get confused anyway.
-
-*********************************************************************/
-
-#if HAVE_MMAP
-
-static int page_size, page_mask;
-
-/* mmap_flush()-- Deletes a memory mapping if something is mapped. */
-
-static try
-mmap_flush (unix_stream * s)
-{
-  if (!s->mmaped)
-    return fd_flush (s);
-
-  if (s->buffer == NULL)
-    return SUCCESS;
-
-  if (munmap (s->buffer, s->active))
-    return FAILURE;
-
-  s->buffer = NULL;
   s->active = 0;
-
   return SUCCESS;
 }
 
 
-/* mmap_alloc()-- mmap() a section of the file.  The whole section is
* guaranteed to be mappable. */
+/* Similar to memset(), but operating on a stream instead of a string.
  Takes care of not using too much memory.  */
 
 static try
-mmap_alloc (unix_stream * s, gfc_offset where,
-           int *len __attribute__ ((unused)))
+fd_sset (unix_stream * s, int c, size_t n)
 {
-  gfc_offset offset;
-  int length;
-  char *p;
-
-  if (mmap_flush (s) == FAILURE)
-    return FAILURE;
+  size_t bytes_left;
+  int trans;
+  void *p;
 
-  offset = where & page_mask;  /* Round down to the next page */
+  bytes_left = n;
 
-  length = ((where - offset) & page_mask) + 2 * page_size;
+  while (bytes_left > 0)
+    {
+      /* memset() in chunks of BUFFER_SIZE.  */
+      trans = (bytes_left < BUFFER_SIZE) ? bytes_left : BUFFER_SIZE;
 
-  p = mmap (NULL, length, s->prot, MAP_SHARED, s->fd, offset);
-  if (p == (char *) MAP_FAILED)
-    return FAILURE;
+      p = fd_alloc_w_at (s, &trans, -1);
+      if (p)
+         memset (p, c, trans);
+      else
+       return FAILURE;
 
-  s->mmaped = 1;
-  s->buffer = p;
-  s->buffer_offset = offset;
-  s->active = length;
+      bytes_left -= trans;
+    }
 
   return SUCCESS;
 }
 
 
-static char *
-mmap_alloc_r_at (unix_stream * s, int *len, gfc_offset where)
-{
-  gfc_offset m;
+/* Stream read function. Avoids using a buffer for big reads. The
+   interface is like POSIX read(), but the nbytes argument is a
+   pointer; on return it contains the number of bytes written. The
+   function return value is the status indicator (0 for success).  */
 
-  if (where == -1)
-    where = s->logical_offset;
+static int
+fd_read (unix_stream * s, void * buf, size_t * nbytes)
+{
+  void *p;
+  int tmp, status;
 
-  m = where + *len;
+  if (*nbytes < BUFFER_SIZE && !s->unbuffered)
+    {
+      tmp = *nbytes;
+      p = fd_alloc_r_at (s, &tmp, -1);
+      if (p)
+       {
+         *nbytes = tmp;
+         memcpy (buf, p, *nbytes);
+         return 0;
+       }
+      else
+       {
+         *nbytes = 0;
+         return errno;
+       }
+    }
 
-  if ((s->buffer == NULL || s->buffer_offset > where ||
-       m > s->buffer_offset + s->active) &&
-      mmap_alloc (s, where, len) == FAILURE)
-    return NULL;
+  /* If the request is bigger than BUFFER_SIZE we flush the buffers
+     and read directly.  */
+  if (fd_flush (s) == FAILURE)
+    {
+      *nbytes = 0;
+      return errno;
+    }
 
-  if (m > s->file_length)
+  if (is_seekable ((stream *) s) && fd_seek (s, s->logical_offset) == FAILURE)
     {
-      *len = s->file_length - s->logical_offset;
-      s->logical_offset = s->file_length;
+      *nbytes = 0;
+      return errno;
     }
-  else
-    s->logical_offset = m;
 
-  return s->buffer + (where - s->buffer_offset);
+  status = do_read (s, buf, nbytes);
+  reset_stream (s, *nbytes);
+  return status;
 }
 
 
-static char *
-mmap_alloc_w_at (unix_stream * s, int *len, gfc_offset where)
-{
-  if (where == -1)
-    where = s->logical_offset;
+/* Stream write function. Avoids using a buffer for big writes. The
+   interface is like POSIX write(), but the nbytes argument is a
+   pointer; on return it contains the number of bytes written. The
+   function return value is the status indicator (0 for success).  */
 
-  /* If we're extending the file, we have to use file descriptor
-   * methods. */
+static int
+fd_write (unix_stream * s, const void * buf, size_t * nbytes)
+{
+  void *p;
+  int tmp, status;
 
-  if (where + *len > s->file_length)
+  if (*nbytes < BUFFER_SIZE && !s->unbuffered)
     {
-      if (s->mmaped)
-       mmap_flush (s);
-      return fd_alloc_w_at (s, len, where);
+      tmp = *nbytes;
+      p = fd_alloc_w_at (s, &tmp, -1);
+      if (p)
+       {
+         *nbytes = tmp;
+         memcpy (p, buf, *nbytes);
+         return 0;
+       }
+      else
+       {
+         *nbytes = 0;
+         return errno;
+       }
     }
 
-  if ((s->buffer == NULL || s->buffer_offset > where ||
-       where + *len > s->buffer_offset + s->active ||
-       where < s->buffer_offset + s->active) &&
-      mmap_alloc (s, where, len) == FAILURE)
-    return NULL;
-
-  s->logical_offset = where + *len;
-
-  return s->buffer + where - s->buffer_offset;
-}
+  /* If the request is bigger than BUFFER_SIZE we flush the buffers
+     and write directly.  */
+  if (fd_flush (s) == FAILURE)
+    {
+      *nbytes = 0;
+      return errno;
+    }
 
+  if (is_seekable ((stream *) s) && fd_seek (s, s->logical_offset) == FAILURE)
+    {
+      *nbytes = 0;
+      return errno;
+    }
 
-static int
-mmap_seek (unix_stream * s, gfc_offset offset)
-{
-  s->logical_offset = offset;
-  return SUCCESS;
+  status =  do_write (s, buf, nbytes);
+  reset_stream (s, *nbytes);
+  return status;
 }
 
 
 static try
-mmap_close (unix_stream * s)
+fd_close (unix_stream * s)
 {
-  try t;
-
-  t = mmap_flush (s);
+  if (fd_flush (s) == FAILURE)
+    return FAILURE;
 
-  if (close (s->fd) < 0)
-    t = FAILURE;
-  free_mem (s);
+  if (s->buffer != NULL && s->buffer != s->small_buffer)
+    free_mem (s->buffer);
 
-  return t;
-}
+  if (s->fd != STDOUT_FILENO && s->fd != STDERR_FILENO)
+    {
+      if (close (s->fd) < 0)
+        return FAILURE;
+    }
 
+  free_mem (s);
 
-static try
-mmap_sfree (unix_stream * s __attribute__ ((unused)))
-{
   return SUCCESS;
 }
 
 
-/* mmap_open()-- mmap_specific open.  If the particular file cannot be
- * mmap()-ed, we fall back to the file descriptor functions. */
-
-static try
-mmap_open (unix_stream * s __attribute__ ((unused)))
+static void
+fd_open (unix_stream * s)
 {
-  char *p;
-  int i;
-
-  page_size = getpagesize ();
-  page_mask = ~0;
-
-  p = mmap (0, page_size, s->prot, MAP_SHARED, s->fd, 0);
-  if (p == (char *) MAP_FAILED)
-    {
-      fd_open (s);
-      return SUCCESS;
-    }
-
-  munmap (p, page_size);
-
-  i = page_size >> 1;
-  while (i != 0)
-    {
-      page_mask <<= 1;
-      i >>= 1;
-    }
+  if (isatty (s->fd))
+    s->unbuffered = 1;
 
-  s->st.alloc_r_at = (void *) mmap_alloc_r_at;
-  s->st.alloc_w_at = (void *) mmap_alloc_w_at;
-  s->st.sfree = (void *) mmap_sfree;
-  s->st.close = (void *) mmap_close;
-  s->st.seek = (void *) mmap_seek;
+  s->st.alloc_r_at = (void *) fd_alloc_r_at;
+  s->st.alloc_w_at = (void *) fd_alloc_w_at;
+  s->st.sfree = (void *) fd_sfree;
+  s->st.close = (void *) fd_close;
+  s->st.seek = (void *) fd_seek;
   s->st.truncate = (void *) fd_truncate;
+  s->st.read = (void *) fd_read;
+  s->st.write = (void *) fd_write;
+  s->st.set = (void *) fd_sset;
 
-  if (lseek (s->fd, s->file_length, SEEK_SET) < 0)
-    return FAILURE;
-
-  return SUCCESS;
+  s->buffer = NULL;
 }
 
-#endif
+
 
 
 /*********************************************************************
@@ -803,12 +861,17 @@ mem_alloc_w_at (unix_stream * s, int *len, gfc_offset where)
 {
   gfc_offset m;
 
+  assert (*len >= 0);  /* Negative values not allowed. */
+  
   if (where == -1)
     where = s->logical_offset;
 
   m = where + *len;
 
-  if (where < s->buffer_offset || m > s->buffer_offset + s->active)
+  if (where < s->buffer_offset)
+    return NULL;
+
+  if (m > s->file_length)
     return NULL;
 
   s->logical_offset = m;
@@ -817,6 +880,60 @@ mem_alloc_w_at (unix_stream * s, int *len, gfc_offset where)
 }
 
 
+/* Stream read function for internal units. This is not actually used
+   at the moment, as all internal IO is formatted and the formatted IO
+   routines use mem_alloc_r_at.  */
+
+static int
+mem_read (unix_stream * s, void * buf, size_t * nbytes)
+{
+  void *p;
+  int tmp;
+
+  tmp = *nbytes;
+  p = mem_alloc_r_at (s, &tmp, -1);
+  if (p)
+    {
+      *nbytes = tmp;
+      memcpy (buf, p, *nbytes);
+      return 0;
+    }
+  else
+    {
+      *nbytes = 0;
+      return errno;
+    }
+}
+
+
+/* Stream write function for internal units. This is not actually used
+   at the moment, as all internal IO is formatted and the formatted IO
+   routines use mem_alloc_w_at.  */
+
+static int
+mem_write (unix_stream * s, const void * buf, size_t * nbytes)
+{
+  void *p;
+  int tmp;
+
+  errno = 0;
+
+  tmp = *nbytes;
+  p = mem_alloc_w_at (s, &tmp, -1);
+  if (p)
+    {
+      *nbytes = tmp;
+      memcpy (p, buf, *nbytes);
+      return 0;
+    }
+  else
+    {
+      *nbytes = 0;
+      return errno;
+    }
+}
+
+
 static int
 mem_seek (unix_stream * s, gfc_offset offset)
 {
@@ -831,6 +948,25 @@ mem_seek (unix_stream * s, gfc_offset offset)
 }
 
 
+static try
+mem_set (unix_stream * s, int c, size_t n)
+{
+  void *p;
+  int len;
+
+  len = n;
+  
+  p = mem_alloc_w_at (s, &len, -1);
+  if (p)
+    {
+      memset (p, c, len);
+      return SUCCESS;
+    }
+  else
+    return FAILURE;
+}
+
+
 static int
 mem_truncate (unix_stream * s __attribute__ ((unused)))
 {
@@ -841,7 +977,8 @@ mem_truncate (unix_stream * s __attribute__ ((unused)))
 static try
 mem_close (unix_stream * s)
 {
-  free_mem (s);
+  if (s != NULL)
+    free_mem (s);
 
   return SUCCESS;
 }
@@ -891,6 +1028,9 @@ open_internal (char *base, int length)
   s->st.close = (void *) mem_close;
   s->st.seek = (void *) mem_seek;
   s->st.truncate = (void *) mem_truncate;
+  s->st.read = (void *) mem_read;
+  s->st.write = (void *) mem_write;
+  s->st.set = (void *) mem_set;
 
   return (stream *) s;
 }
@@ -900,7 +1040,7 @@ open_internal (char *base, int length)
  * around it. */
 
 static stream *
-fd_to_stream (int fd, int prot, int avoid_mmap)
+fd_to_stream (int fd, int prot)
 {
   struct stat statbuf;
   unix_stream *s;
@@ -917,17 +1057,15 @@ fd_to_stream (int fd, int prot, int avoid_mmap)
   /* Get the current length of the file. */
 
   fstat (fd, &statbuf);
-  s->file_length = S_ISREG (statbuf.st_mode) ? statbuf.st_size : -1;
-  s->special_file = !S_ISREG (statbuf.st_mode);
 
-#if HAVE_MMAP
-  if (avoid_mmap)
-    fd_open (s);
+  if (lseek (fd, 0, SEEK_CUR) == (off_t) -1)
+    s->file_length = -1;
   else
-    mmap_open (s);
-#else
+    s->file_length = S_ISREG (statbuf.st_mode) ? statbuf.st_size : -1;
+
+  s->special_file = !S_ISREG (statbuf.st_mode);
+
   fd_open (s);
-#endif
 
   return (stream *) s;
 }
@@ -936,15 +1074,18 @@ fd_to_stream (int fd, int prot, int avoid_mmap)
 /* Given the Fortran unit number, convert it to a C file descriptor.  */
 
 int
-unit_to_fd(int unit)
+unit_to_fd (int unit)
 {
   gfc_unit *us;
+  int fd;
 
-  us = find_unit(unit);
+  us = find_unit (unit);
   if (us == NULL)
     return -1;
 
-  return ((unix_stream *) us->s)->fd;
+  fd = ((unix_stream *) us->s)->fd;
+  unlock_unit (us);
+  return fd;
 }
 
 
@@ -952,7 +1093,7 @@ unit_to_fd(int unit)
  * buffer that is PATH_MAX characters, convert the fortran string to a
  * C string in the buffer.  Returns nonzero if this is not possible.  */
 
-static int
+int
 unpack_filename (char *cstring, const char *fstring, int len)
 {
   len = fstrlen (fstring, len);
@@ -970,11 +1111,11 @@ unpack_filename (char *cstring, const char *fstring, int len)
  * open it.  mkstemp() opens the file for reading and writing, but the
  * library mode prevents anything that is not allowed.  The descriptor
  * is returned, which is -1 on error.  The template is pointed to by 
- * ioparm.file, which is copied into the unit structure
+ * opp->file, which is copied into the unit structure
  * and freed later. */
 
 static int
-tempfile (void)
+tempfile (st_parameter_open *opp)
 {
   const char *tempdir;
   char *template;
@@ -1000,7 +1141,12 @@ tempfile (void)
 
   if (mktemp (template))
     do
+#if defined(HAVE_CRLF) && defined(O_BINARY)
+      fd = open (template, O_RDWR | O_CREAT | O_EXCL | O_BINARY,
+                 S_IREAD | S_IWRITE);
+#else
       fd = open (template, O_RDWR | O_CREAT | O_EXCL, S_IREAD | S_IWRITE);
+#endif
     while (!(fd == -1 && errno == EEXIST) && mktemp (template));
   else
     fd = -1;
@@ -1011,8 +1157,8 @@ tempfile (void)
     free_mem (template);
   else
     {
-      ioparm.file = template;
-      ioparm.file_len = strlen (template);     /* Don't include trailing nul */
+      opp->file = template;
+      opp->file_len = strlen (template);       /* Don't include trailing nul */
     }
 
   return fd;
@@ -1025,7 +1171,7 @@ tempfile (void)
  * Returns the descriptor, which is less than zero on error. */
 
 static int
-regular_file (unit_flags *flags)
+regular_file (st_parameter_open *opp, unit_flags *flags)
 {
   char path[PATH_MAX + 1];
   int mode;
@@ -1033,7 +1179,7 @@ regular_file (unit_flags *flags)
   int crflag;
   int fd;
 
-  if (unpack_filename (path, ioparm.file, ioparm.file_len))
+  if (unpack_filename (path, opp->file, opp->file_len))
     {
       errno = ENOENT;          /* Fake an OS error */
       return -1;
@@ -1057,7 +1203,7 @@ regular_file (unit_flags *flags)
       break;
 
     default:
-      internal_error ("regular_file(): Bad action");
+      internal_error (&opp->common, "regular_file(): Bad action");
     }
 
   switch (flags->status)
@@ -1080,11 +1226,15 @@ regular_file (unit_flags *flags)
       break;
 
     default:
-      internal_error ("regular_file(): Bad status");
+      internal_error (&opp->common, "regular_file(): Bad status");
     }
 
   /* rwflag |= O_LARGEFILE; */
 
+#if defined(HAVE_CRLF) && defined(O_BINARY)
+  crflag |= O_BINARY;
+#endif
+
   mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
   fd = open (path, rwflag | crflag, mode);
   if (flags->action != ACTION_UNSPECIFIED)
@@ -1127,23 +1277,27 @@ regular_file (unit_flags *flags)
  * Returns NULL on operating system error. */
 
 stream *
-open_external (unit_flags *flags)
+open_external (st_parameter_open *opp, unit_flags *flags)
 {
   int fd, prot;
 
   if (flags->status == STATUS_SCRATCH)
     {
-      fd = tempfile ();
+      fd = tempfile (opp);
       if (flags->action == ACTION_UNSPECIFIED)
         flags->action = ACTION_READWRITE;
+
+#if HAVE_UNLINK_OPEN_FILE
       /* We can unlink scratch files now and it will go away when closed. */
-      unlink (ioparm.file);
+      if (fd >= 0)
+       unlink (opp->file);
+#endif
     }
   else
     {
       /* regular_file resets flags->action if it is ACTION_UNSPECIFIED and
        * if it succeeds */
-      fd = regular_file (flags);
+      fd = regular_file (opp, flags);
     }
 
   if (fd < 0)
@@ -1165,10 +1319,10 @@ open_external (unit_flags *flags)
       break;
 
     default:
-      internal_error ("open_external(): Bad action");
+      internal_error (&opp->common, "open_external(): Bad action");
     }
 
-  return fd_to_stream (fd, prot, 0);
+  return fd_to_stream (fd, prot);
 }
 
 
@@ -1178,7 +1332,7 @@ open_external (unit_flags *flags)
 stream *
 input_stream (void)
 {
-  return fd_to_stream (STDIN_FILENO, PROT_READ, 1);
+  return fd_to_stream (STDIN_FILENO, PROT_READ);
 }
 
 
@@ -1188,7 +1342,10 @@ input_stream (void)
 stream *
 output_stream (void)
 {
-  return fd_to_stream (STDOUT_FILENO, PROT_WRITE, 1);
+#if defined(HAVE_CRLF) && defined(HAVE_SETMODE)
+  setmode (STDOUT_FILENO, O_BINARY);
+#endif
+  return fd_to_stream (STDOUT_FILENO, PROT_WRITE);
 }
 
 
@@ -1198,7 +1355,10 @@ output_stream (void)
 stream *
 error_stream (void)
 {
-  return fd_to_stream (STDERR_FILENO, PROT_WRITE, 1);
+#if defined(HAVE_CRLF) && defined(HAVE_SETMODE)
+  setmode (STDERR_FILENO, O_BINARY);
+#endif
+  return fd_to_stream (STDERR_FILENO, PROT_WRITE);
 }
 
 /* init_error_stream()-- Return a pointer to the error stream.  This
@@ -1207,21 +1367,116 @@ error_stream (void)
  * corrupted. */
 
 stream *
-init_error_stream (void)
+init_error_stream (unix_stream *error)
 {
-  static unix_stream error;
+  memset (error, '\0', sizeof (*error));
+
+  error->fd = options.use_stderr ? STDERR_FILENO : STDOUT_FILENO;
 
-  memset (&error, '\0', sizeof (error));
+  error->st.alloc_w_at = (void *) fd_alloc_w_at;
+  error->st.sfree = (void *) fd_sfree;
 
-  error.fd = options.use_stderr ? STDERR_FILENO : STDOUT_FILENO;
+  error->unbuffered = 1;
+  error->buffer = error->small_buffer;
 
-  error.st.alloc_w_at = (void *) fd_alloc_w_at;
-  error.st.sfree = (void *) fd_sfree;
+  return (stream *) error;
+}
 
-  error.unbuffered = 1;
-  error.buffer = error.small_buffer;
+/* st_printf()-- simple printf() function for streams that handles the
+ * formats %d, %s and %c.  This function handles printing of error
+ * messages that originate within the library itself, not from a user
+ * program. */
 
-  return (stream *) & error;
+int
+st_printf (const char *format, ...)
+{
+  int count, total;
+  va_list arg;
+  char *p;
+  const char *q;
+  stream *s;
+  char itoa_buf[GFC_ITOA_BUF_SIZE];
+  unix_stream err_stream;
+
+  total = 0;
+  s = init_error_stream (&err_stream);
+  va_start (arg, format);
+
+  for (;;)
+    {
+      count = 0;
+
+      while (format[count] != '%' && format[count] != '\0')
+       count++;
+
+      if (count != 0)
+       {
+         p = salloc_w (s, &count);
+         memmove (p, format, count);
+         sfree (s);
+       }
+
+      total += count;
+      format += count;
+      if (*format++ == '\0')
+       break;
+
+      switch (*format)
+       {
+       case 'c':
+         count = 1;
+
+         p = salloc_w (s, &count);
+         *p = (char) va_arg (arg, int);
+
+         sfree (s);
+         break;
+
+       case 'd':
+         q = gfc_itoa (va_arg (arg, int), itoa_buf, sizeof (itoa_buf));
+         count = strlen (q);
+
+         p = salloc_w (s, &count);
+         memmove (p, q, count);
+         sfree (s);
+         break;
+
+       case 'x':
+         q = xtoa (va_arg (arg, unsigned), itoa_buf, sizeof (itoa_buf));
+         count = strlen (q);
+
+         p = salloc_w (s, &count);
+         memmove (p, q, count);
+         sfree (s);
+         break;
+
+       case 's':
+         q = va_arg (arg, char *);
+         count = strlen (q);
+
+         p = salloc_w (s, &count);
+         memmove (p, q, count);
+         sfree (s);
+         break;
+
+       case '\0':
+         return total;
+
+       default:
+         count = 2;
+         p = salloc_w (s, &count);
+         p[0] = format[-1];
+         p[1] = format[0];
+         sfree (s);
+         break;
+       }
+
+      total += count;
+      format++;
+    }
+
+  va_end (arg);
+  return total;
 }
 
 
@@ -1230,10 +1485,13 @@ init_error_stream (void)
  * filename. */
 
 int
-compare_file_filename (stream * s, const char *name, int len)
+compare_file_filename (gfc_unit *u, const char *name, int len)
 {
   char path[PATH_MAX + 1];
-  struct stat st1, st2;
+  struct stat st1;
+#ifdef HAVE_WORKING_STAT
+  struct stat st2;
+#endif
 
   if (unpack_filename (path, name, len))
     return 0;                  /* Can't be the same */
@@ -1244,32 +1502,50 @@ compare_file_filename (stream * s, const char *name, int len)
   if (stat (path, &st1) < 0)
     return 0;
 
-  fstat (((unix_stream *) s)->fd, &st2);
-
+#ifdef HAVE_WORKING_STAT
+  fstat (((unix_stream *) (u->s))->fd, &st2);
   return (st1.st_dev == st2.st_dev) && (st1.st_ino == st2.st_ino);
+#else
+  if (len != u->file_len)
+    return 0;
+  return (memcmp(path, u->file, len) == 0);
+#endif
 }
 
 
+#ifdef HAVE_WORKING_STAT
+# define FIND_FILE0_DECL struct stat *st
+# define FIND_FILE0_ARGS st
+#else
+# define FIND_FILE0_DECL const char *file, gfc_charlen_type file_len
+# define FIND_FILE0_ARGS file, file_len
+#endif
+
 /* find_file0()-- Recursive work function for find_file() */
 
 static gfc_unit *
-find_file0 (gfc_unit * u, struct stat *st1)
+find_file0 (gfc_unit *u, FIND_FILE0_DECL)
 {
-  struct stat st2;
   gfc_unit *v;
 
   if (u == NULL)
     return NULL;
 
-  if (fstat (((unix_stream *) u->s)->fd, &st2) >= 0 &&
-      st1->st_dev == st2.st_dev && st1->st_ino == st2.st_ino)
+#ifdef HAVE_WORKING_STAT
+  if (u->s != NULL
+      && fstat (((unix_stream *) u->s)->fd, &st[1]) >= 0 &&
+      st[0].st_dev == st[1].st_dev && st[0].st_ino == st[1].st_ino)
     return u;
+#else
+  if (compare_string (u->file_len, u->file, file_len, file) == 0)
+    return u;
+#endif
 
-  v = find_file0 (u->left, st1);
+  v = find_file0 (u->left, FIND_FILE0_ARGS);
   if (v != NULL)
     return v;
 
-  v = find_file0 (u->right, st1);
+  v = find_file0 (u->right, FIND_FILE0_ARGS);
   if (v != NULL)
     return v;
 
@@ -1281,18 +1557,111 @@ find_file0 (gfc_unit * u, struct stat *st1)
  * that has the file already open.  Returns a pointer to the unit if so. */
 
 gfc_unit *
-find_file (void)
+find_file (const char *file, gfc_charlen_type file_len)
 {
   char path[PATH_MAX + 1];
-  struct stat statbuf;
+  struct stat st[2];
+  gfc_unit *u;
 
-  if (unpack_filename (path, ioparm.file, ioparm.file_len))
+  if (unpack_filename (path, file, file_len))
     return NULL;
 
-  if (stat (path, &statbuf) < 0)
+  if (stat (path, &st[0]) < 0)
     return NULL;
 
-  return find_file0 (g.unit_root, &statbuf);
+  __gthread_mutex_lock (&unit_lock);
+retry:
+  u = find_file0 (unit_root, FIND_FILE0_ARGS);
+  if (u != NULL)
+    {
+      /* Fast path.  */
+      if (! __gthread_mutex_trylock (&u->lock))
+       {
+         /* assert (u->closed == 0); */
+         __gthread_mutex_unlock (&unit_lock);
+         return u;
+       }
+
+      inc_waiting_locked (u);
+    }
+  __gthread_mutex_unlock (&unit_lock);
+  if (u != NULL)
+    {
+      __gthread_mutex_lock (&u->lock);
+      if (u->closed)
+       {
+         __gthread_mutex_lock (&unit_lock);
+         __gthread_mutex_unlock (&u->lock);
+         if (predec_waiting_locked (u) == 0)
+           free_mem (u);
+         goto retry;
+       }
+
+      dec_waiting_unlocked (u);
+    }
+  return u;
+}
+
+static gfc_unit *
+flush_all_units_1 (gfc_unit *u, int min_unit)
+{
+  while (u != NULL)
+    {
+      if (u->unit_number > min_unit)
+       {
+         gfc_unit *r = flush_all_units_1 (u->left, min_unit);
+         if (r != NULL)
+           return r;
+       }
+      if (u->unit_number >= min_unit)
+       {
+         if (__gthread_mutex_trylock (&u->lock))
+           return u;
+         if (u->s)
+           flush (u->s);
+         __gthread_mutex_unlock (&u->lock);
+       }
+      u = u->right;
+    }
+  return NULL;
+}
+
+void
+flush_all_units (void)
+{
+  gfc_unit *u;
+  int min_unit = 0;
+
+  __gthread_mutex_lock (&unit_lock);
+  do
+    {
+      u = flush_all_units_1 (unit_root, min_unit);
+      if (u != NULL)
+       inc_waiting_locked (u);
+      __gthread_mutex_unlock (&unit_lock);
+      if (u == NULL)
+       return;
+
+      __gthread_mutex_lock (&u->lock);
+
+      min_unit = u->unit_number + 1;
+
+      if (u->closed == 0)
+       {
+         flush (u->s);
+         __gthread_mutex_lock (&unit_lock);
+         __gthread_mutex_unlock (&u->lock);
+         (void) predec_waiting_locked (u);
+       }
+      else
+       {
+         __gthread_mutex_lock (&unit_lock);
+         __gthread_mutex_unlock (&u->lock);
+         if (predec_waiting_locked (u) == 0)
+           free_mem (u);
+       }
+    }
+  while (1);
 }
 
 
@@ -1313,7 +1682,7 @@ stream_at_bof (stream * s)
 }
 
 
-/* stream_at_eof()-- Returns nonzero if the stream is at the beginning
+/* stream_at_eof()-- Returns nonzero if the stream is at the end
  * of the file. */
 
 int
@@ -1352,12 +1721,12 @@ delete_file (gfc_unit * u)
  * the system */
 
 int
-file_exists (void)
+file_exists (const char *file, gfc_charlen_type file_len)
 {
   char path[PATH_MAX + 1];
   struct stat statbuf;
 
-  if (unpack_filename (path, ioparm.file, ioparm.file_len))
+  if (unpack_filename (path, file, file_len))
     return 0;
 
   if (stat (path, &statbuf) < 0)
@@ -1368,7 +1737,7 @@ file_exists (void)
 
 
 
-static const char *yes = "YES", *no = "NO", *unknown = "UNKNOWN";
+static const char yes[] = "YES", no[] = "NO", unknown[] = "UNKNOWN";
 
 /* inquire_sequential()-- Given a fortran string, determine if the
  * file is suitable for sequential access.  Returns a C-style
@@ -1524,9 +1893,8 @@ file_position (stream * s)
 int
 is_seekable (stream * s)
 {
-  /* by convention, if file_length == -1, the file is not seekable
-     note that a mmapped file is always seekable, an fd_ file may
-     or may not be. */
+  /* By convention, if file_length == -1, the file is not
+     seekable.  */
   return ((unix_stream *) s)->file_length!=-1;
 }
 
@@ -1545,7 +1913,17 @@ stream_isatty (stream *s)
 char *
 stream_ttyname (stream *s)
 {
+#ifdef HAVE_TTYNAME
   return ttyname (((unix_stream *) s)->fd);
+#else
+  return NULL;
+#endif
+}
+
+gfc_offset
+stream_offset (stream *s)
+{
+  return (((unix_stream *) s)->logical_offset);
 }