| /* Copyright (C) 2002-2019 Free Software Foundation, Inc. |
| Contributed by Andy Vaught |
| F2003 I/O support contributed by Jerry DeLisle |
| |
| This file is part of the GNU Fortran 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 3, 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. |
| |
| Under Section 7 of GPL version 3, you are granted additional |
| permissions described in the GCC Runtime Library Exception, version |
| 3.1, as published by the Free Software Foundation. |
| |
| You should have received a copy of the GNU General Public License and |
| a copy of the GCC Runtime Library Exception along with this program; |
| see the files COPYING3 and COPYING.RUNTIME respectively. If not, see |
| <http://www.gnu.org/licenses/>. */ |
| |
| /* Unix stream I/O module */ |
| |
| #include "io.h" |
| #include "unix.h" |
| #include "async.h" |
| #include <limits.h> |
| |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| |
| #include <string.h> |
| #include <errno.h> |
| |
| |
| /* For mingw, we don't identify files by their inode number, but by a |
| 64-bit identifier created from a BY_HANDLE_FILE_INFORMATION. */ |
| #ifdef __MINGW32__ |
| |
| #define WIN32_LEAN_AND_MEAN |
| #include <windows.h> |
| |
| #if !defined(_FILE_OFFSET_BITS) || _FILE_OFFSET_BITS != 64 |
| #undef lseek |
| #define lseek _lseeki64 |
| #undef fstat |
| #define fstat _fstati64 |
| #undef stat |
| #define stat _stati64 |
| #endif |
| |
| #ifndef HAVE_WORKING_STAT |
| static uint64_t |
| id_from_handle (HANDLE hFile) |
| { |
| BY_HANDLE_FILE_INFORMATION FileInformation; |
| |
| if (hFile == INVALID_HANDLE_VALUE) |
| return 0; |
| |
| memset (&FileInformation, 0, sizeof(FileInformation)); |
| if (!GetFileInformationByHandle (hFile, &FileInformation)) |
| return 0; |
| |
| return ((uint64_t) FileInformation.nFileIndexLow) |
| | (((uint64_t) FileInformation.nFileIndexHigh) << 32); |
| } |
| |
| |
| static uint64_t |
| id_from_path (const char *path) |
| { |
| HANDLE hFile; |
| uint64_t res; |
| |
| if (!path || !*path || access (path, F_OK)) |
| return (uint64_t) -1; |
| |
| hFile = CreateFile (path, 0, 0, NULL, OPEN_EXISTING, |
| FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_READONLY, |
| NULL); |
| res = id_from_handle (hFile); |
| CloseHandle (hFile); |
| return res; |
| } |
| |
| |
| static uint64_t |
| id_from_fd (const int fd) |
| { |
| return id_from_handle ((HANDLE) _get_osfhandle (fd)); |
| } |
| |
| #endif /* HAVE_WORKING_STAT */ |
| |
| |
| /* On mingw, we don't use umask in tempfile_open(), because it |
| doesn't support the user/group/other-based permissions. */ |
| #undef HAVE_UMASK |
| |
| #endif /* __MINGW32__ */ |
| |
| |
| /* These flags aren't defined on all targets (mingw32), so provide them |
| here. */ |
| #ifndef S_IRGRP |
| #define S_IRGRP 0 |
| #endif |
| |
| #ifndef S_IWGRP |
| #define S_IWGRP 0 |
| #endif |
| |
| #ifndef S_IROTH |
| #define S_IROTH 0 |
| #endif |
| |
| #ifndef S_IWOTH |
| #define S_IWOTH 0 |
| #endif |
| |
| |
| #ifndef HAVE_ACCESS |
| |
| #ifndef W_OK |
| #define W_OK 2 |
| #endif |
| |
| #ifndef R_OK |
| #define R_OK 4 |
| #endif |
| |
| #ifndef F_OK |
| #define F_OK 0 |
| #endif |
| |
| /* Fallback implementation of access() on systems that don't have it. |
| Only modes R_OK, W_OK and F_OK are used in this file. */ |
| |
| static int |
| fallback_access (const char *path, int mode) |
| { |
| int fd; |
| |
| if (mode & R_OK) |
| { |
| if ((fd = open (path, O_RDONLY)) < 0) |
| return -1; |
| else |
| close (fd); |
| } |
| |
| if (mode & W_OK) |
| { |
| if ((fd = open (path, O_WRONLY)) < 0) |
| return -1; |
| else |
| close (fd); |
| } |
| |
| if (mode == F_OK) |
| { |
| struct stat st; |
| return stat (path, &st); |
| } |
| |
| return 0; |
| } |
| |
| #undef access |
| #define access fallback_access |
| #endif |
| |
| |
| /* Fallback directory for creating temporary files. P_tmpdir is |
| defined on many POSIX platforms. */ |
| #ifndef P_tmpdir |
| #ifdef _P_tmpdir |
| #define P_tmpdir _P_tmpdir /* MinGW */ |
| #else |
| #define P_tmpdir "/tmp" |
| #endif |
| #endif |
| |
| |
| /* Unix and internal stream I/O module */ |
| |
| static const int FORMATTED_BUFFER_SIZE_DEFAULT = 8192; |
| static const int UNFORMATTED_BUFFER_SIZE_DEFAULT = 128*1024; |
| |
| typedef struct |
| { |
| stream st; |
| |
| gfc_offset buffer_offset; /* File offset of the start of the buffer */ |
| gfc_offset physical_offset; /* Current physical file offset */ |
| gfc_offset logical_offset; /* Current logical file offset */ |
| gfc_offset file_length; /* Length of the file. */ |
| |
| char *buffer; /* Pointer to the buffer. */ |
| ssize_t buffer_size; /* Length of the buffer. */ |
| int fd; /* The POSIX file descriptor. */ |
| |
| int active; /* Length of valid bytes in the buffer */ |
| |
| int ndirty; /* Dirty bytes starting at buffer_offset */ |
| |
| /* Cached stat(2) values. */ |
| dev_t st_dev; |
| ino_t st_ino; |
| |
| bool unbuffered; /* Buffer should be flushed after each I/O statement. */ |
| } |
| unix_stream; |
| |
| |
| /* 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) |
| { |
| #ifdef HAVE_DUP |
| 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); |
| #endif |
| |
| return fd; |
| } |
| |
| |
| /* If the stream corresponds to a preconnected unit, we flush the |
| corresponding C stream. This is bugware for mixed C-Fortran codes |
| where the C code doesn't flush I/O before returning. */ |
| void |
| flush_if_preconnected (stream *s) |
| { |
| int fd; |
| |
| fd = ((unix_stream *) s)->fd; |
| if (fd == STDIN_FILENO) |
| fflush (stdin); |
| else if (fd == STDOUT_FILENO) |
| fflush (stdout); |
| else if (fd == STDERR_FILENO) |
| fflush (stderr); |
| } |
| |
| |
| /******************************************************************** |
| Raw I/O functions (read, write, seek, tell, truncate, close). |
| |
| These functions wrap the basic POSIX I/O syscalls. Any deviation in |
| semantics is a bug, except the following: write restarts in case |
| of being interrupted by a signal, and as the first argument the |
| functions take the unix_stream struct rather than an integer file |
| descriptor. Also, for POSIX read() and write() a nbyte argument larger |
| than SSIZE_MAX is undefined; here the type of nbyte is ssize_t rather |
| than size_t as for POSIX read/write. |
| *********************************************************************/ |
| |
| static int |
| raw_flush (unix_stream *s __attribute__ ((unused))) |
| { |
| return 0; |
| } |
| |
| /* Write/read at most 2 GB - 4k chunks at a time. Linux never reads or |
| writes more than this, and there are reports that macOS fails for |
| larger than 2 GB as well. */ |
| #define MAX_CHUNK 2147479552 |
| |
| static ssize_t |
| raw_read (unix_stream *s, void *buf, ssize_t nbyte) |
| { |
| /* For read we can't do I/O in a loop like raw_write does, because |
| that will break applications that wait for interactive I/O. We |
| still can loop around EINTR, though. This however causes a |
| problem for large reads which must be chunked, see comment above. |
| So assume that if the size is larger than the chunk size, we're |
| reading from a file and not the terminal. */ |
| if (nbyte <= MAX_CHUNK) |
| { |
| while (true) |
| { |
| ssize_t trans = read (s->fd, buf, nbyte); |
| if (trans == -1 && errno == EINTR) |
| continue; |
| return trans; |
| } |
| } |
| else |
| { |
| ssize_t bytes_left = nbyte; |
| char *buf_st = buf; |
| while (bytes_left > 0) |
| { |
| ssize_t to_read = bytes_left < MAX_CHUNK ? bytes_left: MAX_CHUNK; |
| ssize_t trans = read (s->fd, buf_st, to_read); |
| if (trans == -1) |
| { |
| if (errno == EINTR) |
| continue; |
| else |
| return trans; |
| } |
| buf_st += trans; |
| bytes_left -= trans; |
| } |
| return nbyte - bytes_left; |
| } |
| } |
| |
| static ssize_t |
| raw_write (unix_stream *s, const void *buf, ssize_t nbyte) |
| { |
| ssize_t trans, bytes_left; |
| char *buf_st; |
| |
| bytes_left = nbyte; |
| buf_st = (char *) buf; |
| |
| /* We must write in a loop since some systems don't restart system |
| calls in case of a signal. Also some systems might fail outright |
| if we try to write more than 2 GB in a single syscall, so chunk |
| up large writes. */ |
| while (bytes_left > 0) |
| { |
| ssize_t to_write = bytes_left < MAX_CHUNK ? bytes_left: MAX_CHUNK; |
| trans = write (s->fd, buf_st, to_write); |
| if (trans == -1) |
| { |
| if (errno == EINTR) |
| continue; |
| else |
| return trans; |
| } |
| buf_st += trans; |
| bytes_left -= trans; |
| } |
| |
| return nbyte - bytes_left; |
| } |
| |
| static gfc_offset |
| raw_seek (unix_stream *s, gfc_offset offset, int whence) |
| { |
| while (true) |
| { |
| gfc_offset off = lseek (s->fd, offset, whence); |
| if (off == (gfc_offset) -1 && errno == EINTR) |
| continue; |
| return off; |
| } |
| } |
| |
| static gfc_offset |
| raw_tell (unix_stream *s) |
| { |
| while (true) |
| { |
| gfc_offset off = lseek (s->fd, 0, SEEK_CUR); |
| if (off == (gfc_offset) -1 && errno == EINTR) |
| continue; |
| return off; |
| } |
| } |
| |
| static gfc_offset |
| raw_size (unix_stream *s) |
| { |
| struct stat statbuf; |
| if (TEMP_FAILURE_RETRY (fstat (s->fd, &statbuf)) == -1) |
| return -1; |
| if (S_ISREG (statbuf.st_mode)) |
| return statbuf.st_size; |
| else |
| return 0; |
| } |
| |
| static int |
| raw_truncate (unix_stream *s, gfc_offset length) |
| { |
| #ifdef __MINGW32__ |
| HANDLE h; |
| gfc_offset cur; |
| |
| if (isatty (s->fd)) |
| { |
| errno = EBADF; |
| return -1; |
| } |
| h = (HANDLE) _get_osfhandle (s->fd); |
| if (h == INVALID_HANDLE_VALUE) |
| { |
| errno = EBADF; |
| return -1; |
| } |
| cur = lseek (s->fd, 0, SEEK_CUR); |
| if (cur == -1) |
| return -1; |
| if (lseek (s->fd, length, SEEK_SET) == -1) |
| goto error; |
| if (!SetEndOfFile (h)) |
| { |
| errno = EBADF; |
| goto error; |
| } |
| if (lseek (s->fd, cur, SEEK_SET) == -1) |
| return -1; |
| return 0; |
| error: |
| lseek (s->fd, cur, SEEK_SET); |
| return -1; |
| #elif defined HAVE_FTRUNCATE |
| if (TEMP_FAILURE_RETRY (ftruncate (s->fd, length)) == -1) |
| return -1; |
| return 0; |
| #elif defined HAVE_CHSIZE |
| return chsize (s->fd, length); |
| #else |
| runtime_error ("required ftruncate or chsize support not present"); |
| return -1; |
| #endif |
| } |
| |
| static int |
| raw_close (unix_stream *s) |
| { |
| int retval; |
| |
| if (s->fd == -1) |
| retval = -1; |
| else if (s->fd != STDOUT_FILENO |
| && s->fd != STDERR_FILENO |
| && s->fd != STDIN_FILENO) |
| { |
| retval = close (s->fd); |
| /* close() and EINTR is special, as the file descriptor is |
| deallocated before doing anything that might cause the |
| operation to be interrupted. Thus if we get EINTR the best we |
| can do is ignore it and continue (otherwise if we try again |
| the file descriptor may have been allocated again to some |
| other file). */ |
| if (retval == -1 && errno == EINTR) |
| retval = errno = 0; |
| } |
| else |
| retval = 0; |
| free (s); |
| return retval; |
| } |
| |
| static int |
| raw_markeor (unix_stream *s __attribute__ ((unused))) |
| { |
| return 0; |
| } |
| |
| static const struct stream_vtable raw_vtable = { |
| .read = (void *) raw_read, |
| .write = (void *) raw_write, |
| .seek = (void *) raw_seek, |
| .tell = (void *) raw_tell, |
| .size = (void *) raw_size, |
| .trunc = (void *) raw_truncate, |
| .close = (void *) raw_close, |
| .flush = (void *) raw_flush, |
| .markeor = (void *) raw_markeor |
| }; |
| |
| static int |
| raw_init (unix_stream *s) |
| { |
| s->st.vptr = &raw_vtable; |
| |
| s->buffer = NULL; |
| return 0; |
| } |
| |
| |
| /********************************************************************* |
| Buffered I/O functions. These functions have the same semantics as the |
| raw I/O functions above, except that they are buffered in order to |
| improve performance. The buffer must be flushed when switching from |
| reading to writing and vice versa. |
| *********************************************************************/ |
| |
| static int |
| buf_flush (unix_stream *s) |
| { |
| int writelen; |
| |
| /* Flushing in read mode means discarding read bytes. */ |
| s->active = 0; |
| |
| if (s->ndirty == 0) |
| return 0; |
| |
| if (s->physical_offset != s->buffer_offset |
| && raw_seek (s, s->buffer_offset, SEEK_SET) < 0) |
| return -1; |
| |
| writelen = raw_write (s, s->buffer, s->ndirty); |
| |
| s->physical_offset = s->buffer_offset + writelen; |
| |
| if (s->physical_offset > s->file_length) |
| s->file_length = s->physical_offset; |
| |
| s->ndirty -= writelen; |
| if (s->ndirty != 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| static ssize_t |
| buf_read (unix_stream *s, void *buf, ssize_t nbyte) |
| { |
| if (s->active == 0) |
| s->buffer_offset = s->logical_offset; |
| |
| /* Is the data we want in the buffer? */ |
| if (s->logical_offset + nbyte <= s->buffer_offset + s->active |
| && s->buffer_offset <= s->logical_offset) |
| { |
| /* When nbyte == 0, buf can be NULL which would lead to undefined |
| behavior if we called memcpy(). */ |
| if (nbyte != 0) |
| memcpy (buf, s->buffer + (s->logical_offset - s->buffer_offset), |
| nbyte); |
| } |
| else |
| { |
| /* First copy the active bytes if applicable, then read the rest |
| either directly or filling the buffer. */ |
| char *p; |
| int nread = 0; |
| ssize_t to_read, did_read; |
| gfc_offset new_logical; |
| |
| p = (char *) buf; |
| if (s->logical_offset >= s->buffer_offset |
| && s->buffer_offset + s->active >= s->logical_offset) |
| { |
| nread = s->active - (s->logical_offset - s->buffer_offset); |
| memcpy (buf, s->buffer + (s->logical_offset - s->buffer_offset), |
| nread); |
| p += nread; |
| } |
| /* At this point we consider all bytes in the buffer discarded. */ |
| to_read = nbyte - nread; |
| new_logical = s->logical_offset + nread; |
| if (s->physical_offset != new_logical |
| && raw_seek (s, new_logical, SEEK_SET) < 0) |
| return -1; |
| s->buffer_offset = s->physical_offset = new_logical; |
| if (to_read <= s->buffer_size/2) |
| { |
| did_read = raw_read (s, s->buffer, s->buffer_size); |
| if (likely (did_read >= 0)) |
| { |
| s->physical_offset += did_read; |
| s->active = did_read; |
| did_read = (did_read > to_read) ? to_read : did_read; |
| memcpy (p, s->buffer, did_read); |
| } |
| else |
| return did_read; |
| } |
| else |
| { |
| did_read = raw_read (s, p, to_read); |
| if (likely (did_read >= 0)) |
| { |
| s->physical_offset += did_read; |
| s->active = 0; |
| } |
| else |
| return did_read; |
| } |
| nbyte = did_read + nread; |
| } |
| s->logical_offset += nbyte; |
| return nbyte; |
| } |
| |
| static ssize_t |
| buf_write (unix_stream *s, const void *buf, ssize_t nbyte) |
| { |
| if (nbyte == 0) |
| return 0; |
| |
| if (s->ndirty == 0) |
| s->buffer_offset = s->logical_offset; |
| |
| /* Does the data fit into the buffer? As a special case, if the |
| buffer is empty and the request is bigger than s->buffer_size/2, |
| write directly. This avoids the case where the buffer would have |
| to be flushed at every write. */ |
| if (!(s->ndirty == 0 && nbyte > s->buffer_size/2) |
| && s->logical_offset + nbyte <= s->buffer_offset + s->buffer_size |
| && s->buffer_offset <= s->logical_offset |
| && s->buffer_offset + s->ndirty >= s->logical_offset) |
| { |
| memcpy (s->buffer + (s->logical_offset - s->buffer_offset), buf, nbyte); |
| int nd = (s->logical_offset - s->buffer_offset) + nbyte; |
| if (nd > s->ndirty) |
| s->ndirty = nd; |
| } |
| else |
| { |
| /* Flush, and either fill the buffer with the new data, or if |
| the request is bigger than the buffer size, write directly |
| bypassing the buffer. */ |
| buf_flush (s); |
| if (nbyte <= s->buffer_size/2) |
| { |
| memcpy (s->buffer, buf, nbyte); |
| s->buffer_offset = s->logical_offset; |
| s->ndirty += nbyte; |
| } |
| else |
| { |
| if (s->physical_offset != s->logical_offset) |
| { |
| if (raw_seek (s, s->logical_offset, SEEK_SET) < 0) |
| return -1; |
| s->physical_offset = s->logical_offset; |
| } |
| |
| nbyte = raw_write (s, buf, nbyte); |
| s->physical_offset += nbyte; |
| } |
| } |
| s->logical_offset += nbyte; |
| if (s->logical_offset > s->file_length) |
| s->file_length = s->logical_offset; |
| return nbyte; |
| } |
| |
| |
| /* "Unbuffered" really means I/O statement buffering. For formatted |
| I/O, the fbuf manages this, and then uses raw I/O. For unformatted |
| I/O, buffered I/O is used, and the buffer is flushed at the end of |
| each I/O statement, where this function is called. Alternatively, |
| the buffer is flushed at the end of the record if the buffer is |
| more than half full; this prevents needless seeking back and forth |
| when writing sequential unformatted. */ |
| |
| static int |
| buf_markeor (unix_stream *s) |
| { |
| if (s->unbuffered || s->ndirty >= s->buffer_size / 2) |
| return buf_flush (s); |
| return 0; |
| } |
| |
| static gfc_offset |
| buf_seek (unix_stream *s, gfc_offset offset, int whence) |
| { |
| switch (whence) |
| { |
| case SEEK_SET: |
| break; |
| case SEEK_CUR: |
| offset += s->logical_offset; |
| break; |
| case SEEK_END: |
| offset += s->file_length; |
| break; |
| default: |
| return -1; |
| } |
| if (offset < 0) |
| { |
| errno = EINVAL; |
| return -1; |
| } |
| s->logical_offset = offset; |
| return offset; |
| } |
| |
| static gfc_offset |
| buf_tell (unix_stream *s) |
| { |
| return buf_seek (s, 0, SEEK_CUR); |
| } |
| |
| static gfc_offset |
| buf_size (unix_stream *s) |
| { |
| return s->file_length; |
| } |
| |
| static int |
| buf_truncate (unix_stream *s, gfc_offset length) |
| { |
| int r; |
| |
| if (buf_flush (s) != 0) |
| return -1; |
| r = raw_truncate (s, length); |
| if (r == 0) |
| s->file_length = length; |
| return r; |
| } |
| |
| static int |
| buf_close (unix_stream *s) |
| { |
| if (buf_flush (s) != 0) |
| return -1; |
| free (s->buffer); |
| return raw_close (s); |
| } |
| |
| static const struct stream_vtable buf_vtable = { |
| .read = (void *) buf_read, |
| .write = (void *) buf_write, |
| .seek = (void *) buf_seek, |
| .tell = (void *) buf_tell, |
| .size = (void *) buf_size, |
| .trunc = (void *) buf_truncate, |
| .close = (void *) buf_close, |
| .flush = (void *) buf_flush, |
| .markeor = (void *) buf_markeor |
| }; |
| |
| static int |
| buf_init (unix_stream *s, bool unformatted) |
| { |
| s->st.vptr = &buf_vtable; |
| |
| /* Try to guess a good value for the buffer size. For formatted |
| I/O, we use so many CPU cycles converting the data that there is |
| more sense in converving memory and especially cache. For |
| unformatted, a bigger block can have a large impact in some |
| environments. */ |
| |
| if (unformatted) |
| { |
| if (options.unformatted_buffer_size > 0) |
| s->buffer_size = options.unformatted_buffer_size; |
| else |
| s->buffer_size = UNFORMATTED_BUFFER_SIZE_DEFAULT; |
| } |
| else |
| { |
| if (options.formatted_buffer_size > 0) |
| s->buffer_size = options.formatted_buffer_size; |
| else |
| s->buffer_size = FORMATTED_BUFFER_SIZE_DEFAULT; |
| } |
| |
| s->buffer = xmalloc (s->buffer_size); |
| return 0; |
| } |
| |
| |
| /********************************************************************* |
| 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. |
| |
| *********************************************************************/ |
| |
| char * |
| mem_alloc_r (stream *strm, size_t *len) |
| { |
| unix_stream *s = (unix_stream *) strm; |
| gfc_offset n; |
| gfc_offset where = s->logical_offset; |
| |
| if (where < s->buffer_offset || where > s->buffer_offset + s->active) |
| return NULL; |
| |
| n = s->buffer_offset + s->active - where; |
| if ((gfc_offset) *len > n) |
| *len = n; |
| |
| s->logical_offset = where + *len; |
| |
| return s->buffer + (where - s->buffer_offset); |
| } |
| |
| |
| char * |
| mem_alloc_r4 (stream *strm, size_t *len) |
| { |
| unix_stream *s = (unix_stream *) strm; |
| gfc_offset n; |
| gfc_offset where = s->logical_offset; |
| |
| if (where < s->buffer_offset || where > s->buffer_offset + s->active) |
| return NULL; |
| |
| n = s->buffer_offset + s->active - where; |
| if ((gfc_offset) *len > n) |
| *len = n; |
| |
| s->logical_offset = where + *len; |
| |
| return s->buffer + (where - s->buffer_offset) * 4; |
| } |
| |
| |
| char * |
| mem_alloc_w (stream *strm, size_t *len) |
| { |
| unix_stream *s = (unix_stream *)strm; |
| gfc_offset m; |
| gfc_offset where = s->logical_offset; |
| |
| m = where + *len; |
| |
| if (where < s->buffer_offset) |
| return NULL; |
| |
| if (m > s->file_length) |
| return NULL; |
| |
| s->logical_offset = m; |
| |
| return s->buffer + (where - s->buffer_offset); |
| } |
| |
| |
| gfc_char4_t * |
| mem_alloc_w4 (stream *strm, size_t *len) |
| { |
| unix_stream *s = (unix_stream *)strm; |
| gfc_offset m; |
| gfc_offset where = s->logical_offset; |
| gfc_char4_t *result = (gfc_char4_t *) s->buffer; |
| |
| m = where + *len; |
| |
| if (where < s->buffer_offset) |
| return NULL; |
| |
| if (m > s->file_length) |
| return NULL; |
| |
| s->logical_offset = m; |
| return &result[where - s->buffer_offset]; |
| } |
| |
| |
| /* Stream read function for character(kind=1) internal units. */ |
| |
| static ssize_t |
| mem_read (stream *s, void *buf, ssize_t nbytes) |
| { |
| void *p; |
| size_t nb = nbytes; |
| |
| p = mem_alloc_r (s, &nb); |
| if (p) |
| { |
| memcpy (buf, p, nb); |
| return (ssize_t) nb; |
| } |
| else |
| return 0; |
| } |
| |
| |
| /* Stream read function for chracter(kind=4) internal units. */ |
| |
| static ssize_t |
| mem_read4 (stream *s, void *buf, ssize_t nbytes) |
| { |
| void *p; |
| size_t nb = nbytes; |
| |
| p = mem_alloc_r4 (s, &nb); |
| if (p) |
| { |
| memcpy (buf, p, nb * 4); |
| return (ssize_t) nb; |
| } |
| else |
| return 0; |
| } |
| |
| |
| /* Stream write function for character(kind=1) internal units. */ |
| |
| static ssize_t |
| mem_write (stream *s, const void *buf, ssize_t nbytes) |
| { |
| void *p; |
| size_t nb = nbytes; |
| |
| p = mem_alloc_w (s, &nb); |
| if (p) |
| { |
| memcpy (p, buf, nb); |
| return (ssize_t) nb; |
| } |
| else |
| return 0; |
| } |
| |
| |
| /* Stream write function for character(kind=4) internal units. */ |
| |
| static ssize_t |
| mem_write4 (stream *s, const void *buf, ssize_t nwords) |
| { |
| gfc_char4_t *p; |
| size_t nw = nwords; |
| |
| p = mem_alloc_w4 (s, &nw); |
| if (p) |
| { |
| while (nw--) |
| *p++ = (gfc_char4_t) *((char *) buf); |
| return nwords; |
| } |
| else |
| return 0; |
| } |
| |
| |
| static gfc_offset |
| mem_seek (stream *strm, gfc_offset offset, int whence) |
| { |
| unix_stream *s = (unix_stream *)strm; |
| switch (whence) |
| { |
| case SEEK_SET: |
| break; |
| case SEEK_CUR: |
| offset += s->logical_offset; |
| break; |
| case SEEK_END: |
| offset += s->file_length; |
| break; |
| default: |
| return -1; |
| } |
| |
| /* Note that for internal array I/O it's actually possible to have a |
| negative offset, so don't check for that. */ |
| if (offset > s->file_length) |
| { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| s->logical_offset = offset; |
| |
| /* Returning < 0 is the error indicator for sseek(), so return 0 if |
| offset is negative. Thus if the return value is 0, the caller |
| has to use stell() to get the real value of logical_offset. */ |
| if (offset >= 0) |
| return offset; |
| return 0; |
| } |
| |
| |
| static gfc_offset |
| mem_tell (stream *s) |
| { |
| return ((unix_stream *)s)->logical_offset; |
| } |
| |
| |
| static int |
| mem_truncate (unix_stream *s __attribute__ ((unused)), |
| gfc_offset length __attribute__ ((unused))) |
| { |
| return 0; |
| } |
| |
| |
| static int |
| mem_flush (unix_stream *s __attribute__ ((unused))) |
| { |
| return 0; |
| } |
| |
| |
| static int |
| mem_close (unix_stream *s) |
| { |
| if (s) |
| free (s); |
| return 0; |
| } |
| |
| static const struct stream_vtable mem_vtable = { |
| .read = (void *) mem_read, |
| .write = (void *) mem_write, |
| .seek = (void *) mem_seek, |
| .tell = (void *) mem_tell, |
| /* buf_size is not a typo, we just reuse an identical |
| implementation. */ |
| .size = (void *) buf_size, |
| .trunc = (void *) mem_truncate, |
| .close = (void *) mem_close, |
| .flush = (void *) mem_flush, |
| .markeor = (void *) raw_markeor |
| }; |
| |
| static const struct stream_vtable mem4_vtable = { |
| .read = (void *) mem_read4, |
| .write = (void *) mem_write4, |
| .seek = (void *) mem_seek, |
| .tell = (void *) mem_tell, |
| /* buf_size is not a typo, we just reuse an identical |
| implementation. */ |
| .size = (void *) buf_size, |
| .trunc = (void *) mem_truncate, |
| .close = (void *) mem_close, |
| .flush = (void *) mem_flush, |
| .markeor = (void *) raw_markeor |
| }; |
| |
| /********************************************************************* |
| Public functions -- A reimplementation of this module needs to |
| define functional equivalents of the following. |
| *********************************************************************/ |
| |
| /* open_internal()-- Returns a stream structure from a character(kind=1) |
| internal file */ |
| |
| stream * |
| open_internal (char *base, size_t length, gfc_offset offset) |
| { |
| unix_stream *s; |
| |
| s = xcalloc (1, sizeof (unix_stream)); |
| |
| s->buffer = base; |
| s->buffer_offset = offset; |
| |
| s->active = s->file_length = length; |
| |
| s->st.vptr = &mem_vtable; |
| |
| return (stream *) s; |
| } |
| |
| /* open_internal4()-- Returns a stream structure from a character(kind=4) |
| internal file */ |
| |
| stream * |
| open_internal4 (char *base, size_t length, gfc_offset offset) |
| { |
| unix_stream *s; |
| |
| s = xcalloc (1, sizeof (unix_stream)); |
| |
| s->buffer = base; |
| s->buffer_offset = offset; |
| |
| s->active = s->file_length = length * sizeof (gfc_char4_t); |
| |
| s->st.vptr = &mem4_vtable; |
| |
| return (stream *)s; |
| } |
| |
| |
| /* fd_to_stream()-- Given an open file descriptor, build a stream |
| around it. */ |
| |
| static stream * |
| fd_to_stream (int fd, bool unformatted) |
| { |
| struct stat statbuf; |
| unix_stream *s; |
| |
| s = xcalloc (1, sizeof (unix_stream)); |
| |
| s->fd = fd; |
| |
| /* Get the current length of the file. */ |
| |
| if (TEMP_FAILURE_RETRY (fstat (fd, &statbuf)) == -1) |
| { |
| s->st_dev = s->st_ino = -1; |
| s->file_length = 0; |
| if (errno == EBADF) |
| s->fd = -1; |
| raw_init (s); |
| return (stream *) s; |
| } |
| |
| s->st_dev = statbuf.st_dev; |
| s->st_ino = statbuf.st_ino; |
| s->file_length = statbuf.st_size; |
| |
| /* Only use buffered IO for regular files. */ |
| if (S_ISREG (statbuf.st_mode) |
| && !options.all_unbuffered |
| && !(options.unbuffered_preconnected && |
| (s->fd == STDIN_FILENO |
| || s->fd == STDOUT_FILENO |
| || s->fd == STDERR_FILENO))) |
| buf_init (s, unformatted); |
| else |
| { |
| if (unformatted) |
| { |
| s->unbuffered = true; |
| buf_init (s, unformatted); |
| } |
| else |
| raw_init (s); |
| } |
| |
| return (stream *) s; |
| } |
| |
| |
| /* Given the Fortran unit number, convert it to a C file descriptor. */ |
| |
| int |
| unit_to_fd (int unit) |
| { |
| gfc_unit *us; |
| int fd; |
| |
| us = find_unit (unit); |
| if (us == NULL) |
| return -1; |
| |
| fd = ((unix_stream *) us->s)->fd; |
| unlock_unit (us); |
| return fd; |
| } |
| |
| |
| /* Set the close-on-exec flag for an existing fd, if the system |
| supports such. */ |
| |
| static void __attribute__ ((unused)) |
| set_close_on_exec (int fd __attribute__ ((unused))) |
| { |
| /* Mingw does not define F_SETFD. */ |
| #if defined(HAVE_FCNTL) && defined(F_SETFD) && defined(FD_CLOEXEC) |
| if (fd >= 0) |
| fcntl(fd, F_SETFD, FD_CLOEXEC); |
| #endif |
| } |
| |
| |
| /* Helper function for tempfile(). Tries to open a temporary file in |
| the directory specified by tempdir. If successful, the file name is |
| stored in fname and the descriptor returned. Returns -1 on |
| failure. */ |
| |
| static int |
| tempfile_open (const char *tempdir, char **fname) |
| { |
| int fd; |
| const char *slash = "/"; |
| #if defined(HAVE_UMASK) && defined(HAVE_MKSTEMP) |
| mode_t mode_mask; |
| #endif |
| |
| if (!tempdir) |
| return -1; |
| |
| /* Check for the special case that tempdir ends with a slash or |
| backslash. */ |
| size_t tempdirlen = strlen (tempdir); |
| if (*tempdir == 0 || tempdir[tempdirlen - 1] == '/' |
| #ifdef __MINGW32__ |
| || tempdir[tempdirlen - 1] == '\\' |
| #endif |
| ) |
| slash = ""; |
| |
| /* Take care that the template is longer in the mktemp() branch. */ |
| char *template = xmalloc (tempdirlen + 23); |
| |
| #ifdef HAVE_MKSTEMP |
| snprintf (template, tempdirlen + 23, "%s%sgfortrantmpXXXXXX", |
| tempdir, slash); |
| |
| #ifdef HAVE_UMASK |
| /* Temporarily set the umask such that the file has 0600 permissions. */ |
| mode_mask = umask (S_IXUSR | S_IRWXG | S_IRWXO); |
| #endif |
| |
| #if defined(HAVE_MKOSTEMP) && defined(O_CLOEXEC) |
| TEMP_FAILURE_RETRY (fd = mkostemp (template, O_CLOEXEC)); |
| #else |
| TEMP_FAILURE_RETRY (fd = mkstemp (template)); |
| set_close_on_exec (fd); |
| #endif |
| |
| #ifdef HAVE_UMASK |
| (void) umask (mode_mask); |
| #endif |
| |
| #else /* HAVE_MKSTEMP */ |
| fd = -1; |
| int count = 0; |
| size_t slashlen = strlen (slash); |
| int flags = O_RDWR | O_CREAT | O_EXCL; |
| #if defined(HAVE_CRLF) && defined(O_BINARY) |
| flags |= O_BINARY; |
| #endif |
| #ifdef O_CLOEXEC |
| flags |= O_CLOEXEC; |
| #endif |
| do |
| { |
| snprintf (template, tempdirlen + 23, "%s%sgfortrantmpaaaXXXXXX", |
| tempdir, slash); |
| if (count > 0) |
| { |
| int c = count; |
| template[tempdirlen + slashlen + 13] = 'a' + (c% 26); |
| c /= 26; |
| template[tempdirlen + slashlen + 12] = 'a' + (c % 26); |
| c /= 26; |
| template[tempdirlen + slashlen + 11] = 'a' + (c % 26); |
| if (c >= 26) |
| break; |
| } |
| |
| if (!mktemp (template)) |
| { |
| errno = EEXIST; |
| count++; |
| continue; |
| } |
| |
| TEMP_FAILURE_RETRY (fd = open (template, flags, S_IRUSR | S_IWUSR)); |
| } |
| while (fd == -1 && errno == EEXIST); |
| #ifndef O_CLOEXEC |
| set_close_on_exec (fd); |
| #endif |
| #endif /* HAVE_MKSTEMP */ |
| |
| *fname = template; |
| return fd; |
| } |
| |
| |
| /* 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 returned, which is -1 on error. The template is pointed to by |
| opp->file, which is copied into the unit structure |
| and freed later. */ |
| |
| static int |
| tempfile (st_parameter_open *opp) |
| { |
| const char *tempdir; |
| char *fname; |
| int fd = -1; |
| |
| tempdir = secure_getenv ("TMPDIR"); |
| fd = tempfile_open (tempdir, &fname); |
| #ifdef __MINGW32__ |
| if (fd == -1) |
| { |
| char buffer[MAX_PATH + 1]; |
| DWORD ret; |
| ret = GetTempPath (MAX_PATH, buffer); |
| /* If we are not able to get a temp-directory, we use |
| current directory. */ |
| if (ret > MAX_PATH || !ret) |
| buffer[0] = 0; |
| else |
| buffer[ret] = 0; |
| tempdir = strdup (buffer); |
| fd = tempfile_open (tempdir, &fname); |
| } |
| #elif defined(__CYGWIN__) |
| if (fd == -1) |
| { |
| tempdir = secure_getenv ("TMP"); |
| fd = tempfile_open (tempdir, &fname); |
| } |
| if (fd == -1) |
| { |
| tempdir = secure_getenv ("TEMP"); |
| fd = tempfile_open (tempdir, &fname); |
| } |
| #endif |
| if (fd == -1) |
| fd = tempfile_open (P_tmpdir, &fname); |
| |
| opp->file = fname; |
| opp->file_len = strlen (fname); /* Don't include trailing nul */ |
| |
| return fd; |
| } |
| |
| |
| /* regular_file2()-- Open a regular file. |
| Change flags->action if it is ACTION_UNSPECIFIED on entry, |
| unless an error occurs. |
| Returns the descriptor, which is less than zero on error. */ |
| |
| static int |
| regular_file2 (const char *path, st_parameter_open *opp, unit_flags *flags) |
| { |
| int mode; |
| int rwflag; |
| int crflag, crflag2; |
| int fd; |
| |
| #ifdef __CYGWIN__ |
| if (opp->file_len == 7) |
| { |
| if (strncmp (path, "CONOUT$", 7) == 0 |
| || strncmp (path, "CONERR$", 7) == 0) |
| { |
| fd = open ("/dev/conout", O_WRONLY); |
| flags->action = ACTION_WRITE; |
| return fd; |
| } |
| } |
| |
| if (opp->file_len == 6 && strncmp (path, "CONIN$", 6) == 0) |
| { |
| fd = open ("/dev/conin", O_RDONLY); |
| flags->action = ACTION_READ; |
| return fd; |
| } |
| #endif |
| |
| |
| #ifdef __MINGW32__ |
| if (opp->file_len == 7) |
| { |
| if (strncmp (path, "CONOUT$", 7) == 0 |
| || strncmp (path, "CONERR$", 7) == 0) |
| { |
| fd = open ("CONOUT$", O_WRONLY); |
| flags->action = ACTION_WRITE; |
| return fd; |
| } |
| } |
| |
| if (opp->file_len == 6 && strncmp (path, "CONIN$", 6) == 0) |
| { |
| fd = open ("CONIN$", O_RDONLY); |
| flags->action = ACTION_READ; |
| return fd; |
| } |
| #endif |
| |
| switch (flags->action) |
| { |
| case ACTION_READ: |
| rwflag = O_RDONLY; |
| break; |
| |
| case ACTION_WRITE: |
| rwflag = O_WRONLY; |
| break; |
| |
| case ACTION_READWRITE: |
| case ACTION_UNSPECIFIED: |
| rwflag = O_RDWR; |
| break; |
| |
| default: |
| internal_error (&opp->common, "regular_file(): Bad action"); |
| } |
| |
| switch (flags->status) |
| { |
| case STATUS_NEW: |
| crflag = O_CREAT | O_EXCL; |
| break; |
| |
| case STATUS_OLD: /* open will fail if the file does not exist*/ |
| crflag = 0; |
| break; |
| |
| case STATUS_UNKNOWN: |
| if (rwflag == O_RDONLY) |
| crflag = 0; |
| else |
| crflag = O_CREAT; |
| break; |
| |
| case STATUS_REPLACE: |
| crflag = O_CREAT | O_TRUNC; |
| break; |
| |
| default: |
| /* Note: STATUS_SCRATCH is handled by tempfile () and should |
| never be seen here. */ |
| internal_error (&opp->common, "regular_file(): Bad status"); |
| } |
| |
| /* rwflag |= O_LARGEFILE; */ |
| |
| #if defined(HAVE_CRLF) && defined(O_BINARY) |
| crflag |= O_BINARY; |
| #endif |
| |
| #ifdef O_CLOEXEC |
| crflag |= O_CLOEXEC; |
| #endif |
| |
| mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; |
| TEMP_FAILURE_RETRY (fd = open (path, rwflag | crflag, mode)); |
| if (flags->action != ACTION_UNSPECIFIED) |
| return fd; |
| |
| if (fd >= 0) |
| { |
| flags->action = ACTION_READWRITE; |
| return fd; |
| } |
| if (errno != EACCES && errno != EPERM && errno != EROFS) |
| return fd; |
| |
| /* retry for read-only access */ |
| rwflag = O_RDONLY; |
| if (flags->status == STATUS_UNKNOWN) |
| crflag2 = crflag & ~(O_CREAT); |
| else |
| crflag2 = crflag; |
| TEMP_FAILURE_RETRY (fd = open (path, rwflag | crflag2, mode)); |
| if (fd >=0) |
| { |
| flags->action = ACTION_READ; |
| return fd; /* success */ |
| } |
| |
| if (errno != EACCES && errno != EPERM && errno != ENOENT) |
| return fd; /* failure */ |
| |
| /* retry for write-only access */ |
| rwflag = O_WRONLY; |
| TEMP_FAILURE_RETRY (fd = open (path, rwflag | crflag, mode)); |
| if (fd >=0) |
| { |
| flags->action = ACTION_WRITE; |
| return fd; /* success */ |
| } |
| return fd; /* failure */ |
| } |
| |
| |
| /* Lock the file, if necessary, based on SHARE flags. */ |
| |
| #if defined(HAVE_FCNTL) && defined(F_SETLK) && defined(F_UNLCK) |
| static int |
| open_share (st_parameter_open *opp, int fd, unit_flags *flags) |
| { |
| int r = 0; |
| struct flock f; |
| if (fd == STDOUT_FILENO || fd == STDERR_FILENO || fd == STDIN_FILENO) |
| return 0; |
| |
| f.l_start = 0; |
| f.l_len = 0; |
| f.l_whence = SEEK_SET; |
| |
| switch (flags->share) |
| { |
| case SHARE_DENYNONE: |
| f.l_type = F_RDLCK; |
| r = fcntl (fd, F_SETLK, &f); |
| break; |
| case SHARE_DENYRW: |
| /* Must be writable to hold write lock. */ |
| if (flags->action == ACTION_READ) |
| { |
| generate_error (&opp->common, LIBERROR_BAD_ACTION, |
| "Cannot set write lock on file opened for READ"); |
| return -1; |
| } |
| f.l_type = F_WRLCK; |
| r = fcntl (fd, F_SETLK, &f); |
| break; |
| case SHARE_UNSPECIFIED: |
| default: |
| break; |
| } |
| |
| return r; |
| } |
| #else |
| static int |
| open_share (st_parameter_open *opp __attribute__ ((unused)), |
| int fd __attribute__ ((unused)), |
| unit_flags *flags __attribute__ ((unused))) |
| { |
| return 0; |
| } |
| #endif /* defined(HAVE_FCNTL) ... */ |
| |
| |
| /* Wrapper around regular_file2, to make sure we free the path after |
| we're done. */ |
| |
| static int |
| regular_file (st_parameter_open *opp, unit_flags *flags) |
| { |
| char *path = fc_strdup (opp->file, opp->file_len); |
| int fd = regular_file2 (path, opp, flags); |
| free (path); |
| return fd; |
| } |
| |
| /* open_external()-- Open an external file, unix specific version. |
| Change flags->action if it is ACTION_UNSPECIFIED on entry. |
| Returns NULL on operating system error. */ |
| |
| stream * |
| open_external (st_parameter_open *opp, unit_flags *flags) |
| { |
| int fd; |
| |
| if (flags->status == STATUS_SCRATCH) |
| { |
| fd = tempfile (opp); |
| if (flags->action == ACTION_UNSPECIFIED) |
| flags->action = flags->readonly ? ACTION_READ : ACTION_READWRITE; |
| |
| #if HAVE_UNLINK_OPEN_FILE |
| /* We can unlink scratch files now and it will go away when closed. */ |
| if (fd >= 0) |
| unlink (opp->file); |
| #endif |
| } |
| else |
| { |
| /* regular_file resets flags->action if it is ACTION_UNSPECIFIED and |
| if it succeeds */ |
| fd = regular_file (opp, flags); |
| #ifndef O_CLOEXEC |
| set_close_on_exec (fd); |
| #endif |
| } |
| |
| if (fd < 0) |
| return NULL; |
| fd = fix_fd (fd); |
| |
| if (open_share (opp, fd, flags) < 0) |
| return NULL; |
| |
| return fd_to_stream (fd, flags->form == FORM_UNFORMATTED); |
| } |
| |
| |
| /* input_stream()-- Return a stream pointer to the default input stream. |
| Called on initialization. */ |
| |
| stream * |
| input_stream (void) |
| { |
| return fd_to_stream (STDIN_FILENO, false); |
| } |
| |
| |
| /* output_stream()-- Return a stream pointer to the default output stream. |
| Called on initialization. */ |
| |
| stream * |
| output_stream (void) |
| { |
| stream *s; |
| |
| #if defined(HAVE_CRLF) && defined(HAVE_SETMODE) |
| setmode (STDOUT_FILENO, O_BINARY); |
| #endif |
| |
| s = fd_to_stream (STDOUT_FILENO, false); |
| return s; |
| } |
| |
| |
| /* error_stream()-- Return a stream pointer to the default error stream. |
| Called on initialization. */ |
| |
| stream * |
| error_stream (void) |
| { |
| stream *s; |
| |
| #if defined(HAVE_CRLF) && defined(HAVE_SETMODE) |
| setmode (STDERR_FILENO, O_BINARY); |
| #endif |
| |
| s = fd_to_stream (STDERR_FILENO, false); |
| return s; |
| } |
| |
| |
| /* 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 (gfc_unit *u, const char *name, gfc_charlen_type len) |
| { |
| struct stat st; |
| int ret; |
| #ifdef HAVE_WORKING_STAT |
| unix_stream *s; |
| #else |
| # ifdef __MINGW32__ |
| uint64_t id1, id2; |
| # endif |
| #endif |
| |
| char *path = fc_strdup (name, len); |
| |
| /* If the filename doesn't exist, then there is no match with the |
| existing file. */ |
| |
| if (TEMP_FAILURE_RETRY (stat (path, &st)) < 0) |
| { |
| ret = 0; |
| goto done; |
| } |
| |
| #ifdef HAVE_WORKING_STAT |
| s = (unix_stream *) (u->s); |
| ret = (st.st_dev == s->st_dev) && (st.st_ino == s->st_ino); |
| goto done; |
| #else |
| |
| # ifdef __MINGW32__ |
| /* We try to match files by a unique ID. On some filesystems (network |
| fs and FAT), we can't generate this unique ID, and will simply compare |
| filenames. */ |
| id1 = id_from_path (path); |
| id2 = id_from_fd (((unix_stream *) (u->s))->fd); |
| if (id1 || id2) |
| { |
| ret = (id1 == id2); |
| goto done; |
| } |
| # endif |
| if (u->filename) |
| ret = (strcmp(path, u->filename) == 0); |
| else |
| ret = 0; |
| #endif |
| done: |
| free (path); |
| return ret; |
| } |
| |
| |
| #ifdef HAVE_WORKING_STAT |
| # define FIND_FILE0_DECL struct stat *st |
| # define FIND_FILE0_ARGS st |
| #else |
| # define FIND_FILE0_DECL uint64_t id, const char *path |
| # define FIND_FILE0_ARGS id, path |
| #endif |
| |
| /* find_file0()-- Recursive work function for find_file() */ |
| |
| static gfc_unit * |
| find_file0 (gfc_unit *u, FIND_FILE0_DECL) |
| { |
| gfc_unit *v; |
| #if defined(__MINGW32__) && !HAVE_WORKING_STAT |
| uint64_t id1; |
| #endif |
| |
| if (u == NULL) |
| return NULL; |
| |
| #ifdef HAVE_WORKING_STAT |
| if (u->s != NULL) |
| { |
| unix_stream *s = (unix_stream *) (u->s); |
| if (st[0].st_dev == s->st_dev && st[0].st_ino == s->st_ino) |
| return u; |
| } |
| #else |
| # ifdef __MINGW32__ |
| if (u->s && ((id1 = id_from_fd (((unix_stream *) u->s)->fd)) || id1)) |
| { |
| if (id == id1) |
| return u; |
| } |
| else |
| # endif |
| if (u->filename && strcmp (u->filename, path) == 0) |
| return u; |
| #endif |
| |
| v = find_file0 (u->left, FIND_FILE0_ARGS); |
| if (v != NULL) |
| return v; |
| |
| v = find_file0 (u->right, FIND_FILE0_ARGS); |
| 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. */ |
| |
| gfc_unit * |
| find_file (const char *file, gfc_charlen_type file_len) |
| { |
| struct stat st[1]; |
| gfc_unit *u; |
| #if defined(__MINGW32__) && !HAVE_WORKING_STAT |
| uint64_t id = 0ULL; |
| #endif |
| |
| char *path = fc_strdup (file, file_len); |
| |
| if (TEMP_FAILURE_RETRY (stat (path, &st[0])) < 0) |
| { |
| u = NULL; |
| goto done; |
| } |
| |
| #if defined(__MINGW32__) && !HAVE_WORKING_STAT |
| id = id_from_path (path); |
| #endif |
| |
| LOCK (&unit_lock); |
| retry: |
| u = find_file0 (unit_root, FIND_FILE0_ARGS); |
| if (u != NULL) |
| { |
| /* Fast path. */ |
| if (! __gthread_mutex_trylock (&u->lock)) |
| { |
| /* assert (u->closed == 0); */ |
| UNLOCK (&unit_lock); |
| goto done; |
| } |
| |
| inc_waiting_locked (u); |
| } |
| UNLOCK (&unit_lock); |
| if (u != NULL) |
| { |
| LOCK (&u->lock); |
| if (u->closed) |
| { |
| LOCK (&unit_lock); |
| UNLOCK (&u->lock); |
| if (predec_waiting_locked (u) == 0) |
| free (u); |
| goto retry; |
| } |
| |
| dec_waiting_unlocked (u); |
| } |
| done: |
| free (path); |
| return u; |
| } |
| |
| static gfc_unit * |
| flush_all_units_1 (gfc_unit *u, int min_unit) |
| { |
| while (u != NULL) |
| { |
| if (u->unit_number > min_unit) |
| { |
| gfc_unit *r = flush_all_units_1 (u->left, min_unit); |
| if (r != NULL) |
| return r; |
| } |
| if (u->unit_number >= min_unit) |
| { |
| if (__gthread_mutex_trylock (&u->lock)) |
| return u; |
| if (u->s) |
| sflush (u->s); |
| UNLOCK (&u->lock); |
| } |
| u = u->right; |
| } |
| return NULL; |
| } |
| |
| void |
| flush_all_units (void) |
| { |
| gfc_unit *u; |
| int min_unit = 0; |
| |
| LOCK (&unit_lock); |
| do |
| { |
| u = flush_all_units_1 (unit_root, min_unit); |
| if (u != NULL) |
| inc_waiting_locked (u); |
| UNLOCK (&unit_lock); |
| if (u == NULL) |
| return; |
| |
| LOCK (&u->lock); |
| |
| min_unit = u->unit_number + 1; |
| |
| if (u->closed == 0) |
| { |
| sflush (u->s); |
| LOCK (&unit_lock); |
| UNLOCK (&u->lock); |
| (void) predec_waiting_locked (u); |
| } |
| else |
| { |
| LOCK (&unit_lock); |
| UNLOCK (&u->lock); |
| if (predec_waiting_locked (u) == 0) |
| free (u); |
| } |
| } |
| while (1); |
| } |
| |
| |
| /* Unlock the unit if necessary, based on SHARE flags. */ |
| |
| int |
| close_share (gfc_unit *u __attribute__ ((unused))) |
| { |
| int r = 0; |
| #if defined(HAVE_FCNTL) && defined(F_SETLK) && defined(F_UNLCK) |
| unix_stream *s = (unix_stream *) u->s; |
| int fd = s->fd; |
| struct flock f; |
| |
| switch (u->flags.share) |
| { |
| case SHARE_DENYRW: |
| case SHARE_DENYNONE: |
| if (fd != STDOUT_FILENO && fd != STDERR_FILENO && fd != STDIN_FILENO) |
| { |
| f.l_start = 0; |
| f.l_len = 0; |
| f.l_whence = SEEK_SET; |
| f.l_type = F_UNLCK; |
| r = fcntl (fd, F_SETLK, &f); |
| } |
| break; |
| case SHARE_UNSPECIFIED: |
| default: |
| break; |
| } |
| |
| #endif |
| return r; |
| } |
| |
| |
| /* file_exists()-- Returns nonzero if the current filename exists on |
| the system */ |
| |
| int |
| file_exists (const char *file, gfc_charlen_type file_len) |
| { |
| char *path = fc_strdup (file, file_len); |
| int res = !(access (path, F_OK)); |
| free (path); |
| return res; |
| } |
| |
| |
| /* file_size()-- Returns the size of the file. */ |
| |
| GFC_IO_INT |
| file_size (const char *file, gfc_charlen_type file_len) |
| { |
| char *path = fc_strdup (file, file_len); |
| struct stat statbuf; |
| int err; |
| TEMP_FAILURE_RETRY (err = stat (path, &statbuf)); |
| free (path); |
| if (err == -1) |
| return -1; |
| return (GFC_IO_INT) statbuf.st_size; |
| } |
| |
| 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, gfc_charlen_type len) |
| { |
| struct stat statbuf; |
| |
| if (string == NULL) |
| return unknown; |
| |
| char *path = fc_strdup (string, len); |
| int err; |
| TEMP_FAILURE_RETRY (err = stat (path, &statbuf)); |
| free (path); |
| if (err == -1) |
| return unknown; |
| |
| if (S_ISREG (statbuf.st_mode) || |
| S_ISCHR (statbuf.st_mode) || S_ISFIFO (statbuf.st_mode)) |
| return unknown; |
| |
| 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, gfc_charlen_type len) |
| { |
| struct stat statbuf; |
| |
| if (string == NULL) |
| return unknown; |
| |
| char *path = fc_strdup (string, len); |
| int err; |
| TEMP_FAILURE_RETRY (err = stat (path, &statbuf)); |
| free (path); |
| if (err == -1) |
| return unknown; |
| |
| if (S_ISREG (statbuf.st_mode) || S_ISBLK (statbuf.st_mode)) |
| return unknown; |
| |
| 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, gfc_charlen_type len) |
| { |
| struct stat statbuf; |
| |
| if (string == NULL) |
| return unknown; |
| |
| char *path = fc_strdup (string, len); |
| int err; |
| TEMP_FAILURE_RETRY (err = stat (path, &statbuf)); |
| free (path); |
| if (err == -1) |
| return unknown; |
| |
| if (S_ISREG (statbuf.st_mode) || |
| S_ISBLK (statbuf.st_mode) || |
| S_ISCHR (statbuf.st_mode) || S_ISFIFO (statbuf.st_mode)) |
| return unknown; |
| |
| 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, gfc_charlen_type 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, gfc_charlen_type len, int mode) |
| { |
| if (string == NULL) |
| return no; |
| char *path = fc_strdup (string, len); |
| int res = access (path, mode); |
| free (path); |
| if (res == -1) |
| 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, gfc_charlen_type 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, gfc_charlen_type 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, gfc_charlen_type len) |
| { |
| return inquire_access (string, len, R_OK | W_OK); |
| } |
| |
| |
| int |
| stream_isatty (stream *s) |
| { |
| return isatty (((unix_stream *) s)->fd); |
| } |
| |
| int |
| stream_ttyname (stream *s __attribute__ ((unused)), |
| char *buf __attribute__ ((unused)), |
| size_t buflen __attribute__ ((unused))) |
| { |
| #ifdef HAVE_TTYNAME_R |
| return ttyname_r (((unix_stream *)s)->fd, buf, buflen); |
| #elif defined HAVE_TTYNAME |
| char *p; |
| size_t plen; |
| p = ttyname (((unix_stream *)s)->fd); |
| if (!p) |
| return errno; |
| plen = strlen (p); |
| if (buflen < plen) |
| plen = buflen; |
| memcpy (buf, p, plen); |
| return 0; |
| #else |
| return ENOSYS; |
| #endif |
| } |
| |
| |
| |
| |
| /* 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. |
| |
| */ |