ExternalStream.st
author Claus Gittinger <cg@exept.de>
Thu, 27 Aug 1998 14:34:45 +0200
changeset 3790 629791828694
parent 3682 6de8ac1d3e88
child 3863 af67e6995a4a
permissions -rw-r--r--
also keep track of scheduler processes, if dynamic prios are disabled (for procScheduler)

"
 COPYRIGHT (c) 1988 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"

ReadWriteStream subclass:#ExternalStream
	instanceVariableNames:'filePointer mode buffered binary eolMode hitEOF didWrite
		lastErrorNumber readAhead'
	classVariableNames:'Lobby LastErrorNumber InvalidReadSignal InvalidWriteSignal
		InvalidModeSignal OpenErrorSignal StreamNotOpenSignal
		InvalidOperationSignal DefaultEOLMode ReadMode ReadWriteMode
		WriteMode AppendMode CreateReadWriteMode StreamIOErrorSignal'
	poolDictionaries:''
	category:'Streams-External'
!

!ExternalStream primitiveDefinitions!

%{

#ifdef __openVMS__                                        
# undef __new    
#endif

#include <stdio.h>
#define _STDIO_H_INCLUDED_

#ifndef NO_FCNTL_H
# include <fcntl.h>
# define _FCNTL_H_INCLUDED_
#endif

#include <errno.h>
#define _ERRNO_H_INCLUDED_

#ifdef LINUX
  /* use inline string macros */
# define __STRINGDEFS__
# include <linuxIntern.h>
#endif

#ifdef hpux
# define fileno(f)      ((f->__fileH << 8) | (f->__fileL))
#endif

#ifndef SEEK_SET
# define SEEK_SET 0
#endif
#ifndef SEEK_CUR
# define SEEK_CUR 1
#endif
#ifndef SEEK_END
# define SEEK_END 2
#endif

#ifdef __VMS__
# define CLEAR_ERRNO            errno = 0;
#else
# define CLEAR_ERRNO            /* nothing */
#endif

#ifdef LATER
#define __isFilePointer(x)      (__Class(x) == ExternalStream__FilePointer)
#define __CHANGECLASS(o, x)     (__qClass(o) = (x), o)
#define __MKFILEPOINTER(f)      __CHANGECLASS(__MKEXTERNALADDRESS(f), ExternalStream__FilePointer)
#else
#define __MKFILEPOINTER(f)      __MKEXTERNALADDRESS(f)
#endif

#ifdef xxxWIN32
# define NO_STDIO
# define __HANDLEVal(o)         (HFILE)(__externalAddressVal(o))
# define READ(f, cp, n)         _lread((f), (cp), (n))
# define WRITE(f, cp, n)        _lwrite((f), (cp), (n))
# define FFLUSH(fp)             /* nothing */
# undef STDIO_NEEDS_FSEEK
# define FILEPOINTER            HFILE
# define FILENO(f)              (f)
#else
# define STDIO_NEEDS_FSEEK
# define FILEPOINTER            FILE *
# define READ(f, cp, n)         read(f, cp, n)
# define WRITE(f, cp, n)        write(f, cp, n)
# define FFLUSH(fp)             fflush(fp)
# define FILENO(f)              fileno(f)
#endif


#if defined(WIN32) && defined(NO_STDIO)

# ifdef i386
#  define _X86_
# endif

# undef INT
# undef Array
# undef Number
# undef Method
# undef Point
# undef Rectangle
# undef Block

/* # include <windows.h> /* */
# include <stdarg.h> /* */
# include <windef.h> /* */
# include <winbase.h> /* */
# include <wingdi.h> /* */
# include <winuser.h> /* */

# ifdef __DEF_Array
#   define Array __DEF_Array
# endif
# ifdef __DEF_Number
#   define Number __DEF_Number
# endif
# ifdef __DEF_Method
#   define Method __DEF_Method
# endif
# ifdef __DEF_Point
#   define Point __DEF_Point
# endif
# ifdef __DEF_Block
#   define Block __DEF_Block
# endif

# define stat _stat

#endif /* WIN32 */

extern char *__survStartPtr, *__survEndPtr;

#ifdef DEBUGGING
# define DEBUGBUFFER(buf)  \
    if (((char *)(buf) >= __survStartPtr) \
     && ((char *)(buf) < __survEndPtr)) { \
        __fatal0("read into survivor\n"); \
    }

#else
# define DEBUGBUFFER(buf) /* nothing */
#endif

/*
 * stdio library requires an fseek before reading whenever a file
 * is open for read/write and the last operation was a write.
 * (also vice-versa).
 * All code should use the following macro before doing reads:
 */
#ifdef STDIO_NEEDS_FSEEK
# define OPT_FSEEK(f, pos, whence)      fseek(f, pos, whence)
#else
# define OPT_FSEEK(f, pos, whence)      /* nothing */
#endif

#define __READING__(f)                          \
    if ((__INST(didWrite) != false)              \
     && (__INST(mode) == @symbol(readwrite))) {  \
        __INST(didWrite) = false;                \
        OPT_FSEEK(f, 0L, SEEK_CUR); /* needed in stdio */  \
    }

#define __WRITING__(f)                          \
    if ((__INST(didWrite) != true)               \
     && (__INST(mode) == @symbol(readwrite))) {  \
        __INST(didWrite) = true;                 \
        OPT_FSEEK(f, 0L, SEEK_CUR); /* needed in stdio */  \
    }


#ifdef NO_STDIO
#define __UNGETC__(c, f, isBuffered)                    \
    __INST(readAhead) = __MKSMALLINT((c));
#else
#define __UNGETC__(c, f, isBuffered)                    \
    if (isBuffered) {                                   \
        ungetc((c), (f));                               \
    } else {                                            \
        __INST(readAhead) = __MKSMALLINT((c));          \
    }
#endif

#ifdef NO_STDIO
#define __READBYTE__(ret, f, buf, isBuffered)           \
    {                                                   \
        OBJ rA = __INST(readAhead);                     \
        if (rA != nil) {                                \
            *(buf) = __intVal(rA);                      \
            DEBUGBUFFER(buf);                           \
            __INST(readAhead) = nil;                    \
            (ret) = 1;                                  \
        } else {                                        \
            for (;;) {                                  \
                CLEAR_ERRNO;                            \
                (ret) = READ(f, buf, 1);                \
                DEBUGBUFFER(buf);                       \
                if ((ret) >= 0 || errno != EINTR)       \
                    break;                              \
                __HANDLE_INTERRUPTS__;                  \
            }                                           \
        }                                               \
    }
#else
#define __READBYTE__(ret, f, buf, isBuffered)           \
    if (isBuffered) {                                   \
        for (;;) {                                      \
            CLEAR_ERRNO;                                \
            (ret) = getc(f);                            \
            if ((ret) >= 0) {                           \
                DEBUGBUFFER(buf);                       \
                *(buf) = (ret);                         \
                (ret) = 1;                              \
            } else if (ferror(f)) {                     \
                if (errno == EINTR) {                   \
                    __HANDLE_INTERRUPTS__;              \
                    clearerr(f);                        \
                    continue;                           \
                }                                       \
            } else                                      \
                (ret) = 0;                              \
            break;                                      \
        }                                               \
    } else {                                            \
        OBJ rA = __INST(readAhead);                     \
        if (rA != nil) {                                \
            *(buf) = __intVal(rA);                      \
            DEBUGBUFFER(buf);                           \
            __INST(readAhead) = nil;                    \
            (ret) = 1;                                  \
        } else {                                        \
            for (;;) {                                  \
                CLEAR_ERRNO;                            \
                (ret) = read(fileno(f), buf, 1);        \
                DEBUGBUFFER(buf);                       \
                if ((ret) >= 0 || errno != EINTR)       \
                    break;                              \
                __HANDLE_INTERRUPTS__;                  \
            }                                           \
        }                                               \
   }                                                                          
#endif

/*
 * read_bytes into a c-buffer
 * (which may NOT move)
 */
#ifdef NO_STDIO
#define __READBYTES__(ret, f, buf, cnt, isBuffered)     \
    {                                                   \
        int __offs = 0;                                 \
                                                        \
        while (__offs < (cnt)) {                        \
            OBJ rA = __INST(readAhead);                 \
            if (rA != nil) {                            \
                (buf)[__offs] = __intVal(rA);           \
                DEBUGBUFFER(buf);                       \
                __INST(readAhead) = nil;                \
                (ret) = 1;                              \
            } else {                                    \
                CLEAR_ERRNO;                            \
                (ret) = READ(f, (buf)+__offs, (cnt)-__offs); \
                DEBUGBUFFER(buf);                       \
                if ((ret) <= 0) {                       \
                    if ((ret) < 0 && errno == EINTR) {  \
                        __HANDLE_INTERRUPTS__;          \
                        continue;                       \
                    }                                   \
                    break;                              \
                }                                       \
            }                                           \
            __offs += (ret);                            \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
   }
#else
#define __READBYTES__(ret, f, buf, cnt, isBuffered)     \
    (ret) = 0;                                          \
    if (isBuffered) {                                   \
        int __offs = 0;                                 \
        while (__offs < (cnt)) {                        \
            CLEAR_ERRNO;                                \
            (ret) = getc(f);                            \
            if ((ret) < 0) {                            \
                if (ferror(f)) {                        \
                    if (errno == EINTR) {               \
                        __HANDLE_INTERRUPTS__;          \
                        clearerr(f);                    \
                        continue;                       \
                    }                                   \
                } else {                                \
                    (ret) = 0;                          \
                }                                       \
                break;                                  \
            }                                           \
            DEBUGBUFFER(buf);                           \
            (buf)[__offs++] = (ret);                    \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
    } else {                                            \
        int __offs = 0;                                 \
        int fd = fileno(f);                             \
                                                        \
        while (__offs < (cnt)) {                        \
            OBJ rA = __INST(readAhead);                 \
            if (rA != nil) {                            \
                DEBUGBUFFER(buf);                       \
                (buf)[__offs] = __intVal(rA);           \
                __INST(readAhead) = nil;                \
                (ret) = 1;                              \
            } else {                                    \
                CLEAR_ERRNO;                            \
                (ret) = read(fd, (buf)+__offs, (cnt)-__offs);                 \
                DEBUGBUFFER(buf);                       \
                if ((ret) <= 0) {                       \
                    if ((ret) < 0 && errno == EINTR) {  \
                        __HANDLE_INTERRUPTS__;          \
                        continue;                       \
                    }                                   \
                    break;                              \
                }                                       \
            }                                           \
            __offs += (ret);                            \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
   }

#if defined(F_GETFL) && defined(F_SETFL) && defined(FNDELAY)
# define GETFLAGS(fd) \
        fcntl(fd, F_GETFL, 0);

# define SETFLAGS(fd, flags) \
        fcntl(fd, F_SETFL, flags)

# define SETNONBLOCKING(fd) \
        { \
            int flags; \
            flags = fcntl(fd, F_GETFL, 0); \
            if (flags >= 0) { \
                fcntl(fd, F_SETFL, flags | FNDELAY); \
            } \
        }
#else
# define GETFLAGS(fd) 0
# define SETFLAGS(fd, flags) /* nothing */
# define SETNONBLOCKING(fd) /* nothing */
#endif                                                        

#define __READAVAILBYTES__(ret, f, buf, cnt, isBuffered) \
  {                                                     \
    int __offs = 0;                                     \
    int oldFlags;                                       \
                                                        \
    (ret) = 0;                                          \
    oldFlags = GETFLAGS(fileno(f));                     \
    SETNONBLOCKING(fileno(f));                          \
    if (isBuffered) {                                   \
        while (__offs < (cnt)) {                        \
            CLEAR_ERRNO;                                \
            (ret) = getc(f);                            \
            if ((ret) < 0) {                            \
                if (ferror(f)) {                        \
                    if (errno == EINTR) {               \
                        clearerr(f);                    \
                        break;                       \
                    }                                   \
                } else {                                \
                    (ret) = 0;                          \
                }                                       \
                break;                                  \
            }                                           \
            (buf)[__offs++] = (ret);                    \
            DEBUGBUFFER(buf);                           \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
    } else {                                            \
        int fd = fileno(f);                             \
                                                        \
        while (__offs < (cnt)) {                        \
            OBJ rA = __INST(readAhead);                 \
            if (rA != nil) {                            \
                (buf)[__offs] = __intVal(rA);           \
                DEBUGBUFFER(buf);                       \
                __INST(readAhead) = nil;                \
                (ret) = 1;                              \
                __offs += (ret);                        \
                continue;                               \
            }                                           \
            CLEAR_ERRNO;                                \
            (ret) = read(fd, (buf)+__offs, (cnt)-__offs); \
            DEBUGBUFFER(buf);                           \
            if ((ret) < 0) {                            \
                if (errno == EINTR) {                   \
                    break;                              \
                }                                       \
                break;                                  \
            }                                           \
            __offs += (ret);                            \
            break;                                      \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
    }                                                   \
    SETFLAGS(fileno(f), oldFlags);                      \
  }

#endif

/*
 * read_bytes into an object
 * (which may be moved by GC)
 */
#ifdef NO_STDIO
#define __READBYTES_OBJ__(ret, f, obj, obj_offs, cnt, isBuffered)     \
  {                                                     \
    int __ooffs = obj_offs;                             \
    int __offs = 0;                                     \
    char *buf = (char *)(obj);                          \
                                                        \
    (ret) = 0;                                          \
    {                                                   \
        while (__offs < (cnt)) {                        \
            OBJ rA = __INST(readAhead);                 \
            if (rA != nil) {                            \
                (buf)[__ooffs+__offs] = __intVal(rA);   \
                DEBUGBUFFER(buf);                       \
                __INST(readAhead) = nil;                \
                (ret) = 1;                              \
            } else {                                    \
                CLEAR_ERRNO;                            \
                (ret) = READ(f, (buf)+__ooffs+__offs, (cnt)-__offs); \
                DEBUGBUFFER(buf);                       \
                if ((ret) <= 0) {                       \
                    if ((ret) < 0 && errno == EINTR) {  \
                        __HANDLE_INTERRUPTS__;          \
                        /* refetch */                   \
                        buf = (char *)(obj);            \
                        continue;                       \
                    }                                   \
                    break;                              \
                }                                       \
            }                                           \
            __offs += (ret);                            \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
    }                                                   \
  }
#else
#define __READBYTES_OBJ__(ret, f, obj, obj_offs, cnt, isBuffered)     \
  {                                                     \
    int __ooffs = obj_offs;                             \
    int __offs = 0;                                     \
    char *buf = (char *)(obj);                          \
                                                        \
    (ret) = 0;                                          \
    if (isBuffered) {                                   \
        while (__offs < (cnt)) {                        \
            CLEAR_ERRNO;                                \
            (ret) = getc(f);                            \
            if ((ret) < 0) {                            \
                if (ferror(f)) {                        \
                    if (errno == EINTR) {               \
                        __HANDLE_INTERRUPTS__;          \
                        clearerr(f);                    \
                        /* refetch */                   \
                        buf = (char *)(obj);            \
                        DEBUGBUFFER(buf);               \
                        continue;                       \
                    }                                   \
                } else {                                \
                    (ret) = 0;                          \
                }                                       \
                break;                                  \
            }                                           \
            (buf)[__ooffs+__offs] = (ret);              \
            DEBUGBUFFER(buf);                           \
            __offs++;                                   \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
    } else {                                            \
        int fd = fileno(f);                             \
                                                        \
        while (__offs < (cnt)) {                        \
            OBJ rA = __INST(readAhead);                 \
            if (rA != nil) {                            \
                (buf)[__ooffs+__offs] = __intVal(rA);   \
                DEBUGBUFFER(buf);                       \
                __INST(readAhead) = nil;                \
                (ret) = 1;                              \
            } else {                                    \
                CLEAR_ERRNO;                            \
                (ret) = read(fd, (buf)+__ooffs+__offs, (cnt)-__offs); \
                DEBUGBUFFER(buf);                       \
                if ((ret) <= 0) {                       \
                    if ((ret) < 0 && errno == EINTR) {  \
                        __HANDLE_INTERRUPTS__;          \
                        /* refetch */                   \
                        buf = (char *)(obj);            \
                        continue;                       \
                    }                                   \
                    break;                              \
                }                                       \
            }                                           \
            __offs += (ret);                            \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
    }                                                   \
  }

#define __READAVAILBYTES_OBJ__(ret, f, obj, obj_offs, cnt, isBuffered)     \
  {                                                     \
    int __ooffs = obj_offs;                             \
    int __offs = 0;                                     \
    char *buf = (char *)(obj);                          \
                                                        \
    (ret) = 0;                                          \
    if (isBuffered) {                                   \
        while (__offs < (cnt)) {                        \
            CLEAR_ERRNO;                                \
            (ret) = getc(f);                            \
            if ((ret) < 0) {                            \
                if (ferror(f)) {                        \
                    if (errno == EINTR) {               \
                        clearerr(f);                    \
                        /* refetch */                   \
                        buf = (char *)(obj);            \
                        break;                          \
                    }                                   \
                } else {                                \
                    (ret) = 0;                          \
                }                                       \
                break;                                  \
            }                                           \
            (buf)[__ooffs+__offs] = (ret);              \
            DEBUGBUFFER(buf);                           \
            __offs++;                                   \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
    } else {                                            \
        int fd = fileno(f);                             \
                                                        \
        while (__offs < (cnt)) {                        \
            OBJ rA = __INST(readAhead);                 \
            if (rA != nil) {                            \
                (buf)[__ooffs+__offs] = __intVal(rA);   \
                DEBUGBUFFER(buf);                       \
                __INST(readAhead) = nil;                \
                (ret) = 1;                              \
                __offs += (ret);                        \
                continue;                               \
            }                                           \
            CLEAR_ERRNO;                                \
            (ret) = read(fd, (buf)+__ooffs+__offs, (cnt)-__offs); \
            DEBUGBUFFER(buf);                           \
            if ((ret) < 0 && errno == EINTR) {          \
                /* refetch */                           \
                buf = (char *)(obj);                    \
                break;                               \
            }                                           \
            __offs += (ret);                            \
            break;                                      \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
    }                                                   \
  }


#endif

#ifdef NO_STDIO
#define __WRITEBYTE__(ret, f, buf, isBuffered)          \
        for (;;) {                                      \
            CLEAR_ERRNO;                                \
            (ret) = WRITE(f, buf, 1);                   \
            if ((ret) >= 0 || errno != EINTR)           \
                break;                                  \
            __HANDLE_INTERRUPTS__;                      \
        }
#else
#define __WRITEBYTE__(ret, f, buf, isBuffered)          \
    if (isBuffered) {                                   \
        for (;;) {                                      \
            CLEAR_ERRNO;                                \
            ret = putc(*(buf), f);                      \
            if ((ret) >= 0) {                           \
                (ret) = 1;                              \
            } else if (ferror(f)) {                     \
                if (errno == EINTR) {                   \
                    __HANDLE_INTERRUPTS__;              \
                    clearerr(f);                        \
                    continue;                           \
                }                                       \
            } else                                      \
                (ret) = 0;                              \
            break;                                      \
        }                                               \
    } else {                                            \
        for (;;) {                                      \
            CLEAR_ERRNO;                                \
            (ret) = write(fileno(f), buf, 1);           \
            if ((ret) >= 0 || errno != EINTR)           \
                break;                                  \
            __HANDLE_INTERRUPTS__;                      \
        }                                               \
   }                                                                          
#endif

/*
 * write_bytes from a c-buffer
 * (which may NOT move)
 */
#ifdef NO_STDIO
#define __WRITEBYTES__(ret, f, buf, cnt, isBuffered)    \
    (ret) = 0;                                          \
    {                                                   \
        int __offs = 0;                                 \
        while (__offs < (cnt)) {                        \
            CLEAR_ERRNO;                                \
            ret = WRITE(f, (buf)+__offs, (cnt)-__offs); \
            if (ret <= 0) {                             \
                if (ret < 0 && errno == EINTR) {        \
                    __HANDLE_INTERRUPTS__;              \
                    continue;                           \
                }                                       \
                break;                                  \
            }                                           \
            __offs += (ret);                            \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
   }
#else
#define __WRITEBYTES__(ret, f, buf, cnt, isBuffered)    \
    (ret) = 0;                                          \
    if (isBuffered) {                                   \
        int __offs = 0;                                 \
        while (__offs < (cnt)) {                        \
            CLEAR_ERRNO;                                \
            ret = fwrite((buf)+__offs, 1, (cnt)-__offs, f);\
            if ((ret) <= 0) {                            \
                if (ferror(f)) {                        \
                    if (errno == EINTR) {               \
                        __HANDLE_INTERRUPTS__;          \
                        clearerr(f);                    \
                        continue;                       \
                    }                                   \
                    break;                              \
                } else {                                \
                    (ret) = 0;                          \
                }                                       \
            }                                           \
            __offs += (ret);                            \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
    } else {                                            \
        int __offs = 0;                                 \
        while (__offs < (cnt)) {                        \
            CLEAR_ERRNO;                                \
            ret = write(fileno(f), (buf)+__offs, (cnt)-__offs);\
            if (ret <= 0) {                             \
                if (ret < 0 && errno == EINTR) {        \
                    __HANDLE_INTERRUPTS__;              \
                    continue;                           \
                }                                       \
                break;                                  \
            }                                           \
            __offs += (ret);                            \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
   }
#endif

/*
 * write_bytes from an object
 * (which may be moved around by GC)
 */
#ifdef NO_STDIO
#define __WRITEBYTES_OBJ__(ret, f, obj, obj_offs, cnt, isBuffered)            \
  {                                                     \
    int __ooffs = obj_offs;                             \
    int __offs = 0;                                     \
    char *buf = (char *)(obj);                          \
                                                        \
    (ret) = 0;                                          \
    {                                                   \
        while (__offs < (cnt)) {                        \
            CLEAR_ERRNO;                                \
            ret = WRITE(f, (buf)+__ooffs+__offs, (cnt)-__offs);               \
            if (ret <= 0) {                             \
                if (ret < 0 && errno == EINTR) {        \
                    __HANDLE_INTERRUPTS__;              \
                    /* refetch */                       \
                    buf = (char *)(obj);                \
                    continue;                           \
                }                                       \
                break;                                  \
            }                                           \
            __offs += (ret);                            \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
    }                                                   \
  }
#else
#define __WRITEBYTES_OBJ__(ret, f, obj, obj_offs, cnt, isBuffered)            \
  {                                                     \
    int __ooffs = obj_offs;                             \
    int __offs = 0;                                     \
    char *buf = (char *)(obj);                          \
                                                        \
    (ret) = 0;                                          \
    if (isBuffered) {                                   \
        while (__offs < (cnt)) {                        \
            CLEAR_ERRNO;                                \
            ret = fwrite((buf)+__ooffs+__offs, 1, (cnt)-__offs, f);           \
            if ((ret) <= 0) {                           \
                if (ferror(f)) {                        \
                    if (errno == EINTR) {               \
                        __HANDLE_INTERRUPTS__;          \
                        /* refetch */                   \
                        buf = (char *)(obj);            \
                        clearerr(f);                    \
                        continue;                       \
                    }                                   \
                    break;                              \
                } else {                                \
                    (ret) = 0;                          \
                }                                       \
            }                                           \
            __offs += (ret);                            \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
    } else {                                            \
        while (__offs < (cnt)) {                        \
            CLEAR_ERRNO;                                \
            ret = write(fileno(f), (buf)+__ooffs+__offs, (cnt)-__offs);       \
            if (ret <= 0) {                             \
                if (ret < 0 && errno == EINTR) {        \
                    __HANDLE_INTERRUPTS__;              \
                    /* refetch */                       \
                    buf = (char *)(obj);                \
                    continue;                           \
                }                                       \
                break;                                  \
            }                                           \
            __offs += (ret);                            \
        }                                               \
        if (__offs > 0)                                 \
            (ret) = __offs;                             \
    }                                                   \
  }
#endif

/*
 * on some systems errno is a macro ... check for it here
 */
#ifndef errno
 extern errno;
#endif

%}
! !

!ExternalStream class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1988 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

documentation
"
    ExternalStream defines protocol common to Streams which have a file-descriptor and 
    represent some file or communicationChannel of the underlying OperatingSystem.
    ExternalStream is abstract; concrete classes are FileStream, PipeStream etc.

    ExternalStreams can be in two modes: text- (the default) and binary-mode.
    In text-mode, the elements read/written are characters; 
    while in binary-mode the basic elements are bytes which read/write as SmallIntegers 
    in the range 0..255.

    Also, the stream can be either in buffered or unbuffered mode. In buffered mode,
    data is not written until either a cr is written (in text mode) or a synchronizeOutput
    is sent (in both modes).

    The underlying OperatingSystem streams may either be closed explicitely (sending a close)
    or just forgotten - in this case, the garbage collector will eventually collect the
    object AND a close will be performed automatically (but you will NOT know when this 
    happens - so it is recommended, that you close your files when no longer needed).
    Closing is also suggested, since if smalltalk is finished (be it by purpose, or due to
    some crash) the data will not be in the file, if unclosed. 
    All streams understand the close message, so it never hurts to use it (it is defined as 
    a noop in one of the superclasses).

    Most of the methods found here redefine inherited methods for better performance,
    since I/O from/to files should be fast.

    Recovering a snapshot:
      not all streams can be restored to the state they had before - see the implementation of
      reOpen in subclasses for more information.
      For streams sitting on some communication channel (i.e. Pipes and Sockets) you should
      reestablish the stream upon image restart (make someone dependent on ObjectMemory).
      FileStreams are reopened and positioned to their offset they had at snapshot time.
      This may fail, if the file was removed or renamed - or lead to confusion
      if the contents changed in the meantime.
      Therefore, it is a good idea to reopen files and check for these things at restart time.

    [Instance variables:]

	filePointer     <Integer>       the unix FILE*; somehow mapped to an integer
					(notice: not the fd)
					on Windows: the fileHandle
	mode            <Symbol>        #readwrite, #readonly or #writeonly
	buffered        <Boolean>       true, if buffered (i.e. collects characters - does
					not output immediately)
	binary          <Boolean>       true if in binary mode (reads bytes instead of chars)
	eolMode         <Symbol>        one of nil, #cr or #crlf.
					determines how lines should be terminated.
					nil -> newLine (as in Unix);
					#crlf -> with cr-lf (as in MSDOS)
					#cr -> with cr (as in VMS)
	hitEOF          <Boolean>       true, if EOF was reached

	lastErrorNumber <Integer>       the value of errno (only valid right after the error -
					updated with next i/o operation)

    [Class variables:]
	Lobby           <Registry>      keeps track of used ext-streams (to free up FILE*'s)

	StreamErrorSignal       <Signal> parent of all stream errors (see Stream class)
	InvalidReadSignal       <Signal> raised on read from writeonly stream
	InvalidWriteSignal      <Signal> raised on write to readonly stream 
	InvalidModeSignal       <Signal> raised on text I/O with binary-stream
					 or binary I/O with text-stream
	OpenErrorSignal         <Signal> raised if open fails
	StreamNotOpenSignal     <Signal> raised on I/O with non-open stream

    Additional notes:
      This class is implemented using the underlying stdio-c library package, which
      has both advantages and disadvantages: since it is portable (posix defined), porting
      ST/X to non-Unix machines is simplified. The disadvantage is that the stdio library
      has big problems handling unbounded Streams, since the EOF handling in stdio is
      not prepared for data to arrive after EOF has been reached - time will show, if we need
      a complete rewrite for UnboundedStream ...

      Also, depending on the system, the stdio library behaves infriendly when signals
      occur while reading (for example, timer interrupts) - on real unixes (i.e. BSD) the signal
      is handled transparently - on SYS5.3 (i.e. non unixes :-) the read operation returns
      an error and errno is set to EINTR. 
      Thats what the ugly code around all getc-calls is for ...
      Since things get more and more ugly - we will rewrite ExternalStream
      completely, to NOT use any stdio stuff (and do its buffering itself).

      Notice that typical stdio's use a single errno global variable to return an error code,
      this was bad design in the stdio lib (right from the very beginning), since its much
      harder to deal with this in the presence of lightweight processes, where errno gets
      overwritten by an I/O operation done in another thread. (stdio should have been written
      to return errno as a negative number ...).
      To deal with this, the scheduler treats errno like a per-thread private variable,
      and saves/restores the errno setting when switching to another thread.
      (Notice that some thread packages do this also, but ST/X's thread implementation
      does not depend on those, but instead uses a portable private package).

      Finally, if an stdio-stream is open for both reading and writing, we have to call
      fseek whenever we are about to read after write and vice versa.
      Two macros (__READING__ and __WRITING__) have been defined to be used before every
      fread/fgetc and fwrite/putc respectively.

    [author:]
	Claus Gittinger
	Stefan Vogel (many, many fixes ...)

    [see also:]
	FileStream Socket PipeStream
	Filename OperatingSystem
"
!

examples
"
    open a file, read the contents and display it in a textView:

	|topView scrollPane textView fileStream text|

	topView := StandardSystemView new.
	topView label:'contents of Makefile'.

	scrollPane := HVScrollableView in:topView.
	scrollPane origin:0.0@0.0 corner:1.0@1.0.
        
	textView := EditTextView new.
	scrollPane scrolledView:textView.

	fileStream := 'Makefile' asFilename readStream.
	text := fileStream upToEnd.
	fileStream close.

	textView contents:text.

	topView open.



    Notice, all of the above can also be done (simply) as:

	EditTextView openOn:'Makefile'
"
! !

!ExternalStream class methodsFor:'initialization'!

initDefaultEOLMode
    OperatingSystem isUNIXlike ifTrue:[
	"/ unix EOL conventions
	DefaultEOLMode := #nl
    ] ifFalse:[
	OperatingSystem isVMSlike ifTrue:[
	    "/ vms EOL conventions
	    DefaultEOLMode := #cr
	] ifFalse:[
	    "/ msdos EOL conventions
	    DefaultEOLMode := #crlf
	]
    ]
!

initModeStrings
    "initialize modeStrings which are passed down to the underlying
     fopen/fdopen functions."

    OperatingSystem isMSDOSlike ifTrue:[
	ReadMode := 'rb'.
	ReadWriteMode := 'rb+'.
	WriteMode := 'wb'.
	AppendMode := 'ab+'.
	CreateReadWriteMode := 'wb+'.
    ] ifFalse:[
	ReadMode := 'r'.
	ReadWriteMode := 'r+'.
	WriteMode := 'w'.
	AppendMode := 'a+'.
	CreateReadWriteMode := 'w+'.
    ]
!

initialize
    OpenErrorSignal isNil ifTrue:[
        OpenErrorSignal := StreamErrorSignal newSignalMayProceed:true.
        OpenErrorSignal nameClass:self message:#openErrorSignal.
        OpenErrorSignal notifierString:'open error'.

        InvalidReadSignal := ReadErrorSignal newSignalMayProceed:false.
        InvalidReadSignal nameClass:self message:#invalidReadSignal.
        InvalidReadSignal notifierString:'read error'.

        InvalidWriteSignal := WriteErrorSignal newSignalMayProceed:false.
        InvalidWriteSignal nameClass:self message:#invalidWriteSignal.
        InvalidWriteSignal notifierString:'write error'.

        InvalidModeSignal :=  StreamErrorSignal newSignalMayProceed:false.
        InvalidModeSignal nameClass:self message:#invalidModeSignal.
        InvalidModeSignal notifierString:'binary/text mode mismatch'.

        InvalidOperationSignal :=  StreamErrorSignal newSignalMayProceed:false.
        InvalidOperationSignal nameClass:self message:#invalidOperationSignal.
        InvalidOperationSignal notifierString:'unsupported file operation'.

        StreamNotOpenSignal := StreamErrorSignal newSignalMayProceed:false.
        StreamNotOpenSignal nameClass:self message:#streamNotOpenSignal.
        StreamNotOpenSignal notifierString:'stream is not open'.

        StreamIOErrorSignal := StreamErrorSignal newSignalMayProceed:false.
        StreamIOErrorSignal nameClass:self message:#streamIOErrorSignal.
        StreamIOErrorSignal notifierString:'I/O error'.
    ].

    Lobby isNil ifTrue:[
        Lobby := Registry new.

        "want to get informed when returning from snapshot"
        ObjectMemory addDependent:self
    ].
    self initDefaultEOLMode.
    self initModeStrings.

    "Modified: / 21.5.1998 / 16:33:53 / cg"
!

reOpenFiles
    "reopen all files (if possible) after a snapShot load"

    Lobby do:[:aFileStream |
	aFileStream reOpen
    ].
!

update:something with:aParameter from:changedObject
    "have to reopen files when returning from snapshot"

    something == #returnFromSnapshot ifTrue:[
	self reOpenFiles.
	self initDefaultEOLMode
    ]

    "Created: 15.6.1996 / 15:19:59 / cg"
! !

!ExternalStream class methodsFor:'instance creation'!

forFileDescriptor:aFileDescriptor mode:modeString
    "given a fileDescriptor, create an ExternalStream object
     to operate on this fd. 
     The mode-argument is passed to the fdopen call.
     This may be used to wrap fd's as returned by user 
     primitive code, or to wrap pipe-fds into externalStreams."

    |newStream|

    newStream := self basicNew.
    newStream text; buffered:true; eolMode:DefaultEOLMode; clearEOF.
    ^ newStream connectTo:aFileDescriptor withMode:modeString

    "
     the example below will probably fail (15 is a random FD):

     |s|

     s := ExternalStream forFileDescriptor:15 mode:'r'.
     s next.
    "

    "Created: 29.2.1996 / 18:05:00 / cg"
    "Modified: 29.2.1996 / 18:17:07 / cg"
!

forReadWriteToFileDescriptor:aFileDescriptor
    "given a fileDescriptor, create an ExternalStream object
     to read/write from/to this fd. This may be used to wrap fd's
     as returned by user primitive code, or to wrap pipe-
     filedescriptors into externalStreams."

    ^ (self forFileDescriptor:aFileDescriptor mode:ReadWriteMode) readwrite

    "
     the example below will probably fail (15 is a random FD):

     |s|

     s := ExternalStream forReadWriteToFileDescriptor:15.
     s next.
    "

    "Created: 29.2.1996 / 18:15:08 / cg"
    "Modified: 29.2.1996 / 18:16:25 / cg"
!

forReadingFromFileDescriptor:aFileDescriptor
    "given a fileDescriptor, create an ExternalStream object
     to read from this fd. This may be used to wrap fd's
     as returned by user primitive code, or to wrap pipe-
     filedescriptors into externalStreams."

    ^ (self forFileDescriptor:aFileDescriptor mode:ReadMode) readonly

    "
     the example below will probably fail (15 is a random FD):

     |s|

     s := ExternalStream forReadingFromFileDescriptor:15.
     s next.
    "

    "
     |pipe readFd writeFd rs ws|

     'create OS pipe ...'.

     pipe := OperatingSystem makePipe.
     readFd := pipe at:1.
     writeFd := pipe at:2.

     'connect Smalltalk streams ...'.

     rs := ExternalStream forReadingFromFileDescriptor:readFd.
     ws := ExternalStream forWritingToFileDescriptor:writeFd.

     'read ...'.
     [
         1 to:10 do:[:i |
             Transcript showCR:rs nextLine
         ].
         rs close.
     ] forkAt:7.

     'write ...'.
     [
         1 to:10 do:[:i |
             ws nextPutAll:'hello world '; nextPutAll:i printString; cr
         ].
         ws close.
     ] fork.

    "

    "Created: 29.2.1996 / 18:14:24 / cg"
    "Modified: 29.2.1996 / 18:25:02 / cg"
!

forWritingToFileDescriptor:aFileDescriptor
    "given a fileDescriptor, create an ExternalStream object
     to write to this fd. This may be used to wrap fd's
     as returned by user primitive code, or to wrap pipe-
     filedescriptors into externalStreams."

    ^ (self forFileDescriptor:aFileDescriptor mode:WriteMode) writeonly

    "
     the example below will probably fail (15 is a random FD):

     |s|

     s := ExternalStream forWritingToFileDescriptor:15.
     s binary.
     s nextPut:1.
    "

    "Created: 29.2.1996 / 18:14:43 / cg"
    "Modified: 29.2.1996 / 18:15:54 / cg"
!

makePTYPair
    "return an array with two streams - the first one is the master, 
     the second the slave of a ptym/ptys pair.
     This is much like a bidirectional pipe, but allows signals &
     control chars to be passed through the connection.
     This is needed to execute a shell in a view.
     This is the higher level equivalent of OperatingSystem>>makePTYPair
     (which returns an array of file-descriptors)."

     |ptyPair m s|

     ptyPair := OperatingSystem makePTYPair.
     ptyPair notNil ifTrue:[
         m := self forReadWriteToFileDescriptor:(ptyPair at:1).
         s := self forReadWriteToFileDescriptor:(ptyPair at:2).
         ptyPair at:1 put:m.
         ptyPair at:2 put:s.
         ^ ptyPair
     ].
     ^ nil

    "
     ExternalStream makePTYPair.
    "

    "Modified: 29.2.1996 / 18:28:36 / cg"
!

makePipe
    "return an array with two streams - the first one for reading, 
     the second for writing. 
     This is the higher level equivalent of OperatingSystem>>makePipe
     (which returns an array of file-descriptors)."

     |pipe rs ws|

     pipe := OperatingSystem makePipe.

     pipe notNil ifTrue:[
	 rs := self forReadingFromFileDescriptor:(pipe at:1).
	 ws := self forWritingToFileDescriptor:(pipe at:2).
	 ^ Array with:rs with:ws
     ].
     ^ nil

    "
     |pipe rs ws|

     pipe := ExternalStream makePipe.
     rs := pipe at:1.
     ws := pipe at:2.

     'read ...'.
     [
	 1 to:10 do:[:i |
	     Transcript showCR:rs nextLine
	 ].
	 rs close.
     ] forkAt:7.

     'write ...'.
     [
	 1 to:10 do:[:i |
	     ws nextPutAll:'hello world '; nextPutAll:i printString; cr
	 ].
	 ws close.
     ] fork.
    "

    "Modified: 29.2.1996 / 18:28:36 / cg"
!

new
    |newStream|

    newStream := self basicNew.
    newStream text; buffered:true; eolMode:DefaultEOLMode; clearEOF.
    ^ newStream
! !

!ExternalStream class methodsFor:'Signal constants'!

inaccessibleSignal
    "ST-80 compatibility: return openErrorSignal"

    ^ self openErrorSignal

    "Created: 2.7.1996 / 12:27:16 / stefan"
!

invalidModeSignal
    "return the signal raised when doing text-I/O with a binary stream
     or binary-I/O with a text stream"

    ^ InvalidModeSignal
!

invalidOperationSignal
    "return the signal raised when an unsupported or invalid
     I/O operation is attempted"

    ^ InvalidOperationSignal
!

invalidReadSignal
    "return the signal raised when reading from writeonly streams"

    ^ InvalidReadSignal
!

invalidWriteSignal
    "return the signal raised when writing to readonly streams"

    ^ InvalidWriteSignal
!

openErrorSignal
    "return the signal raised when a file open failed"

    ^ OpenErrorSignal
!

streamIOErrorSignal
    "return the signal raised when an I/O error occurs.
     (for example, a device-IO-error, or reading an NFS-dir,
     which is no longer available and has been mounted soft)"

    ^ StreamIOErrorSignal

    "Created: / 21.5.1998 / 16:32:55 / cg"
!

streamNotOpenSignal
    "return the signal raised on I/O with closed streams"

    ^ StreamNotOpenSignal
! !

!ExternalStream class methodsFor:'error handling'!

lastErrorNumber
    "return the errno of the last error"

    ^ LastErrorNumber

    "
     ExternalStream lastErrorNumber
    "
!

lastErrorString
    "return a message string describing the last error"

    ^ OperatingSystem errorTextForNumber:LastErrorNumber

    "
     ExternalStream lastErrorString
    "
! !

!ExternalStream methodsFor:'Squeak compatibility'!

readOnly
    "Squeak compatibility: make the stream readOnly"

    mode := #readonly

    "Modified: 20.10.1997 / 19:23:04 / cg"
    "Created: 20.10.1997 / 19:23:19 / cg"
! !

!ExternalStream methodsFor:'accessing'!

binary
    "switch to binary mode - default is text"

    binary := true
!

buffered:aBoolean
    "turn buffering on or off - default is on"

    buffered := aBoolean
!

contents
    "return the contents of the file from the current position up-to
     the end. If the stream is in binary mode, a ByteArray containing
     the byte values is returned.
     In text-mode, a collection of strings, each representing one line,
     is returned."

    |text l chunks sizes chunk byteCount cnt bytes offset|

    binary ifTrue:[
	"adding to a ByteArray produces quadratic time-space
	 behavior - therefore we allocate chunks, and concatenate them
	 at the end."

	chunks := OrderedCollection new.
	sizes := OrderedCollection new.
	byteCount := 0.
	[self atEnd] whileFalse:[
	    chunk := ByteArray uninitializedNew:4096.
	    cnt := self nextBytes:(chunk size) into:chunk.
	    cnt notNil ifTrue:[
		chunks add:chunk.
		sizes add:cnt.
		byteCount := byteCount + cnt
	    ]
	].

	"now, create one big ByteArray"
	bytes := ByteArray uninitializedNew:byteCount.
	offset := 1.
	1 to:chunks size do:[:index |
	    chunk := chunks at:index.
	    cnt := sizes at:index. 
	    bytes replaceFrom:offset to:(offset + cnt - 1) with:chunk.
	    offset := offset + cnt
	].
	^ bytes
    ].

    text := StringCollection new.
    [self atEnd] whileFalse:[
	l := self nextLine.
	l isNil ifTrue:[
	    ^ text
	].
	text add:l
    ].
    ^ text
!

contentsOfEntireFile
    "ST-80 compatibility: return contents as String"

    ^ self contents asString.

    "Modified: 3.7.1996 / 13:22:16 / stefan"
!

contentsSpecies
    "return the kind of object to be returned by sub-collection builders
     (such as upTo)"

    binary ifTrue:[
	^ ByteArray
    ].
    ^ String
!

eolMode:aSymbolOrNil
    "specify how end-of-line (EOL) is to be marked.
     The argument may be one of:
	#crlf         -> add a CR-NL, as in MSDOS
	#cr           -> add a CR, as in VMS
	#nl           -> add a NL, as in Unix
	anyOther      -> like #nl
    "

    eolMode := aSymbolOrNil
!

fileDescriptor
    "return the fileDescriptor of the receiver -
     notice: this one returns the underlying OSs fileDescriptor -
     this may not be available on all platforms (i.e. non unix systems)."

%{  /* NOCONTEXT */
#ifdef WIN32
    RETURN (nil);
#else
    FILEPOINTER f;
    OBJ fp;

    if ((fp = __INST(filePointer)) != nil) {
	f = __FILEVal(fp);
	RETURN ( __MKINT(fileno(f)) );
    }
#endif
%}.
    ^ self errorNotOpen
!

filePointer
    "return the filePointer of the receiver -
     notice: for portability stdio is used; this means you will get
     a FILE * - not a fileDescriptor. 
     (what you really get is a corresponding integer).
     You cannot do much with the returned value 
     - except passing it to a primitive, for example."

    ^ filePointer
!

lineEndTransparent
    eolMode := #cr
!

readonly
    "set access mode to readonly"

    mode := #readonly
!

readwrite
    "set access mode to readwrite"

    mode := #readwrite
!

text
    "switch to text mode - default is text"

    binary := false
!

useCRLF:aBoolean
    "turn on or off CRLF sending (instead of LF only) - default is off.
     This method is provided for backward compatibility - see #eolMode:
     which offers another choice."

    aBoolean ifTrue:[
	eolMode := #crlf
    ] ifFalse:[
	eolMode := #nl
    ].
!

writeonly
    "set access mode to writeonly"

    mode := #writeonly
! !

!ExternalStream methodsFor:'error handling'!

argumentMustBeCharacter
    "{ Pragma: +optSpace }"

    "report an error, that the argument must be a character"

    ^ self error:'argument must be a character'
!

argumentMustBeInteger
    "{ Pragma: +optSpace }"

    "report an error, that the argument must be an integer"

    ^ self error:'argument must be an integer'
!

argumentMustBeString
    "{ Pragma: +optSpace }"

    "report an error, that the argument must be a string"

    ^ self error:'argument must be a string'
!

errorBinary
    "{ Pragma: +optSpace }"

    "report an error, that the stream is in binary mode"

    ^ self invalidModeSignal
	raiseRequestWith:self
	     errorString:(self class name , ' is in binary mode')
		      in:thisContext sender
!

errorNotBinary
    "{ Pragma: +optSpace }"

    "report an error, that the stream is not in binary mode"

    ^ self invalidModeSignal
	raiseRequestWith:self
	     errorString:(self class name , ' is not in binary mode')
		      in:thisContext sender
!

errorNotBuffered
    "{ Pragma: +optSpace }"

    "report an error, that the stream is not in buffered mode"

    ^ StreamErrorSignal
	raiseRequestWith:self
	     errorString:(self class name , ' is unbuffered - operation not allowed')
		      in:thisContext sender
!

errorNotOpen
    "{ Pragma: +optSpace }"

    "report an error, that the stream has not been opened"

    ^ StreamNotOpenSignal
        raiseRequestWith:self
             errorString:(self class name , ' not open')
                      in:thisContext sender

    "Modified: / 19.5.1998 / 22:07:52 / cg"
!

errorOpen
    "{ Pragma: +optSpace }"

    "report an error, that the stream is already opened"

    ^ self openErrorSignal
	raiseRequestWith:self
	errorString:(self class name , ' is already open')
		 in:thisContext sender
!

errorReadOnly
    "{ Pragma: +optSpace }"

    "report an error, that the stream is a readOnly stream"

    ^ self invalidWriteSignal
	raiseRequestWith:self
	     errorString:(self class name , ' is readonly')
		      in:thisContext sender
!

errorUnsupportedOperation
    "{ Pragma: +optSpace }"

    "report an error, that some unsupported operation was attempted"

    ^ self invalidOperationSignal
	raiseRequestWith:self
	errorString:'unsupported operation'
		 in:thisContext sender
!

errorWriteOnly
    "{ Pragma: +optSpace }"

    "report an error, that the stream is a writeOnly stream"

    ^ self invalidReadSignal
	raiseRequestWith:self
	     errorString:(self class name , ' is writeonly')
		      in:thisContext sender
!

ioError
    "{ Pragma: +optSpace }"

    "report an error, that some I/O error occured.
     (for example, a device-IO-error, or reading an NFS-dir,
     which is no longer available and has been mounted soft)"

    ^ StreamIOErrorSignal
        raiseRequestWith:self
             errorString:('I/O error: ' , self lastErrorString)
                      in:thisContext sender

    "Modified: / 21.5.1998 / 16:33:13 / cg"
!

lastErrorNumber
    "return the last error"

    ^ lastErrorNumber
!

lastErrorString
    "return a message string describing the last error"

    (lastErrorNumber isNil or:[lastErrorNumber == 0]) ifTrue:[
	^ 'I/O error'
    ].
    ^ OperatingSystem errorTextForNumber:lastErrorNumber
!

openError
    "{ Pragma: +optSpace }"

    "report an error, that the open failed"

    ^ self class openErrorSignal
	raiseRequestWith:self
	     errorString:('error on open: ' , self lastErrorString)
		      in:thisContext sender

    "Modified: / 28.1.1998 / 14:37:42 / stefan"
!

readError
    "{ Pragma: +optSpace }"

    "report an error, that some read error occured"

    ^ ReadErrorSignal
	raiseRequestWith:self
	     errorString:('read error: ' , self lastErrorString)
		      in:thisContext sender
!

writeError
    "{ Pragma: +optSpace }"

    "report an error, that some write error occured"

    ^ WriteErrorSignal
	raiseRequestWith:self
	     errorString:('write error: ' , self lastErrorString)
		      in:thisContext sender
! !

!ExternalStream methodsFor:'instance release'!

closeFile
    "low level close - may be redefined in subclasses"

%{
    OBJ fp;

    if ((fp = __INST(filePointer)) != nil) {
	__INST(filePointer) = nil;
#if defined(WIN32) && defined(NO_STDIO)
	CloseHandle(__HANDLEVal(fp));
#else
	__BEGIN_INTERRUPTABLE__
	fclose(__FILEVal(fp));
	__END_INTERRUPTABLE__
#endif
    }
%}
!

disposed
    "some Stream has been collected - close the file if not already done"

    self closeFile
!

setFileDescriptor:anInteger mode:openMode
    "set the filePointer based upon a given fileDescriptor -
     notice: this one is based on the underlying OSs fileDescriptor -
     this may not be available on all platforms (i.e. non unix systems)."

%{
    FILEPOINTER f;
    OBJ fp;
    FILE *fdopen();

    if (__isSmallInteger(anInteger) &&
	__isString(openMode) &&
	(f = fdopen(__intVal(anInteger), __stringVal(openMode))) != 0
    ) {
	__PROTECT__(self);
	fp = __MKFILEPOINTER(f);
	__UNPROTECT__(self);
	__INST(filePointer) = fp; __STORE(self, fp);
	RETURN (self);
    }
%}.
    ^ self primitiveFailed
!

setFilePointer:something
    "set the filePointer to the given one;
     low level private & friend interface; may also be used to connect to some
     externally provided file handle."

    filePointer := something
!

shallowCopyForFinalization
    "return a copy for finalization-registration;
     since all we need at finalization time is the fileDescriptor,
     a cheaper copy is possible."

    ^ self class basicNew setFilePointer:filePointer
!

shutDown
    "close the stream - added for protocol compatibility with PipeStream.
     see comment there"

    filePointer isNil ifTrue:[^ self].
    Lobby unregister:self.
    self closeFile

    "Modified: 30.8.1996 / 00:39:21 / cg"
! !

!ExternalStream methodsFor:'line reading/writing'!

nextLine
    "read the next line (characters up to newline).
     Return a string containing those characters excluding the newline.
     If the previous-to-last character is a cr, this is also removed,
     so its possible to read alien (i.e. ms-dos) text as well.
     The line must be shorter than 16K characters - otherwise an error is signalled."

    |line|

%{  /* STACK:34000 */

    FILEPOINTER f;
    int len, ret;
    char buffer[32*1024];
    char *rslt, *nextPtr, *limit;
    int fd, ch;
    int _buffered;
    OBJ fp;
    int lineTooLong = 0;
    int cutOff = 0;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(writeonly))
	&& (__INST(binary) != true)
    ) {
	f = __FILEVal(fp);
	buffer[0] = '\0';

	_buffered = (__INST(buffered) == true);
	if (_buffered) {
	    __READING__(f);
	} 

	rslt = nextPtr = buffer;
	limit = buffer + sizeof(buffer) - 2;

	for (;;) {
	    __READBYTE__(ret, f, nextPtr, _buffered);
	    if (ret <= 0) {
		if (nextPtr == buffer)
		    rslt = NULL;
		if (ret == 0) {
		    __INST(hitEOF) = true;
		    break;
		} else {
		    __INST(lastErrorNumber) = __MKSMALLINT(errno);
		    goto err;
		}
	    }

	    if (*nextPtr == '\n') {
		cutOff = 1;
		*nextPtr = '\0';
		break;
	    }
	    if (*nextPtr == '\r') {
		char peekChar;

		/*
		 * peek ahead for a newLine ...
		 */
		__READBYTE__(ret, f, &peekChar, _buffered);
		if (ret <= 0) {
		    cutOff = 1;
		    *nextPtr = '\0';
		    if (ret == 0) {
			__INST(hitEOF) = true;
			break;
		    }
		    __INST(lastErrorNumber) = __MKSMALLINT(errno);
		    goto err;
		}

		if (peekChar == '\n') {
		    cutOff = 2;
		    *nextPtr = '\0';
		    break;
		}

		__UNGETC__(peekChar, f, _buffered);

		cutOff = 1;
		*nextPtr = '\0';
		break;
	    }

	    nextPtr++;
	    if (nextPtr >= limit) {
		*nextPtr = '\0';
		lineTooLong = 1;
		if (@global(InfoPrinting) == true) {
		    fprintf(stderr, "ExtStream [warning]: line truncated in nextLine\n");
		}
		break;
	    }
	}

	if (rslt != NULL) {
	    len = nextPtr-buffer;

	    if (__INST(position) != nil) {
		int __p;

		__p = __intVal(__INST(position)) + len + cutOff;
		__INST(position) = __MKSMALLINT(__p);
	    }
	    /* remove any EOL character */
	    if (len != 0) {
		if (buffer[len-1] == '\n') {
		    buffer[--len] = '\0';
		}
		if ((len != 0) && (buffer[len-1] == '\r')) {
		    buffer[--len] = '\0';
		}
	    }
	    line = __MKSTRING_L(buffer, len);
	    if (! lineTooLong) {
		RETURN ( line );
	    }
	}
    }
err: ;
%}.
    line notNil ifTrue:[
	"/ the line as read is longer than 32k characters (boy - what a line)
	"/ The exception could be handled by reading more and returning the
	"/ concatenation in your exception handler (the receiver and the partial
	"/ line are passed as parameter)

	^ ReadErrorSignal
	    raiseRequestWith:(Array with:self with:line)
		 errorString:('line too long read error')
    ].

    hitEOF ifTrue:[^ self pastEnd].
    lastErrorNumber notNil ifTrue:[^ self readError].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    ^ self errorBinary
!

nextPutLine:aString
    "write the characters in aString and append an end-of-Line marker
     (LF, CR or CRLF - depending in the setting of eolMode)"

%{
    FILEPOINTER f;
    int len, cnt, len1, _buffered;
    OBJ pos, fp;
    char *cp;
    int o_offs;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil) 
	&& (__INST(mode) != @symbol(readonly))
	&& (__INST(binary) != true)
	&& __isString(aString)
    ) {
	f = __FILEVal(fp);
	len = __stringSize(aString);

	if (_buffered = (__INST(buffered) == true)) {
	    __WRITING__(f)
	}
#ifdef BUGGY
	__WRITEBYTES__(cnt, f, __stringVal(aString), len, _buffered);
#else
	o_offs = (char *)__stringVal(aString)-(char *)aString;
	__WRITEBYTES_OBJ__(cnt, f, aString, o_offs, len, _buffered);
#endif
	if (cnt == len) {
	    OBJ mode = __INST(eolMode);

	    len1 = len;

	    if (mode == @symbol(cr)) {
		cp = "\r"; len = 1;
	    } else if (mode == @symbol(crlf)) {
		cp = "\r\n"; len = 2;
	    } else {
		cp = "\n"; len = 1;
	    }
	    __WRITEBYTES__(cnt, f, cp, len, _buffered);

	    if (cnt > 0) {
		pos = __INST(position);
		if (pos != nil) {
		    int __p;

		    __p = __intVal(pos) + len1 + cnt;
		    __INST(position) = __MKSMALLINT(__p);
		}
		RETURN ( self );
	    }
	}
	__INST(lastErrorNumber) = __MKSMALLINT(errno);
    }
%}.
    super nextPutLine:aString.
!

nextPutLinesFrom:aStream upToLineStartingWith:aStringOrNil
    "read from aStream up to and including a line starting with aStringOrNil
     and append all lines to self. 
     Can be used to copy/create large files or copy from a pipe/socket.

     If aStringOrNil is nil or not matched, copy preceeds to the end.
     (this allows for example to read a Socket and transfer the data quickly
      into a file - without creating zillions of temporary strings)"

    |srcFilePointer readError line|

    (mode == #readonly) ifTrue:[self errorReadOnly. ^ self].
    filePointer isNil ifTrue:[self errorNotOpen. ^ self].
    srcFilePointer := aStream filePointer.
    srcFilePointer isNil ifTrue:[aStream errorNotOpen. ^ self].

%{  /* STACK:2000 */

#ifndef NO_STDIO
    FILEPOINTER dst;
    FILEPOINTER src;
    char *matchString;
    int matchLen = 0;
    char buffer[1024];

    __INST(lastErrorNumber) = nil;
    if (__isSmallInteger(srcFilePointer)) {
	if ((aStringOrNil == nil) || __isString(aStringOrNil)) {
	    if (aStringOrNil != nil) {
		matchString = (char *) __stringVal(aStringOrNil);
		matchLen = __stringSize(aStringOrNil);
	    }
	    dst = __FILEVal(__INST(filePointer));
	    src = __FILEVal(srcFilePointer);
	    __BEGIN_INTERRUPTABLE__
	    errno = 0;

	    __WRITING__(dst)

	    for (;;) {
		if (fgets(buffer, sizeof(buffer)-1, src) == NULL) {
		    if (ferror(src)) {
			readError = __MKSMALLINT(errno);
			__END_INTERRUPTABLE__
			goto err;
		    }
		    break;
		}
		if (fputs(buffer, dst) == EOF) {
		    if (ferror(dst)) {
			__INST(lastErrorNumber) = __MKSMALLINT(errno);
			__END_INTERRUPTABLE__
			goto err;
		    }
		    break;
		}
#ifndef OLD
		if (__INST(buffered) == false) {
		    FFLUSH(dst);
		}
#endif
		if (matchLen) {
		    if (strncmp(matchString, buffer, matchLen) == 0) 
			break;
		}
	    }
	    __END_INTERRUPTABLE__
	    __INST(position) = nil;
	    RETURN (self);
	}
    }
err: ;
#endif /* ! NO_STDIO */
%}.
    readError ifTrue:[
	aStream setLastErrorNumber:readError.
	aStream readError.
	^ self
    ].
    lastErrorNumber notNil ifTrue:[self writeError. ^ self].
    buffered ifFalse:[self errorNotBuffered. ^ self].

    "
     argument error or unimplemented; try it linewise
    "
    [aStream atEnd] whileFalse:[
	line := aStream nextLine.
	line isNil ifTrue:[
	    ^ self.
	].
	self nextPutLine:line.
	(aStringOrNil notNil and:[line startsWith:aStringOrNil]) ifTrue:[
	    ^ self
	]
    ].
!

peekForLineStartingWith:aString
    "read ahead for next line starting with aString;
     return the line-string if found, or nil if EOF is encountered.
     If matched, not advance position behond that line
     i.e. nextLine will read the matched line.
     If not matched, reposition to original position for firther reading."

    |firstPos lastPos line|

    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    binary ifTrue:[^ self errorBinary].
    buffered ifFalse:[^ self errorNotBuffered].

%{  /* STACK: 2000 */
#ifndef NO_STDIO

    FILEPOINTER f;
    int l;
    char buffer[1024];
    char *cp;
    char *matchString;
    int gotFirst = 0;
#ifdef __VMS__
    fpos_t firstpos, lastpos;
#   define FTELL(__f__, __pos__)        fgetpos(__f__, &(__pos__))
#   define FSEEK(__f__, __pos__)        fsetpos(__f__, &(__pos__))
#else
    long firstpos, lastpos;
#   define FTELL(__f__, __pos__)        __pos__ = ftell(__f__)
#   define FSEEK(__f__, __pos__)        fseek(__f__, __pos__, SEEK_SET)
#endif

    __INST(lastErrorNumber) = nil;
    if (__isString(aString)) {
	matchString = (char *) __stringVal(aString);
	l = __stringSize(aString);

	f = __FILEVal(__INST(filePointer));
	__READING__(f)

	for (;;) {
	    FTELL(f, lastpos);
	    if (!gotFirst) {
		firstpos = lastpos;
		gotFirst = 1;
	    }

	    __BEGIN_INTERRUPTABLE__
	    do {
		cp = fgets(buffer, sizeof(buffer)-1, f);
	    } while ((cp == NULL) && ferror(f) && (errno == EINTR) && (clearerr(f), 1));
	    buffer[sizeof(buffer)-1] = '\0';
	    __END_INTERRUPTABLE__

	    if (cp == NULL) {
		if (ferror(f)) {
		    __INST(lastErrorNumber) = __MKSMALLINT(errno);
		    goto err;
		} else {
		    FSEEK(f, firstpos);
		    RETURN (nil);
		}
	    }
	    if (strncmp(cp, matchString, l) == 0) {
		FSEEK(f, lastpos);
		break;
	    }
	}
	/* remove EOL character */
	cp = buffer;
	while (*cp && (*cp != '\n') && (*cp != '\r')) cp++;
	*cp = '\0';
	RETURN ( __MKSTRING(buffer) );
    }
err: ;

# undef FTELL
# undef FSEEK

#endif
%}.
    lastErrorNumber notNil ifTrue:[^ self readError].

    "/ try using generic code ...

    firstPos := self position.
    [self atEnd] whileFalse:[
	lastPos := self position.
	line := self nextLine.
	line isNil ifTrue:[
	    self position:firstPos.
	    ^ nil
	].
	(line startsWith:aString) ifTrue:[
	    self position:lastPos.
	    ^ line
	]
    ].
    self position:firstPos.
    ^ nil
!

peekForLineStartingWithAny:aCollectionOfStrings
    "read ahead for next line starting with any of aCollectionOfStrings;
     return the index in aCollection if found, nil otherwise..
     If no match, do not change position; otherwise advance right before the
     matched line so that nextLine will return this line."

    |line startPos linePos index|

    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    binary ifTrue:[^ self errorBinary].

    startPos := self position.
    [self atEnd] whileFalse:[
	linePos := self position.
	line := self nextLine.
	line notNil ifTrue:[
	    index := 1.
	    aCollectionOfStrings do:[:prefix |
		(line startsWith:prefix) ifTrue:[
		    self position:linePos.
		    ^ index
		].
		index := index + 1
	    ]
	]
    ].
    self position:startPos.
    ^ nil
! !

!ExternalStream methodsFor:'misc functions'!

async:aBoolean
    "set/clear the async attribute - if set, the availability of data on 
     the receiver will trigger an ioInterrupt.
     If cleared (which is the default) no special notification is made.
     Notice: 
	not every OS supports this 
	- check with OS>>supportsIOInterrupts before using"

    |fd|

    filePointer isNil ifTrue:[^ self errorNotOpen].

    OperatingSystem supportsIOInterrupts ifFalse:[
	^ self errorUnsupportedOperation
    ].

    fd := self fileDescriptor.
    aBoolean ifTrue:[
	^ OperatingSystem enableIOInterruptsOn:fd
    ].
    ^ OperatingSystem disableIOInterruptsOn:fd

    "Modified: 11.1.1997 / 17:50:21 / cg"
!

backStep
    "step back one element -
     redefined, since position is redefined here"

    self position:(self position - 1)
!

blocking:aBoolean
    "set/clear the blocking attribute - if set (which is the default)
     a read (using next) on the receiver will block until data is available.
     If cleared, a read operation will immediately return with a value of
     nil.
     Turning off blocking is useful when reading from PipeStreams
     or Sockets, and the amount of data to be read is not known
     in advance. However, the data must then be read using #nextBytes:
     methods, in order to avoid other (pastEnd) exceptions."

    filePointer isNil ifTrue:[^ self errorNotOpen].
    ^ OperatingSystem setBlocking:aBoolean on:(self fileDescriptor)

    "Modified: / 11.1.1997 / 17:48:01 / cg"
    "Modified: / 15.1.1998 / 11:49:48 / stefan"
!

close
    "close the stream - tell operating system"

    filePointer isNil ifTrue:[^ self].
    Lobby unregister:self.
    self closeFile.
    filePointer := nil
!

create
    "create the stream
     - this must be redefined in subclass"

    ^ self subclassResponsibility
!

ioctl:ioctlNumber
    "to provide a simple ioctl facility - an ioctl is performed
     on the underlying file; no arguments are passed."

    ^ self ioctl:ioctlNumber with:nil
!

ioctl:ioctlNumber with:arg
    "to provide a simple ioctl facility - an ioctl is performed
     on the underlying file; the argument is passed as argument.
     This is not used by ST/X, but provided for special situations 
     - for example, to control proprietrary I/O devices.

     Since the type of the argument depends on the ioctl being
     performed, different arg types are allowed here.
     If the argument is nil, an ioctl without argument is performed.
     If the argument is an integral number, its directly passed; 
     if its a kind of ByteArray (ByteArray, String or Structure),
     or external data (ExternalBytes or ExternalAddress),
     a pointer to the data is passed. 
     This allows performing most ioctls 
     - however, it might be tricky to setup the buffer.
     Be careful in what you pass - ST/X cannot validate its correctness."

%{
#if !defined(MSDOS_LIKE)
# if !defined(__openVMS__)
    FILEPOINTER f;
    int ret, ioNum;
    OBJ fp;
    INT ioArg;

    __INST(lastErrorNumber) = nil;
    if ((fp = __INST(filePointer)) != nil) {
	if (__isSmallInteger(ioctlNumber) 
	 && (__isSmallInteger(arg) 
	     || (arg == nil)
	     || __isBytes(arg)
	     || __isExternalBytes(arg)
	     || __isExternalAddress(arg))) {
	    f = __FILEVal(fp);
	    ioNum = __intVal(ioctlNumber);

	    __BEGIN_INTERRUPTABLE__
	    do {
		errno = 0;
		if (arg == nil) {
		    ioArg = 0;
		} else if (__isSmallInteger(arg)) {
		    ioArg = __intVal(arg);
		} else if (__isExternalBytes(arg)) {
		    ioArg = (INT)(__externalBytesAddress(arg));
		} else if (__isExternalAddress(arg)) {
		    ioArg = (INT)(__externalAddressVal(arg));
		} else {
		    ioArg = (INT)(__ByteArrayInstPtr(arg)->ba_element);
		}
		ret = ioctl(fileno(f), ioNum, ioArg);
	    } while ((ret < 0) && (errno == EINTR));
	    __END_INTERRUPTABLE__

	    if (ret >= 0) {
		RETURN ( __MKSMALLINT(ret) );
	    }
	    __INST(position) = nil;
	    __INST(lastErrorNumber) = __MKSMALLINT(errno);
	}
    }
# endif
#endif
%}.
    lastErrorNumber notNil ifTrue:[^ self ioError].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    "
     argument is not an integer
    "
    (ioctlNumber isMemberOf:SmallInteger) ifFalse:[^ self primitiveFailed].
    "
     the system does not support ioctl (MSDOS or VMS)
    "
    ^ self errorUnsupportedOperation
!

open
    "open the stream
     - this must be redefined in subclass"

    ^ self subclassResponsibility
!

position
    "return the position
     - this must be redefined in subclass"

    ^ self subclassResponsibility
!

position:anInteger
    "set the position
     - this must be redefined in subclass"

    ^ self subclassResponsibility
!

reset
    "set the read position to the beginning of the collection"

    self position:"0" 1
!

setToEnd
    "redefined since it must be implemented differently"

    ^ self subclassResponsibility
!

truncateTo:newSize
    "truncate the underlying OS file to newSize.
     Warning: this may not be implemented on all platforms."

%{
#ifdef HAS_FTRUNCATE
    FILEPOINTER f;
    OBJ fp;

    if (((fp = __INST(filePointer)) != nil)
     && (__INST(mode) != @symbol(readonly))) {
	if (__isSmallInteger(newSize)) {
	    f = __FILEVal(fp);

	    if (__INST(buffered) == true) {
		__READING__(f)
		FFLUSH(f);
		OPT_FSEEK(f, 0L, SEEK_END); /* needed in stdio */
	    }
	    ftruncate(fileno(f), __intVal(newSize));
	    RETURN (self);
	}
    }
#endif
%}.
    filePointer isNil ifTrue:[self errorNotOpen. ^ self].
    (mode == #readonly) ifTrue:[self errorReadOnly. ^ self].
    self errorUnsupportedOperation

    "
     |s|

     s := 'test' asFilename writeStream.
     s next:1000 put:$a.
     s truncateTo:100.
     s close.

     ('test' asFilename fileSize) printNL
    "
! !

!ExternalStream methodsFor:'non homogenous reading'!

nextAvailable:count
    "return the next count elements of the stream as aCollection.
     If the stream reaches the end before count elements have been read,
     return what is available. (i.e. a shorter collection).
     The type of collection is specified in #contentsSpecies."

    |buffer n
     cnt  "{ Class: SmallInteger }"|

    binary ifTrue:[
        buffer := ByteArray new:count
    ] ifFalse:[
        buffer := String new:count
    ].
    n := self nextAvailableBytes:count into:buffer startingAt:1.
    n == 0 ifTrue:[^ nil].

    n ~~ count ifTrue:[
        ^ buffer copyTo:n
    ].
    ^ buffer.

    "
     (ReadStream on:#(1 2 3 4 5)) nextAvailable:3 
     (ReadStream on:#(1 2 3 4 5)) nextAvailable:10 
     (ReadStream on:'hello') nextAvailable:3
     (ReadStream on:'hello') nextAvailable:10 
    "

    "Modified: / 16.6.1998 / 15:52:41 / cg"

!

nextAvailableBytes:count into:anObject startingAt:start
    "read the next count bytes into an object and return the number of
     bytes read or the number of bytes read, if EOF is encountered before,
     or no more bytes are available for reading (from the pipe/socket).

     If the receiver is some socket/pipe-like stream, an exception
     is raised if the connection is broken.

     Notice, that in contrast to other methods,
     this does NOT return nil on EOF, but the actual count.
     Thus allowing read of partial blocks.

     The object must have non-pointer indexed instvars 
     (i.e. it must be a ByteArray, String, Float- or DoubleArray).
     If anObject is a string or byteArray and reused, this provides the
     fastest possible physical I/O (since no new objects are allocated).

     Use with care - non object oriented I/O.
     Warning: in general, you cannot use this method to pass data from other 
     architectures since it does not care for byte order or float representation."

%{
    FILEPOINTER f;
    int cnt, offs, ret, _buffered;
    int objSize, nInstVars, nInstBytes;
    char *cp;
    char *extPtr;
    OBJ pos, fp, oClass;
    int o_offs;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
        && (__INST(mode) != @symbol(writeonly))
        && __bothSmallInteger(count, start)
    ) {
        f = __FILEVal(fp);

        cnt = __intVal(count);
        offs = __intVal(start) - 1;

        oClass = __Class(anObject);
        if (oClass == ExternalBytes) {
            OBJ sz;

            nInstBytes = 0;
            extPtr = (char *)(__externalBytesAddress(anObject));
            sz = __externalBytesSize(anObject);
            if (__isSmallInteger(sz)) {
                objSize = __intVal(sz);
            } else {
                objSize = 0; /* unknown */
            }
        } else {
            switch (__intVal(__ClassInstPtr(oClass)->c_flags) & ARRAYMASK) {
                case BYTEARRAY:
                case WORDARRAY:
                case LONGARRAY:
                case SWORDARRAY:
                case SLONGARRAY:
                case FLOATARRAY:
                case DOUBLEARRAY:
                    break;
                default:
                    goto bad;
            }
            extPtr = (char *)0;
            nInstVars = __intVal(__ClassInstPtr(oClass)->c_ninstvars);
            nInstBytes = OHDR_SIZE + __OBJS2BYTES__(nInstVars);
            objSize = __Size(anObject) - nInstBytes;
        }

        if ((offs >= 0) && (cnt >= 0) && (objSize >= (cnt + offs))) {
            _buffered = (__INST(buffered) == true);
            if (_buffered) {
                __READING__(f);
            }

            if (extPtr) {
                __READAVAILBYTES__(ret, f, extPtr+offs, cnt, _buffered);
            } else {
                /*
                 * on interrupt, anObject may be moved to another location.
                 * So we pass (char *)__InstPtr(anObject) + nInstBytes + offs to the macro __READ_BYTES__,
                 * to get a new address.
                 */
                offs += nInstBytes;
                __READAVAILBYTES_OBJ__(ret, f, anObject, offs, cnt, _buffered);
            }
            if (ret > 0) {
                pos = __INST(position);
                if (pos != nil) {
                    __INST(position) = __MKSMALLINT(__intVal(pos) + ret);
                }
                RETURN (__MKSMALLINT(ret));
            }
            if (ret == 0) { 
                __INST(hitEOF) = true;
            } else /* ret < 0 */ {
                __INST(position) = nil;
                __INST(lastErrorNumber) = __MKSMALLINT(errno);
            }
        }
    }
bad: ;
%}.
    (hitEOF and:[self pastEnd isNil]) ifTrue:[^ 0].
    lastErrorNumber notNil ifTrue:[^ self readError].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    "
     count not integer or arg not bit-like (String, ByteArray etc)
    "
    ^ self primitiveFailed
!

nextByte
    "read the next byte and return it as an Integer; return nil on error.
     This is allowed in both text and binary modes, always returning the
     bytes binary value as an integer in 0..255."

%{
    FILEPOINTER f;
    unsigned char byte;
    int ret, _buffered;
    OBJ fp;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
     && (__INST(mode) != @symbol(writeonly))) {
	f = __FILEVal(fp);

	_buffered = (__INST(buffered) == true);
	if (_buffered) {
	    __READING__(f)
	}
	__READBYTE__(ret, f, &byte, _buffered);
	if (ret > 0) {
	    if (__INST(position) != nil)
		__INST(position) = __MKSMALLINT(__intVal(__INST(position)) + 1);
	    RETURN (__MKSMALLINT(byte));
	}

	if (ret == 0) {
	    __INST(hitEOF) = true;
	} else /* ret < 0 */ {
	    __INST(position) = nil;
	    __INST(lastErrorNumber) = __MKSMALLINT(errno);
	}
    }
%}.
    hitEOF ifTrue:[^ self pastEnd].
    lastErrorNumber notNil ifTrue:[^ self readError].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    ^ self errorWriteOnly
!

nextBytes:count into:anObject startingAt:start
    "read the next count bytes into an object and return the number of
     bytes read or the number of bytes read, if EOF is encountered before.
     If the receiver is some socket/pipe-like stream, an exception
     is raised if the connection is broken.

     Warning: if used with a pipe/socket, this blocks until the requested number
     of bytes have been read. See #nextAvailableBytes:into:startingAt:
     to only read whats there.

     Notice, that in contrast to other methods,
     this does NOT return nil on EOF, but the actual count.
     Thus allowing read of partial blocks.

     The object must have non-pointer indexed instvars 
     (i.e. it must be a ByteArray, String, Float- or DoubleArray),
     or an externalBytes object (with known size).
     If anObject is a string or byteArray and reused, this provides the
     fastest possible physical I/O (since no new objects are allocated).

     Use with care - non object oriented I/O.
     Warning: in general, you cannot use this method to pass data from other 
     architectures (unless you prepared the buffer with care),
     since it does not care for byte order or float representation."

%{
    FILEPOINTER f;
    int cnt, offs, ret, _buffered;
    int objSize, nInstVars, nInstBytes;
    char *extPtr;
    OBJ pos, fp, oClass;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(writeonly))
	&& __bothSmallInteger(count, start)
    ) {
	f = __FILEVal(fp);

	cnt = __intVal(count);
	offs = __intVal(start) - 1;

	oClass = __Class(anObject);
	if (oClass == ExternalBytes) {
	    OBJ sz;

	    nInstBytes = 0;
	    extPtr = (char *)(__externalBytesAddress(anObject));
	    sz = __externalBytesSize(anObject);
	    if (__isSmallInteger(sz)) {
		objSize = __intVal(sz);
	    } else {
		objSize = 0; /* unknown */
	    }
	} else {
	    switch (__intVal(__ClassInstPtr(oClass)->c_flags) & ARRAYMASK) {
		case BYTEARRAY:
		case WORDARRAY:
		case LONGARRAY:
		case SWORDARRAY:
		case SLONGARRAY:
		case FLOATARRAY:
		case DOUBLEARRAY:
		    break;
		default:
		    goto bad;
	    }
	    extPtr = (char *)0;
	    nInstVars = __intVal(__ClassInstPtr(oClass)->c_ninstvars);
	    nInstBytes = OHDR_SIZE + __OBJS2BYTES__(nInstVars);
	    objSize = __Size(anObject) - nInstBytes;
	}
	if ((offs >= 0) && (cnt >= 0) && (objSize >= (cnt + offs))) {
	    _buffered = (__INST(buffered) == true);
	    if (_buffered) {
		__READING__(f);
	    }

	    if (extPtr) {
		__READBYTES__(ret, f, extPtr+offs, cnt, _buffered);
	    } else {
		/*
		 * on interrupt, anObject may be moved to another location.
		 * So we pass anObject, and the offset to the __READBYTES_OBJ__ macro.
		 */
		offs += nInstBytes;
		__READBYTES_OBJ__(ret, f, anObject, offs, cnt, _buffered);
	    }

	    if (ret > 0) {
		pos = __INST(position);
		if (pos != nil) {
		    __INST(position) = __MKSMALLINT(__intVal(pos) + ret);
		}
		RETURN (__MKSMALLINT(ret));
	    }
	    if (ret == 0) { 
		__INST(hitEOF) = true;
	    } else /* ret < 0 */ {
		__INST(position) = nil;
		__INST(lastErrorNumber) = __MKSMALLINT(errno);
	    }
	}
    }
bad: ;
%}.
    (hitEOF and:[self pastEnd isNil]) ifTrue:[^ 0].
    lastErrorNumber notNil ifTrue:[^ self readError].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    "
     count not integer or arg not bit-like (String, ByteArray etc)
    "
    ^ self primitiveFailed
!

nextLong
    "Read four bytes (msb-first) and return the value as a 32-bit signed Integer.
     The returned value may be a LargeInteger.
     (msb-first for compatibility with other smalltalks)"

    ^ self nextUnsignedLongMSB:true
!

nextLongMSB:msbFlag
    "Read four bytes and return the value as a 32-bit signed Integer, 
     which may be a LargeInteger.
     If msbFlag is true, value is read with most-significant byte first, 
     otherwise least-significant byte comes first.
     A nil is returned, if EOF is hit before all 4 bytes have been read.
     Works in both binary and text modes."

%{
    OBJ fp;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(writeonly))
    ) {
	FILEPOINTER f;
	int ret;
	int value;
	char buffer[4];

	f = __FILEVal(fp);
	__READBYTES__(ret, f, buffer, 4, __INST(buffered) == true);

	if (ret == 4) {
	    if (__INST(position) != nil) {
		__INST(position) = __MKSMALLINT(__intVal(__INST(position)) + 4);
	    }
	    if (msbFlag == true) {
		value = (buffer[0] & 0xFF);
		value = (value << 8) | (buffer[1] & 0xFF);
		value = (value << 8) | (buffer[2] & 0xFF);
		value = (value << 8) | (buffer[3] & 0xFF);
	    } else {
		value = (buffer[3] & 0xFF);
		value = (value << 8) | (buffer[2] & 0xFF);
		value = (value << 8) | (buffer[1] & 0xFF);
		value = (value << 8) | (buffer[0] & 0xFF);
	    }
	    if ((value >= _MIN_INT) && (value <= _MAX_INT)) {
		RETURN ( __MKSMALLINT(value));
	    }
	    RETURN ( __MKLARGEINT(value) );
	}

	if (ret < 0) {
	    __INST(position) = nil;
	    __INST(lastErrorNumber) = __MKSMALLINT(errno);
	} else /* ret == 0 */ {
	    __INST(hitEOF) = true;
	}
    }
%}.
    hitEOF ifTrue:[^ self pastEnd].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    ^ self readError.

!

nextShortMSB:msbFlag
    "Read two bytes and return the value as a 16-bit signed Integer.
     If msbFlag is true, value is read with most-significant byte first, 
     otherwise least-significant byte comes first.
     A nil is returned if EOF is reached (also when EOF is hit after the first byte).
     Works in both binary and text modes."

%{
    OBJ fp;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(writeonly))
    ) {
	FILEPOINTER f;
	int ret;
	short value;
	char buffer[2];

	f = __FILEVal(fp);
	__READBYTES__(ret, f, buffer, 2, __INST(buffered) == true);

	if (ret == 2) {
	    if (__INST(position) != nil) {
		__INST(position) = __MKSMALLINT(__intVal(__INST(position)) + 2);
	    }
	    if (msbFlag == true) {
		value = ((buffer[0] & 0xFF) << 8) | (buffer[1] & 0xFF);
	    } else {
		value = ((buffer[1] & 0xFF) << 8) | (buffer[0] & 0xFF);
	    }
	    RETURN (__MKSMALLINT(value));
	}

	if (ret < 0) {
	    __INST(position) = nil;
	    __INST(lastErrorNumber) = __MKSMALLINT(errno);
	} else /* ret == 0 */ {
	    __INST(hitEOF) = true;
	}
    }
%}.
    hitEOF ifTrue:[^ self pastEnd].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    ^ self readError.
!

nextUnsignedLongMSB:msbFlag
    "Read four bytes and return the value as a 32-bit unsigned Integer, which may be
     a LargeInteger.
     If msbFlag is true, value is read with most-significant byte first, otherwise
     least-significant byte comes first.
     A nil is returned, if endOfFile occurs before all 4 bytes have been read.
     Works in both binary and text modes."

%{
    OBJ fp;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
        && (__INST(mode) != @symbol(writeonly))
    ) {
        FILEPOINTER f;
        int ret;
        unsigned INT value;
        unsigned char buffer[4];

        f = __FILEVal(fp);
        __READBYTES__(ret, f, buffer, 4, __INST(buffered) == true);

        if (ret == 4) {
            if (__INST(position) != nil) {
                __INST(position) = __MKSMALLINT(__intVal(__INST(position)) + 4);
            }
            if (msbFlag == true) {
                value = (buffer[0] & 0xFF);
                value = (value << 8) | (buffer[1] & 0xFF);
                value = (value << 8) | (buffer[2] & 0xFF);
                value = (value << 8) | (buffer[3] & 0xFF);
            } else {
                value = (buffer[3] & 0xFF);
                value = (value << 8) | (buffer[2] & 0xFF);
                value = (value << 8) | (buffer[1] & 0xFF);
                value = (value << 8) | (buffer[0] & 0xFF);
            }
#ifdef alpha64
            value &= 0xFFFFFFFF;
#endif
            if (value <= _MAX_INT) {
                RETURN ( __MKSMALLINT(value));
            }
            RETURN ( __MKULARGEINT(value) );
        }

        if (ret < 0) {
            __INST(position) = nil;
            __INST(lastErrorNumber) = __MKSMALLINT(errno);
        } else /* ret == 0 */ {
            __INST(hitEOF) = true;
        }
    }
%}.
    hitEOF ifTrue:[^ self pastEnd].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    ^ self readError.
!

nextUnsignedShortMSB:msbFlag
    "Read two bytes and return the value as a 16-bit unsigned Integer.
     If msbFlag is true, value is read with most-significant byte first, 
     otherwise least-significant byte comes first.
     A nil is returned if EOF is reached (also when EOF is hit after the first byte).
     Works in both binary and text modes."

%{
    OBJ fp;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(writeonly))
    ) {
	FILEPOINTER f;
	int ret;
	unsigned int value;
	char buffer[2];

	f = __FILEVal(fp);
	__READBYTES__(ret, f, buffer, 2, __INST(buffered) == true);

	if (ret == 2) {
	    if (__INST(position) != nil) {
		__INST(position) = __MKSMALLINT(__intVal(__INST(position)) + 2);
	    }
	    if (msbFlag == true) {
		value = ((buffer[0] & 0xFF) << 8) | (buffer[1] & 0xFF);
	    } else {
		value = ((buffer[1] & 0xFF) << 8) | (buffer[0] & 0xFF);
	    }
	    RETURN (__MKSMALLINT(value));
	}

	if (ret < 0) {
	    __INST(position) = nil;
	    __INST(lastErrorNumber) = __MKSMALLINT(errno);
	} else /* ret == 0 */ {
	    __INST(hitEOF) = true;
	}
    }
%}.
    hitEOF ifTrue:[^ self pastEnd].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    ^ self readError.
!

nextWord
    "in text-mode:
	 read the alphaNumeric next word (i.e. up to non letter-or-digit).
	 return a string containing those characters.
     in binary-mode:
	 read two bytes (msb-first) and return the value as a 16-bit 
	 unsigned Integer (for compatibility with other smalltalks)"

    binary ifTrue:[
	^ self nextUnsignedShortMSB:true
    ].
    ^ self nextAlphaNumericWord
! !

!ExternalStream methodsFor:'non homogenous writing'!

nextPutByte:aByteValue
    "write a byte.
     Works in both binary and text modes."

%{
    FILEPOINTER f;
    char c;
    OBJ pos, fp;
    int cnt, _buffered;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(readonly))
	&& __isSmallInteger(aByteValue)

    ) {
	c = __intVal(aByteValue);
	f = __FILEVal(fp);
	if (_buffered = (__INST(buffered) == true)) {
	    __WRITING__(f)
	} 
	__WRITEBYTE__(cnt, f, &c, _buffered);
	if (cnt == 1) {
	    pos = __INST(position);
	    if (pos != nil)
		__INST(position) = __MKSMALLINT(__intVal(pos) + 1);
	    RETURN (self);
	}
	__INST(lastErrorNumber) = __MKSMALLINT(errno);
    }
%}.
    filePointer isNil ifTrue:[self errorNotOpen. ^ self].
    (mode == #readonly) ifTrue:[self errorReadOnly. ^ self].
    self writeError.
!

nextPutBytes:count from:anObject startingAt:start
    "write count bytes from an object starting at index start.
     return the number of bytes written - which could be 0.
     The object must have non-pointer indexed instvars 
     (i.e. be a ByteArray, String, Float- or DoubleArray),
     or an externalBytes object (with known size).

     Use with care - non object oriented i/o.
     Warning: in general, you cannot use this method to pass data to other 
     architectures (unless you prepared the buffer with care),
     since it does not care for byte order or float representation."

%{
    FILEPOINTER f;
    int cnt, len, offs, ret;
    int objSize, nInstVars, nInstBytes, _buffered;
    char *extPtr;
    OBJ oClass, pos, fp;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(readonly))
	&& __bothSmallInteger(count, start)
    ) {
	len = __intVal(count);
	offs = __intVal(start) - 1;
	f = __FILEVal(fp);

	oClass = __Class(anObject);
	if (oClass == ExternalBytes) {
	    OBJ sz;

	    nInstBytes = 0;
	    extPtr = (char *)__externalBytesAddress(anObject);
	    sz = __externalBytesSize(anObject);
	    if (__isSmallInteger(sz)) {
		objSize = __intVal(sz);
	    } else {
		objSize = 0; /* unknown */
	    }
	} else {
	    switch (__intVal(__ClassInstPtr(oClass)->c_flags) & ARRAYMASK) {
		case BYTEARRAY:
		case WORDARRAY:
		case LONGARRAY:
		case SWORDARRAY:
		case SLONGARRAY:
		case FLOATARRAY:
		case DOUBLEARRAY:
		    break;
		default:
		    goto bad;
	    }
	    extPtr = (char *)0;
	    nInstVars = __intVal(__ClassInstPtr(oClass)->c_ninstvars);
	    nInstBytes = OHDR_SIZE + __OBJS2BYTES__(nInstVars);
	    objSize = __Size(anObject) - nInstBytes;
	}
	if ( (offs >= 0) && (len >= 0) && (objSize >= (len + offs)) ) {
	    if (_buffered = (__INST(buffered) == true)) {
		__WRITING__(f)
	    }

	    if (extPtr) {
		__WRITEBYTES__(cnt, f, extPtr+offs, len, _buffered); 
	    } else {
		/*
		 * on interrupt, anObject may be moved to another location.
		 * So we pass anObject, and the offset to the __WRITEBYTES_OBJ__ macro.
		 */
		offs += nInstBytes;
		__WRITEBYTES_OBJ__(cnt, f, anObject, offs, len, _buffered); 
	    }

	    if (cnt >= 0) {
		pos = __INST(position);
		if (pos != nil)
		    __INST(position) = __MKSMALLINT(__intVal(pos) + cnt);
		RETURN ( __MKSMALLINT(cnt) );
	    }
	    __INST(lastErrorNumber) = __MKSMALLINT(errno);
	}
    }
bad: ;
%}.
    lastErrorNumber notNil ifTrue:[^ self writeError].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #readonly) ifTrue:[^ self errorReadOnly].
    ^ self primitiveFailed
!

nextPutLong:aNumber MSB:msbFlag
    "Write the argument, aNumber as a long (four bytes). If msbFlag is
     true, data is written most-significant byte first; otherwise least
     first.
     Works in both binary and text modes."

%{
    int num;
    char bytes[4];
    FILEPOINTER f;
    int cnt, _buffered;
    OBJ fp, pos;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(readonly))
	&& __isSmallInteger(aNumber)
    ) {
	num = __intVal(aNumber);
	if (msbFlag == true) {
	    bytes[0] = (num >> 24) & 0xFF;
	    bytes[1] = (num >> 16) & 0xFF;
	    bytes[2] = (num >> 8) & 0xFF;
	    bytes[3] = num & 0xFF;
	} else {
	    bytes[3] = (num >> 24) & 0xFF;
	    bytes[2] = (num >> 16) & 0xFF;
	    bytes[1] = (num >> 8) & 0xFF;
	    bytes[0] = num & 0xFF;
	}

	f = __FILEVal(fp);
	if (_buffered = (__INST(buffered) == true)) {
	    __WRITING__(f)
	} 
	__WRITEBYTES__(cnt, f, bytes, 4, _buffered);

	if (cnt == 4) {
	    if ((pos = __INST(position)) != nil) {
		__INST(position) = __MKSMALLINT(__intVal(pos) + 4);
	    }
	    RETURN ( self );
	}
	__INST(lastErrorNumber) = __MKSMALLINT(errno);
    }
