*** empty log message ***
[bpt/guile.git] / libguile / fports.c
index 1cc25de..5cbbf36 100644 (file)
@@ -1,4 +1,4 @@
-/*     Copyright (C) 1995,1996 Free Software Foundation, Inc.
+/*     Copyright (C) 1995,1996,1997,1998 Free Software Foundation, Inc.
  * 
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -12,7 +12,8 @@
  * 
  * You should have received a copy of the GNU General Public License
  * along with this software; see the file COPYING.  If not, write to
- * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307 USA
  *
  * As a special exception, the Free Software Foundation gives permission
  * for additional uses of the text contained in its release of GUILE.
@@ -36,8 +37,7 @@
  *
  * If you write modifications of your own for GUILE, it is your choice
  * whether to permit this exception to apply to your modifications.
- * If you do not wish that, delete this exception notice.  
- */
+ * If you do not wish that, delete this exception notice.  */
 \f
 
 #include <stdio.h>
 scm_sizet fwrite ();
 #endif
 
-
-#ifdef __IBMC__
-#include <io.h>
-#include <direct.h>
-#else
-#ifndef MSDOS
-#ifndef ultrix
-#ifndef vms
-#ifdef _DCC
-#include <ioctl.h>
-#define setbuf(stream, buf) setvbuf(stream, buf, _IONBF, 0)
-#else
-#ifdef MWC
-#include <sys/io.h>
-#else
-#ifndef THINK_C
-#ifndef ARM_ULIB
-#include <sys/ioctl.h>
-#endif
-#endif
-#endif
-#endif
-#endif
-#endif
-#endif
-#endif
 \f
+/* Port direction --- handling el cheapo stdio implementations.
+
+   Guile says that when you've got a port that's both readable and
+   writable, like a socket, why then, by gum, you can read from it and
+   write to it!  However, most standard I/O implementations make
+   cheezy caveats like this:
+
+       When a file is opened for update, both input and output  may
+       be done on the resulting stream.  However, output may not be
+       directly followed by input without an intervening  fflush(),
+       fseek(),  fsetpos(),  or  rewind(),  and  input  may  not be
+       directly followed by output without an intervening  fseek(),
+       fsetpos(),   or   rewind(),   or  an  input  operation  that
+       encounters end-of-file.
+                  -- the Solaris fdopen(3S) man page
+
+   I think this behavior is permitted by the ANSI C standard.
+
+   So we made the implementation more complex, so what the user sees
+   remains simple.  When we have a Guile port based on a stdio stream
+   (this source file's specialty), we keep track of whether it was
+   last written to, read from, or whether it is in a safe state for
+   both operations.  Each port operation function just checks the
+   state of the port before each operation, and does the required
+   magic if necessary.
+
+   We use two bits in the CAR of the port, FPORT_READ_SAFE and
+   FPORT_WRITE_SAFE, to indicate what operations the underlying stdio
+   stream could correctly perform next.  You're not allowed to clear
+   them both at the same time, but both can be set --- for example, if
+   the stream has just been opened, or flushed, or had its position
+   changed.
+
+   It's possible for a port to have neither bit set, if we receive a
+   FILE * pointer in an unknown state; this code should handle that
+   gracefully.  */
+
+#define FPORT_READ_SAFE  (1L << 24)
+#define FPORT_WRITE_SAFE (2L << 24)
+
+#define FPORT_ALL_OKAY(port) \
+     (SCM_SETOR_CAR (port, (FPORT_READ_SAFE | FPORT_WRITE_SAFE)))
+
+static inline void
+pre_read (SCM port)
+{
+  if (! (SCM_CAR (port) & FPORT_READ_SAFE))
+    fflush ((FILE *)SCM_STREAM (port));
+
+  /* We've done the flush, so reading is safe.
+     Assuming that we're going to do a read next, writing will not be
+     safe by the time we're done.  */
+  SCM_SETOR_CAR  (port,  FPORT_READ_SAFE);
+  SCM_SETAND_CAR (port, ~FPORT_WRITE_SAFE);
+  
+}
 
