| /* Copyright (C) 2002-2019 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> |
| #include <ctype.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 (!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 |
| /* 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 (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; |
| |
| 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. */ |
| |
| static void |
| mark_single (int unit) |
| { |
| int i,j; |
| |
| if (do_count) |
| { |
| unit_count++; |
| return; |
| } |
| if (search_unit (unit, &i)) |
| { |
| elist[i].conv = endian; |
| } |
| else |
| { |
| for (j=n_elist-1; j>=i; j--) |
| elist[j+1] = elist[j]; |
| |
| n_elist += 1; |
| elist[i].unit = unit; |
| elist[i].conv = endian; |
| } |
| } |
| |
| /* 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; |
| |
| start = p; |
| |
| /* Parse the string. First, let's look for a default. */ |
| 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; |
| |
| 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 = 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 = 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; |
| |
| 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; |
| } |