%}.
    filePointer isNil ifTrue:[self errorNotOpen. ^ self].
    (mode == #readonly) ifTrue:[self errorReadOnly. ^ self].
    lastErrorNumber notNil ifTrue:[self writeError. ^ self].

    aNumber isInteger ifTrue:[
	^ super nextPutLong:aNumber MSB:msbFlag
    ].
    self argumentMustBeInteger
!

nextPutShort:aNumber MSB:msbFlag
    "Write the argument, aNumber as a short (two bytes). If msbFlag is
     true, data is written most-significant byte first; otherwise least
     first.
     Works in both binary and text modes."

%{
    int num;
    char bytes[2];
    FILEPOINTER f;
    int cnt, _buffered;
    OBJ fp, pos;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(readonly))
	&& __isSmallInteger(aNumber)
    ) {
	num = __intVal(aNumber);
	if (msbFlag == true) {
	    bytes[0] = (num >> 8) & 0xFF;
	    bytes[1] = num & 0xFF;
	} else {
	    bytes[1] = (num >> 8) & 0xFF;
	    bytes[0] = num & 0xFF;
	}

	f = __FILEVal(fp);
	if (_buffered = (__INST(buffered) == true)) {
	    __WRITING__(f)
	} 
	__WRITEBYTES__(cnt, f, bytes, 2, _buffered);

	if (cnt == 2) {
	    if ((pos = __INST(position)) != nil) {
		__INST(position) = __MKSMALLINT(__intVal(pos) + 2);
	    }
	    RETURN ( self );
	}
	__INST(lastErrorNumber) = __MKSMALLINT(errno);
    }
