diff options
Diffstat (limited to 'libgfortran/io/unix.c')
-rw-r--r-- | libgfortran/io/unix.c | 1432 |
1 files changed, 1432 insertions, 0 deletions
diff --git a/libgfortran/io/unix.c b/libgfortran/io/unix.c new file mode 100644 index 00000000000..185608aba33 --- /dev/null +++ b/libgfortran/io/unix.c @@ -0,0 +1,1432 @@ +/* Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc. + Contributed by Andy Vaught + +This file is part of the GNU Fortran 95 runtime library (libgfortran). + +Libgfortran is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +Libgfortran is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Libgfortran; see the file COPYING. If not, write to +the Free Software Foundation, 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + +/* Unix stream I/O module */ + +#include "config.h" +#include <stdlib.h> +#include <limits.h> + +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <sys/mman.h> +#include <string.h> +#include <errno.h> + +#include "libgfortran.h" +#include "io.h" + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + +#ifndef MAP_FAILED +#define MAP_FAILED ((void *) -1) +#endif + +/* This implementation of stream I/O is based on the paper: + * + * "Exploiting the advantages of mapped files for stream I/O", + * O. Krieger, M. Stumm and R. Umrau, "Proceedings of the 1992 Winter + * USENIX conference", p. 27-42. + * + * It differs in a number of ways from the version described in the + * paper. First of all, threads are not an issue during I/O and we + * also don't have to worry about having multiple regions, since + * fortran's I/O model only allows you to be one place at a time. + * + * On the other hand, we have to be able to writing at the end of a + * stream, read from the start of a stream or read and write blocks of + * bytes from an arbitrary position. After opening a file, a pointer + * to a stream structure is returned, which is used to handle file + * accesses until the file is closed. + * + * salloc_at_r(stream, len, where)-- Given a stream pointer, return a + * pointer to a block of memory that mirror the file at position + * 'where' that is 'len' bytes long. The len integer is updated to + * reflect how many bytes were actually read. The only reason for a + * short read is end of file. The file pointer is updated. The + * pointer is valid until the next call to salloc_*. + * + * salloc_at_w(stream, len, where)-- Given the stream pointer, returns + * a pointer to a block of memory that is updated to reflect the state + * of the file. The length of the buffer is always equal to that + * requested. The buffer must be completely set by the caller. When + * data has been written, the sfree() function must be called to + * indicate that the caller is done writing data to the buffer. This + * may or may not cause a physical write. + * + * Short forms of these are salloc_r() and salloc_w() which drop the + * 'where' parameter and use the current file pointer. */ + + +#define BUFFER_SIZE 8192 + +typedef struct +{ + stream st; + + int fd; + offset_t buffer_offset; /* File offset of the start of the buffer */ + offset_t physical_offset; /* Current physical file offset */ + offset_t logical_offset; /* Current logical file offset */ + offset_t dirty_offset; /* Start of modified bytes in buffer */ + offset_t file_length; /* Length of the file, -1 if not seekable. */ + + char *buffer; + int len; /* Physical length of the current buffer */ + int active; /* Length of valid bytes in the buffer */ + + int prot; + int ndirty; /* Dirty bytes starting at dirty_offset */ + + unsigned unbuffered:1, mmaped:1; + + char small_buffer[BUFFER_SIZE]; + +} +unix_stream; + +/*move_pos_offset()-- Move the record pointer right or left + *relative to current position */ + +int +move_pos_offset (stream* st, int pos_off) +{ + unix_stream * str = (unix_stream*)st; + if (pos_off < 0) + { + str->active += pos_off; + if (str->active < 0) + str->active = 0; + + str->logical_offset += pos_off; + + if (str->dirty_offset+str->ndirty > str->logical_offset) + { + if (str->ndirty + pos_off > 0) + str->ndirty += pos_off ; + else + { + str->dirty_offset += pos_off + pos_off; + str->ndirty = 0 ; + } + } + + return pos_off ; + } + return 0 ; +} + + +/* fix_fd()-- Given a file descriptor, make sure it is not one of the + * standard descriptors, returning a non-standard descriptor. If the + * user specifies that system errors should go to standard output, + * then closes standard output, we don't want the system errors to a + * file that has been given file descriptor 1 or 0. We want to send + * the error to the invalid descriptor. */ + +static int +fix_fd (int fd) +{ + int input, output, error; + + input = output = error = 0; + +/* Unix allocates the lowest descriptors first, so a loop is not + * required, but this order is. */ + + if (fd == STDIN_FILENO) + { + fd = dup (fd); + input = 1; + } + if (fd == STDOUT_FILENO) + { + fd = dup (fd); + output = 1; + } + if (fd == STDERR_FILENO) + { + fd = dup (fd); + error = 1; + } + + if (input) + close (STDIN_FILENO); + if (output) + close (STDOUT_FILENO); + if (error) + close (STDERR_FILENO); + + return fd; +} + + +/* write()-- Write a buffer to a descriptor, allowing for short writes */ + +static int +writen (int fd, char *buffer, int len) +{ + int n, n0; + + n0 = len; + + while (len > 0) + { + n = write (fd, buffer, len); + if (n < 0) + return n; + + buffer += n; + len -= n; + } + + return n0; +} + + +#if 0 +/* readn()-- Read bytes into a buffer, allowing for short reads. If + * fewer than len bytes are returned, it is because we've hit the end + * of file. */ + +static int +readn (int fd, char *buffer, int len) +{ + int nread, n; + + nread = 0; + + while (len > 0) + { + n = read (fd, buffer, len); + if (n < 0) + return n; + + if (n == 0) + return nread; + + buffer += n; + nread += n; + len -= n; + } + + return nread; +} +#endif + + +/* get_oserror()-- Get the most recent operating system error. For + * unix, this is errno. */ + +const char * +get_oserror (void) +{ + + return strerror (errno); +} + + +/* sys_exit()-- Terminate the program with an exit code */ + +void +sys_exit (int code) +{ + + exit (code); +} + + + +/********************************************************************* + File descriptor stream functions +*********************************************************************/ + +/* fd_flush()-- Write bytes that need to be written */ + +static try +fd_flush (unix_stream * s) +{ + + if (s->ndirty == 0) + return SUCCESS;; + + if (s->physical_offset != s->dirty_offset && + lseek (s->fd, s->dirty_offset, SEEK_SET) < 0) + return FAILURE; + + if (writen (s->fd, s->buffer + (s->dirty_offset - s->buffer_offset), + s->ndirty) < 0) + return FAILURE; + + s->physical_offset = s->dirty_offset + s->ndirty; + if (s->physical_offset > s->file_length) + s->file_length = s->physical_offset; + s->ndirty = 0; + + return SUCCESS; +} + + +/* fd_alloc()-- Arrange a buffer such that the salloc() request can be + * satisfied. This subroutine gets the buffer ready for whatever is + * to come next. */ + +static void +fd_alloc (unix_stream * s, offset_t where, int *len) +{ + char *new_buffer; + int n, read_len; + + if (*len <= BUFFER_SIZE) + { + new_buffer = s->small_buffer; + read_len = BUFFER_SIZE; + } + else + { + new_buffer = get_mem (*len); + read_len = *len; + } + + /* Salvage bytes currently within the buffer. This is important for + * devices that cannot seek. */ + + if (s->buffer != NULL && s->buffer_offset <= where && + where <= s->buffer_offset + s->active) + { + + n = s->active - (where - s->buffer_offset); + memmove (new_buffer, s->buffer + (where - s->buffer_offset), n); + + s->active = n; + } + else + { /* new buffer starts off empty */ + s->active = 0; + } + + s->buffer_offset = where; + + /* free the old buffer if necessary */ + + if (s->buffer != NULL && s->buffer != s->small_buffer) + free_mem (s->buffer); + + s->buffer = new_buffer; + s->len = read_len; + s->mmaped = 0; +} + + +/* fd_alloc_r_at()-- Allocate a stream buffer for reading. Either + * we've already buffered the data or we need to load it. Returns + * NULL on I/O error. */ + +static char * +fd_alloc_r_at (unix_stream * s, int *len, offset_t where) +{ + offset_t m; + int n; + + if (where == -1) + where = s->logical_offset; + + if (s->buffer != NULL && s->buffer_offset <= where && + where + *len <= s->buffer_offset + s->active) + { + + /* Return a position within the current buffer */ + + s->logical_offset = where + *len; + return s->buffer + where - s->buffer_offset; + } + + fd_alloc (s, where, len); + + m = where + s->active; + + if (s->physical_offset != m && lseek (s->fd, m, SEEK_SET) < 0) + return NULL; + + n = read (s->fd, s->buffer + s->active, s->len - s->active); + if (n < 0) + return NULL; + + s->physical_offset = where + n; + + s->active += n; + if (s->active < *len) + *len = s->active; /* Bytes actually available */ + + s->logical_offset = where + *len; + + return s->buffer; +} + + +/* fd_alloc_w_at()-- Allocate a stream buffer for writing. Either + * we've already buffered the data or we need to load it. */ + +static char * +fd_alloc_w_at (unix_stream * s, int *len, offset_t where) +{ + offset_t n; + + if (where == -1) + where = s->logical_offset; + + if (s->buffer == NULL || s->buffer_offset > where || + where + *len > s->buffer_offset + s->len) + { + + if (fd_flush (s) == FAILURE) + return NULL; + fd_alloc (s, where, len); + } + + /* Return a position within the current buffer */ + + if (s->ndirty == 0) + { /* First write into a clean buffer */ + s->dirty_offset = where; + s->ndirty = *len; + } + else + { + if (s->dirty_offset + s->ndirty == where) + s->ndirty += *len; + else + fd_flush (s); /* Can't combine two dirty blocks */ + } + + s->logical_offset = where + *len; + + n = s->logical_offset - s->buffer_offset; + if (n > s->active) + s->active = n; + + return s->buffer + where - s->buffer_offset; +} + + +static try +fd_sfree (unix_stream * s) +{ + + if (s->ndirty != 0 && + (s->buffer != s->small_buffer || options.all_unbuffered || + s->unbuffered)) + return fd_flush (s); + + return SUCCESS; +} + + +static int +fd_seek (unix_stream * s, offset_t offset) +{ + + s->physical_offset = s->logical_offset = offset; + + return (lseek (s->fd, offset, SEEK_SET) < 0) ? FAILURE : SUCCESS; +} + + +/* truncate_file()-- Given a unit, truncate the file at the current + * position. Sets the physical location to the new end of the file. + * Returns nonzero on error. */ + +static try +fd_truncate (unix_stream * s) +{ + + if (ftruncate (s->fd, s->logical_offset)) + return FAILURE; + + s->physical_offset = s->file_length = s->logical_offset; + + if (lseek (s->fd, s->file_length, SEEK_SET) == -1) + return FAILURE; + + return SUCCESS; +} + + +static try +fd_close (unix_stream * s) +{ + + if (fd_flush (s) == FAILURE) + return FAILURE; + + if (s->buffer != NULL && s->buffer != s->small_buffer) + free_mem (s->buffer); + + if (close (s->fd) < 0) + return FAILURE; + + free_mem (s); + + return SUCCESS; +} + + +static void +fd_open (unix_stream * s) +{ + + if (isatty (s->fd)) + s->unbuffered = 1; + + s->st.alloc_r_at = (void *) fd_alloc_r_at; + s->st.alloc_w_at = (void *) fd_alloc_w_at; + s->st.sfree = (void *) fd_sfree; + s->st.close = (void *) fd_close; + s->st.seek = (void *) fd_seek; + s->st.truncate = (void *) fd_truncate; + + s->buffer = NULL; +} + + +/********************************************************************* + mmap stream functions + + Because mmap() is not capable of extending a file, we have to keep + track of how long the file is. We also have to be able to detect end + of file conditions. If there are multiple writers to the file (which + can only happen outside the current program), things will get + confused. Then again, things will get confused anyway. + +*********************************************************************/ + +#if HAVE_MMAP + +static int page_size, page_mask; + +/* mmap_flush()-- Deletes a memory mapping if something is mapped. */ + +static try +mmap_flush (unix_stream * s) +{ + + if (!s->mmaped) + return fd_flush (s); + + if (s->buffer == NULL) + return SUCCESS; + + if (munmap (s->buffer, s->active)) + return FAILURE; + + s->buffer = NULL; + s->active = 0; + + return SUCCESS; +} + + +/* mmap_alloc()-- mmap() a section of the file. The whole section is + * guaranteed to be mappable. */ + +static try +mmap_alloc (unix_stream * s, offset_t where, int *len) +{ + offset_t offset; + int length; + char *p; + + if (mmap_flush (s) == FAILURE) + return FAILURE; + + offset = where & page_mask; /* Round down to the next page */ + + length = ((where - offset) & page_mask) + 2 * page_size; + + p = mmap (NULL, length, s->prot, MAP_SHARED, s->fd, offset); + if (p == (char *) MAP_FAILED) + return FAILURE; + + s->mmaped = 1; + s->buffer = p; + s->buffer_offset = offset; + s->active = length; + + return SUCCESS; +} + + +static char * +mmap_alloc_r_at (unix_stream * s, int *len, offset_t where) +{ + offset_t m; + + if (where == -1) + where = s->logical_offset; + + m = where + *len; + + if ((s->buffer == NULL || s->buffer_offset > where || + m > s->buffer_offset + s->active) && + mmap_alloc (s, where, len) == FAILURE) + return NULL; + + if (m > s->file_length) + { + *len = s->file_length - s->logical_offset; + s->logical_offset = s->file_length; + } + else + s->logical_offset = m; + + return s->buffer + (where - s->buffer_offset); +} + + +static char * +mmap_alloc_w_at (unix_stream * s, int *len, offset_t where) +{ + if (where == -1) + where = s->logical_offset; + + /* If we're extending the file, we have to use file descriptor + * methods. */ + + if (where + *len > s->file_length) + { + if (s->mmaped) + mmap_flush (s); + return fd_alloc_w_at (s, len, where); + } + + if ((s->buffer == NULL || s->buffer_offset > where || + where + *len > s->buffer_offset + s->active) && + mmap_alloc (s, where, len) == FAILURE) + return NULL; + + s->logical_offset = where + *len; + + return s->buffer + where - s->buffer_offset; +} + + +static int +mmap_seek (unix_stream * s, offset_t offset) +{ + + s->logical_offset = offset; + return SUCCESS; +} + + +static try +mmap_close (unix_stream * s) +{ + try t; + + t = mmap_flush (s); + + if (close (s->fd) < 0) + t = FAILURE; + free_mem (s); + + return t; +} + + +static try +mmap_sfree (unix_stream * s) +{ + + return SUCCESS; +} + + +/* mmap_open()-- mmap_specific open. If the particular file cannot be + * mmap()-ed, we fall back to the file descriptor functions. */ + +static try +mmap_open (unix_stream * s) +{ + char *p; + int i; + + page_size = getpagesize (); + page_mask = ~0; + + p = mmap (0, page_size, s->prot, MAP_SHARED, s->fd, 0); + if (p == (char *) MAP_FAILED) + { + fd_open (s); + return SUCCESS; + } + + munmap (p, page_size); + + i = page_size >> 1; + while (i != 0) + { + page_mask <<= 1; + i >>= 1; + } + + s->st.alloc_r_at = (void *) mmap_alloc_r_at; + s->st.alloc_w_at = (void *) mmap_alloc_w_at; + s->st.sfree = (void *) mmap_sfree; + s->st.close = (void *) mmap_close; + s->st.seek = (void *) mmap_seek; + s->st.truncate = (void *) fd_truncate; + + if (lseek (s->fd, s->file_length, SEEK_SET) < 0) + return FAILURE; + + return SUCCESS; +} + +#endif + + +/********************************************************************* + memory stream functions - These are used for internal files + + The idea here is that a single stream structure is created and all + requests must be satisfied from it. The location and size of the + buffer is the character variable supplied to the READ or WRITE + statement. + +*********************************************************************/ + + +static char * +mem_alloc_r_at (unix_stream * s, int *len, offset_t where) +{ + offset_t n; + + if (where == -1) + where = s->logical_offset; + + if (where < s->buffer_offset || where > s->buffer_offset + s->active) + return NULL; + + if (is_internal_unit() && where + *len > s->file_length) + return NULL; + + s->logical_offset = where + *len; + + n = (where - s->buffer_offset) - s->active; + if (*len > n) + *len = n; + + return s->buffer + (where - s->buffer_offset); +} + + +static char * +mem_alloc_w_at (unix_stream * s, int *len, offset_t where) +{ + offset_t m; + + if (where == -1) + where = s->logical_offset; + + m = where + *len; + + if (where < s->buffer_offset || m > s->buffer_offset + s->active) + return NULL; + + s->logical_offset = m; + + return s->buffer + (where - s->buffer_offset); +} + + +static int +mem_seek (unix_stream * s, offset_t offset) +{ + + if (offset > s->file_length) + { + errno = ESPIPE; + return FAILURE; + } + + s->logical_offset = offset; + return SUCCESS; +} + + +static int +mem_truncate (unix_stream * s) +{ + + return SUCCESS; +} + + +static try +mem_close (unix_stream * s) +{ + + return SUCCESS; +} + + +static try +mem_sfree (unix_stream * s) +{ + + return SUCCESS; +} + + + +/********************************************************************* + Public functions -- A reimplementation of this module needs to + define functional equivalents of the following. +*********************************************************************/ + +/* empty_internal_buffer()-- Zero the buffer of Internal file */ + +void +empty_internal_buffer(stream *strm) +{ + unix_stream * s = (unix_stream *) strm; + memset(s->buffer, ' ', s->file_length); +} + +/* open_internal()-- Returns a stream structure from an internal file */ + +stream * +open_internal (char *base, int length) +{ + unix_stream *s; + + s = get_mem (sizeof (unix_stream)); + + s->buffer = base; + s->buffer_offset = 0; + + s->logical_offset = 0; + s->active = s->file_length = length; + + s->st.alloc_r_at = (void *) mem_alloc_r_at; + s->st.alloc_w_at = (void *) mem_alloc_w_at; + s->st.sfree = (void *) mem_sfree; + s->st.close = (void *) mem_close; + s->st.seek = (void *) mem_seek; + s->st.truncate = (void *) mem_truncate; + + return (stream *) s; +} + + +/* fd_to_stream()-- Given an open file descriptor, build a stream + * around it. */ + +static stream * +fd_to_stream (int fd, int prot) +{ + struct stat statbuf; + unix_stream *s; + + s = get_mem (sizeof (unix_stream)); + + s->fd = fd; + s->buffer_offset = 0; + s->physical_offset = 0; + s->logical_offset = 0; + s->prot = prot; + + /* Get the current length of the file. */ + + fstat (fd, &statbuf); + s->file_length = S_ISREG (statbuf.st_mode) ? statbuf.st_size : -1; + +#if HAVE_MMAP + mmap_open (s); +#else + fd_open (s); +#endif + + return (stream *) s; +} + + +/* unpack_filename()-- Given a fortran string and a pointer to a + * buffer that is PATH_MAX characters, convert the fortran string to a + * C string in the buffer. Returns nonzero if this is not possible. */ + +static int +unpack_filename (char *cstring, const char *fstring, int len) +{ + + len = fstrlen (fstring, len); + if (len >= PATH_MAX) + return 1; + + memmove (cstring, fstring, len); + cstring[len] = '\0'; + + return 0; +} + + +/* tempfile()-- Generate a temporary filename for a scratch file and + * open it. mkstemp() opens the file for reading and writing, but the + * library mode prevents anything that is not allowed. The descriptor + * is returns, which is less than zero on error. The template is + * pointed to by ioparm.file, which is copied into the unit structure + * and freed later. */ + +static int +tempfile (void) +{ + const char *tempdir; + char *template; + int fd; + + tempdir = getenv ("GFORTRAN_TMPDIR"); + if (tempdir == NULL) + tempdir = getenv ("TMP"); + if (tempdir == NULL) + tempdir = DEFAULT_TEMPDIR; + + template = get_mem (strlen (tempdir) + 20); + + st_sprintf (template, "%s/gfortantmpXXXXXX", tempdir); + + fd = mkstemp (template); + + if (fd < 0) + free_mem (template); + else + { + ioparm.file = template; + ioparm.file_len = strlen (template); /* Don't include trailing nul */ + } + + return fd; +} + + +/* regular_file()-- Open a regular file. Returns the descriptor, which is less than zero on error. */ + +static int +regular_file (unit_action action, unit_status status) +{ + char path[PATH_MAX + 1]; + struct stat statbuf; + int mode; + + if (unpack_filename (path, ioparm.file, ioparm.file_len)) + { + errno = ENOENT; /* Fake an OS error */ + return -1; + } + + mode = 0; + + switch (action) + { + case ACTION_READ: + mode = O_RDONLY; + break; + + case ACTION_WRITE: + mode = O_WRONLY; + break; + + case ACTION_READWRITE: + mode = O_RDWR; + break; + + default: + internal_error ("regular_file(): Bad action"); + } + + switch (status) + { + case STATUS_NEW: + mode |= O_CREAT | O_EXCL; + break; + + case STATUS_OLD: /* file must exist, so check for its existence */ + if (stat (path, &statbuf) < 0) + return -1; + break; + + case STATUS_UNKNOWN: + case STATUS_SCRATCH: + mode |= O_CREAT; + break; + + case STATUS_REPLACE: + mode |= O_TRUNC; + break; + + default: + internal_error ("regular_file(): Bad status"); + } + + // mode |= O_LARGEFILE; + + return open (path, mode, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); +} + + +/* open_external()-- Open an external file, unix specific version. + * Returns NULL on operating system error. */ + +stream * +open_external (unit_action action, unit_status status) +{ + int fd, prot; + + fd = + (status == STATUS_SCRATCH) ? tempfile () : regular_file (action, status); + + if (fd < 0) + return NULL; + fd = fix_fd (fd); + + switch (action) + { + case ACTION_READ: + prot = PROT_READ; + break; + + case ACTION_WRITE: + prot = PROT_WRITE; + break; + + case ACTION_READWRITE: + prot = PROT_READ | PROT_WRITE; + break; + + default: + internal_error ("open_external(): Bad action"); + } + + /* If this is a scratch file, we can unlink it now and the file will + * go away when it is closed. */ + + if (status == STATUS_SCRATCH) + unlink (ioparm.file); + + return fd_to_stream (fd, prot); +} + + +/* input_stream()-- Return a stream pointer to the default input stream. + * Called on initialization. */ + +stream * +input_stream (void) +{ + + return fd_to_stream (STDIN_FILENO, PROT_READ); +} + + +/* output_stream()-- Return a stream pointer to the default input stream. + * Called on initialization. */ + +stream * +output_stream (void) +{ + + return fd_to_stream (STDOUT_FILENO, PROT_WRITE); +} + + +/* init_error_stream()-- Return a pointer to the error stream. This + * subroutine is called when the stream is needed, rather than at + * initialization. We want to work even if memory has been seriously + * corrupted. */ + +stream * +init_error_stream (void) +{ + static unix_stream error; + + memset (&error, '\0', sizeof (error)); + + error.fd = options.use_stderr ? STDERR_FILENO : STDOUT_FILENO; + + error.st.alloc_w_at = (void *) fd_alloc_w_at; + error.st.sfree = (void *) fd_sfree; + + error.unbuffered = 1; + error.buffer = error.small_buffer; + + return (stream *) & error; +} + + +/* compare_file_filename()-- Given an open stream and a fortran string + * that is a filename, figure out if the file is the same as the + * filename. */ + +int +compare_file_filename (stream * s, const char *name, int len) +{ + char path[PATH_MAX + 1]; + struct stat st1, st2; + + if (unpack_filename (path, name, len)) + return 0; /* Can't be the same */ + + /* If the filename doesn't exist, then there is no match with the + * existing file. */ + + if (stat (path, &st1) < 0) + return 0; + + fstat (((unix_stream *) s)->fd, &st2); + + return (st1.st_dev == st2.st_dev) && (st1.st_ino == st2.st_ino); +} + + +/* find_file0()-- Recursive work function for find_file() */ + +static unit_t * +find_file0 (unit_t * u, struct stat *st1) +{ + struct stat st2; + unit_t *v; + + if (u == NULL) + return NULL; + + if (fstat (((unix_stream *) u->s)->fd, &st2) >= 0 && + st1->st_dev == st2.st_dev && st1->st_ino == st2.st_ino) + return u; + + v = find_file0 (u->left, st1); + if (v != NULL) + return v; + + v = find_file0 (u->right, st1); + if (v != NULL) + return v; + + return NULL; +} + + +/* find_file()-- Take the current filename and see if there is a unit + * that has the file already open. Returns a pointer to the unit if so. */ + +unit_t * +find_file (void) +{ + char path[PATH_MAX + 1]; + struct stat statbuf; + + if (unpack_filename (path, ioparm.file, ioparm.file_len)) + return NULL; + + if (stat (path, &statbuf) < 0) + return NULL; + + return find_file0 (g.unit_root, &statbuf); +} + + +/* stream_at_bof()-- Returns nonzero if the stream is at the beginning + * of the file. */ + +int +stream_at_bof (stream * s) +{ + unix_stream *us; + + us = (unix_stream *) s; + + if (!us->mmaped) + return 0; /* File is not seekable */ + + return us->logical_offset == 0; +} + + +/* stream_at_eof()-- Returns nonzero if the stream is at the beginning + * of the file. */ + +int +stream_at_eof (stream * s) +{ + unix_stream *us; + + us = (unix_stream *) s; + + if (!us->mmaped) + return 0; /* File is not seekable */ + + return us->logical_offset == us->dirty_offset; +} + + +/* delete_file()-- Given a unit structure, delete the file associated + * with the unit. Returns nonzero if something went wrong. */ + +int +delete_file (unit_t * u) +{ + char path[PATH_MAX + 1]; + + if (unpack_filename (path, u->file, u->file_len)) + { /* Shouldn't be possible */ + errno = ENOENT; + return 1; + } + + return unlink (path); +} + + +/* file_exists()-- Returns nonzero if the current filename exists on + * the system */ + +int +file_exists (void) +{ + char path[PATH_MAX + 1]; + struct stat statbuf; + + if (unpack_filename (path, ioparm.file, ioparm.file_len)) + return 0; + + if (stat (path, &statbuf) < 0) + return 0; + + return 1; +} + + + +static const char *yes = "YES", *no = "NO", *unknown = "UNKNOWN"; + +/* inquire_sequential()-- Given a fortran string, determine if the + * file is suitable for sequential access. Returns a C-style + * string. */ + +const char * +inquire_sequential (const char *string, int len) +{ + char path[PATH_MAX + 1]; + struct stat statbuf; + + if (string == NULL || + unpack_filename (path, string, len) || stat (path, &statbuf) < 0) + return unknown; + + if (S_ISREG (statbuf.st_mode) || + S_ISCHR (statbuf.st_mode) || S_ISFIFO (statbuf.st_mode)) + return yes; + + if (S_ISDIR (statbuf.st_mode) || S_ISBLK (statbuf.st_mode)) + return no; + + return unknown; +} + + +/* inquire_direct()-- Given a fortran string, determine if the file is + * suitable for direct access. Returns a C-style string. */ + +const char * +inquire_direct (const char *string, int len) +{ + char path[PATH_MAX + 1]; + struct stat statbuf; + + if (string == NULL || + unpack_filename (path, string, len) || stat (path, &statbuf) < 0) + return unknown; + + if (S_ISREG (statbuf.st_mode) || S_ISBLK (statbuf.st_mode)) + return yes; + + if (S_ISDIR (statbuf.st_mode) || + S_ISCHR (statbuf.st_mode) || S_ISFIFO (statbuf.st_mode)) + return no; + + return unknown; +} + + +/* inquire_formatted()-- Given a fortran string, determine if the file + * is suitable for formatted form. Returns a C-style string. */ + +const char * +inquire_formatted (const char *string, int len) +{ + char path[PATH_MAX + 1]; + struct stat statbuf; + + if (string == NULL || + unpack_filename (path, string, len) || stat (path, &statbuf) < 0) + return unknown; + + if (S_ISREG (statbuf.st_mode) || + S_ISBLK (statbuf.st_mode) || + S_ISCHR (statbuf.st_mode) || S_ISFIFO (statbuf.st_mode)) + return yes; + + if (S_ISDIR (statbuf.st_mode)) + return no; + + return unknown; +} + + +/* inquire_unformatted()-- Given a fortran string, determine if the file + * is suitable for unformatted form. Returns a C-style string. */ + +const char * +inquire_unformatted (const char *string, int len) +{ + + return inquire_formatted (string, len); +} + + +/* inquire_access()-- Given a fortran string, determine if the file is + * suitable for access. */ + +static const char * +inquire_access (const char *string, int len, int mode) +{ + char path[PATH_MAX + 1]; + + if (string == NULL || unpack_filename (path, string, len) || + access (path, mode) < 0) + return no; + + return yes; +} + + +/* inquire_read()-- Given a fortran string, determine if the file is + * suitable for READ access. */ + +const char * +inquire_read (const char *string, int len) +{ + + return inquire_access (string, len, R_OK); +} + + +/* inquire_write()-- Given a fortran string, determine if the file is + * suitable for READ access. */ + +const char * +inquire_write (const char *string, int len) +{ + + return inquire_access (string, len, W_OK); +} + + +/* inquire_readwrite()-- Given a fortran string, determine if the file is + * suitable for read and write access. */ + +const char * +inquire_readwrite (const char *string, int len) +{ + + return inquire_access (string, len, R_OK | W_OK); +} + + +/* file_length()-- Return the file length in bytes, -1 if unknown */ + +offset_t +file_length (stream * s) +{ + + return ((unix_stream *) s)->file_length; +} + + +/* file_position()-- Return the current position of the file */ + +offset_t +file_position (stream * s) +{ + + return ((unix_stream *) s)->logical_offset; +} + + +/* is_seekable()-- Return nonzero if the stream is seekable, zero if + * it is not */ + +int +is_seekable (stream * s) +{ + + return ((unix_stream *) s)->mmaped; +} + + +/* How files are stored: This is an operating-system specific issue, + and therefore belongs here. There are three cases to consider. + + Direct Access: + Records are written as block of bytes corresponding to the record + length of the file. This goes for both formatted and unformatted + records. Positioning is done explicitly for each data transfer, + so positioning is not much of an issue. + + Sequential Formatted: + Records are separated by newline characters. The newline character + is prohibited from appearing in a string. If it does, this will be + messed up on the next read. End of file is also the end of a record. + + Sequential Unformatted: + In this case, we are merely copying bytes to and from main storage, + yet we need to keep track of varying record lengths. We adopt + the solution used by f2c. Each record contains a pair of length + markers: + + Length of record n in bytes + Data of record n + Length of record n in bytes + + Length of record n+1 in bytes + Data of record n+1 + Length of record n+1 in bytes + + The length is stored at the end of a record to allow backspacing to the + previous record. Between data transfer statements, the file pointer + is left pointing to the first length of the current record. + + ENDFILE records are never explicitly stored. + +*/ |