This file is set.def, from which is created set.c.
It implements the "set" and "unset" builtins in Bash.

Copyright (C) 1987-2021 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 3 of the License, 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.  If not, see <http://www.gnu.org/licenses/>.

$PRODUCES set.c

#include <config.h>

#if defined (HAVE_UNISTD_H)
#  ifdef _MINIX
#    include <sys/types.h>
#  endif
#  include <unistd.h>
#endif

#include <stdio.h>

#include "../bashansi.h"
#include "../bashintl.h"

#include "../shell.h"
#include "../parser.h"
#include "../flags.h"
#include "common.h"
#include "bashgetopt.h"

#if defined (READLINE)
#  include "../input.h"
#  include "../bashline.h"
#  include <readline/readline.h>
#endif

#if defined (HISTORY)
#  include "../bashhist.h"
#endif

$BUILTIN set
$FUNCTION set_builtin
$SHORT_DOC set [-abefhkmnptuvxBCEHPT] [-o option-name] [--] [-] [arg ...]
Set or unset values of shell options and positional parameters.

Change the value of shell attributes and positional parameters, or
display the names and values of shell variables.

Options:
  -a  Mark variables which are modified or created for export.
  -b  Notify of job termination immediately.
  -e  Exit immediately if a command exits with a non-zero status.
  -f  Disable file name generation (globbing).
  -h  Remember the location of commands as they are looked up.
  -k  All assignment arguments are placed in the environment for a
      command, not just those that precede the command name.
  -m  Job control is enabled.
  -n  Read commands but do not execute them.
  -o option-name
      Set the variable corresponding to option-name:
          allexport    same as -a
          braceexpand  same as -B
#if defined (READLINE)
          emacs        use an emacs-style line editing interface
#endif /* READLINE */
          errexit      same as -e
          errtrace     same as -E
          functrace    same as -T
          hashall      same as -h
#if defined (BANG_HISTORY)
          histexpand   same as -H
#endif /* BANG_HISTORY */
#if defined (HISTORY)
          history      enable command history
#endif
          ignoreeof    the shell will not exit upon reading EOF
          interactive-comments
                       allow comments to appear in interactive commands
          keyword      same as -k
#if defined (JOB_CONTROL)
          monitor      same as -m
#endif
          noclobber    same as -C
          noexec       same as -n
          noglob       same as -f
          nolog        currently accepted but ignored
#if defined (JOB_CONTROL)
          notify       same as -b
#endif
          nounset      same as -u
          onecmd       same as -t
          physical     same as -P
          pipefail     the return value of a pipeline is the status of
                       the last command to exit with a non-zero status,
                       or zero if no command exited with a non-zero status
          posix        change the behavior of bash where the default
                       operation differs from the Posix standard to
                       match the standard
          privileged   same as -p
          verbose      same as -v
#if defined (READLINE)
          vi           use a vi-style line editing interface
#endif /* READLINE */
          xtrace       same as -x
  -p  Turned on whenever the real and effective user ids do not match.
      Disables processing of the $ENV file and importing of shell
      functions.  Turning this option off causes the effective uid and
      gid to be set to the real uid and gid.
  -t  Exit after reading and executing one command.
  -u  Treat unset variables as an error when substituting.
  -v  Print shell input lines as they are read.
  -x  Print commands and their arguments as they are executed.
#if defined (BRACE_EXPANSION)
  -B  the shell will perform brace expansion
#endif /* BRACE_EXPANSION */
  -C  If set, disallow existing regular files to be overwritten
      by redirection of output.
  -E  If set, the ERR trap is inherited by shell functions.
#if defined (BANG_HISTORY)
  -H  Enable ! style history substitution.  This flag is on
      by default when the shell is interactive.
#endif /* BANG_HISTORY */
  -P  If set, do not resolve symbolic links when executing commands
      such as cd which change the current directory.
  -T  If set, the DEBUG and RETURN traps are inherited by shell functions.
  --  Assign any remaining arguments to the positional parameters.
      If there are no remaining arguments, the positional parameters
      are unset.
  -   Assign any remaining arguments to the positional parameters.
      The -x and -v options are turned off.