-/* {Ports - file ports}
- * 
- */
+static inline void
+pre_write (SCM port)
+{
+  if (! (SCM_CAR (port) & FPORT_WRITE_SAFE))
+    /* This can fail, if we're talking to a line-buffered terminal.  As
+       far as I can tell, there's no way to get mixed reads and writes
+       to work on a line-buffered terminal at all --- you get a full
+       line in the buffer when you read, and then you have to throw it
+       out to write.  You have to do unbuffered input, and make the
+       system provide the second buffer.  */
+    fseek ((FILE *)SCM_STREAM (port), 0, SEEK_CUR);
+
+  /* We've done the seek, so writing is safe.
+     Assuming that we're going to do a write next, reading will not be
+     safe by the time we're done.  */
+  SCM_SETOR_CAR (port, FPORT_WRITE_SAFE);
+  SCM_SETAND_CAR (port, ~FPORT_READ_SAFE);
+}
+
+\f
+/* Helpful operations on stdio FILE-based ports  */
 
 /* should be called with SCM_DEFER_INTS active */
-#ifdef __STDC__
-SCM 
-scm_setbuf0 (SCM port)
-#else
+
 SCM 
 scm_setbuf0 (port)
      SCM port;
-#endif
 {
+  /* NOSETBUF was provided by scm to allow unbuffered ports to be
+     avoided on systems where ungetc didn't work correctly.  See
+     comment in unif.c, which seems to be the only place where it
+     could still be a problem.  */
 #ifndef NOSETBUF
-#ifndef MSDOS
-#ifdef FIONREAD
-#ifndef ultrix
-  SCM_SYSCALL (setbuf ((FILE *)SCM_STREAM (port), 0););
-#endif
-#endif
-#endif
+  /*  SCM_SYSCALL (setbuf ((FILE *)SCM_STREAM (port), 0);); */
+  SCM_SYSCALL (setvbuf ((FILE *)SCM_STREAM (port), 0, _IONBF, 0););
 #endif
   return SCM_UNSPECIFIED;
 }
 
-/* Return the flags that characterize a port based on the mode
- * string used to open a file for that port.
- *
- * See PORT FLAGS in scm.h
- */
-#ifdef __STDC__
-long
-scm_mode_bits (char *modes)
-#else
-long
-scm_mode_bits (modes)
-     char *modes;
+SCM_PROC (s_setvbuf, "setvbuf", 2, 1, 0, scm_setvbuf);
+SCM
+scm_setvbuf (SCM port, SCM mode, SCM size)
+{
+  int rv;
+  int cmode, csize;
+
+  port = SCM_COERCE_OUTPORT (port);
+
+  SCM_ASSERT (SCM_NIMP (port) && SCM_FPORTP (port), port, SCM_ARG1, s_setvbuf);
+  SCM_ASSERT (SCM_INUMP (mode), mode, SCM_ARG2, s_setvbuf);
+  if (SCM_UNBNDP (size))
+    csize = 0;
+  else
+    {
+      SCM_ASSERT (SCM_INUMP (size), size, SCM_ARG3, s_setvbuf);
+      csize = SCM_INUM (size);
+    }
+  cmode = SCM_INUM (mode);
+  if (csize == 0 && cmode == _IOFBF)
+    cmode = _IONBF;
+  SCM_DEFER_INTS;
+  SCM_SYSCALL (rv = setvbuf ((FILE *)SCM_STREAM (port), 0, cmode, csize));
+  if (rv < 0)
+    scm_syserror (s_setvbuf);
+  if (cmode == _IONBF)
+    SCM_SETCAR (port, SCM_CAR (port) | SCM_BUF0);
+  else
+    SCM_SETCAR (port, (SCM_CAR (port) & ~SCM_BUF0));
+  SCM_ALLOW_INTS;
+  return SCM_UNSPECIFIED;
+}
+
+#ifdef FD_SETTER
+#define SET_FILE_FD_FIELD(F,D) ((F)->FD_SETTER = (D))
 #endif
