From e145dd0277d225b04b4c724e583374cfd7253847 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Fri, 9 Oct 1998 12:45:20 +0000 Subject: [PATCH] Do magic to mix reads and writes on stdio FILE-based ports. * fports.c (FPORT_READ_SAFE, FPORT_WRITE_SAFE, FPORT_ALL_OKAY): New macros. (pre_read, pre_write): New functions. (local_fgetc, local_fgets, local_ffwrite, local_fputc, local_fputs): Call them. (local_fflush): Mark the port as ready for reading and writing. (scm_stdio_to_port): Set the FPORT_READ_SAFE, FPORT_WRITE_SAFE flags on new port objects. This might not be accurate --- who knows what state the FILE * is in when we get it --- but it won't do extraneous calls to fflush or fseek, so it's no worse than the behavior before this change. * ports.h: Add comment. Centralize the creation of port objects based on stdio FILE * in fports.c; don't just throw them together anywhere. * fports.c (scm_stdio_to_port): Make NAME a SCM value, which is what the rest of Guile wants. Don't set the revealed count; that's only appropriate for stdin, stdout, stderr. (scm_standard_stream_to_port): This function does set the revealed count. * init.c (scm_init_standard_ports): Use scm_standard_stream_to_port, not scm_stdio_to_port. * filesys.c (scm_open): Call scm_stdio_to_port; don't write it out. * fports.c (scm_open_file): Same. * posix.c (scm_pipe): Same. * socket.c (scm_sock_fd_to_port): Same. * ioext.c (scm_fdopen): Same. (scm_freopen): Moved from here to... * fports.c (scm_freopen): ... here. This is really something that munges the internals of an fport, so it should go here. * fports.h (scm_stdio_to_port): Adjust prototype. (scm_standard_stream_to_port, scm_freopen): New protoypes. * ioext.h (scm_freopen): Prototype removed. --- libguile/fports.c | 178 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 151 insertions(+), 27 deletions(-) diff --git a/libguile/fports.c b/libguile/fports.c index 01a8d81f8..148e71a5a 100644 --- a/libguile/fports.c +++ b/libguile/fports.c @@ -55,9 +55,85 @@ scm_sizet fwrite (); #endif -/* {Ports - file ports} - * - */ + +/* 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); + +} + +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); +} + + +/* Helpful operations on stdio FILE-based ports */ /* should be called with SCM_DEFER_INTS active */ @@ -178,7 +254,6 @@ 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) @@ -192,35 +267,64 @@ scm_open_file (filename, modes) 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); - SCM_SETCAR (port, scm_tc16_fport | scm_mode_bits (mode)); - SCM_SETSTREAM (port, (SCM) f); + +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_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_PTAB_ENTRY (port)->file_name = filename; } SCM_ALLOW_INTS; return port; } + +/* 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. - If NAME is non-zero, use it as the port's filename. - - scm_stdio_to_port sets the revealed count for FILE's file - descriptor to 1, so that FILE won't be closed when the port object - is GC'd. */ + Use NAME as the port's filename. */ SCM -scm_stdio_to_port (file, mode, name) - FILE *file; - char *mode; - char *name; +scm_stdio_to_port (FILE *file, char *mode, SCM name) { long mode_bits = scm_mode_bits (mode); SCM port; @@ -231,18 +335,34 @@ scm_stdio_to_port (file, mode, name) { pt = scm_add_to_port_table (port); SCM_SETPTAB_ENTRY (port, pt); - SCM_SETCAR (port, (scm_tc16_fport | mode_bits)); + 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 = scm_makfrom0str (name); + SCM_PTAB_ENTRY (port)->file_name = name; } SCM_ALLOW_INTS; + return port; +} + + +/* 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; } + +/* The fport and pipe port scm_ptobfuns functions --- reading and writing */ static int prinfport SCM_P ((SCM exp, SCM port, scm_print_state *pstate)); @@ -277,6 +397,7 @@ static int local_fgetc (SCM port) { FILE *s = (FILE *) SCM_STREAM (port); + pre_read (port); if (feof (s)) return EOF; else @@ -293,6 +414,8 @@ local_fgets (SCM port, int *len) char *p; /* pointer to current buffer position */ int limit = 80; /* current size of buffer */ + pre_read (port); + f = (FILE *) SCM_STREAM (port); if (feof (f)) return NULL; @@ -365,11 +488,6 @@ pwrite (ptr, size, nitems, port) #define ffwrite fwrite #endif - -/* This otherwise pointless code helps some poor - * crippled C compilers cope with life. - */ - static int local_fclose (SCM port) { @@ -383,6 +501,7 @@ local_fflush (SCM port) { FILE *fp = (FILE *) SCM_STREAM (port); return fflush (fp); + FPORT_ALL_OKAY (port); } static int @@ -390,6 +509,7 @@ local_fputc (int c, SCM port) { FILE *fp = (FILE *) SCM_STREAM (port); + pre_write (port); return fputc (c, fp); } @@ -397,6 +517,7 @@ static int local_fputs (char *s, SCM port) { FILE *fp = (FILE *) SCM_STREAM (port); + pre_write (port); return fputs (s, fp); } @@ -407,6 +528,7 @@ local_ffwrite (char *ptr, SCM port) { FILE *fp = (FILE *) SCM_STREAM (port); + pre_write (port); return ffwrite (ptr, size, nitems, fp); } @@ -426,6 +548,8 @@ local_pclose (SCM port) } +/* The file and pipe port scm_ptobfuns structures themselves. */ + scm_ptobfuns scm_fptob = { 0,