Using + rather than - causes these flags to be turned off.  The
flags can also be used upon invocation of the shell.  The current
set of flags may be found in $-.  The remaining n ARGs are positional
parameters and are assigned, in order, to $1, $2, .. $n.  If no
ARGs are given, all shell variables are printed.

Exit Status:
Returns success unless an invalid option is given.
$END

typedef int setopt_set_func_t PARAMS((int, char *));
typedef int setopt_get_func_t PARAMS((char *));

static int find_minus_o_option PARAMS((char *));

static void print_minus_o_option PARAMS((char *, int, int));
static void print_all_shell_variables PARAMS((void));

static int set_ignoreeof PARAMS((int, char *));
static int set_posix_mode PARAMS((int, char *));

#if defined (READLINE)
static int set_edit_mode PARAMS((int, char *));
static int get_edit_mode PARAMS((char *));
#endif

#if defined (HISTORY)
static int bash_set_history PARAMS((int, char *));
#endif

static const char * const on = "on";
static const char * const off = "off";

static int previous_option_value;

/* A struct used to match long options for set -o to the corresponding
   option letter or internal variable.  The functions can be called to
   dynamically generate values.  If you add a new variable name here
   that doesn't have a corresponding single-character option letter, make
   sure to set the value appropriately in reset_shell_options. */
