#include "config.h"
#include <stdlib.h>
#include <limits.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
#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
#ifndef PROT_READ
#define PROT_READ 1
#endif
#ifndef PROT_WRITE
#define PROT_WRITE 2
#endif
#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
#define BUFFER_SIZE 8192
typedef struct
{
stream st;
int fd;
gfc_offset buffer_offset;
gfc_offset physical_offset;
gfc_offset logical_offset;
gfc_offset dirty_offset;
gfc_offset file_length;
char *buffer;
int len;
int active;
int prot;
int ndirty;
unsigned unbuffered:1, mmaped:1;
char small_buffer[BUFFER_SIZE];
}
unix_stream;
int
move_pos_offset (stream* st, int pos_off)
{
unix_stream * str = (unix_stream*)st;
if (pos_off < 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;
}
static int
fix_fd (int fd)
{
int input, output, error;
input = output = error = 0;
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;
}
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
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
const char *
get_oserror (void)
{
return strerror (errno);
}
void
sys_exit (int code)
{
exit (code);
}
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->file_length != -1 && s->physical_offset > s->file_length)
s->file_length = s->physical_offset;
s->ndirty = 0;
return SUCCESS;
}
static void
fd_alloc (unix_stream * s, gfc_offset 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;
}
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
{
s->active = 0;
}
s->buffer_offset = where;
if (s->buffer != NULL && s->buffer != s->small_buffer)
free_mem (s->buffer);
s->buffer = new_buffer;
s->len = read_len;
s->mmaped = 0;
}
static char *
fd_alloc_r_at (unix_stream * s, int *len, gfc_offset where)
{
gfc_offset 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)
{
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;
s->logical_offset = where + *len;
return s->buffer;
}
static char *
fd_alloc_w_at (unix_stream * s, int *len, gfc_offset where)
{
gfc_offset 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);
}
if (s->ndirty == 0
|| where > s->dirty_offset + s->ndirty
|| s->dirty_offset > where + *len)
{
if (s->ndirty != 0)
fd_flush (s);
s->dirty_offset = where;
s->ndirty = *len;
}
else
{
gfc_offset start;
if (where < s->dirty_offset)
start = where;
else
start = s->dirty_offset;
if (where + *len > s->dirty_offset + s->ndirty)
s->ndirty = where + *len - start;
else
s->ndirty = s->dirty_offset + s->ndirty - start;
s->dirty_offset = start;
}
s->logical_offset = where + *len;
if (where + *len > s->file_length)
s->file_length = 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, gfc_offset offset)
{
s->physical_offset = s->logical_offset = offset;
return (lseek (s->fd, offset, SEEK_SET) < 0) ? FAILURE : SUCCESS;
}
static try
fd_truncate (unix_stream * s)
{
if (lseek (s->fd, s->logical_offset, SEEK_SET) == -1)
return FAILURE;
#ifdef HAVE_FTRUNCATE
if (ftruncate (s->fd, s->logical_offset))
#else
#ifdef HAVE_CHSIZE
if (chsize (s->fd, s->logical_offset))
#endif
#endif
{
s->physical_offset = s->file_length = 0;
return FAILURE;
}
s->physical_offset = s->file_length = s->logical_offset;
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 (s->fd != STDOUT_FILENO && s->fd != STDERR_FILENO)
{
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;
}
#if HAVE_MMAP
static int page_size, page_mask;
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;
}
static try
mmap_alloc (unix_stream * s, gfc_offset where, int *len)
{
gfc_offset offset;
int length;
char *p;
if (mmap_flush (s) == FAILURE)
return FAILURE;
offset = where & page_mask;
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, gfc_offset where)
{
gfc_offset 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, gfc_offset where)
{
if (where == -1)
where = s->logical_offset;
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 ||
where < 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, gfc_offset 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;
}
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
static char *
mem_alloc_r_at (unix_stream * s, int *len, gfc_offset where)
{
gfc_offset n;
if (where == -1)
where = s->logical_offset;
if (where < s->buffer_offset || where > s->buffer_offset + s->active)
return NULL;
s->logical_offset = where + *len;
n = s->buffer_offset + s->active - where;
if (*len > n)
*len = n;
return s->buffer + (where - s->buffer_offset);
}
static char *
mem_alloc_w_at (unix_stream * s, int *len, gfc_offset where)
{
gfc_offset 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, gfc_offset 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)
{
free_mem (s);
return SUCCESS;
}
static try
mem_sfree (unix_stream * s)
{
return SUCCESS;
}
void
empty_internal_buffer(stream *strm)
{
unix_stream * s = (unix_stream *) strm;
memset(s->buffer, ' ', s->file_length);
}
stream *
open_internal (char *base, int length)
{
unix_stream *s;
s = get_mem (sizeof (unix_stream));
memset (s, '\0', 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;
}
static stream *
fd_to_stream (int fd, int prot, int avoid_mmap)
{
struct stat statbuf;
unix_stream *s;
s = get_mem (sizeof (unix_stream));
memset (s, '\0', sizeof (unix_stream));
s->fd = fd;
s->buffer_offset = 0;
s->physical_offset = 0;
s->logical_offset = 0;
s->prot = prot;
fstat (fd, &statbuf);
s->file_length = S_ISREG (statbuf.st_mode) ? statbuf.st_size : -1;
#if HAVE_MMAP
if (avoid_mmap)
fd_open (s);
else
mmap_open (s);
#else
fd_open (s);
#endif
return (stream *) s;
}
int
unit_to_fd(int unit)
{
gfc_unit *us;
us = find_unit(unit);
if (us == NULL)
return -1;
return ((unix_stream *) us->s)->fd;
}
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;
}
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/gfortrantmpXXXXXX", tempdir);
#ifdef HAVE_MKSTEMP
fd = mkstemp (template);
#else
if (mktemp (template))
do
fd = open (template, O_CREAT | O_EXCL, S_IREAD | S_IWRITE);
while (!(fd == -1 && errno == EEXIST) && mktemp (template));
else
fd = -1;
#endif
if (fd < 0)
free_mem (template);
else
{
ioparm.file = template;
ioparm.file_len = strlen (template);
}
return fd;
}
static int
regular_file (unit_flags *flags)
{
char path[PATH_MAX + 1];
int mode;
int rwflag;
int crflag;
int fd;
if (unpack_filename (path, ioparm.file, ioparm.file_len))
{
errno = ENOENT;
return -1;
}
rwflag = 0;
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 ("regular_file(): Bad action");
}
switch (flags->status)
{
case STATUS_NEW:
crflag = O_CREAT | O_EXCL;
break;
case STATUS_OLD:
crflag = 0;
break;
case STATUS_UNKNOWN:
case STATUS_SCRATCH:
crflag = O_CREAT;
break;
case STATUS_REPLACE:
crflag = O_CREAT | O_TRUNC;
break;
default:
internal_error ("regular_file(): Bad status");
}
mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
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)
return fd;
rwflag = O_RDONLY;
fd = open (path, rwflag | crflag, mode);
if (fd >=0)
{
flags->action = ACTION_READ;
return fd;
}
if (errno != EACCES)
return fd;
rwflag = O_WRONLY;
fd = open (path, rwflag | crflag, mode);
if (fd >=0)
{
flags->action = ACTION_WRITE;
return fd;
}
return fd;
}
stream *
open_external (unit_flags *flags)
{
int fd, prot;
if (flags->status == STATUS_SCRATCH)
{
fd = tempfile ();
if (flags->action == ACTION_UNSPECIFIED)
flags->action = ACTION_READWRITE;
unlink (ioparm.file);
}
else
{
fd = regular_file (flags);
}
if (fd < 0)
return NULL;
fd = fix_fd (fd);
switch (flags->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");
}
return fd_to_stream (fd, prot, 0);
}
stream *
input_stream (void)
{
return fd_to_stream (STDIN_FILENO, PROT_READ, 1);
}
stream *
output_stream (void)
{
return fd_to_stream (STDOUT_FILENO, PROT_WRITE, 1);
}
stream *
error_stream (void)
{
return fd_to_stream (STDERR_FILENO, PROT_WRITE, 1);
}
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;
}
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;
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);
}
static gfc_unit *
find_file0 (gfc_unit * u, struct stat *st1)
{
struct stat st2;
gfc_unit *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;
}
gfc_unit *
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);
}
int
stream_at_bof (stream * s)
{
unix_stream *us;
if (!is_seekable (s))
return 0;
us = (unix_stream *) s;
return us->logical_offset == 0;
}
int
stream_at_eof (stream * s)
{
unix_stream *us;
if (!is_seekable (s))
return 0;
us = (unix_stream *) s;
return us->logical_offset == us->dirty_offset;
}
int
delete_file (gfc_unit * u)
{
char path[PATH_MAX + 1];
if (unpack_filename (path, u->file, u->file_len))
{
errno = ENOENT;
return 1;
}
return unlink (path);
}
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";
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;
}
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;
}
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;
}
const char *
inquire_unformatted (const char *string, int len)
{
return inquire_formatted (string, len);
}
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;
}
const char *
inquire_read (const char *string, int len)
{
return inquire_access (string, len, R_OK);
}
const char *
inquire_write (const char *string, int len)
{
return inquire_access (string, len, W_OK);
}
const char *
inquire_readwrite (const char *string, int len)
{
return inquire_access (string, len, R_OK | W_OK);
}
gfc_offset
file_length (stream * s)
{
return ((unix_stream *) s)->file_length;
}
gfc_offset
file_position (stream * s)
{
return ((unix_stream *) s)->logical_offset;
}
int
is_seekable (stream * s)
{
return ((unix_stream *) s)->file_length!=-1;
}
try
flush (stream *s)
{
return fd_flush( (unix_stream *) s);
}