+
+void
+scm_setfileno (fs, fd)
+     FILE *fs;
+     int fd;
 {
-  return (SCM_OPN
-         | (strchr (modes, 'r') || strchr (modes, '+') ? SCM_RDNG : 0)
-         | (   strchr (modes, 'w')
-            || strchr (modes, 'a')
-            || strchr (modes, '+') ? SCM_WRTNG : 0)
-         | (strchr (modes, '0') ? SCM_BUF0 : 0));
+#ifdef SET_FILE_FD_FIELD
+  SET_FILE_FD_FIELD(fs, fd);
+#else
+  scm_misc_error ("scm_setfileno", "Not fully implemented on this platform",
+                 SCM_EOL);
+#endif
 }
 
+/* Move ports with the specified file descriptor to new descriptors,
+ * reseting the revealed count to 0.
+ * Should be called with SCM_DEFER_INTS active.
+ */
+
+void
+scm_evict_ports (fd)
+     int fd;
+{
+  int i;
+
+  for (i = 0; i < scm_port_table_size; i++)
+    {
+      if (SCM_FPORTP (scm_port_table[i]->port)
+         && fileno ((FILE *)SCM_STREAM (scm_port_table[i]->port)) == fd)
+       {
+         scm_setfileno ((FILE *)SCM_STREAM (scm_port_table[i]->port), dup (fd));
+         scm_set_port_revealed_x (scm_port_table[i]->port, SCM_MAKINUM (0));
+       }
+    }
+}
 
 /* scm_open_file
  * Return a new port open on a given file.
@@ -141,15 +233,11 @@ scm_mode_bits (modes)
  * Return the new port.
  */
 SCM_PROC(s_open_file, "open-file", 2, 0, 0, scm_open_file);
-#ifdef __STDC__
-SCM
-scm_open_file (SCM filename, SCM modes)
-#else
+
 SCM
 scm_open_file (filename, modes)
      SCM filename;
      SCM modes;
-#endif
 {
   SCM port;
   FILE *f;
@@ -166,73 +254,123 @@ scm_open_file (filename, modes)
   file = SCM_ROCHARS (filename);
   mode = SCM_ROCHARS (modes);
 
-  SCM_NEWCELL (port);
   SCM_DEFER_INTS;
   SCM_SYSCALL (f = fopen (file, mode));
   if (!f)
     {
-      scm_syserror_msg (s_open_file, "%S: %S",
+      int en = errno;
+
+      scm_syserror_msg (s_open_file, "%s: %S",
                        scm_listify (scm_makfrom0str (strerror (errno)),
                                     filename,
-                                    SCM_UNDEFINED));
+                                    SCM_UNDEFINED),
+                       en);
     }
   else
-    {
-      struct scm_port_table * pt;
+    port = scm_stdio_to_port (f, mode, filename);
+  SCM_ALLOW_INTS;
+  return port;
+}
 
