/* variables.c -- Functions for hacking shell variables. */ /* Copyright (C) 1987,1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash 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 1, or (at your option) any later version. Bash 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. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include "shell.h" #include "flags.h" #include "version.h" #ifdef SYSV struct passwd *getpwuid (), *getpwent (); #endif /* The list of shell variables that the user has created, or that came from the environment. */ SHELL_VAR *variable_list; /* The current variable context. This is really a count of how deep into executing functions we are. */ int variable_context = 0; /* The array of shell assignments which are made only in the environment for a single command. */ char **temporary_env = (char **)NULL; /* Some funky variables which are known about specially. Here is where "$*", "$1", and all the cruft is kept. */ char *dollar_vars[10]; WORD_LIST *rest_of_args = (WORD_LIST *)NULL; /* The value of $$. */ int dollar_dollar_pid; /* An array which is passed to commands as their environment. It is manufactured from the overlap of the initial environment and the shell variables that are marked for export. */ char **export_env = (char **)NULL; /* Non-zero means that we have to remake EXPORT_ENV. */ int array_needs_making = 1; /* The list of variables that may not be unset in this shell. */ char **non_unsettable_vars = (char **)NULL; #ifdef SYSV #define DEFAULT_MAIL_PATH "/usr/mail/" #else #define DEFAULT_MAIL_PATH "/usr/spool/mail/" #endif /* Initialize the shell variables from the current environment. */ initialize_shell_variables (env) char *env[]; { extern char *primary_prompt, *secondary_prompt; char *name, *string; int c, char_index; int string_index = 0; SHELL_VAR *temp_var; while (string = env[string_index++]) { char_index = 0; name = (char *)alloca (1 + strlen (string)); while ((c = *string++) && c != '=') name[char_index++] = c; name[char_index] = '\0'; /* If exported function, define it now. Sigh. */ if (strncmp ("() {", string, 4) == 0) { char *eval_string = (char *)xmalloc (3 + strlen (string) + strlen (name)); sprintf (eval_string, "%s %s", name, string); parse_and_execute (eval_string, name); } else bind_variable (name, string); set_var_auto_export (name); } /* Remember this pid. */ dollar_dollar_pid = getpid (); /* Now make our own defaults in case the vars that we think are important are missing. */ set_if_not ("PATH", DEFAULT_PATH_VALUE); set_var_auto_export ("PATH"); set_if_not ("TERM", "dumb"); set_var_auto_export ("TERM"); set_if_not ("PS1", primary_prompt); set_if_not ("PS2", secondary_prompt); set_if_not ("IFS", " \t\n"); /* Magic machine types. Pretty convenient. */ bind_variable ("HOSTTYPE", HOSTTYPE); /* Default MAILPATH, and MAILCHECK. */ set_if_not ("MAILCHECK", "60"); if ((get_string_value ("MAIL") == (char *)NULL) && (get_string_value ("MAILPATH") == (char *)NULL)) { extern char *current_user_name; char *tem; tem = (char *)xmalloc (1 + sizeof (DEFAULT_MAIL_PATH) + strlen (current_user_name)); strcpy (tem, DEFAULT_MAIL_PATH); strcat (tem, current_user_name); bind_variable ("MAILPATH", tem); free (tem); } /* Set up $PWD. */ { char *get_working_directory (), *cd; cd = get_working_directory ("shell-init"); if (cd) { bind_variable ("PWD", cd); free (cd); } } /* Do some things with shell level. */ { extern int shell_level; char new_level[10]; int old_level; set_if_not ("SHLVL", "0"); set_var_auto_export ("SHLVL"); sscanf (get_string_value ("SHLVL"), "%d", &old_level); shell_level = old_level + 1; sprintf (new_level, "%d", shell_level); bind_variable ("SHLVL", new_level); } /* Get the full pathname to THIS shell, and set the BASH variable to it. */ { extern char *shell_name, *find_user_command (), *full_pathname (); extern int login_shell; char *tname = find_user_command (shell_name); if ((login_shell == 1) && (*shell_name != '/')) { struct passwd *entry = getpwuid (getuid ()); if (entry) { /* If HOME doesn't exist, set it. */ temp_var = (SHELL_VAR *)find_variable ("HOME"); if (!temp_var) { temp_var = bind_variable ("HOME", entry->pw_dir); temp_var->attributes |= att_exported; } name = savestring (entry->pw_shell); } else name = savestring ("a.out"); } else { if (!tname) { char *make_absolute (); name = make_absolute (shell_name, get_string_value ("PWD")); } else { name = full_pathname (tname); free (tname); } } /* Make the exported environment variable SHELL be whatever the name of this shell is. Note that the `tset' command looks at this variable to determine what style of commands to output; if it ends in "csh", then C-shell commands are output, else Bourne shell commands. */ set_if_not ("SHELL", name); set_var_auto_export ("SHELL"); /* Make a variable called BASH, which is the name of THIS shell. */ temp_var = bind_variable ("BASH", name); temp_var->attributes |= att_exported; free (name); } /* Make a variable called BASH_VERSION which contains the version info. */ { char tt[12]; extern char *dist_version; extern int build_version; sprintf (tt, "%s.%d", dist_version, build_version); bind_variable ("BASH_VERSION", tt); } /* Set history variables to defaults, and then do whatever we would do if the variable had just been set. */ { char *tilde_expand (); char *tem = tilde_expand ("~/.bash_history"); set_if_not ("HISTFILE", tem); free (tem); set_if_not ("HISTSIZE", "500"); sv_histsize ("HISTSIZE"); } /* Gee, might as well get parent pid. */ { char aval[10]; sprintf (aval, "%d", getppid ()); bind_variable ("PPID", aval); } non_unsettable ("PATH"); non_unsettable ("PS1"); non_unsettable ("PS2"); non_unsettable ("IFS"); /* Get the users real user id, and save that in an readonly variable. To make the variable *really* readonly, we have added it to a special list of vars. */ sv_uids (); set_var_read_only ("UID"); set_var_read_only ("EUID"); non_unsettable ("EUID"); non_unsettable ("UID"); } /* Add NAME to the list of variables that cannot be unset if it isn't already there. */ non_unsettable (name) char *name; { register int i; if (!non_unsettable_vars) { non_unsettable_vars = (char **)xmalloc (1 * sizeof (char *)); non_unsettable_vars[0] = (char *)NULL; } for (i = 0; non_unsettable_vars[i]; i++) if (strcmp (non_unsettable_vars[i], name) == 0) return; non_unsettable_vars = (char **)xrealloc (non_unsettable_vars, (2 + i) * sizeof (char *)); non_unsettable_vars[i] = savestring (name); non_unsettable_vars[i + 1] = (char *)NULL; } /* Set NAME to VALUE if NAME has no value. */ set_if_not (name, value) char *name, *value; { char *temp = get_string_value (name); if (!temp) bind_variable (name, value); } /* Print LIST (a linked list of shell variables) to stdout in such a way that they can be read back in. */ print_var_list (list) register SHELL_VAR *list; { while (list) { if (!invisible_p (list)) print_assignment (list); list = list->next; } } /* Print the value of a single SHELL_VAR. No newline is output, but the variable is printed in such a way that it can be read back in. */ print_assignment (var) SHELL_VAR *var; { if (var->value) { printf ("%s=", var->name); print_var_value (var); printf ("\n"); } if (var->function) { printf ("%s=", var->name); print_var_function (var); printf ("\n"); } } /* Print the value cell of VAR, a shell variable. Do not print the name, nor leading/trailing newline. */ print_var_value (var) SHELL_VAR *var; { if (var->value) printf ("%s", var->value); } /* Print the function cell of VAR, a shell variable. Do not print the name, nor leading/trailing newline. */ print_var_function (var) SHELL_VAR *var; { char *named_function_string (); if (var->function) printf ("%s", named_function_string ((char *)NULL, var->function, 1)); } /* Look up the variable entry whose name matches STRING. Returns the entry or NULL. */ SHELL_VAR * find_variable (string) char *string; { register SHELL_VAR *list = variable_list; while (list) { if (strcmp (string, list->name) == 0) return (list); list = list->next; } return ((SHELL_VAR *)NULL); } /* Return the string value of a variable. Return NULL if the variable doesn't exist, or only has a function as a value. Don't cons a new string. */ char * get_string_value (var_name) char *var_name; { SHELL_VAR *var = find_variable (var_name); if (!var) return (char *)NULL; else return (var->value); } /* Create a local variable referenced by NAME. */ SHELL_VAR * make_local_variable (name) char *name; { SHELL_VAR *new_var, *old_var, *bind_variable (); int var_has_name (); /* local foo; local foo; is a no-op. */ { old_var = find_variable (name); if (old_var && old_var->context == variable_context) return (old_var); } old_var = (SHELL_VAR *)delete_element (&variable_list, var_has_name, name); /* If a variable does not already exist with this name, then just make a new one. */ if ((int)old_var == -1) { new_var = bind_variable (name, ""); } else { new_var = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); new_var->name = savestring (name); new_var->value = savestring (""); new_var->function = (char *)copy_command (old_var->function); new_var->attributes = 0; if (exported_p (old_var)) new_var->attributes |= att_exported; new_var->prev_context = old_var; new_var->next = variable_list; variable_list = new_var; } new_var->context = variable_context; return (new_var); } /* Bind a variable name to some string. This conses up the name and value strings. */ SHELL_VAR * bind_variable (name, value) char *name, *value; { SHELL_VAR *entry = find_variable (name); if (!entry) { /* Make a new entry for this variable. Then do the binding. */ entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); entry->attributes = 0; entry->name = savestring (name); if (value) entry->value = savestring (value); else entry->value = (char *)NULL; entry->function = (char *)NULL; entry->next = variable_list; /* Always assume variables are to be made at toplevel! make_local_variable has the responsibilty of changing the variable context. */ entry->context = 0; entry->prev_context = (SHELL_VAR *)NULL; variable_list = entry; } else { if (readonly_p (entry)) { report_error ("%s: read-only variable", name); return (entry); } if (entry->value) free (entry->value); if (value) entry->value = savestring (value); else entry->value = (char *)NULL; } if (mark_modified_vars) entry->attributes |= att_exported; if (exported_p (entry)) array_needs_making = 1; return (entry); } /* Dispose of the information attached to a variable. */ dispose_variable (var) SHELL_VAR *var; { if (!var) return; if (var->function) dispose_command (var->function); if (var->value) free (var->value); free (var->name); if (exported_p (var)) array_needs_making = 1; free (var); } /* Return 1 if VAR (a variable) is named NAME (a string). */ var_has_name (var, name) SHELL_VAR *var; char *name; { if (var) return ((strcmp (var->name, name) == 0)); else return (0); } /* Unbind the function cell of a variable. If the variable has no function cell, then discard it. Return -1 if the variable couldn't be found. */ unbind_function (name) char *name; { SHELL_VAR *var = find_variable (name); if (!var) return (-1); if (var->function) { dispose_command (var->function); var->function = (char *)NULL; } if (!var->value) makunbound (name); return (0); } /* Unbind the value cell of a variable. If the variable has no value cell, then discard it. Return -1 if the variable couldn't be found. */ unbind_variable (name) char *name; { SHELL_VAR *var = find_variable (name); if (!var) return (-1); if (var->value) { free (var->value); var->value = (char *)NULL; } if (!var->function) makunbound (name); return (0); } /* Make the variable associated with NAME go away. Return non-zero if the variable couldn't be found. */ makunbound (name) char *name; { SHELL_VAR *elt = (SHELL_VAR *)delete_element (&variable_list, var_has_name, name); if ((int)elt != -1) { if (elt->prev_context) { SHELL_VAR *new = elt->prev_context; new->next = variable_list; variable_list = new; } if (exported_p (elt)) set_var_auto_export (elt->name); dispose_variable (elt); return (0); } return (-1); } /* Remove the variable with NAME if it is a local variable in the current context. */ kill_local_variable (name) char *name; { SHELL_VAR *temp = find_variable (name); if (temp && (temp->context == variable_context)) { makunbound (name); return (0); } return (-1); } /* Get rid of all of the variables in the current context. */ kill_all_local_variables () { register SHELL_VAR *list = variable_list; register SHELL_VAR *prev = (SHELL_VAR *)NULL; while (list) { if (list->context && list->context == variable_context) { if (list->prev_context) list->prev_context->next = list->next; else list->prev_context = list->next; if (prev) { prev->next = list->prev_context; dispose_variable (list); list = prev; } else { variable_list = list->prev_context; dispose_variable (list); list = variable_list; } } prev = list; list = list->next; } } /* Do a function binding to a variable. You pass the name and the command to bind to. This conses the name and command. */ SHELL_VAR * bind_function (name, value) char *name; COMMAND *value; { SHELL_VAR *entry = find_variable (name); if (!entry) { entry = bind_variable (name, ""); free (entry->value); entry->value = (char *)NULL; } if (entry->function) dispose_command (entry->function); entry->function = (char *)copy_command (value); entry->attributes |= att_function; if (mark_modified_vars) entry->attributes |= att_exported; array_needs_making = 1; return (entry); } /* Copy VAR to a new data structure and return that structure. */ SHELL_VAR * copy_variable (var) SHELL_VAR *var; { SHELL_VAR *copy = (SHELL_VAR *)NULL; if (var) { copy = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); copy->attributes = var->attributes; copy->name = savestring (var->name); if (var->function) copy->function = (char *)copy_command (var->function); if (var->value) copy->value = savestring (var->value); else copy->value = (char *)NULL; copy->context = var->context; /* Don't bother copying previous contexts along with this variable. */ copy->prev_context = (SHELL_VAR *)NULL; } return (copy); } /* Make the variable associated with NAME be read-only. If NAME does not exist yet, create it. */ set_var_read_only (name) char *name; { SHELL_VAR *entry = find_variable (name); if (!entry) { entry = bind_variable (name, ""); if (!no_invisible_vars) entry->attributes |= att_invisible; } entry->attributes |= att_readonly; } /* Make the variable associated with NAME be auto-exported. If NAME does not exist yet, create it. */ set_var_auto_export (name) char *name; { SHELL_VAR *entry = find_variable (name); if (!entry) { entry = bind_variable (name, ""); if (!no_invisible_vars) entry->attributes |= att_invisible; } entry->attributes |= att_exported; array_needs_making = 1; } /* Returns non-zero if STRING is an assignment statement. The returned value is the index of the `=' sign. */ assignment (string) char *string; { register int c, index = 0; c = string[index]; if (!isletter (c) && c != '_') return (0); while (c = string[index]) { /* The following is safe. Note that '=' at the start of a word is not an assignment statement. */ if (c == '=') return (index); if (!isletter (c) && !digit (c) && c != '_') return (0); index++; } return (0); } /* Make an array out of LIST, a list of SHELL_VAR. Only visible variables which are marked for export are eligible. */ char ** make_var_array (list) SHELL_VAR *list; { register int count = 0; register char **array; register SHELL_VAR *head = list; int hack_it_twice = 0; while (list) { if (exported_p (list) && !invisible_p (list)) { if (list->function) count++; if (list->value) count++; } list = list->next; } array = (char **)xmalloc ((1 + count) * sizeof (char *)); list = head; count = 0; while (list) { if (exported_p (list) && !invisible_p (list)) { char *value, *named_function_string (); if (list->function && !hack_it_twice) { value = named_function_string ((char *)NULL, (COMMAND *)list->function, 0); hack_it_twice++; } else { value = list->value; hack_it_twice = 0; } if (value) { array[count] = (char *)xmalloc (2 + strlen (list->name) + strlen (value)); sprintf (array[count], "%s=%s", list->name, value); count++; } } if (!hack_it_twice) list = list->next; } array[count] = (char *)NULL; return (array); } /* Add STRING to the array of foo=bar strings that we already have to add to the environment. */ assign_in_env (string) char *string; { int size; int offset = assignment (string); char *name = savestring (string); char *temp, *value = (char *)NULL; if (name[offset] == '=') { char *tilde_expand (), *string_list (); WORD_LIST *list, *expand_string (); extern int disallow_filename_globbing; name[offset] = 0; temp = name + offset + 1; if (!disallow_filename_globbing) temp = tilde_expand (temp); else temp = savestring (temp); list = expand_string (temp, 0); value = string_list (list); if (list) free (list); free (temp); } if (!value) value = savestring (""); temp = (char *)xmalloc (2 + strlen (name) + strlen (value)); sprintf (temp, "%s=%s", name, value); free (name); if (!temporary_env) { temporary_env = (char **)xmalloc (sizeof (char *)); temporary_env [0] = (char *)NULL; } size = array_len (temporary_env); temporary_env = (char **)xrealloc (temporary_env, (size + 2) * (sizeof (char *))); temporary_env[size] = (temp); temporary_env[size + 1] = (char *)NULL; array_needs_making = 1; } /* Find a variable in the temporary environment that is named NAME. Return a consed variable, or NULL if not found. */ SHELL_VAR * find_tempenv_variable (name) char *name; { register int i, l = strlen (name); if (!temporary_env) return ((SHELL_VAR *)NULL); for (i = 0; temporary_env[i]; i++) { if (strncmp (temporary_env[i], name, l) == 0 && temporary_env[i][l] == '=') { SHELL_VAR *temp = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); temp->next = (SHELL_VAR *)NULL; temp->name = savestring (name); if (temporary_env[i][l + 1]) temp->value = savestring (&temporary_env[i][l + 1]); else temp->value = savestring (""); temp->function = (char *)NULL; temp->attributes = att_exported; temp->context = 0; temp->prev_context = (SHELL_VAR *)NULL; return (temp); } } return ((SHELL_VAR *)NULL); } /* Free the storage used in the variable array for temporary environment variables. */ dispose_used_env_vars () { if (!temporary_env) return; free_array (temporary_env); temporary_env = (char **)NULL; array_needs_making = 1; } /* Stupid comparison routine for qsort () ing strings. */ qsort_string_compare (s1, s2) register char **s1, **s2; { return (strcmp (*s1, *s2)); } /* Add ASSIGN to ARRAY, or supercede a previous assignment in the array with the same left-hand side. Return the new array. */ char ** add_or_supercede (assign, array) char *assign; register char **array; { register int i; int equal_offset = assignment (assign); if (!equal_offset) return (array); for (i = 0; array[i]; i++) { if (strncmp (assign, array[i], equal_offset + 1) == 0) { free (array[i]); array[i] = savestring (assign); return (array); } } array = (char **)xrealloc (array, (2 + array_len (array)) * sizeof (char *)); array[i++] = savestring (assign); array[i] = (char *)NULL; return (array); } /* Make the environment array for the command about to be executed. If the array needs making. Otherwise, do nothing. If a shell action could change the array that commands receive for their environment, then the code should `array_needs_making++'. */ maybe_make_export_env () { register int i; register char **temp_array; if (array_needs_making) { if (export_env) free_array (export_env); #ifdef SHADOWED_ENV export_env = (char **)xmalloc ((1 + array_len (shell_environment)) * sizeof (char *)); for (i = 0; shell_environment[i]; i++) export_env[i] = savestring (shell_environment[i]); export_env[i] = (char *)NULL; #else /* !SHADOWED_ENV */ export_env = (char **)xmalloc (sizeof (char *)); export_env[0] = (char *)NULL; #endif /* SHADOWED_ENV */ temp_array = make_var_array (variable_list); for (i = 0; temp_array[i]; i++) export_env = add_or_supercede (temp_array[i], export_env); free_array (temp_array); if (temporary_env) { for (i = 0; temporary_env[i]; i++) export_env = add_or_supercede (temporary_env[i], export_env); /* Sort the array alphabetically. */ qsort (export_env, array_len (export_env), sizeof (char *), qsort_string_compare); } array_needs_making = 0; } } /* We supply our own version of getenv () because we want library routines to get the changed values of exported variables. */ char *last_tempenv_value = (char *)NULL; char * getenv (name) char *name; { SHELL_VAR *var = find_tempenv_variable (name); if (var) { if (last_tempenv_value) free (last_tempenv_value); last_tempenv_value = savestring (value_cell (var)); dispose_variable (var); return (last_tempenv_value); } else { var = find_variable (name); if (var && exported_p (var)) return (value_cell (var)); } return ((char *)NULL); }