/* libc-style File I/O Routines for Quake III: Arena Copyright 2001 PhaethonH Permission granted to copy, modify, distribute, or otherwise use this code, provided this copyright notice remains intact */ /* Last revision 2002.03.09 */ #include "q_shared.h" #include "bg_io.h" #define debug Com_Printf /* Sections code: chaoZ Clotho Lachesis Atropos charoN Iris .macro'd Methods mentioned in BSD manpage for stdio: L clearerr - checks and resets stream status Z fclose - close stream //fdopen - open stream from file descriptor -- impossible to do correctly -- L feof - check stream status L ferror - check stream error status A fflush - flush stream C fgetc - get next character from stream //fgetpos - get position in stream -- needlessly complicated for QVM -- C fgets - get line from stream L fileno - get file descriptor of stream Z fopen - open file as stream A fprintf - formatted output //fpurge - flush stream -- not found in glibc -- A fputc - output character to stream A fputs - output string to stream C fread - binary stream input Z freopen - open different file as the same stream fscanf - formatted input from stream N fseek - reposition stream //fsetpos - reposition stream -- needlessly complicated for QVM -- N ftell - get position in stream A fwrite - binary stream output C getc - get next character from stream . getchar - get next character from stdin //gets - get line from stdin -- *** DO NOT IMPLEMENT!!! *** use fgets instead //getw - get next (SysV) word from stream -- SVID only -- //mktemp - create unique temporary filename from template -- DO NOT IMPLEMENT -- use tmpfile instead. I perror - prints system error message to stderr A printf - formatted output to stdout A putc - output character to stream . putchar - output character to stdout A puts - output string to stream //putw - output (SysV) word character to stream -- SVID only -- //remove - remove directory -- impossible to do in QVM -- N rewind - reposition stream to beginning scanf - formatted input from stdin //setbuf - set stream buffer, size set by library -- not applicable to QVM -- //setbuffer - set stream buffer, size set by caller -- not applicable to QVM -- //setlinebuf - set file to line-buffered mode -- not applicable to QVM -- //setvbuf - meddle with file buffering mode -- not applicable to QVM -- I sprintf - formatted output to string //sscanf - formatted input from string -- already implemented in bg_lib -- I strerror - string describing an error code I sys_errlist - system error message -- array of string -- I sys_nerr - system error message -- global integer -- //tempnam - create temporary file name given directory and filename prefix. -- DO NOT IMPLEMENT -- use tmpfile instead. tmpfile - creates FILE of temporary file, mode "w+b", closed on program end. //tmpnam - return unique temporary filename of length L_tmpnam. -- DO NOT IMPLEMENT --- use tmpfile instead. C ungetc - unget a character from stream (pushback for later read) A vfprintf - varargs formatted output to stream -- redundant -- //vfscanf - varargs formatted input from stream -- redundant -- A vprintf - varargs formatted output to stdout -- redundant -- //vscanf - varargs formatted input from stdin -- redundant -- //vsprintf - varargs formatted output to string -- already implemented in bg_lib -- //vsscanf - varargs formatted input from string -- redundant -- */ #define _ioerror(n) (errno = n) #define _ferror (stream->flags |= FILE_ERROR) #define READABLE (stream->flags & FILE_READ) #define WRITABLE (stream->flags & FILE_WRITE) #define SANITY if ((stream < _qfiles) || (stream > (_qfiles + FOPEN_MAX))) return #define INSANE ((stream < _qfiles) || (stream > (_qfiles + FOPEN_MAX))) #define ERRCHECK if (stream->errno) return EOF /**************************** ** Global variable errno ** ****************************/ int errno; /* The collection of files. */ FILE _qfiles[MAX_FILES] = { 0, }; /********************* Helper functions. **********************/ /* Output to terminal (screen). This really ought to be generalized into a Unix-like devices framework. */ static int _terminal_out (const void *ptr, int size) { char buf[256]; int chew, bite; chew = bite = 0; while (chew < size) { bite = (size - chew > sizeof(buf)) ? sizeof(buf) : size - chew + 1; Q_strncpyz(buf, ((char*)ptr) + chew, bite); chew += bite - 1; Com_Printf("%s", buf); } return chew; } static int _terminal_err (const void *ptr, int size) { return _terminal_out(ptr, size); } /* Find a new file slot among the rabble. */ static FILE * _fnewslot () { int i; for (i = 0; (i < FOPEN_MAX) && (_qfiles[i].flags & FILE_ACTIVE); i++); if (i < FOPEN_MAX) return _qfiles + i; return NULL; } /* Convert from string to Q3-style numerical permission value. */ static int _fmode2mode (const char *s) /* "r" = open for read, stream at start "r+" = open for read-write, stream at start -- not allowed "w" = open for write, erase content, stream at start "w+" = open for read-write, erase content, stream at start -- not allowed "a" = open for append, stream at end "a+" = open for read-append, stream at end -- reinterpreted as sync'd append "b" = binary mode */ { int mode; int i; for(mode = 0, i = 0; s[i]; i++) { switch (s[i]) { case 'r': mode = FS_READ; break; case 'w': mode = FS_WRITE; break; case 'a': mode = FS_APPEND; break; case '+': if (mode == FS_APPEND) mode = FS_APPEND_SYNC; break; } } return mode; } /* Determine if character is a line terminator. Well, someone royally f*cked-up with line-terminator choice. Un*x goes with C's literal specs on line-terminator (\n => 10 => LF) CPM/MS-DOS's is justifiable by analogy to teletypewriters (but in software?!) (CR for carriage (cursor) to far left, LF to go down one line) MacOS corresponds literally to the Enter/Return key (CR => 13) But this having to check for text file type is silly. One-character line terminator (Unix/Mac) is far easier to deal with. */ static int _lineterm (int c, FILE *stream) { if ((c != 10) && (c != 13)) return 0; if (stream->eol == EOL_UNKNOWN) { if (c == 10) stream->eol = EOL_UNIX; else if (c == 13) { stream->eolcheck = c, c = fgetc(stream); stream->eol = (c == 10) ? EOL_CPM : EOL_MAC; } } switch (stream->eol) { case EOL_UNIX: case EOL_CPM: if (c == 10) return 1; break; case EOL_MAC: if (c == 13) return 1; break; } return 0; } /* Initialize sys_errlist */ static void _errlist_init() { sys_errlist[ENONE] = "Success"; sys_errlist[EBADF] = "Bad file descriptor"; sys_errlist[EINVAL] = "Invalid seek"; sys_errlist[ENAMETOOLONG] = "Filename too long"; sys_errlist[EFAULT] = "No such file or directory"; sys_errlist[EMFILE] = "Too many files open"; sys_errlist[ENFILE] = "Too many files open in system"; sys_errlist[EACCESS] = "Permission denied"; } /* Do-nothing function. */ static void _nop() { return; } void (*files_init)(); /* Install sanity into stdin/out/err. */ static void _qfiles_init() { _qfiles[0].flags |= (FILE_ACTIVE | FILE_READ); _qfiles[0].eol = EOL_UNIX; _qfiles[0].fd = -1; _qfiles[0].len = 0x7FFFFFFF; strcpy(_qfiles[0].name, "stdin"); _qfiles[1].flags |= (FILE_ACTIVE | FILE_WRITE); _qfiles[1].eol = EOL_UNIX; _qfiles[1].fd = -2; _qfiles[1].len = 0x7FFFFFFF; strcpy(_qfiles[1].name, "stdout"); _qfiles[2].flags |= (FILE_ACTIVE | FILE_WRITE); _qfiles[2].eol = EOL_UNIX; _qfiles[2].fd = -3; _qfiles[2].len = 0x7FFFFFFF; strcpy(_qfiles[2].name, "stderr"); files_init = _nop; } void (*files_init)() = _qfiles_init; /************* *************** ** ** ** CHAOS ** ** ** ** Construct ** ** Destruct ** ** ** *************** *************/ /* With stream, closes old file, opens 'path', assigns to stream. Usually used for redirecting stdin, stdout, stderr. Returns: * stream * EOF on error */ FILE * freopen (const char *path, const char *mode, FILE *stream) { int qmode; int newlen, newfd; files_init(); if INSANE { _ioerror(EBADF); return NULL; } if (stream->fd > 0) trap_FS_FCloseFile(stream->fd); stream->flags |= FILE_ACTIVE; qmode = _fmode2mode(mode); switch (qmode) { case FS_READ: stream->flags |= FILE_READ; break; case FS_WRITE: case FS_APPEND: case FS_APPEND_SYNC: stream->flags |= FILE_WRITE; break; } stream->len = trap_FS_FOpenFile(path, &(stream->fd), qmode); if (!stream->fd) { _ioerror((stream->flags & FILE_READ) ? EACCESS : EFAULT); return NULL; } strcpy(stream->name, path); return stream; } /* Opens a file by name. Returns: * pointer to stream object * NULL on error */ FILE * fopen (const char *filename, const char *mode) { FILE *retval; retval = _fnewslot(); if (!retval) return NULL; memset(retval, 0, sizeof(*retval)); return freopen(filename, mode, retval); /* Cheap cheap cheap... */ } /* Returns: 0 on success EOF on error, 'errno' is set, further access to stream is undefined. Errors: EBADF - underlying file descriptor is invalid. */ int fclose (FILE *stream) { files_init(); if INSANE { _ioerror(EBADF); return EOF; } if (stream->fd > 0) { trap_FS_FCloseFile(stream->fd); memset(stream, 0, sizeof(*stream)); } return 0; } /************* *************** ** ** ** CLOTHO ** ** ** ** Data In ** ** ** *************** *************/ /* Returns: number of items (nmemb) read from file. Upon EOF or error, shortened value (possibly 0). The interface is designed to deal with endianness. Since QVM remains little-endian (Intel byte order) across platforms, we ignore byte swapping. This could have been written in terms of fgetc (especially with that stream->buf part), but the code was done this way on the principle that the fewer calls made to the VM (traps), the better the performance will be. */ size_t fread (void *ptr, size_t size, size_t nmemb, FILE *stream) { unsigned char *dest; int maxlen; int i; files_init(); if INSANE return 0; if (!READABLE) return 0; switch (stream->fd) { case -1: return 0; break; case -2: case -3: return 0; break; } if (stream->fd < 1) { return 0; } maxlen = size * nmemb; if (maxlen > (stream->len - stream->pos)) maxlen = stream->len - stream->pos; if (stream->flags & FILE_BUFFED) { *((char*)ptr) = stream->buf; maxlen--; ptr = ((char*)ptr) + 1; } //void trap_FS_Read( void *buffer, int len, fileHandle_t f ); trap_FS_Read(ptr, maxlen, stream->fd); stream->pos += maxlen; maxlen /= size; return maxlen; } /* Reads one character from stream. Returns: * the actual character read. * EOF on error */ int fgetc (FILE *stream) { unsigned char ch; if INSANE return EOF; if (!READABLE) return EOF; if (stream->flags & FILE_BUFFED) { stream->flags &= ~FILE_BUFFED; return stream->buf; } if (fread((void*)&ch, sizeof(ch), 1, stream)) return ch; _ferror; return EOF; } /* Pushes back a character to the stream for later re-reading. Only one pushback is guaranteed. Returns: * the pushed character * EOF on error */ int ungetc (int c, FILE *stream) { if INSANE return EOF; if (!READABLE) return EOF; stream->flags |= FILE_BUFFED; stream->buf = (unsigned char)c; return stream->buf; } /* Reads in at most size-1 characters from stream and stores into buffer at s. Line terminators are preserved. A \0 (string terminator) is written to the buffer at the end. Returns: * s on success * NULL on error or immediate EOF */ char * fgets (char *s, int size, FILE *stream) { char c; int i; if INSANE return NULL; if (!READABLE) return NULL; if (stream->pos >= stream->len) return NULL; i = 0; while ( ((c = fgetc(stream)) >= 0) // && (!_lineterm(c, stream)) && (i < (size - 1)) ) { s[i++] = c; if (_lineterm(c, stream)) break; } s[i] = 0; if (c == EOF) { _ferror; return NULL; } return s; } /************** **************** ** ** ** ATROPOS ** ** ** ** Data Out ** ** ** **************** **************/ /* Write character into stream. Returns: * the actual character written. * EOF on error. */ int fputc (int c, FILE *stream) { unsigned char ch; if INSANE return EOF; if (!WRITABLE) return EOF; ch = (unsigned char)c; if (fwrite((void*)&ch, sizeof(unsigned char), 1, stream)) return ch; _ferror; return EOF; } /* Returns: Number of items (not bytes) successfully written. If an error occurs, return value is shortened (probably 0). The interface is designed to deal with endianness. Since QVM remains little-endian (Intel byte order) across platforms, we ignore byte swapping. */ size_t fwrite (const void *ptr, size_t size, size_t nmemb, FILE *stream) { int outbytes; files_init(); if INSANE return 0; if (!WRITABLE) return 0; outbytes = size * nmemb; //void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); switch (stream->fd) { case -1: return 0; break; case -2: return _terminal_out(ptr, outbytes); break; case -3: return _terminal_err(ptr, outbytes); break; default: if (stream->fd < 1) { return 0; } trap_FS_Write(ptr, outbytes, stream->fd); break; } return nmemb; } /* Writes string s to stream (without the final \0 put into the stream). Returns: * non-negative (zero or positive) number on success * EOF on error */ int fputs (const char *s, FILE *stream) { #if 0 //One-by-one. char *p; if INSANE return EOF; if (!WRITABLE) return EOF; for(p = s; *p; fputc(*p, stream), p++); return (p - s); #else //Block write. if INSANE return EOF; if (!WRITABLE) return EOF; return fwrite(s, sizeof(char), strlen(s), stream); #endif } /* Force the flushing of output of a stream. Since we can't actually force the VM to do anything about file buffering, we just pretend we succeed all the time. Returns: * 0 on success * EOF on error */ int fflush (FILE *stream) { if INSANE return EOF; return 0; } /* Formatted output to stream with varargs args. Returns number of characters written. */ int vfprintf (FILE *stream, const char *format, va_list argptr) { char buf[32000]; /* Total local vars space must be < 32768 */ files_init(); vsprintf(buf, format, argptr); return fputs(buf, stream); } /* Formatted output to stream. Returns number of characters written. */ int fprintf (FILE *stream, const char *format, ...) { va_list argptr; int i; files_init(); va_start(argptr, format); i = vfprintf(stream, format, argptr); va_end(argptr); return i; } /* Formatted output to stdout with varargs args. Returns number of characters written. */ int vprintf (const char *format, va_list argptr) { return vfprintf(stdout, format, argptr); } /* Formatted output to stdout. Returns number of characters written. */ int printf (const char *format, ...) { va_list argptr; int i; files_init(); va_start(argptr, format); i = vfprintf(stdout, format, argptr); // i = fputs(va((char*)format, argptr), stdout); va_end(argptr); return i; } /************* *************** ** ** ** CHARON ** ** ** ** Traveling ** ** the ** ** Stream ** ** ** *************** *************/ /* Current value of file position indicator. Returns: * 0 on success * -1 on error, errno set */ long ftell (FILE *stream) { files_init(); if INSANE { _ioerror(EBADF); return -1; } return stream->pos; } /* Set file position indicator. Due to QVM, only read-mode seek works. Returns: * 0 on success * -1 on error, errno set */ int fseek (FILE *stream, long offset, int whence) { int retval; long target; int skipped; int newlen, newfd; #define JUMPSIZE 256 char z[JUMPSIZE]; //debug("fseek: start %d %d from %d\n", offset, whence, stream->pos); files_init(); if INSANE { _ioerror(EBADF); return -1; } if (WRITABLE) { _ioerror(EBADF); return -1; } switch (whence) { case SEEK_CUR: target = stream->pos + offset; break; case SEEK_SET: target = offset; break; case SEEK_END: target = stream->len - offset; break; /* Check interpretation on _END. Positive means plus or minus? */ default: _ioerror(EINVAL); _ferror; return -1; break; } //debug("fseek: targetting absolute offset %d\n", target); if (target < 0) target = 0; newlen = trap_FS_FOpenFile(stream->name, &(newfd), FS_READ); //debug("fseek: reopen => %d (%d)\n", newfd, newlen); if (!newfd) { _ioerror(EBADF); _ferror; return -1; } if (stream->fd > 0) trap_FS_FCloseFile(stream->fd); stream->fd = newfd; stream->len = newlen; stream->pos = 0; retval = 0; if (target > stream->len) { //debug("fseek: target exceeds filesize. Error\n"); retval = -1; _ioerror(1); target = stream->len; } // target = (target < stream->len) ? target : stream->len; while (target > 0) { skipped = (JUMPSIZE < target) ? JUMPSIZE : target; trap_FS_Read(z, skipped, stream->fd); stream->pos += skipped; target -= skipped; } // stream->errno = 0; //debug("fseek: settled on %d\n", stream->pos); stream->flags &= ~(FILE_BUFFED | FILE_ERROR); return retval; } int rewind (FILE *stream) { return fseek(stream, 0, FS_SEEK_SET); } /************ ************** ** ** ** LACHESIS ** ** ** ** File ** ** Status ** ** ** ************** ************/ /* Checks for EOF condition. Returns: * non-zero on End Of File * zero if not end of file */ int feof (FILE *stream) { files_init(); if INSANE return 1; /* If it's not a file, it ended. */ if (stream->pos >= stream->len) return 1; return 0; } /* Returns the underlying filedescriptor for stream. Returns -1 on error, and sets errno to EBADF. */ int fileno (FILE *stream) { files_init(); if INSANE { _ioerror(EBADF); return -1; } return stream->fd; } /* BSD manpage hints this just returns the truth of an error condition. */ int ferror (FILE *stream) { files_init(); if INSANE return EBADF; /* convenient. */ return ((stream->flags & FILE_ERROR) ? 1 : 0); // return stream->errno; } /* Clears the error marker (code) on stream. */ void clearerr (FILE *stream) { files_init(); if INSANE return; // stream->errno = ENONE; stream->flags &= ~(FILE_ERROR | FILE_BUFFED); } /************ ************** ** ** ** IRIS ** ** ** ** Messages ** ** ** ************** ************/ char *sys_errlist[sys_nerr]; /* Returns: * Error message associated with errnum * NULL on error */ char * strerror (int errnum) { if (*sys_errlist == 0) _errlist_init(); if ((errnum < 0) || (errnum > sys_nerr)) return NULL; return sys_errlist[errnum]; } /* Print error message, with prefix, to stderr. */ void perror (const char *s) { return; } /* Formatted output to string. */ int sprintf (char *str, const char *format, ...) { va_list argptr; int i; files_init(); va_start(argptr, format); i = vsprintf(str, format, argptr); va_end(argptr); return i; }