-      pt = scm_add_to_port_table (port);
-      SCM_SETPTAB_ENTRY (port, pt);
-      if (SCM_BUF0 & (SCM_CAR (port) = scm_tc16_fport | scm_mode_bits (mode)))
-       scm_setbuf0 (port);
+
+SCM_PROC (s_freopen, "freopen", 3, 0, 0, scm_freopen);
+
+SCM 
+scm_freopen (filename, modes, port)
+     SCM filename;
+     SCM modes;
+     SCM port;
+{
+  FILE *f;
+  SCM_ASSERT (SCM_NIMP (filename) && SCM_ROSTRINGP (filename), filename,
+             SCM_ARG1, s_freopen);
+  SCM_ASSERT (SCM_NIMP (modes) && SCM_ROSTRINGP (modes), modes, SCM_ARG2,
+             s_freopen);
+
+  SCM_COERCE_SUBSTR (filename);
+  SCM_COERCE_SUBSTR (modes);
+  port = SCM_COERCE_OUTPORT (port);
+  SCM_DEFER_INTS;
+  SCM_ASSERT (SCM_NIMP (port) && SCM_FPORTP (port), port, SCM_ARG3, s_freopen);
+  SCM_SYSCALL (f = freopen (SCM_ROCHARS (filename), SCM_ROCHARS (modes),
+                           (FILE *)SCM_STREAM (port)));
+  if (!f)
+    {
+      SCM p;
+      p = port;
+      port = SCM_MAKINUM (errno);
+      SCM_SETAND_CAR (p, ~SCM_OPN);
+      scm_remove_from_port_table (p);
+    }
+  else
+    {
       SCM_SETSTREAM (port, (SCM)f);
-      SCM_PTAB_ENTRY (port)->file_name = filename;
+      SCM_SETCAR (port, (scm_tc16_fport
+                        | scm_mode_bits (SCM_ROCHARS (modes))
+                        | FPORT_READ_SAFE | FPORT_WRITE_SAFE));
+      if (SCM_BUF0 & SCM_CAR (port))
+       scm_setbuf0 (port);
     }
   SCM_ALLOW_INTS;
   return port;
 }
 
-/* Return the mode flags from an open port.
- * Some modes such as "append" are only used when opening
- * a file and are not returned here.
- */
 
-SCM_PROC(s_port_mode, "port-mode", 1, 0, 0, scm_port_mode);
-#ifdef __STDC__
-SCM
-scm_port_mode (SCM port)
-#else
+\f
+/* Building Guile ports from stdio FILE pointers.  */
+
+/* Build a Scheme port from an open stdio port, FILE.
+   MODE indicates whether FILE is open for reading or writing; it uses
+      the same notation as open-file's second argument.
+   Use NAME as the port's filename.  */
 SCM
