|  | /* Copyright (C) 2002-2020 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/>.  */ | 
|  |  | 
|  | #include "io.h" | 
|  | #include "fbuf.h" | 
|  | #include "format.h" | 
|  | #include "unix.h" | 
|  | #include "async.h" | 
|  | #include <string.h> | 
|  | #include <assert.h> | 
|  |  | 
|  |  | 
|  | /* IO locking rules: | 
|  | UNIT_LOCK is a master lock, protecting UNIT_ROOT tree and UNIT_CACHE. | 
|  | Concurrent use of different units should be supported, so | 
|  | each unit has its own lock, LOCK. | 
|  | Open should be atomic with its reopening of units and list_read.c | 
|  | in several places needs find_unit another unit while holding stdin | 
|  | unit's lock, so it must be possible to acquire UNIT_LOCK while holding | 
|  | some unit's lock.  Therefore to avoid deadlocks, it is forbidden | 
|  | to acquire unit's private locks while holding UNIT_LOCK, except | 
|  | for freshly created units (where no other thread can get at their | 
|  | address yet) or when using just trylock rather than lock operation. | 
|  | In addition to unit's private lock each unit has a WAITERS counter | 
|  | and CLOSED flag.  WAITERS counter must be either only | 
|  | atomically incremented/decremented in all places (if atomic builtins | 
|  | are supported), or protected by UNIT_LOCK in all places (otherwise). | 
|  | CLOSED flag must be always protected by unit's LOCK. | 
|  | After finding a unit in UNIT_CACHE or UNIT_ROOT with UNIT_LOCK held, | 
|  | WAITERS must be incremented to avoid concurrent close from freeing | 
|  | the unit between unlocking UNIT_LOCK and acquiring unit's LOCK. | 
|  | Unit freeing is always done under UNIT_LOCK.  If close_unit sees any | 
|  | WAITERS, it doesn't free the unit but instead sets the CLOSED flag | 
|  | and the thread that decrements WAITERS to zero while CLOSED flag is | 
|  | set is responsible for freeing it (while holding UNIT_LOCK). | 
|  | flush_all_units operation is iterating over the unit tree with | 
|  | increasing UNIT_NUMBER while holding UNIT_LOCK and attempting to | 
|  | flush each unit (and therefore needs the unit's LOCK held as well). | 
|  | To avoid deadlocks, it just trylocks the LOCK and if unsuccessful, | 
|  | remembers the current unit's UNIT_NUMBER, unlocks UNIT_LOCK, acquires | 
|  | unit's LOCK and after flushing reacquires UNIT_LOCK and restarts with | 
|  | the smallest UNIT_NUMBER above the last one flushed. | 
|  |  | 
|  | If find_unit/find_or_create_unit/find_file/get_unit routines return | 
|  | non-NULL, the returned unit has its private lock locked and when the | 
|  | caller is done with it, it must call either unlock_unit or close_unit | 
|  | on it.  unlock_unit or close_unit must be always called only with the | 
|  | private lock held.  */ | 
|  |  | 
|  |  | 
|  |  | 
|  | /* Table of allocated newunit values.  A simple solution would be to | 
|  | map OS file descriptors (fd's) to unit numbers, e.g. with newunit = | 
|  | -fd - 2, however that doesn't work since Fortran allows an existing | 
|  | unit number to be reassociated with a new file. Thus the simple | 
|  | approach may lead to a situation where we'd try to assign a | 
|  | (negative) unit number which already exists. Hence we must keep | 
|  | track of allocated newunit values ourselves. This is the purpose of | 
|  | the newunits array. The indices map to newunit values as newunit = | 
|  | -index + NEWUNIT_FIRST. E.g. newunits[0] having the value true | 
|  | means that a unit with number NEWUNIT_FIRST exists. Similar to | 
|  | POSIX file descriptors, we always allocate the lowest (in absolute | 
|  | value) available unit number. | 
|  | */ | 
|  | static bool *newunits; | 
|  | static int newunit_size; /* Total number of elements in the newunits array.  */ | 
|  | /* Low water indicator for the newunits array. Below the LWI all the | 
|  | units are allocated, above and equal to the LWI there may be both | 
|  | allocated and free units. */ | 
|  | static int newunit_lwi; | 
|  |  | 
|  | /* Unit numbers assigned with NEWUNIT start from here.  */ | 
|  | #define NEWUNIT_START -10 | 
|  |  | 
|  | #define CACHE_SIZE 3 | 
|  | static gfc_unit *unit_cache[CACHE_SIZE]; | 
|  |  | 
|  | gfc_offset max_offset; | 
|  | gfc_offset default_recl; | 
|  |  | 
|  | gfc_unit *unit_root; | 
|  | #ifdef __GTHREAD_MUTEX_INIT | 
|  | __gthread_mutex_t unit_lock = __GTHREAD_MUTEX_INIT; | 
|  | #else | 
|  | __gthread_mutex_t unit_lock; | 
|  | #endif | 
|  |  | 
|  | /* We use these filenames for error reporting.  */ | 
|  |  | 
|  | static char stdin_name[] = "stdin"; | 
|  | static char stdout_name[] = "stdout"; | 
|  | static char stderr_name[] = "stderr"; | 
|  |  | 
|  |  | 
|  | #ifdef HAVE_NEWLOCALE | 
|  | locale_t c_locale; | 
|  | #else | 
|  | /* If we don't have POSIX 2008 per-thread locales, we need to use the | 
|  | traditional setlocale().  To prevent multiple concurrent threads | 
|  | doing formatted I/O from messing up the locale, we need to store a | 
|  | global old_locale, and a counter keeping track of how many threads | 
|  | are currently doing formatted I/O.  The first thread saves the old | 
|  | locale, and the last one restores it.  */ | 
|  | char *old_locale; | 
|  | int old_locale_ctr; | 
|  | #ifdef __GTHREAD_MUTEX_INIT | 
|  | __gthread_mutex_t old_locale_lock = __GTHREAD_MUTEX_INIT; | 
|  | #else | 
|  | __gthread_mutex_t old_locale_lock; | 
|  | #endif | 
|  | #endif | 
|  |  | 
|  |  | 
|  | /* This implementation is based on Stefan Nilsson's article in the | 
|  | July 1997 Doctor Dobb's Journal, "Treaps in Java". */ | 
|  |  | 
|  | /* pseudo_random()-- Simple linear congruential pseudorandom number | 
|  | generator.  The period of this generator is 44071, which is plenty | 
|  | for our purposes.  */ | 
|  |  | 
|  | static int | 
|  | pseudo_random (void) | 
|  | { | 
|  | static int x0 = 5341; | 
|  |  | 
|  | x0 = (22611 * x0 + 10) % 44071; | 
|  | return x0; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* rotate_left()-- Rotate the treap left */ | 
|  |  | 
|  | static gfc_unit * | 
|  | rotate_left (gfc_unit *t) | 
|  | { | 
|  | gfc_unit *temp; | 
|  |  | 
|  | temp = t->right; | 
|  | t->right = t->right->left; | 
|  | temp->left = t; | 
|  |  | 
|  | return temp; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* rotate_right()-- Rotate the treap right */ | 
|  |  | 
|  | static gfc_unit * | 
|  | rotate_right (gfc_unit *t) | 
|  | { | 
|  | gfc_unit *temp; | 
|  |  | 
|  | temp = t->left; | 
|  | t->left = t->left->right; | 
|  | temp->right = t; | 
|  |  | 
|  | return temp; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int | 
|  | compare (int a, int b) | 
|  | { | 
|  | if (a < b) | 
|  | return -1; | 
|  | if (a > b) | 
|  | return 1; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* insert()-- Recursive insertion function.  Returns the updated treap. */ | 
|  |  | 
|  | static gfc_unit * | 
|  | insert (gfc_unit *new, gfc_unit *t) | 
|  | { | 
|  | int c; | 
|  |  | 
|  | if (t == NULL) | 
|  | return new; | 
|  |  | 
|  | c = compare (new->unit_number, t->unit_number); | 
|  |  | 
|  | if (c < 0) | 
|  | { | 
|  | t->left = insert (new, t->left); | 
|  | if (t->priority < t->left->priority) | 
|  | t = rotate_right (t); | 
|  | } | 
|  |  | 
|  | if (c > 0) | 
|  | { | 
|  | t->right = insert (new, t->right); | 
|  | if (t->priority < t->right->priority) | 
|  | t = rotate_left (t); | 
|  | } | 
|  |  | 
|  | if (c == 0) | 
|  | internal_error (NULL, "insert(): Duplicate key found!"); | 
|  |  | 
|  | return t; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* insert_unit()-- Create a new node, insert it into the treap.  */ | 
|  |  | 
|  | static gfc_unit * | 
|  | insert_unit (int n) | 
|  | { | 
|  | gfc_unit *u = xcalloc (1, sizeof (gfc_unit)); | 
|  | u->unit_number = n; | 
|  | u->internal_unit_kind = 0; | 
|  | #ifdef __GTHREAD_MUTEX_INIT | 
|  | { | 
|  | __gthread_mutex_t tmp = __GTHREAD_MUTEX_INIT; | 
|  | u->lock = tmp; | 
|  | } | 
|  | #else | 
|  | __GTHREAD_MUTEX_INIT_FUNCTION (&u->lock); | 
|  | #endif | 
|  | LOCK (&u->lock); | 
|  | u->priority = pseudo_random (); | 
|  | unit_root = insert (u, unit_root); | 
|  | return u; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* destroy_unit_mutex()-- Destroy the mutex and free memory of unit.  */ | 
|  |  | 
|  | static void | 
|  | destroy_unit_mutex (gfc_unit *u) | 
|  | { | 
|  | __gthread_mutex_destroy (&u->lock); | 
|  | free (u); | 
|  | } | 
|  |  | 
|  |  | 
|  | static gfc_unit * | 
|  | delete_root (gfc_unit *t) | 
|  | { | 
|  | gfc_unit *temp; | 
|  |  | 
|  | if (t->left == NULL) | 
|  | return t->right; | 
|  | if (t->right == NULL) | 
|  | return t->left; | 
|  |  | 
|  | if (t->left->priority > t->right->priority) | 
|  | { | 
|  | temp = rotate_right (t); | 
|  | temp->right = delete_root (t); | 
|  | } | 
|  | else | 
|  | { | 
|  | temp = rotate_left (t); | 
|  | temp->left = delete_root (t); | 
|  | } | 
|  |  | 
|  | return temp; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* delete_treap()-- Delete an element from a tree.  The 'old' value | 
|  | does not necessarily have to point to the element to be deleted, it | 
|  | must just point to a treap structure with the key to be deleted. | 
|  | Returns the new root node of the tree. */ | 
|  |  | 
|  | static gfc_unit * | 
|  | delete_treap (gfc_unit *old, gfc_unit *t) | 
|  | { | 
|  | int c; | 
|  |  | 
|  | if (t == NULL) | 
|  | return NULL; | 
|  |  | 
|  | c = compare (old->unit_number, t->unit_number); | 
|  |  | 
|  | if (c < 0) | 
|  | t->left = delete_treap (old, t->left); | 
|  | if (c > 0) | 
|  | t->right = delete_treap (old, t->right); | 
|  | if (c == 0) | 
|  | t = delete_root (t); | 
|  |  | 
|  | return t; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* delete_unit()-- Delete a unit from a tree */ | 
|  |  | 
|  | static void | 
|  | delete_unit (gfc_unit *old) | 
|  | { | 
|  | unit_root = delete_treap (old, unit_root); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* get_gfc_unit()-- Given an integer, return a pointer to the unit | 
|  | structure.  Returns NULL if the unit does not exist, | 
|  | otherwise returns a locked unit. */ | 
|  |  | 
|  | static gfc_unit * | 
|  | get_gfc_unit (int n, int do_create) | 
|  | { | 
|  | gfc_unit *p; | 
|  | int c, created = 0; | 
|  |  | 
|  | NOTE ("Unit n=%d, do_create = %d", n, do_create); | 
|  | LOCK (&unit_lock); | 
|  |  | 
|  | retry: | 
|  | for (c = 0; c < CACHE_SIZE; c++) | 
|  | if (unit_cache[c] != NULL && unit_cache[c]->unit_number == n) | 
|  | { | 
|  | p = unit_cache[c]; | 
|  | goto found; | 
|  | } | 
|  |  | 
|  | p = unit_root; | 
|  | while (p != NULL) | 
|  | { | 
|  | c = compare (n, p->unit_number); | 
|  | if (c < 0) | 
|  | p = p->left; | 
|  | if (c > 0) | 
|  | p = p->right; | 
|  | if (c == 0) | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (p == NULL && do_create) | 
|  | { | 
|  | p = insert_unit (n); | 
|  | created = 1; | 
|  | } | 
|  |  | 
|  | if (p != NULL) | 
|  | { | 
|  | for (c = 0; c < CACHE_SIZE - 1; c++) | 
|  | unit_cache[c] = unit_cache[c + 1]; | 
|  |  | 
|  | unit_cache[CACHE_SIZE - 1] = p; | 
|  | } | 
|  |  | 
|  | if (created) | 
|  | { | 
|  | /* Newly created units have their lock held already | 
|  | from insert_unit.  Just unlock UNIT_LOCK and return.  */ | 
|  | UNLOCK (&unit_lock); | 
|  | return p; | 
|  | } | 
|  |  | 
|  | found: | 
|  | if (p != NULL && (p->child_dtio == 0)) | 
|  | { | 
|  | /* Fast path.  */ | 
|  | if (! TRYLOCK (&p->lock)) | 
|  | { | 
|  | /* assert (p->closed == 0); */ | 
|  | UNLOCK (&unit_lock); | 
|  | return p; | 
|  | } | 
|  |  | 
|  | inc_waiting_locked (p); | 
|  | } | 
|  |  | 
|  |  | 
|  | UNLOCK (&unit_lock); | 
|  |  | 
|  | if (p != NULL && (p->child_dtio == 0)) | 
|  | { | 
|  | LOCK (&p->lock); | 
|  | if (p->closed) | 
|  | { | 
|  | LOCK (&unit_lock); | 
|  | UNLOCK (&p->lock); | 
|  | if (predec_waiting_locked (p) == 0) | 
|  | destroy_unit_mutex (p); | 
|  | goto retry; | 
|  | } | 
|  |  | 
|  | dec_waiting_unlocked (p); | 
|  | } | 
|  | return p; | 
|  | } | 
|  |  | 
|  |  | 
|  | gfc_unit * | 
|  | find_unit (int n) | 
|  | { | 
|  | return get_gfc_unit (n, 0); | 
|  | } | 
|  |  | 
|  |  | 
|  | gfc_unit * | 
|  | find_or_create_unit (int n) | 
|  | { | 
|  | return get_gfc_unit (n, 1); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Helper function to check rank, stride, format string, and namelist. | 
|  | This is used for optimization. You can't trim out blanks or shorten | 
|  | the string if trailing spaces are significant.  */ | 
|  | static bool | 
|  | is_trim_ok (st_parameter_dt *dtp) | 
|  | { | 
|  | /* Check rank and stride.  */ | 
|  | if (dtp->internal_unit_desc) | 
|  | return false; | 
|  | /* Format strings cannot have 'BZ' or '/'.  */ | 
|  | if (dtp->common.flags & IOPARM_DT_HAS_FORMAT) | 
|  | { | 
|  | char *p = dtp->format; | 
|  | if (dtp->common.flags & IOPARM_DT_HAS_BLANK) | 
|  | return false; | 
|  | for (gfc_charlen_type i = 0; i < dtp->format_len; i++) | 
|  | { | 
|  | if (p[i] == '/') return false; | 
|  | if (p[i] == 'b' || p[i] == 'B') | 
|  | if (p[i+1] == 'z' || p[i+1] == 'Z') | 
|  | return false; | 
|  | } | 
|  | } | 
|  | if (dtp->u.p.ionml) /* A namelist.  */ | 
|  | return false; | 
|  | return true; | 
|  | } | 
|  |  | 
|  |  | 
|  | gfc_unit * | 
|  | set_internal_unit (st_parameter_dt *dtp, gfc_unit *iunit, int kind) | 
|  | { | 
|  | gfc_offset start_record = 0; | 
|  |  | 
|  | iunit->unit_number = dtp->common.unit; | 
|  | iunit->recl = dtp->internal_unit_len; | 
|  | iunit->internal_unit = dtp->internal_unit; | 
|  | iunit->internal_unit_len = dtp->internal_unit_len; | 
|  | iunit->internal_unit_kind = kind; | 
|  |  | 
|  | /* As an optimization, adjust the unit record length to not | 
|  | include trailing blanks. This will not work under certain conditions | 
|  | where trailing blanks have significance.  */ | 
|  | if (dtp->u.p.mode == READING && is_trim_ok (dtp)) | 
|  | { | 
|  | int len; | 
|  | if (kind == 1) | 
|  | len = string_len_trim (iunit->internal_unit_len, | 
|  | iunit->internal_unit); | 
|  | else | 
|  | len = string_len_trim_char4 (iunit->internal_unit_len, | 
|  | (const gfc_char4_t*) iunit->internal_unit); | 
|  | iunit->internal_unit_len = len; | 
|  | iunit->recl = iunit->internal_unit_len; | 
|  | } | 
|  |  | 
|  | /* Set up the looping specification from the array descriptor, if any.  */ | 
|  |  | 
|  | if (is_array_io (dtp)) | 
|  | { | 
|  | iunit->rank = GFC_DESCRIPTOR_RANK (dtp->internal_unit_desc); | 
|  | iunit->ls = (array_loop_spec *) | 
|  | xmallocarray (iunit->rank, sizeof (array_loop_spec)); | 
|  | iunit->internal_unit_len *= | 
|  | init_loop_spec (dtp->internal_unit_desc, iunit->ls, &start_record); | 
|  |  | 
|  | start_record *= iunit->recl; | 
|  | } | 
|  |  | 
|  | /* Set initial values for unit parameters.  */ | 
|  | if (kind == 4) | 
|  | iunit->s = open_internal4 (iunit->internal_unit - start_record, | 
|  | iunit->internal_unit_len, -start_record); | 
|  | else | 
|  | iunit->s = open_internal (iunit->internal_unit - start_record, | 
|  | iunit->internal_unit_len, -start_record); | 
|  |  | 
|  | iunit->bytes_left = iunit->recl; | 
|  | iunit->last_record=0; | 
|  | iunit->maxrec=0; | 
|  | iunit->current_record=0; | 
|  | iunit->read_bad = 0; | 
|  | iunit->endfile = NO_ENDFILE; | 
|  |  | 
|  | /* Set flags for the internal unit.  */ | 
|  |  | 
|  | iunit->flags.access = ACCESS_SEQUENTIAL; | 
|  | iunit->flags.action = ACTION_READWRITE; | 
|  | iunit->flags.blank = BLANK_NULL; | 
|  | iunit->flags.form = FORM_FORMATTED; | 
|  | iunit->flags.pad = PAD_YES; | 
|  | iunit->flags.status = STATUS_UNSPECIFIED; | 
|  | iunit->flags.sign = SIGN_PROCDEFINED; | 
|  | iunit->flags.decimal = DECIMAL_POINT; | 
|  | iunit->flags.delim = DELIM_UNSPECIFIED; | 
|  | iunit->flags.encoding = ENCODING_DEFAULT; | 
|  | iunit->flags.async = ASYNC_NO; | 
|  | iunit->flags.round = ROUND_PROCDEFINED; | 
|  |  | 
|  | /* Initialize the data transfer parameters.  */ | 
|  |  | 
|  | dtp->u.p.advance_status = ADVANCE_YES; | 
|  | dtp->u.p.seen_dollar = 0; | 
|  | dtp->u.p.skips = 0; | 
|  | dtp->u.p.pending_spaces = 0; | 
|  | dtp->u.p.max_pos = 0; | 
|  | dtp->u.p.at_eof = 0; | 
|  | return iunit; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* get_unit()-- Returns the unit structure associated with the integer | 
|  | unit or the internal file.  */ | 
|  |  | 
|  | gfc_unit * | 
|  | get_unit (st_parameter_dt *dtp, int do_create) | 
|  | { | 
|  | gfc_unit *unit; | 
|  |  | 
|  | if ((dtp->common.flags & IOPARM_DT_HAS_INTERNAL_UNIT) != 0) | 
|  | { | 
|  | int kind; | 
|  | if (dtp->common.unit == GFC_INTERNAL_UNIT) | 
|  | kind = 1; | 
|  | else if (dtp->common.unit == GFC_INTERNAL_UNIT4) | 
|  | kind = 4; | 
|  | else | 
|  | internal_error (&dtp->common, "get_unit(): Bad internal unit KIND"); | 
|  |  | 
|  | dtp->u.p.unit_is_internal = 1; | 
|  | dtp->common.unit = newunit_alloc (); | 
|  | unit = get_gfc_unit (dtp->common.unit, do_create); | 
|  | set_internal_unit (dtp, unit, kind); | 
|  | fbuf_init (unit, 128); | 
|  | return unit; | 
|  | } | 
|  |  | 
|  | /* Has to be an external unit.  */ | 
|  | dtp->u.p.unit_is_internal = 0; | 
|  | dtp->internal_unit = NULL; | 
|  | dtp->internal_unit_desc = NULL; | 
|  |  | 
|  | /* For an external unit with unit number < 0 creating it on the fly | 
|  | is not allowed, such units must be created with | 
|  | OPEN(NEWUNIT=...).  */ | 
|  | if (dtp->common.unit < 0) | 
|  | { | 
|  | if (dtp->common.unit > NEWUNIT_START) /* Reserved units.  */ | 
|  | return NULL; | 
|  | return get_gfc_unit (dtp->common.unit, 0); | 
|  | } | 
|  |  | 
|  | return get_gfc_unit (dtp->common.unit, do_create); | 
|  | } | 
|  |  | 
|  |  | 
|  | /*************************/ | 
|  | /* Initialize everything.  */ | 
|  |  | 
|  | void | 
|  | init_units (void) | 
|  | { | 
|  | gfc_unit *u; | 
|  |  | 
|  | #ifdef HAVE_NEWLOCALE | 
|  | c_locale = newlocale (0, "C", 0); | 
|  | #else | 
|  | #ifndef __GTHREAD_MUTEX_INIT | 
|  | __GTHREAD_MUTEX_INIT_FUNCTION (&old_locale_lock); | 
|  | #endif | 
|  | #endif | 
|  |  | 
|  | #ifndef __GTHREAD_MUTEX_INIT | 
|  | __GTHREAD_MUTEX_INIT_FUNCTION (&unit_lock); | 
|  | #endif | 
|  |  | 
|  | if (sizeof (max_offset) == 8) | 
|  | { | 
|  | max_offset = GFC_INTEGER_8_HUGE; | 
|  | /* Why this weird value? Because if the recl specifier in the | 
|  | inquire statement is a 4 byte value, u->recl is truncated, | 
|  | and this trick ensures it becomes HUGE(0) rather than -1. | 
|  | The full 8 byte value of default_recl is still 0.99999999 * | 
|  | max_offset which is large enough for all practical | 
|  | purposes.  */ | 
|  | default_recl = max_offset & ~(1LL<<31); | 
|  | } | 
|  | else if (sizeof (max_offset) == 4) | 
|  | max_offset = default_recl = GFC_INTEGER_4_HUGE; | 
|  | else | 
|  | internal_error (NULL, "sizeof (max_offset) must be 4 or 8"); | 
|  |  | 
|  | if (options.stdin_unit >= 0) | 
|  | {				/* STDIN */ | 
|  | u = insert_unit (options.stdin_unit); | 
|  | u->s = input_stream (); | 
|  |  | 
|  | u->flags.action = ACTION_READ; | 
|  |  | 
|  | u->flags.access = ACCESS_SEQUENTIAL; | 
|  | u->flags.form = FORM_FORMATTED; | 
|  | u->flags.status = STATUS_OLD; | 
|  | u->flags.blank = BLANK_NULL; | 
|  | u->flags.pad = PAD_YES; | 
|  | u->flags.position = POSITION_ASIS; | 
|  | u->flags.sign = SIGN_PROCDEFINED; | 
|  | u->flags.decimal = DECIMAL_POINT; | 
|  | u->flags.delim = DELIM_UNSPECIFIED; | 
|  | u->flags.encoding = ENCODING_DEFAULT; | 
|  | u->flags.async = ASYNC_NO; | 
|  | u->flags.round = ROUND_PROCDEFINED; | 
|  | u->flags.share = SHARE_UNSPECIFIED; | 
|  | u->flags.cc = CC_LIST; | 
|  |  | 
|  | u->recl = default_recl; | 
|  | u->endfile = NO_ENDFILE; | 
|  |  | 
|  | u->filename = strdup (stdin_name); | 
|  |  | 
|  | fbuf_init (u, 0); | 
|  |  | 
|  | UNLOCK (&u->lock); | 
|  | } | 
|  |  | 
|  | if (options.stdout_unit >= 0) | 
|  | {				/* STDOUT */ | 
|  | u = insert_unit (options.stdout_unit); | 
|  | u->s = output_stream (); | 
|  |  | 
|  | u->flags.action = ACTION_WRITE; | 
|  |  | 
|  | u->flags.access = ACCESS_SEQUENTIAL; | 
|  | u->flags.form = FORM_FORMATTED; | 
|  | u->flags.status = STATUS_OLD; | 
|  | u->flags.blank = BLANK_NULL; | 
|  | u->flags.position = POSITION_ASIS; | 
|  | u->flags.sign = SIGN_PROCDEFINED; | 
|  | u->flags.decimal = DECIMAL_POINT; | 
|  | u->flags.delim = DELIM_UNSPECIFIED; | 
|  | u->flags.encoding = ENCODING_DEFAULT; | 
|  | u->flags.async = ASYNC_NO; | 
|  | u->flags.round = ROUND_PROCDEFINED; | 
|  | u->flags.share = SHARE_UNSPECIFIED; | 
|  | u->flags.cc = CC_LIST; | 
|  |  | 
|  | u->recl = default_recl; | 
|  | u->endfile = AT_ENDFILE; | 
|  |  | 
|  | u->filename = strdup (stdout_name); | 
|  |  | 
|  | fbuf_init (u, 0); | 
|  |  | 
|  | UNLOCK (&u->lock); | 
|  | } | 
|  |  | 
|  | if (options.stderr_unit >= 0) | 
|  | {				/* STDERR */ | 
|  | u = insert_unit (options.stderr_unit); | 
|  | u->s = error_stream (); | 
|  |  | 
|  | u->flags.action = ACTION_WRITE; | 
|  |  | 
|  | u->flags.access = ACCESS_SEQUENTIAL; | 
|  | u->flags.form = FORM_FORMATTED; | 
|  | u->flags.status = STATUS_OLD; | 
|  | u->flags.blank = BLANK_NULL; | 
|  | u->flags.position = POSITION_ASIS; | 
|  | u->flags.sign = SIGN_PROCDEFINED; | 
|  | u->flags.decimal = DECIMAL_POINT; | 
|  | u->flags.encoding = ENCODING_DEFAULT; | 
|  | u->flags.async = ASYNC_NO; | 
|  | u->flags.round = ROUND_PROCDEFINED; | 
|  | u->flags.share = SHARE_UNSPECIFIED; | 
|  | u->flags.cc = CC_LIST; | 
|  |  | 
|  | u->recl = default_recl; | 
|  | u->endfile = AT_ENDFILE; | 
|  |  | 
|  | u->filename = strdup (stderr_name); | 
|  |  | 
|  | fbuf_init (u, 256);  /* 256 bytes should be enough, probably not doing | 
|  | any kind of exotic formatting to stderr.  */ | 
|  |  | 
|  | UNLOCK (&u->lock); | 
|  | } | 
|  | /* The default internal units.  */ | 
|  | u = insert_unit (GFC_INTERNAL_UNIT); | 
|  | UNLOCK (&u->lock); | 
|  | u = insert_unit (GFC_INTERNAL_UNIT4); | 
|  | UNLOCK (&u->lock); | 
|  | } | 
|  |  | 
|  |  | 
|  | static int | 
|  | close_unit_1 (gfc_unit *u, int locked) | 
|  | { | 
|  | int i, rc; | 
|  |  | 
|  | if (ASYNC_IO && u->au) | 
|  | async_close (u->au); | 
|  |  | 
|  | /* If there are previously written bytes from a write with ADVANCE="no" | 
|  | Reposition the buffer before closing.  */ | 
|  | if (u->previous_nonadvancing_write) | 
|  | finish_last_advance_record (u); | 
|  |  | 
|  | rc = (u->s == NULL) ? 0 : sclose (u->s) == -1; | 
|  |  | 
|  | u->closed = 1; | 
|  | if (!locked) | 
|  | LOCK (&unit_lock); | 
|  |  | 
|  | for (i = 0; i < CACHE_SIZE; i++) | 
|  | if (unit_cache[i] == u) | 
|  | unit_cache[i] = NULL; | 
|  |  | 
|  | delete_unit (u); | 
|  |  | 
|  | free (u->filename); | 
|  | u->filename = NULL; | 
|  |  | 
|  | free_format_hash_table (u); | 
|  | fbuf_destroy (u); | 
|  |  | 
|  | if (u->unit_number <= NEWUNIT_START) | 
|  | newunit_free (u->unit_number); | 
|  |  | 
|  | if (!locked) | 
|  | UNLOCK (&u->lock); | 
|  |  | 
|  | /* If there are any threads waiting in find_unit for this unit, | 
|  | avoid freeing the memory, the last such thread will free it | 
|  | instead.  */ | 
|  | if (u->waiting == 0) | 
|  | destroy_unit_mutex (u); | 
|  |  | 
|  | if (!locked) | 
|  | UNLOCK (&unit_lock); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | void | 
|  | unlock_unit (gfc_unit *u) | 
|  | { | 
|  | if (u) | 
|  | { | 
|  | NOTE ("unlock_unit = %d", u->unit_number); | 
|  | UNLOCK (&u->lock); | 
|  | NOTE ("unlock_unit done"); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* close_unit()-- Close a unit.  The stream is closed, and any memory | 
|  | associated with the stream is freed.  Returns nonzero on I/O error. | 
|  | Should be called with the u->lock locked. */ | 
|  |  | 
|  | int | 
|  | close_unit (gfc_unit *u) | 
|  | { | 
|  | return close_unit_1 (u, 0); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* close_units()-- Delete units on completion.  We just keep deleting | 
|  | the root of the treap until there is nothing left. | 
|  | Not sure what to do with locking here.  Some other thread might be | 
|  | holding some unit's lock and perhaps hold it indefinitely | 
|  | (e.g. waiting for input from some pipe) and close_units shouldn't | 
|  | delay the program too much.  */ | 
|  |  | 
|  | void | 
|  | close_units (void) | 
|  | { | 
|  | LOCK (&unit_lock); | 
|  | while (unit_root != NULL) | 
|  | close_unit_1 (unit_root, 1); | 
|  | UNLOCK (&unit_lock); | 
|  |  | 
|  | free (newunits); | 
|  |  | 
|  | #ifdef HAVE_FREELOCALE | 
|  | freelocale (c_locale); | 
|  | #endif | 
|  | } | 
|  |  | 
|  |  | 
|  | /* High level interface to truncate a file, i.e. flush format buffers, | 
|  | and generate an error or set some flags.  Just like POSIX | 
|  | ftruncate, returns 0 on success, -1 on failure.  */ | 
|  |  | 
|  | int | 
|  | unit_truncate (gfc_unit *u, gfc_offset pos, st_parameter_common *common) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | /* Make sure format buffer is flushed.  */ | 
|  | if (u->flags.form == FORM_FORMATTED) | 
|  | { | 
|  | if (u->mode == READING) | 
|  | pos += fbuf_reset (u); | 
|  | else | 
|  | fbuf_flush (u, u->mode); | 
|  | } | 
|  |  | 
|  | /* struncate() should flush the stream buffer if necessary, so don't | 
|  | bother calling sflush() here.  */ | 
|  | ret = struncate (u->s, pos); | 
|  |  | 
|  | if (ret != 0) | 
|  | generate_error (common, LIBERROR_OS, NULL); | 
|  | else | 
|  | { | 
|  | u->endfile = AT_ENDFILE; | 
|  | u->flags.position = POSITION_APPEND; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* filename_from_unit()-- If the unit_number exists, return a pointer to the | 
|  | name of the associated file, otherwise return the empty string.  The caller | 
|  | must free memory allocated for the filename string.  */ | 
|  |  | 
|  | char * | 
|  | filename_from_unit (int n) | 
|  | { | 
|  | gfc_unit *u; | 
|  | int c; | 
|  |  | 
|  | /* Find the unit.  */ | 
|  | u = unit_root; | 
|  | while (u != NULL) | 
|  | { | 
|  | c = compare (n, u->unit_number); | 
|  | if (c < 0) | 
|  | u = u->left; | 
|  | if (c > 0) | 
|  | u = u->right; | 
|  | if (c == 0) | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* Get the filename.  */ | 
|  | if (u != NULL && u->filename != NULL) | 
|  | return strdup (u->filename); | 
|  | else | 
|  | return (char *) NULL; | 
|  | } | 
|  |  | 
|  | void | 
|  | finish_last_advance_record (gfc_unit *u) | 
|  | { | 
|  |  | 
|  | if (u->saved_pos > 0) | 
|  | fbuf_seek (u, u->saved_pos, SEEK_CUR); | 
|  |  | 
|  | if (!(u->unit_number == options.stdout_unit | 
|  | || u->unit_number == options.stderr_unit)) | 
|  | { | 
|  | #ifdef HAVE_CRLF | 
|  | const int len = 2; | 
|  | #else | 
|  | const int len = 1; | 
|  | #endif | 
|  | char *p = fbuf_alloc (u, len); | 
|  | if (!p) | 
|  | os_error ("Completing record after ADVANCE_NO failed"); | 
|  | #ifdef HAVE_CRLF | 
|  | *(p++) = '\r'; | 
|  | #endif | 
|  | *p = '\n'; | 
|  | } | 
|  |  | 
|  | fbuf_flush (u, u->mode); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Assign a negative number for NEWUNIT in OPEN statements or for | 
|  | internal units.  */ | 
|  | int | 
|  | newunit_alloc (void) | 
|  | { | 
|  | LOCK (&unit_lock); | 
|  | if (!newunits) | 
|  | { | 
|  | newunits = xcalloc (16, 1); | 
|  | newunit_size = 16; | 
|  | } | 
|  |  | 
|  | /* Search for the next available newunit.  */ | 
|  | for (int ii = newunit_lwi; ii < newunit_size; ii++) | 
|  | { | 
|  | if (!newunits[ii]) | 
|  | { | 
|  | newunits[ii] = true; | 
|  | newunit_lwi = ii + 1; | 
|  | UNLOCK (&unit_lock); | 
|  | return -ii + NEWUNIT_START; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Search failed, bump size of array and allocate the first | 
|  | available unit.  */ | 
|  | int old_size = newunit_size; | 
|  | newunit_size *= 2; | 
|  | newunits = xrealloc (newunits, newunit_size); | 
|  | memset (newunits + old_size, 0, old_size); | 
|  | newunits[old_size] = true; | 
|  | newunit_lwi = old_size + 1; | 
|  | UNLOCK (&unit_lock); | 
|  | return -old_size + NEWUNIT_START; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Free a previously allocated newunit= unit number.  unit_lock must | 
|  | be held when calling.  */ | 
|  |  | 
|  | void | 
|  | newunit_free (int unit) | 
|  | { | 
|  | int ind = -unit + NEWUNIT_START; | 
|  | assert(ind >= 0 && ind < newunit_size); | 
|  | newunits[ind] = false; | 
|  | if (ind < newunit_lwi) | 
|  | newunit_lwi = ind; | 
|  | } |