%}.
    lastErrorNumber notNil ifTrue:[^ self writeError. ^ self].
    filePointer isNil ifTrue:[self errorNotOpen. ^ self].
    (mode == #readonly) ifTrue:[self errorReadOnly. ^ self].
    self argumentMustBeInteger
! !

!ExternalStream methodsFor:'private'!

clearEOF
    hitEOF := false
!

connectTo:aFileDescriptor withMode:openmode
    "connect a fileDescriptor; openmode is the string defining the way to open.
     This can be used to connect an extrnally provided fileDescriptor (from
     primitive code) or a pipeFileDescriptor (as returned by makePipe) to
     a Stream object. 
     The openMode ('r', 'w' etc.) must match the mode in which
     the fileDescriptor was originally opened (otherwise i/o errors will be reported later)."

    |retVal|

    filePointer notNil ifTrue:[^ self errorOpen].
%{
    FILEPOINTER f;
    OBJ fp;
    FILE *fdopen();

    if (__isSmallInteger(aFileDescriptor) 
     && (__qClass(openmode)== @global(String))) {
	f = (FILEPOINTER) fdopen(__intVal(aFileDescriptor), (char *)__stringVal(openmode));
	if (f == NULL) {
	    __INST(lastErrorNumber) = __MKSMALLINT(errno);
	    __INST(position) = nil;
	} else {
	    __INST(filePointer) = fp = __MKFILEPOINTER(f); __STORE(self, fp);
	    __INST(position) = __MKSMALLINT(1);
	    retVal = self;
	}
    }
%}.
    retVal notNil ifTrue:[
	buffered := true.       "default is buffered"
	Lobby register:self
    ].
    lastErrorNumber notNil ifTrue:[
	"
	 the open failed for some reason ...
	"
	^ self openError
    ].
    ^ retVal
!

open:aPath withMode:mode
    "low level open; opens the file/device and sets the filePointer instance
     variable. Careful: this does not care for any other state."

    |ok|

    ok := false.
%{
    FILE *f; 
    OBJ fp;

    if (__isString(aPath)) { 
	__BEGIN_INTERRUPTABLE__
	do {
	    f = fopen((char *) __stringVal(aPath), (char *) __stringVal(mode));
	} while ((f == NULL) && (errno == EINTR));
	__END_INTERRUPTABLE__
	if (f == NULL) {
	    __INST(lastErrorNumber) = __MKSMALLINT(errno);
	} else {
	    __INST(filePointer) = fp = __MKFILEPOINTER(f); __STORE(self, fp);
	    ok = true;
	}
    }
%}.
    ok ifFalse:[
	"
	 the open failed for some reason ...
	"
	^ self openError
    ]. 
    Lobby register:self. 
!

reOpen
    "sent after snapin to reopen streams.
     cannot reopen here since I am abstract and have no device knowledge"

    self class name errorPrint. ' [warning]: automatic reOpen not supported - stream closed' errorPrintCR.
    filePointer := nil.
    Lobby unregister:self.

    "Modified: 10.1.1997 / 17:50:44 / cg"
!

setLastError:aNumber
    lastErrorNumber := aNumber
! !

!ExternalStream methodsFor:'queries'!

isBinary
    "return true, if the stream is in binary (as opposed to text-) mode.
     The default when created is false."

    ^ binary
!

isExternalStream
    "return true, if the receiver is some kind of externalStream;
     true is returned here - the method redefined from Object."

    ^ true
!

isReadable 
    "return true, if this stream can be read from"

    ^ (mode ~~ #writeonly)
!

isWritable 
    "return true, if this stream can be written to"

    ^ (mode ~~ #readonly)
! !

!ExternalStream methodsFor:'reading'!

next
    "return the next element; advance read position.
     In binary mode, an integer is returned, otherwise a character.
     If there are no more elements, nil is returned."

%{
    FILEPOINTER f;
    int ret, _buffered;
    OBJ pos, fp;
    unsigned char ch;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(writeonly))
    ) {
	f = __FILEVal(fp);

	_buffered = (__INST(buffered) == true);
	if (_buffered) {
	    __READING__(f)
	}
	__READBYTE__(ret, f, &ch, _buffered);

	if (ret > 0) {
	    pos = __INST(position);
	    if (__isSmallInteger(pos)) {
		__INST(position) = __MKSMALLINT(__intVal(pos) + 1);
	    } else {
		__INST(position) = nil;
	    }
	    if (__INST(binary) == true) {
		RETURN ( __MKSMALLINT(ch) );
	    }
	    RETURN ( __MKCHARACTER(ch) );
	}

	__INST(position) = nil;
	if (ret < 0) {
	    __INST(lastErrorNumber) = __MKSMALLINT(errno);
	} else /* ret == 0 */ {
	    __INST(hitEOF) = true;
	}
    }
%}.
    hitEOF ifTrue:[^ self pastEnd].
    lastErrorNumber notNil ifTrue:[^ self readError].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    ^ self errorWriteOnly
!

next:count
    "return the next count elements of the stream as a collection.
     Redefined to return a String or ByteArray instead of the default: Array."

    |coll nRead|

    binary ifTrue:[
	coll := ByteArray uninitializedNew:count
    ] ifFalse:[
	coll := String new:count
    ].
    nRead := self nextBytes:count into:coll startingAt:1.

    "/ for readStream protocol compatibility, 
    "/ we must raise an exception here.

    nRead < count ifTrue:[
	^ self pastEnd
    ].
    ^ coll

    "Modified: 11.1.1997 / 17:44:17 / cg"
!

peek
    "return the element to be read next without advancing read position.
     In binary mode, an integer is returned, otherwise a character.
     If there are no more elements, nil is returned."

%{
    FILEPOINTER f;
    unsigned char c;
    int ret, _buffered;
    OBJ fp;
    OBJ ra;

    if ((ra = __INST(readAhead)) != nil) {
	if (__INST(binary) == true) {
	    RETURN ( ra );
	}
	c = __intVal(ra);
	RETURN ( __MKCHARACTER(c) );
    }

    __INST(lastErrorNumber) = nil;

    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(writeonly))
    ) {
	f = __FILEVal(fp);
	_buffered = (__INST(buffered) == true);
	if (_buffered) {
	    __READING__(f)
	}
	__READBYTE__(ret, f, &c, _buffered);

	if (ret > 0) {
	    __UNGETC__(c, f, _buffered);

	    if (__INST(binary) == true) {
		RETURN ( __MKSMALLINT(c) );
	    }
	    RETURN ( __MKCHARACTER(c) );
	}
	if (ret < 0) {
	    __INST(lastErrorNumber) = __MKSMALLINT(errno);
	} else /* ret == 0 */ {
	    __INST(hitEOF) = true;
	}
    }