const struct {
  char *name;
  int letter;
  int *variable;
  setopt_set_func_t *set_func;
  setopt_get_func_t *get_func;
} o_options[] = {
  { "allexport",  'a', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
#if defined (BRACE_EXPANSION)
  { "braceexpand",'B', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
#endif
#if defined (READLINE)
  { "emacs",     '\0', (int *)NULL, set_edit_mode, get_edit_mode },
#endif
  { "errexit",	  'e', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
  { "errtrace",	  'E', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
  { "functrace",  'T', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
  { "hashall",    'h', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
#if defined (BANG_HISTORY)
  { "histexpand", 'H', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
#endif /* BANG_HISTORY */
#if defined (HISTORY)
  { "history",   '\0', &enable_history_list, bash_set_history, (setopt_get_func_t *)NULL },
#endif
  { "ignoreeof", '\0', &ignoreeof, set_ignoreeof, (setopt_get_func_t *)NULL },
  { "interactive-comments", '\0', &interactive_comments, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
  { "keyword",    'k', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
#if defined (JOB_CONTROL)
  { "monitor",	  'm', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
#endif
  { "noclobber",  'C', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
  { "noexec",	  'n', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
  { "noglob",	  'f', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
#if defined (HISTORY)
  { "nolog",     '\0', &dont_save_function_defs, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
#endif
#if defined (JOB_CONTROL)
  { "notify",	  'b', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
#endif /* JOB_CONTROL */
  { "nounset",	  'u', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
  { "onecmd",	  't', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
  { "physical",   'P', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
  { "pipefail",  '\0', &pipefail_opt, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
  { "posix",     '\0', &posixly_correct, set_posix_mode, (setopt_get_func_t *)NULL },
  { "privileged", 'p', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
  { "verbose",	  'v', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
#if defined (READLINE)
  { "vi",        '\0', (int *)NULL, set_edit_mode, get_edit_mode },
#endif
  { "xtrace",	  'x', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL  },
  {(char *)NULL, 0 , (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
};

#define N_O_OPTIONS	(sizeof (o_options) / sizeof (o_options[0]))

#define GET_BINARY_O_OPTION_VALUE(i, name) \
  ((o_options[i].get_func) ? (*o_options[i].get_func) (name) \
			   : (*o_options[i].variable))

#define SET_BINARY_O_OPTION_VALUE(i, onoff, name) \
  ((o_options[i].set_func) ? (*o_options[i].set_func) (onoff, name) \
			   : (*o_options[i].variable = (onoff == FLAG_ON)))

static int
find_minus_o_option (name)
     char *name;
{
  register int i;

  for (i = 0; o_options[i].name; i++)
    if (STREQ (name, o_options[i].name))
      return i;
  return -1;
}

int
minus_o_option_value (name)
     char *name;
{
  register int	i;
  int *on_or_off;

  i = find_minus_o_option (name);
  if (i < 0)
    return (-1);

  if (o_options[i].letter)
    {
      on_or_off = find_flag (o_options[i].letter);
      return ((on_or_off == FLAG_UNKNOWN) ? -1 : *on_or_off);
    }
  else
    return (GET_BINARY_O_OPTION_VALUE (i, name));
}

#define MINUS_O_FORMAT "%-15s\t%s\n"

static void
print_minus_o_option (name, value, pflag)
     char *name;
     int value, pflag;
{
  if (pflag == 0)
    printf (MINUS_O_FORMAT, name, value ? on : off);
  else
    printf ("set %co %s\n", value ? '-' : '+', name);
}

void
list_minus_o_opts (mode, reusable)
     int mode, reusable;
{
  register int	i;
  int *on_or_off, value;

  for (i = 0; o_options[i].name; i++)
    {
      if (o_options[i].letter)
	{
	  value = 0;
	  on_or_off = find_flag (o_options[i].letter);
	  if (on_or_off == FLAG_UNKNOWN)
	    on_or_off = &value;
	  if (mode == -1 || mode == *on_or_off)
	    print_minus_o_option (o_options[i].name, *on_or_off, reusable);
	}
      else
	{
	  value = GET_BINARY_O_OPTION_VALUE (i, o_options[i].name);
	  if (mode == -1 || mode == value)
	    print_minus_o_option (o_options[i].name, value, reusable);
	}
    }
}

char **
get_minus_o_opts ()
{
  char **ret;
  int i;

  ret = strvec_create (N_O_OPTIONS + 1);
  for (i = 0; o_options[i].name; i++)
    ret[i] = o_options[i].name;
  ret[i] = (char *)NULL;
  return ret;
}

char *
get_current_options ()
{
  char *temp;
  int i, posixopts;

  posixopts = num_posix_options ();	/* shopts modified by posix mode */
  /* Make the buffer big enough to hold the set -o options and the shopt
     options modified by posix mode. */
  temp = (char *)xmalloc (1 + N_O_OPTIONS + posixopts);
  for (i = 0; o_options[i].name; i++)
    {
      if (o_options[i].letter)
	temp[i] = *(find_flag (o_options[i].letter));
      else
	temp[i] = GET_BINARY_O_OPTION_VALUE (i, o_options[i].name);
    }

  /* Add the shell options that are modified by posix mode to the end of the
     bitmap. They will be handled in set_current_options() */
  get_posix_options (temp+i);
  temp[i+posixopts] = '\0';
  return (temp);
}

void
set_current_options (bitmap)
     const char *bitmap;
{
  int i, v, cv, *on_or_off;

  if (bitmap == 0)
    return;

  for (i = 0; o_options[i].name; i++)
    {
      v = bitmap[i] ? FLAG_ON : FLAG_OFF;
      if (o_options[i].letter)
	{
	  /* We should not get FLAG_UNKNOWN here */
	  on_or_off = find_flag (o_options[i].letter);
	  cv = *on_or_off ? FLAG_ON : FLAG_OFF;
	  if (v != cv)
	    change_flag (o_options[i].letter, v);
	}
      else
	{
	  cv = GET_BINARY_O_OPTION_VALUE (i, o_options[i].name);
	  cv = cv ? FLAG_ON : FLAG_OFF;
	  if (v != cv)
	    SET_BINARY_O_OPTION_VALUE (i, v, o_options[i].name);
	}
    }

  /* Now reset the variables changed by posix mode */
  set_posix_options (bitmap+i);
}

static int
set_ignoreeof (on_or_off, option_name)
     int on_or_off;
     char *option_name;
{
  ignoreeof = on_or_off == FLAG_ON;
  unbind_variable_noref ("ignoreeof");
  if (ignoreeof)
    bind_variable ("IGNOREEOF", "10", 0); 
  else
    unbind_variable_noref ("IGNOREEOF");
  sv_ignoreeof ("IGNOREEOF");
  return 0;
}

static int
set_posix_mode (on_or_off, option_name)
     int on_or_off;
     char *option_name;
{
  /* short-circuit on no-op */
  if ((on_or_off == FLAG_ON && posixly_correct) ||
      (on_or_off == FLAG_OFF && posixly_correct == 0))
    return 0;

  posixly_correct = on_or_off == FLAG_ON;
  if (posixly_correct == 0)
    unbind_variable_noref ("POSIXLY_CORRECT");
  else
    bind_variable ("POSIXLY_CORRECT", "y", 0);
  sv_strict_posix ("POSIXLY_CORRECT");
  return (0);
}

#if defined (READLINE)
/* Magic.  This code `knows' how readline handles rl_editing_mode. */
static int
set_edit_mode (on_or_off, option_name)
     int on_or_off;
     char *option_name;
{
  int isemacs;

  if (on_or_off == FLAG_ON)
    {
      rl_variable_bind ("editing-mode", option_name);

      if (interactive)
	with_input_from_stdin ();
      no_line_editing = 0;
    }
  else
    {
      isemacs = rl_editing_mode == 1;
      if ((isemacs && *option_name == 'e') || (!isemacs && *option_name == 'v'))
	{
	  if (interactive)
	    with_input_from_stream (stdin, "stdin");
	  no_line_editing = 1;
	}
    }
  return 1-no_line_editing;
}

static int
get_edit_mode (name)
     char *name;
{
  return (*name == 'e' ? no_line_editing == 0 && rl_editing_mode == 1
		       : no_line_editing == 0 && rl_editing_mode == 0);
}
#endif /* READLINE */

#if defined (HISTORY)
static int
bash_set_history (on_or_off, option_name)
     int on_or_off;
     char *option_name;
{
  if (on_or_off == FLAG_ON)
    {
      enable_history_list = 1;
      bash_history_enable ();
      if (history_lines_this_session == 0)
	load_history ();
    }
  else
    {
      enable_history_list = 0;
      bash_history_disable ();
    }
  return (1 - enable_history_list);
}
#endif

int
set_minus_o_option (on_or_off, option_name)
     int on_or_off;
     char *option_name;
{
  register int i;

  i = find_minus_o_option (option_name);
  if (i < 0)
    {
      sh_invalidoptname (option_name);
      return (EX_USAGE);
    }

  if (o_options[i].letter == 0)
    {
      previous_option_value = GET_BINARY_O_OPTION_VALUE (i, o_options[i].name);
      SET_BINARY_O_OPTION_VALUE (i, on_or_off, option_name);
      return (EXECUTION_SUCCESS);
    }
  else
    {
      if ((previous_option_value = change_flag (o_options[i].letter, on_or_off)) == FLAG_ERROR)
	{
	  sh_invalidoptname (option_name);
	  return (EXECUTION_FAILURE);
	}
      else
	return (EXECUTION_SUCCESS);
    }
}

static void
print_all_shell_variables ()
{
  SHELL_VAR **vars;

  vars = all_shell_variables ();
  if (vars)
    {
      print_var_list (vars);
      free (vars);
    }

  /* POSIX.2 does not allow function names and definitions to be output when
     `set' is invoked without options (PASC Interp #202). */
  if (posixly_correct == 0)
    {
      vars = all_shell_functions ();
      if (vars)
	{
	  print_func_list (vars);
	  free (vars);
	}
    }
}

void
set_shellopts ()
{
  char *value;
  char tflag[N_O_OPTIONS];
  int vsize, i, vptr, *ip, exported;
  SHELL_VAR *v;

  for (vsize = i = 0; o_options[i].name; i++)
    {
      tflag[i] = 0;
      if (o_options[i].letter)
	{
	  ip = find_flag (o_options[i].letter);
	  if (ip && *ip)
	    {
	      vsize += strlen (o_options[i].name) + 1;
	      tflag[i] = 1;
	    }
	}
      else if (GET_BINARY_O_OPTION_VALUE (i, o_options[i].name))
	{
	  vsize += strlen (o_options[i].name) + 1;
	  tflag[i] = 1;
	}
    }

  value = (char *)xmalloc (vsize + 1);

  for (i = vptr = 0; o_options[i].name; i++)
    {
      if (tflag[i])
	{
	  strcpy (value + vptr, o_options[i].name);
	  vptr += strlen (o_options[i].name);
	  value[vptr++] = ':';
	}
    }

  if (vptr)
    vptr--;			/* cut off trailing colon */
  value[vptr] = '\0';

  v = find_variable ("SHELLOPTS");

  /* Turn off the read-only attribute so we can bind the new value, and
     note whether or not the variable was exported. */
  if (v)
    {
      VUNSETATTR (v, att_readonly);
      exported = exported_p (v);
    }
  else
    exported = 0;

  v = bind_variable ("SHELLOPTS", value, 0);

  /* Turn the read-only attribute back on, and turn off the export attribute
     if it was set implicitly by mark_modified_vars and SHELLOPTS was not
     exported before we bound the new value. */
  VSETATTR (v, att_readonly);
  if (mark_modified_vars && exported == 0 && exported_p (v))
    VUNSETATTR (v, att_exported);

  free (value);
}

void
parse_shellopts (value)
     char *value;
{
  char *vname;
  int vptr;

  vptr = 0;
  while (vname = extract_colon_unit (value, &vptr))
    {
      set_minus_o_option (FLAG_ON, vname);
      free (vname);
    }
}

void
initialize_shell_options (no_shellopts)
     int no_shellopts;
{
  char *temp;
  SHELL_VAR *var;

  if (no_shellopts == 0)
    {
      var = find_variable ("SHELLOPTS");
      /* set up any shell options we may have inherited. */
      if (var && imported_p (var))
	{
	  temp = (array_p (var) || assoc_p (var)) ? (char *)NULL : savestring (value_cell (var));
	  if (temp)
	    {
	      parse_shellopts (temp);
	      free (temp);
	    }
	}
    }

  /* Set up the $SHELLOPTS variable. */
  set_shellopts ();
}

/* Reset the values of the -o options that are not also shell flags.  This is
   called from execute_cmd.c:initialize_subshell() when setting up a subshell
   to run an executable shell script without a leading `#!'. */
void
reset_shell_options ()
{
  pipefail_opt = 0;
  ignoreeof = 0;

#if defined (STRICT_POSIX)
  posixly_correct = 1;
#else
  posixly_correct = 0;
#endif
#if defined (HISTORY)
  dont_save_function_defs = 0;
  remember_on_history = enable_history_list = 1;	/* XXX */
#endif
}

/* Set some flags from the word values in the input list.  If LIST is empty,
   then print out the values of the variables instead.  If LIST contains
   non-flags, then set $1 - $9 to the successive words of LIST. */
int
set_builtin (list)
     WORD_LIST *list;
{
  int on_or_off, flag_name, force_assignment, opts_changed, rv, r;
  register char *arg;
  char s[3];

  if (list == 0)
    {
      print_all_shell_variables ();
      return (sh_chkwrite (EXECUTION_SUCCESS));
    }

  /* Check validity of flag arguments. */
  rv = EXECUTION_SUCCESS;
  reset_internal_getopt ();
  while ((flag_name = internal_getopt (list, optflags)) != -1)
    {
      switch (flag_name)
	{
	  case 'i':	/* don't allow set -i */
	    s[0] = list_opttype;
	    s[1] = 'i';
	    s[2] = '\0';
	    sh_invalidopt (s);
	    builtin_usage ();
	    return (EX_USAGE);
	  CASE_HELPOPT;
	  case '?':
	    builtin_usage ();
	    return (list_optopt == '?' ? EXECUTION_SUCCESS : EX_USAGE);
	  default:
	    break;
	}
    }
    
  /* Do the set command.  While the list consists of words starting with
     '-' or '+' treat them as flags, otherwise, start assigning them to
     $1 ... $n. */
  for (force_assignment = opts_changed = 0; list; )
    {
      arg = list->word->word;

      /* If the argument is `--' or `-' then signal the end of the list
	 and remember the remaining arguments. */
      if (arg[0] == '-' && (!arg[1] || (arg[1] == '-' && !arg[2])))
	{
	  list = list->next;

	  /* `set --' unsets the positional parameters. */
	  if (arg[1] == '-')
	    force_assignment = 1;

	  /* Until told differently, the old shell behaviour of
	     `set - [arg ...]' being equivalent to `set +xv [arg ...]'
	     stands.  Posix.2 says the behaviour is marked as obsolescent. */
	  else
	    {
	      change_flag ('x', '+');
	      change_flag ('v', '+');
	      opts_changed = 1;
	    }

	  break;
	}

      if ((on_or_off = *arg) && (on_or_off == '-' || on_or_off == '+'))
	{
	  while (flag_name = *++arg)
	    {
	      if (flag_name == '?')
		{
		  builtin_usage ();
		  return (EXECUTION_SUCCESS);
		}
	      else if (flag_name == 'o') /* -+o option-name */
		{
		  char *option_name;
		  WORD_LIST *opt;

		  opt = list->next;

		  if (opt == 0)
		    {
		      list_minus_o_opts (-1, (on_or_off == '+'));
		      rv = sh_chkwrite (rv);
		      continue;
		    }

		  option_name = opt->word->word;

		  if (option_name == 0 || *option_name == '\0' ||
		      *option_name == '-' || *option_name == '+')
		    {
		      list_minus_o_opts (-1, (on_or_off == '+'));
		      continue;
		    }
		  list = list->next; /* Skip over option name. */

		  opts_changed = 1;
		  if ((r = set_minus_o_option (on_or_off, option_name)) != EXECUTION_SUCCESS)
		    {
		      set_shellopts ();
		      return (r);
		    }
		}
	      else if (change_flag (flag_name, on_or_off) == FLAG_ERROR)
		{
		  s[0] = on_or_off;
		  s[1] = flag_name;
		  s[2] = '\0';
		  sh_invalidopt (s);
		  builtin_usage ();
		  set_shellopts ();
		  return (EXECUTION_FAILURE);
		}
	      opts_changed = 1;
	    }
	}
      else
	{
	  break;
	}
      list = list->next;
    }

  /* Assigning $1 ... $n */
  if (list || force_assignment)
    remember_args (list, 1);
  /* Set up new value of $SHELLOPTS */
  if (opts_changed)
    set_shellopts ();
  return (rv);
}

$BUILTIN unset
$FUNCTION unset_builtin
$SHORT_DOC unset [-f] [-v] [-n] [name ...]
Unset values and attributes of shell variables and functions.

For each NAME, remove the corresponding variable or function.

Options:
  -f	treat each NAME as a shell function
  -v	treat each NAME as a shell variable
  -n	treat each NAME as a name reference and unset the variable itself
		rather than the variable it references

Without options, unset first tries to unset a variable, and if that fails,
tries to unset a function.

Some variables cannot be unset; also see `readonly'.

Exit Status:
Returns success unless an invalid option is given or a NAME is read-only.
$END

#define NEXT_VARIABLE()	any_failed++; list = list->next; continue;

int
unset_builtin (list)
  WORD_LIST *list;
{
  int unset_function, unset_variable, unset_array, opt, nameref, any_failed;
  int global_unset_func, global_unset_var, vflags, base_vflags, valid_id;
  char *name, *tname;

  unset_function = unset_variable = unset_array = nameref = any_failed = 0;
  global_unset_func = global_unset_var = 0;

  reset_internal_getopt ();
  while ((opt = internal_getopt (list, "fnv")) != -1)
    {
      switch (opt)
	{
	case 'f':
	  global_unset_func = 1;
	  break;
	case 'v':
	  global_unset_var = 1;
	  break;
	case 'n':
	  nameref = 1;
	  break;
	CASE_HELPOPT;
	default:
	  builtin_usage ();
	  return (EX_USAGE);
	}
    }

  list = loptend;

  if (global_unset_func && global_unset_var)
    {
      builtin_error (_("cannot simultaneously unset a function and a variable"));
      return (EXECUTION_FAILURE);
    }
  else if (unset_function && nameref)
    nameref = 0;

#if defined (ARRAY_VARS)
  base_vflags = assoc_expand_once ? VA_NOEXPAND : 0;
#endif

  while (list)
    {
      SHELL_VAR *var;
      int tem;
#if defined (ARRAY_VARS)
      char *t;
#endif

      name = list->word->word;

      unset_function = global_unset_func;
      unset_variable = global_unset_var;

#if defined (ARRAY_VARS)
      vflags = builtin_arrayref_flags (list->word, base_vflags);
#endif

#if defined (ARRAY_VARS)
      unset_array = 0;
      /* XXX valid array reference second arg was 0 */
      if (!unset_function && nameref == 0 && tokenize_array_reference (name, vflags, &t))
	unset_array = 1;
#endif
      /* Get error checking out of the way first.  The low-level functions
	 just perform the unset, relying on the caller to verify. */
      valid_id = legal_identifier (name);

      /* Whether or not we are in posix mode, if neither -f nor -v appears,
	 skip over trying to unset variables with invalid names and just
	 treat them as potential shell function names. */
      if (global_unset_func == 0 && global_unset_var == 0 && valid_id == 0)
	{
	  unset_variable = unset_array = 0;
	  unset_function = 1;
	}

      /* Bash allows functions with names which are not valid identifiers
	 to be created when not in posix mode, so check only when in posix
	 mode when unsetting a function. */
      if (unset_function == 0 && valid_id == 0)
	{
	  sh_invalidid (name);
	  NEXT_VARIABLE ();
	}

      /* Search for functions here if -f supplied or if NAME cannot be a
	 variable name. */
      var = unset_function ? find_function (name)
			   : (nameref ? find_variable_last_nameref (name, 0) : find_variable (name));

      /* Some variables (but not functions yet) cannot be unset, period. */
      if (var && unset_function == 0 && non_unsettable_p (var))
	{
	  builtin_error (_("%s: cannot unset"), name);
	  NEXT_VARIABLE ();
	}

      /* if we have a nameref we want to use it */
      if (var && unset_function == 0 && nameref == 0 && STREQ (name, name_cell(var)) == 0)
	name = name_cell (var);

      /* Posix.2 says try variables first, then functions.  If we would
	 find a function after unsuccessfully searching for a variable,
	 note that we're acting on a function now as if -f were
	 supplied.  The readonly check below takes care of it. */
      if (var == 0 && nameref == 0 &&  unset_variable == 0 && unset_function == 0)
	{
	  if (var = find_function (name))
	    unset_function = 1;
	}

      /* Posix.2 says that unsetting readonly variables is an error. */
      if (var && readonly_p (var))
	{
	  builtin_error (_("%s: cannot unset: readonly %s"),
			 var->name, unset_function ? "function" : "variable");
	  NEXT_VARIABLE ();
	}

      /* Unless the -f option is supplied, the name refers to a variable. */
#if defined (ARRAY_VARS)
      if (var && unset_array)
	{
	  if (shell_compatibility_level <= 51)
	    vflags |= VA_ALLOWALL;

	  /* Let unbind_array_element decide what to do with non-array vars */
	  tem = unbind_array_element (var, t, vflags);	/* XXX new third arg */
	  if (tem == -2 && array_p (var) == 0 && assoc_p (var) == 0)
	    {
	      builtin_error (_("%s: not an array variable"), var->name);
	      NEXT_VARIABLE ();
	    }
	  else if (tem < 0)
	    any_failed++;
	}
      else
#endif /* ARRAY_VARS */
      /* If we're trying to unset a nameref variable whose value isn't a set
	 variable, make sure we still try to unset the nameref's value */
      if (var == 0 && nameref == 0 && unset_function == 0)
	{
	  var = find_variable_last_nameref (name, 0);
	  if (var && nameref_p (var))
	    {
#if defined (ARRAY_VARS)
	      if (valid_array_reference (nameref_cell (var), 0))
		{
		  int len;

		  tname = savestring (nameref_cell (var));
		  if (var = array_variable_part (tname, 0, &t, &len))
		    {
		      /* change to what unbind_array_element now expects */
		      if (t[len - 1] == ']')
			t[len - 1] = 0;
		      tem = unbind_array_element (var, t, vflags);	/* XXX new third arg */
		    }
		  free (tname);
		}
	      else
#endif
		tem = unbind_variable (nameref_cell (var));
	    }
	  else
	    tem = unbind_variable (name);
	}
      else
	tem = unset_function ? unbind_func (name) : (nameref ? unbind_nameref (name) : unbind_variable (name));

      /* This is what Posix.2 says:  ``If neither -f nor -v
	 is specified, the name refers to a variable; if a variable by
	 that name does not exist, a function by that name, if any,
	 shall be unset.'' */
      if (tem == -1 && nameref == 0 && unset_function == 0 && unset_variable == 0)
	tem = unbind_func (name);

      name = list->word->word;		/* reset above for namerefs */

      /* SUSv3, POSIX.1-2001 say:  ``Unsetting a variable or function that
	 was not previously set shall not be considered an error.'' */

      if (unset_function == 0)
	stupidly_hack_special_variables (name);

      list = list->next;
    }

  return (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
}