-scm_port_mode (port)
-     SCM port;
-#endif
+scm_stdio_to_port (FILE *file, char *mode, SCM name)
 {
-  char modes[3];
-  modes[0] = '\0';
-  SCM_ASSERT (SCM_NIMP (port) && SCM_OPPORTP (port), port, SCM_ARG1, s_port_mode);  
-  if (SCM_CAR (port) & SCM_RDNG) {
-    if (SCM_CAR (port) & SCM_WRTNG)
-      strcpy (modes, "r+");
-    else
-      strcpy (modes, "r");
+  long mode_bits = scm_mode_bits (mode);
+  SCM port;
+  struct scm_port_table * pt;
+
+  SCM_NEWCELL (port);
+  SCM_DEFER_INTS;
+  {
+    pt = scm_add_to_port_table (port);
+    SCM_SETPTAB_ENTRY (port, pt);
+    SCM_SETCAR (port, (scm_tc16_fport
+                      | mode_bits
+                      | FPORT_READ_SAFE | FPORT_WRITE_SAFE));
+    SCM_SETSTREAM (port, (SCM) file);
+    if (SCM_BUF0 & SCM_CAR (port))
+      scm_setbuf0 (port);
+    SCM_PTAB_ENTRY (port)->file_name = name;
   }
-  else if (SCM_CAR (port) & SCM_WRTNG)
-    strcpy (modes, "w");
-  if (SCM_CAR (port) & SCM_BUF0)
-    strcat (modes, "0");
-  return scm_makfromstr (modes, strlen (modes), 0);
+  SCM_ALLOW_INTS;
+  return port;
 }
 
 
-#ifdef __STDC__
-static int 
-prinfport (SCM exp, SCM port, int writing)
-#else
+/* Like scm_stdio_to_port, except that:
+   - NAME is a standard C string, not a Guile string
+   - we set the revealed count for FILE's file descriptor to 1, so
+     that FILE won't be closed when the port object is GC'd.  */
+SCM
+scm_standard_stream_to_port (FILE *file, char *mode, char *name)
+{
+  SCM port = scm_stdio_to_port (file, mode, scm_makfrom0str (name));
+  scm_set_port_revealed_x (port, SCM_MAKINUM (1));
+  return port;
+}
+
+
+\f
+/* The fport and pipe port scm_ptobfuns functions --- reading and writing  */
+
+static int prinfport SCM_P ((SCM exp, SCM port, scm_print_state *pstate));
+
 static int 
-prinfport (exp, port, writing)
+prinfport (exp, port, pstate)
      SCM exp;
      SCM port;
-     int writing;
-#endif
+     scm_print_state *pstate;
 {
   SCM name;
   char * c;
@@ -254,32 +392,99 @@ prinfport (exp, port, writing)
 }
 
 
-#ifdef __STDC__
-static int
-scm_fgetc (FILE * s)
-#else
+
 static int
-scm_fgetc (s)
-     FILE * s;
-#endif
+local_fgetc (SCM port)
 {
+  FILE *s = (FILE *) SCM_STREAM (port);
+  pre_read (port);
   if (feof (s))
     return EOF;
   else
     return fgetc (s);
 }
 
+
+static char *
+local_fgets (SCM port, int *len)
+{
+  FILE *f;
+
+  char *buf   = NULL;
+  char *p;             /* pointer to current buffer position */
+  int   limit = 80;    /* current size of buffer */
+
+  pre_read (port);
+
+  /* If this is a socket port or something where we can't rely on
+     ftell to determine how much we've read, then call the generic
+     function.  We could use a separate scm_ptobfuns table with
+     scm_generic_fgets, but then we'd have to change SCM_FPORTP, etc.
+     Ideally, it should become something that means "this port has a
+     file descriptor"; sometimes we reject sockets when we shouldn't.
+     But I'm too stupid at the moment to do that right.  */
+  if (SCM_CAR (port) & SCM_NOFTELL)
+    return scm_generic_fgets (port, len);
+
+  f = (FILE *) SCM_STREAM (port);
+  if (feof (f))
+    return NULL;
+
+  buf = (char *) malloc (limit * sizeof(char));
+  *len = 0;
+
+  /* If a char has been pushed onto the port with scm_ungetc,
+     read that first. */
+  while (SCM_CRDYP (port))
+    {
+      buf[*len] = SCM_CGETUN (port);
+      SCM_TRY_CLRDY (port);
+      if (buf[(*len)++] == '\n' || *len == limit - 1)
+       {
+         buf[*len] = '\0';
+         return buf;
+       }
+    }
+
+  while (1)
+    {
+      int chunk_size = limit - *len;
+      long int numread, pos;
+
+      p = buf + *len;
+
+      /* We must use ftell to figure out how many characters were read.
+        If there are null characters near the end of file, and no
+        terminating newline, there is no other way to tell the difference
+        between an embedded null and the string-terminating null. */
+
+      pos = ftell (f);
+      if (fgets (p, chunk_size, f) == NULL) {
+       if (*len)
+         return buf;
+       free (buf);
+       return NULL;
+      }
+      numread = ftell (f) - pos;
+      *len += numread;
+
+      if (numread < chunk_size - 1 || buf[limit-2] == '\n')
+       return buf;
+
+      buf = (char *) realloc (buf, sizeof(char) * limit * 2);
+      limit *= 2;
+    }
+}
+
 #ifdef vms
-#ifdef __STDC__
-static scm_sizet 
-pwrite (char *ptr, scm_sizet size, nitems, FILE *port)
-#else
+
+static scm_sizet pwrite SCM_P ((char *ptr, scm_sizet size, nitems, FILE *port));
+
 static scm_sizet 
 pwrite (ptr, size, nitems, port)
      char *ptr;
      scm_sizet size, nitems;
      FILE *port;
-#endif
 {
   scm_sizet len = size * nitems;
   scm_sizet i = 0;
@@ -293,54 +498,71 @@ pwrite (ptr, size, nitems, port)
 #define ffwrite fwrite
 #endif
 
-\f
-/* This otherwise pointless code helps some poor 
- * crippled C compilers cope with life. 
- */
 static int
-local_fclose (fp)
-     FILE * fp;
+local_fclose (SCM port)
 {
+  FILE *fp = (FILE *) SCM_STREAM (port);
+
   return fclose (fp);
 }
 
 static int
-local_fflush (fp)
-     FILE * fp;
+local_fflush (SCM port)
 {
+  FILE *fp = (FILE *) SCM_STREAM (port);
   return fflush (fp);
+  FPORT_ALL_OKAY (port);
 }
 
 static int
-local_fputc (c, fp)
-     int c;
-     FILE * fp;
+local_fputc (int c, SCM port)
 {
+  FILE *fp = (FILE *) SCM_STREAM (port);
+
+  pre_write (port);
   return fputc (c, fp);
 }
 
 static int
-local_fputs (s, fp)
-     char * s;
-     FILE * fp;
+local_fputs (char *s, SCM port)
 {
+  FILE *fp = (FILE *) SCM_STREAM (port);
+  pre_write (port);
   return fputs (s, fp);
 }
 
 static scm_sizet
-local_ffwrite (ptr, size, nitems, fp)
-     void * ptr;
-     int size;
-     int nitems;
-     FILE * fp;
+local_ffwrite (char *ptr,
+              scm_sizet size,
+              scm_sizet nitems, 
+              SCM port)
 {
+  FILE *fp = (FILE *) SCM_STREAM (port);
+  pre_write (port);
   return ffwrite (ptr, size, nitems, fp);
 }
 
+static int
+print_pipe_port (SCM exp, SCM port, scm_print_state *pstate)
+{
+  scm_prinport (exp, port, "pipe");
+  return 1;
+}
+
+static int
+local_pclose (SCM port)
+{
+  FILE *fp = (FILE *) SCM_STREAM (port);
+
+  return pclose (fp);
+}
+
 \f
+/* The file and pipe port scm_ptobfuns structures themselves.  */
+
 scm_ptobfuns scm_fptob =
 {
-  scm_mark0,
+  0,
   local_fclose,
   prinfport,
   0,
@@ -348,35 +570,32 @@ scm_ptobfuns scm_fptob =
   local_fputs,
   local_ffwrite,
   local_fflush,
-  scm_fgetc,
+  local_fgetc,
+  local_fgets,
   local_fclose
 };
 
-/* {Pipe ports}
- */
+/* {Pipe ports} */
 scm_ptobfuns scm_pipob =
 {
-  scm_mark0,
-  0,                           /* replaced by pclose in scm_init_ioext() */
-  0,                           /* replaced by prinpipe in scm_init_ioext() */
+  0,
+  local_pclose,  
+  print_pipe_port,
   0,
   local_fputc,
   local_fputs,
   local_ffwrite,
   local_fflush,
-  scm_fgetc,
-  0
-};                             /* replaced by pclose in scm_init_ioext() */
-
+  local_fgetc,
+  scm_generic_fgets,
+  local_pclose
+};
 
-#ifdef __STDC__
-void
-scm_init_fports (void)
-#else
 void
 scm_init_fports ()
-#endif
 {
 #include "fports.x"
+  scm_sysintern ("_IOFBF", SCM_MAKINUM (_IOFBF));
+  scm_sysintern ("_IOLBF", SCM_MAKINUM (_IOLBF));
+  scm_sysintern ("_IONBF", SCM_MAKINUM (_IONBF));
 }
-