%}.
    hitEOF ifTrue:[^ self pastEnd]. 
    lastErrorNumber notNil ifTrue:[^ self readError].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    ^ self errorWriteOnly
!

upToEnd
    "return a collection of the elements up-to the end.
     Return nil if the stream-end is reached before."

    |answerStream ch|

    answerStream := WriteStream on:(self contentsSpecies new).
    [self atEnd] whileFalse:[
	ch := self next.
	ch notNil ifTrue:[
	    answerStream nextPut:ch
	]
    ].
    ^ answerStream contents

    "Created: 25.4.1996 / 14:40:31 / cg"
! !

!ExternalStream methodsFor:'reimplemented for speed'!

nextAlphaNumericWord
    "read the next word (i.e. up to non letter-or-digit) after first
     skipping any whiteSpace.
     Return a string containing those characters.
     There is a limit of 1023 characters in the word - if longer,
     it is truncated."

%{  /* STACK: 2000 */
    FILEPOINTER f;
    int len;
    char buffer[1024];
    unsigned char ch;
    int cnt = 0;
    int ret, _buffered;
    OBJ fp;

    if (((fp = __INST(filePointer)) != nil)
     && (__INST(mode) != @symbol(writeonly))
    ) {
	f = __FILEVal(fp);
	_buffered = (__INST(buffered) == true);
	if (_buffered) {
	    __READING__(f)
	}

	/*
	 * skip whiteSpace first ...
	 */
	do {
	    __READBYTE__(ret, f, &ch, _buffered);
	    if (ret > 0)
		cnt++;
	    else
		break;
	} while (
#ifndef NON_ASCII
		 (ch < ' ') ||
#endif
		 (ch == ' ' ) || (ch == '\t') || (ch == '\r')
	      || (ch == '\n') || (ch == 0x0b));

	len = 0;
	while (ret > 0 && 
	       (((ch >= 'a') && (ch <= 'z')) ||
		((ch >= 'A') && (ch <= 'Z')) ||
		((ch >= '0') && (ch <= '9')))
	) {
	    buffer[len++] = ch;
	    if (len >= (sizeof(buffer)-1)) {
		/* emergency */
		break;
	    }
	    __READBYTE__(ret, f, &ch, _buffered);
	    if (ret > 0)
		cnt++;
	    else
		break;
	}

	if (ret <= 0) {
	    if (ret < 0) {
		__INST(lastErrorNumber) = __MKSMALLINT(errno);
		goto err;
	    }
	    else if (ret == 0)
		__INST(hitEOF) = true;
	} else {
	    __UNGETC__(ch, f, _buffered);
	    cnt--;
	}

	if (__INST(position) != nil) {
	    __INST(position) = __MKSMALLINT(__intVal(__INST(position)) + cnt);
	}
	if (len != 0) {
	    buffer[len] = '\0';
	    RETURN ( __MKSTRING_L(buffer, len) );
	}
    }
err: ;
%}.
    hitEOF ifTrue:[^ self pastEnd].
    lastErrorNumber notNil ifTrue:[^ self readError].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    ^ self errorWriteOnly
