730 lines
15 KiB
C
730 lines
15 KiB
C
/* Copyright (C) 2002-2022 Free Software Foundation, Inc.
|
|
Contributed by Andy Vaught
|
|
|
|
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 "libgfortran.h"
|
|
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
|
|
/* Implementation of secure_getenv() for targets where it is not
|
|
provided. */
|
|
|
|
#ifdef FALLBACK_SECURE_GETENV
|
|
|
|
#if SUPPORTS_WEAKREF && defined(HAVE___SECURE_GETENV)
|
|
static char* weak_secure_getenv (const char*)
|
|
__attribute__((__weakref__("__secure_getenv")));
|
|
#endif
|
|
|
|
char *
|
|
secure_getenv (const char *name)
|
|
{
|
|
#if SUPPORTS_WEAKREF && defined(HAVE___SECURE_GETENV)
|
|
if (weak_secure_getenv)
|
|
return weak_secure_getenv (name);
|
|
#endif
|
|
|
|
if ((getuid () == geteuid ()) && (getgid () == getegid ()))
|
|
return getenv (name);
|
|
else
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
/* Examine the environment for controlling aspects of the program's
|
|
execution. Our philosophy here that the environment should not prevent
|
|
the program from running, so any invalid value will be ignored. */
|
|
|
|
|
|
options_t options;
|
|
|
|
typedef struct variable
|
|
{
|
|
const char *name;
|
|
int default_value;
|
|
int *var;
|
|
void (*init) (struct variable *);
|
|
}
|
|
variable;
|
|
|
|
static void init_unformatted (variable *);
|
|
|
|
|
|
/* Initialize an integer environment variable. */
|
|
|
|
static void
|
|
init_integer (variable * v)
|
|
{
|
|
char *p, *q;
|
|
|
|
p = getenv (v->name);
|
|
if (p == NULL)
|
|
return;
|
|
|
|
for (q = p; *q; q++)
|
|
if (!safe_isdigit (*q) && (p != q || *q != '-'))
|
|
return;
|
|
|
|
*v->var = atoi (p);
|
|
}
|
|
|
|
|
|
/* Initialize a boolean environment variable. We only look at the first
|
|
letter of the value. */
|
|
|
|
static void
|
|
init_boolean (variable * v)
|
|
{
|
|
char *p;
|
|
|
|
p = getenv (v->name);
|
|
if (p == NULL)
|
|
return;
|
|
|
|
if (*p == '1' || *p == 'Y' || *p == 'y')
|
|
*v->var = 1;
|
|
else if (*p == '0' || *p == 'N' || *p == 'n')
|
|
*v->var = 0;
|
|
}
|
|
|
|
|
|
/* Initialize a list output separator. It may contain any number of spaces
|
|
and at most one comma. */
|
|
|
|
static void
|
|
init_sep (variable * v)
|
|
{
|
|
int seen_comma;
|
|
char *p;
|
|
|
|
p = getenv (v->name);
|
|
if (p == NULL)
|
|
goto set_default;
|
|
|
|
options.separator = p;
|
|
options.separator_len = strlen (p);
|
|
|
|
/* Make sure the separator is valid */
|
|
|
|
if (options.separator_len == 0)
|
|
goto set_default;
|
|
seen_comma = 0;
|
|
|
|
while (*p)
|
|
{
|
|
if (*p == ',')
|
|
{
|
|
if (seen_comma)
|
|
goto set_default;
|
|
seen_comma = 1;
|
|
p++;
|
|
continue;
|
|
}
|
|
|
|
if (*p++ != ' ')
|
|
goto set_default;
|
|
}
|
|
|
|
return;
|
|
|
|
set_default:
|
|
options.separator = " ";
|
|
options.separator_len = 1;
|
|
}
|
|
|
|
|
|
static variable variable_table[] = {
|
|
|
|
/* Unit number that will be preconnected to standard input */
|
|
{ "GFORTRAN_STDIN_UNIT", GFC_STDIN_UNIT_NUMBER, &options.stdin_unit,
|
|
init_integer },
|
|
|
|
/* Unit number that will be preconnected to standard output */
|
|
{ "GFORTRAN_STDOUT_UNIT", GFC_STDOUT_UNIT_NUMBER, &options.stdout_unit,
|
|
init_integer },
|
|
|
|
/* Unit number that will be preconnected to standard error */
|
|
{ "GFORTRAN_STDERR_UNIT", GFC_STDERR_UNIT_NUMBER, &options.stderr_unit,
|
|
init_integer },
|
|
|
|
/* If TRUE, all output will be unbuffered */
|
|
{ "GFORTRAN_UNBUFFERED_ALL", 0, &options.all_unbuffered, init_boolean },
|
|
|
|
/* If TRUE, output to preconnected units will be unbuffered */
|
|
{ "GFORTRAN_UNBUFFERED_PRECONNECTED", 0, &options.unbuffered_preconnected,
|
|
init_boolean },
|
|
|
|
/* Whether to print filename and line number on runtime error */
|
|
{ "GFORTRAN_SHOW_LOCUS", 1, &options.locus, init_boolean },
|
|
|
|
/* Print optional plus signs in numbers where permitted */
|
|
{ "GFORTRAN_OPTIONAL_PLUS", 0, &options.optional_plus, init_boolean },
|
|
|
|
/* Separator to use when writing list output */
|
|
{ "GFORTRAN_LIST_SEPARATOR", 0, NULL, init_sep },
|
|
|
|
/* Set the default data conversion for unformatted I/O */
|
|
{ "GFORTRAN_CONVERT_UNIT", 0, 0, init_unformatted },
|
|
|
|
/* Print out a backtrace if possible on runtime error */
|
|
{ "GFORTRAN_ERROR_BACKTRACE", -1, &options.backtrace, init_boolean },
|
|
|
|
/* Buffer size for unformatted files. */
|
|
{ "GFORTRAN_UNFORMATTED_BUFFER_SIZE", 0, &options.unformatted_buffer_size,
|
|
init_integer },
|
|
|
|
/* Buffer size for formatted files. */
|
|
{ "GFORTRAN_FORMATTED_BUFFER_SIZE", 0, &options.formatted_buffer_size,
|
|
init_integer },
|
|
|
|
{ NULL, 0, NULL, NULL }
|
|
};
|
|
|
|
|
|
/* Initialize most runtime variables from
|
|
* environment variables. */
|
|
|
|
void
|
|
init_variables (void)
|
|
{
|
|
variable *v;
|
|
|
|
for (v = variable_table; v->name; v++)
|
|
{
|
|
if (v->var)
|
|
*v->var = v->default_value;
|
|
v->init (v);
|
|
}
|
|
}
|
|
|
|
|
|
/* This is the handling of the GFORTRAN_CONVERT_UNITS environment variable.
|
|
It is called from environ.c to parse this variable, and from
|
|
open.c to determine if the user specified a default for an
|
|
unformatted file.
|
|
The syntax of the environment variable is, in bison grammar:
|
|
|
|
GFORTRAN_CONVERT_UNITS: mode | mode ';' exception ;
|
|
mode: 'native' | 'swap' | 'big_endian' | 'little_endian' ;
|
|
exception: mode ':' unit_list | unit_list ;
|
|
unit_list: unit_spec | unit_list unit_spec ;
|
|
unit_spec: INTEGER | INTEGER '-' INTEGER ;
|
|
*/
|
|
|
|
/* Defines for the tokens. Other valid tokens are ',', ':', '-'. */
|
|
|
|
|
|
#define NATIVE 257
|
|
#define SWAP 258
|
|
#define BIG 259
|
|
#define LITTLE 260
|
|
#ifdef HAVE_GFC_REAL_17
|
|
#define R16_IEEE 261
|
|
#define R16_IBM 262
|
|
#endif
|
|
|
|
/* Some space for additional tokens later. */
|
|
#define INTEGER 273
|
|
#define END (-1)
|
|
#define ILLEGAL (-2)
|
|
|
|
typedef struct
|
|
{
|
|
int unit;
|
|
unit_convert conv;
|
|
} exception_t;
|
|
|
|
|
|
static char *p; /* Main character pointer for parsing. */
|
|
static char *lastpos; /* Auxiliary pointer, for backing up. */
|
|
static int unit_num; /* The last unit number read. */
|
|
static int unit_count; /* The number of units found. */
|
|
static int do_count; /* Parsing is done twice - first to count the number
|
|
of units, then to fill in the table. This
|
|
variable controls what to do. */
|
|
static exception_t *elist; /* The list of exceptions to the default. This is
|
|
sorted according to unit number. */
|
|
static int n_elist; /* Number of exceptions to the default. */
|
|
|
|
static unit_convert endian; /* Current endianness. */
|
|
|
|
static unit_convert def; /* Default as specified (if any). */
|
|
|
|
/* Search for a unit number, using a binary search. The
|
|
first argument is the unit number to search for. The second argument
|
|
is a pointer to an index.
|
|
If the unit number is found, the function returns 1, and the index
|
|
is that of the element.
|
|
If the unit number is not found, the function returns 0, and the
|
|
index is the one where the element would be inserted. */
|
|
|
|
static int
|
|
search_unit (int unit, int *ip)
|
|
{
|
|
int low, high, mid;
|
|
|
|
if (n_elist == 0)
|
|
{
|
|
*ip = 0;
|
|
return 0;
|
|
}
|
|
|
|
low = 0;
|
|
high = n_elist - 1;
|
|
|
|
do
|
|
{
|
|
mid = (low + high) / 2;
|
|
if (unit == elist[mid].unit)
|
|
{
|
|
*ip = mid;
|
|
return 1;
|
|
}
|
|
else if (unit > elist[mid].unit)
|
|
low = mid + 1;
|
|
else
|
|
high = mid - 1;
|
|
} while (low <= high);
|
|
|
|
if (unit > elist[mid].unit)
|
|
*ip = mid + 1;
|
|
else
|
|
*ip = mid;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* This matches a keyword. If it is found, return the token supplied,
|
|
otherwise return ILLEGAL. */
|
|
|
|
static int
|
|
match_word (const char *word, int tok)
|
|
{
|
|
int res;
|
|
|
|
if (strncasecmp (p, word, strlen (word)) == 0)
|
|
{
|
|
p += strlen (word);
|
|
res = tok;
|
|
}
|
|
else
|
|
res = ILLEGAL;
|
|
return res;
|
|
}
|
|
|
|
/* Match an integer and store its value in unit_num. This only works
|
|
if p actually points to the start of an integer. The caller has
|
|
to ensure this. */
|
|
|
|
static int
|
|
match_integer (void)
|
|
{
|
|
unit_num = 0;
|
|
while (safe_isdigit (*p))
|
|
unit_num = unit_num * 10 + (*p++ - '0');
|
|
return INTEGER;
|
|
}
|
|
|
|
/* This reads the next token from the GFORTRAN_CONVERT_UNITS variable.
|
|
Returned values are the different tokens. */
|
|
|
|
static int
|
|
next_token (void)
|
|
{
|
|
int result;
|
|
|
|
lastpos = p;
|
|
switch (*p)
|
|
{
|
|
case '\0':
|
|
result = END;
|
|
break;
|
|
|
|
case ':':
|
|
case ',':
|
|
case '-':
|
|
case ';':
|
|
result = *p;
|
|
p++;
|
|
break;
|
|
|
|
case 'b':
|
|
case 'B':
|
|
result = match_word ("big_endian", BIG);
|
|
break;
|
|
|
|
case 'l':
|
|
case 'L':
|
|
result = match_word ("little_endian", LITTLE);
|
|
break;
|
|
|
|
case 'n':
|
|
case 'N':
|
|
result = match_word ("native", NATIVE);
|
|
break;
|
|
|
|
case 's':
|
|
case 'S':
|
|
result = match_word ("swap", SWAP);
|
|
break;
|
|
|
|
#ifdef HAVE_GFC_REAL_17
|
|
case 'r':
|
|
case 'R':
|
|
result = match_word ("r16_ieee", R16_IEEE);
|
|
if (result == ILLEGAL)
|
|
result = match_word ("r16_ibm", R16_IBM);
|
|
break;
|
|
|
|
#endif
|
|
case '1': case '2': case '3': case '4': case '5':
|
|
case '6': case '7': case '8': case '9':
|
|
result = match_integer ();
|
|
break;
|
|
|
|
default:
|
|
result = ILLEGAL;
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* Back up the last token by setting back the character pointer. */
|
|
|
|
static void
|
|
push_token (void)
|
|
{
|
|
p = lastpos;
|
|
}
|
|
|
|
/* This is called when a unit is identified. If do_count is nonzero,
|
|
increment the number of units by one. If do_count is zero,
|
|
put the unit into the table. For POWER, we have to make sure that
|
|
we can also put in the conversion btween IBM and IEEE long double. */
|
|
|
|
static void
|
|
mark_single (int unit)
|
|
{
|
|
int i,j;
|
|
|
|
if (do_count)
|
|
{
|
|
unit_count++;
|
|
return;
|
|
}
|
|
if (search_unit (unit, &i))
|
|
{
|
|
#ifdef HAVE_GFC_REAL_17
|
|
elist[i].conv |= endian;
|
|
#else
|
|
elist[i].conv = endian;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
for (j=n_elist-1; j>=i; j--)
|
|
elist[j+1] = elist[j];
|
|
|
|
n_elist += 1;
|
|
elist[i].unit = unit;
|
|
#ifdef HAVE_GFC_REAL_17
|
|
elist[i].conv |= endian;
|
|
#else
|
|
elist[i].conv = endian;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* This is called when a unit range is identified. If do_count is
|
|
nonzero, increase the number of units. If do_count is zero,
|
|
put the unit into the table. */
|
|
|
|
static void
|
|
mark_range (int unit1, int unit2)
|
|
{
|
|
int i;
|
|
if (do_count)
|
|
unit_count += abs (unit2 - unit1) + 1;
|
|
else
|
|
{
|
|
if (unit2 < unit1)
|
|
for (i=unit2; i<=unit1; i++)
|
|
mark_single (i);
|
|
else
|
|
for (i=unit1; i<=unit2; i++)
|
|
mark_single (i);
|
|
}
|
|
}
|
|
|
|
/* Parse the GFORTRAN_CONVERT_UNITS variable. This is called
|
|
twice, once to count the units and once to actually mark them in
|
|
the table. When counting, we don't check for double occurrences
|
|
of units. */
|
|
|
|
static int
|
|
do_parse (void)
|
|
{
|
|
int tok;
|
|
int unit1;
|
|
int continue_ulist;
|
|
char *start;
|
|
|
|
unit_count = 0;
|
|
|
|
/* Parse the string. First, let's look for a default. */
|
|
endian = 0;
|
|
while (1)
|
|
{
|
|
start = p;
|
|
tok = next_token ();
|
|
switch (tok)
|
|
{
|
|
case NATIVE:
|
|
endian = GFC_CONVERT_NATIVE;
|
|
break;
|
|
|
|
case SWAP:
|
|
endian = GFC_CONVERT_SWAP;
|
|
break;
|
|
|
|
case BIG:
|
|
endian = GFC_CONVERT_BIG;
|
|
break;
|
|
|
|
case LITTLE:
|
|
endian = GFC_CONVERT_LITTLE;
|
|
break;
|
|
|
|
#ifdef HAVE_GFC_REAL_17
|
|
case R16_IEEE:
|
|
endian = GFC_CONVERT_R16_IEEE;
|
|
break;
|
|
|
|
case R16_IBM:
|
|
endian = GFC_CONVERT_R16_IBM;
|
|
break;
|
|
#endif
|
|
case INTEGER:
|
|
/* A leading digit means that we are looking at an exception.
|
|
Reset the position to the beginning, and continue processing
|
|
at the exception list. */
|
|
p = start;
|
|
goto exceptions;
|
|
break;
|
|
|
|
case END:
|
|
goto end;
|
|
break;
|
|
|
|
default:
|
|
goto error;
|
|
break;
|
|
}
|
|
|
|
tok = next_token ();
|
|
switch (tok)
|
|
{
|
|
case ';':
|
|
def = def == GFC_CONVERT_NONE ? endian : def | endian;
|
|
break;
|
|
|
|
case ':':
|
|
/* This isn't a default after all. Reset the position to the
|
|
beginning, and continue processing at the exception list. */
|
|
p = start;
|
|
goto exceptions;
|
|
break;
|
|
|
|
case END:
|
|
def = def == GFC_CONVERT_NONE ? endian : def | endian;
|
|
goto end;
|
|
break;
|
|
|
|
default:
|
|
goto error;
|
|
break;
|
|
}
|
|
}
|
|
|
|
exceptions:
|
|
|
|
/* Loop over all exceptions. */
|
|
while(1)
|
|
{
|
|
tok = next_token ();
|
|
switch (tok)
|
|
{
|
|
case NATIVE:
|
|
if (next_token () != ':')
|
|
goto error;
|
|
endian = GFC_CONVERT_NATIVE;
|
|
break;
|
|
|
|
case SWAP:
|
|
if (next_token () != ':')
|
|
goto error;
|
|
endian = GFC_CONVERT_SWAP;
|
|
break;
|
|
|
|
case LITTLE:
|
|
if (next_token () != ':')
|
|
goto error;
|
|
endian = GFC_CONVERT_LITTLE;
|
|
break;
|
|
|
|
case BIG:
|
|
if (next_token () != ':')
|
|
goto error;
|
|
endian = GFC_CONVERT_BIG;
|
|
break;
|
|
#ifdef HAVE_GFC_REAL_17
|
|
case R16_IEEE:
|
|
if (next_token () != ':')
|
|
goto error;
|
|
endian = GFC_CONVERT_R16_IEEE;
|
|
break;
|
|
|
|
case R16_IBM:
|
|
if (next_token () != ':')
|
|
goto error;
|
|
endian = GFC_CONVERT_R16_IBM;
|
|
break;
|
|
#endif
|
|
|
|
case INTEGER:
|
|
push_token ();
|
|
break;
|
|
|
|
case END:
|
|
goto end;
|
|
break;
|
|
|
|
default:
|
|
goto error;
|
|
break;
|
|
}
|
|
/* We arrive here when we want to parse a list of
|
|
numbers. */
|
|
continue_ulist = 1;
|
|
do
|
|
{
|
|
tok = next_token ();
|
|
if (tok != INTEGER)
|
|
goto error;
|
|
|
|
unit1 = unit_num;
|
|
tok = next_token ();
|
|
/* The number can be followed by a - and another number,
|
|
which means that this is a unit range, a comma
|
|
or a semicolon. */
|
|
if (tok == '-')
|
|
{
|
|
if (next_token () != INTEGER)
|
|
goto error;
|
|
|
|
mark_range (unit1, unit_num);
|
|
tok = next_token ();
|
|
if (tok == END)
|
|
goto end;
|
|
else if (tok == ';')
|
|
continue_ulist = 0;
|
|
else if (tok != ',')
|
|
goto error;
|
|
}
|
|
else
|
|
{
|
|
mark_single (unit1);
|
|
switch (tok)
|
|
{
|
|
case ';':
|
|
continue_ulist = 0;
|
|
break;
|
|
|
|
case ',':
|
|
break;
|
|
|
|
case END:
|
|
goto end;
|
|
break;
|
|
|
|
default:
|
|
goto error;
|
|
}
|
|
}
|
|
} while (continue_ulist);
|
|
}
|
|
end:
|
|
return 0;
|
|
error:
|
|
def = GFC_CONVERT_NONE;
|
|
return -1;
|
|
}
|
|
|
|
void init_unformatted (variable * v)
|
|
{
|
|
char *val;
|
|
val = getenv (v->name);
|
|
def = GFC_CONVERT_NONE;
|
|
n_elist = 0;
|
|
|
|
if (val == NULL)
|
|
return;
|
|
do_count = 1;
|
|
p = val;
|
|
do_parse ();
|
|
if (do_count <= 0)
|
|
{
|
|
n_elist = 0;
|
|
elist = NULL;
|
|
}
|
|
else
|
|
{
|
|
elist = xmallocarray (unit_count, sizeof (exception_t));
|
|
do_count = 0;
|
|
p = val;
|
|
do_parse ();
|
|
}
|
|
}
|
|
|
|
/* Get the default conversion for for an unformatted unit. */
|
|
|
|
unit_convert
|
|
get_unformatted_convert (int unit)
|
|
{
|
|
int i;
|
|
|
|
if (elist == NULL)
|
|
return def;
|
|
else if (search_unit (unit, &i))
|
|
return elist[i].conv;
|
|
else
|
|
return def;
|
|
}
|