!

nextChunk
    "return the next chunk, i.e. all characters up to the next
     exclamation mark. Within the chunk, exclamation marks have to be doubled -
     except within primitive code (this exception was added to make it easier
     to edit primitive code with external editors). This means, that other
     Smalltalks cannot always read chunks containing primitive code - but that
     doesnt really matter, since C-primitives are an ST/X feature anyway.
     Reimplemented here for more speed."

    |retVal outOfMemory buffer|

    filePointer isNil ifTrue:[^ self errorNotOpen].
    binary ifTrue:[^ self errorBinary].
    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].

%{  /* STACK: 12000 */
    /*
     * the main trick (and a bit of complication) here
     * is to read the first 8k into a stack-buffer.
     * Since most chunks fit into that, 
     * this avoids creating lots of garbage for thos small chunks.
     */
    FILEPOINTER f;
    int nread = 0, done = 0;
    unsigned char c, lastC, peekC;
    int ret;
    unsigned char *bufferPtr = (unsigned char *)0;
    unsigned char fastBuffer[8000];
    REGISTER int index;
    int currSize, fastFlag, _buffered;
    int atBeginOfLine = 1, inPrimitive = 0;
    int haveCR = 0, haveNL = 0;

    __INST(lastErrorNumber) = nil;
    f = __FILEVal(__INST(filePointer));

    _buffered = (__INST(buffered) == true);
    if (_buffered) {
	__READING__(f)
    }

    /*
     * skip spaces
     */
    c = '\n';
    while (! done) {
	lastC = c;
	__READBYTE__(ret, f, &c, _buffered);
	if (ret <= 0) {
	    goto err;
	}
	nread++;
	atBeginOfLine = 0;
	switch (c) {
	    case '\n':
	    case ' ':
	    case '\t':
	    case '\r':
	    case '\b':
	    case '\014':
		break;

	    default:
		atBeginOfLine = (lastC == '\n');
		done = 1;
		break;
	}
    }

    /*
     * read chunk into a buffer
     */
    bufferPtr = fastBuffer; fastFlag = 1;
    currSize = sizeof(fastBuffer);

    index = 0;
    for (;;) {
	__READBYTE__(ret, f, &peekC, _buffered);
	if ((c == '%') && atBeginOfLine && ret > 0) {
	    if (peekC == '{') {
		inPrimitive = 1;
	    } else if (peekC == '}') {
		inPrimitive = 0;
	    }
	} else if (c == '!' && !inPrimitive) {
	    if (ret > 0 && peekC == '!') {
		/*
		 * convert double-! to a single !
		 */
		__READBYTE__(ret, f, &peekC, _buffered);
		if (ret > 0)
		    nread++;
	    } else {
		/*
		 * End of chunk, push back lookahead character
		 * and leave loop.
		 */
		if (ret > 0) {
		    __UNGETC__(peekC, f, _buffered);
		} else
		    ret = 1;
		break; 
	    }
	}

	/* 
	 * do we have to resize the buffer ? 
	 */
	if ((index+2) >= currSize) {
	    OBJ newBuffer;
	    unsigned char *nbp;

	    newBuffer = __MKEMPTYSTRING(currSize * 2);
	    if (!fastFlag) {
		/*
		 * refetch - buffer could be moved by GC
		 */
		bufferPtr = __stringVal(buffer);
	    }
	    if (newBuffer == nil) {
		/*
		 * mhmh - chunk seems to be very big ....
		 */
		outOfMemory = true;
		goto err;
	    }
	    nbp = __stringVal(newBuffer);
	    bcopy(bufferPtr, nbp, index);
	    bufferPtr = nbp;
	    bufferPtr[index] = '\0';
	    buffer = newBuffer;
	    fastFlag = 0;
	    currSize = currSize * 2;
	}

	if (!fastFlag) {
	    /*
	     * old buffer may have moved (interrupt while reading)
	     * - refetch pointer
	     */
	    bufferPtr = __stringVal(buffer);
	}

	/*
	 * filter out cr-nl; make it nl
	 */
	if (c == '\n') {
	    haveNL = 1;
	    if (index > 0) {
		if (bufferPtr[index-1] == '\r') {
		    index--;
		}
	    }
	} else {
	    if (c == '\r') {
		haveCR = 1;
	    }
	}
	bufferPtr[index++] = c;
	if (ret <= 0) {
	    if (ret == 0)
		break;          /* End of chunk reached */
	    goto err;
	}
	nread++;
	atBeginOfLine = (c == '\n');
	c = peekC;
    }

    /*
     * care for CR-limited files.
     * Replace CR's by NL's
     */
    if (!haveNL && haveCR) {
	int i;

	for (i=index-1; i>=0; i--) {
	    if (bufferPtr[i] == '\r') {
		bufferPtr[i] = '\n';
	    }
	}
    }

    /*
     * make it a string
     * be careful here - allocating a new string may make the
     * existing buffer move around. Need to check if copying from
     * fast (C) buffer or from real (ST) buffer.
     */
    if (fastFlag) {
	retVal = __MKSTRING_L(bufferPtr, index);
    } else {
	retVal = __MKSTRING_ST_L(buffer, index);
    }

err: ;
    if (ret <= 0) {
	if (ret == 0)
	    __INST(hitEOF) = true;
	else /* ret < 0 */
	    __INST(lastErrorNumber) = __MKSMALLINT(errno);
    }
    if (__INST(position) != nil) {
	__INST(position) = __MKSMALLINT(__intVal(__INST(position)) + nread);
    }
%}.
    retVal isNil ifTrue:[
	"/
	"/ arrive here with retVal==nil either on error or premature EOF
	"/ or if running out of malloc-memory
	"/
	hitEOF ifTrue:[^ self pastEnd].
	lastErrorNumber notNil ifTrue:[^ self readError].
	outOfMemory == true ifTrue:[
	    "
	     buffer memory allocation failed.
	     When we arrive here, there was no memory available for the
	     chunk. (seems to be too big of a chunk ...)
	     Bad luck - you should increase the ulimit and/or swap space on your machine.
	    "
	    ^ ObjectMemory allocationFailureSignal raise.
	]
    ].
    ^ retVal
!

nextMatchFor:anObject
    "skip all objects up-to and including anObject, return anObject on success,
     nil if end-of-file is reached before. The next read operation will return
     the element after anObject.
     Only single byte characters are currently supported."

%{
    FILEPOINTER f;
    int ret, peekValue;
    char c;
    OBJ fp;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(writeonly))
    ) {
	if ((__INST(binary) == true) && __isSmallInteger(anObject)) {
	    peekValue = __intVal(anObject) & 0xFF;
	} else {
	    if ((__INST(binary) != true) && __isCharacter(anObject)) {
		peekValue = __intVal(_characterVal(anObject)) & 0xFF;
	    } else {
		peekValue = -1;
	    }   
	}

	if (peekValue >= 0) {
	    int _buffered;

	    __INST(position) = nil;
	    f = __FILEVal(fp);

	    if (_buffered = (__INST(buffered) == true)) {
		__READING__(f)
	    }

	    for (;;) {
		__READBYTE__(ret, f, &c, _buffered);
		if (ret <= 0) {
		    if (ret < 0)
			__INST(lastErrorNumber) = __MKSMALLINT(errno);
		    else
			__INST(hitEOF) = true;
		    break;
		}
		if (c == peekValue) {
		    RETURN (anObject);
		}
	    }
	}
    }
%}.
    hitEOF ifTrue:[^ self pastEnd].
    lastErrorNumber notNil ifTrue:[^ self readError].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    ^ super nextMatchFor:anObject
!

peekFor:anObject
    "return true and move past next element, if next == something.
     Otherwise, stay and return false. False is also returned
     when EOF is encountered.
     The argument must be an integer if in binary, a character if in
     text mode; only single byte characters are currently supported."

%{
    FILEPOINTER f;
    unsigned char c;
    int ret, peekValue, _buffered;
    OBJ fp;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(writeonly))
    ) {
	if (__INST(binary) == true) {
	    if (__isSmallInteger(anObject)) {
		peekValue = __intVal(anObject) & 0xFF;
	    } else {
		goto bad;
	    }
	} else {
	    if (__isCharacter(anObject)) {
		peekValue = __intVal(_characterVal(anObject)) & 0xFF;
	    } else {
		goto bad;
	    }
	}

	f = __FILEVal(fp);

	_buffered = (__INST(buffered) == true);
	if (_buffered) {
	    __READING__(f)
	}
	__READBYTE__(ret, f, &c, _buffered);
	if (ret > 0) {
	    if (c == peekValue) {
		OBJ pos;

		if ((pos = __INST(position)) != nil) {
		    __INST(position) = __MKSMALLINT(__intVal(pos) + 1);
		}
		RETURN (true);
	    }
	    __UNGETC__(c, f, _buffered);
	    RETURN (false);
	} 
	if (ret < 0) {
	    __INST(lastErrorNumber) = __MKSMALLINT(errno);
	} else /* ret == 0 */
	    __INST(hitEOF) = true;
	}
bad: ;
%}.
    mode == #writeonly ifTrue:[^ self errorWriteOnly].
    hitEOF ifTrue:[self pastEnd. ^ false].
    lastErrorNumber notNil ifTrue:[^ self readError].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    ^ super peekFor:anObject
!

skipLine
    "read the next line (characters up to newline) skip only;
     return nil if EOF reached, self otherwise. 
     Not allowed in binary mode."

%{  /* STACK:2000 */

    FILEPOINTER f;
    OBJ fp;
    int ret, _buffered;
    char c;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(writeonly))
	&& (__INST(binary) != true)
    ) {
	f = __FILEVal(fp);
	if (_buffered = (__INST(buffered) == true)) {
	    __READING__(f)
	}
	for (;;) {
	    __READBYTE__(ret, f, &c, _buffered);
	    if (ret > 0) {
		if (c == '\n')
		    RETURN(self) 
	    } else
		break;
	}
	if (ret < 0) {
	    __INST(lastErrorNumber) = __MKSMALLINT(errno);
	} else /* ret == 0 */ {
	    __INST(hitEOF) = true;
	}
    }
%}.
    hitEOF ifTrue:[^ self pastEnd].
    lastErrorNumber notNil ifTrue:[self readError. ^ self].
    filePointer isNil ifTrue:[self errorNotOpen. ^ self].
    binary ifTrue:[self errorBinary. ^ self].
    self errorWriteOnly
!

skipSeparators
    "skip all whitespace; next will return next non-white-space character
     or nil if endOfFile reached. Not allowed in binary mode.
     - reimplemented for speed"

%{
    FILEPOINTER f;
    char c;
    int ret, _buffered;
    OBJ fp;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(writeonly))
	&& (__INST(binary) != true)
    ) {
	f = __FILEVal(fp);
	_buffered = (__INST(buffered) == true);
	if (_buffered) {
	    __READING__(f)
	}

	while (1) {
	    __READBYTE__(ret, f, &c, _buffered);
	    if (ret <= 0) {
		if (ret < 0) {
		    __INST(lastErrorNumber) = __MKSMALLINT(errno);
		} else /* ret == 0 */ {
		    __INST(hitEOF) = true;
		}
		break;
	    }
	    switch (c) {
		case ' ':
		case '\t':
		case '\n':
		case '\r':
		case '\b':
		case '\f':
		    break;

		default:
		    __UNGETC__(c, f, _buffered);
		    RETURN ( __MKCHARACTER(c & 0xFF) );
	    }
	}
    }
%}.
    hitEOF ifTrue:[^ self pastEnd].
    lastErrorNumber notNil ifTrue:[^ self readError].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    ^ self errorBinary.
!

skipSeparatorsExceptCR
    "skip all whitespace but no newlines;
     next will return next non-white-space character
     or nil if endOfFile reached. Not allowed in binary mode.
     - reimplemented for speed"

%{
    FILEPOINTER f;
    char c;
    int ret, _buffered;
    OBJ fp;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(writeonly))
	&& (__INST(binary) != true)
    ) {
	f = __FILEVal(fp);
	_buffered = (__INST(buffered) == true);
	if (_buffered) {
	    __READING__(f)
	}
	while (1) {
	    __READBYTE__(ret, f, &c, _buffered);
	    if (ret <= 0) {
		if (ret < 0) {
		    __INST(lastErrorNumber) = __MKSMALLINT(errno);
		} else /* ret == 0 */ {
		    __INST(hitEOF) = true;
		}
		break;
	    }
	    switch (c) {
		case ' ':
		case '\t':
		case '\b':
		case '\f':
		    break;

		default:
		    __UNGETC__(c, f, _buffered);
		    RETURN ( __MKCHARACTER(c & 0xFF) );
	    }
	}
    }
%}.
    hitEOF ifTrue:[^ self pastEnd].
    lastErrorNumber notNil ifTrue:[^ self readError].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    ^ self errorBinary.
!

skipThrough:aCharacter
    "skip all characters up-to and including aCharacter. Return the receiver if
     skip was successful, otherwise (i.e. if not found) return nil.
     The next read operation will return the character after aCharacter.
     The argument, aCharacter must be character, or integer when in binary mode.
     Only single byte characters are currently supported."

%{
    FILEPOINTER f;
    REGISTER int cSearch;
    unsigned char c;
    int ret, _buffered;
    OBJ fp;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(writeonly))
    ) {
	if (__INST(binary) == true) {
	    /* searched for object must be a smallInteger */
	    if (! __isSmallInteger(aCharacter)) goto badArgument;
	    cSearch = __intVal(aCharacter);
	} else {
	    /* searched for object must be a character */
	    if (! __isCharacter(aCharacter)) goto badArgument;
	    cSearch = __intVal(_characterVal(aCharacter));
	}
	/* Q: should we just say: "not found" ? */
	if ((cSearch < 0) || (cSearch > 255)) goto badArgument;

	f = __FILEVal(fp);
	if (_buffered = (__INST(buffered) == true)) {
	    __READING__(f)
	}

	while (1) {
	    __READBYTE__(ret, f, &c, _buffered);
	    if (ret <= 0) {
		if (ret < 0) {
		    __INST(lastErrorNumber) = __MKSMALLINT(errno);
		} else /* ret == 0 */ {
		    __INST(hitEOF) = true;
		}
		break;
	    }
	    if (c == cSearch) {
		RETURN (self);
	    }
	}
    }
badArgument: ;
%}.
    hitEOF ifTrue:[^ self pastEnd].
    lastErrorNumber notNil ifTrue:[self readError. ^ self].
    filePointer isNil ifTrue:[self errorNotOpen. ^ self].
    (mode == #writeonly) ifTrue:[self errorWriteOnly. ^ self].
    "
     argument must be integer/character in binary mode, 
     character in text mode
    "
    self error:'invalid argument'.

    "
     |s|
     s := 'Makefile' asFilename readStream.
     s skipThrough:$=.
     s next:10
    "
!

skipToAll:aString
    "skip for the sequence given by the argument, aCollection;
     return nil if not found, self otherwise. 
     On a successful match, the next read will return characters of aString."

    |oldPos|

    oldPos := self position.
    (self skipThroughAll:aString) isNil ifTrue:[
	"
	 restore position
	"
	self position:oldPos.
	^ nil
    ].
    "
     position before match-string
    "
    self position:(self position - aString size).
    ^ self

    "
     |s|
     s := 'Makefile' asFilename readStream.
     s skipToAll:'are'.
     s next:10 
    "

    "Modified: 26.6.1996 / 09:22:05 / cg"
! !

!ExternalStream methodsFor:'testing'!

atEnd
    "return true, if position is at end"

%{
    FILEPOINTER f;
    OBJ fp;
    char c;
    int ret, _buffered;

    if (__INST(hitEOF) == true) {
	RETURN (true);
    }

    __INST(lastErrorNumber) = nil;

    if ((fp = __INST(filePointer)) != nil) {
	f = __FILEVal(fp);

	if (_buffered = (__INST(buffered) == true)) {
	    __READING__(f);
	} else {
	    if (__INST(readAhead) != nil) {
		RETURN (false);
	    }
	}

	/*
	 * read ahead ...
	 */
	do {
	    __BEGIN_INTERRUPTABLE__
	    __READBYTE__(ret, f, &c, _buffered);
	    __END_INTERRUPTABLE__
	} while ((ret < 0) && (errno == EINTR));

#if defined(WIN32) && defined(EPIPE)
	if (errno == EPIPE)
	    ret = 0;
#endif
	if (ret > 0) {
	    __UNGETC__(c&0xff, f, _buffered);
	    RETURN (false);
	}

	if (ret == 0) { 
	    __INST(hitEOF) = true;
	    RETURN (true);
	}

	/* ret < 0 */
	__INST(lastErrorNumber) = __MKSMALLINT(errno);
    }
%}.
    lastErrorNumber notNil ifTrue:[^ self readError].
    ^ self errorNotOpen
!

canReadWithoutBlocking
    "return true, if any data is available for reading (i.e.
     a read operation will not block the smalltalk process), false otherwise."

    |fd|

    readAhead notNil ifTrue:[^ true].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    mode == #writeonly ifTrue:[^ self errorWriteOnly].

    fd := self fileDescriptor.
    ^ OperatingSystem readCheck:fd

    "
     |pipe|

     pipe := PipeStream readingFrom:'(sleep 10; echo hello)'.
     pipe canReadWithoutBlocking ifTrue:[
	 Transcript showCR:'data available'
     ] ifFalse:[
	 Transcript showCR:'no data available'
     ].
     pipe close
    "

    "Modified: 25.9.1997 / 13:08:45 / stefan"
!

canWriteWithoutBlocking
    "return true, if data can be written into the stream 
     (i.e. a write operation will not block the smalltalk process)."

    |fd|

    filePointer isNil ifTrue:[^ self errorNotOpen].
    mode == #readonly ifTrue:[^ self errorReadOnly].

    fd := self fileDescriptor.
    ^ OperatingSystem writeCheck:fd
!

numAvailable
    "return the number of bytes available for reading"

    |fd|

    filePointer isNil ifTrue:[^ self errorNotOpen].
    mode == #writeonly ifTrue:[^ self errorWriteOnly].

    fd := self fileDescriptor.
    ^ OperatingSystem numAvailableForReadOn:fd.

"/    self atEnd ifTrue:[^ 0].
"/    self canReadWithoutBlocking ifTrue:[
"/        ^ 1
"/    ].
"/    ^ 0

    "Created: / 4.2.1998 / 16:56:01 / cg"
    "Modified: / 4.2.1998 / 17:31:11 / cg"
! !

!ExternalStream methodsFor:'waiting for I/O'!

readWait
    "suspend the current process, until the receiver
     becomes ready for reading. If data is already available,
     return immediate. 
     The other threads are not affected by the wait."

    self readWaitWithTimeoutMs:nil
!

readWaitTimeoutMs:timeout
    "ST-80 compatibility"
    ^ self readWaitWithTimeoutMs:timeout 
!

readWaitWithTimeout:timeout
    "suspend the current process, until the receiver
     becomes ready for reading or a timeout (in seconds) expired. 
     If data is already available, return immediate. 
     Return true if a timeout occured (i.e. false, if data is available).
     The other threads are not affected by the wait."

    ^ self readWaitWithTimeoutMs:timeout * 1000
!

readWaitWithTimeoutMs:timeout 
    "suspend the current process, until the receiver
     becomes ready for reading or a timeout (in milliseconds) expired. 
     If data is already available, return immediate. 
     Return true if a timeout occured (i.e. false, if data is available).
     The other threads are not affected by the wait."

    |fd inputSema hasData wasBlocked|

    readAhead notNil ifTrue:[^ false].

    filePointer isNil ifTrue:[^ self errorNotOpen].
    mode == #writeonly ifTrue:[^ self errorWriteOnly].

    fd := self fileDescriptor.
    (OperatingSystem readCheck:fd) ifTrue:[^ false].

    wasBlocked := OperatingSystem blockInterrupts.
    hasData := OperatingSystem readCheck:fd.
    hasData ifFalse:[
	inputSema := Semaphore new name:'readWait'.
	[
	    timeout notNil ifTrue:[
		Processor signal:inputSema afterMilliseconds:timeout 
	    ].
	    Processor signal:inputSema onInput:fd.
	    Processor activeProcess state:#ioWait.
	    inputSema wait.
	    Processor disableSemaphore:inputSema.
	    hasData := OperatingSystem readCheck:fd
	] valueOnUnwindDo:[
	    Processor disableSemaphore:inputSema.
	    wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
	]
    ].
    wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
    ^ hasData not
!

readWriteWait
    "suspend the current process, until the receiver
     becomes ready for writing or reading.
     Return immediate if the receiver is already ready. 
     The other threads are not affected by the wait.
     This is actually only used with sockets, to wait for a connect to
     be finished."

    self readWriteWaitWithTimeoutMs:nil
!

readWriteWaitWithTimeoutMs:timeout
    "suspend the current process, until the receiver
     becomes ready for reading or writing or a timeout (in seconds) expired. 
     Return true if a timeout occured (i.e. false, if data is available).
     Return immediate if the receiver is already ready. 
     The other threads are not affected by the wait.
     This is actually only used with sockets, to wait for a connect to
     be finished."

    |fd sema canReadWrite wasBlocked|

    filePointer isNil ifTrue:[
        ^ self errorNotOpen
    ].

    fd := self fileDescriptor.
    (OperatingSystem readWriteCheck:fd) ifTrue:[^ false].

    wasBlocked := OperatingSystem blockInterrupts.
    canReadWrite := OperatingSystem readWriteCheck:fd.
    canReadWrite ifFalse:[
        sema := Semaphore new name:'readWriteWait'.
        [
            timeout notNil ifTrue:[
                Processor signal:sema afterMilliseconds:timeout
            ].
            Processor signal:sema onOutput:fd.
            Processor signal:sema onInput:fd.
            Processor activeProcess state:#ioWait.
            sema wait.
            Processor disableSemaphore:sema.
            canReadWrite := OperatingSystem readWriteCheck:fd
        ] valueOnUnwindDo:[
            Processor disableSemaphore:sema.
            wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
        ]
    ].
    wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
    ^ canReadWrite not
!

writeWait
    "suspend the current process, until the receiver
     becomes ready for writing.
     Return immediate if the receiver is already ready. 
     The other threads are not affected by the wait."

    self writeWaitWithTimeoutMs:nil
!

writeWaitTimeoutMs:timeout
    "ST-80 compatibility"
    ^ self writeWaitWithTimeoutMs:timeout 
!

writeWaitWithTimeout:timeout
    "suspend the current process, until the receiver
     becomes ready for writing or a timeout (in seconds) expired. 
     Return true if a timeout occured (i.e. false, if data is available).
     Return immediate if the receiver is already ready. 
     The other threads are not affected by the wait."

    ^ self writeWaitWithTimeoutMs:timeout * 1000
!

writeWaitWithTimeoutMs:timeout
    "suspend the current process, until the receiver
     becomes ready for writing or a timeout (in seconds) expired. 
     Return true if a timeout occured (i.e. false, if data is available).
     Return immediate if the receiver is already ready. 
     The other threads are not affected by the wait."

    |fd outputSema canWrite wasBlocked|

    filePointer isNil ifTrue:[
	^ self errorNotOpen
    ].
    mode == #readonly ifTrue:[
	^ self errorReadOnly
    ].

    fd := self fileDescriptor.
    (OperatingSystem writeCheck:fd) ifTrue:[^ false].

    wasBlocked := OperatingSystem blockInterrupts.
    canWrite := OperatingSystem writeCheck:fd.
    canWrite ifFalse:[
	outputSema := Semaphore new name:'writeWait'.
	[
	    timeout notNil ifTrue:[
		Processor signal:outputSema afterMilliseconds:timeout
	    ].
	    Processor signal:outputSema onOutput:fd.
	    Processor activeProcess state:#ioWait.
	    outputSema wait.
	    Processor disableSemaphore:outputSema.
	    canWrite := OperatingSystem writeCheck:fd
	] valueOnUnwindDo:[
	    Processor disableSemaphore:outputSema.
	    wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
	]
    ].
    wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
    ^ canWrite not
! !

!ExternalStream methodsFor:'writing'!

cr
    "append an end-of-line character (or CRLF if in crlf mode).
     reimplemented for speed"

%{
    FILEPOINTER f;
    int len, cnt, _buffered;
    OBJ fp;
    char *cp;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(readonly))
	&& (__INST(binary) != true)
    ) {
	f = __FILEVal(fp);

	if (_buffered = (__INST(buffered) == true)) { 
	    __WRITING__(f)
	}
	{
	    OBJ mode = __INST(eolMode);

	    if (mode == @symbol(cr)) {
		cp = "\r"; len = 1;
	    } else if (mode == @symbol(crlf)) {
		cp = "\r\n"; len = 2;
	    } else {
		cp = "\n"; len = 1;
	    }
	}
	__WRITEBYTES__(cnt, f, cp, len, _buffered);

	if (cnt == len) {
	    if (__INST(position) != nil) {
		__INST(position) = __MKSMALLINT(__intVal(__INST(position)) + len);
	    }
	    RETURN ( self );
	}
	__INST(lastErrorNumber) = __MKSMALLINT(errno);
    }
%}.
    lastErrorNumber notNil ifTrue:[self writeError. ^ self].
    filePointer isNil ifTrue:[self errorNotOpen. ^ self].
    (mode == #readonly) ifTrue:[self errorReadOnly. ^ self].
    self errorBinary
!

flush
    "write all buffered data - ignored if unbuffered"

%{
    OBJ fp;

    __INST(lastErrorNumber) = nil;
    if ((fp = __INST(filePointer)) != nil) {
	if (__INST(mode) != @symbol(readonly)) {
	    if (__INST(buffered) == true) {
		__BEGIN_INTERRUPTABLE__
		FFLUSH( __FILEVal(fp) );
		__END_INTERRUPTABLE__
	    }
	}
    }
%}
!

nextPut:aCharacter
    "write the argument, aCharacter - return nil if failed, self if ok.
     Only single-byte characters are currently supported"

%{
    FILEPOINTER f;
    char c;
    int cnt, _buffered;
    OBJ pos, fp;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil) 
	&& (__INST(mode) != @symbol(readonly))
    ) {
	if (__INST(binary) != true) {
	    if (__isCharacter(aCharacter)) {
		c = __intVal(__characterVal(aCharacter)) & 0xFF;
    doWrite:
		f = __FILEVal(fp);


		if (_buffered = (__INST(buffered) == true)) {
		    __WRITING__(f)
		}
		__WRITEBYTE__(cnt, f, &c, _buffered);

		if (cnt == 1) {
		    pos = __INST(position);
		    if (pos != nil) {
			__INST(position) = __MKSMALLINT(__intVal(pos) + 1);
		    }
		    RETURN ( self );
		}
		__INST(lastErrorNumber) = __MKSMALLINT(errno);
	    }
	} else {
	    if (__isSmallInteger(aCharacter)) {
		c = __intVal(aCharacter);
		goto doWrite;
	    }
	}
    }
%}.
    lastErrorNumber notNil ifTrue:[self writeError. ^ self].
    filePointer isNil ifTrue:[self errorNotOpen. ^ self].
    (mode == #readonly) ifTrue:[self errorReadOnly. ^ self].
    binary ifFalse:[self argumentMustBeCharacter. ^ self].
    self argumentMustBeInteger.
!

nextPutAll:aCollection
    "write all elements of the argument, aCollection.
     Reimplemented for speed when writing strings or byteArrays.
     For others, falls back to general method in superclass."

%{
    FILEPOINTER f;
    int len, cnt;
    OBJ pos, fp;
    int _buffered;
    int o_offs;

    __INST(lastErrorNumber) = nil;

    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(readonly))
    ) {
	f = __FILEVal(fp);
	if (_buffered = (__INST(buffered) == true)) {
	    __WRITING__(f)
	}

	if (__isString(aCollection) || __isSymbol(aCollection)) {
	    len = __stringSize(aCollection);
#ifdef BUGGY
	    __WRITEBYTES__(cnt, f, __stringVal(aCollection), len, _buffered);
#else
	    o_offs = (char *)__stringVal(aCollection)-(char *)__InstPtr(aCollection);
	    __WRITEBYTES_OBJ__(cnt, f, aCollection, o_offs, len, _buffered);
#endif
	} else {
	    if (__INST(binary) == true) {
		int offs;

		if (__isByteArray(aCollection)) {
		    offs = 0;
		    len = __byteArraySize(aCollection);
		} else if (__isBytes(aCollection)) {
		    offs = __OBJS2BYTES__(__intVal(__ClassInstPtr(__qClass(aCollection))->c_ninstvars));
		    len = __byteArraySize(aCollection) - offs;
		} else
		    goto out;
#ifdef BUGGY
		__WRITEBYTES__(cnt, f, __ByteArrayInstPtr(aCollection)->ba_element+offs, len, _buffered);
#else
		o_offs = (char *)(__ByteArrayInstPtr(aCollection)->ba_element) - (char *)__InstPtr(aCollection);
		o_offs += offs;
		__WRITEBYTES_OBJ__(cnt, f, aCollection, o_offs, len, _buffered);
#endif
	    } else
		goto out;
	}

	if (cnt == len) {
	    pos = __INST(position);
	    if (pos != nil) {
		__INST(position) = __MKSMALLINT(__intVal(pos) + len);
	    }
	    RETURN (self);
	}
	__INST(lastErrorNumber) = __MKSMALLINT(errno);
    }
out: ;
%}.
    lastErrorNumber notNil ifTrue:[self writeError. ^ self].
    filePointer isNil ifTrue:[self errorNotOpen. ^ self].
    (mode == #readonly) ifTrue:[self errorReadOnly. ^ self].

    ^ super nextPutAll:aCollection
!

nextPutAll:aCollection startingAt:start to:stop
    "write a range of elements of the argument, aCollection.
     Reimplemented for speed when writing strings or byteArrays.
     For others, falls back to general method in superclass."

%{
    FILEPOINTER f;
    int offs, len, cnt, iStart, iStop, _buffered;
    OBJ fp, pos;
    int o_offs;

    __INST(lastErrorNumber) = nil;
    if (((fp = __INST(filePointer)) != nil)
	&& (__INST(mode) != @symbol(readonly))
	&& __bothSmallInteger(start, stop)
    ) {
	f = __FILEVal(fp);
	if (_buffered = (__INST(buffered) == true)) {
	    __WRITING__(f)
	}
	iStart = __intVal(start);
	iStop = __intVal(stop);
	if ((iStart < 1) || (iStop < iStart)) {
	    RETURN(self);
	}
	if (__isString(aCollection) || __isSymbol(aCollection)) {
	    len = __stringSize(aCollection);
	    if (iStop > len) {
		RETURN(self);
	    }
	    if (iStop > len)
		iStop = len;
	    len = iStop - iStart + 1;
#ifdef BUGGY
	    __WRITEBYTES__(cnt, f, __stringVal(aCollection)+iStart-1, len, _buffered);
#else
	    o_offs = (char *)__stringVal(aCollection)-(char *)__InstPtr(aCollection);
	    __WRITEBYTES_OBJ__(cnt, f, aCollection, o_offs+iStart-1, len, _buffered);
#endif
	} else {
	    if (__INST(binary) == true) {
		int offs;

		if (__isByteArray(aCollection)) {
		    offs = 0;
		    len = __byteArraySize(aCollection);
		} else if (__isBytes(aCollection)) {
		    offs = __OBJS2BYTES__(__intVal(__ClassInstPtr(__qClass(aCollection))->c_ninstvars));
		    len = __byteArraySize(aCollection) - offs;
		} else
		    goto out;

		if (iStop > len) {
		    RETURN(self);
		}
		if (iStop > len)
		    iStop = len;
		len = iStop - iStart + 1;
		offs += iStart - 1;
#ifdef BUGGY
		__WRITEBYTES__(cnt, f,  __ByteArrayInstPtr(aCollection)->ba_element+offs, len, _buffered);
#else
		o_offs = (char *)(__ByteArrayInstPtr(aCollection)->ba_element)-(char *)__InstPtr(aCollection);
		__WRITEBYTES_OBJ__(cnt, f,  aCollection, o_offs+offs, len, _buffered);
#endif
	    } else
		goto out;
	}
	if (cnt == len) {
	    pos = __INST(position);
	    if (pos != nil) {
		__INST(position) = __MKSMALLINT(__intVal(pos) + len);
	    }
	    RETURN (self);
	}
	__INST(lastErrorNumber) = __MKSMALLINT(errno);
    }
out: ;
%}.
    lastErrorNumber notNil ifTrue:[^ self writeError].
    ^ super nextPutAll:aCollection startingAt:start to:stop
! !

!ExternalStream class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libbasic/ExternalStream.st,v 1.174 1998-07-28 13:41:12 cg Exp $'
! !
ExternalStream initialize!